toys-core 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 (49) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +89 -0
  3. data/README.md +13 -12
  4. data/docs/guide.md +356 -143
  5. data/lib/toys/acceptor.rb +1 -1
  6. data/lib/toys/arg_parser.rb +77 -45
  7. data/lib/toys/cli.rb +448 -336
  8. data/lib/toys/completion.rb +36 -45
  9. data/lib/toys/context.rb +80 -23
  10. data/lib/toys/core.rb +1 -1
  11. data/lib/toys/dsl/base.rb +22 -8
  12. data/lib/toys/dsl/internal.rb +29 -150
  13. data/lib/toys/dsl/tool.rb +176 -93
  14. data/lib/toys/errors.rb +142 -47
  15. data/lib/toys/flag_group.rb +16 -14
  16. data/lib/toys/input_file.rb +13 -9
  17. data/lib/toys/loader/load_state.rb +449 -0
  18. data/lib/toys/loader/tool_registry.rb +250 -0
  19. data/lib/toys/loader.rb +299 -631
  20. data/lib/toys/middleware.rb +1 -1
  21. data/lib/toys/runner.rb +542 -0
  22. data/lib/toys/source_info/origin.rb +228 -0
  23. data/lib/toys/source_info.rb +359 -222
  24. data/lib/toys/source_list.rb +89 -0
  25. data/lib/toys/source_spec.rb +480 -0
  26. data/lib/toys/standard_middleware/apply_config.rb +65 -13
  27. data/lib/toys/standard_middleware/show_help.rb +8 -9
  28. data/lib/toys/standard_middleware/show_root_version.rb +1 -1
  29. data/lib/toys/standard_mixins/bundler.rb +6 -3
  30. data/lib/toys/standard_mixins/exec.rb +8 -8
  31. data/lib/toys/standard_mixins/fileutils.rb +1 -1
  32. data/lib/toys/standard_mixins/gems.rb +1 -1
  33. data/lib/toys/standard_mixins/git_cache.rb +1 -1
  34. data/lib/toys/standard_mixins/highline.rb +2 -2
  35. data/lib/toys/standard_mixins/pager.rb +1 -1
  36. data/lib/toys/standard_mixins/terminal.rb +2 -2
  37. data/lib/toys/standard_mixins/xdg.rb +1 -1
  38. data/lib/toys/template.rb +10 -10
  39. data/lib/toys/tool_definition.rb +189 -93
  40. data/lib/toys/tool_name_splitter.rb +94 -0
  41. data/lib/toys/unique_key.rb +38 -0
  42. data/lib/toys/utils/completion_engine.rb +97 -22
  43. data/lib/toys/utils/gems.rb +251 -34
  44. data/lib/toys/utils/git_cache.rb +84 -16
  45. data/lib/toys/utils/help_text.rb +8 -5
  46. data/lib/toys/utils/standard_ui.rb +111 -34
  47. data/lib/toys/utils/terminal.rb +1 -1
  48. data/lib/toys-core.rb +64 -9
  49. metadata +13 -5
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 72758c7ebe4c70a841bd76672f81e206a6de66c2a1104c81a3eb5b6b8c0b3c1f
4
- data.tar.gz: 8c5b44be27ba6721dbb1dda109b6f4c46c4b1fc45dcba5b2b295d9082d19582e
3
+ metadata.gz: a09f479bfecbde001cec1076fdce57080117cbdeecfc2738879445a682b7363f
4
+ data.tar.gz: 277635722c80c2691759147cf7fc4b928755f0349a54fd970327c46bae443170
5
5
  SHA512:
6
- metadata.gz: f9cb82ec77824ca0d5ca3e220559498c717aeef15e0a66b60b96f51e087a5313e6c8b2a9c547fd3cb84d41e1fd0f70dab2d416ddcce3e28559f7438437e532d8
7
- data.tar.gz: f3216d320f1feb0edd4b5e65cf3adc453adc97e8865b332182350bb27c65bc9580469cd55f06b3fda42c0e6b75d98180eb48e17ec10baa4eaa15e8524787b10d
6
+ metadata.gz: 6cfaa55d28079f651f9eab9d8a693af1386ddc6d08e58553ab457c4c669265902da5bba6e0f336faa8113bf0a92b2fbbe2bde4b49480118fe1d63b00a38ba791
7
+ data.tar.gz: f8b6b6efcc6c978d1a1ce7cece9040eb5475eb7baa58c6b1527ba2dc5bb780c8401f30917e62fcc43999224ecd755b136d03d05d79c03fdc26622efc306e02d3
data/CHANGELOG.md CHANGED
@@ -1,5 +1,94 @@
1
1
  # Release History
2
2
 
3
+ ### v0.23.0 / 2026-09-09
4
+
5
+ This is a major release, with several new features, a number of fixes, and a significant refactor of the tool loading and execution layers, including several breaking interface changes. It is a release candidate for the upcoming version 1.0.
6
+
7
+ Highlights include:
8
+
9
+ * Major refactor of some of the larger classes that were doing too much: Extracted run logic out of `Toys::CLI` into `Toys::Runner`, extracted source configuration out of `Toys::Loader` into `Toys::SourceSpec` and `Toys::SourceList`, and extracted delimiter handling out of `Toys::Loader` into `Toys::ToolNameSplitter`. This cleaned up the architecture significantly, eliminating most back-references to the CLI object.
10
+ * Added the ability to treat unknown flags as positional args via the `treat_unknown_flags_as_args` DSL directive and corresponding methods on `Toys::ToolDefinition`. Useful for wrapping and delegating to other commands.
11
+ * Updates to error handling to improve the output and fix some long-standing issues related to tool delegation.
12
+ * Several fixes to bundler integration, especially with Bundler 4.
13
+
14
+ Details follow.
15
+
16
+ * Changes to tool definition:
17
+ * BREAKING CHANGE: In a tool that delegates, `Toys::ToolDefinition#run_handler` now returns the delegate target (the target's full name as a frozen array of strings) instead of a Proc. The `run_handler=` setter still accepts only a proc, symbol, or nil, so delegation must still be configured via `delegate_to`.
18
+ * BREAKING CHANGE: The `validation_errors` field in `Toys::FlagGroup` classes now return strings rather than `Toys::ArgParser::FlagGroupConstraintError` objects.
19
+ * 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.
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
+ * `ToolDefinition#custom_context_directory=` and the corresponding `set_context_directory` DSL directive can take Pathname arguments and can handle relative paths (by expanding them into absolute paths).
23
+ * Toys now raises `Toys::ToolDefinitionError` if you attempt to "reset" a tool previously defined using a `Toys::Tool` subclass.
24
+ * SECURITY FIX: Toys now raises `Toys::ToolSourceError` if a tool loaded from a RubyGem attempts to load tools from the local file system.
25
+ * `Toys::ToolDefinition#add_flag_group` and `Toys::ToolDefinition#completion=` now properly raise ToolDefinitionError if the definition has already been finished.
26
+ * 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.
27
+ * Caught additional cases of incorrectly located `Toys::Tool` subclasses and raised `Toys::ToolDefinitionError`.
28
+ * If `Toys::ToolDefinition#finish_definition` raises an error (e.g. due to a middleware issue), the tool now goes into an error state and refuses any operation unless it is reset.
29
+ * The root tool's display_name is now "(root)" instead of the empty string.
30
+
31
+ * Changes to tool loading:
32
+ * BREAKING CHANGE: You no longer add sources to a `Toys::Loader` in place. Instead, construct a `Toys::SourceList` and add sources there, then pass it to the `Toys::Loader` constructor. (`Toys::CLI` has been modified to use this mechanism.)
33
+ * BREAKING CHANGE: Removed `Loader#split_path` and `Loader#split_partial_path` in favor of `Toys::ToolNameSplitter`. Use `Loader#tool_name_splitter` to get the name splitter object from a Loader. Related, `Loader.new` now takes a `tool_name_splitter` keyword argument in place of `extra_delimiters`. (`CLI.new` still takes `extra_delimiters`.)
34
+ * BREAKING CHANGE: `Toys::SourceInfo#apply_lib_paths` was replaced with `Toys::SourceInfo#find_lib_paths`, which returns a list of paths but does not actually `require` them. It is now the responsibility of the caller to do the `require`.
35
+ * BREAKING CHANGE: Removed git- and gem- specific attributes from `Toys::SourceInfo` and replaced with `Toys::SourceInfo#origin`.
36
+ * BREAKING CHANGE: `Toys::Loader#lookup_specific` now cannot take a tool name with delimiters. The tool name must be a string array.
37
+ * BREAKING CHANGE: Errors raised when the loader cannot open a tool source (e.g. a gem is missing) are now `Toys::ToolSourceError` instead of `Toys::ToolDefinitionError`.
38
+ * Added `Toys::SourceInfo#find_preload_files` which returns all files to preload.
39
+ * `Toys::Loader#list_subtools` now ensures returned tools have been finished by their middleware.
40
+ * The priority for the fallback root tool is now `-999_999_999` instead of `-999_999`. This value should still fit within the fixnum optimization.
41
+ * Directory contents are loaded in deterministic (sorted) order.
42
+
43
+ * Changes to tool execution:
44
+ * New class `Toys::Runner` now directs tool execution (which previously was available only from `Toys::CLI`.) `Toys::Runner` provides finer-grained control over error handling, allowing independent control over `Toys::ContextualError` wrapping and error handler usage.
45
+ * Added `Toys::Context#runner` and `Toys::Context#loader` for easy access to those facilities from a running tool.
46
+ * 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.
47
+ * Standard context keys and sentinels use the new class `Toys::UniqueKey`, which displays useful names in diagnostic output.
48
+ * Delegation now propagates the caller's verbosity to the delegate target, rather than resetting it to zero.
49
+ * 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.
50
+ * 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.
51
+ * BREAKING CHANGE: Several internal `Toys::ArgParser` changes: The constructor now takes the tool and loader instead of the CLI and tool, and the `default_data` keyword argument was renamed to `common_data`. It also no longer populates context keys such as `LOGGER` and `TOOL` that are not directly related to parsing; those must be passed in via `common_data`, and are generally handled by `Tool::Runner`.
52
+ * `Toys::ArgParser#data` now reflects the args, unmatched args, unmatched flags, unmatched positional, and usage errors accumulated so far while parsing, instead of only after `finish` is called.
53
+
54
+ * Changes to the error handling system, particularly important if you provide a custom error handler:
55
+ * BREAKING CHANGE: `Toys::ContextualError` no longer wraps `SignalException`. Custom error handlers should be prepared to handle `SignalException` directly.
56
+ * BREAKING CHANGE: Custom error handlers should also be prepared to handle unwrapped `StandardError` and `ScriptError` exceptions directly, since it is now possible to disable `Toys::ContextualError` wrapping.
57
+ * An error raised by a delegated tool now raises nested `ContextualError` objects providing information about the inter-tool call sequence, one wrapper per tool in the chain. This might happen, for example, if a tool was delegated or if a subtool was called without an error handler. In normal runs, this displays both the delegating and original tools in the error message. It also means custom error handlers should use the new `Toys::ContextualError#root_cause` if they want the original exception.
58
+ * `ContextualError` now sets its backtrace from the wrapped error's backtrace locations when available (falling back to the backtrace strings), so an enclosing capture can still locate the config file line.
59
+ * Renamed `config_path` and `config_line` to `tool_file_path` and `tool_file_line`. The original names have been aliased for backward compatibility.
60
+ * `ContextualError` now captures tool names in more places, and captures the current phase (loading or running).
61
+ * Revamped the exception output provided by `Toys::Utils::StandardUI` so it's more compact and usable.
62
+
63
+ * Changes to the shell completion system:
64
+ * BREAKING CHANGE: Minor simplifications to the completion internals, notably `Toys::Completion::Context#fragment_prefix` has been removed, and `prefix_constraint` has been removed from several provided completions. A completion now always receives the entire word under the cursor as its `fragment`, and every candidate must be a replacement for the entire word. (The prefix handling requirements of the Bash completion have been moved up into `Toys::Utils::CompletionEngine`.)
65
+ * BREAKING CHANGE: The Bash and Zsh completion engine constructors now take a completion and a loader instead of a cli, and some of the internals and override points have been reorganized.
66
+ * 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.
67
+ * 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 `:`.
68
+ * 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.
69
+ * 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.
70
+
71
+ * Changes to the CLI interfaces:
72
+ * 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.
73
+ * 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.
74
+ * BREAKING CHANGE: It is no longer possible to configure the index file name, lib and data directory names, and preload names, via keyword arguments. This was done to simplify the CLI and Loader interfaces, and because I realized that if they ever were to be customizable, it would need to be per source directory rather than per-CLI.
75
+ * 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.
76
+ * 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`.
77
+ * 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).
78
+ * `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.
79
+ * The `Toys::CLI` constructor now takes `git_cache` and `gems_util` keyword arguments letting you customize the objects used to resolve git and gem sources.
80
+ * Calling `Toys::CLI#add_search_path` with a nonexistent directory no longer raises.
81
+ * The ApplyConfig middleware now behaves correctly when configured globally in the CLI or loader.
82
+
83
+ * Rubygems/Bundler integration changes:
84
+ * 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.
85
+ * Bundler integration properly detects provenance attributes such as `path:` and `git:`.
86
+ * Calling bundler integration with a nonempty group list no longer loses gems that were already loaded.
87
+ * Bundler integration now honors `BUNDLE_LOCKFILE` (new in Bundler 4) and stops leaking it from the modified bundle.
88
+
89
+ * Other fixes
90
+ * Terminal mixin now raises the correct ArgumentError (instead of NameError) if given an unknown style code.
91
+
3
92
  ### v0.22.0 / 2026-05-05
4
93
 
5
94
  Toys-core 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.
data/README.md CHANGED
@@ -87,7 +87,7 @@ require "toys-core"
87
87
  cli = Toys::CLI.new
88
88
 
89
89
  #### Insert the following block ...
90
- cli.add_config_block do
90
+ cli.add_source do
91
91
  desc "My first executable!"
92
92
  flag :whom, default: "world"
93
93
  def run
@@ -109,9 +109,9 @@ $ ./mycmd --help
109
109
  ```
110
110
 
111
111
  Notice that we did not create a `tool` block, but instead set up description,
112
- flags, and functionality directly in the configuration block. This configures
113
- the "root tool", i.e. what happens when you run the executable without passing
114
- a tool name to it. (In fact, it's technically legal to do this in Toys as well,
112
+ flags, and functionality directly in the source block. This configures the
113
+ "root tool", i.e. what happens when you run the executable without passing a
114
+ tool name to it. (In fact, it's technically legal to do this in Toys as well,
115
115
  by setting functionality at the "top level" of a `.toys.rb` file without any
116
116
  `tool` block, although you probably won't actually want to do so.)
117
117
 
@@ -119,7 +119,8 @@ by setting functionality at the "top level" of a `.toys.rb` file without any
119
119
 
120
120
  But perhaps you want your executable to have multiple "tools", similar to other
121
121
  familiar executables like git or kubectl. You can define tools, including
122
- nested tools, by writing `tool` blocks in your config. Here's an example:
122
+ nested tools, by writing `tool` sub-blocks within your source block. Here's an
123
+ example:
123
124
 
124
125
  ```ruby
125
126
  #!/usr/bin/env ruby
@@ -128,8 +129,8 @@ require "toys-core"
128
129
 
129
130
  cli = Toys::CLI.new
130
131
 
131
- #### Change the config block as follows ...
132
- cli.add_config_block do
132
+ #### Change the source block as follows ...
133
+ cli.add_source do
133
134
  # Things outside any tool block still apply to the root
134
135
  desc "My first executable with several tools"
135
136
 
@@ -162,7 +163,7 @@ available tools.
162
163
  $ ./mycmd
163
164
  ```
164
165
 
165
- Notice that the description set at the "root" of the config block (outside the
166
+ Notice that the description set at the "root" of the source block (outside the
166
167
  tool blocks) shows up here.
167
168
 
168
169
  ### Configuring the CLI
@@ -189,8 +190,8 @@ cli = Toys::CLI.new(
189
190
  }
190
191
  )
191
192
 
192
- #### Change the config block as follows ...
193
- cli.add_config_block do
193
+ #### Change the source block as follows ...
194
+ cli.add_source do
194
195
  tool "example" do
195
196
  tool "greet" do
196
197
  def run
@@ -238,8 +239,8 @@ middlewares = [
238
239
  ]
239
240
  cli = Toys::CLI.new middleware_stack: middlewares
240
241
 
241
- #### Use this config block ...
242
- cli.add_config_block do
242
+ #### Use this source block ...
243
+ cli.add_source do
243
244
  tool "greet" do
244
245
  def run
245
246
  puts "Hello, world!"