toys 0.22.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 +68 -0
- 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 +1 -1
- data/core-docs/toys/acceptor.rb +13 -13
- data/core-docs/toys/arg_parser.rb +30 -29
- data/core-docs/toys/cli.rb +315 -167
- data/core-docs/toys/completion.rb +43 -46
- data/core-docs/toys/context.rb +84 -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 +2 -2
- data/core-docs/toys/dsl/flag_group.rb +2 -2
- data/core-docs/toys/dsl/positional_arg.rb +2 -2
- data/core-docs/toys/dsl/tool.rb +128 -32
- data/core-docs/toys/errors.rb +77 -25
- data/core-docs/toys/flag.rb +8 -18
- data/core-docs/toys/flag_group.rb +14 -14
- data/core-docs/toys/input_file.rb +8 -21
- 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 +83 -336
- data/core-docs/toys/middleware.rb +9 -36
- data/core-docs/toys/mixin.rb +4 -10
- data/core-docs/toys/module_lookup.rb +2 -2
- data/core-docs/toys/positional_arg.rb +2 -2
- 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 -163
- 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 +2 -2
- data/core-docs/toys/standard_middleware/apply_config.rb +36 -6
- data/core-docs/toys/standard_middleware/handle_usage_errors.rb +2 -2
- data/core-docs/toys/standard_middleware/set_default_descriptions.rb +2 -2
- data/core-docs/toys/standard_middleware/show_help.rb +8 -8
- data/core-docs/toys/standard_middleware/show_root_version.rb +3 -3
- data/core-docs/toys/standard_mixins/bundler.rb +6 -3
- data/core-docs/toys/standard_mixins/exec.rb +6 -6
- data/core-docs/toys/standard_mixins/fileutils.rb +3 -3
- data/core-docs/toys/standard_mixins/gems.rb +5 -5
- 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 +4 -4
- data/core-docs/toys/standard_mixins/xdg.rb +3 -3
- data/core-docs/toys/template.rb +14 -17
- data/core-docs/toys/tool_definition.rb +84 -89
- 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 +6 -6
- data/core-docs/toys/utils/gems.rb +31 -36
- data/core-docs/toys/utils/git_cache.rb +15 -15
- data/core-docs/toys/utils/help_text.rb +5 -5
- data/core-docs/toys/utils/pager.rb +2 -2
- data/core-docs/toys/utils/standard_ui.rb +26 -13
- data/core-docs/toys/utils/terminal.rb +4 -4
- data/core-docs/toys/utils/xdg.rb +4 -4
- data/core-docs/toys/wrappable_string.rb +2 -2
- data/core-docs/toys-core.rb +66 -19
- data/docs/guide.md +188 -12
- data/lib/toys/standard_cli.rb +27 -48
- data/lib/toys/templates/clean.rb +1 -1
- data/lib/toys/templates/gem_build.rb +1 -1
- data/lib/toys/templates/minitest.rb +1 -1
- data/lib/toys/templates/rdoc.rb +2 -2
- data/lib/toys/templates/rspec.rb +1 -1
- data/lib/toys/templates/rubocop.rb +1 -1
- data/lib/toys/templates/yardoc.rb +1 -1
- data/lib/toys/testing.rb +25 -6
- data/lib/toys/version.rb +1 -1
- data/lib/toys.rb +2 -1
- metadata +15 -7
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,73 @@
|
|
|
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
|
+
|
|
3
71
|
### v0.22.0 / 2026-05-05
|
|
4
72
|
|
|
5
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.)
|
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
|
|
@@ -30,7 +30,7 @@ tool "eval" do
|
|
|
30
30
|
|
|
31
31
|
def run
|
|
32
32
|
require "toys/utils/completion_engine"
|
|
33
|
-
result = ::Toys::Utils::CompletionEngine::Bash.new(cli).run
|
|
33
|
+
result = ::Toys::Utils::CompletionEngine::Bash.new(cli.completion, cli.loader).run
|
|
34
34
|
if result > 1
|
|
35
35
|
logger.fatal("This tool must be invoked as a bash completion command.")
|
|
36
36
|
end
|
data/builtins/system/test.rb
CHANGED
|
@@ -208,7 +208,7 @@ end
|
|
|
208
208
|
|
|
209
209
|
def tool_dir
|
|
210
210
|
@tool_dir ||= begin
|
|
211
|
-
words = cli.
|
|
211
|
+
words = cli.tool_name_splitter.split(tool)
|
|
212
212
|
dir = base_dir
|
|
213
213
|
unless words.empty?
|
|
214
214
|
dir = ::File.join(dir, *words)
|
|
@@ -232,7 +232,7 @@ def base_dir
|
|
|
232
232
|
else
|
|
233
233
|
dir = ::File.realpath(::Dir.getwd)
|
|
234
234
|
loop do
|
|
235
|
-
candidate = ::File.join(dir, ::Toys::StandardCLI::
|
|
235
|
+
candidate = ::File.join(dir, ::Toys::StandardCLI::TOPLEVEL_TOOL_DIR_NAME)
|
|
236
236
|
break candidate if ::File.directory?(candidate)
|
|
237
237
|
parent = ::File.dirname(dir)
|
|
238
238
|
if parent == dir
|
data/builtins/system/tools.rb
CHANGED
|
@@ -73,7 +73,7 @@ tool "list" do
|
|
|
73
73
|
def run
|
|
74
74
|
loader = choose_loader(local, from_dir)
|
|
75
75
|
words = namespace
|
|
76
|
-
words = loader.
|
|
76
|
+
words = loader.tool_name_splitter.split(words.first) if words.size == 1
|
|
77
77
|
tool_list = loader.list_subtools(words,
|
|
78
78
|
recursive: recursive,
|
|
79
79
|
include_hidden: show_all,
|
|
@@ -128,7 +128,7 @@ tool "show" do
|
|
|
128
128
|
def run
|
|
129
129
|
loader = choose_loader(local, from_dir)
|
|
130
130
|
words = name
|
|
131
|
-
words = loader.
|
|
131
|
+
words = loader.tool_name_splitter.split(words.first) if words.size == 1
|
|
132
132
|
tool = loader.lookup_specific(words)
|
|
133
133
|
output =
|
|
134
134
|
if tool.nil?
|
|
@@ -32,7 +32,7 @@ tool "eval" do
|
|
|
32
32
|
|
|
33
33
|
def run
|
|
34
34
|
require "toys/utils/completion_engine"
|
|
35
|
-
result = ::Toys::Utils::CompletionEngine::Zsh.new(cli).run
|
|
35
|
+
result = ::Toys::Utils::CompletionEngine::Zsh.new(cli.completion, cli.loader).run
|
|
36
36
|
if result > 1
|
|
37
37
|
logger.fatal("This tool must be invoked as a zsh completion command.")
|
|
38
38
|
end
|
data/core-docs/toys/acceptor.rb
CHANGED
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
module Toys
|
|
2
2
|
##
|
|
3
|
-
# **_Defined in the toys-core gem_**
|
|
4
|
-
#
|
|
5
3
|
# An Acceptor validates and converts arguments. It is designed to be
|
|
6
4
|
# compatible with the OptionParser accept mechanism.
|
|
7
5
|
#
|
|
@@ -16,13 +14,15 @@ module Toys
|
|
|
16
14
|
# Finally, an acceptor has a name that may appear in help text for flags and
|
|
17
15
|
# arguments that use it.
|
|
18
16
|
#
|
|
17
|
+
# **_Defined in the toys-core gem_**
|
|
18
|
+
#
|
|
19
19
|
module Acceptor
|
|
20
20
|
##
|
|
21
21
|
# A sentinel that may be returned from a function-based acceptor to
|
|
22
22
|
# indicate invalid input.
|
|
23
23
|
# @return [Object]
|
|
24
24
|
#
|
|
25
|
-
REJECT = ::
|
|
25
|
+
REJECT = ::Toys::UniqueKey.new("Toys::Acceptor::REJECT")
|
|
26
26
|
|
|
27
27
|
##
|
|
28
28
|
# The default type description.
|
|
@@ -31,8 +31,6 @@ module Toys
|
|
|
31
31
|
DEFAULT_TYPE_DESC = "string"
|
|
32
32
|
|
|
33
33
|
##
|
|
34
|
-
# **_Defined in the toys-core gem_**
|
|
35
|
-
#
|
|
36
34
|
# A base class for acceptors.
|
|
37
35
|
#
|
|
38
36
|
# The base acceptor does not do any validation (i.e. it accepts all
|
|
@@ -40,6 +38,8 @@ module Toys
|
|
|
40
38
|
# subclass this base class and override the {#match} and {#convert} methods
|
|
41
39
|
# to implement an acceptor.
|
|
42
40
|
#
|
|
41
|
+
# **_Defined in the toys-core gem_**
|
|
42
|
+
#
|
|
43
43
|
class Base
|
|
44
44
|
##
|
|
45
45
|
# Create a base acceptor.
|
|
@@ -146,13 +146,13 @@ module Toys
|
|
|
146
146
|
DEFAULT = Base.new(type_desc: "string", well_known_spec: ::Object)
|
|
147
147
|
|
|
148
148
|
##
|
|
149
|
-
# **_Defined in the toys-core gem_**
|
|
150
|
-
#
|
|
151
149
|
# An acceptor that uses a simple function to validate and convert input.
|
|
152
150
|
# The function must take the input string as its argument, and either
|
|
153
151
|
# return the converted object to indicate success, or raise an exception or
|
|
154
152
|
# return the sentinel {Toys::Acceptor::REJECT} to indicate invalid input.
|
|
155
153
|
#
|
|
154
|
+
# **_Defined in the toys-core gem_**
|
|
155
|
+
#
|
|
156
156
|
class Simple < Base
|
|
157
157
|
##
|
|
158
158
|
# Create a simple acceptor.
|
|
@@ -178,12 +178,12 @@ module Toys
|
|
|
178
178
|
end
|
|
179
179
|
|
|
180
180
|
##
|
|
181
|
-
# **_Defined in the toys-core gem_**
|
|
182
|
-
#
|
|
183
181
|
# An acceptor that uses a regex to validate input. It also supports a
|
|
184
182
|
# custom conversion function that generates the final value from the match
|
|
185
183
|
# results.
|
|
186
184
|
#
|
|
185
|
+
# **_Defined in the toys-core gem_**
|
|
186
|
+
#
|
|
187
187
|
class Pattern < Base
|
|
188
188
|
##
|
|
189
189
|
# Create a pattern acceptor.
|
|
@@ -216,8 +216,6 @@ module Toys
|
|
|
216
216
|
end
|
|
217
217
|
|
|
218
218
|
##
|
|
219
|
-
# **_Defined in the toys-core gem_**
|
|
220
|
-
#
|
|
221
219
|
# An acceptor that recognizes a fixed set of values.
|
|
222
220
|
#
|
|
223
221
|
# You provide a list of valid values. The input argument string will be
|
|
@@ -230,6 +228,8 @@ module Toys
|
|
|
230
228
|
# converter will yield the integer `3`. If an argument of "three" is
|
|
231
229
|
# passed in, the match will fail.
|
|
232
230
|
#
|
|
231
|
+
# **_Defined in the toys-core gem_**
|
|
232
|
+
#
|
|
233
233
|
class Enum < Base
|
|
234
234
|
##
|
|
235
235
|
# Create an acceptor.
|
|
@@ -254,8 +254,6 @@ module Toys
|
|
|
254
254
|
end
|
|
255
255
|
|
|
256
256
|
##
|
|
257
|
-
# **_Defined in the toys-core gem_**
|
|
258
|
-
#
|
|
259
257
|
# An acceptor that recognizes a range of values.
|
|
260
258
|
#
|
|
261
259
|
# The input argument is matched against the given range. For example, you
|
|
@@ -281,6 +279,8 @@ module Toys
|
|
|
281
279
|
# * If the range beginning and/or end are not numeric types, then no
|
|
282
280
|
# conversion is done by default.
|
|
283
281
|
#
|
|
282
|
+
# **_Defined in the toys-core gem_**
|
|
283
|
+
#
|
|
284
284
|
class Range < Simple
|
|
285
285
|
##
|
|
286
286
|
# Create an acceptor.
|
|
@@ -1,16 +1,14 @@
|
|
|
1
1
|
module Toys
|
|
2
2
|
##
|
|
3
|
-
# **_Defined in the toys-core gem_**
|
|
4
|
-
#
|
|
5
3
|
# An internal class that parses command line arguments for a tool.
|
|
6
4
|
#
|
|
7
5
|
# Generally, you should not need to use this class directly. It is called
|
|
8
6
|
# from {Toys::CLI}.
|
|
9
7
|
#
|
|
8
|
+
# **_Defined in the toys-core gem_**
|
|
9
|
+
#
|
|
10
10
|
class ArgParser
|
|
11
11
|
##
|
|
12
|
-
# **_Defined in the toys-core gem_**
|
|
13
|
-
#
|
|
14
12
|
# Base representation of a usage error reported by the ArgParser.
|
|
15
13
|
#
|
|
16
14
|
# This is normally not raised directly, but returned as an element in the
|
|
@@ -18,6 +16,8 @@ module Toys
|
|
|
18
16
|
# message and backtrace attributes, along with additional fields as defined
|
|
19
17
|
# in this class, and it can be raised later if desired.
|
|
20
18
|
#
|
|
19
|
+
# **_Defined in the toys-core gem_**
|
|
20
|
+
#
|
|
21
21
|
class UsageError < ::StandardError
|
|
22
22
|
##
|
|
23
23
|
# Create a UsageError given a message and common data
|
|
@@ -81,11 +81,11 @@ module Toys
|
|
|
81
81
|
end
|
|
82
82
|
|
|
83
83
|
##
|
|
84
|
-
# **_Defined in the toys-core gem_**
|
|
85
|
-
#
|
|
86
84
|
# A UsageError indicating a value was provided for a flag that does not
|
|
87
85
|
# take a value.
|
|
88
86
|
#
|
|
87
|
+
# **_Defined in the toys-core gem_**
|
|
88
|
+
#
|
|
89
89
|
class FlagValueNotAllowedError < UsageError
|
|
90
90
|
##
|
|
91
91
|
# Create a FlagValueNotAllowedError.
|
|
@@ -100,11 +100,11 @@ module Toys
|
|
|
100
100
|
end
|
|
101
101
|
|
|
102
102
|
##
|
|
103
|
-
# **_Defined in the toys-core gem_**
|
|
104
|
-
#
|
|
105
103
|
# A UsageError indicating a value was not provided for a flag that requires
|
|
106
104
|
# a value.
|
|
107
105
|
#
|
|
106
|
+
# **_Defined in the toys-core gem_**
|
|
107
|
+
#
|
|
108
108
|
class FlagValueMissingError < UsageError
|
|
109
109
|
##
|
|
110
110
|
# Create a FlagValueMissingError.
|
|
@@ -119,10 +119,10 @@ module Toys
|
|
|
119
119
|
end
|
|
120
120
|
|
|
121
121
|
##
|
|
122
|
-
# **_Defined in the toys-core gem_**
|
|
123
|
-
#
|
|
124
122
|
# A UsageError indicating a flag name was not recognized.
|
|
125
123
|
#
|
|
124
|
+
# **_Defined in the toys-core gem_**
|
|
125
|
+
#
|
|
126
126
|
class FlagUnrecognizedError < UsageError
|
|
127
127
|
##
|
|
128
128
|
# Create a FlagUnrecognizedError.
|
|
@@ -139,11 +139,11 @@ module Toys
|
|
|
139
139
|
end
|
|
140
140
|
|
|
141
141
|
##
|
|
142
|
-
# **_Defined in the toys-core gem_**
|
|
143
|
-
#
|
|
144
142
|
# A UsageError indicating a flag name prefix was given that matched
|
|
145
143
|
# multiple flags.
|
|
146
144
|
#
|
|
145
|
+
# **_Defined in the toys-core gem_**
|
|
146
|
+
#
|
|
147
147
|
class FlagAmbiguousError < UsageError
|
|
148
148
|
##
|
|
149
149
|
# Create a FlagAmbiguousError.
|
|
@@ -160,10 +160,10 @@ module Toys
|
|
|
160
160
|
end
|
|
161
161
|
|
|
162
162
|
##
|
|
163
|
-
# **_Defined in the toys-core gem_**
|
|
164
|
-
#
|
|
165
163
|
# A UsageError indicating a flag did not accept the value given it.
|
|
166
164
|
#
|
|
165
|
+
# **_Defined in the toys-core gem_**
|
|
166
|
+
#
|
|
167
167
|
class FlagValueUnacceptableError < UsageError
|
|
168
168
|
##
|
|
169
169
|
# Create a FlagValueUnacceptableError.
|
|
@@ -181,11 +181,11 @@ module Toys
|
|
|
181
181
|
end
|
|
182
182
|
|
|
183
183
|
##
|
|
184
|
-
# **_Defined in the toys-core gem_**
|
|
185
|
-
#
|
|
186
184
|
# A UsageError indicating a positional argument did not accept the value
|
|
187
185
|
# given it.
|
|
188
186
|
#
|
|
187
|
+
# **_Defined in the toys-core gem_**
|
|
188
|
+
#
|
|
189
189
|
class ArgValueUnacceptableError < UsageError
|
|
190
190
|
##
|
|
191
191
|
# Create an ArgValueUnacceptableError.
|
|
@@ -203,10 +203,10 @@ module Toys
|
|
|
203
203
|
end
|
|
204
204
|
|
|
205
205
|
##
|
|
206
|
-
# **_Defined in the toys-core gem_**
|
|
207
|
-
#
|
|
208
206
|
# A UsageError indicating a required positional argument was not fulfilled.
|
|
209
207
|
#
|
|
208
|
+
# **_Defined in the toys-core gem_**
|
|
209
|
+
#
|
|
210
210
|
class ArgMissingError < UsageError
|
|
211
211
|
##
|
|
212
212
|
# Create an ArgMissingError.
|
|
@@ -221,10 +221,10 @@ module Toys
|
|
|
221
221
|
end
|
|
222
222
|
|
|
223
223
|
##
|
|
224
|
-
# **_Defined in the toys-core gem_**
|
|
225
|
-
#
|
|
226
224
|
# A UsageError indicating extra arguments were supplied.
|
|
227
225
|
#
|
|
226
|
+
# **_Defined in the toys-core gem_**
|
|
227
|
+
#
|
|
228
228
|
class ExtraArgumentsError < UsageError
|
|
229
229
|
##
|
|
230
230
|
# Create an ExtraArgumentsError.
|
|
@@ -244,10 +244,10 @@ module Toys
|
|
|
244
244
|
end
|
|
245
245
|
|
|
246
246
|
##
|
|
247
|
-
# **_Defined in the toys-core gem_**
|
|
248
|
-
#
|
|
249
247
|
# A UsageError indicating the given subtool name does not exist.
|
|
250
248
|
#
|
|
249
|
+
# **_Defined in the toys-core gem_**
|
|
250
|
+
#
|
|
251
251
|
class ToolUnrecognizedError < UsageError
|
|
252
252
|
##
|
|
253
253
|
# Create a ToolUnrecognizedError.
|
|
@@ -270,10 +270,10 @@ module Toys
|
|
|
270
270
|
end
|
|
271
271
|
|
|
272
272
|
##
|
|
273
|
-
# **_Defined in the toys-core gem_**
|
|
274
|
-
#
|
|
275
273
|
# A UsageError indicating a flag group constraint was not fulfilled.
|
|
276
274
|
#
|
|
275
|
+
# **_Defined in the toys-core gem_**
|
|
276
|
+
#
|
|
277
277
|
class FlagGroupConstraintError < UsageError
|
|
278
278
|
##
|
|
279
279
|
# Create a FlagGroupConstraintError.
|
|
@@ -288,13 +288,14 @@ module Toys
|
|
|
288
288
|
##
|
|
289
289
|
# Create an argument parser for a particular tool.
|
|
290
290
|
#
|
|
291
|
-
# @param cli [Toys::CLI] The CLI in effect.
|
|
292
291
|
# @param tool [Toys::ToolDefinition] The tool defining the argument format.
|
|
293
|
-
# @param
|
|
292
|
+
# @param loader [Toys::Loader] The loader, used to generate suggestions
|
|
293
|
+
# for unrecognized arguments.
|
|
294
|
+
# @param common_data [Hash] Additional initial data (such as verbosity).
|
|
294
295
|
# @param require_exact_flag_match [boolean] Whether to require flag matches
|
|
295
296
|
# be exact (not partial). Default is false.
|
|
296
297
|
#
|
|
297
|
-
def initialize(
|
|
298
|
+
def initialize(tool, loader, common_data: {}, require_exact_flag_match: false)
|
|
298
299
|
# Source available in the toys-core gem
|
|
299
300
|
end
|
|
300
301
|
|
|
@@ -394,8 +395,8 @@ module Toys
|
|
|
394
395
|
# * One or more extra arguments were provided.
|
|
395
396
|
# * Restrictions defined in one or more flag groups were not fulfilled.
|
|
396
397
|
#
|
|
397
|
-
# Any errors are added to the errors array
|
|
398
|
-
#
|
|
398
|
+
# Any errors are added to the errors array, and are thus reflected in
|
|
399
|
+
# {#data} under `Context::Key::USAGE_ERRORS`.
|
|
399
400
|
#
|
|
400
401
|
# After this method is called, this object is locked down, and no
|
|
401
402
|
# additional arguments may be parsed.
|