toys 0.3.0 → 0.3.1

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: e1303b77289f7e37c45866ec22513df6187556c2d7188b97d2706731b73377c5
4
- data.tar.gz: 31c4013f235e7fb83575c9b031798dc410ab0bdee2395303f3f54e54fc870c05
3
+ metadata.gz: 1dcbd6fabf227072fda60d4acebcc09ae1a8af3d10db580bcea3d4a56a6623fd
4
+ data.tar.gz: bdc7a717793c190af3cb0c470cfbd97078779478e1528297397303c88af1b6a3
5
5
  SHA512:
6
- metadata.gz: 8389b529719c6135483a52a65ec2521e90964be02fac0da56fa7016672da8f4f9a8f7ad24c8a0493db8558040c3c80e47553656f3a567524f4a7e310669cd05a
7
- data.tar.gz: 708270b18a969b0adb5aa16c9b517966d82a3531a4a88604b7ee3cae27ecb67c839ef40318378c89d6a3347658c6d3fe590a6482177d0a899d96eaf0847f6366
6
+ metadata.gz: 74b91bdd53c8ca7aec7ba1b1d5ed9d98e2f3a826846ea9d41975391d504e14047d25f6db9551318568fabb81194dd3bf896183316a50e2fe63f92a4e0811ee9c
7
+ data.tar.gz: 81617b1a8aff51c05cae72cc79e4fffd5030af0176f50f389e3e235334161efbbb9f6ca60e5273f15a9a00cf76bbbfc675aec0a906c87110991e760c9ff1e2da
@@ -0,0 +1,11 @@
1
+ --no-private
2
+ --title=Toys
3
+ --markup=markdown
4
+ --embed-mixin=ClassMethods
5
+ --main=README.md
6
+ --exclude=lib/toys/builtins
7
+ ./lib/**/*.rb
8
+ -
9
+ README.md
10
+ LICENSE.md
11
+ CHANGELOG.md
data/README.md CHANGED
@@ -1,5 +1,7 @@
1
1
  # Toys
2
2
 
3
+ [![Travis-CI Build Status](https://travis-ci.org/dazuma/toys.svg)](https://travis-ci.org/dazuma/toys/)
4
+
3
5
  Toys is a Ruby library and command line tool that lets you build your own
4
6
  command line suite of tools (with commands and subcommands) using a Ruby DSL.
5
7
  You can define commands globally or configure special commands scoped to
@@ -29,11 +31,12 @@ The source can be found on Github at
29
31
 
30
32
  ### TODO items
31
33
 
32
- * Document methods
33
- * Write overall documentation
34
34
  * Decide about highline integration
35
35
  * Output formats middleware
36
36
  * System paths tool
37
+ * Search function in group help
38
+ * Split out toys-core gem
39
+ * Write overall documentation
37
40
 
38
41
  ## License
39
42
 
@@ -35,13 +35,13 @@
35
35
  #
36
36
  module Toys
37
37
  ##
38
- # Namespace for common utility classes
38
+ # Namespace for common utility classes.
39
39
  #
40
40
  module Utils; end
41
41
  end
42
42
 
43
- require "toys/builder"
44
43
  require "toys/cli"
44
+ require "toys/config_dsl"
45
45
  require "toys/context"
46
46
  require "toys/errors"
47
47
  require "toys/helpers"
@@ -43,6 +43,7 @@ tool "update" do
43
43
  use :exec
44
44
 
45
45
  execute do
46
+ logger.info "Checking rubygems for the latest toys release..."
46
47
  version_info = capture("gem query -q -r -e toys")
47
48
  if version_info =~ /toys\s\((.+)\)/
48
49
  latest_version = ::Gem::Version.new($1)
@@ -50,6 +51,9 @@ tool "update" do
50
51
  if latest_version > cur_version
51
52
  logger.warn("Updating toys from #{cur_version} to #{latest_version}...")
52
53
  sh("gem install toys")
54
+ elsif latest_version < cur_version
55
+ logger.warn("Toys is already at experimental version #{cur_version}, which is later than" \
56
+ " the latest released version #{latest_version}")
53
57
  else
54
58
  logger.warn("Toys is already at the latest version: #{latest_version}")
55
59
  end
@@ -31,7 +31,9 @@ require "logger"
31
31
 
32
32
  module Toys
33
33
  ##
34
- # A Toys-based CLI
34
+ # A Toys-based CLI.
35
+ #
36
+ # Use this class to implement a CLI using Toys.
35
37
  #
36
38
  class CLI
37
39
  ##
@@ -75,6 +77,38 @@ module Toys
75
77
  " globally or scoped to specific directories that you choose." \
76
78
  " For detailed information, see https://www.rubydoc.info/gems/toys".freeze
77
79
 
80
+ ##
81
+ # Create a CLI
82
+ #
83
+ # @param [String,nil] binary_name The binary name displayed in help text.
84
+ # Optional. Defaults to the ruby program name.
85
+ # @param [Logger,nil] logger The logger to use. If not provided, a default
86
+ # logger that writes to `STDERR` is used.
87
+ # @param [String,nil] config_dir_name A directory with this name that
88
+ # appears in the loader path, is treated as a configuration directory
89
+ # whose contents are loaded into the toys configuration. Optional.
90
+ # If not provided, toplevel configuration directories are disabled.
91
+ # The default toys CLI sets this to `".toys"`.
92
+ # @param [String,nil] config_file_name A file with this name that appears
93
+ # in the loader path, is treated as a toplevel configuration file
94
+ # whose contents are loaded into the toys configuration. Optional.
95
+ # If not provided, toplevel configuration files are disabled.
96
+ # The default toys CLI sets this to `".toys.rb"`.
97
+ # @param [String,nil] index_file_name A file with this name that appears
98
+ # in any configuration directory (not just a toplevel directory) is
99
+ # loaded first as a standalone configuration file. If not provided,
100
+ # standalone configuration files are disabled.
101
+ # The default toys CLI sets this to `".toys.rb"`.
102
+ # @param [String,nil] preload_file_name A file with this name that appears
103
+ # in any configuration directory (not just a toplevel directory) is
104
+ # loaded before any configuration files. It is not treated as a
105
+ # configuration file in that the configuration DSL is not honored. You
106
+ # may use such a file to define auxiliary Ruby modules and classes that
107
+ # used by the tools defined in that directory.
108
+ # @param [Array] middleware An array of middleware that will be used by
109
+ # default for all tools loaded by this CLI.
110
+ # @param [String] root_desc The description of the root tool.
111
+ #
78
112
  def initialize(
79
113
  binary_name: nil,
80
114
  logger: nil,
@@ -97,16 +131,41 @@ module Toys
97
131
  @context_base = Context::Base.new(@loader, binary_name, logger)
98
132
  end
99
133
 
134
+ ##
135
+ # Add one or more configuration files/directories to the loader.
136
+ #
137
+ # If a CLI has a default tool set, it might use this to point to the
138
+ # directory that defines those tools. For example, the default Toys CLI
139
+ # uses this to load the builtin tools from the `builtins` directory.
140
+ #
141
+ # @param [String,Array<String>] paths One or more paths to add.
142
+ #
100
143
  def add_config_paths(paths)
101
144
  @loader.add_config_paths(paths)
102
145
  self
103
146
  end
104
147
 
148
+ ##
149
+ # Add one or more path directories to the loader. These directories are
150
+ # searched for config directories and config files. Typically a CLI may
151
+ # include the current directory, or the user's home directory, `/etc` or
152
+ # other configuration-centric directories here.
153
+ #
154
+ # @param [String,Array<String>] paths One or more paths to add.
155
+ #
105
156
  def add_paths(paths)
106
157
  @loader.add_paths(paths)
107
158
  self
108
159
  end
109
160
 
161
+ ##
162
+ # Add the given path and all ancestor directories to the loader as paths.
163
+ # You may optionally provide a stopping point using the `base` argument,
164
+ # which, if present, will be the _last_ directory added.
165
+ #
166
+ # @param [String] path The first directory to add
167
+ # @param [String] base The last directory to add. Defaults to `"/"`.
168
+ #
110
169
  def add_path_hierarchy(path = nil, base = "/")
111
170
  path ||= ::Dir.pwd
112
171
  paths = []
@@ -121,6 +180,11 @@ module Toys
121
180
  self
122
181
  end
123
182
 
183
+ ##
184
+ # Add a standard set of paths. This includes the contents of the
185
+ # `TOYS_PATH` environment variable if present, the current user's home
186
+ # directory, and any system configuration directories such as `/etc`.
187
+ #
124
188
  def add_standard_paths
125
189
  toys_path = ::ENV["TOYS_PATH"].to_s.split(::File::PATH_SEPARATOR)
126
190
  if toys_path.empty?
@@ -131,11 +195,19 @@ module Toys
131
195
  self
132
196
  end
133
197
 
198
+ ##
199
+ # Run the CLI with the given command line arguments.
200
+ #
134
201
  def run(*args)
135
202
  exit(@context_base.run(args.flatten, verbosity: 0))
136
203
  end
137
204
 
138
205
  class << self
206
+ ##
207
+ # Configure and create the standard Toys CLI.
208
+ #
209
+ # @return [Toys::CLI]
210
+ #
139
211
  def create_standard
140
212
  cli = new(
141
213
  binary_name: DEFAULT_BINARY_NAME,
@@ -152,15 +224,30 @@ module Toys
152
224
  cli
153
225
  end
154
226
 
227
+ ##
228
+ # Returns a default set of middleware used by the standard Toys CLI.
229
+ # This middleware handles usage errors, provides a behavior for groups
230
+ # that displays the group command list, provides a `--help` option for
231
+ # showing individual tool documentation, and provides `--verbose` and
232
+ # `--quiet` switches for setting the verbosity, which in turn controls
233
+ # the logger level.
234
+ #
235
+ # @return [Array]
236
+ #
155
237
  def default_middleware_stack
156
238
  [
157
239
  Middleware.lookup(:show_usage_errors).new,
158
- Middleware.lookup(:group_default).new,
159
- Middleware.lookup(:show_tool_help).new,
240
+ Middleware.lookup(:show_group_usage).new,
241
+ Middleware.lookup(:show_tool_usage).new,
160
242
  Middleware.lookup(:set_verbosity).new
161
243
  ]
162
244
  end
163
245
 
246
+ ##
247
+ # Returns a default logger that logs to `STDERR`.
248
+ #
249
+ # @return [Logger]
250
+ #
164
251
  def default_logger
165
252
  logger = ::Logger.new(::STDERR)
166
253
  logger.formatter = proc do |severity, time, _progname, msg|
@@ -0,0 +1,432 @@
1
+ # Copyright 2018 Daniel Azuma
2
+ #
3
+ # All rights reserved.
4
+ #
5
+ # Redistribution and use in source and binary forms, with or without
6
+ # modification, are permitted provided that the following conditions are met:
7
+ #
8
+ # * Redistributions of source code must retain the above copyright notice,
9
+ # this list of conditions and the following disclaimer.
10
+ # * Redistributions in binary form must reproduce the above copyright notice,
11
+ # this list of conditions and the following disclaimer in the documentation
12
+ # and/or other materials provided with the distribution.
13
+ # * Neither the name of the copyright holder, nor the names of any other
14
+ # contributors to this software, may be used to endorse or promote products
15
+ # derived from this software without specific prior written permission.
16
+ #
17
+ # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
18
+ # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
19
+ # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
20
+ # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
21
+ # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
22
+ # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
23
+ # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
24
+ # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
25
+ # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
26
+ # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
27
+ # POSSIBILITY OF SUCH DAMAGE.
28
+ ;
29
+
30
+ module Toys
31
+ ##
32
+ # This class defines the DSL for a toys configuration file.
33
+ #
34
+ # A toys configuration defines one or more named tools. It provides syntax
35
+ # for setting the description, defining switches and arguments, specifying
36
+ # how to execute the tool, and requesting helper modules and other services.
37
+ # It also lets you define subtools, nested arbitrarily deep, using blocks.
38
+ #
39
+ # Generally ConfigDSL is invoked from the {Loader}. Applications should not
40
+ # need to create instances of ConfigDSL directly.
41
+ #
42
+ # ## Simple example
43
+ #
44
+ # Create a file called `.toys.rb` in the current directory, with the
45
+ # following contents:
46
+ #
47
+ # tool "greet" do
48
+ # desc "Prints a simple greeting"
49
+ #
50
+ # optional_arg :recipient, default: "world"
51
+ #
52
+ # execute do
53
+ # puts "Hello, #{self[:recipient]}!"
54
+ # end
55
+ # end
56
+ #
57
+ # Now you can execute it using:
58
+ #
59
+ # toys greet
60
+ #
61
+ # or try:
62
+ #
63
+ # toys greet rubyists
64
+ #
65
+ class ConfigDSL
66
+ ##
67
+ # Create an instance of the DSL.
68
+ # @private
69
+ #
70
+ # @param [String] path The path to the config file being evaluated
71
+ # @param [Toys::Tool] tool The tool being defined at the top level
72
+ # @param [Array<String>,nil] remaining_words Arguments remaining in the
73
+ # current lookup.
74
+ # @param [Integer] priority Priority of this configuration
75
+ # @param [Toys::Loader] loader Current active loader
76
+ # @param [:tool,:append,:group] type Type of tool being configured
77
+ #
78
+ # @return [Toys::ConfigDSL]
79
+ #
80
+ def initialize(path, tool, remaining_words, priority, loader, type)
81
+ @path = path
82
+ @tool = tool
83
+ @remaining_words = remaining_words
84
+ @priority = priority
85
+ @loader = loader
86
+ @type = type
87
+ end
88
+
89
+ ##
90
+ # Create a subtool.
91
+ #
92
+ # If the subtool is not an alias, you must provide a block defining the
93
+ # subtool.
94
+ #
95
+ # If the subtool is already defined (either as a tool or a group), the old
96
+ # definition is discarded and replaced with the new definition. If the old
97
+ # tool was a group, all its descendants are also discarded, recursively.
98
+ #
99
+ # @param [String] word The name of the subtool
100
+ # @param [String,nil] alias_of If set, this subtool is set to be an alias
101
+ # of the given subtool name. Defaults to `nil`, indicating the subtool
102
+ # is not an alias.
103
+ #
104
+ def tool(word, alias_of: nil, &block)
105
+ word = word.to_s
106
+ subtool = @loader.get_or_create_tool(@tool.full_name + [word], @priority, assume_parent: true)
107
+ return self if subtool.nil?
108
+ if alias_of
109
+ if block
110
+ raise ToolDefinitionError, "Cannot take a block with alias_of"
111
+ end
112
+ subtool.make_alias_of_word(alias_of.to_s)
113
+ return self
114
+ end
115
+ next_remaining = Loader.next_remaining_words(@remaining_words, word)
116
+ ConfigDSL.evaluate(@path, subtool, next_remaining, @priority, @loader, :tool, block)
117
+ self
118
+ end
119
+ alias name tool
120
+
121
+ ##
122
+ # Append subtools to an existing group.
123
+ #
124
+ # Pass a group name to this method to "reopen" that group. You must provide
125
+ # a block. In that block, you may not modify any properties of the group
126
+ # itself, but you may add or replace subtools within the group.
127
+ #
128
+ # @param [String] word The name of the group.
129
+ #
130
+ def append(word, &block)
131
+ word = word.to_s
132
+ subtool = @loader.get_or_create_tool(@tool.full_name + [word], nil, assume_parent: true)
133
+ next_remaining = Loader.next_remaining_words(@remaining_words, word)
134
+ ConfigDSL.evaluate(@path, subtool, next_remaining, @priority, @loader, :append, block)
135
+ self
136
+ end
137
+
138
+ ##
139
+ # Create a group subtool. You must provide a block defining the group's
140
+ # properties and contents.
141
+ #
142
+ # If the subtool is already defined (either as a tool or a group), the old
143
+ # definition is discarded and replaced with the new definition.
144
+ #
145
+ # @param [String] word The name of the group
146
+ #
147
+ def group(word, &block)
148
+ word = word.to_s
149
+ subtool = @loader.get_or_create_tool(@tool.full_name + [word], @priority, assume_parent: true)
150
+ return self if subtool.nil?
151
+ next_remaining = Loader.next_remaining_words(@remaining_words, word)
152
+ ConfigDSL.evaluate(@path, subtool, next_remaining, @priority, @loader, :group, block)
153
+ self
154
+ end
155
+
156
+ ##
157
+ # Create an alias of the current tool.
158
+ #
159
+ # @param [String] word The name of the alias
160
+ #
161
+ def alias_as(word)
162
+ if @tool.root?
163
+ raise ToolDefinitionError, "Cannot make an alias of the root tool"
164
+ end
165
+ if @type == :group || @type == :append
166
+ raise ToolDefinitionError, "Cannot make an alias of a group"
167
+ end
168
+ alias_name = @tool.full_name.slice(0..-2) + [word.to_s]
169
+ alias_tool = @loader.get_or_create_tool(alias_name, @priority)
170
+ alias_tool.make_alias_of(@tool.simple_name) if alias_tool
171
+ self
172
+ end
173
+
174
+ ##
175
+ # Make the current tool an alias of the given sibling
176
+ #
177
+ # @param [String] word The name of the sibling tool to alias
178
+ #
179
+ def alias_of(word)
180
+ if @tool.root?
181
+ raise ToolDefinitionError, "Cannot make the root tool an alias"
182
+ end
183
+ if @type == :group || @type == :append
184
+ raise ToolDefinitionError, "Cannot make a group an alias"
185
+ end
186
+ @tool.make_alias_of(word.to_s)
187
+ self
188
+ end
189
+
190
+ ##
191
+ # Include another config file or directory at the current location.
192
+ #
193
+ # @param [String] path The file or directory to include.
194
+ #
195
+ def include(path)
196
+ @tool.yield_definition do
197
+ @loader.include_path(path, @tool.full_name, @remaining_words, @priority)
198
+ end
199
+ self
200
+ end
201
+
202
+ ##
203
+ # Expand the given template in the current location.
204
+ #
205
+ # The template may be specified as a class or a well-known template name.
206
+ # You may also provide arguments to pass to the template.
207
+ #
208
+ # @param [Class,String,Symbol] template_class The template, either as a
209
+ # class or a well-known name.
210
+ # @param [Object...] args Template arguments
211
+ #
212
+ def expand(template_class, *args)
213
+ unless template_class.is_a?(::Class)
214
+ name = template_class.to_s
215
+ template_class = Templates.lookup(name)
216
+ if template_class.nil?
217
+ raise ToolDefinitionError, "Template not found: #{name.inspect}"
218
+ end
219
+ end
220
+ template = template_class.new(*args)
221
+ yield template if block_given?
222
+ instance_exec(template, &template_class.expander)
223
+ self
224
+ end
225
+
226
+ ##
227
+ # Set the long description for the current tool. The long description is
228
+ # displayed in the usage documentation for the tool itself.
229
+ #
230
+ # @param [String] desc The long description string.
231
+ #
232
+ def long_desc(desc)
233
+ if @type == :append
234
+ raise ToolDefinitionError, "Cannot set the description when appending"
235
+ end
236
+ @tool.long_desc = desc
237
+ self
238
+ end
239
+
240
+ ##
241
+ # Set the short description for the current tool. The short description is
242
+ # displayed with the tool in a command list. You may also use the
243
+ # equivalent method `short_desc`.
244
+ #
245
+ # @param [String] desc The short description string.
246
+ #
247
+ def desc(desc)
248
+ if @type == :append
249
+ raise ToolDefinitionError, "Cannot set the description when appending"
250
+ end
251
+ @tool.desc = desc
252
+ self
253
+ end
254
+ alias short_desc desc
255
+
256
+ ##
257
+ # Add a switch to the current tool. Each switch must specify a key which
258
+ # the executor may use to obtain the switch value from the context.
259
+ # You may then provide the switches themselves in `OptionParser` form.
260
+ #
261
+ # @param [Symbol] key The key to use to retrieve the value from the
262
+ # execution context.
263
+ # @param [String...] switches The switches in OptionParser format.
264
+ # @param [Object,nil] accept An OptionParser acceptor. Optional.
265
+ # @param [Object] default The default value. This is the value that will
266
+ # be set in the context if this switch is not provided on the command
267
+ # line. Defaults to `nil`.
268
+ # @param [String,nil] doc The documentation for the switch, which appears
269
+ # in the usage documentation. Defaults to `nil` for no documentation.
270
+ # @param [Boolean] only_unique If true, any switches that are already
271
+ # defined in this tool are removed from this switch. For example, if
272
+ # an earlier switch uses `-a`, and this switch wants to use both
273
+ # `-a` and `-b`, then only `-b` will be assigned to this switch.
274
+ # Defaults to false.
275
+ # @param [Proc,nil] handler An optional handler for setting/updating the
276
+ # value. If given, it should take two arguments, the new given value
277
+ # and the previous value, and it should return the new value that
278
+ # should be set. The default handler simply replaces the previous
279
+ # value. i.e. the default is effectively `-> (val, _prev) { val }`.
280
+ #
281
+ def switch(key, *switches,
282
+ accept: nil, default: nil, doc: nil, only_unique: false, handler: nil)
283
+ if @type == :append
284
+ raise ToolDefinitionError, "Cannot add a switch when appending"
285
+ end
286
+ @tool.add_switch(key, *switches,
287
+ accept: accept, default: default, doc: doc,
288
+ only_unique: only_unique, handler: handler)
289
+ self
290
+ end
291
+
292
+ ##
293
+ # Add a required positional argument to the current tool. You must specify
294
+ # a key which the executor may use to obtain the argument value from the
295
+ # context.
296
+ #
297
+ # @param [Symbol] key The key to use to retrieve the value from the
298
+ # execution context.
299
+ # @param [Object,nil] accept An OptionParser acceptor. Optional.
300
+ # @param [String,nil] doc The documentation for the switch, which appears
301
+ # in the usage documentation. Defaults to `nil` for no documentation.
302
+ #
303
+ def required_arg(key, accept: nil, doc: nil)
304
+ if @type == :append
305
+ raise ToolDefinitionError, "Cannot add an argument when appending"
306
+ end
307
+ @tool.add_required_arg(key, accept: accept, doc: doc)
308
+ self
309
+ end
310
+
311
+ ##
312
+ # Add an optional positional argument to the current tool. You must specify
313
+ # a key which the executor may use to obtain the argument value from the
314
+ # context. If an optional argument is not given on the command line, the
315
+ # value is set to the given default.
316
+ #
317
+ # @param [Symbol] key The key to use to retrieve the value from the
318
+ # execution context.
319
+ # @param [Object,nil] accept An OptionParser acceptor. Optional.
320
+ # @param [Object] default The default value. This is the value that will
321
+ # be set in the context if this argument is not provided on the command
322
+ # line. Defaults to `nil`.
323
+ # @param [String,nil] doc The documentation for the argument, which appears
324
+ # in the usage documentation. Defaults to `nil` for no documentation.
325
+ #
326
+ def optional_arg(key, accept: nil, default: nil, doc: nil)
327
+ if @type == :append
328
+ raise ToolDefinitionError, "Cannot add an argument when appending"
329
+ end
330
+ @tool.add_optional_arg(key, accept: accept, default: default, doc: doc)
331
+ self
332
+ end
333
+
334
+ ##
335
+ # Specify what should be done with unmatched positional arguments. You must
336
+ # specify a key which the executor may use to obtain the remaining args
337
+ # from the context.
338
+ #
339
+ # @param [Symbol] key The key to use to retrieve the value from the
340
+ # execution context.
341
+ # @param [Object,nil] accept An OptionParser acceptor. Optional.
342
+ # @param [Object] default The default value. This is the value that will
343
+ # be set in the context if no unmatched arguments are provided on the
344
+ # command line. Defaults to the empty array `[]`.
345
+ # @param [String,nil] doc The documentation for the remaining arguments,
346
+ # which appears in the usage documentation. Defaults to `nil` for no
347
+ # documentation.
348
+ #
349
+ def remaining_args(key, accept: nil, default: [], doc: nil)
350
+ if @type == :append
351
+ raise ToolDefinitionError, "Cannot add an argument when appending"
352
+ end
353
+ @tool.set_remaining_args(key, accept: accept, default: default, doc: doc)
354
+ self
355
+ end
356
+
357
+ ##
358
+ # Specify the executor for this tool. This is a block that will be called,
359
+ # with `self` set to a {Toys::Context}.
360
+ #
361
+ def execute(&block)
362
+ if @type == :group || @type == :append
363
+ raise ToolDefinitionError, "Cannot set the executor of a group"
364
+ end
365
+ @tool.executor = block
366
+ self
367
+ end
368
+
369
+ ##
370
+ # Define a helper method that may be called from this tool's executor.
371
+ # You must provide a name for the method, and a block for the method
372
+ # definition.
373
+ #
374
+ # @param [String,Symbol] name Name of the method. May not begin with an
375
+ # underscore.
376
+ #
377
+ def helper(name, &block)
378
+ if @type == :group || @type == :append
379
+ raise ToolDefinitionError, "Cannot define a helper method to a group"
380
+ end
381
+ @tool.add_helper(name, &block)
382
+ self
383
+ end
384
+
385
+ ##
386
+ # Specify that the given module should be mixed in to this tool's executor.
387
+ # Effectively, the module is added to the {Toys::Context} object.
388
+ # You may either provide a module directly, or specify the name of a
389
+ # well-known module.
390
+ #
391
+ # @param [Module,Symbol] mod Module or name of well-known module.
392
+ #
393
+ def use(mod)
394
+ if @type == :group || @type == :append
395
+ raise ToolDefinitionError, "Cannot use a helper module in a group"
396
+ end
397
+ @tool.use_module(mod)
398
+ self
399
+ end
400
+
401
+ ## @private
402
+ def _binding
403
+ binding
404
+ end
405
+
406
+ ## @private
407
+ def self.evaluate(path, tool, remaining_words, priority, loader, type, source)
408
+ dsl = new(path, tool, remaining_words, priority, loader, type)
409
+ if type == :append
410
+ eval_source(dsl, path, source)
411
+ else
412
+ tool.defining_from(path) do
413
+ eval_source(dsl, path, source)
414
+ tool.finish_definition
415
+ end
416
+ end
417
+ tool
418
+ end
419
+
420
+ ## @private
421
+ def self.eval_source(dsl, path, source)
422
+ case source
423
+ when String
424
+ # rubocop:disable Security/Eval
425
+ eval(source, dsl._binding, path, 1)
426
+ # rubocop:enable Security/Eval
427
+ when ::Proc
428
+ dsl.instance_eval(&source)
429
+ end
430
+ end
431
+ end
432
+ end