diff --git a/README.md b/README.md index 11c1cfc..1ba9252 100644 --- a/README.md +++ b/README.md @@ -49,11 +49,11 @@ main args/List: cli.Example "Do something with the flag:" --arguments="--some-option=foo --no-some-flag rest1 rest1", ] - --run=:: | app/cli.Application parsed/cli.Parsed | - print parsed["some-option"] - print parsed["some-flag"] - print parsed["rest-arg"] // A list. - app.ui.result "Computed result" + --run=:: | invocation/cli.Invocation | + print invocation["some-option"] + print invocation["some-flag"] + print invocation["rest-arg"] // A list. + invocation.cli.ui.result "Computed result" command.run args ``` @@ -86,7 +86,7 @@ main args/List: sub := cli.Command "subcommand" --help="This is a subcommand." - --run=:: | app/cli.Application parsed/cli.Parsed | + --run=:: | app/cli.Application parsed/cli.Invocation | print "This is a subcommand." command.add sub @@ -117,17 +117,24 @@ of the library for a complete list. Users are encouraged to extend the `cli.Option` class and create their own typed options. -## Application +## Invocation A call to `command.run` parses the given arguments and then executes the -appropriate lambda. The lambda receives two arguments: an `Application` object and a -`Parsed` object. +appropriate lambda. The lambda receives one argument: an `Invocation` object. -The `Application` object contains getters for the cache, config, and UI objects. +The `Invocation` object contains: +- `cli`: A `Cli` object that contains common functionality for CLI applications, like + the `cache`, `config`, and `ui` objects. It is common to pass this object to + functions that are called from the lambda. +- `parameters`: An object that contains the parsed options and rest arguments. The + `Invocation` object has a shortcut operator `[]` that forwards to the `parameters`. +- `path`: A list of strings that contains the path to the command that was called. +- `command`: The command that was called. ### Cache -The cache is a simple key-value store that persists between runs. It is typically +The cache is a simple key-value store that persists between runs. Cached data may +be removed at any point without major implications to the user. It is typically stored in `~/.cache/`. Environment variables, such as `$XDG_CACHE_HOME`, or `$APP_CACHE_DIR` (where `APP` is the capitalized name) can be used to change the location of the cache. See the documentation of the `cache` library for more details. @@ -139,13 +146,12 @@ The cache can either store bytes, or handle paths to cached folders. The cache can store bytes. For example: ``` toit -import cli -import cli.cache as cli +import cli show Cli FileStore -store-bytes app/cli.Application: - cache := app.cache +store-bytes cli/Cli: + cache := cli.cache - data := cache.get "my-key": | store/cli.FileStore | + data := cache.get "my-key": | store/FileStore | // Block that is called when the key is not found. // The returned data is stored in the cache. print "Data is not cached. Computing it." @@ -158,14 +164,13 @@ The `FileStore` class provides convenience methods to store data. For example, i allows to store (either copy or move) existing files: ``` toit -import cli -import cli.cache as cli +import cli show Cli FileStore import host.file -store-from-file app/cli.Application: - cache := app.cache +store-from-file cli/Cli: + cache := cli.cache - data := cache.get "my-file-key": | store/cli.FileStore | + data := cache.get "my-file-key": | store/FileStore | // Block that is called when the key is not found. print "Data is not cached. Computing it." store.with-tmp-directory: | tmp-dir | @@ -184,13 +189,12 @@ directory in the cache structure. The cache class has the `get-directory-path` method for this use case: ``` toit -import cli -import cli.cache as cli +import cli show Cli DirectoryStore -store-directory app/cli.Application: - cache := app.cache +store-directory cli/Cli: + cache := cli.cache - directory := cache.get-directory-path "my-dir-key": | store/cli.DirectoryStore | + directory := cache.get-directory-path "my-dir-key": | store/DirectoryStore | // Block that is called when the key is not found. // The returned directory is stored in the cache. print "Directory is not cached. Computing it." @@ -216,11 +220,10 @@ strings, and the values can be any json-serializable object. When modifying a configuration it is necessary to `write` the changes back to disk. ``` toit -import cli -import cli.config as cli +import cli show Cli Config -config-example app/cli.Application: - config := app.config +config-example cli/Cli: + config := cli.config print "old value: $(config.get "my-key")" @@ -231,8 +234,8 @@ config-example app/cli.Application: Keys are split at "." to allow for nested values. For example: ``` toit -dotted-example app/cli.Application: - config := app.config +dotted-example cli/Cli: + config := cli.config print "old value: $(config.get "super-key.sub-key")" @@ -266,14 +269,14 @@ add the following options to the root command: --verbosity-level debug|info|verbose|quiet|silent Specify the verbosity level. (default: info) ``` -A corresponding UI object is then available in the `Application` object. Whenever the +A corresponding UI object is then available in the `Cli` object. Whenever the program wants to output something, it should use the `ui` object. ``` toit -import cli +import cli show Cli -some-chatty-method app/cli.Application: - ui := app.ui +some-chatty-method cli/Cli: + ui := cli.ui ui.debug "This is a debug message." ui.verbose "This is a verbose message." ui.info "This is an info message." @@ -303,22 +306,21 @@ Developers are encouraged to use the `ui.emit --structured` method to emit struc data. This is especially true for the result message. ``` toit -import cli -import cli.ui as cli +import cli show * main args: - cmd := cli.Command "my-app" + cmd := Command "my-app" --help="My app does something." - --run=:: | app/cli.Application parsed/cli.Parsed | - run-app app parsed + --run=:: run it -run-app app/cli.Application parsed/cli.Parsed: - ui := app.ui +run invocation/Invocation: + ui := invocation.cli.ui ui.emit // Block that is invoked if structured data is needed. --structured=: { "result": "Computed result" } + // Block that is invoked if text data is needed. --text=: "Computed result as text message." ``` @@ -330,6 +332,9 @@ The `Ui` class has furthermore convenience methods to print tables, maps and lis Typically, these methods are used for result messages, but they can be used for other messages as well. +The shorthands `ui.info`, `ui.debug`, also dispatch to these methods if they receive a + table (list of lists), map or list. + See the documentation of the `ui` library for more details. ## Features and bugs diff --git a/examples/main.toit b/examples/commands.toit similarity index 80% rename from examples/main.toit rename to examples/commands.toit index 98152f1..0ae0178 100644 --- a/examples/main.toit +++ b/examples/commands.toit @@ -18,7 +18,7 @@ It can not be used to manage the fleet, for example by adding or removing devices. Usage: - examples/main.toit + examples/main.toit [] Commands: device Manage a particular device. @@ -26,17 +26,20 @@ Commands: status Shows the status of the fleet. Options: - -h, --help Show help for this command. + -h, --help Show help for this command. + --output-format text|json Specify the format used when printing to the console. (default: text) + --verbose Enable verbose output. Shorthand for --verbosity-level=verbose. + --verbosity-level debug|info|verbose|quiet|silent Specify the verbosity level. (default: info) Examples: - # Do a soft-reset of device 'foo': - fleet_manager device --device=foo reset -m soft + # Uploads the file 'foo.data' to the device 'foo': + main.toit device --device=foo upload foo.data # Show a detailed status of the fleet: - fleet_manager status --verbose + main.toit --verbose status - # Uploads the file 'foo.data' to the device 'foo': - fleet_manager device --device=foo upload foo.data + # Do a soft-reset of device 'foo': + main.toit device --device=foo reset -m soft ``` The help for the `reset` command looks as follows: @@ -61,10 +64,10 @@ Global options: Examples: # Do a soft-reset of device 'foo': - fleet_manager device --device=foo reset -m soft + main.toit device --device=foo reset -m soft # Do a hard-reset: - fleet_manager device reset --mode=hard + main.toit device reset --mode=hard ``` */ @@ -97,10 +100,11 @@ create-status-command -> cli.Command: cli.Example "Show a detailed status of the fleet:" --arguments="--verbose" --global-priority=7, // Show this example for the root command. ] - --run=:: | app parsed | fleet-status app parsed + --run=:: fleet-status it -fleet-status app/cli.Application parsed/cli.Parsed: - max-lines := parsed["max-lines"] +fleet-status invocation/cli.Invocation: + max-lines := invocation["max-lines"] + app := invocation.cli verbose := app.ui.level >= cli.Ui.VERBOSE-LEVEL app.ui.emit @@ -131,20 +135,10 @@ create-device-command -> cli.Command: cli.Option "device" --short-name="d" --help="The device to operate on." ] - device-cmd.add create-reset-command device-cmd.add create-upload-command + device-cmd.add create-reset-command return device-cmd -with-device app/cli.Application parsed/cli.Parsed [block]: - device := parsed["device"] - if not device: - device = app.config.get "default-device" - - if not device: - app.ui.abort "No device specified and no default device set." - - block.call device - create-upload-command -> cli.Command: return cli.Command "upload" --help=""" @@ -164,13 +158,7 @@ create-upload-command -> cli.Command: --arguments="--device=foo foo.data" --global-priority=8, // Include this example for super commands. ] - --run=:: | app parsed | upload-to-device app parsed - -upload-to-device app/cli.Application parsed/cli.Parsed: - data := parsed["data"] - - with-device app parsed: | device | - print "Uploading file '$data' to device '$device'." + --run=:: upload-to-device it create-reset-command -> cli.Command: return cli.Command "reset" @@ -196,12 +184,32 @@ create-reset-command -> cli.Command: "Do a hard-reset:" --arguments="--mode=hard", ] - --run=:: | app parsed | reset-device app parsed + --run=:: reset-device it + +with-device invocation/cli.Invocation [block]: + app := invocation.cli + + device := invocation["device"] + if not device: + device = app.config.get "default-device" + + if not device: + app.ui.abort "No device specified and no default device set." + + block.call device + +upload-to-device invocation/cli.Invocation: + data := invocation["data"] + + with-device invocation: | device | + print "Uploading file '$data' to device '$device'." + +reset-device invocation/cli.Invocation: + app := invocation.cli -reset-device app/cli.Application parsed/cli.Parsed: - mode := parsed["mode"] - force := parsed["force"] + mode := invocation["mode"] + force := invocation["force"] - with-device app parsed: | device | + with-device invocation: | device | app.ui.info "Resetting device '$device' in $(mode)-mode." if force: app.ui.debug "Using the force if necessary." diff --git a/main.toit b/main.toit index f6d6ecb..df6d53f 100644 --- a/main.toit +++ b/main.toit @@ -60,10 +60,10 @@ main args: command.add other2 command.run args -run_help parsed/arguments.Parsed: +run_help parsed/arguments.Invocation: print "in_help" print parsed -run_other parsed/arguments.Parsed: +run_other parsed/arguments.Invocation: print "in_other" print parsed diff --git a/src/cli.toit b/src/cli.toit index 4bc5755..4afc4e5 100644 --- a/src/cli.toit +++ b/src/cli.toit @@ -13,11 +13,37 @@ import .help-generator_ import .ui export Ui +export Cache FileStore DirectoryStore +export Config /** An object giving access to common operations for CLI programs. */ -class Application: +interface Cli: + /** + The name of the application. + + Used to find configurations and caches. + */ + name -> string + + /** + The UI object to use for this application. + + Output should be written to this object. + */ + ui -> Ui + + /** The cache object for this application. */ + cache -> Cache + + /** The configuration object for this application. */ + config -> Config + +/** +An object giving access to common operations for CLI programs. +*/ +class Cli_ implements Cli: /** The name of the application. @@ -28,14 +54,21 @@ class Application: cache_/Cache? := null config_/Config? := null + /** + The UI object to use for this application. + + Output should be written to this object. + */ ui/Ui - constructor.private_ .name --.ui: + constructor .name --.ui: + /** The cache object for this application. */ cache -> Cache: if not cache_: cache_ = Cache --app-name=name return cache_ + /** The configuration object for this application. */ config -> Config: if not config_: config_ = Config --app-name=name return config_ @@ -48,7 +81,7 @@ The main program is a command, and so are all subcommands. class Command: /** The name of the command. - The name of the root command is used as application name for the $Application. + The name of the root command is used as application name for the $Cli. */ name/string @@ -105,9 +138,9 @@ class Command: indented lines to continue paragraphs (just like toitdoc). The first paragraph of the $help is used as short help, and should have meaningful content on its own. - The $run callback is invoked when the command is executed. It is given the $Application and the - $Parsed object. If $run is null, then at least one subcommand must be added to this - command. + The $run callback is invoked when the command is executed. It is given an + $Invocation object. If $run is null, then at least one subcommand must be added + to this command. */ constructor name --usage/string?=null --help/string?=null --examples/List=[] \ --aliases/List=[] --options/List=[] --rest/List=[] --subcommands/List=[] --hidden/bool=false \ @@ -198,7 +231,7 @@ class Command: Runs this command. Parses the given $arguments and then invokes the command or one of its subcommands - with the $Parsed output. + with the $Invocation output. The $invoked-command is used only for the usage message in case of an error. It defaults to $system.program-name. @@ -214,10 +247,11 @@ class Command: if not ui: ui = create-ui-from-args_ arguments if add-ui-help: add-ui-options_ - app := Application.private_ name --ui=ui + app := Cli_ name --ui=ui parser := Parser_ --invoked-command=invoked-command - parsed := parser.parse this arguments - parsed.command.run-callback_.call app parsed + parser.parse this arguments: | path/List parameters/Parameters | + invocation := Invocation.private_ app path parameters + invocation.command.run-callback_.call invocation add-ui-options_: has-output-format-option := false @@ -345,7 +379,7 @@ class Command: An option to a command. Options are used for any input from the command line to the program. They must have unique names, - so that they can be identified in the $Parsed output. + so that they can be identified in the $Invocation output. Non-rest options can be used with '--$name' or '-$short-name' (if provided). Rest options are positional and their name is not exposed to the user except for the help. @@ -409,7 +443,7 @@ abstract class Option: which is an alias for the $OptionString constructor. The $name sets the name of the option. It must be unique among all options of a command. - It is also used to extract the parsed value from the $Parsed object. For multi-word + It is also used to extract the parsed value from the $Invocation object. For multi-word options kebab case ('foo-bar') is recommended. The constructor automatically converts snake case ('foo_bar') to kebab case. This also means, that it's not possible to have two options that only differ in their case (kebab and snake). @@ -862,13 +896,51 @@ The result of parsing the command line arguments. An instance of this class is given to the command's `run` method. */ -class Parsed: +class Invocation: + /** + The $Cli object representing this application. + + It is common to pass this object to other functions and libraries. + */ + cli/Cli + /** A list of $Command objects, representing the commands that were given on the command line. The first command is the root command, the last command is the command that should be executed. */ path/List + /** + The parameters passed to the command. + */ + parameters/Parameters + + /** + Constructors a new invocation object. + */ + constructor.private_ .cli .path .parameters: + + /** + The command that should be executed. + */ + command -> Command: return path.last + + /** + Returns the value of the option with the given $name. + The $name must be an option of the command or one of its super commands. + + If the given $name is in snake_case, it is automatically converted + to kebab-case. + + This method is a shortcut for $parameters[name] ($Parameters.[]). + */ + operator[] name/string -> any: + return parameters[name] + +/** +The parameters passed to the command. +*/ +class Parameters: /** The parsed options. All options, including flags and rest arguments, are stored in this map. @@ -883,14 +955,9 @@ class Parsed: seen-options_/Set /** - Builds a new $Parsed object. - */ - constructor.private_ .path .options_ .seen-options_: - - /** - The command that should be executed. + Builds a new parameters object. */ - command -> Command: return path.last + constructor.private_ .options_ .seen-options_: /** Returns the value of the option with the given $name. diff --git a/src/help-generator_.toit b/src/help-generator_.toit index bf5a16a..336f00e 100644 --- a/src/help-generator_.toit +++ b/src/help-generator_.toit @@ -444,24 +444,25 @@ class HelpGenerator: // Parse it, to verify that it actually is valid. // We are also using the result to reorder the options. parser := Parser_ --invoked-command="root" --for-help-example - parsed/Parsed? := null + invocation-path/List? := null + invocation-parameters/Parameters? := null exception := catch: - parsed = parser.parse example-path.first command-line + parser.parse example-path.first command-line: | path parameters | + invocation-path = path + invocation-parameters = parameters if exception: throw "Error in example '$arguments-line': $exception" - parsed-path := parsed.path - // For each command, collect the options that are defined on it and that were // used in the example. option-to-command := {:} // Map from option to command. command-level := {:} flags := {} - for j := 0; j < parsed-path.size; j++: - current-command/Command := parsed-path[j] + for j := 0; j < invocation-path.size; j++: + current-command/Command := invocation-path[j] command-level[current-command] = j current-command.options_.do: | option/Option | - if not parsed.was-provided option.name: continue.do + if not invocation-parameters.was-provided option.name: continue.do option-to-command["--$option.name"] = current-command if option.short-name: option-to-command["-$option.short-name"] = current-command if option.is-flag: @@ -478,7 +479,7 @@ class HelpGenerator: if argument == "--": break if not argument.starts-with "-": - if path-index >= parsed-path.size - 1: + if path-index >= invocation-path.size - 1: argument-index-- break else: @@ -525,7 +526,7 @@ class HelpGenerator: list.add command-line[argument-index++] list - options-for-command.update parsed-path.last --init=(: []) : | list/List | + options-for-command.update invocation-path.last --init=(: []) : | list/List | list.add-all command-line[argument-index..] list @@ -536,7 +537,7 @@ class HelpGenerator: // For examples, we don't want the full path that was used to invoke the // command (like `build/bin/artemis`), but only the basename. app-name := basename_ invoked-command_ - parsed-path.do: | current-command | + invocation-path.do: | current-command | if is-root: is-root = false full-command.add app-name diff --git a/src/parser_.toit b/src/parser_.toit index 601211c..268b27e 100644 --- a/src/parser_.toit +++ b/src/parser_.toit @@ -42,7 +42,15 @@ class Parser_: ui.abort unreachable - parse root-command/Command arguments -> Parsed: + /** + Parses the command line $arguments and calls the given $block with + the result. + + Calls the $block with two arguments: + - The path (a list of $Command) to the command that was invoked. + - The $Parameters that were parsed. + */ + parse root-command/Command arguments/List [block] -> none: path := [] // Populate the options from the default values or empty lists (for multi-options) options := {:} @@ -64,10 +72,11 @@ class Parser_: seen-options.add option.name - create-help := : | arguments/List | - help-command := Command "help" --run=:: | app/Application _ | - help-command_ path arguments --invoked-command=invoked-command_ --ui=app.ui - Parsed.private_ [help-command] {:} {} + return-help := : | arguments/List | + help-command := Command "help" --run=:: | app/Invocation | + help-command_ path arguments --invoked-command=invoked-command_ --ui=app.cli.ui + block.call [help-command] (Parameters.private_ {:} {}) + return command/Command? := null set-command := : | new-command/Command | @@ -112,7 +121,7 @@ class Parser_: option := all-named-options.get kebab-name if not option: - if name == "help" and not is-inverted: return create-help.call [] + if name == "help" and not is-inverted: return-help.call [] fatal path "Unknown option: --$name" if option.is-flag and value != null: @@ -143,7 +152,7 @@ class Parser_: option-length++ if not option: - if short-name == "h": return create-help.call [] + if short-name == "h": return-help.call [] fatal path "Unknown option: -$short-name" i += option-length @@ -165,7 +174,7 @@ class Parser_: if not subcommand: if argument == "help" and command == root-command: // Special case for the help command. - return create-help.call arguments[index..] + return-help.call arguments[index..] fatal path "Unknown command: $argument" set-command.call subcommand @@ -195,4 +204,4 @@ class Parser_: if not command.run-callback_: fatal path "Missing subcommand." - return Parsed.private_ path options seen-options + block.call path (Parameters.private_ options seen-options) diff --git a/src/ui.toit b/src/ui.toit index 94ad7c1..e6e50ce 100644 --- a/src/ui.toit +++ b/src/ui.toit @@ -335,7 +335,6 @@ class Ui: if printer_.needs-structured --kind=kind: printer_.emit-structured --kind=kind structured.call else: - global-print_ "calling 'emit'" printer_.emit --kind=kind "$(structured.call)" /** diff --git a/tests/check_test.toit b/tests/check_test.toit index 57e90a7..0774cb5 100644 --- a/tests/check_test.toit +++ b/tests/check_test.toit @@ -242,7 +242,7 @@ snake-kebab: cli.Option "foo-bar", cli.Option "foo_bar" ] - --run=:: | parsed/cli.Parsed | + --run=:: | parsed/cli.Invocation | unreachable expect-throw "Ambiguous option of 'root': --foo-bar.": root.check --invoked-command="root" diff --git a/tests/dashdash_test.toit b/tests/dashdash_test.toit index 605cabf..b6a2813 100644 --- a/tests/dashdash_test.toit +++ b/tests/dashdash_test.toit @@ -11,11 +11,11 @@ main: cli.Option "first" --required, cli.Option "arg" --multi ] - --run=:: | _ parsed| test-dashdash parsed + --run=:: test-dashdash it root.run ["--", "prog", "arg1", "arg2", "arg3"] -test-dashdash parsed/cli.Parsed: - first := parsed["first"] - rest := parsed["arg"] +test-dashdash invocation/cli.Invocation: + first := invocation["first"] + rest := invocation["arg"] expect-equals "prog" first expect-list-equals ["arg1", "arg2", "arg3"] rest diff --git a/tests/health/readme1.toit b/tests/health/readme1.toit index d6852ea..ff64f91 100644 --- a/tests/health/readme1.toit +++ b/tests/health/readme1.toit @@ -24,7 +24,7 @@ main args/List: cli.Example "Do something with the flag:" --arguments="--some-option=foo --no-some-flag rest1 rest1", ] - --run=:: | app/cli.Application parsed/cli.Parsed | + --run=:: | app/cli.Application parsed/cli.Invocation | print parsed["some-option"] print parsed["some-flag"] print parsed["rest-arg"] // A list. diff --git a/tests/health/readme2.toit b/tests/health/readme2.toit index bd95c3d..5d3167a 100644 --- a/tests/health/readme2.toit +++ b/tests/health/readme2.toit @@ -10,7 +10,7 @@ main args/List: sub := cli.Command "subcommand" --help="This is a subcommand." - --run=:: | app/cli.Application parsed/cli.Parsed | + --run=:: | app/cli.Application parsed/cli.Invocation | print "This is a subcommand." command.add sub diff --git a/tests/health/readme3.toit b/tests/health/readme3.toit index c0288bf..85e5816 100644 --- a/tests/health/readme3.toit +++ b/tests/health/readme3.toit @@ -32,7 +32,7 @@ store-from-file app/cli.Application: main args: cmd := cli.Command "my-app" - --run=:: | app/cli.Application parsed/cli.Parsed | + --run=:: | app/cli.Application parsed/cli.Invocation | print "Data is cached in $app.cache.path" store-bytes app store-from-file app diff --git a/tests/health/readme4.toit b/tests/health/readme4.toit index 77d3a42..abc0b11 100644 --- a/tests/health/readme4.toit +++ b/tests/health/readme4.toit @@ -23,7 +23,7 @@ store-directory app/cli.Application: main args: cmd := cli.Command "my-app" - --run=:: | app/cli.Application parsed/cli.Parsed | + --run=:: | app/cli.Application parsed/cli.Invocation | print "Data is cached in $app.cache.path" store-directory app diff --git a/tests/health/readme5.toit b/tests/health/readme5.toit index fc911c0..9143292 100644 --- a/tests/health/readme5.toit +++ b/tests/health/readme5.toit @@ -23,7 +23,7 @@ dotted-example app/cli.Application: main args: cmd := cli.Command "my-app" - --run=:: | app/cli.Application parsed/cli.Parsed | + --run=:: | app/cli.Application parsed/cli.Invocation | print "Configuration is stored in $app.config.path" config-example app dotted-example app diff --git a/tests/parser_test.toit b/tests/parser_test.toit index 38ec6a3..2b3b425 100644 --- a/tests/parser_test.toit +++ b/tests/parser_test.toit @@ -7,9 +7,9 @@ import expect show * import .test-ui -check-arguments expected/Map parsed/cli.Parsed: +check-arguments expected/Map invocation/cli.Invocation: expected.do: | key value | - expect-equals value parsed[key] + expect-equals value invocation[key] main: test-options @@ -33,8 +33,7 @@ test-options: cli.Option "bar" --short-name="b", cli.OptionInt "gee" --short-name="g", ] - --run=:: | app/cli.Application parsed/cli.Parsed | - check-arguments expected parsed + --run=:: check-arguments expected it expected = {"foo": "foo_value", "bar": "bar_value", "gee": null} cmd.run ["-f", "foo_value", "-b", "bar_value"] @@ -57,8 +56,7 @@ test-options: cli.Flag "bar" --short-name="b", cli.Option "fizz" --short-name="iz" --default="default_fizz", ] - --run=:: | app/cli.Application parsed/cli.Parsed | - check-arguments expected parsed + --run=:: check-arguments expected it expected = {"foo": "default_foo", "bar": true, "fizz": "default_fizz"} cmd.run ["-b"] @@ -87,8 +85,7 @@ test-options: --options=[ cli.Flag "foo" --short-name="f" --default=false, ] - --run=:: | app/cli.Application parsed/cli.Parsed | - check-arguments expected parsed + --run=:: check-arguments expected it expected = {"foo": false} cmd.run [] @@ -103,8 +100,7 @@ test-options: --options=[ cli.Flag "foo" --short-name="f" --default=true, ] - --run=:: | app/cli.Application parsed/cli.Parsed | - check-arguments expected parsed + --run=:: check-arguments expected it expected = {"foo": true } @@ -117,8 +113,7 @@ test-multi: cli.Option "foo" --short-name="f" --multi, cli.Option "bar" --short-name="b" --multi --split-commas, ] - --run=:: | app/cli.Application parsed/cli.Parsed | - check-arguments expected parsed + --run=:: check-arguments expected it expected = {"foo": ["foo_value"], "bar": ["bar_value"]} cmd.run ["-f", "foo_value", "-b", "bar_value"] @@ -147,8 +142,7 @@ test-multi: --options=[ cli.OptionInt "foo" --short-name="f" --multi --split-commas, ] - --run=:: | app/cli.Application parsed/cli.Parsed | - check-arguments expected parsed + --run=:: check-arguments expected it expected = {"foo": [1, 2, 3]} cmd.run ["-f", "1", "-f", "2", "-f", "3"] @@ -158,8 +152,7 @@ test-multi: --options=[ cli.Flag "foo" --short-name="f" --multi, ] - --run=:: | app/cli.Application parsed/cli.Parsed | - check-arguments expected parsed + --run=:: check-arguments expected it expected = {"foo": [true, true, true]} cmd.run ["-f", "-f", "-f"] @@ -170,8 +163,7 @@ test-multi: --options=[ cli.OptionEnum "foo" ["a", "b"] --short-name="f" --multi, ] - --run=:: | app/cli.Application parsed/cli.Parsed | - check-arguments expected parsed + --run=:: check-arguments expected it expected = {"foo": ["a", "b", "a"]} cmd.run ["-f", "a", "-f", "b", "-f", "a"] @@ -183,8 +175,7 @@ test-rest: cli.Option "foo", cli.OptionInt "bar", ] - --run=:: | app/cli.Application parsed/cli.Parsed | - check-arguments expected parsed + --run=:: check-arguments expected it expected = {"foo": "foo_value", "bar": 42} cmd.run ["foo_value", "42"] @@ -200,8 +191,7 @@ test-rest: cli.Option "foo" --required, cli.OptionInt "bar" --default=42, ] - --run=:: | app/cli.Application parsed/cli.Parsed | - check-arguments expected parsed + --run=:: check-arguments expected it expected = {"foo": "foo_value", "bar": 42} cmd.run ["foo_value"] @@ -217,8 +207,7 @@ test-rest: cli.Option "foo" --required, cli.Option "bar" --required --multi, ] - --run=:: | app/cli.Application parsed/cli.Parsed | - check-arguments expected parsed + --run=:: check-arguments expected it expected = {"foo": "foo_value", "bar": ["bar_value"]} cmd.run ["foo_value", "bar_value"] @@ -242,17 +231,14 @@ test-subcommands: --options=[ cli.Option "foo" --short-name="f", ] - --run=:: | app/cli.Application parsed/cli.Parsed | - check-arguments expected parsed, + --run=:: check-arguments expected it, cli.Command "sub2" --options=[ cli.Option "bar" --short-name="b", ] - --run=:: | app/cli.Application parsed/cli.Parsed | - check-arguments expected parsed, + --run=:: check-arguments expected it ] - --run=:: | app/cli.Application parsed/cli.Parsed | - check-arguments expected parsed + --run=:: check-arguments expected it expected = {"foo": "foo_value"} cmd.run ["sub1", "-f", "foo_value"] @@ -270,8 +256,8 @@ test-subcommands: test-no-option: cmd := cli.Command "test" - --run=:: | app/cli.Application parsed/cli.Parsed | - expect-throw "No option named 'foo'": parsed["foo"] + --run=:: | invocation/cli.Invocation | + expect-throw "No option named 'foo'": invocation["foo"] cmd.run [] cmd = cli.Command "test" @@ -283,8 +269,8 @@ test-no-option: --options=[ cli.Option "bar" --short-name="b", ] - --run=:: | app/cli.Application parsed/cli.Parsed | - expect-throw "No option named 'gee'": parsed["gee"], + --run=:: | invocation/cli.Invocation | + expect-throw "No option named 'gee'": invocation["gee"], ] cmd.run ["sub1", "-b", "bar_value"] @@ -296,8 +282,7 @@ test-invert-flag: --options=[ cli.Flag "foo" --short-name="f", ] - --run=:: | app/cli.Application parsed/cli.Parsed | - check-arguments expected parsed + --run=:: check-arguments expected it expected = {"foo": null} cmd.run [] @@ -312,8 +297,7 @@ test-invert-flag: --options=[ cli.Flag "foo" --short-name="f" --default=true, ] - --run=:: | app/cli.Application parsed/cli.Parsed | - check-arguments expected parsed + --run=:: check-arguments expected it expected = {"foo": true} cmd.run [] @@ -326,7 +310,7 @@ test-invert-non-flag: --options=[ cli.Option "foo" --short-name="f", ] - --run=:: | app/cli.Application parsed/cli.Parsed | + --run=:: | invocation/cli.Invocation | unreachable expect-abort "Cannot invert non-boolean flag --foo.": | ui/cli.Ui | @@ -337,7 +321,7 @@ test-value-for-flag: --options=[ cli.Flag "foo" --short-name="f", ] - --run=:: | app/cli.Application parsed/cli.Parsed | + --run=:: | invocation/cli.Invocation | unreachable expect-abort "Cannot specify value for boolean flag --foo.": | ui/cli.Ui | @@ -348,7 +332,7 @@ test-missing-args: --options=[ cli.Option "foo" --short-name="f", ] - --run=:: | app/cli.Application parsed/cli.Parsed | + --run=:: | invocation/cli.Invocation | unreachable expect-abort "Option --foo requires an argument.": | ui/cli.Ui | @@ -372,8 +356,8 @@ test-dash-arg: --options=[ cli.Option "foo" --short-name="f", ] - --run=:: | app/cli.Application parsed/cli.Parsed | - check-arguments {"foo": "-"} parsed + --run=:: | invocation/cli.Invocation | + check-arguments {"foo": "-"} invocation cmd.run ["-f", "-"] @@ -388,8 +372,8 @@ test-mixed-rest-named: --rest=[ cli.Option "baz" --required, ] - --run=:: | app/cli.Application parsed/cli.Parsed | - check-arguments {"foo": "foo_value", "bar": "bar_value", "baz": "baz_value"} parsed + --run=:: | invocation/cli.Invocation | + check-arguments {"foo": "foo_value", "bar": "bar_value", "baz": "baz_value"} invocation cmd.run ["--foo", "foo_value", "--bar", "bar_value", "baz_value"] cmd.run ["baz_value", "--foo", "foo_value", "--bar", "bar_value"] @@ -403,8 +387,8 @@ test-mixed-rest-named: --rest=[ cli.Option "baz" --required, ] - --run=:: | app/cli.Application parsed/cli.Parsed | - check-arguments {"foo": "foo_value", "bar": "bar_value", "baz": "--foo"} parsed + --run=:: | invocation/cli.Invocation | + check-arguments {"foo": "foo_value", "bar": "bar_value", "baz": "--foo"} invocation // Because of the '--', the rest argument is not interpreted as a named argument. cmd.run ["--foo", "foo_value", "--bar", "bar_value", "--", "--foo"] @@ -415,9 +399,9 @@ test-snake-kebab: cli.Option "foo-bar" --short-name="f", cli.Option "toto_titi" ] - --run=:: | app/cli.Application parsed/cli.Parsed | - check-arguments {"foo-bar": "foo_value", "toto-titi": "toto_value" } parsed - check-arguments {"foo_bar": "foo_value", "toto_titi": "toto_value" } parsed + --run=:: | invocation/cli.Invocation | + check-arguments {"foo-bar": "foo_value", "toto-titi": "toto_value" } invocation + check-arguments {"foo_bar": "foo_value", "toto_titi": "toto_value" } invocation cmd.run ["--foo-bar", "foo_value", "--toto-titi", "toto_value"] cmd.run ["--foo_bar", "foo_value", "--toto_titi", "toto_value"] diff --git a/tests/subcommand_test.toit.toit b/tests/subcommand_test.toit.toit index 4bfdc5e..c7f37d4 100644 --- a/tests/subcommand_test.toit.toit +++ b/tests/subcommand_test.toit.toit @@ -5,7 +5,7 @@ import cli import expect show * -check-arguments expected/Map parsed/cli.Parsed: +check-arguments expected/Map parsed/cli.Invocation: expected.do: | key value | expect-equals value parsed[key]