toys-core 0.3.6 → 0.3.7

Sign up to get free protection for your applications and to get access to all the features.
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