toys 0.19.1 → 0.20.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: a74557d30e1049351aadf228ff90e2d81bc95c3eceb12058994f4344b7166d6a
4
- data.tar.gz: 127e2bee0af281200cb10af3d3185e928d805f39c3650f2564bd4d626b499a63
3
+ metadata.gz: 91bb0564850a2dc5b0742038d542cbb1239b2bcdef5db7f256c8deceac580e82
4
+ data.tar.gz: 894bdc6c036528a156fecdd79ee65ec4d099281552c5fe3f6ec6488627985fb2
5
5
  SHA512:
6
- metadata.gz: 0d0002be9a833ec73cb6b4d6051f71f6652855b8e4ea9de80c707a0e215f2de88755c625bf4d3759abb0e9a26c9ef5719ee8cd3a4f65309800f471413bdf24d3
7
- data.tar.gz: 7de57d4dd7c143da7dce7316d530639859817a81cfb29b08d886380a23be9fb0fe6994bb24647c79ecdbb0d9924785665c9fb8b968a83a0fa653fb76d67889ce
6
+ metadata.gz: 576e41a7ffda908b16b696158bf7baa00b0a9fe065425847e24ee5db01082dbbc61119362de0e14a98f00d5a0edb727ec42f5428ab110b3be5a0cf318041d628
7
+ data.tar.gz: 56e9a65cbc25bd8c40b30885a61399fc7927b047b2f2edffbea0533a642b969734d52303a01637efc9a38e1300d1f6ed3697fa61515a6522d54148511b1d2a1d
data/CHANGELOG.md CHANGED
@@ -1,5 +1,59 @@
1
1
  # Release History
2
2
 
3
+ ### v0.20.0 / 2026-03-09
4
+
5
+ Toys 0.20 is a major release with several new features and a number of fixes, including a few minor breaking changes.
6
+
7
+ Changes in the `:minitest` template:
8
+
9
+ * BREAKING CHANGE: Tools produced by the `:minitest` template default to looking for test files of the forms `*_test.rb` and `test_*.rb` instead of `test*.rb`.
10
+ * NEW: The `:minitest` template supports installing specified gems without using bundler.
11
+ * NEW: Tools produced by the `:minitest` template provide command line arguments for overriding the gem installation or bundler settings.
12
+ * NEW: Tools produced by the `:minitest` template recognize `--include` as an alias for `--name`. This matches recent versions of minitest.
13
+ * NEW: The `:minitest` template generates more comprehensive documentation.
14
+ * NEW: The minitest tool template now requires "minitest/autorun" before loading tests, so tests don't have to do so themselves.
15
+ * BREAKING API CHANGE: Toys::Templates::Minitest::DEFAULT_GEM_VERSION_REQUIREMENTS is now a hash that covers multiple gems rather than just the minitest gem
16
+ * FIXED: The minitest template no longer exceeds command line length limits if the list of test files is extremely long.
17
+
18
+ New functionality in the `:rspec` template:
19
+
20
+ * NEW: The `:rspec` template supports installing specified gems without using bundler.
21
+ * NEW: Tools produced by the `:rspec` template provide command line arguments for overriding the gem installation or bundler settings.
22
+ * NEW: Tools produced by the `:rspec` template recognize the `--example-matches` flag, and can handle multiple `--example` and `--tag` flags.
23
+ * NEW: The `:rspec` template generates more comprehensive documentation.
24
+ * BREAKING API CHANGE: Toys::Templates::Rspec::DEFAULT_GEM_VERSION_REQUIREMENTS is now a hash that covers potentially multiple gems
25
+
26
+ New functionality in the `:clean` template:
27
+
28
+ * The `:clean` template supports specifying certain gitignored files to preserve.
29
+ * The `:clean` template is more robust against concurrent modification and works better with large git repos.
30
+
31
+ New functionality in the `system test` builtin tool:
32
+
33
+ * BREAKING CHANGE: The `system test` builtin looks for test files of the form `*_test.rb` in addition to `test_*.rb`.
34
+ * The `system test` builtin uses bundler to install gems if a Gemfile is present in the `.test` directory.
35
+ * The `system test` builtin supports flags that can specify arbitrary gems to load.
36
+
37
+ Updates to the Exec mixin and library:
38
+
39
+ * NEW: The new `Toys::Utils::Exec::Result#effective_result` method provides a reasonable integer result code even when a process terminates via signal or fails to start at all.
40
+ * BREAKING API CHANGE: `:cli` is no longer a legal config option.
41
+ * BREAKING API CHANGE: An options hash is no longer passed to the proc when executing a proc using a fork.
42
+ * BREAKING API CHANGE: Passing an IO object as an input or output stream no longer closes it afterward.
43
+ * BREAKING API CHANGE: `Toys::Utils::Exec::Controller#result` no longer preemptively (and prematurely) closes the controller input stream
44
+ * FIXED: The `:unsetenv_others` option now works properly when executing a proc using a fork.
45
+ * FIXED: Environment variable values specified as nil are now correctly unset when executing a proc using a fork.
46
+ * FIXED: Fixed a rare concurrency issue if multiple threads concurrently get the result from a controller.
47
+
48
+ Other changes:
49
+
50
+ * NEW: Native tab completion for zsh.
51
+ * NEW: The `:bundler` mixin supports "manual" bundle setup, allowing bundler decisions to be deferred to execution time
52
+ * FIXED: The `:bundler` mixin will not attempt to add the `pathname` gem to generated Gemfiles when running on TruffleRuby. This caused issues because TruffleRuby includes a special version of the gem and cannot install the one from Rubygems.
53
+ * FIXED: `ContextualError` no longer overrides `Exception#cause`, which could confuse TruffleRuby.
54
+ * DOCUMENTATION: Updated user guide to cover zsh completion and manual bundler setup
55
+ * DOCUMENTATION: Some reorganization and cleanup in the user guide
56
+
3
57
  ### v0.19.1 / 2026-01-06
4
58
 
5
59
  * FIXED: The minitest template and the "system test" builtin now support minitest 6
@@ -9,159 +9,242 @@ flag :seed, "-s", "--seed SEED",
9
9
  flag :warnings, "-w", "--[no-]warnings",
10
10
  default: true,
11
11
  desc: "Turn on Ruby warnings (defaults to true)"
12
- flag :name, "-n", "--name PATTERN",
12
+ flag :include_name, "-n", "-i", "--name PATTERN", "--include PATTERN",
13
13
  desc: "Filter run on /regexp/ or string."
14
- flag :exclude, "-e", "--exclude PATTERN",
14
+ flag :exclude_name, "-e", "-x", "--exclude PATTERN",
15
15
  desc: "Exclude /regexp/ or string from run."
16
16
  flag :recursive, "--[no-]recursive", default: true,
17
17
  desc: "Recursively test subtools (default is true)"
18
18
  flag :tool, "-t TOOL", "--tool TOOL", default: "",
19
19
  desc: "Run tests only for tools under the given path"
20
- flag :minitest_version, "--minitest-version=VERSION", default: ">=5.0,<7",
21
- desc: "Set the minitest version requirement (default is >=5.0,<7)"
20
+ flag :minitest_version, "--minitest-version=VERSION", "--minitest=VERSION",
21
+ desc: "Set the minitest version requirement during runs where no Gemfile is present"
22
22
  flag :minitest_focus, "--minitest-focus[=VERSION]",
23
- desc: "Make minitest-focus available during the run"
23
+ desc: "Make minitest-focus available during runs where no Gemfile is present"
24
24
  flag :minitest_mock, "--minitest-mock[=VERSION]",
25
- desc: "Make minitest-mock available during the run"
25
+ desc: "Make minitest-mock available during runs where no Gemfile is present"
26
26
  flag :minitest_rg, "--minitest-rg[=VERSION]",
27
- desc: "Make minitest-rg available during the run"
28
- flag :minitest_compat, "--[no-]minitest-compat",
27
+ desc: "Make minitest-rg available during runs where no Gemfile is present"
28
+ flag :use_gems, "--use-gem=SPEC",
29
+ default: [], handler: :push,
30
+ desc: "Install the given gem with version requirements during runs where no Gemfile is present"
31
+ flag :minitest_compat, "--[no-]minitest-compat", "--[no-]mt-compat",
29
32
  desc: "Set MT_COMPAT to retain compatibility with certain old plugins"
33
+ flag :expand_globs, "--globs", "--expand-globs",
34
+ desc: "Expand globs in the test file arguments."
35
+
36
+ remaining_args :tests,
37
+ complete: :file_system,
38
+ desc: "Paths to the tests to run (defaults to all tests)"
30
39
 
31
40
  include :exec
32
41
  include :gems
33
42
  include :terminal
34
43
 
35
44
  def run
36
- env = ruby_env
37
- ENV["MT_COMPAT"] = env["MT_COMPAT"] if env.key?("MT_COMPAT")
38
- load_minitest_gems
39
- result = exec_ruby(ruby_args, log_cmd: "Starting minitest...", env: env)
40
- if result.error?
41
- logger.error("Minitest failed!")
42
- exit(result.exit_code)
43
- end
44
- end
45
-
46
- def load_minitest_gems
47
- if minitest_focus
48
- set :minitest_focus, "~>1.4,>=1.4.1" if minitest_focus == true
49
- gem "minitest-focus", *minitest_focus.split(",")
50
- require "minitest/focus"
51
- end
52
- if minitest_mock
53
- set :minitest_mock, "~>5.27" if minitest_mock == true
54
- gem "minitest-mock", *minitest_mock.split(",")
55
- require "minitest/mock"
56
- end
57
- if minitest_rg
58
- set :minitest_rg, "~>5.4" if minitest_rg == true
59
- gem "minitest-rg", *minitest_rg.split(",")
60
- require "minitest/rg"
45
+ setup_mt_compat
46
+ final_code = 0
47
+ jobs = determine_jobs
48
+ if jobs.empty?
49
+ puts "WARNING: No test files found", :yellow, :bold
50
+ exit
61
51
  end
62
- gem "minitest", *minitest_version.split(",")
63
- require "minitest"
64
-
65
- @minitest_version = @minitest_focus_version = @minitest_mock_version = @minitest_rg_version = nil
66
- Gem.loaded_specs.each_value do |spec|
67
- @minitest_version = spec.version.to_s if spec.name == "minitest"
68
- @minitest_focus_version = spec.version.to_s if spec.name == "minitest-focus"
69
- @minitest_mock_version = spec.version.to_s if spec.name == "minitest-mock"
70
- @minitest_rg_version = spec.version.to_s if spec.name == "minitest-rg"
52
+ jobs.each do |job|
53
+ puts "Running #{job.name}", :bold
54
+ result = run_job(job)
55
+ if result.success?
56
+ puts "Succeeded: #{job.name}", :green, :bold
57
+ else
58
+ puts "Failed: #{job.name} (code=#{result.effective_code})", :red, :bold
59
+ final_code = 1
60
+ end
71
61
  end
62
+ exit(final_code)
72
63
  end
73
64
 
74
- def ruby_env
65
+ def setup_mt_compat
75
66
  case minitest_compat
76
67
  when true
77
- { "MT_COMPAT" => "true" }
68
+ ENV["MT_COMPAT"] = "true"
78
69
  when false
79
- { "MT_COMPAT" => nil }
80
- else
81
- {}
70
+ ENV.delete("MT_COMPAT")
82
71
  end
83
72
  end
84
73
 
85
- def ruby_args
86
- args = []
87
- args << "-w" if warnings
88
- args << "-I#{::Toys::CORE_LIB_PATH}#{::File::PATH_SEPARATOR}#{::Toys::LIB_PATH}"
89
- args << "-e" << ruby_code.join("\n")
90
- args << "--"
74
+ Job = ::Struct.new(:name, :globs, :tests, :gemfile)
75
+
76
+ def run_job(job)
77
+ args = ["system", "test", "_internal"]
78
+ args.concat(verbosity_flags)
91
79
  args << "--seed" << seed if seed
92
- args << "--verbose" if verbosity.positive?
93
- args << "--name" << name if name
94
- args << "--exclude" << exclude if exclude
95
- args
80
+ args << "--no-warnings" unless warnings
81
+ args << "--name" << include_name if include_name
82
+ args << "--exclude" << exclude_name if exclude_name
83
+ if job.gemfile
84
+ args << "--gemfile-path" << job.gemfile
85
+ else
86
+ add_gem_args(args)
87
+ end
88
+ args << "--globs" if job.globs
89
+ args << "--preload-code" << preload_code
90
+ args.concat(Array(job.globs || job.tests))
91
+ exec_separate_tool(args)
92
+ end
93
+
94
+ def preload_code
95
+ <<~RUBY
96
+ require "toys"
97
+ require "toys/testing"
98
+ Toys::Testing.toys_custom_paths(#{base_dir.inspect})
99
+ Toys::Testing.toys_include_builtins(false)
100
+ RUBY
96
101
  end
97
102
 
98
- def ruby_code
99
- code = []
100
- code << "gem 'minitest', '= #{@minitest_version}'"
101
- code << "require 'minitest/autorun'"
102
- if @minitest_focus_version
103
- code << "gem 'minitest-focus', '= #{@minitest_focus_version}'"
104
- code << "require 'minitest/focus'"
103
+ def add_gem_args(args)
104
+ use_gems_hash = use_gems.to_h do |spec|
105
+ name, version = spec.strip.split(/\s*,\s*/, 2)
106
+ [name, version]
105
107
  end
106
- if @minitest_mock_version
107
- code << "gem 'minitest-mock', '= #{@minitest_mock_version}'"
108
- code << "require 'minitest/mock'"
108
+ use_gems_hash["minitest"] = minitest_version
109
+ use_gems_hash["minitest-mock"] = minitest_mock if minitest_mock
110
+ use_gems_hash["minitest-focus"] = minitest_focus if minitest_focus
111
+ use_gems_hash["minitest-rg"] = minitest_rg if minitest_rg
112
+ if ::ENV["TOYS_DEV"] == "true"
113
+ args << "--libs" << ::Toys::CORE_LIB_PATH unless use_gems_hash["toys-core"]
114
+ args << "--libs" << ::Toys::LIB_PATH unless use_gems_hash["toys"]
115
+ else
116
+ use_gems_hash["toys"] ||= ::Toys::VERSION
109
117
  end
110
- if @minitest_rg_version
111
- code << "gem 'minitest-rg', '= #{@minitest_rg_version}'"
112
- code << "require 'minitest/rg'"
118
+ use_gems_hash.each do |name, versions|
119
+ versions = nil if versions == true
120
+ args << "--use-gem" << [name, versions].compact.join(",")
113
121
  end
114
- code << "require 'toys'"
115
- code << "require 'toys/testing'"
116
- if directory
117
- code << "Toys::Testing.toys_custom_paths(#{::File.absolute_path(directory).inspect})"
118
- code << "Toys::Testing.toys_include_builtins(false)"
122
+ end
123
+
124
+ def determine_jobs
125
+ return determine_jobs_from_tests unless tests.empty?
126
+ jobs = []
127
+ job = build_job_under(::File.join(tool_dir, ".test"))
128
+ jobs << job if job
129
+ if recursive
130
+ ::Dir.glob("#{tool_dir}/*/**/.test").sort.each do |test_dir|
131
+ job = build_job_under(test_dir)
132
+ jobs << job if job
133
+ end
134
+ end
135
+ jobs
136
+ end
137
+
138
+ def determine_jobs_from_tests
139
+ jobs = []
140
+ paths_by_test_dir = {}
141
+ paths_without_gemfile = []
142
+ preprocess_tests.each do |path|
143
+ test_dir = find_test_dir(path)
144
+ if test_dir
145
+ (paths_by_test_dir[test_dir] ||= []) << path
146
+ else
147
+ paths_without_gemfile << path
148
+ end
149
+ end
150
+ paths_by_test_dir.each do |test_dir, paths|
151
+ gemfile_path = ::File.join(test_dir, "Gemfile")
152
+ if ::File.file?(gemfile_path)
153
+ jobs << Job.new("specified tests under #{test_dir}", nil, paths, gemfile_path)
154
+ else
155
+ paths_without_gemfile.concat(paths)
156
+ end
119
157
  end
120
- find_test_files.each do |file|
121
- code << "load '#{file}'"
158
+ unless paths_without_gemfile.empty?
159
+ name_prefix = jobs.empty? ? "" : "remaining "
160
+ jobs << Job.new("#{name_prefix}specified tests", nil, paths_without_gemfile, nil)
122
161
  end
123
- code
162
+ jobs
124
163
  end
125
164
 
126
- def find_test_files
127
- glob = ".test/**/test_*.rb"
128
- glob = "**/#{glob}" if recursive
129
- glob = "#{tool_dir}/#{glob}"
130
- test_files = Dir.glob(glob)
131
- if test_files.empty?
132
- logger.warn("No test files found")
133
- exit
165
+ def preprocess_tests
166
+ results = []
167
+ tests.each do |elem|
168
+ if expand_globs
169
+ glob_results = ::Dir.glob(elem)
170
+ if glob_results.empty?
171
+ logger.warn("Pattern did not match any test files: #{elem}")
172
+ else
173
+ glob_results.each do |path|
174
+ results << ::File.realpath(path)
175
+ end
176
+ end
177
+ else
178
+ begin
179
+ results << ::File.realpath(elem)
180
+ rescue ::Errno::ENOENT
181
+ logger.error("Unable to find file: #{elem}")
182
+ exit(1)
183
+ end
184
+ end
134
185
  end
135
- test_files.each do |file|
136
- logger.info("Loading: #{file}")
186
+ results
187
+ end
188
+
189
+ def find_test_dir(path)
190
+ dir = ::File.dirname(path)
191
+ while dir != path
192
+ return dir if ::File.basename(dir) == ".test"
193
+ path = dir
194
+ dir = ::File.dirname(dir)
137
195
  end
138
- test_files
196
+ nil
197
+ end
198
+
199
+ def build_job_under(test_path)
200
+ return nil unless ::File.directory?(test_path)
201
+ globs = ["#{test_path}/**/test_*.rb", "#{test_path}/**/*_test.rb"]
202
+ globs.delete_if { |glob| ::Dir.glob(glob).empty? }
203
+ return nil if globs.empty?
204
+ gemfile_path = ::File.join(test_path, "Gemfile")
205
+ gemfile_path = nil unless ::File.file?(gemfile_path)
206
+ Job.new("tests under #{test_path}", globs, nil, gemfile_path)
139
207
  end
140
208
 
141
209
  def tool_dir
142
- words = cli.loader.split_path(tool)
143
- dir = base_dir
144
- unless words.empty?
145
- dir = ::File.join(dir, *words)
146
- unless ::File.directory?(dir)
147
- logger.warn("No such directory: #{dir}")
148
- exit
210
+ @tool_dir ||= begin
211
+ words = cli.loader.split_path(tool)
212
+ dir = base_dir
213
+ unless words.empty?
214
+ dir = ::File.join(dir, *words)
215
+ unless ::File.directory?(dir)
216
+ logger.warn("No such directory: #{dir}")
217
+ exit
218
+ end
149
219
  end
220
+ dir
150
221
  end
151
- dir
152
222
  end
153
223
 
154
224
  def base_dir
155
- return ::File.absolute_path(directory) if directory
156
- dir = ::Dir.getwd
157
- loop do
158
- candidate = ::File.join(dir, ::Toys::StandardCLI::CONFIG_DIR_NAME)
159
- return candidate if ::File.directory?(candidate)
160
- parent = ::File.dirname(dir)
161
- if parent == dir
162
- logger.error("Unable to find a Toys directory")
163
- exit(1)
225
+ @base_dir ||=
226
+ if directory
227
+ unless ::File.directory?(directory)
228
+ logger.error("Directory not found: #{directory}")
229
+ exit(1)
230
+ end
231
+ ::File.realpath(directory)
232
+ else
233
+ dir = ::File.realpath(::Dir.getwd)
234
+ loop do
235
+ candidate = ::File.join(dir, ::Toys::StandardCLI::CONFIG_DIR_NAME)
236
+ break candidate if ::File.directory?(candidate)
237
+ parent = ::File.dirname(dir)
238
+ if parent == dir
239
+ logger.error("Unable to find a Toys directory")
240
+ exit(1)
241
+ end
242
+ dir = parent
243
+ end
164
244
  end
165
- dir = parent
166
- end
245
+ end
246
+
247
+ expand :minitest do |mt|
248
+ mt.name = "_internal"
249
+ mt.files = []
167
250
  end
@@ -0,0 +1,91 @@
1
+ # frozen_string_literal: true
2
+
3
+ desc "Zsh tab completion for Toys"
4
+
5
+ long_desc \
6
+ "Tools that manage tab completion for Toys in the zsh shell.",
7
+ "",
8
+ "To install tab completion for Toys, execute the following line in a zsh shell, or" \
9
+ " include it in an init file such as your .zshrc (after the line that calls compinit):",
10
+ [" $(toys system zsh-completion install)"],
11
+ "",
12
+ "To remove tab completion, execute:",
13
+ [" $(toys system zsh-completion remove)"],
14
+ "",
15
+ "It is also possible to install completions for different executable names if you have" \
16
+ " aliases for Toys. See the help for the \"install\" and \"remove\" tools for details.",
17
+ "",
18
+ "The \"eval\" tool is the actual completion command invoked by zsh when it needs to" \
19
+ " complete a toys command line. You shouldn't need to invoke it directly."
20
+
21
+ tool "install" do
22
+ desc "Install zsh tab completion"
23
+
24
+ long_desc \
25
+ "Outputs a command to set up Toys tab completion in the current zsh shell.",
26
+ "",
27
+ "To use, execute the following line in a zsh shell, or include it in an init file" \
28
+ " such as your .zshrc (after the line that calls compinit):",
29
+ [" $(toys system zsh-completion install)"],
30
+ "",
31
+ "This will associate the toys tab completion logic with the `toys` executable by default." \
32
+ " If you have aliases for the toys executable, pass them as arguments. e.g.",
33
+ [" $(toys system zsh-completion install my-toys-alias another-alias)"]
34
+
35
+ remaining_args :executable_names,
36
+ desc: "Names of executables for which to set up tab completion" \
37
+ " (default: #{::Toys::StandardCLI::EXECUTABLE_NAME})"
38
+
39
+ def run
40
+ require "shellwords"
41
+ path = ::File.join(::File.dirname(::File.dirname(__dir__)), "share", "zsh-completion.sh")
42
+ exes = executable_names.empty? ? [::Toys::StandardCLI::EXECUTABLE_NAME] : executable_names
43
+ puts Shellwords.join(["source", path] + exes)
44
+ end
45
+ end
46
+
47
+ tool "remove" do
48
+ desc "Remove zsh tab completion"
49
+
50
+ long_desc \
51
+ "Outputs a command to remove Toys tab completion from the current zsh shell.",
52
+ "",
53
+ "To use, execute the following line in a zsh shell:",
54
+ [" $(toys system zsh-completion remove)"],
55
+ "",
56
+ "If you have other names or aliases for the toys executable, pass them as arguments. e.g.",
57
+ [" $(toys system zsh-completion remove my-toys-alias another-alias)"]
58
+
59
+ remaining_args :executable_names,
60
+ desc: "Names of executables for which to remove tab completion" \
61
+ " (default: #{::Toys::StandardCLI::EXECUTABLE_NAME})"
62
+
63
+ def run
64
+ require "shellwords"
65
+ path = ::File.join(::File.dirname(::File.dirname(__dir__)), "share", "zsh-completion-remove.sh")
66
+ exes = executable_names.empty? ? [::Toys::StandardCLI::EXECUTABLE_NAME] : executable_names
67
+ puts Shellwords.join(["source", path] + exes)
68
+ end
69
+ end
70
+
71
+ tool "eval" do
72
+ desc "Tab completion command (executed by zsh)"
73
+
74
+ long_desc \
75
+ "Completion command invoked by zsh to complete a toys command line. Generally you do not" \
76
+ " need to invoke this directly. It reads the command line context from the COMP_LINE" \
77
+ " and COMP_POINT environment variables, and outputs completion candidates to stdout in" \
78
+ " two sections separated by a blank line: final completions first, then partial" \
79
+ " completions (such as directory paths)."
80
+
81
+ disable_argument_parsing
82
+
83
+ def run
84
+ require "toys/utils/completion_engine"
85
+ result = ::Toys::Utils::CompletionEngine::Zsh.new(cli).run
86
+ if result > 1
87
+ logger.fatal("This tool must be invoked as a zsh completion command.")
88
+ end
89
+ exit(result)
90
+ end
91
+ end
@@ -9,6 +9,6 @@ module Toys
9
9
  # Current version of Toys core.
10
10
  # @return [String]
11
11
  #
12
- VERSION = "0.19.1"
12
+ VERSION = "0.20.0"
13
13
  end
14
14
  end
@@ -58,17 +58,18 @@ module Toys
58
58
  #
59
59
  # @private This interface is internal and subject to change without warning.
60
60
  #
61
- def initialize(cause, banner,
61
+ def initialize(underlying_error, banner,
62
62
  config_path: nil, config_line: nil,
63
63
  tool_name: nil, tool_args: nil)
64
64
  # Source available in the toys-core gem
65
65
  end
66
66
 
67
67
  ##
68
- # The underlying exception
68
+ # The underlying exception.
69
+ # Generally the same as `Exception#cause`.
69
70
  # @return [::StandardError]
70
71
  #
71
- attr_reader :cause
72
+ attr_reader :underlying_error
72
73
 
73
74
  ##
74
75
  # An overall banner message
@@ -22,9 +22,21 @@ module Toys
22
22
  #
23
23
  # The following parameters can be passed when including this mixin:
24
24
  #
25
- # * `:static` (Boolean) If `true`, installs the bundle immediately, when
26
- # defining the tool. If `false` (the default), installs the bundle just
27
- # before the tool runs.
25
+ # * `:static` (Boolean) Has the same effect as passing `:static` to the
26
+ # `:setup` parameter.
27
+ #
28
+ # * `:setup` (:auto,:manual,:static) A symbol indicating when the bundle
29
+ # should be installed. Possible values are:
30
+ #
31
+ # * `:auto` - (Default) Installs the bundle just before the tool runs.
32
+ # * `:static` - Installs the bundle immediately when defining the
33
+ # tool.
34
+ # * `:manual` - Does not install the bundle, but defines the methods
35
+ # `bundler_setup` and `bundler_setup?` in the tool. The tool can
36
+ # call `bundler_setup` to install the bundle, optionally passing
37
+ # any of the remaining keyword arguments below to override the
38
+ # corresponding mixin parameters. The `bundler_setup?` method can
39
+ # be queried to determine whether the bundle has been set up yet.
28
40
  #
29
41
  # * `:groups` (Array\<String\>) The groups to include in setup.
30
42
  #
@@ -76,7 +88,9 @@ module Toys
76
88
  # (optional)
77
89
  #
78
90
  # * `:terminal` (Toys::Utils::Terminal) Terminal to use (optional)
91
+ #
79
92
  # * `:input` (IO) Input IO (optional, defaults to STDIN)
93
+ #
80
94
  # * `:output` (IO) Output IO (optional, defaults to STDOUT)
81
95
  #
82
96
  module Bundler
@@ -109,7 +109,8 @@ module Toys
109
109
  # an existing File stream. Unlike `Process#spawn`, this works for IO
110
110
  # objects that do not have a corresponding file descriptor (such as
111
111
  # StringIO objects). In such a case, a thread will be spawned to pipe
112
- # the IO data through to the child process.
112
+ # the IO data through to the child process. Note that the IO object
113
+ # will _not_ be closed on completion.
113
114
  #
114
115
  # * **Redirect to a pipe:** You can redirect to a pipe created using
115
116
  # `IO.pipe` (i.e. a two-element array of read and write IO objects) by
@@ -12,11 +12,15 @@ module Toys
12
12
  ##
13
13
  # **_Defined in the toys-core gem_**
14
14
  #
15
- # A completion engine for bash.
15
+ # Base class for shell completion engines that use a
16
+ # `COMP_LINE` / `COMP_POINT` protocol.
17
+ #
18
+ # Subclasses must implement the private methods `#shell_name` and
19
+ # `#output_completions`.
16
20
  #
17
- class Bash
21
+ class Base
18
22
  ##
19
- # Create a bash completion engine.
23
+ # Create a completion engine.
20
24
  #
21
25
  # @param cli [Toys::CLI] The CLI.
22
26
  #
@@ -27,8 +31,8 @@ module Toys
27
31
  ##
28
32
  # Perform completion in the current shell environment, which must
29
33
  # include settings for the `COMP_LINE` and `COMP_POINT` environment
30
- # variables. Prints out completion candidates, one per line, and
31
- # returns a status code indicating the result.
34
+ # variables. Prints out completion candidates and returns a status code
35
+ # indicating the result.
32
36
  #
33
37
  # * **0** for success.
34
38
  # * **1** if completion failed.
@@ -42,6 +46,30 @@ module Toys
42
46
  end
43
47
  end
44
48
 
49
+ ##
50
+ # **_Defined in the toys-core gem_**
51
+ #
52
+ # A completion engine for bash.
53
+ #
54
+ class Bash < Base
55
+ ##
56
+ # Create a bash completion engine.
57
+ #
58
+ # @param cli [Toys::CLI] The CLI.
59
+ #
60
+ def initialize(cli)
61
+ # Source available in the toys-core gem
62
+ end
63
+ end
64
+
65
+ ##
66
+ # **_Defined in the toys-core gem_**
67
+ #
68
+ # A completion engine for zsh.
69
+ #
70
+ class Zsh < Base
71
+ end
72
+
45
73
  class << self
46
74
  end
47
75
  end