From 48f0db2017b8c2e77c0014698b029d1bc06ef2d9 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Mon, 19 Oct 2026 13:17:07 -0400 Subject: [PATCH 1/2] Add RBS for gli 2.22 Types GLI::App, the DSL it shares with GLI::Command (desc, arg, flag, switch, command and friends), the command objects yielded to command blocks, the action/pre/post/around/on_error block parameters, and the exception classes apps rescue or raise. GLI::Options subclasses OpenStruct, but RBS 4 has no OpenStruct signatures (declaring ostruct as a dependency fails with "Cannot find type definitions for library: ostruct"), so it is declared without a superclass and with the OpenStruct methods it relies on, as gems/config does for Config::Options. Co-Authored-By: Claude Opus 5.5 (1M context) Claude-Session: https://claude.ai/code/session_01Wo4FerFK9aaxrGrmVE9ncZ --- gems/gli/.rubocop.yml | 19 ++ gems/gli/2.22/_test/metadata.yaml | 4 + gems/gli/2.22/_test/test.rb | 91 ++++++ gems/gli/2.22/_test/test.rbs | 3 + gems/gli/2.22/gli.rbs | 487 ++++++++++++++++++++++++++++++ gems/gli/_reviewers.yaml | 2 + 6 files changed, 606 insertions(+) create mode 100644 gems/gli/.rubocop.yml create mode 100644 gems/gli/2.22/_test/metadata.yaml create mode 100644 gems/gli/2.22/_test/test.rb create mode 100644 gems/gli/2.22/_test/test.rbs create mode 100644 gems/gli/2.22/gli.rbs create mode 100644 gems/gli/_reviewers.yaml diff --git a/gems/gli/.rubocop.yml b/gems/gli/.rubocop.yml new file mode 100644 index 000000000..7d0be8ff2 --- /dev/null +++ b/gems/gli/.rubocop.yml @@ -0,0 +1,19 @@ +# This configuration inherits from /.rubocop.yml. +# You can configure RBS style of this gem. +# This file is used on CI. It is configured to automatically +# make rubocop suggestions on pull requests for this gem. +# If you do not like the style enforcement, you should remove this file. +inherit_from: ../../.rubocop.yml + +## +# If you want to customize the style, please consult with the gem reviewers. +# You can see the list of cops at https://github.com/ksss/rubocop-on-rbs/blob/main/docs/modules/ROOT/pages/cops.adoc + +RBS/Layout: + Enabled: true + +RBS/Lint: + Enabled: true + +RBS/Style: + Enabled: true diff --git a/gems/gli/2.22/_test/metadata.yaml b/gems/gli/2.22/_test/metadata.yaml new file mode 100644 index 000000000..29015bc02 --- /dev/null +++ b/gems/gli/2.22/_test/metadata.yaml @@ -0,0 +1,4 @@ +# If the test needs gem's RBS files, declare them here. +# +# additional_gems: +# - GEM_NAME diff --git a/gems/gli/2.22/_test/test.rb b/gems/gli/2.22/_test/test.rb new file mode 100644 index 000000000..92590accf --- /dev/null +++ b/gems/gli/2.22/_test/test.rb @@ -0,0 +1,91 @@ +require "gli" + +class TodoApp + extend GLI::App + + program_desc "Manage a todo list" + program_long_desc "A longer description of the todo list" + version "1.0.0" + subcommand_option_handling :normal + arguments :strict + sort_help :manually + wrap_help_text :verbatim + synopsis_format :compact + hide_commands_without_desc true + preserve_argv + autocomplete_commands false + + accept(Integer) { |value| value.to_i } + + desc "Be verbose" + switch [:v, :verbose], negatable: false + + desc "Location of the list" + default_value "~/.todo" + arg_name "path" + flag [:f, :file] + + flag :format, desc: "Output format", must_match: ["json", "text"], default_value: "text" + + desc "Add a task" + arg "task_name" + arg "priority", :optional + command :add do |c| + c.desc "Due date" + c.flag :due, type: String, required: true + c.switch :urgent, desc: "Mark as urgent" + c.example "todo add 'Buy milk'", desc: "Add a task" + + c.action do |global_options, options, args| + args.first&.upcase + options.fetch(:due) + global_options.key?(:verbose) + end + end + + desc "List tasks" + command [:list, :ls] do |c| + c.command :done do |sub| + sub.action do |_global_options, _options, args| + args.size + end + end + c.default_command :done + end + + command :all => [:add, :list] + + default_command :list + + pre do |global_options, command, _options, _args| + command.name.to_s + global_options.key?(:file) + end + + post do |_global_options, command, _options, _args| + command.names_and_aliases.size + end + + around do |_global_options, _command, _options, _args, code| + code.call + end + + on_error do |exception| + !exception.message.empty? + end +end + +exit_code = TodoApp.run(["add", "Buy milk"]) +exit_code + 1 +TodoApp.commands.fetch(:add).description&.upcase +TodoApp.flags.each_value { |flag| flag.argument_name.upcase } +TodoApp.switches.each_value { |switch| switch.negatable? } + +begin + TodoApp.exit_now!("bad input", 2) +rescue GLI::CustomExit => e + e.exit_code + 1 +end + +GLI::Commands::Help.skips_pre = false +GLI::VERSION.split(".") diff --git a/gems/gli/2.22/_test/test.rbs b/gems/gli/2.22/_test/test.rbs new file mode 100644 index 000000000..66cafd170 --- /dev/null +++ b/gems/gli/2.22/_test/test.rbs @@ -0,0 +1,3 @@ +class TodoApp + extend GLI::App +end diff --git a/gems/gli/2.22/gli.rbs b/gems/gli/2.22/gli.rbs new file mode 100644 index 000000000..7d87ae286 --- /dev/null +++ b/gems/gli/2.22/gli.rbs @@ -0,0 +1,487 @@ +module GLI + VERSION: String + + include App + + # Prints a warning that GLI 2 no longer supports this entry point. + def self.run: (*untyped args) -> Integer + + type options_hash = Hash[Symbol | String, untyped] + + type action_block = ^(options_hash global_options, options_hash options, Array[String] arguments) -> void + + type name = Symbol | String + + type names = name | Array[name] + + type argument_option = :optional | :multiple + + # Methods for declaring flags, switches, descriptions and commands. + # Shared by GLI::App and GLI::Command. + module DSL + def desc: (String description) -> String + + alias d desc + + def long_desc: (String long_desc) -> String + + def arg_name: (String name, ?argument_option | Array[argument_option] options) -> void + + def arg: (name name, ?argument_option | Array[argument_option] options) -> Array[Argument] + + def default_value: (untyped val) -> untyped + + def flag: (*names names, + ?desc: String?, + ?long_desc: String?, + ?default_value: untyped, + ?arg_name: String?, + ?must_match: Regexp | Array[String] | Hash[String, untyped], + ?type: Module, + ?mask: bool, + ?required: bool, + ?multiple: bool) -> Flag + + alias f flag + + def switch: (*names names, + ?desc: String?, + ?long_desc: String?, + ?default_value: bool?, + ?negatable: bool) -> Switch + + alias s switch + + def clear_nexts: () -> void + + def command: (*names names) { (Command command) -> void } -> Command + | (Hash[name, Array[name]] compound) -> Commands::CompoundCommand + + alias c command + + def command_missing: () { (Symbol command_name, options_hash global_options) -> Command? } -> void + + def flags_declaration_order: () -> Array[Flag] + + def switches_declaration_order: () -> Array[Switch] + end + + # Behavior of a running app, mixed into GLI::App. + module AppSupport + def error_device=: (IO e) -> IO + + def context_description: () -> String + + def reset: () -> void + + def exe_name: () -> String + + def commands_declaration_order: () -> Array[Command] + + def version_string: () -> String? + + def get_default_command: () -> Symbol + + # Runs the app and returns the exit code for the process. + def run: (Array[String] args) -> Integer + + def config_file_name: () -> String? + + def accepts: () -> Hash[Module, ^(String) -> untyped] + + def parse_config: () -> Hash[String, untyped] + + def stderr: () -> IO + + def flags: () -> Hash[Symbol, Flag] + + def switches: () -> Hash[Symbol, Switch] + + def commands: () -> Hash[Symbol, Command] + + def pre_block: () -> ^(options_hash global_options, Command command, options_hash options, Array[String] arguments) -> boolish + + def post_block: () -> ^(options_hash global_options, Command command, options_hash options, Array[String] arguments) -> void + + def around_blocks: () -> Array[^(options_hash global_options, Command command, options_hash options, Array[String] arguments, ^() -> untyped code) -> untyped] + + def help_sort_type: () -> (:alpha | :manually) + + def help_text_wrap_type: () -> (:to_terminal | :verbatim | :one_line | :tty_only) + + def synopsis_format_type: () -> (:full | :terminal | :compact) + + def argument_handling_strategy: () -> (:loose | :strict) + + def subcommand_option_handling_strategy: () -> (:legacy | :normal) + + def autocomplete: () -> bool + end + + # Extend a class or module with GLI::App to declare a command-line app. + # + # class MyApp + # extend GLI::App + # + # program_desc 'Describe your app' + # command :hello do |c| + # c.action { |global_options, options, args| puts 'hello' } + # end + # end + # exit MyApp.run(ARGV) + module App + include DSL + + include AppSupport + + def commands_from: (String path) -> void + + def program_desc: (?String? description) -> String? + + def program_long_desc: (?String? description) -> String? + + def hide_commands_without_desc: (?bool? hide) -> bool + + def skips_pre: () -> true + + def skips_post: () -> true + + def skips_around: () -> true + + def config_file: (String filename) -> String + + def pre: () { (options_hash global_options, Command command, options_hash options, Array[String] arguments) -> boolish } -> void + + def post: () { (options_hash global_options, Command command, options_hash options, Array[String] arguments) -> void } -> void + + def around: () { (options_hash global_options, Command command, options_hash options, Array[String] arguments, ^() -> untyped code) -> untyped } -> void + + # The block returns false to skip GLI's own error message. + def on_error: () { (Exception exception) -> boolish } -> void + + def version: (String version) -> Switch + + def preserve_argv: (?bool preserve) -> bool + + def use_openstruct: (bool use_openstruct) -> bool + + def accept: (Module object) { (String value) -> untyped } -> void + + def exit_now!: (String message, ?Integer exit_code) -> bot + + def help_now!: (?String? message) -> bot + + def sort_help: (:alpha | :manually sort_type) -> (:alpha | :manually) + + def wrap_help_text: (:to_terminal | :verbatim | :one_line | :tty_only wrap_type) -> (:to_terminal | :verbatim | :one_line | :tty_only) + + def synopsis_format: (:full | :terminal | :compact format) -> (:full | :terminal | :compact) + + # Deprecated; prints a warning. + def program_name: (?untyped override) -> nil + + def default_command: (name command) -> Symbol + + def subcommand_option_handling: (:legacy | :normal handling_strategy) -> (:legacy | :normal) + + def arguments: (:loose | :strict handling_strategy) -> (:loose | :strict) + + def autocomplete_commands: (bool boolean) -> bool + end + + class CommandLineToken + attr_reader name: Symbol + + attr_reader aliases: Array[Symbol]? + + attr_reader description: String? + + attr_reader long_description: String? + + def initialize: (names names, String? description, ?String? long_description) -> void + + def <=>: (CommandLineToken other) -> Integer? + + def names_and_aliases: () -> Array[String] + end + + class CommandLineOption < CommandLineToken + attr_accessor default_value: untyped + + attr_accessor associated_command: Command? + + def initialize: (names names, ?Hash[Symbol, untyped] options) -> void + + def self.name_as_string: (name name, ?bool negatable) -> String + end + + class Flag < CommandLineOption + attr_reader must_match: Regexp | Array[String] | Hash[String, untyped] | nil + + attr_reader type: Module? + + attr_reader argument_name: String + + def initialize: (names names, Hash[Symbol, untyped] options) -> void + + def required?: () -> bool? + + def multiple?: () -> bool? + + def safe_default_value: () -> untyped + + def arguments_for_option_parser: () -> Array[untyped] + + def all_forms: (?String joiner) -> String + end + + class Switch < CommandLineOption + attr_reader negatable: bool + + def initialize: (names names, ?Hash[Symbol, untyped] options) -> void + + def arguments_for_option_parser: () -> Array[String] + + def negatable?: () -> bool + + def required?: () -> false + end + + class Argument + attr_reader name: name + + attr_reader options: Array[argument_option] + + def initialize: (name name, ?Array[argument_option] options) -> void + + def optional?: () -> bool + + def multiple?: () -> bool + end + + # Methods of GLI::Command that its subclasses share. + module CommandSupport + attr_accessor parent: untyped + + def context_description: () -> String + + def nodoc: () -> boolish + + def arguments_description: () -> String + + def arguments_options: () -> Array[argument_option] + + def arguments: () -> Array[Argument] + + def skips_pre: () -> bool? + + def skips_post: () -> bool? + + def skips_around: () -> bool? + + def names: () -> String + + def examples: () -> Array[Hash[Symbol, String]] + + def commands_declaration_order: () -> Array[Command] + + def flags: () -> Hash[Symbol, Flag] + + def switches: () -> Hash[Symbol, Switch] + + def commands: () -> Hash[Symbol, Command] + + def default_description: () -> String? + + def execute: (options_hash global_options, options_hash options, Array[String] arguments) -> untyped + + def topmost_ancestor: () -> Command + + def has_action?: () -> bool + + def get_default_command: () -> Symbol? + end + + # The object yielded by GLI::DSL#command. + class Command < CommandLineToken + include DSL + + include CommandSupport + + class ParentKey + def to_sym: () -> Symbol + end + + PARENT: ParentKey + + def initialize: (Hash[Symbol, untyped] options) -> void + + def example: (String example_invocation, ?desc: String) -> void + + def default_command: (name command_name) -> name + + def action: () { (options_hash global_options, options_hash options, Array[String] arguments) -> void } -> void + + def default_desc: (String desc) -> String + + def has_option?: (String option) -> bool + + def name_for_help: () -> Array[String] + + def self.name_as_string: (name name, ?bool negatable) -> String + end + + # Hash-like options passed to action blocks when the app calls use_openstruct(true). + # + # GLI::Options subclasses OpenStruct, but RBS 4 ships no OpenStruct + # signatures, so the OpenStruct methods are declared here instead. + class Options + def initialize: (?Hash[name, untyped]? hash) -> void + + def to_h: () -> Hash[Symbol, untyped] + + def each_pair: () { ([Symbol, untyped]) -> void } -> self + | () -> Enumerator[[Symbol, untyped], self] + + def dig: (name name, *untyped names) -> untyped + + def delete_field: (name name) -> untyped + + def []: (name k) -> untyped + + def []=: (name k, untyped v) -> untyped + + def map: [T] () { ([Symbol, untyped]) -> T } -> Array[T] + end + + class OptionParsingResult + attr_accessor global_options: options_hash + + attr_accessor command: Command? + + attr_accessor command_options: options_hash + + attr_accessor arguments: Array[String] + + def convert_to_openstruct!: () -> self + + def to_a: () -> [options_hash, Command?, options_hash, Array[String]] + end + + module StandardException + def exit_code: () -> Integer + end + + class RequestHelp < StandardError + include StandardException + + attr_reader command_in_context: Command? + + def initialize: (Command? command_in_context) -> void + end + + class BadCommandLine < StandardError + include StandardException + end + + class PreconditionFailed < StandardError + include StandardException + end + + class UnknownCommand < BadCommandLine + end + + class AmbiguousCommand < BadCommandLine + end + + class UnknownGlobalArgument < BadCommandLine + end + + class CommandException < BadCommandLine + attr_reader command_in_context: Command + + def initialize: (String message, Command command_in_context, ?Integer? exit_code) -> void + end + + class BadCommandOptionsOrArguments < BadCommandLine + attr_reader command_in_context: Command + + def initialize: (String message, Command command) -> void + end + + class MissingRequiredArgumentsException < BadCommandOptionsOrArguments + attr_reader num_arguments_received: Integer + + attr_reader range_arguments_accepted: Range[Integer] + + def initialize: (Command command, Integer num_arguments_received, Range[Integer] range_arguments_accepted) -> void + end + + class MissingRequiredOptionsException < BadCommandOptionsOrArguments + def initialize: (Command command, Array[CommandLineOption] missing_required_options) -> void + end + + class UnknownCommandArgument < CommandException + end + + # Raised by GLI::App#exit_now! to exit with a given status. + class CustomExit < StandardError + include StandardException + + attr_reader exit_code: Integer + + def initialize: (String message, Integer exit_code) -> void + end + + class Terminal + def self.default_size: () -> [Integer, Integer] + + def self.default_size=: ([Integer, Integer] size) -> [Integer, Integer] + + def self.instance: () -> Terminal + + def self.command_exists?: (String command) -> bool + + def command_exists?: (String command) -> bool + + # Width and height of the terminal. + def size: () -> [Integer, Integer] + end + + class InitConfig < Command + def initialize: (String config_file_name, Hash[Symbol, Command] commands, Hash[Symbol, Flag] flags, Hash[Symbol, Switch] switches) -> void + end + + module Commands + # The built-in help command. + class Help < Command + def self.skips_pre=: (bool skips_pre) -> bool + + def self.skips_post=: (bool skips_post) -> bool + + def self.skips_around=: (bool skips_around) -> bool + + def initialize: (App app, ?IO output, ?IO error) -> void + + def skips_pre: () -> bool + + def skips_post: () -> bool + + def skips_around: () -> bool + end + + # The built-in _doc command. + class Doc < Command + def initialize: (App app) -> void + + def nodoc: () -> true + + def document: (untyped document_listener) -> void + end + + # A command made by passing a Hash to GLI::DSL#command, which runs other commands in turn. + class CompoundCommand < Command + def initialize: (untyped base, Hash[name, Array[name]] configuration, ?Hash[Symbol, untyped] options) -> void + end + end +end diff --git a/gems/gli/_reviewers.yaml b/gems/gli/_reviewers.yaml new file mode 100644 index 000000000..8d710c457 --- /dev/null +++ b/gems/gli/_reviewers.yaml @@ -0,0 +1,2 @@ +reviewers: + - apiology From 7494eedd2e5378bc7bbe249d36428d60ace48382 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Mon, 19 Oct 2026 14:52:13 -0400 Subject: [PATCH 2/2] Copy gli RDoc comments into its RBS Generated with rbs annotate from ri data built from gli v2.22.2 (rdoc --ri lib), the same rdoc-file comment format minitest and logger use here, so IDEs show gli's own docs on hover. gli is Apache-2.0. Methods gli marks :nodoc: stay undocumented, so the hand-written summaries on GLI.run, AppSupport#run and App#program_name are dropped rather than presented as gli docs. Co-Authored-By: Claude Opus 5.5 (1M context) Claude-Session: https://claude.ai/code/session_01Wo4FerFK9aaxrGrmVE9ncZ --- gems/gli/2.22/gli.rbs | 1102 ++++++++++++++++++++++++++++++++++++++++- 1 file changed, 1077 insertions(+), 25 deletions(-) diff --git a/gems/gli/2.22/gli.rbs b/gems/gli/2.22/gli.rbs index 7d87ae286..7fc68cb94 100644 --- a/gems/gli/2.22/gli.rbs +++ b/gems/gli/2.22/gli.rbs @@ -3,7 +3,6 @@ module GLI include App - # Prints a warning that GLI 2 no longer supports this entry point. def self.run: (*untyped args) -> Integer type options_hash = Hash[Symbol | String, untyped] @@ -16,21 +15,170 @@ module GLI type argument_option = :optional | :multiple - # Methods for declaring flags, switches, descriptions and commands. - # Shared by GLI::App and GLI::Command. + # + # The primary DSL for GLI. This represents the methods shared between your + # top-level app and the commands. See GLI::Command for additional methods that + # apply only to command objects. + # module DSL + # + # Describe the next switch, flag, or command. This should be a short, one-line + # description + # + # `description` + # : A String of the short descripiton of the switch, flag, or command + # following + # def desc: (String description) -> String alias d desc + # + # Provide a longer, more detailed description. This will be reformatted and + # wrapped to fit in the terminal's columns + # + # `long_desc` + # : A String that is s longer description of the switch, flag, or command + # following. + # def long_desc: (String long_desc) -> String + # + # Describe the argument name of the next flag. It's important to keep this VERY + # short and, ideally, without any spaces (see Example). + # + # `name` + # : A String that **briefly** describes the argument given to the following + # command or flag. + # + # `options` + # : Symbol or array of symbols to annotate this argument. This doesn't affect + # parsing, just the help output. Values recognized are: + # `:optional` + # : indicates this argument is optional; will format it with square + # brackets + # + # `:multiple` + # : indicates multiple values are accepted; will format appropriately + # + # + # + # Example: + # desc 'Set the filename' + # arg_name 'file_name' + # flag [:f,:filename] + # + # Produces: + # -f, --filename=file_name Set the filename + # def arg_name: (String name, ?argument_option | Array[argument_option] options) -> void + # + # Describes one of the arguments of the next command + # + # `name` + # : A String that **briefly** describes the argument given to the following + # command. + # + # `options` + # : Symbol or array of symbols to annotate this argument. This doesn't affect + # parsing, just the help output. Values recognized are: + # `:optional` + # : indicates this argument is optional; will format it with square + # brackets + # + # `:multiple` + # : indicates multiple values are accepted; will format appropriately + # + # + # + # Example: + # arg :output + # arg :input, :multiple + # command :pack do ... + # + # Produces the synopsis: + # app.rb [global options] pack output input... + # def arg: (name name, ?argument_option | Array[argument_option] options) -> Array[Argument] + # + # set the default value of the next flag or switch + # + # `val` + # : The default value to be used for the following flag if the user doesn't + # specify one and, when using a config file, the config also doesn't specify + # one. For a switch, this is the value to be used if the switch isn't + # specified on the command-line. Note that if you set a switch to have a + # default of true, using the switch on the command-line has no effect. To + # disable a switch where the default is true, use the `--no-` form. + # def default_value: (untyped val) -> untyped + # + # Create a flag, which is a switch that takes an argument + # + # `names` + # : a String or Symbol, or an Array of String or Symbol that represent all the + # different names and aliases for this flag. The last element can be a hash + # of options: + # `:desc` + # : the description, instead of using #desc + # + # `:long_desc` + # : the long_description, instead of using #long_desc + # + # `:default_value` + # : the default value, instead of using #default_value + # + # `:arg_name` + # : the arg name, instead of using #arg_name + # + # `:must_match` + # : A regexp that the flag's value must match or an array of allowable + # values + # + # `:type` + # : A Class (or object you passed to GLI::App#accept) to trigger type + # coversion + # + # `:multiple` + # : if true, flag may be used multiple times and values are stored in an + # array + # + # + # + # Example: + # + # desc 'Set the filename' + # flag [:f,:filename,'file-name'] + # + # flag :ipaddress, :desc => "IP Address", :must_match => /\d+\.\d+\.\d+\.\d+/ + # + # flag :names, :desc => "list of names", :type => Array + # + # Produces: + # + # -f, --filename, --file-name=arg Set the filename + # def flag: (*names names, ?desc: String?, ?long_desc: String?, @@ -44,6 +192,31 @@ module GLI alias f flag + # + # Create a switch, which is a command line flag that takes no arguments (thus, + # it *switches* something on) + # + # `names` + # : a String or Symbol, or an Array of String or Symbol that represent all the + # different names and aliases for this switch. The last element can be a + # hash of options: + # `:desc` + # : the description, instead of using #desc + # + # `:long_desc` + # : the long_description, instead of using #long_desc + # + # `:default_value` + # : if the switch is omitted, use this as the default value. By default, + # switches default to off, or `false` + # + # `:negatable` + # : if true, this switch will get a negatable form (e.g. `--[no-]switch`, + # false it will not. Default is true + # def switch: (*names names, ?desc: String?, ?long_desc: String?, @@ -54,6 +227,52 @@ module GLI def clear_nexts: () -> void + # + # Define a new command. This can be done in a few ways, but the most common + # method is to pass a symbol (or Array of symbols) representing the command name + # (or names) and a block. + # + # The block will be given an instance of the Command that was created. You then + # may call methods on this object to define aspects of that Command. + # + # Alternatively, you can call this with a one element Hash, where the key is the + # symbol representing the name of the command, and the value being an Array of + # symbols representing the commands to call in order, as a chained or compound + # command. Note that these commands must exist already, and that only those + # command-specific options defined in **this** command will be parsed and passed + # to the chained commands. This might not be what you expect + # + # `names` + # : a String or Symbol, or an Array of String or Symbol that represent all the + # different names and aliases for this command **or** a Hash, as described + # above. + # + # + # ## Examples + # + # # Make a command named list + # command :list do |c| + # c.action do |global_options,options,args| + # # your command code + # end + # end + # + # # Make a command named list, callable by ls as well + # command [:list,:ls] do |c| + # c.action do |global_options,options,args| + # # your command code + # end + # end + # + # # Make a command named all, that calls list and list_contexts + # command :all => [ :list, :list_contexts ] + # + # # Make a command named all, aliased as :a:, that calls list and list_contexts + # command [:all,:a] => [ :list, :list_contexts ] + # def command: (*names names) { (Command command) -> void } -> Command | (Hash[name, Array[name]] compound) -> Commands::CompoundCommand @@ -66,7 +285,9 @@ module GLI def switches_declaration_order: () -> Array[Switch] end - # Behavior of a running app, mixed into GLI::App. + # + # Internals for make App work + # module AppSupport def error_device=: (IO e) -> IO @@ -80,9 +301,14 @@ module GLI def version_string: () -> String? + # + # Get the default command for the entire app + # def get_default_command: () -> Symbol - # Runs the app and returns the exit code for the process. def run: (Array[String] args) -> Integer def config_file_name: () -> String? @@ -118,77 +344,432 @@ module GLI def autocomplete: () -> bool end - # Extend a class or module with GLI::App to declare a command-line app. + # + # A means to define and parse a command line interface that works as Git's does, + # in that you specify global options, a command name, command specific options, + # and then command arguments. # - # class MyApp - # extend GLI::App - # - # program_desc 'Describe your app' - # command :hello do |c| - # c.action { |global_options, options, args| puts 'hello' } - # end - # end - # exit MyApp.run(ARGV) module App include DSL include AppSupport + # + # Loads ruby files in the load path that start with `path`, which are presumed + # to be commands for your executable. This is useful for decomposing your bin + # file into different classes, but can also be used as a plugin mechanism, + # allowing users to provide additional commands for your app at runtime. All + # that being said, it's basically a glorified `require`. + # + # path + # : a path from which to load `.rb` files that, presumably, contain commands. + # If this is an absolute path, any files in that path are loaded. If not, + # it is interpretted as relative to somewhere in the `LOAD_PATH`. + # + # + # ## Example: + # + # # loads *.rb from your app's install - great for decomposing your bin file + # commands_from "my_app/commands" + # + # # loads *.rb files from the user's home dir - great and an extension/plugin mechanism + # commands_from File.join(ENV["HOME"],".my_app","plugins") + # def commands_from: (String path) -> void + # + # Describe the overall application/programm. This should be a one-sentence + # summary of what your program does that will appear in the help output. + # + # `description` + # : A String of the short description of your program's purpose + # def program_desc: (?String? description) -> String? + # + # Provide a longer description of the program. This can be as long as needed, + # and use double-newlines for paragraphs. This will show up in the help output. + # + # description + # : A String for the description + # def program_long_desc: (?String? description) -> String? + # + # Provide a flag to choose whether to hide or not from the help the undescribed + # commands. By default the undescribed commands will be shown in the help. + # + # hide + # : A Bool for hide the undescribed commands + # def hide_commands_without_desc: (?bool? hide) -> bool + # + # Use this if the following command should not have the pre block executed. By + # default, the pre block is executed before each command and can result in + # aborting the call. Using this will avoid that behavior for the following + # command + # def skips_pre: () -> true + # + # Use this if the following command should not have the post block executed. By + # default, the post block is executed after each command. Using this will avoid + # that behavior for the following command + # def skips_post: () -> true + # + # Use this if the following command should not have the around block executed. + # By default, the around block is executed, but for commands that might not want + # the setup to happen, this can be handy + # def skips_around: () -> true + # + # Sets that this app uses a config file as well as the name of the config file. + # + # `filename` + # : A String representing the path to the file to use for the config file. If + # it's an absolute path, this is treated as the path to the file. If it's + # **not**, it's treated as relative to the user's home directory as produced + # by `File.expand_path('~')`. + # def config_file: (String filename) -> String + # + # Define a block to run after command line arguments are parsed but before any + # command is run. If this block raises an exception the command specified will + # not be executed. The block will receive the global-options,command,options, + # and arguments If this block evaluates to true, the program will proceed; + # otherwise the program will end immediately and exit nonzero + # def pre: () { (options_hash global_options, Command command, options_hash options, Array[String] arguments) -> boolish } -> void + # + # Define a block to run after the command was executed, **only if there was not + # an error**. The block will receive the global-options,command,options, and + # arguments + # def post: () { (options_hash global_options, Command command, options_hash options, Array[String] arguments) -> void } -> void + # + # This inverts the pre/post concept. This is useful when you have a global + # shared resource that is governed by a block instead of separate open/close + # methods. The block you pass here will be given four parameters: + # + # global options + # : the parsed global options + # + # command + # : The GLI::Command that the user is going to invoke + # + # options + # : the command specific options + # + # args + # : unparsed command-line args + # + # code + # : a block that you must `call` to execute the command. + # + # + # #help_now! and #exit_now! work as expected; you can abort the command call by + # simply not calling it. + # + # You can declare as many #around blocks as you want. They will be called in + # the order in which they are defined. + # + # Note that if you declare #around blocks, #pre and #post blocks will still + # work. The #pre is called first, followed by the around, followed by the + # #post. + # + # Call #skips_around before a command that should not have this hook fired + # def around: () { (options_hash global_options, Command command, options_hash options, Array[String] arguments, ^() -> untyped code) -> untyped } -> void - # The block returns false to skip GLI's own error message. + # + # Define a block to run if an error occurs. The block will receive any Exception + # that was caught. It should evaluate to false to avoid the built-in error + # handling (which basically just prints out a message). GLI uses a variety of + # exceptions that you can use to find out what errors might've occurred during + # command-line parsing: + # * GLI::CustomExit + # * GLI::UnknownCommandArgument + # * GLI::UnknownGlobalArgument + # * GLI::UnknownCommand + # * GLI::BadCommandLine + # def on_error: () { (Exception exception) -> boolish } -> void + # + # Indicate the version of your application + # + # `version` + # : String containing the version of your application. + # def version: (String version) -> Switch + # + # By default, GLI mutates the argument passed to it. This is consistent with + # `OptionParser`, but be less than ideal. Since that value, for scaffolded + # apps, is `ARGV`, you might want to refer to the entire command-line via `ARGV` + # and thus not want it mutated. + # def preserve_argv: (?bool preserve) -> bool + # + # Call this with `true` will cause the `global_options` and `options` passed to + # your code to be wrapped in Options, which is a subclass of `OpenStruct` that + # adds `[]` and `[]=` methods. + # + # `use_openstruct` + # : a Boolean indicating if we should use OpenStruct instead of Hashes + # def use_openstruct: (bool use_openstruct) -> bool + # + # Configure a type conversion not already provided by the underlying + # OptionParser. This works more or less like the OptionParser version. It's + # global. + # + # object + # : the class (or whatever) that triggers the type conversion + # + # block + # : the block that will be given the string argument and is expected to return + # the converted value + # + # + # Example + # + # accept(Hash) do |value| + # result = {} + # value.split(/,/).each do |pair| + # k,v = pair.split(/:/) + # result[k] = v + # end + # result + # end + # + # flag :properties, :type => Hash + # def accept: (Module object) { (String value) -> untyped } -> void + # + # Simpler means of exiting with a custom exit code. This will raise a + # CustomExit with the given message and exit code, which will ultimatley cause + # your application to exit with the given exit_code as its exit status Use + # #help_now! if you want to show the help in addition to the error message + # + # message + # : message to show the user + # + # exit_code + # : exit code to exit as, defaults to 1 + # def exit_now!: (String message, ?Integer exit_code) -> bot + # + # Exit now, showing the user help for the command they executed. Use #exit_now! + # to just show the error message + # + # message + # : message to indicate how the user has messed up the CLI invocation or nil + # to just simply show help + # def help_now!: (?String? message) -> bot + # + # Control how commands and options are sorted in help output. By default, they + # are sorted alphabetically. + # + # sort_type + # : How you want help commands/options sorted: + # `:manually` + # : help commands/options are ordered in the order declared. + # + # `:alpha` + # : sort alphabetically (default) + # def sort_help: (:alpha | :manually sort_type) -> (:alpha | :manually) + # + # Set how help text is wrapped. + # + # wrap_type + # : Symbol indicating how you'd like text wrapped: + # `:to_terminal` + # : Wrap text based on the width of the terminal (default) + # + # `:verbatim` + # : Format text exactly as it was given to the various methods. This is + # useful if your output has formatted output, e.g. ascii tables and you + # don't want it messed with. + # + # `:one_line` + # : Do not wrap text at all. This will bring all help content onto one + # line, removing any newlines + # + # `:tty_only` + # : Wrap like `:to_terminal` if this output is going to a TTY, otherwise + # don't wrap (like `:one_line`) + # def wrap_help_text: (:to_terminal | :verbatim | :one_line | :tty_only wrap_type) -> (:to_terminal | :verbatim | :one_line | :tty_only) + # + # Control how the SYNOPSIS is formatted. + # + # format + # : one of: + # `:full` + # : the default, show subcommand options and flags inline + # + # `:terminal` + # : if :full would be wider than the terminal, use :compact + # + # `:compact` + # : use a simpler and shorter SYNOPSIS. Useful if your app has a lot of + # options and showing them in the SYNOPSIS makes things more confusing + # def synopsis_format: (:full | :terminal | :compact format) -> (:full | :terminal | :compact) - # Deprecated; prints a warning. def program_name: (?untyped override) -> nil + # + # Sets a default command to run when none is specified on the command line. + # Note that if you use this, you won't be able to pass arguments, flags, or + # switches to the command when run in default mode. All flags and switches are + # treated as global, and any argument will be interpretted as the command name + # and likely fail. + # + # `command` + # : Command as a Symbol to run as default + # def default_command: (name command) -> Symbol + # + # How to handle subcommand options. In general, you want to set this to + # `:normal`, which treats each subcommand as establishing its own namespace for + # options. This is what the scaffolding should generate, but it is **not** what + # GLI 2.5.x and lower apps had as a default. To maintain backwards + # compatibility, the default is `:legacy`, which is that all subcommands of a + # particular command share a namespace for options, making it impossible for two + # subcommands to have options of the same name. + # def subcommand_option_handling: (:legacy | :normal handling_strategy) -> (:legacy | :normal) + # + # How to handle argument validation. + # + # handling_strategy + # : One of: + # `:loose` + # : no argument validation. Use of `arg` or `arg_name` is for + # documentation purposes only. (Default) + # + # `:strict` + # : arguments are validated according to their specification. `action` + # blocks may assume the value of `arguments` matches the specification + # provided in `arg`. Note that to use this strategy, you must also be + # sure that `subcommand_option_handling` is set. + # def arguments: (:loose | :strict handling_strategy) -> (:loose | :strict) + # + # Enables/Disables command autocomplete, where partially spelled commands are + # automatically expanded to their full form + # + # Example: When enabled, executing 'shake' would execute 'shake_hand' (if no + # 'shake' command is defined). When disabled, executing 'shake' would throw an + # UnknownCommand error + # + # `boolean` + # : Boolean value to enable or disable autocomplete, respectively. True by + # default. + # def autocomplete_commands: (bool boolean) -> bool end + # + # Abstract base class for a logical element of a command line, mostly so that + # subclasses can have similar initialization and interface + # class CommandLineToken attr_reader name: Symbol @@ -200,8 +781,20 @@ module GLI def initialize: (names names, String? description, ?String? long_description) -> void + # + # Sort based on primary name + # def <=>: (CommandLineToken other) -> Integer? + # + # Array of the name and aliases, as string + # def names_and_aliases: () -> Array[String] end @@ -259,40 +852,109 @@ module GLI def multiple?: () -> bool end - # Methods of GLI::Command that its subclasses share. + # + # Things unrelated to the true public interface of Command that are needed for + # bookkeeping and help support. Generally, you shouldn't be calling these + # methods; they are technically public but are essentially part of GLI's + # internal implementation and subject to change + # module CommandSupport + # + # The parent of this command, either the GLI app, or another command + # attr_accessor parent: untyped def context_description: () -> String + # + # Return true to avoid including this command in your help strings Will honor + # the hide_commands_without_desc flag + # def nodoc: () -> boolish + # + # Return the arguments description + # def arguments_description: () -> String def arguments_options: () -> Array[argument_option] def arguments: () -> Array[Argument] + # + # If true, this command doesn't want the pre block run before it executes + # def skips_pre: () -> bool? + # + # If true, this command doesn't want the post block run before it executes + # def skips_post: () -> bool? + # + # If true, this command doesn't want the around block called + # def skips_around: () -> bool? + # + # Return the Array of the command's names + # def names: () -> String + # + # Returns the array of examples + # def examples: () -> Array[Hash[Symbol, String]] def commands_declaration_order: () -> Array[Command] + # + # Return the flags as a Hash + # def flags: () -> Hash[Symbol, Flag] + # + # Return the switches as a Hash + # def switches: () -> Hash[Symbol, Switch] def commands: () -> Hash[Symbol, Command] def default_description: () -> String? + # + # Executes the command + # def execute: (options_hash global_options, options_hash options, Array[String] arguments) -> untyped def topmost_ancestor: () -> Command @@ -302,7 +964,31 @@ module GLI def get_default_command: () -> Symbol? end - # The object yielded by GLI::DSL#command. + # + # A command to be run, in context of global flags and switches. You are given + # an instance of this class to the block you use for GLI::DSL#command. This + # class mixes in GLI::DSL so all of those methods are available to describe the + # command, in addition to the methods documented here, most importantly #action. + # + # Example: + # + # command :list do |c| # <- c is an instance of GLI::Command + # c.desc 'use long form' + # c.switch :l + # + # c.action do |global,options,args| + # # list things here + # end + # + # c.command :tasks do |t| # <- t is an instance of GLI::Command + # # this is a "subcommand" of list + # + # t.action do |global,options,args| + # # do whatever list tasks should do + # end + # end + # end + # class Command < CommandLineToken include DSL @@ -312,26 +998,199 @@ module GLI def to_sym: () -> Symbol end + # + # Key in an options hash to find the parent's parsed options. Note that if you + # are using openstruct, e.g. via `use_openstruct true` in your app setup, you + # will need to use the method `__parent__` to access parent parsed options. + # PARENT: ParentKey + # + # Create a new command. + # + # options + # : Keys should be: + # `names` + # : A String, Symbol, or Array of String or Symbol that represents the + # name(s) of this command (required). + # + # `description` + # : short description of this command as a String + # + # `arguments_name` + # : description of the arguments as a String, or nil if this command + # doesn't take arguments + # + # `long_desc` + # : a longer description of the command, possibly with multiple lines. A + # double line-break is treated as a paragraph break. No other + # formatting is respected, though inner whitespace is maintained. + # + # `skips_pre` + # : if true, this command advertises that it doesn't want the pre block + # called first + # + # `skips_post` + # : if true, this command advertises that it doesn't want the post block + # called after it + # + # `skips_around` + # : if true, this command advertises that it doesn't want the around block + # called + # + # `hide_commands_without_desc` + # : if true and there isn't a description the command is not going to be + # shown in the help + # + # `examples` + # : An array of Hashes, where each hash must have the key `:example` + # mapping to a string, and may optionally have the key `:desc` that + # documents that example. + # def initialize: (Hash[Symbol, untyped] options) -> void + # + # Specify an example invocation. + # + # example_invocation + # : test of a complete command-line invocation you want to show + # + # options + # : refine the example: + # `:desc` + # : A description of the example to be shown with it (optional) + # def example: (String example_invocation, ?desc: String) -> void + # + # Set the default command if this command has subcommands and the user doesn't + # provide a subcommand when invoking THIS command. When nil, this will show an + # error and the help for this command; when set, the command with this name will + # be executed. + # + # `command_name` + # : The primary name of the subcommand of this command that should be run by + # default as a String or Symbol. + # def default_command: (name command_name) -> name + # + # Define the action to take when the user executes this command. Every command + # should either define this action block, or have subcommands (or both). + # + # `block` + # : A block of code to execute. The block will be given 3 arguments: + # `global_options` + # : A Hash of the *global* options specified by the user, with defaults + # set and config file values used (if using a config file, see + # GLI::App#config_file) + # + # `options` + # : A Hash of the command-specific options specified by the user, with + # defaults set and config file values used (if using a config file, see + # GLI::App#config_file). + # + # `arguments` + # : An Array of Strings representing the unparsed command line arguments + # + # The block's result value is not used; raise an exception or use + # GLI#exit_now! if you need an early exit based on an error condition + # def action: () { (options_hash global_options, options_hash options, Array[String] arguments) -> void } -> void + # + # Describes this commands action block when it **also** has subcommands. In this + # case, the GLI::DSL#desc value is the general description of the commands that + # this command groups, and the value for **this** method documents what will + # happen if you omit a subcommand. + # + # Note that if you omit the action block and specify a subcommand, that + # subcommand's description will be used to describe what happens by default. + # + # desc + # : the description of what this command's action block does. + # + # + # Example + # + # desc 'list things' + # command :list do |c| + # + # c.desc 'list tasks' + # c.command :tasks do |t| + # t.action do |global,options,args| + # end + # end + # + # c.desc 'list contexts' + # c.command :contexts do |t| + # t.action do |global,options,args| + # end + # end + # + # c.default_desc 'list both tasks and contexts' + # c.action do |global,options,args| + # # list everything + # end + # end + # + # > todo help list + # NAME + # list - List things + # + # SYNOPSIS + # todo [global options] list [command options] + # todo [global options] list [command options] tasks + # todo [global options] list [command options] contexts + # + # COMMANDS + # - list both tasks and contexts + # tasks - list tasks + # contexts - list contexts + # def default_desc: (String desc) -> String def has_option?: (String option) -> bool + # + # Returns full name for help command including parents + # + # Example + # + # command :remote do |t| + # t.command :add do |global,options,args| + # end + # end + # + # @add_command.name_for_help # => ["remote", "add"] + # def name_for_help: () -> Array[String] def self.name_as_string: (name name, ?bool negatable) -> String end - # Hash-like options passed to action blocks when the app calls use_openstruct(true). + # + # Subclass of `OpenStruct` that provides hash-like methods for #[] and #[]=. + # Note that is is **not** a Hash. By using GLI::App#use_openstruct, your options + # will be coerced into one of these. # # GLI::Options subclasses OpenStruct, but RBS 4 ships no OpenStruct # signatures, so the OpenStruct methods are declared here instead. @@ -347,8 +1206,20 @@ module GLI def delete_field: (name name) -> untyped + # + # Return the value of an attribute + # def []: (name k) -> untyped + # + # Set the value of an attribute + # def []=: (name k, untyped v) -> untyped def map: [T] () { ([Symbol, untyped]) -> T } -> Array[T] @@ -365,21 +1236,43 @@ module GLI def convert_to_openstruct!: () -> self + # + # Allows us to splat this object into blocks and methods expecting parameters in + # this order + # def to_a: () -> [options_hash, Command?, options_hash, Array[String]] end + # + # Mixed into all exceptions that GLI handles; you can use this to catch anything + # that came from GLI intentionally. You can also mix this into non-GLI + # exceptions to get GLI's exit behavior. + # module StandardException def exit_code: () -> Integer end + # + # Hack to request help from within a command Will **not** be rethrown when + # GLI_DEBUG is ON + # class RequestHelp < StandardError include StandardException + # + # The command for which the argument was unknown + # attr_reader command_in_context: Command? def initialize: (Command? command_in_context) -> void end + # + # Indicates that the command line invocation was bad + # class BadCommandLine < StandardError include StandardException end @@ -388,22 +1281,47 @@ module GLI include StandardException end + # + # Indicates the bad command line was an unknown command + # class UnknownCommand < BadCommandLine end + # + # The command issued partially matches more than one command + # class AmbiguousCommand < BadCommandLine end + # + # Indicates the bad command line was an unknown global argument + # class UnknownGlobalArgument < BadCommandLine end class CommandException < BadCommandLine + # + # The command for which the argument was unknown + # attr_reader command_in_context: Command + # + # `message` + # : the error message to show the user + # + # `command` + # : the command we were using to parse command-specific options + # def initialize: (String message, Command command_in_context, ?Integer? exit_code) -> void end class BadCommandOptionsOrArguments < BadCommandLine + # + # The command for which the argument was unknown + # attr_reader command_in_context: Command def initialize: (String message, Command command) -> void @@ -421,30 +1339,109 @@ module GLI def initialize: (Command command, Array[CommandLineOption] missing_required_options) -> void end + # + # Indicates the bad command line was an unknown command argument + # class UnknownCommandArgument < CommandException end - # Raised by GLI::App#exit_now! to exit with a given status. + # + # Raise this if you want to use an exit status that isn't the default provided + # by GLI. Note that GLI::App#exit_now! might be a bit more to your liking. + # + # Example: + # + # raise CustomExit.new("Not connected to DB",-5) unless connected? + # raise CustomExit.new("Bad SQL",-6) unless valid_sql?(args[0]) + # class CustomExit < StandardError include StandardException attr_reader exit_code: Integer + # + # Create a custom exit exception + # + # `message` + # : String containing error message to show the user + # + # `exit_code` + # : the exit code to use (as an Int), overridding GLI's default + # def initialize: (String message, Integer exit_code) -> void end + # + # Class to encapsulate stuff about the terminal. This is useful to application + # developers as a canonical means to get information about the user's current + # terminal configuraiton. GLI uses this to determine the number of columns to + # use when printing to the screen. + # + # To access it, use Terminal.instance. This is a singleton mostly to facilitate + # testing, but it seems reasonable enough, since there's only one terminal in + # effect + # + # Example: + # + # Terminal.instance.size[0] # => columns in the terminal + # Terminal.default_size = [128,24] # => change default when we can't figure it out + # raise "no ls?!?!?" unless Terminal.instance.command_exists?("ls") + # class Terminal + # + # Get the default size of the terminal when we can't figure it out + # + # Returns an array of int [cols,rows] + # def self.default_size: () -> [Integer, Integer] + # + # Set the default size of the terminal to use when we can't figure it out + # + # `size` + # : array of two int [cols,rows] + # def self.default_size=: ([Integer, Integer] size) -> [Integer, Integer] + # + # Provide access to the shared instance. + # def self.instance: () -> Terminal + # + # Returns true if the given command exists on this system + # + # `command` + # : The command, as a String, to check for, without any path information. + # def self.command_exists?: (String command) -> bool def command_exists?: (String command) -> bool - # Width and height of the terminal. + # + # Get the size of the current terminal. Ripped from + # [hirb](https://github.com/cldwalker/hirb/blob/master/lib/hirb/util.rb) + # + # Returns an Array of size two Ints representing the terminal width and height + # def size: () -> [Integer, Integer] end @@ -453,12 +1450,38 @@ module GLI end module Commands - # The built-in help command. + # + # The help command used for the two-level interactive help system + # class Help < Command + # + # Configure help to explicitly skip or not skip the pre block when the help + # command runs. This is here because the creation of the help command is outside + # of the client programmer's control + # def self.skips_pre=: (bool skips_pre) -> bool + # + # Configure help to explicitly skip or not skip the post block when the help + # command runs. This is here because the creation of the help command is outside + # of the client programmer's control + # def self.skips_post=: (bool skips_post) -> bool + # + # Configure help to explicitly skip or not skip the around block when the help + # command runs. This is here because the creation of the help command is outside + # of the client programmer's control + # def self.skips_around=: (bool skips_around) -> bool def initialize: (App app, ?IO output, ?IO error) -> void @@ -470,17 +1493,46 @@ module GLI def skips_around: () -> bool end - # The built-in _doc command. + # + # Takes a DocListener which will be called with all of the meta-data and + # documentation about your app, so as to create documentation in whatever format + # you want + # class Doc < Command + # + # Create the Doc generator based on the GLI app passed in + # def initialize: (App app) -> void def nodoc: () -> true + # + # Generates documentation using the listener + # def document: (untyped document_listener) -> void end - # A command made by passing a Hash to GLI::DSL#command, which runs other commands in turn. + # + # A command that calls other commands in order + # class CompoundCommand < Command + # + # base + # : object that respondes to `commands` + # + # configuration + # : Array of arrays: index 0 is the array of names of this command and index 1 + # is the names of the compound commands. + # def initialize: (untyped base, Hash[name, Array[name]] configuration, ?Hash[Symbol, untyped] options) -> void end end