toys 0.21.0 → 0.23.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +124 -0
- data/LICENSE.md +1 -1
- data/README.md +101 -67
- data/builtins/do.rb +208 -2
- data/builtins/system/bash-completion.rb +1 -1
- data/builtins/system/test.rb +2 -2
- data/builtins/system/tools.rb +2 -2
- data/builtins/system/zsh-completion.rb +22 -22
- data/core-docs/toys/acceptor.rb +20 -29
- data/core-docs/toys/arg_parser.rb +61 -48
- data/core-docs/toys/cli.rb +314 -163
- data/core-docs/toys/completion.rb +64 -67
- data/core-docs/toys/context.rb +88 -40
- data/core-docs/toys/core.rb +3 -3
- data/core-docs/toys/dsl/base.rb +6 -6
- data/core-docs/toys/dsl/flag.rb +4 -3
- data/core-docs/toys/dsl/flag_group.rb +3 -3
- data/core-docs/toys/dsl/positional_arg.rb +2 -2
- data/core-docs/toys/dsl/tool.rb +227 -88
- data/core-docs/toys/errors.rb +72 -61
- data/core-docs/toys/flag.rb +24 -34
- data/core-docs/toys/flag_group.rb +15 -19
- data/core-docs/toys/input_file.rb +8 -12
- data/core-docs/toys/loader/load_state.rb +4 -0
- data/core-docs/toys/loader/tool_registry.rb +4 -0
- data/core-docs/toys/loader.rb +88 -326
- data/core-docs/toys/middleware.rb +13 -40
- data/core-docs/toys/mixin.rb +12 -7
- data/core-docs/toys/module_lookup.rb +3 -3
- data/core-docs/toys/positional_arg.rb +13 -10
- data/core-docs/toys/runner.rb +159 -0
- data/core-docs/toys/source_info/origin.rb +131 -0
- data/core-docs/toys/source_info.rb +67 -160
- data/core-docs/toys/source_list.rb +72 -0
- data/core-docs/toys/source_spec.rb +317 -0
- data/core-docs/toys/standard_middleware/add_verbosity_flags.rb +4 -4
- data/core-docs/toys/standard_middleware/apply_config.rb +36 -6
- data/core-docs/toys/standard_middleware/handle_usage_errors.rb +3 -3
- data/core-docs/toys/standard_middleware/set_default_descriptions.rb +2 -2
- data/core-docs/toys/standard_middleware/show_help.rb +54 -54
- data/core-docs/toys/standard_middleware/show_root_version.rb +3 -3
- data/core-docs/toys/standard_mixins/bundler.rb +7 -4
- data/core-docs/toys/standard_mixins/exec.rb +14 -14
- data/core-docs/toys/standard_mixins/fileutils.rb +3 -3
- data/core-docs/toys/standard_mixins/gems.rb +55 -4
- data/core-docs/toys/standard_mixins/git_cache.rb +3 -3
- data/core-docs/toys/standard_mixins/highline.rb +4 -4
- data/core-docs/toys/standard_mixins/pager.rb +3 -3
- data/core-docs/toys/standard_mixins/terminal.rb +6 -6
- data/core-docs/toys/standard_mixins/xdg.rb +3 -3
- data/core-docs/toys/template.rb +28 -16
- data/core-docs/toys/tool_definition.rb +112 -111
- data/core-docs/toys/tool_name_splitter.rb +82 -0
- data/core-docs/toys/unique_key.rb +35 -0
- data/core-docs/toys/utils/completion_engine.rb +24 -13
- data/core-docs/toys/utils/exec.rb +30 -12
- data/core-docs/toys/utils/gems.rb +52 -45
- data/core-docs/toys/utils/git_cache.rb +163 -163
- data/core-docs/toys/utils/help_text.rb +24 -15
- data/core-docs/toys/utils/pager.rb +2 -2
- data/core-docs/toys/utils/standard_ui.rb +47 -34
- data/core-docs/toys/utils/terminal.rb +8 -8
- data/core-docs/toys/utils/xdg.rb +25 -16
- data/core-docs/toys/wrappable_string.rb +4 -4
- data/core-docs/toys-core.rb +66 -20
- data/docs/guide.md +1964 -1342
- data/lib/toys/standard_cli.rb +28 -49
- data/lib/toys/templates/clean.rb +1 -1
- data/lib/toys/templates/gem_build.rb +46 -31
- data/lib/toys/templates/minitest.rb +10 -10
- data/lib/toys/templates/rake.rb +9 -9
- data/lib/toys/templates/rdoc.rb +5 -5
- data/lib/toys/templates/rspec.rb +10 -10
- data/lib/toys/templates/rubocop.rb +8 -8
- data/lib/toys/templates/yardoc.rb +28 -28
- data/lib/toys/testing.rb +25 -6
- data/lib/toys/version.rb +1 -1
- data/lib/toys.rb +2 -1
- metadata +15 -8
- data/core-docs/toys/settings.rb +0 -543
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: ee5fe663d934949887ff7ce01289453e7b59efcc553331e2efb8a8ae949b626b
|
|
4
|
+
data.tar.gz: c34a38d893789bdb14fc159f211ac83b09a39b2d897601c961755876be2b6d28
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 0fd0c5c2edfe80ede91db110a513bfe176766b4d3c8a75e378721296b8fe4c7c5681e07695fa249f9efede159ffb9feff43524d3ad085f98ca7498af9433c3d1
|
|
7
|
+
data.tar.gz: 5345ecacee610f36dd439f7adf8b1118a1cc0ecb858f19a5f3c938a5e86a33fb19be94fff5173e3170d1bac6b8ed4309fc54192f5542573e15a7109ddeaba9e2
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,129 @@
|
|
|
1
1
|
# Release History
|
|
2
2
|
|
|
3
|
+
### v0.23.0 / 2026-09-09
|
|
4
|
+
|
|
5
|
+
This is a major release, with several new features. It also includes a significant refactor of some of the underlying layers, which should be mostly invisible. It is a release candidate for the upcoming version 1.0.
|
|
6
|
+
|
|
7
|
+
Highlights include:
|
|
8
|
+
|
|
9
|
+
* Added the ability to treat unknown flags as positional args via the `treat_unknown_flags_as_args` DSL directive. Useful for wrapping and delegating to other commands.
|
|
10
|
+
* The `do` builtin tool supports `--gem=`, `--git=`, and `--path=` flags that make additional tool sources available.
|
|
11
|
+
* Running tools from within a tool should generally now be done via the new `Toys::Runner` object, now available in the runtime context, instead of via the `Toys::CLI`. The new object provides finer-grained control over error handling and verbosity.
|
|
12
|
+
* Updates to error handling to improve the output and fix some long-standing issues related to tool delegation.
|
|
13
|
+
* Several fixes to bundler integration, especially with Bundler 4.
|
|
14
|
+
|
|
15
|
+
Details follow.
|
|
16
|
+
|
|
17
|
+
* Changes to tool definition:
|
|
18
|
+
* Added the `treat_unknown_flags_as_args` DSL directive (and the corresponding `ToolDefinition#treat_unknown_flags_as_args` setter and `ToolDefinition#unknown_flags_are_args?` query), which redirects unrecognized flags to the tool's positional arguments instead of reporting a usage error. Useful for wrapping and delegating to other commands.
|
|
19
|
+
* The `set_context_directory` DSL directive can now take Pathname arguments and handles relative paths (by expanding to absolute paths).
|
|
20
|
+
* When two or more flags are declared using the same context key, within a flag group, the flag group's requirements now evaluate correctly.
|
|
21
|
+
* A flag or optional/remaining positional argument declared with a `nil` default no longer clears default data already set for the same key by an earlier declaration.
|
|
22
|
+
* Toys now raises `Toys::ToolDefinitionError` if you attempt to "reset" a tool previously defined using a `Toys::Tool` subclass.
|
|
23
|
+
* SECURITY FIX: Toys now raises `Toys::ToolSourceError` if a tool loaded from a RubyGem attempts to load tools from the local file system.
|
|
24
|
+
* BREAKING CHANGE: Toys now raises `Toys::ToolDefintionError` if you attempt to create or descend into a subtool from a `subtool_apply` block or a middleware-based config.
|
|
25
|
+
* Caught additional cases of incorrectly located `Toys::Tool` subclasses and raised `Toys::ToolDefinitionError`
|
|
26
|
+
* Toys directory contents are loaded in deterministic (sorted) order.
|
|
27
|
+
|
|
28
|
+
* Changes to tool execution:
|
|
29
|
+
* Added `Toys::Context#runner` for access to the `Toys::Runner` object. This is now the preferred way to run tools from within other tools (instead of `Toys::CLI#run`), unless you need to change the configuration.
|
|
30
|
+
* Added `Toys::Context#loader` for access to the `Toys::Loader` object.
|
|
31
|
+
* BREAKING CHANGE: `Toys::Context#context_directory`, and `Toys::Context#get` with the associated context key, now never return nil. If no context directory is set for the tool, these now return the current working directory.
|
|
32
|
+
* BREAKING CHANGE: Removed git- and gem- specific attributes from `Toys::SourceInfo` and replaced with `Toys::SourceInfo#origin`.
|
|
33
|
+
* Standard context keys and sentinels use the new class `Toys::UniqueKey`, which displays useful names in diagnostic output.
|
|
34
|
+
* Delegation now propagates the caller's verbosity to the delegate target, rather than resetting it to zero.
|
|
35
|
+
* When a tool delegates, a `SignalException` unhandled by the inner tool now propagates outward so each tool in the delegation chain gets a chance at its own `on_interrupt` / `on_signal` handler.
|
|
36
|
+
* A nested run that shares a logger with the run that called it now uses the base level already in effect for that logger, so verbosity no longer compounds across nested runs.
|
|
37
|
+
* Revamped the output and stack trace printed when an exception occurs.
|
|
38
|
+
|
|
39
|
+
* Changes to shell completion:
|
|
40
|
+
* Fixed shell completion for non-flag words containing `=` or `:`: completions are now computed against the whole word, and the engine trims candidates to the span the shell will actually replace.
|
|
41
|
+
* Zsh completion now replaces the entire word rather than only the text after an `=` or `:`, matching how zsh actually handles word breaks; bash continues to break at `=` and `:`.
|
|
42
|
+
* Word-break trimming for bash now ignores `=` and `:` characters that were quoted or backslash-escaped, since the shell does not break a word at a quoted character.
|
|
43
|
+
* Subtool completion now uses the loader's configured delimiters to decide where a tool path ends, rather than a separate regex over the fragment prefix, so completing a partially typed tool path works consistently with any configured extra delimiters.
|
|
44
|
+
|
|
45
|
+
* Changes to builtin tools:
|
|
46
|
+
* The `do` builtin tool supports `--gem=`, `--git=`, and `--path=` flags that make additional tool sources available.
|
|
47
|
+
|
|
48
|
+
* Changes to CLI interfaces:
|
|
49
|
+
* `Toys::CLI#child` accepts `copy_sources: true`, which populates the new CLI's loader with the same sources as the original. This makes it easy to create a new CLI with additional sources on top of the current.
|
|
50
|
+
* You can get the `Toys::Runner` for a CLI using `Toys::CLI#runner`, providing a way to customize the run process more closely than using `Toys::CLI#run`.
|
|
51
|
+
* Added `add_source` method that takes an instance of the new `Toys::SourceSpec` types that describe a tool source. This method replaces the now deprecated `add_config_*` methods (although those methods will continue to be supported for a time). In general, the "config" terminology is being retired in favor of "source" which I think better describes what is going on.
|
|
52
|
+
* BREAKING CHANGE: The `config_file_name` and `config_dir_name` arguments to CLI have been renamed to `toplevel_tool_file_name` and `toplevel_tool_dir_name`, respectively, as part of a general removal of the "config" term. The old names are not aliased.
|
|
53
|
+
* BREAKING CHANGE: `Toys::CLI#run` no longer accepts the `delegated_from:` keyword argument. Tool delegation is now handled internally and its runtime is not exposed in the public interface.
|
|
54
|
+
* BREAKING CHANGE: Renamed the `StandardCLI::CONFIG_DIR_NAME` to `StandardCLI::TOPLEVEL_TOOL_DIR_NAME` and `StandardCLI::CONFIG_FILE_NAME` to `StandardCLI::TOPLEVEL_TOOL_FILE_NAME`.
|
|
55
|
+
* BREAKING CHANGE: Removed the constants `INDEX_FILE_NAME`, `PRELOAD_DIR_NAME`, `PRELOAD_FILE_NAME`, `DATA_DIR_NAME`, and `LIB_DIR_NAME` from `StandardCLI`.
|
|
56
|
+
* The `Toys::StandardCLI` constructor now takes `git_cache` and `gems_util` keyword arguments letting you customize the objects used to resolve git and gem sources.
|
|
57
|
+
* Path parameters for the methods that add sources, such as `path` and `context_directory`, can now take Pathname objects and can handle relative paths properly (by expanding them into absolute paths).
|
|
58
|
+
|
|
59
|
+
* Rubygems/Bundler integration changes:
|
|
60
|
+
* Bundler integration requires Bundler 2.4 or later. This is the default on Ruby 3.2, but requires that Bundler is updated on earlier Rubies.
|
|
61
|
+
* Bundler integration properly detects provenance attributes such as `path:` and `git:`.
|
|
62
|
+
* Calling bundler integration with a nonempty group list no longer loses gems that were already loaded.
|
|
63
|
+
* Bundler integration now honors `BUNDLE_LOCKFILE` (new in Bundler 4) and stops leaking it from the modified bundle.
|
|
64
|
+
|
|
65
|
+
* Other fixes
|
|
66
|
+
* Terminal mixin now raises the correct ArgumentError (instead of NameError) if given an unknown style code.
|
|
67
|
+
* `Toys::Loader#list_subtools` now ensures returned tools have been finished by their middleware. This affects listing tools not underneath the currently running tool.
|
|
68
|
+
|
|
69
|
+
A number of additional minor breaking changes were made to internal interfaces in the toys-core gem classes. It is unlikely you will encounter these unless you are extending the framework, but you can see the toys-core changelog for details.
|
|
70
|
+
|
|
71
|
+
### v0.22.0 / 2026-05-05
|
|
72
|
+
|
|
73
|
+
Toys 0.22 is a major release focused on polish and cleanup in preparation for version 1.0. It includes a number of small breaking changes where needed to clean up the interfaces. (Note that many of the changes listed below are actually in the `toys-core` gem.)
|
|
74
|
+
|
|
75
|
+
Breaking interface changes:
|
|
76
|
+
|
|
77
|
+
* The `static` directive no longer "forces" creating of helper methods, instead using the same logic as the flag and argument directives. In particular, by default it will not create a method starting with underscore.
|
|
78
|
+
* Removed `Toys::Settings` and moved inheritable_helper_methods to a dedicated tool attribute. Use the `basic_settings` gem if you need the old settings class.
|
|
79
|
+
* `Toys::Acceptor#create` no longer takes a hash as the spec. (This was undocumented but worked previously.) To construct an object, pass the class as the main spec object, and the keyword arguments in the options.
|
|
80
|
+
* `Toys::Acceptor::Enum#values` now returns the values themselves instead of a zipped array that includes strings.
|
|
81
|
+
* `Toys::FlagGroup::Base` is no longer instantiable; use `Toys::FlagGroup::Optional` instead.
|
|
82
|
+
* The `acceptor` and `display_name` attributes of `Toys::PositionalArg` are no longer writable and must be set in the constructor.
|
|
83
|
+
* `Toys::Completion#create` no longer takes a hash as the spec. (This was undocumented but worked previously.) To construct an object, pass the class as the main spec object, and the keyword arguments in the options.
|
|
84
|
+
* The `delegation_target` attribute of `Toys::ToolDefinition::DefaultCompletion` is no longer writable and must be set in the constructor.
|
|
85
|
+
* You can no longer override a completion class using the `:""` key. (This was undocumented but worked previously.) Just pass a fully constructed completion object if you need a custom object.
|
|
86
|
+
* The original `Module#include` method is now available in the DSL as `include_module` instead of `super_include`.
|
|
87
|
+
* Template classes no longer automatically include `Toys::Context::Key`. This behavior was undocumented and inconsistent between different ways of defining templates.
|
|
88
|
+
* Made `Toys::CLI#load_tool` return the block value rather than the exit_code.
|
|
89
|
+
* Made `Toys::Testing#toys_load_tool` return the block value rather than the exit_code
|
|
90
|
+
* Dropped undocumented `Loader#add_path_set` option to include individual source names in the relative_paths argument.
|
|
91
|
+
* Renamed `use_less` in the `Toys::StandardMiddleware::ShowHelp` constructor to `use_pager`, and added support for custom pagers.
|
|
92
|
+
* Renamed many of the methods in `StandardUI` to reduce confusion.
|
|
93
|
+
* Removed `LoaderError` and used `ToolDefinitionError` for those cases, as it was decided that the distinction was ambiguous and dependent on internal details.
|
|
94
|
+
* Removed `ContextualError#underlying_error` as it was synonymous with `cause`.
|
|
95
|
+
* Consolidated `ContextualError` public interface into a single `capture` method.
|
|
96
|
+
* `ArgParser::ExtraArgumentsError` now takes `:arguments` instead of `:values`, and provides an accessor for the same.
|
|
97
|
+
* `ArgParser::ToolUnrecognizedError` now takes `:full_name` instead of `:values`, and provides an accessor for the same.
|
|
98
|
+
* Renamed `ArgParser::UsageError#full_message` to `message_with_suggestions` to avoid overriding `Exception#full_message`.
|
|
99
|
+
|
|
100
|
+
New features:
|
|
101
|
+
|
|
102
|
+
* The range acceptor supports beginless and endless ranges.
|
|
103
|
+
* A custom source_name can be provided to `Loader#add_path_set`, `Loader#add_git`, and `Loader#add_gem`.
|
|
104
|
+
* The `:gems` mixin provides a context key for retrieving the underlying `Toys::Utils::Gems` service object.
|
|
105
|
+
* The `:gems` mixin provides an explicit `Toys::Utils::Gems::ClassMethods` module defining the directives added to the tool class.
|
|
106
|
+
* The `activate` and `bundle` methods in the Gems utility now return useful results.
|
|
107
|
+
|
|
108
|
+
Other fixes:
|
|
109
|
+
|
|
110
|
+
* The `:update` argument to the `load_git` directive had no effect if the `:as` argument was provided.
|
|
111
|
+
* `FlagValueUnacceptableError` and `ArgValueUnacceptableError` have their value set correctly.
|
|
112
|
+
* If a CLI has a static (shared) logger, creating a child with `logger: nil` actually clears it.
|
|
113
|
+
* Removed unused `Toys::StandardMiddleware::ShowHelp::TOOL_NAME_KEY`.
|
|
114
|
+
* ShowHelp middleware displays an error message instead of raising an exception when help cannot be generated due to a bad search regex.
|
|
115
|
+
* `ArgParser::UsageError` is now a proper exception.
|
|
116
|
+
* `ContextualError` does a better job of capturing relevant syntax errors.
|
|
117
|
+
* Various minor completion system fixes.
|
|
118
|
+
|
|
119
|
+
Several libraries under `toys/utils` were extracted into their own gems. The current code remains under `Toys::Utils` as a vendored copy of the external gem, so the extracted gems are not actually dependencies of Toys. The affected libraries are:
|
|
120
|
+
|
|
121
|
+
* `Toys::Utils::Exec` which was extracted to the gem `exec_service`.
|
|
122
|
+
* `Toys::Utils::XDG` which was extracted to the gem `simple_xdg`.
|
|
123
|
+
* `Toys::Utils::GitCache` which was extracted to the gem `git_cache`.
|
|
124
|
+
|
|
125
|
+
Finally, a variety of clarifications and fixes were made to the reference documentation, readme, and user's guide.
|
|
126
|
+
|
|
3
127
|
### v0.21.0 / 2026-03-23
|
|
4
128
|
|
|
5
129
|
This release includes a variety of small fixes and updates toward improving product polish in preparation for a 1.0 release. It focuses on the following areas:
|
data/LICENSE.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# License
|
|
2
2
|
|
|
3
|
-
Copyright 2019-
|
|
3
|
+
Copyright 2019-2026 Daniel Azuma and the Toys contributors
|
|
4
4
|
|
|
5
5
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
6
|
of this software and associated documentation files (the "Software"), to deal
|
data/README.md
CHANGED
|
@@ -23,12 +23,16 @@ Here's a tutorial to help you get a feel of what Toys can do.
|
|
|
23
23
|
|
|
24
24
|
Install the **toys** gem using:
|
|
25
25
|
|
|
26
|
-
|
|
26
|
+
```
|
|
27
|
+
$ gem install toys
|
|
28
|
+
```
|
|
27
29
|
|
|
28
30
|
This installs the `toys` executable, along with some built-in tools and
|
|
29
31
|
libraries. You can run the executable immediately:
|
|
30
32
|
|
|
31
|
-
|
|
33
|
+
```
|
|
34
|
+
$ toys
|
|
35
|
+
```
|
|
32
36
|
|
|
33
37
|
This displays overall help for Toys. If you have `less` installed, Toys will
|
|
34
38
|
use it to display the help screen. Press `q` to exit.
|
|
@@ -36,19 +40,25 @@ use it to display the help screen. Press `q` to exit.
|
|
|
36
40
|
You may notice that the help screen lists some tools that are pre-installed.
|
|
37
41
|
Let's run one of them:
|
|
38
42
|
|
|
39
|
-
|
|
43
|
+
```
|
|
44
|
+
$ toys system version
|
|
45
|
+
```
|
|
40
46
|
|
|
41
47
|
The `system version` tool displays the current version of the toys gem.
|
|
42
48
|
|
|
43
|
-
Toys also provides optional tab completion for bash. To install
|
|
44
|
-
following command in your shell, or add it to your bash
|
|
45
|
-
(e.g. `~/.bashrc`).
|
|
49
|
+
Toys also provides optional tab completion for bash and zsh. To install for
|
|
50
|
+
bash, execute the following command in your shell, or add it to your bash
|
|
51
|
+
configuration file (e.g. `~/.bashrc`).
|
|
46
52
|
|
|
47
|
-
|
|
53
|
+
```
|
|
54
|
+
$(toys system bash-completion install)
|
|
55
|
+
```
|
|
48
56
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
57
|
+
To install for zsh, execute this or add to your `~/.zshrc`.
|
|
58
|
+
|
|
59
|
+
```
|
|
60
|
+
$(toys system zsh-completion install)
|
|
61
|
+
```
|
|
52
62
|
|
|
53
63
|
### Write your first tool
|
|
54
64
|
|
|
@@ -56,32 +66,42 @@ You can define tools by creating a *Toys file*. Go into any directory, and,
|
|
|
56
66
|
using your favorite editor, create a new file called `.toys.rb` (note the
|
|
57
67
|
leading period). Copy the following text into the file, and save it:
|
|
58
68
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
69
|
+
```ruby
|
|
70
|
+
tool "greet" do
|
|
71
|
+
desc "My first tool!"
|
|
72
|
+
flag :whom, default: "world"
|
|
73
|
+
def run
|
|
74
|
+
puts "Hello, #{whom}!"
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
```
|
|
66
78
|
|
|
67
79
|
This defines a tool named "greet". Try running it:
|
|
68
80
|
|
|
69
|
-
|
|
81
|
+
```
|
|
82
|
+
$ toys greet
|
|
83
|
+
```
|
|
70
84
|
|
|
71
85
|
The tool also recognizes a flag on the command line. Try this:
|
|
72
86
|
|
|
73
|
-
|
|
87
|
+
```
|
|
88
|
+
$ toys greet --whom=ruby
|
|
89
|
+
```
|
|
74
90
|
|
|
75
91
|
Toys provides a rich set of features for defining command line arguments and
|
|
76
92
|
flags. It can also validate arguments. Try this:
|
|
77
93
|
|
|
78
|
-
|
|
94
|
+
```
|
|
95
|
+
$ toys greet --bye
|
|
96
|
+
```
|
|
79
97
|
|
|
80
98
|
Notice that Toys automatically generated a usage summary for your tool. It also
|
|
81
99
|
automatically generates a full help screen, which you can view using the
|
|
82
100
|
`--help` flag:
|
|
83
101
|
|
|
84
|
-
|
|
102
|
+
```
|
|
103
|
+
$ toys greet --help
|
|
104
|
+
```
|
|
85
105
|
|
|
86
106
|
Toys searches up the directory hierarchy for Toys files. So it will find this
|
|
87
107
|
`.toys.rb` if you are located in this directory or any subdirectory. It will
|
|
@@ -99,53 +119,59 @@ likely to see in real-world usage. Add the following to your `.toys.rb` file.
|
|
|
99
119
|
(You don't need to replace the greet tool you just wrote; just add this new
|
|
100
120
|
tool to the end of the file.)
|
|
101
121
|
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
puts "Created repo in #{name}", :green, :bold
|
|
126
|
-
end
|
|
127
|
-
|
|
128
|
-
def create_repo
|
|
129
|
-
exec "git init"
|
|
130
|
-
File.write ".gitignore", <<~CONTENT
|
|
131
|
-
tmp
|
|
132
|
-
.DS_Store
|
|
133
|
-
CONTENT
|
|
134
|
-
# You can add additional files here.
|
|
135
|
-
exec "git add ."
|
|
136
|
-
exec "git commit -m 'Initial commit'"
|
|
137
|
-
end
|
|
122
|
+
```ruby
|
|
123
|
+
tool "new-repo" do
|
|
124
|
+
desc "Create a new git repo"
|
|
125
|
+
|
|
126
|
+
optional_arg :name, desc: "Name of the directory to create"
|
|
127
|
+
|
|
128
|
+
include :exec, exit_on_nonzero_status: true
|
|
129
|
+
include :fileutils
|
|
130
|
+
include :terminal
|
|
131
|
+
|
|
132
|
+
def run
|
|
133
|
+
if name.nil?
|
|
134
|
+
response = ask "Please enter a directory name: "
|
|
135
|
+
set :name, response
|
|
136
|
+
end
|
|
137
|
+
if File.exist? name
|
|
138
|
+
puts "Aborting because #{name} already exists", :red, :bold
|
|
139
|
+
exit 1
|
|
140
|
+
end
|
|
141
|
+
logger.info "Creating new repo in directory #{name}..."
|
|
142
|
+
mkdir name
|
|
143
|
+
cd name do
|
|
144
|
+
create_repo
|
|
138
145
|
end
|
|
146
|
+
puts "Created repo in #{name}", :green, :bold
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
def create_repo
|
|
150
|
+
exec "git init"
|
|
151
|
+
File.write ".gitignore", <<~CONTENT
|
|
152
|
+
tmp
|
|
153
|
+
.DS_Store
|
|
154
|
+
CONTENT
|
|
155
|
+
# You can add additional files here.
|
|
156
|
+
exec "git add ."
|
|
157
|
+
exec "git commit -m 'Initial commit'"
|
|
158
|
+
end
|
|
159
|
+
end
|
|
160
|
+
```
|
|
139
161
|
|
|
140
162
|
Now you should have an additional tool called `new-repo` available. Type:
|
|
141
163
|
|
|
142
|
-
|
|
164
|
+
```
|
|
165
|
+
$ toys
|
|
166
|
+
```
|
|
143
167
|
|
|
144
168
|
The help screen lists both the `greet` tool we started with, and the new
|
|
145
169
|
`new-repo` tool. This new tool creates a directory containing a newly created
|
|
146
170
|
git repo. (It assumes you have `git` available on your path.) Try running it:
|
|
147
171
|
|
|
148
|
-
|
|
172
|
+
```
|
|
173
|
+
$ toys new-repo foo
|
|
174
|
+
```
|
|
149
175
|
|
|
150
176
|
That should create a directory `foo`, initialize a git repository within it,
|
|
151
177
|
and make a commit.
|
|
@@ -155,7 +181,9 @@ any combination of flags and required and optional arguments. This tool's
|
|
|
155
181
|
argument is declared with a description string, which you can see if you view
|
|
156
182
|
the tool's help:
|
|
157
183
|
|
|
158
|
-
|
|
184
|
+
```
|
|
185
|
+
$ toys new-repo --help
|
|
186
|
+
```
|
|
159
187
|
|
|
160
188
|
The argument is marked as "optional" which means you can omit it. Notice that
|
|
161
189
|
the tool's code detects that it has been omitted and responds by prompting you
|
|
@@ -172,7 +200,7 @@ than modules. These symbols are the names of some of Toys's built-in helper
|
|
|
172
200
|
*mixins*, which are configurable modules that enhance your tool. They may
|
|
173
201
|
provide methods your tool can call, or invoke other behavior. In our example:
|
|
174
202
|
|
|
175
|
-
*
|
|
203
|
+
* The `:exec` mixin provides a variety of methods for running external
|
|
176
204
|
commands. In this example, we use the `exec` method to run shell
|
|
177
205
|
commands, but you can also signal and control these commands, capture
|
|
178
206
|
and redirect streams, and so forth. Note that we pass the
|
|
@@ -181,10 +209,10 @@ provide methods your tool can call, or invoke other behavior. In our example:
|
|
|
181
209
|
to `set -e` in bash). This is a common pattern when writing tools that
|
|
182
210
|
invoke external commands. (If you want more control, the `:exec` mixin also
|
|
183
211
|
provides ways to respond to result codes individually.)
|
|
184
|
-
*
|
|
212
|
+
* The `:fileutils` mixin provides the methods of the Ruby `FileUtils`
|
|
185
213
|
library, such as `mkdir` and `cd` used in this example. It's effectively
|
|
186
214
|
shorthand for `require "fileutils"; include ::FileUtils`.
|
|
187
|
-
*
|
|
215
|
+
* The `:terminal` mixin provides styled output, as you can see with the style
|
|
188
216
|
codes being passed to `puts`. It also provides some user interaction
|
|
189
217
|
commands such as `ask`, as well as spinners and other controls. You can see
|
|
190
218
|
operation of the `:terminal` mixin in the tool's output, which is styled
|
|
@@ -193,7 +221,9 @@ provide methods your tool can call, or invoke other behavior. In our example:
|
|
|
193
221
|
|
|
194
222
|
Now try running this:
|
|
195
223
|
|
|
196
|
-
|
|
224
|
+
```
|
|
225
|
+
$ toys new-repo bar --verbose
|
|
226
|
+
```
|
|
197
227
|
|
|
198
228
|
You'll notice some diagnostic log output. Toys provides a standard Ruby Logger
|
|
199
229
|
for each tool, and you can use it to emit diagnostic logs directly as
|
|
@@ -219,14 +249,18 @@ If you have a project with a Rakefile, move into that directory and create a
|
|
|
219
249
|
new file called `.toys.rb` in that same directory (next to the Rakefile). Add
|
|
220
250
|
the following line to your `.toys.rb` file:
|
|
221
251
|
|
|
222
|
-
|
|
252
|
+
```ruby
|
|
253
|
+
expand :rake
|
|
254
|
+
```
|
|
223
255
|
|
|
224
256
|
This syntax is called a "template expansion." It's a way to generate tools
|
|
225
257
|
programmatically. In this case, Toys provides the `:rake` template, which reads
|
|
226
258
|
your Rakefile and generates Toys tools corresponding to all your Rake tasks!
|
|
227
259
|
Now if you run:
|
|
228
260
|
|
|
229
|
-
|
|
261
|
+
```
|
|
262
|
+
$ toys
|
|
263
|
+
```
|
|
230
264
|
|
|
231
265
|
You'll see that you now have tools associated with each of your Rake tasks. So
|
|
232
266
|
if you have a `rake test` task, you can run it using `toys test`.
|
|
@@ -326,7 +360,7 @@ because it has a few known bugs that affect Toys.
|
|
|
326
360
|
|
|
327
361
|
## License
|
|
328
362
|
|
|
329
|
-
Copyright 2019-
|
|
363
|
+
Copyright 2019-2026 Daniel Azuma and the Toys contributors
|
|
330
364
|
|
|
331
365
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
332
366
|
of this software and associated documentation files (the "Software"), to deal
|
data/builtins/do.rb
CHANGED
|
@@ -15,7 +15,18 @@ long_desc \
|
|
|
15
15
|
"You may change the delimiter using the --delim flag. For example:",
|
|
16
16
|
[" toys do --delim=/ rails build --staging / deploy --migrate"],
|
|
17
17
|
"The --delim flag must appear first before the tools to run. Any flags that appear later in" \
|
|
18
|
-
" the command line will be passed to the tools themselves."
|
|
18
|
+
" the command line will be passed to the tools themselves.",
|
|
19
|
+
"",
|
|
20
|
+
"You may also make additional tools available to the tools you run, by passing the --gem," \
|
|
21
|
+
" --git, and --path flags. Each adds a source of tools, which takes priority over the" \
|
|
22
|
+
" tools that would otherwise be found. The three flags may be repeated and interleaved," \
|
|
23
|
+
" and the source added by the leftmost flag takes priority over the sources added by the" \
|
|
24
|
+
" flags to its right. For example:",
|
|
25
|
+
[" toys do --gem=my-tools --git=https://github.com/dazuma/example deploy --migrate"],
|
|
26
|
+
"Here, a tool defined by both sources is taken from the gem. See the descriptions of the" \
|
|
27
|
+
" individual flags below for the syntax of their values. Note that the commas within those" \
|
|
28
|
+
" values are part of the flag value, and are unrelated to the delimiter that separates the" \
|
|
29
|
+
" tools to run."
|
|
19
30
|
|
|
20
31
|
flag :delim do
|
|
21
32
|
flags "-d", "--delim=VALUE"
|
|
@@ -24,6 +35,67 @@ flag :delim do
|
|
|
24
35
|
long_desc "Sets the delimiter that separates tool invocations. The default value is \",\"."
|
|
25
36
|
end
|
|
26
37
|
|
|
38
|
+
# The source flags all share the :sources key, and their handlers append to
|
|
39
|
+
# the same array, tagging each value with the kind of source requested. That
|
|
40
|
+
# way the array records the order in which the flags appeared on the command
|
|
41
|
+
# line, across all of the flags, which is the order that determines priority.
|
|
42
|
+
# The handlers store the raw value; parsing happens later, in build_cli,
|
|
43
|
+
# because an error raised from a handler would come out of the argument parser
|
|
44
|
+
# as a stack trace rather than as a simple message.
|
|
45
|
+
|
|
46
|
+
flag :sources do
|
|
47
|
+
flags "--gem=GEM"
|
|
48
|
+
handler { |val, prev| prev + [[:gem, val]] }
|
|
49
|
+
default []
|
|
50
|
+
desc "Make the tools from the given gem available"
|
|
51
|
+
long_desc \
|
|
52
|
+
"Adds the tools from the given gem, prompting to install the gem if it is not present.",
|
|
53
|
+
"",
|
|
54
|
+
"The value is the gem name, optionally followed by any number of version requirements," \
|
|
55
|
+
" all separated by commas. Whitespace surrounding each element is ignored. The version" \
|
|
56
|
+
" requirements use the same syntax as Rubygems and Bundler. For example:",
|
|
57
|
+
[" --gem=\"my-tools, ~> 1.5, >= 1.5.2\""]
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
flag :sources do
|
|
61
|
+
flags "--git=SPEC"
|
|
62
|
+
handler { |val, prev| prev + [[:git, val]] }
|
|
63
|
+
default []
|
|
64
|
+
desc "Make the tools from the given git repository available"
|
|
65
|
+
long_desc \
|
|
66
|
+
"Adds the tools from the given git repository, fetching the repository into a local cache" \
|
|
67
|
+
" if it is not already there.",
|
|
68
|
+
"",
|
|
69
|
+
"The value is the git remote (i.e. the repository URL or path), optionally followed by any" \
|
|
70
|
+
" number of \"key=value\" elements, all separated by commas. Whitespace surrounding each" \
|
|
71
|
+
" element, and surrounding each equals sign, is ignored. For example:",
|
|
72
|
+
[" --git=\"https://github.com/dazuma/example, path=toys, commit=main\""],
|
|
73
|
+
"The recognized keys are:",
|
|
74
|
+
[" path The file or directory within the repository to load. By default, the"],
|
|
75
|
+
[" entire repository is loaded."],
|
|
76
|
+
[" commit The SHA, tag, or branch to load. By default, the repository head is used."],
|
|
77
|
+
[" update Whether to refresh a previously cached repository. Pass \"true\" or \"false\","],
|
|
78
|
+
[" or a number of seconds, to refresh only if the cache is at least that old."],
|
|
79
|
+
[" The default is \"false\"."],
|
|
80
|
+
"Unlike --gem, which takes its elements positionally, the elements here are named, because" \
|
|
81
|
+
" a git source has three independent optional fields and a positional syntax could not" \
|
|
82
|
+
" express, for example, a path with no commit.",
|
|
83
|
+
"",
|
|
84
|
+
"There is no way to escape a comma appearing within the value."
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
flag :sources do
|
|
88
|
+
flags "--path=PATH"
|
|
89
|
+
handler { |val, prev| prev + [[:path, val]] }
|
|
90
|
+
default []
|
|
91
|
+
complete_values :file_system
|
|
92
|
+
desc "Make the tools from the given path available"
|
|
93
|
+
long_desc \
|
|
94
|
+
"Adds the tools from the given file system path. The path must name either a directory" \
|
|
95
|
+
" of tools, or a single Ruby file defining tools. For example:",
|
|
96
|
+
[" --path=/path/to/my-tools"]
|
|
97
|
+
end
|
|
98
|
+
|
|
27
99
|
remaining_args :commands do
|
|
28
100
|
complete do |context|
|
|
29
101
|
commands = context.arg_parser.data[:commands]
|
|
@@ -37,10 +109,144 @@ end
|
|
|
37
109
|
enforce_flags_before_args
|
|
38
110
|
|
|
39
111
|
def run
|
|
112
|
+
tool_cli = build_cli
|
|
40
113
|
commands
|
|
41
114
|
.chunk { |arg| arg == delim ? :_separator : true }
|
|
42
115
|
.each do |_, action|
|
|
43
|
-
code =
|
|
116
|
+
code = tool_cli.run(action)
|
|
44
117
|
exit(code) unless code.zero?
|
|
45
118
|
end
|
|
46
119
|
end
|
|
120
|
+
|
|
121
|
+
# Returns the CLI used to run the requested tools. Normally this is simply the
|
|
122
|
+
# current CLI, but if any sources were requested, we need a new CLI because
|
|
123
|
+
# sources cannot be added to a CLI that has already started loading tools. The
|
|
124
|
+
# new CLI copies the current sources and adds the requested ones on top of
|
|
125
|
+
# them. The requested sources are added in reverse order so that the first flag
|
|
126
|
+
# on the command line ends up with the highest priority.
|
|
127
|
+
#
|
|
128
|
+
# All the flag values are parsed up front, in command line order, so that a
|
|
129
|
+
# malformed value is reported before any source is added, and is reported
|
|
130
|
+
# against the first offending flag rather than the last.
|
|
131
|
+
#
|
|
132
|
+
# All added sources are then proactively resolved and failures reported.
|
|
133
|
+
def build_cli
|
|
134
|
+
specs = sources.map { |kind, value| parse_source_request(kind, value) }
|
|
135
|
+
return cli if specs.empty?
|
|
136
|
+
require "toys/utils/gems" if specs.any? { |spec| spec.is_a?(::Toys::SourceSpec::Gem) }
|
|
137
|
+
tool_cli = cli.child(copy_sources: true) do |child_cli|
|
|
138
|
+
specs.reverse_each { |spec| child_cli.add_source(spec, high_priority: true) }
|
|
139
|
+
end
|
|
140
|
+
begin
|
|
141
|
+
tool_cli.loader.resolve_sources
|
|
142
|
+
rescue ::Toys::ToolSourceError => e
|
|
143
|
+
logger.error("Unable to resolve the given tool sources: #{e.message}")
|
|
144
|
+
exit(1)
|
|
145
|
+
end
|
|
146
|
+
tool_cli
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
# Parses and checks a single source request, returning the source spec to add
|
|
150
|
+
# to the CLI later.
|
|
151
|
+
def parse_source_request(kind, value)
|
|
152
|
+
case kind
|
|
153
|
+
when :gem
|
|
154
|
+
parse_gem_request(value)
|
|
155
|
+
when :git
|
|
156
|
+
parse_git_request(value)
|
|
157
|
+
when :path
|
|
158
|
+
parse_path_request(value)
|
|
159
|
+
end
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
# Splits a --gem flag value into the gem name and its version requirements,
|
|
163
|
+
# and checks that both are valid. The requirements are validated by Rubygems
|
|
164
|
+
# itself, but are passed along as the original strings.
|
|
165
|
+
def parse_gem_request(gem_request)
|
|
166
|
+
gem_name, *gem_version = gem_request.split(",", -1).map(&:strip)
|
|
167
|
+
if gem_name.nil? || gem_name.empty? || gem_version.any?(&:empty?)
|
|
168
|
+
logger.fatal("Invalid --gem value: #{gem_request.inspect}")
|
|
169
|
+
exit(1)
|
|
170
|
+
end
|
|
171
|
+
begin
|
|
172
|
+
::Gem::Requirement.create(*gem_version)
|
|
173
|
+
rescue ::Gem::Requirement::BadRequirementError => e
|
|
174
|
+
logger.fatal("Invalid version requirement for gem #{gem_name.inspect}: #{e.message}")
|
|
175
|
+
exit(1)
|
|
176
|
+
end
|
|
177
|
+
::Toys::SourceSpec.gem(gem_name, version: gem_version)
|
|
178
|
+
end
|
|
179
|
+
|
|
180
|
+
# Splits a --git flag value into the git remote and the options that follow it,
|
|
181
|
+
# and checks that all of them are valid. Unrecognized and duplicate keys are
|
|
182
|
+
# errors rather than being ignored or resolved as last-wins, because a mistyped
|
|
183
|
+
# key would otherwise silently load something other than what was asked for.
|
|
184
|
+
def parse_git_request(git_request)
|
|
185
|
+
git_remote, *elements = git_request.split(",", -1).map(&:strip)
|
|
186
|
+
git_error(git_request, "the git remote is required") if git_remote.nil? || git_remote.empty?
|
|
187
|
+
opts = { path: nil, commit: nil, update: false }
|
|
188
|
+
seen_keys = []
|
|
189
|
+
elements.each do |element|
|
|
190
|
+
key, value = parse_git_element(git_request, element, seen_keys)
|
|
191
|
+
case key
|
|
192
|
+
when "path"
|
|
193
|
+
opts[:path] = value
|
|
194
|
+
when "commit"
|
|
195
|
+
opts[:commit] = value
|
|
196
|
+
when "update"
|
|
197
|
+
opts[:update] = parse_git_update(git_request, value)
|
|
198
|
+
else
|
|
199
|
+
git_error(git_request, "unrecognized key #{key.inspect}")
|
|
200
|
+
end
|
|
201
|
+
end
|
|
202
|
+
::Toys::SourceSpec.git(git_remote, **opts)
|
|
203
|
+
end
|
|
204
|
+
|
|
205
|
+
# Splits one element following the git remote into its key and value, and
|
|
206
|
+
# checks that it is well-formed and does not repeat an earlier key.
|
|
207
|
+
def parse_git_element(git_request, element, seen_keys)
|
|
208
|
+
key, value = element.split("=", 2).map(&:strip)
|
|
209
|
+
if value.nil? || key.empty?
|
|
210
|
+
git_error(git_request, "expected \"key=value\" but got #{element.inspect}")
|
|
211
|
+
end
|
|
212
|
+
git_error(git_request, "empty value for key #{key.inspect}") if value.empty?
|
|
213
|
+
git_error(git_request, "duplicate key #{key.inspect}") if seen_keys.include?(key)
|
|
214
|
+
seen_keys << key
|
|
215
|
+
[key, value]
|
|
216
|
+
end
|
|
217
|
+
|
|
218
|
+
# Interprets the value of the "update" key in a --git flag value, which is
|
|
219
|
+
# either a boolean or a number of seconds.
|
|
220
|
+
def parse_git_update(git_request, value)
|
|
221
|
+
case value
|
|
222
|
+
when "true"
|
|
223
|
+
true
|
|
224
|
+
when "false"
|
|
225
|
+
false
|
|
226
|
+
when /\A\d+\z/
|
|
227
|
+
value.to_i
|
|
228
|
+
else
|
|
229
|
+
git_error(git_request, "invalid update value #{value.inspect}")
|
|
230
|
+
end
|
|
231
|
+
end
|
|
232
|
+
|
|
233
|
+
# Reports a malformed --git flag value, naming the part of the value that was
|
|
234
|
+
# not understood.
|
|
235
|
+
def git_error(git_request, message)
|
|
236
|
+
logger.fatal("Invalid --git value: #{git_request.inspect}: #{message}")
|
|
237
|
+
exit(1)
|
|
238
|
+
end
|
|
239
|
+
|
|
240
|
+
# Checks that a --path flag value is present. Whether the path actually names
|
|
241
|
+
# tools is left to the loader to determine, so that this tool does not have to
|
|
242
|
+
# duplicate that rule. The spec carries no context directory, like the gem and
|
|
243
|
+
# git sources, because it is injected from the command line rather than found
|
|
244
|
+
# in a project.
|
|
245
|
+
def parse_path_request(path_request)
|
|
246
|
+
path = path_request.strip
|
|
247
|
+
if path.empty?
|
|
248
|
+
logger.fatal("Invalid --path value: #{path_request.inspect}")
|
|
249
|
+
exit(1)
|
|
250
|
+
end
|
|
251
|
+
::Toys::SourceSpec.path(path)
|
|
252
|
+
end
|