toys-core 0.19.1 → 0.21.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: 815f546de5a0da65099cf1fdce70527fb42aafd3793bf8a99e35f63d77c4e66b
4
- data.tar.gz: 79bd650fb1bdc5d04a2b1e1d235864e39313b4320518b9ac6296a8e1892aa8b1
3
+ metadata.gz: f2805bf71091b4ea8bf6f6a670281171dc6a5df4edec5196d80d64124ce2a199
4
+ data.tar.gz: b5c860449af3a0566e9b1fe8dc527cba4df6750ccc3b259365faf99fa79682fb
5
5
  SHA512:
6
- metadata.gz: e696b9c3921aec0c56796bdd7b93a09d056f6d46347a5936bbf6a0ce9bc7b2811f90d4b9553c54bdf69b62d74289e4608da98473e9b62e34f155accbf665f7a1
7
- data.tar.gz: 675e01b73df268ceda5cfd5bd62f4a21b164c2fce0b37e88f3397bc12f76ba8baa4b2969df5d82901ffbb78440dd1156374cc6642792d81a4ec9c149a800148a
6
+ metadata.gz: 95490a978d1a3ae04be668a2fd318de2499e9610bb6072a053776fb47214d2444c0d93a73fa690b46a6d4706e2fec1f20b76f8d111100ec65216f0c274efe77a
7
+ data.tar.gz: b9421ebfa989c45444024222cc3576a166d5797d7c982033265a11aa149c71ef0ab069441c70da490819e5bbafe95eb017c92289d6b46d7917c19634ee3eaf64
data/CHANGELOG.md CHANGED
@@ -1,5 +1,61 @@
1
1
  # Release History
2
2
 
3
+ ### v0.21.0 / 2026-03-23
4
+
5
+ This release includes a variety of small fixes and updates toward improving product polish in preparation for a 1.0 release. It focuses on the following areas:
6
+
7
+ * Updates to the help system
8
+ * FIXED / BREAKING CHANGE: The help middleware omits the subtool filter flags on runnable tools that have subtools
9
+ * Updates to bundler integration
10
+ * FIXED: Fixed some cleanup issues when bundler setup fails
11
+ * Updates to the exec utility
12
+ * ADDED: Support for the `:unbundle` option which removes any existing bundle for a subprocess
13
+ * FIXED: Fixed several thread-safety issues with the controller
14
+ * FIXED: The result callback is now called in background mode if the result is never explicitly obtained from the controller
15
+ * Updates to the git_cache utility
16
+ * ADDED: Support for filtering lookups of ref and source info
17
+ * ADDED: Support for SHA-256 repos
18
+ * FIXED: Fixed failure to get a parent directory after previously getting a descendant object
19
+ * FIXED: GitCache no longer unnecessarily sets the write bit on files copied into a specified directory
20
+ * FIXED: GitCache no longer cleans out existing files when writing to a shared source, which should reduce issues with concurrent clients
21
+ * Updates to the XDG utility
22
+ * FIXED / BREAKING CHANGE: The XDG utility returns an empty array (instead of the system defaults) from the corresponding methods if `XDG_DATA_DIRS` or `XDG_CONFIG_DIRS` is nonempty but contains only relative paths
23
+ * ADDED: Provided `lookup_state` and `lookup_cache` methods on the XDG utility
24
+ * Updates to the settings system:
25
+ * FIXED: Block certain reserved names from being used as Settings field names
26
+ * FIXED: Fixed `Settings#load_data!` when subclassing another settings class
27
+ * FIXED: Reject non-String values in Settings regexp type spec
28
+ * FIXED: Settings guards against unknown classes when loading YAML on older Ruby versions
29
+ * FIXED: Settings guards against `ILLEGAL_VALUE` being passed to `range.member?`
30
+ * FIXED: Settings does a better job choosing exact-match values when using a union type
31
+ * FIXED: Settings reject non-numeric strings in Integer and Float converters
32
+ * FIXED: Settings propagates nested group errors in `Settings#load_data!`
33
+
34
+ ### v0.20.0 / 2026-03-09
35
+
36
+ Toys-core 0.20 is a major release with several new features and a number of fixes, including a few minor breaking changes.
37
+
38
+ Major changes:
39
+
40
+ * NEW: Native tab completion for zsh.
41
+ * NEW: The `:bundler` mixin supports "manual" bundle setup, allowing bundler decisions to be deferred to execution time
42
+
43
+ Updates to the Exec mixin and library:
44
+
45
+ * 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.
46
+ * BREAKING API CHANGE: `:cli` is no longer a legal config option.
47
+ * BREAKING API CHANGE: An options hash is no longer passed to the proc when executing a proc using a fork.
48
+ * BREAKING API CHANGE: Passing an IO object as an input or output stream no longer closes it afterward.
49
+ * BREAKING API CHANGE: `Toys::Utils::Exec::Controller#result` no longer preemptively (and prematurely) closes the controller input stream
50
+ * FIXED: The `:unsetenv_others` option now works properly when executing a proc using a fork.
51
+ * FIXED: Environment variable values specified as nil are now correctly unset when executing a proc using a fork.
52
+ * FIXED: Fixed a rare concurrency issue if multiple threads concurrently get the result from a controller.
53
+
54
+ Minor fixes for TruffleRuby compatibility:
55
+
56
+ * 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.
57
+ * FIXED: `ContextualError` no longer overrides `Exception#cause`, which could confuse TruffleRuby.
58
+
3
59
  ### v0.19.1 / 2026-01-06
4
60
 
5
61
  * DOCS: Some formatting fixes in the user guide
data/README.md CHANGED
@@ -265,8 +265,8 @@ Navigate to the simple-gem example:
265
265
 
266
266
  $ cd toys-core/examples/simple-gem
267
267
 
268
- This example wraps the simple "greet" executable that we
269
- [covered earlier](#Add_some_functionality) in a gem. You can see the
268
+ This example wraps the simple "greet" executable that we covered earlier, in a
269
+ gem. You can see the
270
270
  [executable file](https://github.com/dazuma/toys/tree/main/toys-core/examples/simple-gem/bin/toys-core-simple-example)
271
271
  in the bin directory.
272
272
 
@@ -348,7 +348,7 @@ recommended because it has a few known bugs that affect Toys.
348
348
 
349
349
  ## License
350
350
 
351
- Copyright 2019-2025 Daniel Azuma and the Toys contributors
351
+ Copyright 2019-2026 Daniel Azuma and the Toys contributors
352
352
 
353
353
  Permission is hereby granted, free of charge, to any person obtaining a copy
354
354
  of this software and associated documentation files (the "Software"), to deal
data/lib/toys/cli.rb CHANGED
@@ -552,7 +552,7 @@ module Toys
552
552
  #
553
553
  def default_error_handler
554
554
  proc do |error|
555
- cause = error.cause
555
+ cause = error.respond_to?(:underlying_error) ? error.underlying_error : error.cause
556
556
  raise cause.is_a?(::SignalException) ? cause : error
557
557
  end
558
558
  end
data/lib/toys/compat.rb CHANGED
@@ -13,35 +13,72 @@ module Toys
13
13
  parts = ::RUBY_VERSION.split(".")
14
14
  ruby_version = (parts[0].to_i * 10000) + (parts[1].to_i * 100) + parts[2].to_i
15
15
 
16
+ ##
16
17
  # @private
18
+ # An integer representation of the Ruby version, guaranteed to have the
19
+ # correct ordering. Currently, this is `major*10000 + minor*100 + patch`.
20
+ #
21
+ # @return [Integer]
22
+ #
17
23
  RUBY_VERSION_CODE = ruby_version
18
24
 
25
+ ##
19
26
  # @private
27
+ # Whether the current Ruby implementation is JRuby
28
+ #
29
+ # @return [boolean]
30
+ #
20
31
  def self.jruby?
21
32
  ::RUBY_ENGINE == "jruby"
22
33
  end
23
34
 
35
+ ##
24
36
  # @private
37
+ # Whether the current Ruby implementation is TruffleRuby
38
+ #
39
+ # @return [boolean]
40
+ #
25
41
  def self.truffleruby?
26
42
  ::RUBY_ENGINE == "truffleruby"
27
43
  end
28
44
 
45
+ ##
29
46
  # @private
47
+ # Whether we are running on Windows
48
+ #
49
+ # @return [boolean]
50
+ #
30
51
  def self.windows?
31
52
  ::RbConfig::CONFIG["host_os"] =~ /mswin|msys|mingw|cygwin|bccwin|wince|emc/
32
53
  end
33
54
 
55
+ ##
34
56
  # @private
57
+ # Whether we are running on Mac OS
58
+ #
59
+ # @return [boolean]
60
+ #
35
61
  def self.macos?
36
62
  ::RbConfig::CONFIG["host_os"] =~ /darwin/
37
63
  end
38
64
 
65
+ ##
39
66
  # @private
67
+ # Whether fork is supported on the current Ruby and OS
68
+ #
69
+ # @return [boolean]
70
+ #
40
71
  def self.allow_fork?
41
72
  !jruby? && !truffleruby? && !windows?
42
73
  end
43
74
 
75
+ ##
44
76
  # @private
77
+ # Whether it is possible to get suggestions from DidYouMean. If this
78
+ # returns false, {Compat.suggestions} will always return the empty array.
79
+ #
80
+ # @return [boolean]
81
+ #
45
82
  def self.supports_suggestions?
46
83
  unless defined?(@supports_suggestions)
47
84
  begin
@@ -59,7 +96,16 @@ module Toys
59
96
  @supports_suggestions
60
97
  end
61
98
 
99
+ ##
62
100
  # @private
101
+ # A list of suggestions from DidYouMean.
102
+ #
103
+ # @param word [String] A value that seems wrong
104
+ # @param list [Array<String>] A list of valid values
105
+ #
106
+ # @return [Array<String>] A possibly empty array of suggestions from the
107
+ # valid list that could match the given word.
108
+ #
63
109
  def self.suggestions(word, list)
64
110
  if supports_suggestions?
65
111
  ::DidYouMean::SpellChecker.new(dictionary: list).correct(word)
@@ -67,5 +113,22 @@ module Toys
67
113
  []
68
114
  end
69
115
  end
116
+
117
+ ##
118
+ # @private
119
+ # A list of gems that should generally not be included in a bundle, usually
120
+ # because the Ruby implementation handles the library specially and cannot
121
+ # install the real gem. Currently, this includes the `pathname` gem for
122
+ # TruffleRuby, since TruffleRuby includes a special version of it.
123
+ #
124
+ # @return [Array<String>]
125
+ #
126
+ def self.gems_to_omit_from_bundles
127
+ if truffleruby?
128
+ ["pathname"]
129
+ else
130
+ []
131
+ end
132
+ end
70
133
  end
71
134
  end
data/lib/toys/core.rb CHANGED
@@ -9,7 +9,7 @@ module Toys
9
9
  # Current version of Toys core.
10
10
  # @return [String]
11
11
  #
12
- VERSION = "0.19.1"
12
+ VERSION = "0.21.0"
13
13
  end
14
14
 
15
15
  ##
data/lib/toys/errors.rb CHANGED
@@ -51,24 +51,25 @@ module Toys
51
51
  #
52
52
  # @private This interface is internal and subject to change without warning.
53
53
  #
54
- def initialize(cause, banner,
54
+ def initialize(underlying_error, banner,
55
55
  config_path: nil, config_line: nil,
56
56
  tool_name: nil, tool_args: nil)
57
- super("#{banner} : #{cause.message} (#{cause.class})")
58
- @cause = cause
57
+ super("#{banner} : #{underlying_error.message} (#{underlying_error.class})")
58
+ @underlying_error = underlying_error
59
59
  @banner = banner
60
60
  @config_path = config_path
61
61
  @config_line = config_line
62
62
  @tool_name = tool_name
63
63
  @tool_args = tool_args
64
- set_backtrace(cause.backtrace)
64
+ set_backtrace(underlying_error.backtrace)
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
@@ -141,10 +142,10 @@ module Toys
141
142
  end
142
143
  raise e
143
144
  rescue ::ScriptError, ::StandardError, ::SignalException => e
144
- e = ContextualError.new(e, banner)
145
- add_fields_if_missing(e, opts)
146
- add_config_path_if_missing(e, path)
147
- raise e
145
+ ce = ContextualError.new(e, banner)
146
+ add_fields_if_missing(ce, opts)
147
+ add_config_path_if_missing(ce, path)
148
+ raise ce
148
149
  end
149
150
 
150
151
  ##
@@ -172,7 +173,7 @@ module Toys
172
173
 
173
174
  def add_config_path_if_missing(error, path)
174
175
  if error.config_path.nil? && error.config_line.nil?
175
- l = (error.cause.backtrace_locations || []).find do |b|
176
+ l = (error.underlying_error.backtrace_locations || []).find do |b|
176
177
  b.absolute_path == path || b.path == path
177
178
  end
178
179
  if l
data/lib/toys/settings.rb CHANGED
@@ -35,8 +35,12 @@ module Toys
35
35
  # Attribute names must start with an ascii letter, and may contain only ascii
36
36
  # letters, digits, and underscores. Unlike method names, they may not include
37
37
  # non-ascii unicode characters, nor may they end with `!` or `?`.
38
- # Additionally, the name `method_missing` is not allowed because of its
39
- # special behavior in Ruby.
38
+ # Additionally, some names are reserved because they would shadow critical
39
+ # Ruby methods or interfere with Settings' internal behavior (see
40
+ # {Toys::Settings::RESERVED_FIELD_NAMES} for the complete list, which
41
+ # currently includes names such as `class`, `clone`, `dup`, `freeze`, `hash`,
42
+ # `initialize`, `method_missing`, `object_id`, `public_send`, `raise`,
43
+ # `require`, and `send`).
40
44
  #
41
45
  # Each attribute defines four methods: a getter, a setter, an unsetter, and a
42
46
  # set detector. In the above example, the attribute named `:endpoint` creates
@@ -297,12 +301,27 @@ module Toys
297
301
  # grandchild_settings.str # => "value_from_root"
298
302
  #
299
303
  class Settings
304
+ ##
300
305
  # A special value indicating a type check failure.
306
+ #
301
307
  ILLEGAL_VALUE = ::Object.new.freeze
302
308
 
309
+ ##
303
310
  # A special type specification indicating infer from the default value.
311
+ #
304
312
  DEFAULT_TYPE = ::Object.new.freeze
305
313
 
314
+ ##
315
+ # Field names that are not allowed because they would shadow critical Ruby
316
+ # methods or break Settings' own internal method calls.
317
+ #
318
+ # @return [Array<String>]
319
+ #
320
+ RESERVED_FIELD_NAMES = [
321
+ "class", "clone", "dup", "freeze", "hash", "initialize",
322
+ "method_missing", "object_id", "public_send", "raise", "require", "send"
323
+ ].freeze
324
+
306
325
  ##
307
326
  # Error raised when a value does not match the type constraint.
308
327
  #
@@ -447,15 +466,14 @@ module Toys
447
466
  range_class = (range.begin || range.end).class
448
467
  new("(#{range})") do |val|
449
468
  converted = convert(val, range_class)
450
- range.member?(converted) ? converted : ILLEGAL_VALUE
469
+ converted != ILLEGAL_VALUE && range.member?(converted) ? converted : ILLEGAL_VALUE
451
470
  end
452
471
  end
453
472
 
454
473
  def for_regexp(regexp)
455
474
  regexp_str = regexp.source.gsub("/", "\\/")
456
475
  new("/#{regexp_str}/") do |val|
457
- str = val.to_s
458
- regexp.match(str) ? str : ILLEGAL_VALUE
476
+ val.is_a?(::String) && regexp.match(val) ? val : ILLEGAL_VALUE
459
477
  end
460
478
  end
461
479
 
@@ -466,7 +484,7 @@ module Toys
466
484
  result = ILLEGAL_VALUE
467
485
  types.each do |type|
468
486
  converted = type.call(val)
469
- if converted == val
487
+ if converted.eql?(val)
470
488
  result = val
471
489
  break
472
490
  elsif result == ILLEGAL_VALUE
@@ -518,7 +536,7 @@ module Toys
518
536
  float_converter = proc do |val|
519
537
  case val
520
538
  when ::String
521
- val.to_f
539
+ Float(val)
522
540
  when ::Numeric
523
541
  converted = val.to_f
524
542
  converted == val ? converted : ILLEGAL_VALUE
@@ -530,7 +548,7 @@ module Toys
530
548
  integer_converter = proc do |val|
531
549
  case val
532
550
  when ::String
533
- val.to_i
551
+ Integer(val)
534
552
  when ::Numeric
535
553
  converted = val.to_i
536
554
  converted == val ? converted : ILLEGAL_VALUE
@@ -605,7 +623,7 @@ module Toys
605
623
  raise FieldError.new(value, self.class, name, nil) unless field
606
624
  if field.group?
607
625
  raise FieldError.new(value, self.class, name, "Hash") unless value.is_a?(::Hash)
608
- get!(field).load_data!(value)
626
+ errors.concat(get!(field).load_data!(value, raise_on_failure: raise_on_failure))
609
627
  else
610
628
  set!(field, value)
611
629
  end
@@ -628,7 +646,7 @@ module Toys
628
646
  #
629
647
  def load_yaml!(str, raise_on_failure: false)
630
648
  require "psych"
631
- load_data!(::Psych.load(str), raise_on_failure: raise_on_failure)
649
+ load_data!(::Psych.safe_load(str, permitted_classes: [::Symbol]), raise_on_failure: raise_on_failure)
632
650
  end
633
651
 
634
652
  ##
@@ -641,7 +659,7 @@ module Toys
641
659
  # @return [Array<FieldError>] An array of errors.
642
660
  #
643
661
  def load_yaml_file!(filename, raise_on_failure: false)
644
- load_yaml!(File.read(filename), raise_on_failure: raise_on_failure)
662
+ load_yaml!(::File.read(filename), raise_on_failure: raise_on_failure)
645
663
  end
646
664
 
647
665
  ##
@@ -668,7 +686,7 @@ module Toys
668
686
  # @return [Array<FieldError>] An array of errors.
669
687
  #
670
688
  def load_json_file!(filename, raise_on_failure: false, **json_opts)
671
- load_json!(File.read(filename), raise_on_failure: raise_on_failure, **json_opts)
689
+ load_json!(::File.read(filename), raise_on_failure: raise_on_failure, **json_opts)
672
690
  end
673
691
 
674
692
  ##
@@ -848,7 +866,12 @@ module Toys
848
866
  # all its instances.
849
867
  #
850
868
  def fields
851
- @fields ||= {}
869
+ @fields ||=
870
+ if superclass.respond_to?(:fields)
871
+ superclass.fields.dup
872
+ else
873
+ {}
874
+ end
852
875
  end
853
876
 
854
877
  ##
@@ -882,7 +905,7 @@ module Toys
882
905
 
883
906
  def interpret_name(name)
884
907
  name = name.to_s
885
- if name !~ /^[a-zA-Z]\w*$/ || name == "method_missing"
908
+ if name !~ /^[a-zA-Z]\w*$/ || RESERVED_FIELD_NAMES.include?(name)
886
909
  raise ::ArgumentError, "Illegal settings field name: #{name}"
887
910
  end
888
911
  existing = public_instance_methods(false)
@@ -53,43 +53,43 @@ module Toys
53
53
  # Key set when the show help flag is present
54
54
  # @return [Object]
55
55
  #
56
- SHOW_HELP_KEY = Object.new.freeze
56
+ SHOW_HELP_KEY = ::Object.new.freeze
57
57
 
58
58
  ##
59
59
  # Key set when the show usage flag is present
60
60
  # @return [Object]
61
61
  #
62
- SHOW_USAGE_KEY = Object.new.freeze
62
+ SHOW_USAGE_KEY = ::Object.new.freeze
63
63
 
64
64
  ##
65
65
  # Key set when the show subtool list flag is present
66
66
  # @return [Object]
67
67
  #
68
- SHOW_LIST_KEY = Object.new.freeze
68
+ SHOW_LIST_KEY = ::Object.new.freeze
69
69
 
70
70
  ##
71
71
  # Key for the recursive setting
72
72
  # @return [Object]
73
73
  #
74
- RECURSIVE_SUBTOOLS_KEY = Object.new.freeze
74
+ RECURSIVE_SUBTOOLS_KEY = ::Object.new.freeze
75
75
 
76
76
  ##
77
77
  # Key for the search string
78
78
  # @return [Object]
79
79
  #
80
- SEARCH_STRING_KEY = Object.new.freeze
80
+ SEARCH_STRING_KEY = ::Object.new.freeze
81
81
 
82
82
  ##
83
83
  # Key for the show-all-subtools setting
84
84
  # @return [Object]
85
85
  #
86
- SHOW_ALL_SUBTOOLS_KEY = Object.new.freeze
86
+ SHOW_ALL_SUBTOOLS_KEY = ::Object.new.freeze
87
87
 
88
88
  ##
89
89
  # Key for the tool name
90
90
  # @return [Object]
91
91
  #
92
- TOOL_NAME_KEY = Object.new.freeze
92
+ TOOL_NAME_KEY = ::Object.new.freeze
93
93
 
94
94
  ##
95
95
  # Create a ShowHelp middleware.
@@ -214,7 +214,7 @@ module Toys
214
214
  list_flags = has_subtools ? add_list_flags(tool) : []
215
215
  can_display_help = !help_flags.empty? || !list_flags.empty? ||
216
216
  !usage_flags.empty? || @fallback_execution
217
- if can_display_help && has_subtools
217
+ if can_display_help && has_subtools && !tool.runnable?
218
218
  add_recursive_flags(tool)
219
219
  add_search_flags(tool)
220
220
  add_show_all_subtools_flags(tool)
@@ -23,7 +23,7 @@ module Toys
23
23
  # Key set when the version flag is present
24
24
  # @return [Object]
25
25
  #
26
- SHOW_VERSION_KEY = Object.new.freeze
26
+ SHOW_VERSION_KEY = ::Object.new.freeze
27
27
 
28
28
  ##
29
29
  # Create a ShowVersion middleware
@@ -22,9 +22,22 @@ 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. This is present largely for historical
27
+ # compatibility, but it is supported and _not_ deprecated.
28
+ #
29
+ # * `:setup` (:auto,:manual,:static) A symbol indicating when the bundle
30
+ # should be installed. Possible values are:
31
+ #
32
+ # * `:auto` - (Default) Installs the bundle just before the tool runs.
33
+ # * `:static` - Installs the bundle immediately when defining the
34
+ # tool.
35
+ # * `:manual` - Does not install the bundle, but defines the methods
36
+ # `bundler_setup` and `bundler_setup?` in the tool. The tool can
37
+ # call `bundler_setup` to install the bundle, optionally passing
38
+ # any of the remaining keyword arguments below to override the
39
+ # corresponding mixin parameters. The `bundler_setup?` method can
40
+ # be queried to determine whether the bundle has been set up yet.
28
41
  #
29
42
  # * `:groups` (Array\<String\>) The groups to include in setup.
30
43
  #
@@ -76,7 +89,9 @@ module Toys
76
89
  # (optional)
77
90
  #
78
91
  # * `:terminal` (Toys::Utils::Terminal) Terminal to use (optional)
92
+ #
79
93
  # * `:input` (IO) Input IO (optional, defaults to STDIN)
94
+ #
80
95
  # * `:output` (IO) Output IO (optional, defaults to STDOUT)
81
96
  #
82
97
  module Bundler
@@ -94,6 +109,14 @@ module Toys
94
109
  #
95
110
  DEFAULT_TOYS_GEMFILE_NAMES = [".gems.rb", "Gemfile"].freeze
96
111
 
112
+ ##
113
+ # @private
114
+ # Context key for the mixin parameters when using manual setup. The value
115
+ # will be a hash of parameters if the bundle has not been set up yet, or
116
+ # nil if the bundle has already been set up.
117
+ #
118
+ SETUP_PARAMS_KEY = ::Object.new.freeze
119
+
97
120
  ##
98
121
  # @private
99
122
  #
@@ -121,17 +144,45 @@ module Toys
121
144
  gems.bundle(groups: groups, gemfile_path: gemfile_path, retries: retries)
122
145
  end
123
146
 
124
- on_initialize do |static: false, **kwargs|
125
- unless static
147
+ on_initialize do |static: false, setup: nil, **kwargs|
148
+ setup ||= (static ? :static : :auto)
149
+ case setup
150
+ when :auto
126
151
  context_directory = self[::Toys::Context::Key::CONTEXT_DIRECTORY]
127
152
  source_info = self[::Toys::Context::Key::TOOL_SOURCE]
128
153
  ::Toys::StandardMixins::Bundler.setup_bundle(context_directory, source_info, **kwargs)
154
+ when :manual
155
+ self[::Toys::StandardMixins::Bundler::SETUP_PARAMS_KEY] = kwargs
156
+ when :static
157
+ # Already set up at include time
129
158
  end
130
159
  end
131
160
 
132
- on_include do |static: false, **kwargs|
133
- if static
161
+ on_include do |static: false, setup: nil, **kwargs|
162
+ setup ||= (static ? :static : :auto)
163
+ case setup
164
+ when :static
134
165
  ::Toys::StandardMixins::Bundler.setup_bundle(context_directory, source_info, **kwargs)
166
+ when :manual
167
+ # @private Defined dynamically for the tool but not visible to YARD
168
+ def bundler_setup(**kwargs)
169
+ original_kwargs = self[::Toys::StandardMixins::Bundler::SETUP_PARAMS_KEY]
170
+ raise ::Toys::Utils::Gems::AlreadyBundledError unless original_kwargs
171
+ context_directory = self[::Toys::Context::Key::CONTEXT_DIRECTORY]
172
+ source_info = self[::Toys::Context::Key::TOOL_SOURCE]
173
+ final_kwargs = original_kwargs.merge(kwargs)
174
+ ::Toys::StandardMixins::Bundler.setup_bundle(context_directory, source_info, **final_kwargs)
175
+ self[::Toys::StandardMixins::Bundler::SETUP_PARAMS_KEY] = nil
176
+ end
177
+
178
+ # @private Defined dynamically for the tool but not visible to YARD
179
+ def bundler_setup?
180
+ self[::Toys::StandardMixins::Bundler::SETUP_PARAMS_KEY].nil?
181
+ end
182
+ when :auto
183
+ # Do nothing at this point
184
+ else
185
+ raise ::ArgumentError, "Unrecognized setup type: #{setup.inspect}"
135
186
  end
136
187
  end
137
188
 
@@ -145,7 +196,7 @@ module Toys
145
196
  def initialize(context_directory, source_info, gemfile_names, toys_gemfile_names)
146
197
  @context_directory = context_directory
147
198
  @source_info = source_info
148
- @gemfile_names = gemfile_names
199
+ @gemfile_names = gemfile_names || ::Toys::Utils::Gems::DEFAULT_GEMFILE_NAMES
149
200
  @toys_gemfile_names = toys_gemfile_names || DEFAULT_TOYS_GEMFILE_NAMES
150
201
  end
151
202
 
@@ -165,7 +216,7 @@ module Toys
165
216
  when :toys
166
217
  search_toys
167
218
  else
168
- raise ::ArgumentError, "Unrecognized search_dir: #{dir.inspect}"
219
+ raise ::ArgumentError, "Unrecognized search_dir: #{search_dir.inspect}"
169
220
  end
170
221
  end
171
222