toys-core 0.3.6 → 0.3.7

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 (46) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +13 -0
  3. data/lib/toys-core.rb +20 -5
  4. data/lib/toys/cli.rb +39 -32
  5. data/lib/toys/core_version.rb +1 -1
  6. data/lib/toys/{tool → definition}/acceptor.rb +21 -15
  7. data/lib/toys/{utils/line_output.rb → definition/alias.rb} +47 -59
  8. data/lib/toys/{tool/arg_definition.rb → definition/arg.rb} +17 -7
  9. data/lib/toys/{tool/flag_definition.rb → definition/flag.rb} +19 -9
  10. data/lib/toys/definition/tool.rb +574 -0
  11. data/lib/toys/dsl/arg.rb +118 -0
  12. data/lib/toys/dsl/flag.rb +132 -0
  13. data/lib/toys/dsl/tool.rb +521 -0
  14. data/lib/toys/errors.rb +2 -2
  15. data/lib/toys/helpers.rb +3 -3
  16. data/lib/toys/helpers/exec.rb +31 -25
  17. data/lib/toys/helpers/fileutils.rb +8 -2
  18. data/lib/toys/helpers/highline.rb +8 -1
  19. data/lib/toys/{alias.rb → helpers/terminal.rb} +44 -53
  20. data/lib/toys/input_file.rb +61 -0
  21. data/lib/toys/loader.rb +87 -77
  22. data/lib/toys/middleware.rb +3 -3
  23. data/lib/toys/middleware/add_verbosity_flags.rb +22 -20
  24. data/lib/toys/middleware/base.rb +53 -5
  25. data/lib/toys/middleware/handle_usage_errors.rb +9 -12
  26. data/lib/toys/middleware/set_default_descriptions.rb +6 -7
  27. data/lib/toys/middleware/show_help.rb +71 -67
  28. data/lib/toys/middleware/show_root_version.rb +9 -9
  29. data/lib/toys/runner.rb +157 -0
  30. data/lib/toys/template.rb +4 -3
  31. data/lib/toys/templates.rb +2 -2
  32. data/lib/toys/templates/clean.rb +2 -2
  33. data/lib/toys/templates/gem_build.rb +5 -5
  34. data/lib/toys/templates/minitest.rb +2 -2
  35. data/lib/toys/templates/rubocop.rb +2 -2
  36. data/lib/toys/templates/yardoc.rb +2 -2
  37. data/lib/toys/tool.rb +168 -625
  38. data/lib/toys/utils/exec.rb +19 -18
  39. data/lib/toys/utils/gems.rb +140 -0
  40. data/lib/toys/utils/help_text.rb +25 -20
  41. data/lib/toys/utils/terminal.rb +412 -0
  42. data/lib/toys/utils/wrappable_string.rb +3 -1
  43. metadata +15 -24
  44. data/lib/toys/config_dsl.rb +0 -699
  45. data/lib/toys/context.rb +0 -290
  46. data/lib/toys/helpers/spinner.rb +0 -142
@@ -0,0 +1,118 @@
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
+ module DSL
32
+ ##
33
+ # DSL for an arg definition block. Lets you set arg attributes in a block
34
+ # instead of a long series of keyword arguments.
35
+ #
36
+ class Arg
37
+ ## @private
38
+ def initialize(accept, default, display_name, desc, long_desc)
39
+ @accept = accept
40
+ @default = default
41
+ @display_name = display_name
42
+ @desc = desc
43
+ @long_desc = long_desc
44
+ end
45
+
46
+ ##
47
+ # Set the OptionParser acceptor.
48
+ # @param [Object] accept
49
+ #
50
+ def accept(accept)
51
+ @accept = accept
52
+ self
53
+ end
54
+
55
+ ##
56
+ # Set the default value.
57
+ # @param [Object] default
58
+ #
59
+ def default(default)
60
+ @default = default
61
+ self
62
+ end
63
+
64
+ ##
65
+ # Set the name of this arg as it appears in help screens.
66
+ # @param [String] display_name
67
+ #
68
+ def display_name(display_name)
69
+ @handler = display_name
70
+ self
71
+ end
72
+
73
+ ##
74
+ # Set the short description. See {Toys::DSL::Tool#desc} for the allowed
75
+ # formats.
76
+ #
77
+ # @param [String,Array<String>,Toys::Utils::WrappableString] desc
78
+ #
79
+ def desc(desc)
80
+ @desc = desc
81
+ self
82
+ end
83
+
84
+ ##
85
+ # Adds to the long description. This may be called multiple times, and
86
+ # the results are cumulative. See {Toys::DSL::Tool#long_desc} for the
87
+ # allowed formats.
88
+ #
89
+ # @param [String,Array<String>,Toys::Utils::WrappableString...] long_desc
90
+ #
91
+ def long_desc(*long_desc)
92
+ @long_desc += long_desc
93
+ self
94
+ end
95
+
96
+ ## @private
97
+ def _add_required_to(tool, key)
98
+ tool.add_required_arg(key,
99
+ accept: @accept, display_name: @display_name,
100
+ desc: @desc, long_desc: @long_desc)
101
+ end
102
+
103
+ ## @private
104
+ def _add_optional_to(tool, key)
105
+ tool.add_optional_arg(key,
106
+ accept: @accept, default: @default, display_name: @display_name,
107
+ desc: @desc, long_desc: @long_desc)
108
+ end
109
+
110
+ ## @private
111
+ def _set_remaining_on(tool, key)
112
+ tool.set_remaining_args(key,
113
+ accept: @accept, default: @default, display_name: @display_name,
114
+ desc: @desc, long_desc: @long_desc)
115
+ end
116
+ end
117
+ end
118
+ end
@@ -0,0 +1,132 @@
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
+ module DSL
32
+ ##
33
+ # DSL for a flag definition block. Lets you set flag attributes in a block
34
+ # instead of a long series of keyword arguments.
35
+ #
36
+ class Flag
37
+ ## @private
38
+ def initialize(flags, accept, default, handler, report_collisions, desc, long_desc)
39
+ @flags = flags
40
+ @accept = accept
41
+ @default = default
42
+ @handler = handler
43
+ @report_collisions = report_collisions
44
+ @desc = desc
45
+ @long_desc = long_desc
46
+ end
47
+
48
+ ##
49
+ # Add flags in OptionParser format. This may be called multiple times,
50
+ # and the results are cumulative.
51
+ # @param [String...] flags
52
+ #
53
+ def flags(*flags)
54
+ @flags += flags
55
+ self
56
+ end
57
+
58
+ ##
59
+ # Set the OptionParser acceptor.
60
+ # @param [Object] accept
61
+ #
62
+ def accept(accept)
63
+ @accept = accept
64
+ self
65
+ end
66
+
67
+ ##
68
+ # Set the default value.
69
+ # @param [Object] default
70
+ #
71
+ def default(default)
72
+ @default = default
73
+ self
74
+ end
75
+
76
+ ##
77
+ # Set the optional handler for setting/updating the value when a flag is
78
+ # parsed. It should be a Proc taking two arguments, the new given value
79
+ # and the previous value, and it should return the new value that should
80
+ # be set.
81
+ #
82
+ # @param [Proc] handler
83
+ #
84
+ def handler(handler)
85
+ @handler = handler
86
+ self
87
+ end
88
+
89
+ ##
90
+ # Set whether to raise an exception if a flag is requested that is
91
+ # already in use or marked as disabled.
92
+ #
93
+ # @param [Boolean] setting
94
+ #
95
+ def report_collisions(setting)
96
+ @report_collisions = setting
97
+ self
98
+ end
99
+
100
+ ##
101
+ # Set the short description. See {Toys::DSL::Tool#desc} for the allowed
102
+ # formats.
103
+ #
104
+ # @param [String,Array<String>,Toys::Utils::WrappableString] desc
105
+ #
106
+ def desc(desc)
107
+ @desc = desc
108
+ self
109
+ end
110
+
111
+ ##
112
+ # Adds to the long description. This may be called multiple times, and
113
+ # the results are cumulative. See {Toys::DSL::Tool#long_desc} for the
114
+ # allowed formats.
115
+ #
116
+ # @param [String,Array<String>,Toys::Utils::WrappableString...] long_desc
117
+ #
118
+ def long_desc(*long_desc)
119
+ @long_desc += long_desc
120
+ self
121
+ end
122
+
123
+ ## @private
124
+ def _add_to(tool, key)
125
+ tool.add_flag(key, @flags,
126
+ accept: @accept, default: @default, handler: @handler,
127
+ report_collisions: @report_collisions,
128
+ desc: @desc, long_desc: @long_desc)
129
+ end
130
+ end
131
+ end
132
+ end
@@ -0,0 +1,521 @@
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
+ module DSL
32
+ ##
33
+ # This class defines the DSL for a toys configuration file.
34
+ #
35
+ # A toys configuration defines one or more named tools. It provides syntax
36
+ # for setting the description, defining flags and arguments, specifying
37
+ # how to execute the tool, and requesting helper modules and other services.
38
+ # It also lets you define subtools, nested arbitrarily deep, using blocks.
39
+ #
40
+ # Generally the DSL is invoked from the {Loader}. Applications should not
41
+ # need to create instances of DSL::Tool directly.
42
+ #
43
+ # ## Simple example
44
+ #
45
+ # Create a file called `.toys.rb` in the current directory, with the
46
+ # following contents:
47
+ #
48
+ # tool "greet" do
49
+ # desc "Prints a simple greeting"
50
+ #
51
+ # optional_arg :recipient, default: "world"
52
+ #
53
+ # script do
54
+ # puts "Hello, #{self[:recipient]}!"
55
+ # end
56
+ # end
57
+ #
58
+ # Now you can execute it using:
59
+ #
60
+ # toys greet
61
+ #
62
+ # or try:
63
+ #
64
+ # toys greet rubyists
65
+ #
66
+ module Tool
67
+ ## @private
68
+ def method_added(meth)
69
+ cur_tool = DSL::Tool.activate_tool(self)
70
+ cur_tool.mark_runnable if cur_tool && meth == :run
71
+ end
72
+
73
+ ##
74
+ # Create an acceptor that can be passed into a flag or arg. An acceptor
75
+ # validates and/or converts a string parameter to a Ruby object. This
76
+ # acceptor may, for the current tool, be referenced by the name you provide
77
+ # when you create a flag or arg.
78
+ #
79
+ # An acceptor contains a validator, which parses and validates the string
80
+ # syntax of an argument, and a converter, which takes the validation
81
+ # results and returns a final value for the context data.
82
+ #
83
+ # The validator may be either a regular expression or a list of valid
84
+ # inputs.
85
+ #
86
+ # If the validator is a regular expression, it is matched against the
87
+ # argument string and succeeds only if the expression covers the *entire*
88
+ # string. The elements of the MatchData (i.e. the string matched, plus any
89
+ # captures) are then passed into the conversion function.
90
+ #
91
+ # If the validator is an array, the *string form* of the array elements
92
+ # (i.e. the results of calling to_s on each element) are considered the
93
+ # valid values for the argument. This is useful for enums, for example.
94
+ # In this case, the input is converted to the original array element, and
95
+ # any converter function you provide is ignored.
96
+ #
97
+ # If you provide no validator, then no validation takes place and all
98
+ # argument strings are considered valid. The string itself is passed on to
99
+ # the converter.
100
+ #
101
+ # The converter should be a proc that takes as its arguments the results
102
+ # of validation. For example, if you use a regular expression validator,
103
+ # the converter should take a series of strings arguments, the first of
104
+ # which is the full input string, and the rest of which are captures.
105
+ # If you provide no converter, no conversion is done and the input string
106
+ # is considered the final value. You may also provide the converter as a
107
+ # block.
108
+ #
109
+ # @param [String] name The acceptor name.
110
+ # @param [Regexp,Array,nil] validator The validator.
111
+ # @param [Proc,nil] converter The validator.
112
+ #
113
+ def acceptor(name, validator = nil, converter = nil, &block)
114
+ cur_tool = DSL::Tool.activate_tool(self)
115
+ return self if cur_tool.nil?
116
+ accept =
117
+ case validator
118
+ when ::Regexp
119
+ Definition::PatternAcceptor.new(name, validator, converter, &block)
120
+ when ::Array
121
+ Definition::EnumAcceptor.new(name, validator)
122
+ when nil
123
+ Definition::Acceptor.new(name, converter, &block)
124
+ else
125
+ raise ToolDefinitionError, "Illegal validator: #{validator.inspect}"
126
+ end
127
+ cur_tool.add_acceptor(accept)
128
+ self
129
+ end
130
+
131
+ ##
132
+ # Create a named helper module.
133
+ # This module may be included by name in this tool or any subtool.
134
+ #
135
+ # You should pass a block and define methods in that block.
136
+ #
137
+ # @param [String] name Name of the helper
138
+ #
139
+ def helper(name, &block)
140
+ cur_tool = DSL::Tool.activate_tool(self)
141
+ cur_tool.add_helper(name, ::Module.new(&block)) if cur_tool
142
+ self
143
+ end
144
+
145
+ ##
146
+ # Create a subtool. You must provide a block defining the subtool.
147
+ #
148
+ # If the subtool is already defined (either as a tool or a namespace), the
149
+ # old definition is discarded and replaced with the new definition.
150
+ #
151
+ # @param [String] word The name of the subtool
152
+ #
153
+ def tool(word, &block)
154
+ word = word.to_s
155
+ subtool_words = @__words + [word]
156
+ next_remaining = Loader.next_remaining_words(@__remaining_words, word)
157
+ subtool_class = @__loader.get_tool_definition(subtool_words, @__priority).tool_class
158
+ DSL::Tool.prepare(subtool_class, next_remaining, @__path) do
159
+ subtool_class.class_eval(&block)
160
+ end
161
+ self
162
+ end
163
+ alias name tool
164
+
165
+ ##
166
+ # Create an alias in the current namespace.
167
+ #
168
+ # @param [String] word The name of the alias
169
+ # @param [String] target The target of the alias
170
+ #
171
+ def alias_tool(word, target)
172
+ @__loader.make_alias(@__words + [word.to_s], @__words + [target.to_s], @__priority)
173
+ self
174
+ end
175
+
176
+ ##
177
+ # Create an alias of the current tool.
178
+ #
179
+ # @param [String] word The name of the alias
180
+ #
181
+ def alias_as(word)
182
+ if @__words.empty?
183
+ raise ToolDefinitionError, "Cannot make an alias of the root."
184
+ end
185
+ @__loader.make_alias(@__words[0..-2] + [word.to_s], @__words, @__priority)
186
+ self
187
+ end
188
+
189
+ ##
190
+ # Include another config file or directory at the current location.
191
+ #
192
+ # @param [String] path The file or directory to include.
193
+ #
194
+ def load(path)
195
+ @__loader.include_path(path, @__words, @__remaining_words, @__priority)
196
+ self
197
+ end
198
+
199
+ ##
200
+ # Expand the given template in the current location.
201
+ #
202
+ # The template may be specified as a class or a well-known template name.
203
+ # You may also provide arguments to pass to the template.
204
+ #
205
+ # @param [Class,String,Symbol] template_class The template, either as a
206
+ # class or a well-known name.
207
+ # @param [Object...] args Template arguments
208
+ #
209
+ def expand(template_class, *args)
210
+ unless template_class.is_a?(::Class)
211
+ name = template_class.to_s
212
+ template_class = Templates.lookup!(name)
213
+ end
214
+ template = template_class.new(*args)
215
+ yield template if block_given?
216
+ class_exec(template, &template_class.expander)
217
+ self
218
+ end
219
+
220
+ ##
221
+ # Set the short description for the current tool. The short description is
222
+ # displayed with the tool in a subtool list. You may also use the
223
+ # equivalent method `short_desc`.
224
+ #
225
+ # The description is a {Toys::Utils::WrappableString}, which may be word-
226
+ # wrapped when displayed in a help screen. You may pass a
227
+ # {Toys::Utils::WrappableString} directly to this method, or you may pass
228
+ # any input that can be used to construct a wrappable string:
229
+ #
230
+ # * If you pass a String, its whitespace will be compacted (i.e. tabs,
231
+ # newlines, and multiple consecutive whitespace will be turned into a
232
+ # single space), and it will be word-wrapped on whitespace.
233
+ # * If you pass an Array of Strings, each string will be considered a
234
+ # literal word that cannot be broken, and wrapping will be done across
235
+ # the strings in the array. In this case, whitespace is not compacted.
236
+ #
237
+ # For example, if you pass in a sentence as a simple string, it may be
238
+ # word wrapped when displayed:
239
+ #
240
+ # desc "This sentence may be wrapped."
241
+ #
242
+ # To specify a sentence that should never be word-wrapped, pass it as the
243
+ # sole element of a string array:
244
+ #
245
+ # desc ["This sentence will not be wrapped."]
246
+ #
247
+ # @param [Toys::Utils::WrappableString,String,Array<String>] str
248
+ #
249
+ def desc(str)
250
+ cur_tool = DSL::Tool.activate_tool(self)
251
+ cur_tool.desc = str if cur_tool
252
+ self
253
+ end
254
+ alias short_desc desc
255
+
256
+ ##
257
+ # Set the long description for the current tool. The long description is
258
+ # displayed in the usage documentation for the tool itself.
259
+ #
260
+ # A long description is a series of descriptions, which are generally
261
+ # displayed in a series of lines/paragraphs. Each individual description
262
+ # uses the form described in the {Toys::DSL::Tool#desc} documentation, and
263
+ # may be word-wrapped when displayed. To insert a blank line, include an
264
+ # empty string as one of the descriptions.
265
+ #
266
+ # Example:
267
+ #
268
+ # long_desc "This is an initial paragraph that might be word wrapped.",
269
+ # "This next paragraph is followed by a blank line.",
270
+ # "",
271
+ # ["This line will not be wrapped."]
272
+ #
273
+ # @param [Toys::Utils::WrappableString,String,Array<String>...] strs
274
+ #
275
+ def long_desc(*strs)
276
+ cur_tool = DSL::Tool.activate_tool(self)
277
+ cur_tool.long_desc = strs if cur_tool
278
+ self
279
+ end
280
+
281
+ ##
282
+ # Add a flag to the current tool. Each flag must specify a key which
283
+ # the script may use to obtain the flag value from the context.
284
+ # You may then provide the flags themselves in `OptionParser` form.
285
+ #
286
+ # Attributes of the flag may be passed in as arguments to this method, or
287
+ # set in a block passed to this method.
288
+ #
289
+ # @param [Symbol] key The key to use to retrieve the value from the
290
+ # execution context.
291
+ # @param [String...] flags The flags in OptionParser format.
292
+ # @param [Object] accept An acceptor that validates and/or converts the
293
+ # value. You may provide either the name of an acceptor you have
294
+ # defined, or one of the default acceptors provided by OptionParser.
295
+ # Optional. If not specified, accepts any value as a string.
296
+ # @param [Object] default The default value. This is the value that will
297
+ # be set in the context if this flag is not provided on the command
298
+ # line. Defaults to `nil`.
299
+ # @param [Proc,nil] handler An optional handler for setting/updating the
300
+ # value. If given, it should take two arguments, the new given value
301
+ # and the previous value, and it should return the new value that
302
+ # should be set. The default handler simply replaces the previous
303
+ # value. i.e. the default is effectively `-> (val, _prev) { val }`.
304
+ # @param [Boolean] report_collisions Raise an exception if a flag is
305
+ # requested that is already in use or marked as unusable. Default is
306
+ # true.
307
+ # @param [String,Array<String>,Toys::Utils::WrappableString] desc Short
308
+ # description for the flag. See {Toys::DSL::Tool#desc} for a
309
+ # description of the allowed formats. Defaults to the empty string.
310
+ # @param [Array<String,Array<String>,Toys::Utils::WrappableString>] long_desc
311
+ # Long description for the flag. See {Toys::DSL::Tool#long_desc} for
312
+ # a description of the allowed formats. (But note that this param
313
+ # takes an Array of description lines, rather than a series of
314
+ # arguments.) Defaults to the empty array.
315
+ # @yieldparam flag_dsl [Toys::DSL::Flag] An object that lets you
316
+ # configure this flag in a block.
317
+ #
318
+ def flag(key, *flags,
319
+ accept: nil, default: nil, handler: nil,
320
+ report_collisions: true,
321
+ desc: nil, long_desc: nil)
322
+ cur_tool = DSL::Tool.activate_tool(self)
323
+ return self if cur_tool.nil?
324
+ flag_dsl = DSL::Flag.new(flags, accept, default, handler, report_collisions,
325
+ desc, long_desc)
326
+ yield flag_dsl if block_given?
327
+ flag_dsl._add_to(cur_tool, key)
328
+ self
329
+ end
330
+
331
+ ##
332
+ # Add a required positional argument to the current tool. You must specify
333
+ # a key which the script may use to obtain the argument value from the
334
+ # context.
335
+ #
336
+ # @param [Symbol] key The key to use to retrieve the value from the
337
+ # execution context.
338
+ # @param [Object] accept An acceptor that validates and/or converts the
339
+ # value. You may provide either the name of an acceptor you have
340
+ # defined, or one of the default acceptors provided by OptionParser.
341
+ # Optional. If not specified, accepts any value as a string.
342
+ # @param [String] display_name A name to use for display (in help text and
343
+ # error reports). Defaults to the key in upper case.
344
+ # @param [String,Array<String>,Toys::Utils::WrappableString] desc Short
345
+ # description for the flag. See {Toys::DSL::Tool#desc} for a
346
+ # description of the allowed formats. Defaults to the empty string.
347
+ # @param [Array<String,Array<String>,Toys::Utils::WrappableString>] long_desc
348
+ # Long description for the flag. See {Toys::DSL::Tool#long_desc} for
349
+ # a description of the allowed formats. (But note that this param
350
+ # takes an Array of description lines, rather than a series of
351
+ # arguments.) Defaults to the empty array.
352
+ # @yieldparam arg_dsl [Toys::DSL::Arg] An object that lets you configure
353
+ # this argument in a block.
354
+ #
355
+ def required_arg(key, accept: nil, display_name: nil, desc: nil, long_desc: nil)
356
+ cur_tool = DSL::Tool.activate_tool(self)
357
+ return self if cur_tool.nil?
358
+ arg_dsl = DSL::Arg.new(accept, nil, display_name, desc, long_desc)
359
+ yield arg_dsl if block_given?
360
+ arg_dsl._add_required_to(cur_tool, key)
361
+ self
362
+ end
363
+ alias required required_arg
364
+
365
+ ##
366
+ # Add an optional positional argument to the current tool. You must specify
367
+ # a key which the script may use to obtain the argument value from the
368
+ # context. If an optional argument is not given on the command line, the
369
+ # value is set to the given default.
370
+ #
371
+ # @param [Symbol] key The key to use to retrieve the value from the
372
+ # execution context.
373
+ # @param [Object] default The default value. This is the value that will
374
+ # be set in the context if this argument is not provided on the command
375
+ # line. Defaults to `nil`.
376
+ # @param [Object] accept An acceptor that validates and/or converts the
377
+ # value. You may provide either the name of an acceptor you have
378
+ # defined, or one of the default acceptors provided by OptionParser.
379
+ # Optional. If not specified, accepts any value as a string.
380
+ # @param [String] display_name A name to use for display (in help text and
381
+ # error reports). Defaults to the key in upper case.
382
+ # @param [String,Array<String>,Toys::Utils::WrappableString] desc Short
383
+ # description for the flag. See {Toys::DSL::Tool#desc} for a
384
+ # description of the allowed formats. Defaults to the empty string.
385
+ # @param [Array<String,Array<String>,Toys::Utils::WrappableString>] long_desc
386
+ # Long description for the flag. See {Toys::DSL::Tool#long_desc} for
387
+ # a description of the allowed formats. (But note that this param
388
+ # takes an Array of description lines, rather than a series of
389
+ # arguments.) Defaults to the empty array.
390
+ # @yieldparam arg_dsl [Toys::DSL::Arg] An object that lets you configure
391
+ # this argument in a block.
392
+ #
393
+ def optional_arg(key, default: nil, accept: nil, display_name: nil,
394
+ desc: nil, long_desc: nil)
395
+ cur_tool = DSL::Tool.activate_tool(self)
396
+ return self if cur_tool.nil?
397
+ arg_dsl = DSL::Arg.new(accept, default, display_name, desc, long_desc)
398
+ yield arg_dsl if block_given?
399
+ arg_dsl._add_optional_to(cur_tool, key)
400
+ self
401
+ end
402
+ alias optional optional_arg
403
+
404
+ ##
405
+ # Specify what should be done with unmatched positional arguments. You must
406
+ # specify a key which the script may use to obtain the remaining args from
407
+ # the context.
408
+ #
409
+ # @param [Symbol] key The key to use to retrieve the value from the
410
+ # execution context.
411
+ # @param [Object] default The default value. This is the value that will
412
+ # be set in the context if no unmatched arguments are provided on the
413
+ # command line. Defaults to the empty array `[]`.
414
+ # @param [Object] accept An acceptor that validates and/or converts the
415
+ # value. You may provide either the name of an acceptor you have
416
+ # defined, or one of the default acceptors provided by OptionParser.
417
+ # Optional. If not specified, accepts any value as a string.
418
+ # @param [String] display_name A name to use for display (in help text and
419
+ # error reports). Defaults to the key in upper case.
420
+ # @param [String,Array<String>,Toys::Utils::WrappableString] desc Short
421
+ # description for the flag. See {Toys::DSL::Tool#desc} for a
422
+ # description of the allowed formats. Defaults to the empty string.
423
+ # @param [Array<String,Array<String>,Toys::Utils::WrappableString>] long_desc
424
+ # Long description for the flag. See {Toys::DSL::Tool#long_desc} for
425
+ # a description of the allowed formats. (But note that this param
426
+ # takes an Array of description lines, rather than a series of
427
+ # arguments.) Defaults to the empty array.
428
+ # @yieldparam arg_dsl [Toys::DSL::Arg] An object that lets you configure
429
+ # this argument in a block.
430
+ #
431
+ def remaining_args(key, default: [], accept: nil, display_name: nil,
432
+ desc: nil, long_desc: nil)
433
+ cur_tool = DSL::Tool.activate_tool(self)
434
+ return self if cur_tool.nil?
435
+ arg_dsl = DSL::Arg.new(accept, default, display_name, desc, long_desc)
436
+ yield arg_dsl if block_given?
437
+ arg_dsl._set_remaining_on(cur_tool, key)
438
+ self
439
+ end
440
+ alias remaining remaining_args
441
+
442
+ ##
443
+ # Specify how to run this tool. You may do this by providing a block to
444
+ # this directive, or by defining the `run` method in the tool.
445
+ #
446
+ def run(&block)
447
+ define_method(:run, &block)
448
+ self
449
+ end
450
+
451
+ ##
452
+ # Specify that the given module should be mixed into this tool, and its
453
+ # methods made available when running the tool.
454
+ #
455
+ # You may provide either a module, the string name of a helper that you
456
+ # have defined in this tool or one of its ancestors, or the symbol name
457
+ # of a well-known helper.
458
+ #
459
+ # @param [Module,Symbol,String] mod Module or module name.
460
+ #
461
+ def include(mod)
462
+ cur_tool = DSL::Tool.activate_tool(self)
463
+ return if cur_tool.nil?
464
+ name = mod.to_s
465
+ if mod.is_a?(::String)
466
+ mod = cur_tool.resolve_helper(mod)
467
+ elsif mod.is_a?(::Symbol)
468
+ mod = Helpers.lookup!(name)
469
+ end
470
+ if mod.nil?
471
+ raise ToolDefinitionError, "Module not found: #{name.inspect}"
472
+ end
473
+ super(mod)
474
+ end
475
+
476
+ ## @private
477
+ def self.new_class(words, priority, loader)
478
+ tool_class = ::Class.new(::Toys::Tool)
479
+ tool_class.extend(DSL::Tool)
480
+ tool_class.instance_variable_set(:@__words, words)
481
+ tool_class.instance_variable_set(:@__priority, priority)
482
+ tool_class.instance_variable_set(:@__loader, loader)
483
+ tool_class.instance_variable_set(:@__remaining_words, nil)
484
+ tool_class.instance_variable_set(:@__path, nil)
485
+ tool_class
486
+ end
487
+
488
+ ## @private
489
+ def self.activate_tool(tool_class)
490
+ path = tool_class.instance_variable_get(:@__path)
491
+ cur_tool =
492
+ if tool_class.instance_variable_defined?(:@__cur_tool)
493
+ tool_class.instance_variable_get(:@__cur_tool)
494
+ else
495
+ loader = tool_class.instance_variable_get(:@__loader)
496
+ words = tool_class.instance_variable_get(:@__words)
497
+ priority = tool_class.instance_variable_get(:@__priority)
498
+ cur_tool = loader.activate_tool_definition(words, priority)
499
+ if cur_tool.is_a?(Definition::Alias)
500
+ raise ToolDefinitionError,
501
+ "Cannot configure #{words.join(' ').inspect} because it is an alias"
502
+ end
503
+ tool_class.instance_variable_set(:@__cur_tool, cur_tool)
504
+ cur_tool
505
+ end
506
+ cur_tool.lock_source_path(path) if cur_tool
507
+ cur_tool
508
+ end
509
+
510
+ ## @private
511
+ def self.prepare(tool_class, remaining_words, path)
512
+ tool_class.instance_variable_set(:@__remaining_words, remaining_words)
513
+ tool_class.instance_variable_set(:@__path, path)
514
+ yield
515
+ ensure
516
+ tool_class.instance_variable_set(:@__remaining_words, nil)
517
+ tool_class.instance_variable_set(:@__path, nil)
518
+ end
519
+ end
520
+ end
521
+ end