toys 0.22.0 → 0.24.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 (78) hide show
  1. checksums.yaml +4 -4
  2. data/.yardopts +0 -1
  3. data/CHANGELOG.md +73 -0
  4. data/builtins/do.rb +212 -2
  5. data/builtins/system/bash-completion.rb +1 -1
  6. data/builtins/system/test.rb +2 -2
  7. data/builtins/system/tools.rb +2 -2
  8. data/builtins/system/zsh-completion.rb +1 -1
  9. data/core-docs/toys/acceptor.rb +13 -13
  10. data/core-docs/toys/arg_parser.rb +30 -29
  11. data/core-docs/toys/cli.rb +315 -167
  12. data/core-docs/toys/completion.rb +43 -46
  13. data/core-docs/toys/context.rb +84 -40
  14. data/core-docs/toys/core.rb +3 -3
  15. data/core-docs/toys/dsl/base.rb +6 -6
  16. data/core-docs/toys/dsl/flag.rb +2 -2
  17. data/core-docs/toys/dsl/flag_group.rb +2 -2
  18. data/core-docs/toys/dsl/positional_arg.rb +2 -2
  19. data/core-docs/toys/dsl/tool.rb +142 -64
  20. data/core-docs/toys/errors.rb +77 -25
  21. data/core-docs/toys/flag.rb +8 -18
  22. data/core-docs/toys/flag_group.rb +14 -14
  23. data/core-docs/toys/input_file.rb +8 -21
  24. data/core-docs/toys/loader/load_state.rb +4 -0
  25. data/core-docs/toys/loader/tool_registry.rb +4 -0
  26. data/core-docs/toys/loader.rb +83 -336
  27. data/core-docs/toys/middleware.rb +9 -36
  28. data/core-docs/toys/mixin.rb +4 -10
  29. data/core-docs/toys/module_lookup.rb +2 -2
  30. data/core-docs/toys/positional_arg.rb +2 -2
  31. data/core-docs/toys/runner.rb +159 -0
  32. data/core-docs/toys/source_info/origin.rb +131 -0
  33. data/core-docs/toys/source_info.rb +67 -163
  34. data/core-docs/toys/source_list.rb +72 -0
  35. data/core-docs/toys/source_spec.rb +349 -0
  36. data/core-docs/toys/standard_middleware/add_verbosity_flags.rb +2 -2
  37. data/core-docs/toys/standard_middleware/apply_config.rb +36 -6
  38. data/core-docs/toys/standard_middleware/handle_usage_errors.rb +2 -2
  39. data/core-docs/toys/standard_middleware/set_default_descriptions.rb +2 -2
  40. data/core-docs/toys/standard_middleware/show_help.rb +8 -8
  41. data/core-docs/toys/standard_middleware/show_root_version.rb +3 -3
  42. data/core-docs/toys/standard_mixins/bundler.rb +6 -3
  43. data/core-docs/toys/standard_mixins/exec.rb +6 -6
  44. data/core-docs/toys/standard_mixins/fileutils.rb +3 -3
  45. data/core-docs/toys/standard_mixins/gems.rb +5 -5
  46. data/core-docs/toys/standard_mixins/git_cache.rb +3 -3
  47. data/core-docs/toys/standard_mixins/highline.rb +4 -4
  48. data/core-docs/toys/standard_mixins/pager.rb +3 -3
  49. data/core-docs/toys/standard_mixins/terminal.rb +4 -4
  50. data/core-docs/toys/standard_mixins/xdg.rb +3 -3
  51. data/core-docs/toys/template.rb +14 -17
  52. data/core-docs/toys/tool_definition.rb +84 -89
  53. data/core-docs/toys/tool_name_splitter.rb +82 -0
  54. data/core-docs/toys/unique_key.rb +35 -0
  55. data/core-docs/toys/utils/completion_engine.rb +24 -13
  56. data/core-docs/toys/utils/exec.rb +6 -6
  57. data/core-docs/toys/utils/gems.rb +45 -42
  58. data/core-docs/toys/utils/git_cache.rb +15 -15
  59. data/core-docs/toys/utils/help_text.rb +5 -5
  60. data/core-docs/toys/utils/pager.rb +2 -2
  61. data/core-docs/toys/utils/standard_ui.rb +26 -13
  62. data/core-docs/toys/utils/terminal.rb +4 -4
  63. data/core-docs/toys/utils/xdg.rb +4 -4
  64. data/core-docs/toys/wrappable_string.rb +2 -2
  65. data/core-docs/toys-core.rb +66 -19
  66. data/docs/guide.md +204 -13
  67. data/lib/toys/standard_cli.rb +27 -48
  68. data/lib/toys/templates/clean.rb +1 -1
  69. data/lib/toys/templates/gem_build.rb +1 -1
  70. data/lib/toys/templates/minitest.rb +2 -6
  71. data/lib/toys/templates/rdoc.rb +2 -2
  72. data/lib/toys/templates/rspec.rb +2 -6
  73. data/lib/toys/templates/rubocop.rb +1 -1
  74. data/lib/toys/templates/yardoc.rb +1 -1
  75. data/lib/toys/testing.rb +25 -6
  76. data/lib/toys/version.rb +1 -1
  77. data/lib/toys.rb +2 -1
  78. 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: 1a607f5cce147c21bdd90781026767b310054e69bbbff8c9a1103fdbd3d77b6b
4
+ data.tar.gz: ab0b2ff711816c75e2a50206e93ae19673c21f88618a3332e0642387b7baa6f3
5
5
  SHA512:
6
- metadata.gz: f888f8e2b005d13c943a7856add1ae37d56739dc0553612fa09b13472a4b43b002a594b11b8d77723f4ddafdf05170a99c2ade023ed247136cd946d7345784fa
7
- data.tar.gz: a8e90f01ab91878bf4175bde290c39d1d126985a77d23779134aa008ba217801f1f2b2e876733b4bf1ef61336506758b7fb4f9dab0bd3a5da7c03ae9aba9d141
6
+ metadata.gz: a99af47c6944f33da01db83f1aeb9d77021424cf2ef3b739c7f34caeaffac8dfbcfa25fff7e6ce73d742c4d4d44cabd1ab2b6521d3b64aeaf7d9adb5aeff0ffa
7
+ data.tar.gz: 3d853bf3958d6894a5388830c5f4c2595f3631a13280741d93cc70410d43374c690273a23314bd1758b558a523b0d3d4372446f3054a2d616cf9b78db0d526bd
data/.yardopts CHANGED
@@ -1,7 +1,6 @@
1
1
  --no-private
2
2
  --title=Toys
3
3
  --markup=markdown
4
- --markup-provider redcarpet
5
4
  --main=README.md
6
5
  ./core-docs/toys/**/*.rb
7
6
  ./core-docs/toys-core.rb
data/CHANGELOG.md CHANGED
@@ -1,5 +1,78 @@
1
1
  # Release History
2
2
 
3
+ ### v0.24.0 / 2026-09-11
4
+
5
+ * Feature: The `toys do` builtin provides an `--on-missing-gem` flag that governs how the `--gem` flag handles gems that are not installed.
6
+ * BREAKING CHANGE: Removed deprecated `gem_version:` argument from `:rspec` and `:minitest` templates.
7
+
8
+ ### v0.23.0 / 2026-09-09
9
+
10
+ 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.
11
+
12
+ Highlights include:
13
+
14
+ * 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.
15
+ * The `do` builtin tool supports `--gem=`, `--git=`, and `--path=` flags that make additional tool sources available.
16
+ * 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.
17
+ * Updates to error handling to improve the output and fix some long-standing issues related to tool delegation.
18
+ * Several fixes to bundler integration, especially with Bundler 4.
19
+
20
+ Details follow.
21
+
22
+ * Changes to tool definition:
23
+ * 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.
24
+ * The `set_context_directory` DSL directive can now take Pathname arguments and handles relative paths (by expanding to absolute paths).
25
+ * When two or more flags are declared using the same context key, within a flag group, the flag group's requirements now evaluate correctly.
26
+ * 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.
27
+ * Toys now raises `Toys::ToolDefinitionError` if you attempt to "reset" a tool previously defined using a `Toys::Tool` subclass.
28
+ * SECURITY FIX: Toys now raises `Toys::ToolSourceError` if a tool loaded from a RubyGem attempts to load tools from the local file system.
29
+ * 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.
30
+ * Caught additional cases of incorrectly located `Toys::Tool` subclasses and raised `Toys::ToolDefinitionError`
31
+ * Toys directory contents are loaded in deterministic (sorted) order.
32
+
33
+ * Changes to tool execution:
34
+ * 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.
35
+ * Added `Toys::Context#loader` for access to the `Toys::Loader` object.
36
+ * 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.
37
+ * BREAKING CHANGE: Removed git- and gem- specific attributes from `Toys::SourceInfo` and replaced with `Toys::SourceInfo#origin`.
38
+ * Standard context keys and sentinels use the new class `Toys::UniqueKey`, which displays useful names in diagnostic output.
39
+ * Delegation now propagates the caller's verbosity to the delegate target, rather than resetting it to zero.
40
+ * 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.
41
+ * 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.
42
+ * Revamped the output and stack trace printed when an exception occurs.
43
+
44
+ * Changes to shell completion:
45
+ * 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.
46
+ * 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 `:`.
47
+ * 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.
48
+ * 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.
49
+
50
+ * Changes to builtin tools:
51
+ * The `do` builtin tool supports `--gem=`, `--git=`, and `--path=` flags that make additional tool sources available.
52
+
53
+ * Changes to CLI interfaces:
54
+ * `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.
55
+ * 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`.
56
+ * 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.
57
+ * 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.
58
+ * 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.
59
+ * 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`.
60
+ * BREAKING CHANGE: Removed the constants `INDEX_FILE_NAME`, `PRELOAD_DIR_NAME`, `PRELOAD_FILE_NAME`, `DATA_DIR_NAME`, and `LIB_DIR_NAME` from `StandardCLI`.
61
+ * 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.
62
+ * 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).
63
+
64
+ * Rubygems/Bundler integration changes:
65
+ * 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.
66
+ * Bundler integration properly detects provenance attributes such as `path:` and `git:`.
67
+ * Calling bundler integration with a nonempty group list no longer loses gems that were already loaded.
68
+ * Bundler integration now honors `BUNDLE_LOCKFILE` (new in Bundler 4) and stops leaking it from the modified bundle.
69
+
70
+ * Other fixes
71
+ * Terminal mixin now raises the correct ArgumentError (instead of NameError) if given an unknown style code.
72
+ * `Toys::Loader#list_subtools` now ensures returned tools have been finished by their middleware. This affects listing tools not underneath the currently running tool.
73
+
74
+ 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.
75
+
3
76
  ### v0.22.0 / 2026-05-05
4
77
 
5
78
  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,12 @@ 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 load tools from additional sources, by passing the --gem, --git, and --path" \
21
+ " flags. Any number of these flags can be added; each adds a source of tools, with earlier" \
22
+ " flags taking priority over later. For example:",
23
+ [" toys do --gem=my-tools --git=https://github.com/dazuma/example deploy --migrate"]
19
24
 
20
25
  flag :delim do
21
26
  flags "-d", "--delim=VALUE"
@@ -24,6 +29,82 @@ flag :delim do
24
29
  long_desc "Sets the delimiter that separates tool invocations. The default value is \",\"."
25
30
  end
26
31
 
32
+ # The source flags all share the :sources key, and their handlers append to
33
+ # the same array, tagging each value with the kind of source requested. That
34
+ # way the array records the order in which the flags appeared on the command
35
+ # line, across all of the flags, which is the order that determines priority.
36
+ # The handlers store the raw value; parsing happens later, in build_cli,
37
+ # because an error raised from a handler would come out of the argument parser
38
+ # as a stack trace rather than as a simple message.
39
+
40
+ flag :sources do
41
+ flags "--gem=GEM"
42
+ handler { |val, prev| prev + [[:gem, val]] }
43
+ default []
44
+ desc "Make the tools from the given gem available"
45
+ long_desc \
46
+ "Adds the tools from the given gem. If the specified gem is not currently installed," \
47
+ " follows the policy set by the --on-missing-gem flag, which is normally to prompt" \
48
+ " whether to install.",
49
+ "",
50
+ "The value is the gem name, optionally followed by any number of version requirements," \
51
+ " all separated by commas. Whitespace surrounding each element is ignored. The version" \
52
+ " requirements use the same syntax as Rubygems and Bundler. For example:",
53
+ [" --gem=\"my-tools, ~> 1.5, >= 1.5.2\""]
54
+ end
55
+
56
+ flag :sources do
57
+ flags "--git=SPEC"
58
+ handler { |val, prev| prev + [[:git, val]] }
59
+ default []
60
+ desc "Make the tools from the given git repository available"
61
+ long_desc \
62
+ "Adds the tools from the given git repository, fetching the repository into a local cache" \
63
+ " if it is not already there.",
64
+ "",
65
+ "The value is the git remote (i.e. the repository URL or path), optionally followed by any" \
66
+ " number of \"key=value\" elements, all separated by commas. There is no way to escape" \
67
+ " a comma appearing within the value. Whitespace surrounding each element, and surrounding" \
68
+ " each equals sign, is ignored. For example:",
69
+ [" --git=\"https://github.com/dazuma/example, path=toys, commit=main\""],
70
+ "",
71
+ "The recognized keys are:",
72
+ "* \"path\" : The file or directory within the repository to load. By default, the entire" \
73
+ " repository is loaded.",
74
+ "* \"commit\" : The SHA, tag, or branch to load. By default, the repository head is used.",
75
+ "* \"update\" : Whether to refresh a previously cached repository. Pass \"true\" or" \
76
+ " \"false\", or a number of seconds, to refresh only if the cache is at least that old." \
77
+ " The default is \"false\"."
78
+ end
79
+
80
+ flag :sources do
81
+ flags "--path=PATH"
82
+ handler { |val, prev| prev + [[:path, val]] }
83
+ default []
84
+ complete_values :file_system
85
+ desc "Make the tools from the given path available"
86
+ long_desc \
87
+ "Adds the tools from the given file system path. The path must name either a directory" \
88
+ " of tools, or a single Ruby file defining tools. For example:",
89
+ [" --path=/path/to/my-tools"]
90
+ end
91
+
92
+ flag :on_missing_gem do
93
+ flags "--on-missing-gem=WHAT"
94
+ accept [:confirm, :install, :error]
95
+ complete_values ["confirm", "install", "error"]
96
+ desc "Specify what to do if a gem is not installed"
97
+ long_desc \
98
+ "Specifies what to do if a gem referenced by the --gem flag is not installed." \
99
+ " Possible values are:",
100
+ "* \"confirm\" : Prompt for what to do.",
101
+ "* \"install\" : Do not prompt, but just install the needed gem.",
102
+ "* \"error\" : Display an error and abort.",
103
+ "",
104
+ "Defaults to the existing CLI configuration, normally \"confirm\". Note this does not affect" \
105
+ " any other gem references such as via the \"gem\" or \"load_gem\" directives."
106
+ end
107
+
27
108
  remaining_args :commands do
28
109
  complete do |context|
29
110
  commands = context.arg_parser.data[:commands]
@@ -37,10 +118,139 @@ end
37
118
  enforce_flags_before_args
38
119
 
39
120
  def run
121
+ tool_cli = build_cli
40
122
  commands
41
123
  .chunk { |arg| arg == delim ? :_separator : true }
42
124
  .each do |_, action|
43
- code = cli.run(action)
125
+ code = tool_cli.run(action)
44
126
  exit(code) unless code.zero?
45
127
  end
46
128
  end
129
+
130
+ # Returns the CLI used to run the requested tools. Normally this is simply the
131
+ # current CLI, but if any sources were requested, we need a new CLI with those
132
+ # sources added. Any new sources are added in reverse order so that the first
133
+ # flag on the command line ends up with the highest priority.
134
+ #
135
+ # All the flag values are parsed up front, in command line order, so that a
136
+ # malformed value is reported before any source is added, and is reported
137
+ # against the first offending flag rather than the last. All added sources are
138
+ # then proactively resolved and failures reported.
139
+ def build_cli
140
+ specs = sources.map { |kind, value| parse_source_request(kind, value) }
141
+ return cli if specs.empty?
142
+ tool_cli = cli.child(copy_sources: true)
143
+ specs.reverse_each { |spec| tool_cli.add_source(spec, high_priority: true) }
144
+ begin
145
+ tool_cli.loader.resolve_sources
146
+ rescue ::Toys::ToolSourceError => e
147
+ logger.error("Unable to resolve the given tool sources: #{e.message}")
148
+ exit(1)
149
+ end
150
+ tool_cli
151
+ end
152
+
153
+ # Parses and checks a single source request, returning the source spec to add
154
+ # to the CLI later.
155
+ def parse_source_request(kind, value)
156
+ case kind
157
+ when :gem
158
+ parse_gem_request(value)
159
+ when :git
160
+ parse_git_request(value)
161
+ when :path
162
+ parse_path_request(value)
163
+ end
164
+ end
165
+
166
+ # Splits a --gem flag value into the gem name and its version requirements,
167
+ # and checks that both are valid. The requirements are validated by Rubygems
168
+ # itself, but are passed along as the original strings.
169
+ def parse_gem_request(gem_request)
170
+ gem_name, *gem_version = gem_request.split(",", -1).map(&:strip)
171
+ if gem_name.nil? || gem_name.empty? || gem_version.any?(&:empty?)
172
+ logger.fatal("Invalid --gem value: #{gem_request.inspect}")
173
+ exit(1)
174
+ end
175
+ begin
176
+ ::Gem::Requirement.create(*gem_version)
177
+ rescue ::Gem::Requirement::BadRequirementError => e
178
+ logger.fatal("Invalid version requirement for gem #{gem_name.inspect}: #{e.message}")
179
+ exit(1)
180
+ end
181
+ ::Toys::SourceSpec.gem(gem_name, version: gem_version, on_missing: on_missing_gem)
182
+ end
183
+
184
+ # Splits a --git flag value into the git remote and the options that follow it,
185
+ # and checks that all of them are valid. Unrecognized and duplicate keys are
186
+ # errors rather than being ignored or resolved as last-wins, because a mistyped
187
+ # key would otherwise silently load something other than what was asked for.
188
+ def parse_git_request(git_request)
189
+ git_remote, *elements = git_request.split(",", -1).map(&:strip)
190
+ git_error(git_request, "the git remote is required") if git_remote.nil? || git_remote.empty?
191
+ opts = { path: nil, commit: nil, update: false }
192
+ seen_keys = []
193
+ elements.each do |element|
194
+ key, value = parse_git_element(git_request, element, seen_keys)
195
+ case key
196
+ when "path"
197
+ opts[:path] = value
198
+ when "commit"
199
+ opts[:commit] = value
200
+ when "update"
201
+ opts[:update] = parse_git_update(git_request, value)
202
+ else
203
+ git_error(git_request, "unrecognized key #{key.inspect}")
204
+ end
205
+ end
206
+ ::Toys::SourceSpec.git(git_remote, **opts)
207
+ end
208
+
209
+ # Splits one element following the git remote into its key and value, and
210
+ # checks that it is well-formed and does not repeat an earlier key.
211
+ def parse_git_element(git_request, element, seen_keys)
212
+ key, value = element.split("=", 2).map(&:strip)
213
+ if value.nil? || key.empty?
214
+ git_error(git_request, "expected \"key=value\" but got #{element.inspect}")
215
+ end
216
+ git_error(git_request, "empty value for key #{key.inspect}") if value.empty?
217
+ git_error(git_request, "duplicate key #{key.inspect}") if seen_keys.include?(key)
218
+ seen_keys << key
219
+ [key, value]
220
+ end
221
+
222
+ # Interprets the value of the "update" key in a --git flag value, which is
223
+ # either a boolean or a number of seconds.
224
+ def parse_git_update(git_request, value)
225
+ case value
226
+ when "true"
227
+ true
228
+ when "false"
229
+ false
230
+ when /\A\d+\z/
231
+ value.to_i
232
+ else
233
+ git_error(git_request, "invalid update value #{value.inspect}")
234
+ end
235
+ end
236
+
237
+ # Reports a malformed --git flag value, naming the part of the value that was
238
+ # not understood.
239
+ def git_error(git_request, message)
240
+ logger.fatal("Invalid --git value: #{git_request.inspect}: #{message}")
241
+ exit(1)
242
+ end
243
+
244
+ # Checks that a --path flag value is present. Whether the path actually names
245
+ # tools is left to the loader to determine, so that this tool does not have to
246
+ # duplicate that rule. The spec carries no context directory, like the gem and
247
+ # git sources, because it is injected from the command line rather than found
248
+ # in a project.
249
+ def parse_path_request(path_request)
250
+ path = path_request.strip
251
+ if path.empty?
252
+ logger.fatal("Invalid --path value: #{path_request.inspect}")
253
+ exit(1)
254
+ end
255
+ ::Toys::SourceSpec.path(path)
256
+ 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.