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.
Files changed (77) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +68 -0
  3. data/builtins/do.rb +208 -2
  4. data/builtins/system/bash-completion.rb +1 -1
  5. data/builtins/system/test.rb +2 -2
  6. data/builtins/system/tools.rb +2 -2
  7. data/builtins/system/zsh-completion.rb +1 -1
  8. data/core-docs/toys/acceptor.rb +13 -13
  9. data/core-docs/toys/arg_parser.rb +30 -29
  10. data/core-docs/toys/cli.rb +315 -167
  11. data/core-docs/toys/completion.rb +43 -46
  12. data/core-docs/toys/context.rb +84 -40
  13. data/core-docs/toys/core.rb +3 -3
  14. data/core-docs/toys/dsl/base.rb +6 -6
  15. data/core-docs/toys/dsl/flag.rb +2 -2
  16. data/core-docs/toys/dsl/flag_group.rb +2 -2
  17. data/core-docs/toys/dsl/positional_arg.rb +2 -2
  18. data/core-docs/toys/dsl/tool.rb +128 -32
  19. data/core-docs/toys/errors.rb +77 -25
  20. data/core-docs/toys/flag.rb +8 -18
  21. data/core-docs/toys/flag_group.rb +14 -14
  22. data/core-docs/toys/input_file.rb +8 -21
  23. data/core-docs/toys/loader/load_state.rb +4 -0
  24. data/core-docs/toys/loader/tool_registry.rb +4 -0
  25. data/core-docs/toys/loader.rb +83 -336
  26. data/core-docs/toys/middleware.rb +9 -36
  27. data/core-docs/toys/mixin.rb +4 -10
  28. data/core-docs/toys/module_lookup.rb +2 -2
  29. data/core-docs/toys/positional_arg.rb +2 -2
  30. data/core-docs/toys/runner.rb +159 -0
  31. data/core-docs/toys/source_info/origin.rb +131 -0
  32. data/core-docs/toys/source_info.rb +67 -163
  33. data/core-docs/toys/source_list.rb +72 -0
  34. data/core-docs/toys/source_spec.rb +317 -0
  35. data/core-docs/toys/standard_middleware/add_verbosity_flags.rb +2 -2
  36. data/core-docs/toys/standard_middleware/apply_config.rb +36 -6
  37. data/core-docs/toys/standard_middleware/handle_usage_errors.rb +2 -2
  38. data/core-docs/toys/standard_middleware/set_default_descriptions.rb +2 -2
  39. data/core-docs/toys/standard_middleware/show_help.rb +8 -8
  40. data/core-docs/toys/standard_middleware/show_root_version.rb +3 -3
  41. data/core-docs/toys/standard_mixins/bundler.rb +6 -3
  42. data/core-docs/toys/standard_mixins/exec.rb +6 -6
  43. data/core-docs/toys/standard_mixins/fileutils.rb +3 -3
  44. data/core-docs/toys/standard_mixins/gems.rb +5 -5
  45. data/core-docs/toys/standard_mixins/git_cache.rb +3 -3
  46. data/core-docs/toys/standard_mixins/highline.rb +4 -4
  47. data/core-docs/toys/standard_mixins/pager.rb +3 -3
  48. data/core-docs/toys/standard_mixins/terminal.rb +4 -4
  49. data/core-docs/toys/standard_mixins/xdg.rb +3 -3
  50. data/core-docs/toys/template.rb +14 -17
  51. data/core-docs/toys/tool_definition.rb +84 -89
  52. data/core-docs/toys/tool_name_splitter.rb +82 -0
  53. data/core-docs/toys/unique_key.rb +35 -0
  54. data/core-docs/toys/utils/completion_engine.rb +24 -13
  55. data/core-docs/toys/utils/exec.rb +6 -6
  56. data/core-docs/toys/utils/gems.rb +31 -36
  57. data/core-docs/toys/utils/git_cache.rb +15 -15
  58. data/core-docs/toys/utils/help_text.rb +5 -5
  59. data/core-docs/toys/utils/pager.rb +2 -2
  60. data/core-docs/toys/utils/standard_ui.rb +26 -13
  61. data/core-docs/toys/utils/terminal.rb +4 -4
  62. data/core-docs/toys/utils/xdg.rb +4 -4
  63. data/core-docs/toys/wrappable_string.rb +2 -2
  64. data/core-docs/toys-core.rb +66 -19
  65. data/docs/guide.md +188 -12
  66. data/lib/toys/standard_cli.rb +27 -48
  67. data/lib/toys/templates/clean.rb +1 -1
  68. data/lib/toys/templates/gem_build.rb +1 -1
  69. data/lib/toys/templates/minitest.rb +1 -1
  70. data/lib/toys/templates/rdoc.rb +2 -2
  71. data/lib/toys/templates/rspec.rb +1 -1
  72. data/lib/toys/templates/rubocop.rb +1 -1
  73. data/lib/toys/templates/yardoc.rb +1 -1
  74. data/lib/toys/testing.rb +25 -6
  75. data/lib/toys/version.rb +1 -1
  76. data/lib/toys.rb +2 -1
  77. metadata +15 -7
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 7c91d8f8914503e8d8baf7eae17725fa3eee2470273311917561c0c1dfa9b341
4
- data.tar.gz: 01a7b93c64123371a1f82afbb708c15377efc9b63b0bd2c52fd5dd0e9acee8c7
3
+ metadata.gz: ee5fe663d934949887ff7ce01289453e7b59efcc553331e2efb8a8ae949b626b
4
+ data.tar.gz: c34a38d893789bdb14fc159f211ac83b09a39b2d897601c961755876be2b6d28
5
5
  SHA512:
6
- metadata.gz: f888f8e2b005d13c943a7856add1ae37d56739dc0553612fa09b13472a4b43b002a594b11b8d77723f4ddafdf05170a99c2ade023ed247136cd946d7345784fa
7
- data.tar.gz: a8e90f01ab91878bf4175bde290c39d1d126985a77d23779134aa008ba217801f1f2b2e876733b4bf1ef61336506758b7fb4f9dab0bd3a5da7c03ae9aba9d141
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 = cli.run(action)
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
@@ -208,7 +208,7 @@ end
208
208
 
209
209
  def tool_dir
210
210
  @tool_dir ||= begin
211
- words = cli.loader.split_path(tool)
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::CONFIG_DIR_NAME)
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
@@ -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.split_path(words.first) if words.size == 1
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.split_path(words.first) if words.size == 1
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
@@ -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 = ::Object.new.freeze
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 default_data [Hash] Additional initial data (such as verbosity).
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(cli, tool, default_data: {}, require_exact_flag_match: false)
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. It also fills in final values
398
- # for `Context::Key::USAGE_ERRORS` and `Context::Key::ARGS`.
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.