toys-core 0.11.2 → 0.12.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 (43) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +46 -1
  3. data/README.md +1 -1
  4. data/lib/toys-core.rb +4 -1
  5. data/lib/toys/acceptor.rb +3 -3
  6. data/lib/toys/arg_parser.rb +6 -7
  7. data/lib/toys/cli.rb +44 -14
  8. data/lib/toys/compat.rb +19 -22
  9. data/lib/toys/completion.rb +3 -1
  10. data/lib/toys/context.rb +2 -2
  11. data/lib/toys/core.rb +1 -1
  12. data/lib/toys/dsl/base.rb +85 -0
  13. data/lib/toys/dsl/flag.rb +3 -3
  14. data/lib/toys/dsl/flag_group.rb +7 -7
  15. data/lib/toys/dsl/internal.rb +206 -0
  16. data/lib/toys/dsl/positional_arg.rb +3 -3
  17. data/lib/toys/dsl/tool.rb +174 -216
  18. data/lib/toys/errors.rb +1 -0
  19. data/lib/toys/flag.rb +15 -18
  20. data/lib/toys/flag_group.rb +5 -4
  21. data/lib/toys/input_file.rb +4 -4
  22. data/lib/toys/loader.rb +189 -50
  23. data/lib/toys/middleware.rb +1 -1
  24. data/lib/toys/mixin.rb +2 -2
  25. data/lib/toys/positional_arg.rb +3 -3
  26. data/lib/toys/settings.rb +900 -0
  27. data/lib/toys/source_info.rb +121 -18
  28. data/lib/toys/standard_middleware/apply_config.rb +5 -4
  29. data/lib/toys/standard_middleware/set_default_descriptions.rb +18 -18
  30. data/lib/toys/standard_middleware/show_help.rb +17 -5
  31. data/lib/toys/standard_mixins/bundler.rb +5 -1
  32. data/lib/toys/standard_mixins/exec.rb +22 -15
  33. data/lib/toys/standard_mixins/git_cache.rb +48 -0
  34. data/lib/toys/standard_mixins/xdg.rb +56 -0
  35. data/lib/toys/template.rb +2 -2
  36. data/lib/toys/{tool.rb → tool_definition.rb} +100 -41
  37. data/lib/toys/utils/exec.rb +37 -16
  38. data/lib/toys/utils/gems.rb +48 -14
  39. data/lib/toys/utils/git_cache.rb +184 -0
  40. data/lib/toys/utils/help_text.rb +90 -34
  41. data/lib/toys/utils/terminal.rb +1 -1
  42. data/lib/toys/utils/xdg.rb +293 -0
  43. metadata +15 -8
data/lib/toys/dsl/flag.rb CHANGED
@@ -9,7 +9,7 @@ module Toys
9
9
  # These directives are available inside a block passed to
10
10
  # {Toys::DSL::Tool#flag}.
11
11
  #
12
- # ## Example
12
+ # ### Example
13
13
  #
14
14
  # tool "mytool" do
15
15
  # flag :value do
@@ -212,7 +212,7 @@ module Toys
212
212
  # across the strings in the array. In this case, whitespace is not
213
213
  # compacted.
214
214
  #
215
- # ## Examples
215
+ # ### Examples
216
216
  #
217
217
  # If you pass in a sentence as a simple string, it may be word wrapped
218
218
  # when displayed:
@@ -243,7 +243,7 @@ module Toys
243
243
  # word-wrapped when displayed. To insert a blank line, include an empty
244
244
  # string as one of the descriptions.
245
245
  #
246
- # ## Example
246
+ # ### Example
247
247
  #
248
248
  # long_desc "This initial paragraph might get word wrapped.",
249
249
  # "This next paragraph is followed by a blank line.",
@@ -10,7 +10,7 @@ module Toys
10
10
  # {Toys::DSL::Tool#at_most_one}, {Toys::DSL::Tool#at_least_one}, or
11
11
  # {Toys::DSL::Tool#exactly_one}.
12
12
  #
13
- # ## Example
13
+ # ### Example
14
14
  #
15
15
  # tool "login" do
16
16
  # all_required do
@@ -43,7 +43,7 @@ module Toys
43
43
  # set in a block passed to this method. If you provide a block, you can
44
44
  # use directives in {Toys::DSL::Flag} within the block.
45
45
  #
46
- # ## Flag syntax
46
+ # ### Flag syntax
47
47
  #
48
48
  # The flags themselves should be provided in OptionParser form. Following
49
49
  # are examples of valid syntax.
@@ -98,7 +98,7 @@ module Toys
98
98
  # or off. This effectively creates two flags, `--abc` which sets the
99
99
  # value to `true`, and `--no-abc` which sets the falue to `false`.
100
100
  #
101
- # ## Default flag syntax
101
+ # ### Default flag syntax
102
102
  #
103
103
  # If no flag syntax strings are provided, a default syntax will be
104
104
  # inferred based on the key and other options.
@@ -123,7 +123,7 @@ module Toys
123
123
  # flag :number, accept: Integer
124
124
  # flag :number, "--number=VAL", accept: Integer
125
125
  #
126
- # ## More examples
126
+ # ### More examples
127
127
  #
128
128
  # A flag that sets its value to the number of times it appears on the
129
129
  # command line:
@@ -200,7 +200,7 @@ module Toys
200
200
  report_collisions, @flag_group, desc, long_desc, display_name)
201
201
  flag_dsl.instance_exec(flag_dsl, &block) if block
202
202
  flag_dsl._add_to(@tool, key)
203
- DSL::Tool.maybe_add_getter(@tool_dsl, key)
203
+ DSL::Internal.maybe_add_getter(@tool_dsl, key)
204
204
  self
205
205
  end
206
206
 
@@ -221,7 +221,7 @@ module Toys
221
221
  # across the strings in the array. In this case, whitespace is not
222
222
  # compacted.
223
223
  #
224
- # ## Examples
224
+ # ### Examples
225
225
  #
226
226
  # If you pass in a sentence as a simple string, it may be word wrapped
227
227
  # when displayed:
@@ -252,7 +252,7 @@ module Toys
252
252
  # word-wrapped when displayed. To insert a blank line, include an empty
253
253
  # string as one of the descriptions.
254
254
  #
255
- # ## Example
255
+ # ### Example
256
256
  #
257
257
  # long_desc "This initial paragraph might get word wrapped.",
258
258
  # "This next paragraph is followed by a blank line.",
@@ -0,0 +1,206 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Toys
4
+ module DSL
5
+ ##
6
+ # Internal utility calls used by the DSL.
7
+ #
8
+ # @private
9
+ #
10
+ module Internal
11
+ class << self
12
+ ##
13
+ # Called by the Loader and InputFile to prepare a tool class for running
14
+ # the DSL.
15
+ #
16
+ # @private
17
+ #
18
+ def prepare(tool_class, words, priority, remaining_words, source, loader)
19
+ unless tool_class.is_a?(DSL::Tool)
20
+ class << tool_class
21
+ alias_method :super_include, :include
22
+ end
23
+ tool_class.extend(DSL::Tool)
24
+ end
25
+ unless tool_class.instance_variable_defined?(:@__words)
26
+ tool_class.instance_variable_set(:@__words, words)
27
+ tool_class.instance_variable_set(:@__priority, priority)
28
+ tool_class.instance_variable_set(:@__loader, loader)
29
+ tool_class.instance_variable_set(:@__source, [])
30
+ end
31
+ tool_class.instance_variable_set(:@__remaining_words, remaining_words)
32
+ tool_class.instance_variable_get(:@__source).push(source)
33
+ old_source = ::Thread.current[:__toys_current_source]
34
+ begin
35
+ ::Thread.current[:__toys_current_source] = source
36
+ yield
37
+ ensure
38
+ tool_class.instance_variable_get(:@__source).pop
39
+ ::Thread.current[:__toys_current_source] = old_source
40
+ end
41
+ end
42
+
43
+ ##
44
+ # Called by the DSL implementation to get, and optionally activate, the
45
+ # current tool.
46
+ #
47
+ # @private
48
+ #
49
+ def current_tool(tool_class, activate)
50
+ memoize_var = activate ? :@__active_tool : :@__cur_tool
51
+ if tool_class.instance_variable_defined?(memoize_var)
52
+ tool_class.instance_variable_get(memoize_var)
53
+ else
54
+ loader = tool_class.instance_variable_get(:@__loader)
55
+ words = tool_class.instance_variable_get(:@__words)
56
+ priority = tool_class.instance_variable_get(:@__priority)
57
+ cur_tool =
58
+ if activate
59
+ loader.activate_tool(words, priority)
60
+ else
61
+ loader.get_tool(words, priority)
62
+ end
63
+ if cur_tool && activate
64
+ source = tool_class.instance_variable_get(:@__source).last
65
+ cur_tool.lock_source(source)
66
+ end
67
+ tool_class.instance_variable_set(memoize_var, cur_tool)
68
+ end
69
+ end
70
+
71
+ ##
72
+ # Called by the DSL implementation to add a getter to the tool class.
73
+ #
74
+ # @private
75
+ #
76
+ def maybe_add_getter(tool_class, key)
77
+ if key.is_a?(::Symbol) && key.to_s =~ /^[_a-zA-Z]\w*[!?]?$/ && key != :run
78
+ unless tool_class.public_method_defined?(key)
79
+ tool_class.class_eval do
80
+ define_method(key) do
81
+ self[key]
82
+ end
83
+ end
84
+ end
85
+ end
86
+ end
87
+
88
+ ##
89
+ # Called by the DSL implementation to find a named mixin.
90
+ #
91
+ # @private
92
+ #
93
+ def resolve_mixin(mixin, cur_tool, loader)
94
+ mod =
95
+ case mixin
96
+ when ::String
97
+ cur_tool.lookup_mixin(mixin)
98
+ when ::Symbol
99
+ loader.resolve_standard_mixin(mixin.to_s)
100
+ when ::Module
101
+ mixin
102
+ end
103
+ raise ToolDefinitionError, "Mixin not found: #{mixin.inspect}" unless mod
104
+ mod
105
+ end
106
+
107
+ ##
108
+ # Called by the DSL implementation to load a long description from a
109
+ # file.
110
+ #
111
+ # @private
112
+ #
113
+ def load_long_desc_file(path)
114
+ if ::File.extname(path) == ".txt"
115
+ begin
116
+ ::File.readlines(path).map do |line|
117
+ line = line.chomp
118
+ line =~ /^\s/ ? [line] : line
119
+ end
120
+ rescue ::SystemCallError => e
121
+ raise Toys::ToolDefinitionError, e.to_s
122
+ end
123
+ else
124
+ raise Toys::ToolDefinitionError, "Cannot load long desc from file type: #{path}"
125
+ end
126
+ end
127
+
128
+ ##
129
+ # Called by the Tool base class to set config values for a subclass.
130
+ #
131
+ # @private
132
+ #
133
+ def configure_class(tool_class, given_name = nil)
134
+ return if tool_class.name.nil? || tool_class.instance_variable_defined?(:@__loader)
135
+
136
+ mod_names = tool_class.name.split("::")
137
+ class_name = mod_names.pop
138
+ parent = parent_from_mod_name_segments(mod_names)
139
+ loader = parent.instance_variable_get(:@__loader)
140
+ name = given_name ? loader.split_path(given_name) : class_name_to_tool_name(class_name)
141
+
142
+ priority = parent.instance_variable_get(:@__priority)
143
+ words = parent.instance_variable_get(:@__words) + name
144
+ subtool = loader.get_tool(words, priority, tool_class)
145
+
146
+ remaining_words = parent.instance_variable_get(:@__remaining_words)
147
+ next_remaining = name.reduce(remaining_words) do |running_words, word|
148
+ Loader.next_remaining_words(running_words, word)
149
+ end
150
+
151
+ tool_class.instance_variable_set(:@__words, words)
152
+ tool_class.instance_variable_set(:@__priority, priority)
153
+ tool_class.instance_variable_set(:@__loader, loader)
154
+ tool_class.instance_variable_set(:@__source, [current_source_from_context])
155
+ tool_class.instance_variable_set(:@__remaining_words, next_remaining)
156
+ tool_class.instance_variable_set(:@__cur_tool, subtool)
157
+ end
158
+
159
+ ##
160
+ # Called by the Tool base class to add the DSL to a subclass.
161
+ #
162
+ # @private
163
+ #
164
+ def setup_class_dsl(tool_class)
165
+ return if tool_class.name.nil? || tool_class.is_a?(DSL::Tool)
166
+ class << tool_class
167
+ alias_method :super_include, :include
168
+ end
169
+ tool_class.extend(DSL::Tool)
170
+ end
171
+
172
+ private
173
+
174
+ def class_name_to_tool_name(class_name)
175
+ name = class_name.to_s.sub(/^_+/, "").sub(/_+$/, "").gsub(/_+/, "-")
176
+ while name.sub!(/([^-])([A-Z])/, "\\1-\\2") do end
177
+ [name.downcase!]
178
+ end
179
+
180
+ def parent_from_mod_name_segments(mod_names)
181
+ parent = mod_names.reduce(::Object) do |running_mod, seg|
182
+ running_mod.const_get(seg)
183
+ end
184
+ if !parent.is_a?(::Toys::Tool) && parent.instance_variable_defined?(:@__tool_class)
185
+ parent = parent.instance_variable_get(:@__tool_class)
186
+ end
187
+ unless parent.ancestors.include?(::Toys::Context)
188
+ raise ToolDefinitionError, "Toys::Tool can be subclassed only from the Toys DSL"
189
+ end
190
+ parent
191
+ end
192
+
193
+ def current_source_from_context
194
+ source = ::Thread.current[:__toys_current_source]
195
+ if source.nil?
196
+ raise ToolDefinitionError, "Toys::Tool can be subclassed only from a Toys config file"
197
+ end
198
+ unless source.source_type == :file
199
+ raise ToolDefinitionError, "Toys::Tool cannot be subclassed inside a tool block"
200
+ end
201
+ source
202
+ end
203
+ end
204
+ end
205
+ end
206
+ end
@@ -10,7 +10,7 @@ module Toys
10
10
  # {Toys::DSL::Tool#required_arg}, {Toys::DSL::Tool#optional_arg}, or
11
11
  # {Toys::DSL::Tool#remaining_args}.
12
12
  #
13
- # ## Example
13
+ # ### Example
14
14
  #
15
15
  # tool "mytool" do
16
16
  # optional_arg :value do
@@ -103,7 +103,7 @@ module Toys
103
103
  # across the strings in the array. In this case, whitespace is not
104
104
  # compacted.
105
105
  #
106
- # ## Examples
106
+ # ### Examples
107
107
  #
108
108
  # If you pass in a sentence as a simple string, it may be word wrapped
109
109
  # when displayed:
@@ -134,7 +134,7 @@ module Toys
134
134
  # word-wrapped when displayed. To insert a blank line, include an empty
135
135
  # string as one of the descriptions.
136
136
  #
137
- # ## Example
137
+ # ### Example
138
138
  #
139
139
  # long_desc "This initial paragraph might get word wrapped.",
140
140
  # "This next paragraph is followed by a blank line.",
data/lib/toys/dsl/tool.rb CHANGED
@@ -10,7 +10,7 @@ module Toys
10
10
  # how to execute the tool, and requesting mixin modules and other services.
11
11
  # It also lets you define subtools, nested arbitrarily deep, using blocks.
12
12
  #
13
- # ## Simple example
13
+ # ### Simple example
14
14
  #
15
15
  # Create a file called `.toys.rb` in the current directory, with the
16
16
  # following contents:
@@ -36,7 +36,8 @@ module Toys
36
36
  module Tool
37
37
  ## @private
38
38
  def method_added(_meth)
39
- DSL::Tool.current_tool(self, true)&.check_definition_state(is_method: true)
39
+ super
40
+ DSL::Internal.current_tool(self, true)&.check_definition_state(is_method: true)
40
41
  end
41
42
 
42
43
  ##
@@ -83,7 +84,7 @@ module Toys
83
84
  # an exception (descended from `StandardError`) to indicate that the
84
85
  # string parameter is invalid.
85
86
  #
86
- # ## Example
87
+ # ### Example
87
88
  #
88
89
  # The following example creates an acceptor named "hex" that is defined
89
90
  # via a regular expression. It then uses it to validate values passed to
@@ -105,7 +106,7 @@ module Toys
105
106
  # @return [self]
106
107
  #
107
108
  def acceptor(name, spec = nil, type_desc: nil, &block)
108
- cur_tool = DSL::Tool.current_tool(self, false)
109
+ cur_tool = DSL::Internal.current_tool(self, false)
109
110
  cur_tool&.add_acceptor(name, spec, type_desc: type_desc || name.to_s, &block)
110
111
  self
111
112
  end
@@ -122,7 +123,7 @@ module Toys
122
123
  # block. Alternatively, you can create a module separately and pass it
123
124
  # directly to this directive.
124
125
  #
125
- # ## Example
126
+ # ### Example
126
127
  #
127
128
  # The following example creates a named mixin and uses it in a tool.
128
129
  #
@@ -149,7 +150,7 @@ module Toys
149
150
  # @return [self]
150
151
  #
151
152
  def mixin(name, mixin_module = nil, &block)
152
- cur_tool = DSL::Tool.current_tool(self, false)
153
+ cur_tool = DSL::Internal.current_tool(self, false)
153
154
  cur_tool&.add_mixin(name, mixin_module, &block)
154
155
  self
155
156
  end
@@ -175,7 +176,7 @@ module Toys
175
176
  # directly. See {Toys::Template} for details on creating a template
176
177
  # class.
177
178
  #
178
- # ## Example
179
+ # ### Example
179
180
  #
180
181
  # The following example creates and uses a simple template.
181
182
  #
@@ -204,7 +205,7 @@ module Toys
204
205
  # @return [self]
205
206
  #
206
207
  def template(name, template_class = nil, &block)
207
- cur_tool = DSL::Tool.current_tool(self, false)
208
+ cur_tool = DSL::Internal.current_tool(self, false)
208
209
  return self if cur_tool.nil?
209
210
  cur_tool.add_template(name, template_class, &block)
210
211
  self
@@ -228,7 +229,7 @@ module Toys
228
229
  # * The symbol `:file_system` which indicates that paths in the file
229
230
  # system should serve as completion candidates.
230
231
  #
231
- # ## Example
232
+ # ### Example
232
233
  #
233
234
  # The following example defines a completion that uses only the immediate
234
235
  # files in the current directory as candidates. (This is different from
@@ -252,7 +253,7 @@ module Toys
252
253
  # @return [self]
253
254
  #
254
255
  def completion(name, spec = nil, **options, &block)
255
- cur_tool = DSL::Tool.current_tool(self, false)
256
+ cur_tool = DSL::Internal.current_tool(self, false)
256
257
  return self if cur_tool.nil?
257
258
  cur_tool.add_completion(name, spec, **options, &block)
258
259
  self
@@ -261,7 +262,7 @@ module Toys
261
262
  ##
262
263
  # Create a subtool. You must provide a block defining the subtool.
263
264
  #
264
- # ## Example
265
+ # ### Example
265
266
  #
266
267
  # The following example defines a tool and two subtools within it.
267
268
  #
@@ -280,7 +281,7 @@ module Toys
280
281
  #
281
282
  # The following example defines a tool that runs one of its subtools.
282
283
  #
283
- # tool "test", runs: ["test", "unit"] do
284
+ # tool "test", delegate_to: ["test", "unit"] do
284
285
  # tool "unit" do
285
286
  # def run
286
287
  # puts "Running unit tests"
@@ -316,7 +317,7 @@ module Toys
316
317
  when :ignore
317
318
  return self
318
319
  when :reset
319
- subtool.reset_definition(@__loader)
320
+ subtool.reset_definition
320
321
  end
321
322
  end
322
323
  if delegate_to
@@ -328,7 +329,6 @@ module Toys
328
329
  end
329
330
  self
330
331
  end
331
- alias name tool
332
332
 
333
333
  ##
334
334
  # Create an alias, representing an "alternate name" for a tool.
@@ -337,7 +337,7 @@ module Toys
337
337
  # `delegate_to` option, except that `alias_tool` takes a _relative_ name
338
338
  # for the delegate.
339
339
  #
340
- # ## Example
340
+ # ### Example
341
341
  #
342
342
  # This example defines a tool and an alias pointing to it. Both the tool
343
343
  # name `test` and the alias `t` will then refer to the same tool.
@@ -364,7 +364,7 @@ module Toys
364
364
  # Causes the current tool to delegate to another tool. When run, it
365
365
  # simply invokes the target tool with the same arguments.
366
366
  #
367
- # ## Example
367
+ # ### Example
368
368
  #
369
369
  # This example defines a tool that runs one of its subtools. Running the
370
370
  # `test` tool will have the same effect (and recognize the same args) as
@@ -386,7 +386,7 @@ module Toys
386
386
  # @return [self]
387
387
  #
388
388
  def delegate_to(target)
389
- cur_tool = DSL::Tool.current_tool(self, true)
389
+ cur_tool = DSL::Internal.current_tool(self, true)
390
390
  return self if cur_tool.nil?
391
391
  cur_tool.delegate_to(@__loader.split_path(target))
392
392
  self
@@ -397,20 +397,64 @@ module Toys
397
397
  # at the current location.
398
398
  #
399
399
  # @param path [String] The file or directory to load.
400
+ # @param as [String] Load into the given tool/namespace. If omitted,
401
+ # configuration will be loaded into the current namespace.
402
+ #
400
403
  # @return [self]
401
404
  #
402
- def load(path)
405
+ def load(path, as: nil)
406
+ if as
407
+ tool(as) do
408
+ load(path)
409
+ end
410
+ return self
411
+ end
403
412
  @__loader.load_path(source_info, path, @__words, @__remaining_words, @__priority)
404
413
  self
405
414
  end
406
415
 
416
+ ##
417
+ # Load configuration from a public git repository, as if its contents
418
+ # were inserted at the current location.
419
+ #
420
+ # @param remote [String] The URL of the git repository. Defaults to the
421
+ # current repository if already loading from git.
422
+ # @param path [String] The path within the repo to the file or directory
423
+ # to load. Defaults to the root of the repo.
424
+ # @param commit [String] The commit branch, tag, or sha. Defaults to the
425
+ # current commit if already loading from git, or to `HEAD`.
426
+ # @param as [String] Load into the given tool/namespace. If omitted,
427
+ # configuration will be loaded into the current namespace.
428
+ # @param update [Boolean] Force-fetch from the remote (unless the commit
429
+ # is a SHA). This will ensure that symbolic commits, such as branch
430
+ # names, are up to date. Default is false.
431
+ #
432
+ # @return [self]
433
+ #
434
+ def load_git(remote: nil, path: nil, commit: nil, as: nil, update: false)
435
+ if as
436
+ tool(as) do
437
+ load_git(remote: remote, path: path, commit: commit)
438
+ end
439
+ return self
440
+ end
441
+ remote ||= source_info.git_remote
442
+ raise ToolDefinitionError, "Git remote not specified" unless remote
443
+ path ||= ""
444
+ commit ||= source_info.git_commit || "HEAD"
445
+ @__loader.load_git(source_info, remote, path, commit,
446
+ @__words, @__remaining_words, @__priority,
447
+ update: update)
448
+ self
449
+ end
450
+
407
451
  ##
408
452
  # Expand the given template in the current location.
409
453
  #
410
454
  # The template may be specified as a class or a well-known template name.
411
455
  # You may also provide arguments to pass to the template.
412
456
  #
413
- # ## Example
457
+ # ### Example
414
458
  #
415
459
  # The following example creates and uses a simple template.
416
460
  #
@@ -437,12 +481,13 @@ module Toys
437
481
  # @return [self]
438
482
  #
439
483
  def expand(template_class, *args, **kwargs)
440
- cur_tool = DSL::Tool.current_tool(self, false)
484
+ cur_tool = DSL::Internal.current_tool(self, false)
441
485
  return self if cur_tool.nil?
442
486
  name = template_class.to_s
443
- if template_class.is_a?(::String)
487
+ case template_class
488
+ when ::String
444
489
  template_class = cur_tool.lookup_template(template_class)
445
- elsif template_class.is_a?(::Symbol)
490
+ when ::Symbol
446
491
  template_class = @__loader.resolve_standard_template(name)
447
492
  end
448
493
  if template_class.nil?
@@ -472,7 +517,7 @@ module Toys
472
517
  # across the strings in the array. In this case, whitespace is not
473
518
  # compacted.
474
519
  #
475
- # ## Examples
520
+ # ### Examples
476
521
  #
477
522
  # If you pass in a sentence as a simple string, it may be word wrapped
478
523
  # when displayed:
@@ -488,7 +533,7 @@ module Toys
488
533
  # @return [self]
489
534
  #
490
535
  def desc(str)
491
- cur_tool = DSL::Tool.current_tool(self, true)
536
+ cur_tool = DSL::Internal.current_tool(self, true)
492
537
  return self if cur_tool.nil?
493
538
  cur_tool.desc = str
494
539
  self
@@ -506,7 +551,7 @@ module Toys
506
551
  # word-wrapped when displayed. To insert a blank line, include an empty
507
552
  # string as one of the descriptions.
508
553
  #
509
- # ## Example
554
+ # ### Example
510
555
  #
511
556
  # long_desc "This initial paragraph might get word wrapped.",
512
557
  # "This next paragraph is followed by a blank line.",
@@ -524,7 +569,7 @@ module Toys
524
569
  # @return [self]
525
570
  #
526
571
  def long_desc(*strs, file: nil, data: nil)
527
- cur_tool = DSL::Tool.current_tool(self, true)
572
+ cur_tool = DSL::Internal.current_tool(self, true)
528
573
  return self if cur_tool.nil?
529
574
  if file
530
575
  unless source_info.source_path
@@ -535,7 +580,7 @@ module Toys
535
580
  elsif data
536
581
  file = source_info.find_data(data, type: :file)
537
582
  end
538
- strs += DSL::Tool.load_long_desc_file(file) if file
583
+ strs += DSL::Internal.load_long_desc_file(file) if file
539
584
  cur_tool.append_long_desc(strs)
540
585
  self
541
586
  end
@@ -545,7 +590,7 @@ module Toys
545
590
  # belong to the group. The flags in the group are listed together in
546
591
  # help screens.
547
592
  #
548
- # ## Example
593
+ # ### Example
549
594
  #
550
595
  # The following example creates a flag group in which all flags are
551
596
  # optional.
@@ -562,11 +607,11 @@ module Toys
562
607
  # `:optional`, `:exactly_one`, `:at_most_one`, `:at_least_one`.
563
608
  # Default is `:optional`.
564
609
  # @param desc [String,Array<String>,Toys::WrappableString] Short
565
- # description for the group. See {Toys::Tool#desc=} for a description
566
- # of allowed formats. Defaults to `"Flags"`.
610
+ # description for the group. See {Toys::DSL::Tool#desc} for a
611
+ # description of allowed formats. Defaults to `"Flags"`.
567
612
  # @param long_desc [Array<String,Array<String>,Toys::WrappableString>]
568
613
  # Long description for the flag group. See
569
- # {Toys::Tool#long_desc=} for a description of allowed formats.
614
+ # {Toys::DSL::Tool#long_desc} for a description of allowed formats.
570
615
  # Defaults to the empty array.
571
616
  # @param name [String,Symbol,nil] The name of the group, or nil for no
572
617
  # name.
@@ -581,7 +626,7 @@ module Toys
581
626
  #
582
627
  def flag_group(type: :optional, desc: nil, long_desc: nil, name: nil,
583
628
  report_collisions: true, prepend: false, &block)
584
- cur_tool = DSL::Tool.current_tool(self, true)
629
+ cur_tool = DSL::Internal.current_tool(self, true)
585
630
  return self if cur_tool.nil?
586
631
  cur_tool.add_flag_group(type: type, desc: desc, long_desc: long_desc, name: name,
587
632
  report_collisions: report_collisions, prepend: prepend)
@@ -596,7 +641,7 @@ module Toys
596
641
  # defined in the block belong to the group. All flags in this group are
597
642
  # required.
598
643
  #
599
- # ## Example
644
+ # ### Example
600
645
  #
601
646
  # The following example creates a group of required flags.
602
647
  #
@@ -609,11 +654,11 @@ module Toys
609
654
  # end
610
655
  #
611
656
  # @param desc [String,Array<String>,Toys::WrappableString] Short
612
- # description for the group. See {Toys::Tool#desc=} for a description
613
- # of allowed formats. Defaults to `"Flags"`.
657
+ # description for the group. See {Toys::DSL::Tool#desc} for a
658
+ # description of allowed formats. Defaults to `"Flags"`.
614
659
  # @param long_desc [Array<String,Array<String>,Toys::WrappableString>]
615
660
  # Long description for the flag group. See
616
- # {Toys::Tool#long_desc=} for a description of allowed formats.
661
+ # {Toys::DSL::Tool#long_desc} for a description of allowed formats.
617
662
  # Defaults to the empty array.
618
663
  # @param name [String,Symbol,nil] The name of the group, or nil for no
619
664
  # name.
@@ -637,7 +682,7 @@ module Toys
637
682
  # defined in the block belong to the group. At most one flag in this
638
683
  # group must be provided on the command line.
639
684
  #
640
- # ## Example
685
+ # ### Example
641
686
  #
642
687
  # The following example creates a group of flags in which either one or
643
688
  # none may be set, but not more than one.
@@ -652,11 +697,11 @@ module Toys
652
697
  # end
653
698
  #
654
699
  # @param desc [String,Array<String>,Toys::WrappableString] Short
655
- # description for the group. See {Toys::Tool#desc=} for a description
656
- # of allowed formats. Defaults to `"Flags"`.
700
+ # description for the group. See {Toys::DSL::Tool#desc} for a
701
+ # description of allowed formats. Defaults to `"Flags"`.
657
702
  # @param long_desc [Array<String,Array<String>,Toys::WrappableString>]
658
703
  # Long description for the flag group. See
659
- # {Toys::Tool#long_desc=} for a description of allowed formats.
704
+ # {Toys::DSL::Tool#long_desc} for a description of allowed formats.
660
705
  # Defaults to the empty array.
661
706
  # @param name [String,Symbol,nil] The name of the group, or nil for no
662
707
  # name.
@@ -681,7 +726,7 @@ module Toys
681
726
  # defined in the block belong to the group. At least one flag in this
682
727
  # group must be provided on the command line.
683
728
  #
684
- # ## Example
729
+ # ### Example
685
730
  #
686
731
  # The following example creates a group of flags in which one or more
687
732
  # may be set.
@@ -696,11 +741,11 @@ module Toys
696
741
  # end
697
742
  #
698
743
  # @param desc [String,Array<String>,Toys::WrappableString] Short
699
- # description for the group. See {Toys::Tool#desc=} for a description
700
- # of allowed formats. Defaults to `"Flags"`.
744
+ # description for the group. See {Toys::DSL::Tool#desc} for a
745
+ # description of allowed formats. Defaults to `"Flags"`.
701
746
  # @param long_desc [Array<String,Array<String>,Toys::WrappableString>]
702
747
  # Long description for the flag group. See
703
- # {Toys::Tool#long_desc=} for a description of allowed formats.
748
+ # {Toys::DSL::Tool#long_desc} for a description of allowed formats.
704
749
  # Defaults to the empty array.
705
750
  # @param name [String,Symbol,nil] The name of the group, or nil for no
706
751
  # name.
@@ -725,7 +770,7 @@ module Toys
725
770
  # defined in the block belong to the group. Exactly one flag in this
726
771
  # group must be provided on the command line.
727
772
  #
728
- # ## Example
773
+ # ### Example
729
774
  #
730
775
  # The following example creates a group of flags in which exactly one
731
776
  # must be set.
@@ -740,11 +785,11 @@ module Toys
740
785
  # end
741
786
  #
742
787
  # @param desc [String,Array<String>,Toys::WrappableString] Short
743
- # description for the group. See {Toys::Tool#desc=} for a description
744
- # of allowed formats. Defaults to `"Flags"`.
788
+ # description for the group. See {Toys::DSL::Tool#desc} for a
789
+ # description of allowed formats. Defaults to `"Flags"`.
745
790
  # @param long_desc [Array<String,Array<String>,Toys::WrappableString>]
746
791
  # Long description for the flag group. See
747
- # {Toys::Tool#long_desc=} for a description of allowed formats.
792
+ # {Toys::DSL::Tool#long_desc} for a description of allowed formats.
748
793
  # Defaults to the empty array.
749
794
  # @param name [String,Symbol,nil] The name of the group, or nil for no
750
795
  # name.
@@ -778,7 +823,7 @@ module Toys
778
823
  # set in a block passed to this method. If you provide a block, you can
779
824
  # use directives in {Toys::DSL::Flag} within the block.
780
825
  #
781
- # ## Flag syntax
826
+ # ### Flag syntax
782
827
  #
783
828
  # The flags themselves should be provided in OptionParser form. Following
784
829
  # are examples of valid syntax.
@@ -833,7 +878,7 @@ module Toys
833
878
  # or off. This effectively creates two flags, `--abc` which sets the
834
879
  # value to `true`, and `--no-abc` which sets the falue to `false`.
835
880
  #
836
- # ## Default flag syntax
881
+ # ### Default flag syntax
837
882
  #
838
883
  # If no flag syntax strings are provided, a default syntax will be
839
884
  # inferred based on the key and other options.
@@ -858,7 +903,7 @@ module Toys
858
903
  # flag :number, accept: Integer
859
904
  # flag :number, "--number=VAL", accept: Integer
860
905
  #
861
- # ## More examples
906
+ # ### More examples
862
907
  #
863
908
  # A flag that sets its value to the number of times it appears on the
864
909
  # command line:
@@ -937,7 +982,7 @@ module Toys
937
982
  report_collisions: true, group: nil,
938
983
  desc: nil, long_desc: nil, display_name: nil,
939
984
  &block)
940
- cur_tool = DSL::Tool.current_tool(self, true)
985
+ cur_tool = DSL::Internal.current_tool(self, true)
941
986
  return self if cur_tool.nil?
942
987
  flag_dsl = DSL::Flag.new(
943
988
  flags.flatten, accept, default, handler, complete_flags, complete_values,
@@ -945,14 +990,14 @@ module Toys
945
990
  )
946
991
  flag_dsl.instance_exec(flag_dsl, &block) if block
947
992
  flag_dsl._add_to(cur_tool, key)
948
- DSL::Tool.maybe_add_getter(self, key)
993
+ DSL::Internal.maybe_add_getter(self, key)
949
994
  self
950
995
  end
951
996
 
952
997
  ##
953
- # Add a required positional argument to the current tool. You must specify
954
- # a key which the script may use to obtain the argument value from the
955
- # context.
998
+ # Add a required positional argument to the current tool. You must
999
+ # specify a key which the script may use to obtain the argument value
1000
+ # from the context.
956
1001
  #
957
1002
  # If the given key is a symbol representing a valid method name, then a
958
1003
  # helper method is automatically added to retrieve the value. Otherwise,
@@ -963,7 +1008,7 @@ module Toys
963
1008
  # set in a block passed to this method. If you provide a block, you can
964
1009
  # use directives in {Toys::DSL::PositionalArg} within the block.
965
1010
  #
966
- # ## Example
1011
+ # ### Example
967
1012
  #
968
1013
  # This tool "moves" something from a source to destination, and takes two
969
1014
  # required arguments:
@@ -987,8 +1032,8 @@ module Toys
987
1032
  # values of this arg. This is the empty completion by default. To
988
1033
  # customize completion, set this to the name of a previously defined
989
1034
  # completion, or any spec recognized by {Toys::Completion.create}.
990
- # @param display_name [String] A name to use for display (in help text and
991
- # error reports). Defaults to the key in upper case.
1035
+ # @param display_name [String] A name to use for display (in help text
1036
+ # and error reports). Defaults to the key in upper case.
992
1037
  # @param desc [String,Array<String>,Toys::WrappableString] Short
993
1038
  # description for the flag. See {Toys::DSL::Tool#desc} for a
994
1039
  # description of the allowed formats. Defaults to the empty string.
@@ -1006,21 +1051,21 @@ module Toys
1006
1051
  accept: nil, complete: nil, display_name: nil,
1007
1052
  desc: nil, long_desc: nil,
1008
1053
  &block)
1009
- cur_tool = DSL::Tool.current_tool(self, true)
1054
+ cur_tool = DSL::Internal.current_tool(self, true)
1010
1055
  return self if cur_tool.nil?
1011
1056
  arg_dsl = DSL::PositionalArg.new(accept, nil, complete, display_name, desc, long_desc)
1012
1057
  arg_dsl.instance_exec(arg_dsl, &block) if block
1013
1058
  arg_dsl._add_required_to(cur_tool, key)
1014
- DSL::Tool.maybe_add_getter(self, key)
1059
+ DSL::Internal.maybe_add_getter(self, key)
1015
1060
  self
1016
1061
  end
1017
1062
  alias required required_arg
1018
1063
 
1019
1064
  ##
1020
- # Add an optional positional argument to the current tool. You must specify
1021
- # a key which the script may use to obtain the argument value from the
1022
- # context. If an optional argument is not given on the command line, the
1023
- # value is set to the given default.
1065
+ # Add an optional positional argument to the current tool. You must
1066
+ # specify a key which the script may use to obtain the argument value
1067
+ # from the context. If an optional argument is not given on the command
1068
+ # line, the value is set to the given default.
1024
1069
  #
1025
1070
  # If the given key is a symbol representing a valid method name, then a
1026
1071
  # helper method is automatically added to retrieve the value. Otherwise,
@@ -1031,7 +1076,7 @@ module Toys
1031
1076
  # set in a block passed to this method. If you provide a block, you can
1032
1077
  # use directives in {Toys::DSL::PositionalArg} within the block.
1033
1078
  #
1034
- # ## Example
1079
+ # ### Example
1035
1080
  #
1036
1081
  # This tool creates a "link" to a given target. The link location is
1037
1082
  # optional; if it is not given, it is inferred from the target.
@@ -1048,8 +1093,8 @@ module Toys
1048
1093
  # @param key [String,Symbol] The key to use to retrieve the value from
1049
1094
  # the execution context.
1050
1095
  # @param default [Object] The default value. This is the value that will
1051
- # be set in the context if this argument is not provided on the command
1052
- # line. Defaults to `nil`.
1096
+ # be set in the context if this argument is not provided on the
1097
+ # command line. Defaults to `nil`.
1053
1098
  # @param accept [Object] An acceptor that validates and/or converts the
1054
1099
  # value. You may provide either the name of an acceptor you have
1055
1100
  # defined, one of the default acceptors provided by OptionParser, or
@@ -1059,8 +1104,8 @@ module Toys
1059
1104
  # values of this arg. This is the empty completion by default. To
1060
1105
  # customize completion, set this to the name of a previously defined
1061
1106
  # completion, or any spec recognized by {Toys::Completion.create}.
1062
- # @param display_name [String] A name to use for display (in help text and
1063
- # error reports). Defaults to the key in upper case.
1107
+ # @param display_name [String] A name to use for display (in help text
1108
+ # and error reports). Defaults to the key in upper case.
1064
1109
  # @param desc [String,Array<String>,Toys::WrappableString] Short
1065
1110
  # description for the flag. See {Toys::DSL::Tool#desc} for a
1066
1111
  # description of the allowed formats. Defaults to the empty string.
@@ -1078,20 +1123,20 @@ module Toys
1078
1123
  default: nil, accept: nil, complete: nil, display_name: nil,
1079
1124
  desc: nil, long_desc: nil,
1080
1125
  &block)
1081
- cur_tool = DSL::Tool.current_tool(self, true)
1126
+ cur_tool = DSL::Internal.current_tool(self, true)
1082
1127
  return self if cur_tool.nil?
1083
1128
  arg_dsl = DSL::PositionalArg.new(accept, default, complete, display_name, desc, long_desc)
1084
1129
  arg_dsl.instance_exec(arg_dsl, &block) if block
1085
1130
  arg_dsl._add_optional_to(cur_tool, key)
1086
- DSL::Tool.maybe_add_getter(self, key)
1131
+ DSL::Internal.maybe_add_getter(self, key)
1087
1132
  self
1088
1133
  end
1089
1134
  alias optional optional_arg
1090
1135
 
1091
1136
  ##
1092
- # Specify what should be done with unmatched positional arguments. You must
1093
- # specify a key which the script may use to obtain the remaining args from
1094
- # the context.
1137
+ # Specify what should be done with unmatched positional arguments. You
1138
+ # must specify a key which the script may use to obtain the remaining
1139
+ # args from the context.
1095
1140
  #
1096
1141
  # If the given key is a symbol representing a valid method name, then a
1097
1142
  # helper method is automatically added to retrieve the value. Otherwise,
@@ -1102,7 +1147,7 @@ module Toys
1102
1147
  # set in a block passed to this method. If you provide a block, you can
1103
1148
  # use directives in {Toys::DSL::PositionalArg} within the block.
1104
1149
  #
1105
- # ## Example
1150
+ # ### Example
1106
1151
  #
1107
1152
  # This tool displays a "list" of the given directories. If no directories
1108
1153
  # ar given, lists the current directory.
@@ -1131,8 +1176,8 @@ module Toys
1131
1176
  # values of this arg. This is the empty completion by default. To
1132
1177
  # customize completion, set this to the name of a previously defined
1133
1178
  # completion, or any spec recognized by {Toys::Completion.create}.
1134
- # @param display_name [String] A name to use for display (in help text and
1135
- # error reports). Defaults to the key in upper case.
1179
+ # @param display_name [String] A name to use for display (in help text
1180
+ # and error reports). Defaults to the key in upper case.
1136
1181
  # @param desc [String,Array<String>,Toys::WrappableString] Short
1137
1182
  # description for the flag. See {Toys::DSL::Tool#desc} for a
1138
1183
  # description of the allowed formats. Defaults to the empty string.
@@ -1150,12 +1195,12 @@ module Toys
1150
1195
  default: [], accept: nil, complete: nil, display_name: nil,
1151
1196
  desc: nil, long_desc: nil,
1152
1197
  &block)
1153
- cur_tool = DSL::Tool.current_tool(self, true)
1198
+ cur_tool = DSL::Internal.current_tool(self, true)
1154
1199
  return self if cur_tool.nil?
1155
1200
  arg_dsl = DSL::PositionalArg.new(accept, default, complete, display_name, desc, long_desc)
1156
1201
  arg_dsl.instance_exec(arg_dsl, &block) if block
1157
1202
  arg_dsl._set_remaining_on(cur_tool, key)
1158
- DSL::Tool.maybe_add_getter(self, key)
1203
+ DSL::Internal.maybe_add_getter(self, key)
1159
1204
  self
1160
1205
  end
1161
1206
  alias remaining remaining_args
@@ -1168,7 +1213,7 @@ module Toys
1168
1213
  # if the key is a string or does not represent a valid method name, the
1169
1214
  # tool can retrieve the value by calling {Toys::Context#get}.
1170
1215
  #
1171
- # ## Example
1216
+ # ### Example
1172
1217
  #
1173
1218
  # tool "hello" do
1174
1219
  # static :greeting, "Hi there"
@@ -1190,16 +1235,16 @@ module Toys
1190
1235
  # @return [self]
1191
1236
  #
1192
1237
  def static(key, value = nil)
1193
- cur_tool = DSL::Tool.current_tool(self, true)
1238
+ cur_tool = DSL::Internal.current_tool(self, true)
1194
1239
  return self if cur_tool.nil?
1195
1240
  if key.is_a?(::Hash)
1196
1241
  cur_tool.default_data.merge!(key)
1197
1242
  key.each_key do |k|
1198
- DSL::Tool.maybe_add_getter(self, k)
1243
+ DSL::Internal.maybe_add_getter(self, k)
1199
1244
  end
1200
1245
  else
1201
1246
  cur_tool.default_data[key] = value
1202
- DSL::Tool.maybe_add_getter(self, key)
1247
+ DSL::Internal.maybe_add_getter(self, key)
1203
1248
  end
1204
1249
  self
1205
1250
  end
@@ -1207,7 +1252,7 @@ module Toys
1207
1252
  ##
1208
1253
  # Set a option values statically without creating helper methods.
1209
1254
  #
1210
- # ## Example
1255
+ # ### Example
1211
1256
  #
1212
1257
  # tool "hello" do
1213
1258
  # set :greeting, "Hi there"
@@ -1229,7 +1274,7 @@ module Toys
1229
1274
  # @return [self]
1230
1275
  #
1231
1276
  def set(key, value = nil)
1232
- cur_tool = DSL::Tool.current_tool(self, true)
1277
+ cur_tool = DSL::Internal.current_tool(self, true)
1233
1278
  return self if cur_tool.nil?
1234
1279
  if key.is_a?(::Hash)
1235
1280
  cur_tool.default_data.merge!(key)
@@ -1251,7 +1296,7 @@ module Toys
1251
1296
  # @return [self]
1252
1297
  #
1253
1298
  def enforce_flags_before_args(state = true)
1254
- DSL::Tool.current_tool(self, true)&.enforce_flags_before_args(state)
1299
+ DSL::Internal.current_tool(self, true)&.enforce_flags_before_args(state)
1255
1300
  self
1256
1301
  end
1257
1302
 
@@ -1267,7 +1312,7 @@ module Toys
1267
1312
  # @return [self]
1268
1313
  #
1269
1314
  def require_exact_flag_match(state = true)
1270
- DSL::Tool.current_tool(self, true)&.require_exact_flag_match(state)
1315
+ DSL::Internal.current_tool(self, true)&.require_exact_flag_match(state)
1271
1316
  self
1272
1317
  end
1273
1318
 
@@ -1282,7 +1327,7 @@ module Toys
1282
1327
  # @return [self]
1283
1328
  #
1284
1329
  def disable_argument_parsing
1285
- DSL::Tool.current_tool(self, true)&.disable_argument_parsing
1330
+ DSL::Internal.current_tool(self, true)&.disable_argument_parsing
1286
1331
  self
1287
1332
  end
1288
1333
 
@@ -1291,7 +1336,7 @@ module Toys
1291
1336
  # subsequent flag definition. This can be used to prevent middleware from
1292
1337
  # defining a particular flag.
1293
1338
  #
1294
- # ## Example
1339
+ # ### Example
1295
1340
  #
1296
1341
  # This tool does not support the `-v` and `-q` short forms for the two
1297
1342
  # verbosity flags (although it still supports the long forms `--verbose`
@@ -1308,7 +1353,7 @@ module Toys
1308
1353
  # @return [self]
1309
1354
  #
1310
1355
  def disable_flag(*flags)
1311
- DSL::Tool.current_tool(self, true)&.disable_flag(*flags)
1356
+ DSL::Internal.current_tool(self, true)&.disable_flag(*flags)
1312
1357
  self
1313
1358
  end
1314
1359
 
@@ -1319,12 +1364,13 @@ module Toys
1319
1364
  # * The string name of a completion defined in this tool or any of its
1320
1365
  # its ancestors.
1321
1366
  # * A hash of options to pass to the constructor of
1322
- # {Toys::Tool::DefaultCompletion}.
1367
+ # {Toys::ToolDefinition::DefaultCompletion}.
1323
1368
  # * `nil` or `:default` to select the standard completion strategy
1324
- # (which is {Toys::Tool::DefaultCompletion} with no extra options).
1369
+ # (which is {Toys::ToolDefinition::DefaultCompletion} with no extra
1370
+ # options).
1325
1371
  # * Any other specification recognized by {Toys::Completion.create}.
1326
1372
  #
1327
- # ## Example
1373
+ # ### Example
1328
1374
  #
1329
1375
  # The namespace "foo" supports completion only of subtool names. It does
1330
1376
  # not complete the standard flags (like --help).
@@ -1345,7 +1391,7 @@ module Toys
1345
1391
  # @return [self]
1346
1392
  #
1347
1393
  def complete_tool_args(spec = nil, **options, &block)
1348
- cur_tool = DSL::Tool.current_tool(self, true)
1394
+ cur_tool = DSL::Internal.current_tool(self, true)
1349
1395
  return self if cur_tool.nil?
1350
1396
  cur_tool.completion = Completion.scalarize_spec(spec, options, block)
1351
1397
  self
@@ -1360,7 +1406,7 @@ module Toys
1360
1406
  # in the lexical scope. However, it is often more convenient to use
1361
1407
  # {#static} to set the value in the context.)
1362
1408
  #
1363
- # ## Example
1409
+ # ### Example
1364
1410
  #
1365
1411
  # tool "foo" do
1366
1412
  # cur_time = Time.new
@@ -1373,7 +1419,9 @@ module Toys
1373
1419
  # @return [self]
1374
1420
  #
1375
1421
  def to_run(&block)
1376
- define_method(:run, &block)
1422
+ cur_tool = DSL::Internal.current_tool(self, true)
1423
+ return self if cur_tool.nil?
1424
+ cur_tool.run_handler = block
1377
1425
  self
1378
1426
  end
1379
1427
  alias on_run to_run
@@ -1385,7 +1433,7 @@ module Toys
1385
1433
  # either case, the block or method should take one argument, the
1386
1434
  # Interrupt exception that was raised.
1387
1435
  #
1388
- # ## Example
1436
+ # ### Example
1389
1437
  #
1390
1438
  # tool "foo" do
1391
1439
  # def run
@@ -1402,7 +1450,7 @@ module Toys
1402
1450
  # @return [self]
1403
1451
  #
1404
1452
  def on_interrupt(handler = nil, &block)
1405
- cur_tool = DSL::Tool.current_tool(self, true)
1453
+ cur_tool = DSL::Internal.current_tool(self, true)
1406
1454
  return self if cur_tool.nil?
1407
1455
  cur_tool.interrupt_handler = handler || block
1408
1456
  self
@@ -1415,7 +1463,7 @@ module Toys
1415
1463
  # either case, the block or method should take one argument, the array of
1416
1464
  # usage errors reported.
1417
1465
  #
1418
- # ## Example
1466
+ # ### Example
1419
1467
  #
1420
1468
  # This tool runs even if a usage error is encountered. You can find info
1421
1469
  # on the errors from {Toys::Context::Key::USAGE_ERRORS},
@@ -1434,7 +1482,7 @@ module Toys
1434
1482
  # @return [self]
1435
1483
  #
1436
1484
  def on_usage_error(handler = nil, &block)
1437
- cur_tool = DSL::Tool.current_tool(self, true)
1485
+ cur_tool = DSL::Internal.current_tool(self, true)
1438
1486
  return self if cur_tool.nil?
1439
1487
  cur_tool.usage_error_handler = handler || block
1440
1488
  self
@@ -1448,7 +1496,7 @@ module Toys
1448
1496
  # have defined in this tool or one of its ancestors, or the symbol name
1449
1497
  # of a well-known mixin.
1450
1498
  #
1451
- # ## Example
1499
+ # ### Example
1452
1500
  #
1453
1501
  # Include the well-known mixin `:terminal` and perform some terminal
1454
1502
  # magic.
@@ -1463,28 +1511,16 @@ module Toys
1463
1511
  # end
1464
1512
  # end
1465
1513
  #
1466
- # @param mod [Module,Symbol,String] Module or module name.
1514
+ # @param mixin [Module,Symbol,String] Module or module name.
1467
1515
  # @param args [Object...] Arguments to pass to the initializer
1468
1516
  # @param kwargs [keywords] Keyword arguments to pass to the initializer
1469
1517
  # @return [self]
1470
1518
  #
1471
- def include(mod, *args, **kwargs)
1472
- cur_tool = DSL::Tool.current_tool(self, true)
1519
+ def include(mixin, *args, **kwargs)
1520
+ cur_tool = DSL::Internal.current_tool(self, true)
1473
1521
  return self if cur_tool.nil?
1474
- mod = DSL::Tool.resolve_mixin(mod, cur_tool, @__loader)
1475
- if included_modules.include?(mod)
1476
- raise ToolDefinitionError, "Mixin already included: #{mod.name}"
1477
- end
1478
- cur_tool.mark_includes_modules
1479
- super(mod)
1480
- if mod.respond_to?(:initializer)
1481
- callback = mod.initializer
1482
- cur_tool.add_initializer(callback, *args, **kwargs) if callback
1483
- end
1484
- if mod.respond_to?(:inclusion)
1485
- callback = mod.inclusion
1486
- class_exec(*args, **kwargs, &callback) if callback
1487
- end
1522
+ mod = DSL::Internal.resolve_mixin(mixin, cur_tool, @__loader)
1523
+ cur_tool.include_mixin(mod, *args, **kwargs)
1488
1524
  self
1489
1525
  end
1490
1526
 
@@ -1501,9 +1537,9 @@ module Toys
1501
1537
  # @return [nil] if the current tool is not active.
1502
1538
  #
1503
1539
  def include?(mod)
1504
- cur_tool = DSL::Tool.current_tool(self, false)
1540
+ cur_tool = DSL::Internal.current_tool(self, false)
1505
1541
  return if cur_tool.nil?
1506
- super(DSL::Tool.resolve_mixin(mod, cur_tool, @__loader))
1542
+ super(DSL::Internal.resolve_mixin(mod, cur_tool, @__loader))
1507
1543
  end
1508
1544
 
1509
1545
  ##
@@ -1523,7 +1559,7 @@ module Toys
1523
1559
  # in a directory called `.data` inside a Toys directory. This directive
1524
1560
  # locates a data file during tool definition.
1525
1561
  #
1526
- # ## Example
1562
+ # ### Example
1527
1563
  #
1528
1564
  # This tool reads its description from a text file in the `.data`
1529
1565
  # directory.
@@ -1557,17 +1593,17 @@ module Toys
1557
1593
  # @return [nil] if there is no context.
1558
1594
  #
1559
1595
  def context_directory
1560
- DSL::Tool.current_tool(self, false)&.context_directory || source_info.context_directory
1596
+ DSL::Internal.current_tool(self, false)&.context_directory || source_info.context_directory
1561
1597
  end
1562
1598
 
1563
1599
  ##
1564
- # Return the current tool object. This object can be queried to determine
1600
+ # Return the current tool config. This object can be queried to determine
1565
1601
  # such information as the name, but it should not be altered.
1566
1602
  #
1567
- # @return [Toys::Tool]
1603
+ # @return [Toys::ToolDefinition]
1568
1604
  #
1569
1605
  def current_tool
1570
- DSL::Tool.current_tool(self, false)
1606
+ DSL::Internal.current_tool(self, false)
1571
1607
  end
1572
1608
 
1573
1609
  ##
@@ -1577,7 +1613,7 @@ module Toys
1577
1613
  # @return [self]
1578
1614
  #
1579
1615
  def set_context_directory(dir) # rubocop:disable Naming/AccessorMethodName
1580
- cur_tool = DSL::Tool.current_tool(self, false)
1616
+ cur_tool = DSL::Internal.current_tool(self, false)
1581
1617
  return self if cur_tool.nil?
1582
1618
  cur_tool.custom_context_directory = dir
1583
1619
  self
@@ -1591,7 +1627,7 @@ module Toys
1591
1627
  # The block is applied only to subtools defined *after* the block
1592
1628
  # appears. Subtools defined before the block appears are not affected.
1593
1629
  #
1594
- # ## Example
1630
+ # ### Example
1595
1631
  #
1596
1632
  # It is common for tools to use the `:exec` mixin to invoke external
1597
1633
  # programs. This example automatically includes the exec mixin in all
@@ -1616,7 +1652,7 @@ module Toys
1616
1652
  # end
1617
1653
  #
1618
1654
  def subtool_apply(&block)
1619
- cur_tool = DSL::Tool.current_tool(self, false)
1655
+ cur_tool = DSL::Internal.current_tool(self, false)
1620
1656
  return self if cur_tool.nil?
1621
1657
  cur_tool.subtool_middleware_stack.add(:apply_config,
1622
1658
  parent_source: source_info, &block)
@@ -1640,6 +1676,15 @@ module Toys
1640
1676
  end
1641
1677
  end
1642
1678
 
1679
+ ##
1680
+ # Get the settings for this tool.
1681
+ #
1682
+ # @return [Toys::ToolDefinition::Settings] Tool-specific settings.
1683
+ #
1684
+ def settings
1685
+ DSL::Internal.current_tool(self, false)&.settings
1686
+ end
1687
+
1643
1688
  ##
1644
1689
  # Determines whether the current Toys version satisfies the given
1645
1690
  # requirements.
@@ -1672,93 +1717,6 @@ module Toys
1672
1717
  end
1673
1718
  self
1674
1719
  end
1675
-
1676
- ## @private
1677
- def self.new_class(words, priority, loader)
1678
- tool_class = ::Class.new(::Toys::Context)
1679
- tool_class.extend(DSL::Tool)
1680
- tool_class.instance_variable_set(:@__words, words)
1681
- tool_class.instance_variable_set(:@__priority, priority)
1682
- tool_class.instance_variable_set(:@__loader, loader)
1683
- tool_class.instance_variable_set(:@__remaining_words, nil)
1684
- tool_class.instance_variable_set(:@__source, [])
1685
- tool_class
1686
- end
1687
-
1688
- ## @private
1689
- def self.current_tool(tool_class, activate)
1690
- memoize_var = activate ? :@__active_tool : :@__cur_tool
1691
- if tool_class.instance_variable_defined?(memoize_var)
1692
- tool_class.instance_variable_get(memoize_var)
1693
- else
1694
- loader = tool_class.instance_variable_get(:@__loader)
1695
- words = tool_class.instance_variable_get(:@__words)
1696
- priority = tool_class.instance_variable_get(:@__priority)
1697
- cur_tool =
1698
- if activate
1699
- loader.activate_tool(words, priority)
1700
- else
1701
- loader.get_tool(words, priority)
1702
- end
1703
- if cur_tool && activate
1704
- source = tool_class.instance_variable_get(:@__source).last
1705
- cur_tool.lock_source(source)
1706
- end
1707
- tool_class.instance_variable_set(memoize_var, cur_tool)
1708
- end
1709
- end
1710
-
1711
- ## @private
1712
- def self.prepare(tool_class, remaining_words, source)
1713
- tool_class.instance_variable_set(:@__remaining_words, remaining_words)
1714
- tool_class.instance_variable_get(:@__source).push(source)
1715
- yield
1716
- ensure
1717
- tool_class.instance_variable_get(:@__source).pop
1718
- end
1719
-
1720
- ## @private
1721
- def self.maybe_add_getter(tool_class, key)
1722
- if key.is_a?(::Symbol) && key.to_s =~ /^[_a-zA-Z]\w*[!\?]?$/ && key != :run
1723
- unless tool_class.public_method_defined?(key)
1724
- tool_class.class_eval do
1725
- define_method(key) do
1726
- self[key]
1727
- end
1728
- end
1729
- end
1730
- end
1731
- end
1732
-
1733
- ## @private
1734
- def self.resolve_mixin(mod, cur_tool, loader)
1735
- name = mod.to_s
1736
- if mod.is_a?(::String)
1737
- mod = cur_tool.lookup_mixin(mod)
1738
- elsif mod.is_a?(::Symbol)
1739
- mod = loader.resolve_standard_mixin(name)
1740
- end
1741
- unless mod.is_a?(::Module)
1742
- raise ToolDefinitionError, "Module not found: #{name.inspect}"
1743
- end
1744
- mod
1745
- end
1746
-
1747
- ## @private
1748
- def self.load_long_desc_file(path)
1749
- if ::File.extname(path) == ".txt"
1750
- begin
1751
- ::File.readlines(path).map do |line|
1752
- line = line.chomp
1753
- line =~ /^\s/ ? [line] : line
1754
- end
1755
- rescue ::SystemCallError => e
1756
- raise Toys::ToolDefinitionError, e.to_s
1757
- end
1758
- else
1759
- raise Toys::ToolDefinitionError, "Cannot load long desc from file type: #{path}"
1760
- end
1761
- end
1762
1720
  end
1763
1721
  end
1764
1722
  end