toys-core 0.7.0 → 0.8.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (59) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +98 -0
  3. data/LICENSE.md +16 -24
  4. data/README.md +307 -59
  5. data/docs/guide.md +44 -4
  6. data/lib/toys-core.rb +58 -49
  7. data/lib/toys/acceptor.rb +672 -0
  8. data/lib/toys/alias.rb +106 -0
  9. data/lib/toys/arg_parser.rb +624 -0
  10. data/lib/toys/cli.rb +422 -181
  11. data/lib/toys/compat.rb +83 -0
  12. data/lib/toys/completion.rb +442 -0
  13. data/lib/toys/context.rb +354 -0
  14. data/lib/toys/core_version.rb +18 -26
  15. data/lib/toys/dsl/flag.rb +213 -56
  16. data/lib/toys/dsl/flag_group.rb +237 -51
  17. data/lib/toys/dsl/positional_arg.rb +210 -0
  18. data/lib/toys/dsl/tool.rb +968 -317
  19. data/lib/toys/errors.rb +46 -28
  20. data/lib/toys/flag.rb +821 -0
  21. data/lib/toys/flag_group.rb +282 -0
  22. data/lib/toys/input_file.rb +18 -26
  23. data/lib/toys/loader.rb +110 -100
  24. data/lib/toys/middleware.rb +24 -31
  25. data/lib/toys/mixin.rb +90 -59
  26. data/lib/toys/module_lookup.rb +125 -0
  27. data/lib/toys/positional_arg.rb +184 -0
  28. data/lib/toys/source_info.rb +192 -0
  29. data/lib/toys/standard_middleware/add_verbosity_flags.rb +38 -43
  30. data/lib/toys/standard_middleware/handle_usage_errors.rb +39 -40
  31. data/lib/toys/standard_middleware/set_default_descriptions.rb +111 -89
  32. data/lib/toys/standard_middleware/show_help.rb +130 -113
  33. data/lib/toys/standard_middleware/show_root_version.rb +29 -35
  34. data/lib/toys/standard_mixins/exec.rb +116 -78
  35. data/lib/toys/standard_mixins/fileutils.rb +16 -24
  36. data/lib/toys/standard_mixins/gems.rb +29 -30
  37. data/lib/toys/standard_mixins/highline.rb +34 -41
  38. data/lib/toys/standard_mixins/terminal.rb +72 -26
  39. data/lib/toys/template.rb +51 -35
  40. data/lib/toys/tool.rb +1161 -206
  41. data/lib/toys/utils/completion_engine.rb +171 -0
  42. data/lib/toys/utils/exec.rb +279 -182
  43. data/lib/toys/utils/gems.rb +58 -49
  44. data/lib/toys/utils/help_text.rb +117 -111
  45. data/lib/toys/utils/terminal.rb +69 -62
  46. data/lib/toys/wrappable_string.rb +162 -0
  47. metadata +24 -22
  48. data/lib/toys/definition/acceptor.rb +0 -191
  49. data/lib/toys/definition/alias.rb +0 -112
  50. data/lib/toys/definition/arg.rb +0 -140
  51. data/lib/toys/definition/flag.rb +0 -370
  52. data/lib/toys/definition/flag_group.rb +0 -205
  53. data/lib/toys/definition/source_info.rb +0 -190
  54. data/lib/toys/definition/tool.rb +0 -842
  55. data/lib/toys/dsl/arg.rb +0 -132
  56. data/lib/toys/runner.rb +0 -188
  57. data/lib/toys/standard_middleware.rb +0 -47
  58. data/lib/toys/utils/module_lookup.rb +0 -135
  59. data/lib/toys/utils/wrappable_string.rb +0 -165
@@ -0,0 +1,354 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Copyright 2019 Daniel Azuma
4
+ #
5
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ # of this software and associated documentation files (the "Software"), to deal
7
+ # in the Software without restriction, including without limitation the rights
8
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ # copies of the Software, and to permit persons to whom the Software is
10
+ # furnished to do so, subject to the following conditions:
11
+ #
12
+ # The above copyright notice and this permission notice shall be included in
13
+ # all copies or substantial portions of the Software.
14
+ #
15
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
20
+ # FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
21
+ # IN THE SOFTWARE.
22
+ ;
23
+
24
+ require "logger"
25
+
26
+ module Toys
27
+ ##
28
+ # This is the base class for tool execution. It represents `self` when your
29
+ # tool's methods (such as `run`) are called, and it defines the methods that
30
+ # can be called by your tool (such as {#logger} and {#exit}.)
31
+ #
32
+ # This class also manages the "data" available to your tool when it runs.
33
+ # This data is a hash of key-value pairs. It consists of values set by flags
34
+ # and arguments defined by the tool, plus some "well-known" values such as
35
+ # the logger and verbosity level.
36
+ #
37
+ # You can obtain a value from the data using the {Toys::Context#get} method.
38
+ # Additionally, convenience methods are provided for many of the well-known
39
+ # keys. For instance, you can call {Toys::Context#verbosity} to obtain the
40
+ # value for the key {Toys::Context::Key::VERBOSITY}. Finally, flags and
41
+ # positional arguments that store their data here will also typically
42
+ # generate convenience methods. For example, an argument with key `:abc` will
43
+ # add a method called `abc` that you can call to get the value.
44
+ #
45
+ # By convention, flags and arguments defined by your tool should use strings
46
+ # or symbols as keys. Keys that are not strings or symbols should either be
47
+ # well-known keys such as {Toys::Context::Key::VERBOSITY}, or should be used
48
+ # for internal private information needed by middleware and mixins. The
49
+ # module {Toys::Context::Key} defines a number of well-known keys as
50
+ # constants.
51
+ #
52
+ class Context
53
+ ##
54
+ # Well-known context keys.
55
+ #
56
+ # This module is mixed into the runtime context. This means you can
57
+ # reference any of these constants directly from your run method.
58
+ #
59
+ # ## Example
60
+ #
61
+ # tool "my-name" do
62
+ # def run
63
+ # # TOOL_NAME is available here.
64
+ # puts "My name is #{get(TOOL_NAME)}"
65
+ # end
66
+ # end
67
+ #
68
+ module Key
69
+ ##
70
+ # Context key for the argument list passed to the current tool. Value is
71
+ # an array of strings.
72
+ # @return [Object]
73
+ #
74
+ ARGS = ::Object.new.freeze
75
+
76
+ ##
77
+ # Context key for the currently running {Toys::CLI}. You can use the
78
+ # value to run other tools from your tool by calling {Toys::CLI#run}.
79
+ # @return [Object]
80
+ #
81
+ CLI = ::Object.new.freeze
82
+
83
+ ##
84
+ # Context key for the context directory path. The value is a string
85
+ # @return [Object]
86
+ #
87
+ CONTEXT_DIRECTORY = ::Object.new.freeze
88
+
89
+ ##
90
+ # Context key for the active `Logger` object.
91
+ # @return [Object]
92
+ #
93
+ LOGGER = ::Object.new.freeze
94
+
95
+ ##
96
+ # Context key for the {Toys::Tool} object being executed.
97
+ # @return [Object]
98
+ #
99
+ TOOL = ::Object.new.freeze
100
+
101
+ ##
102
+ # Context key for the full name of the tool being executed. Value is an
103
+ # array of strings.
104
+ # @return [Object]
105
+ #
106
+ TOOL_NAME = ::Object.new.freeze
107
+
108
+ ##
109
+ # Context key for the {Toys::SourceInfo} describing the source of this
110
+ # tool.
111
+ # @return [Object]
112
+ #
113
+ TOOL_SOURCE = ::Object.new.freeze
114
+
115
+ ##
116
+ # Context key for all unmatched args in order. The value is an array of
117
+ # strings.
118
+ # @return [Object]
119
+ #
120
+ UNMATCHED_ARGS = ::Object.new.freeze
121
+
122
+ ##
123
+ # Context key for unmatched flags. The value is an array of strings.
124
+ # @return [Object]
125
+ #
126
+ UNMATCHED_FLAGS = ::Object.new.freeze
127
+
128
+ ##
129
+ # Context key for unmatched positional args. The value is an array of
130
+ # strings.
131
+ # @return [Object]
132
+ #
133
+ UNMATCHED_POSITIONAL = ::Object.new.freeze
134
+
135
+ ##
136
+ # Context key for the list of usage errors raised. The value is an array
137
+ # of {Toys::ArgParser::UsageError}.
138
+ # @return [Object]
139
+ #
140
+ USAGE_ERRORS = ::Object.new.freeze
141
+
142
+ ##
143
+ # Context key for the verbosity value. The value is an integer defaulting
144
+ # to 0, with higher values meaning more verbose and lower meaning more
145
+ # quiet.
146
+ # @return [Object]
147
+ #
148
+ VERBOSITY = ::Object.new.freeze
149
+ end
150
+
151
+ ##
152
+ # Create a Context object. Applications generally will not need to create
153
+ # these objects directly; they are created by the tool when it is preparing
154
+ # for execution.
155
+ #
156
+ # @private
157
+ #
158
+ # @param data [Hash]
159
+ #
160
+ def initialize(data)
161
+ @__data = data
162
+ end
163
+
164
+ ##
165
+ # The raw arguments passed to the tool, as an array of strings.
166
+ # This does not include the tool name itself.
167
+ #
168
+ # This is a convenience getter for {Toys::Context::Key::ARGS}.
169
+ #
170
+ # @return [Array<String>]
171
+ #
172
+ def args
173
+ @__data[Key::ARGS]
174
+ end
175
+
176
+ ##
177
+ # The currently running CLI.
178
+ #
179
+ # This is a convenience getter for {Toys::Context::Key::CLI}.
180
+ #
181
+ # @return [Toys::CLI]
182
+ #
183
+ def cli
184
+ @__data[Key::CLI]
185
+ end
186
+
187
+ ##
188
+ # Return the context directory for this tool. Generally, this defaults
189
+ # to the directory containing the toys config directory structure being
190
+ # read, but it may be changed by setting a different context directory
191
+ # for the tool.
192
+ #
193
+ # This is a convenience getter for {Toys::Context::Key::CONTEXT_DIRECTORY}.
194
+ #
195
+ # @return [String] Context directory path
196
+ # @return [nil] if there is no context.
197
+ #
198
+ def context_directory
199
+ @__data[Key::CONTEXT_DIRECTORY]
200
+ end
201
+
202
+ ##
203
+ # The logger for this execution.
204
+ #
205
+ # This is a convenience getter for {Toys::Context::Key::LOGGER}.
206
+ #
207
+ # @return [Logger]
208
+ #
209
+ def logger
210
+ @__data[Key::LOGGER]
211
+ end
212
+
213
+ ##
214
+ # The full name of the tool being executed, as an array of strings.
215
+ #
216
+ # This is a convenience getter for {Toys::Context::Key::TOOL_NAME}.
217
+ #
218
+ # @return [Array<String>]
219
+ #
220
+ def tool_name
221
+ @__data[Key::TOOL_NAME]
222
+ end
223
+
224
+ ##
225
+ # The source of the tool being executed.
226
+ #
227
+ # This is a convenience getter for {Toys::Context::Key::TOOL_SOURCE}.
228
+ #
229
+ # @return [Toys::SourceInfo]
230
+ #
231
+ def tool_source
232
+ @__data[Key::TOOL_SOURCE]
233
+ end
234
+
235
+ ##
236
+ # The (possibly empty) array of errors detected during argument parsing.
237
+ #
238
+ # This is a convenience getter for {Toys::Context::Key::USAGE_ERRORS}.
239
+ #
240
+ # @return [Array<Toys::ArgParser::UsageError>]
241
+ #
242
+ def usage_errors
243
+ @__data[Key::USAGE_ERRORS]
244
+ end
245
+
246
+ ##
247
+ # The current verbosity setting as an integer.
248
+ #
249
+ # This is a convenience getter for {Toys::Context::Key::VERBOSITY}.
250
+ #
251
+ # @return [Integer]
252
+ #
253
+ def verbosity
254
+ @__data[Key::VERBOSITY]
255
+ end
256
+
257
+ ##
258
+ # Fetch an option or other piece of data by key.
259
+ #
260
+ # @param key [Symbol]
261
+ # @return [Object]
262
+ #
263
+ def [](key)
264
+ @__data[key]
265
+ end
266
+ alias get []
267
+ alias __get []
268
+
269
+ ##
270
+ # Set an option or other piece of context data by key.
271
+ #
272
+ # @param key [Symbol]
273
+ # @param value [Object]
274
+ #
275
+ def []=(key, value)
276
+ @__data[key] = value
277
+ end
278
+
279
+ ##
280
+ # Set one or more options or other context data by key.
281
+ #
282
+ # @return [self]
283
+ #
284
+ # @overload set(key, value)
285
+ # Set an option or other piece of context data by key.
286
+ # @param key [Symbol]
287
+ # @param value [Object]
288
+ # @return [self]
289
+ #
290
+ # @overload set(hash)
291
+ # Set multiple content data keys and values
292
+ # @param hash [Hash] The keys and values to set
293
+ # @return [self]
294
+ #
295
+ def set(key, value = nil)
296
+ if key.is_a?(::Hash)
297
+ @__data.merge!(key)
298
+ else
299
+ @__data[key] = value
300
+ end
301
+ self
302
+ end
303
+
304
+ ##
305
+ # The subset of the context that uses string or symbol keys. By convention,
306
+ # this includes keys that are set by tool flags and arguments, but does not
307
+ # include well-known context values such as verbosity or private context
308
+ # values used by middleware or mixins.
309
+ #
310
+ # @return [Hash]
311
+ #
312
+ def options
313
+ @__data.select do |k, _v|
314
+ k.is_a?(::Symbol) || k.is_a?(::String)
315
+ end
316
+ end
317
+
318
+ ##
319
+ # Find the given data file or directory in this tool's search path.
320
+ #
321
+ # @param path [String] The path to find
322
+ # @param type [nil,:file,:directory] Type of file system object to find,
323
+ # or nil to return any type.
324
+ #
325
+ # @return [String] Absolute path of the result
326
+ # @return [nil] if the data was not found.
327
+ #
328
+ def find_data(path, type: nil)
329
+ @__data[Key::TOOL_SOURCE].find_data(path, type: type)
330
+ end
331
+
332
+ ##
333
+ # Exit immediately with the given status code
334
+ #
335
+ # @param code [Integer] The status code, which should be 0 for no error,
336
+ # or nonzero for an error condition. Default is 0.
337
+ # @return [void]
338
+ #
339
+ def exit(code = 0)
340
+ throw :result, code
341
+ end
342
+
343
+ ##
344
+ # Exit immediately with the given status code
345
+ #
346
+ # @param code [Integer] The status code, which should be 0 for no error,
347
+ # or nonzero for an error condition. Default is 0.
348
+ # @return [void]
349
+ #
350
+ def self.exit(code = 0)
351
+ throw :result, code
352
+ end
353
+ end
354
+ end
@@ -1,38 +1,30 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- # Copyright 2018 Daniel Azuma
3
+ # Copyright 2019 Daniel Azuma
4
4
  #
5
- # All rights reserved.
5
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ # of this software and associated documentation files (the "Software"), to deal
7
+ # in the Software without restriction, including without limitation the rights
8
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ # copies of the Software, and to permit persons to whom the Software is
10
+ # furnished to do so, subject to the following conditions:
6
11
  #
7
- # Redistribution and use in source and binary forms, with or without
8
- # modification, are permitted provided that the following conditions are met:
12
+ # The above copyright notice and this permission notice shall be included in
13
+ # all copies or substantial portions of the Software.
9
14
  #
10
- # * Redistributions of source code must retain the above copyright notice,
11
- # this list of conditions and the following disclaimer.
12
- # * Redistributions in binary form must reproduce the above copyright notice,
13
- # this list of conditions and the following disclaimer in the documentation
14
- # and/or other materials provided with the distribution.
15
- # * Neither the name of the copyright holder, nor the names of any other
16
- # contributors to this software, may be used to endorse or promote products
17
- # derived from this software without specific prior written permission.
18
- #
19
- # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
20
- # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
21
- # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
22
- # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
23
- # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
24
- # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
25
- # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
26
- # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
27
- # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
28
- # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
29
- # POSSIBILITY OF SUCH DAMAGE.
15
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
20
+ # FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
21
+ # IN THE SOFTWARE.
30
22
  ;
31
23
 
32
24
  module Toys
33
25
  ##
34
- # Current version of Toys core
26
+ # Current version of Toys core.
35
27
  # @return [String]
36
28
  #
37
- CORE_VERSION = "0.7.0"
29
+ CORE_VERSION = "0.8.0"
38
30
  end
@@ -1,32 +1,24 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- # Copyright 2018 Daniel Azuma
3
+ # Copyright 2019 Daniel Azuma
4
4
  #
5
- # All rights reserved.
5
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ # of this software and associated documentation files (the "Software"), to deal
7
+ # in the Software without restriction, including without limitation the rights
8
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ # copies of the Software, and to permit persons to whom the Software is
10
+ # furnished to do so, subject to the following conditions:
6
11
  #
7
- # Redistribution and use in source and binary forms, with or without
8
- # modification, are permitted provided that the following conditions are met:
12
+ # The above copyright notice and this permission notice shall be included in
13
+ # all copies or substantial portions of the Software.
9
14
  #
10
- # * Redistributions of source code must retain the above copyright notice,
11
- # this list of conditions and the following disclaimer.
12
- # * Redistributions in binary form must reproduce the above copyright notice,
13
- # this list of conditions and the following disclaimer in the documentation
14
- # and/or other materials provided with the distribution.
15
- # * Neither the name of the copyright holder, nor the names of any other
16
- # contributors to this software, may be used to endorse or promote products
17
- # derived from this software without specific prior written permission.
18
- #
19
- # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
20
- # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
21
- # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
22
- # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
23
- # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
24
- # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
25
- # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
26
- # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
27
- # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
28
- # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
29
- # POSSIBILITY OF SUCH DAMAGE.
15
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
20
+ # FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
21
+ # IN THE SOFTWARE.
30
22
  ;
31
23
 
32
24
  module Toys
@@ -38,12 +30,23 @@ module Toys
38
30
  # These directives are available inside a block passed to
39
31
  # {Toys::DSL::Tool#flag}.
40
32
  #
33
+ # ## Example
34
+ #
35
+ # tool "mytool" do
36
+ # flag :value do
37
+ # # The directives in here are defined by this class
38
+ # flags "--value=VAL"
39
+ # accept Integer
40
+ # desc "An integer value"
41
+ # end
42
+ # # ...
43
+ # end
44
+ #
41
45
  class Flag
42
46
  ## @private
43
- def initialize(flags, accept, default, handler, report_collisions,
44
- group, desc, long_desc, display_name)
47
+ def initialize(flags, acceptor, default, handler, flag_completion, value_completion,
48
+ report_collisions, group, desc, long_desc, display_name)
45
49
  @flags = flags
46
- @accept = accept
47
50
  @default = default
48
51
  @handler = handler
49
52
  @report_collisions = report_collisions
@@ -51,36 +54,98 @@ module Toys
51
54
  @desc = desc
52
55
  @long_desc = long_desc || []
53
56
  @display_name = display_name
57
+ accept(acceptor)
58
+ complete_flags(flag_completion)
59
+ complete_values(value_completion)
54
60
  end
55
61
 
56
62
  ##
57
63
  # Add flags in OptionParser format. This may be called multiple times,
58
64
  # and the results are cumulative.
59
65
  #
60
- # @param [String...] flags
61
- # @return [Toys::DSL::Flag] self, for chaining.
66
+ # Following are examples of valid syntax.
67
+ #
68
+ # * `-a` : A short boolean switch. When this appears as an argument,
69
+ # the value is set to `true`.
70
+ # * `--abc` : A long boolean switch. When this appears as an argument,
71
+ # the value is set to `true`.
72
+ # * `-aVAL` or `-a VAL` : A short flag that takes a required value.
73
+ # These two forms are treated identically. If this argument appears
74
+ # with a value attached (e.g. `-afoo`), the attached string (e.g.
75
+ # `"foo"`) is taken as the value. Otherwise, the following argument
76
+ # is taken as the value (e.g. for `-a foo`, the value is set to
77
+ # `"foo"`.) The following argument is treated as the value even if it
78
+ # looks like a flag (e.g. `-a -a` causes the string `"-a"` to be
79
+ # taken as the value.)
80
+ # * `-a[VAL]` : A short flag that takes an optional value. If this
81
+ # argument appears with a value attached (e.g. `-afoo`), the attached
82
+ # string (e.g. `"foo"`) is taken as the value. Otherwise, the value
83
+ # is set to `true`. The following argument is never interpreted as
84
+ # the value. (Compare with `-a [VAL]`.)
85
+ # * `-a [VAL]` : A short flag that takes an optional value. If this
86
+ # argument appears with a value attached (e.g. `-afoo`), the attached
87
+ # string (e.g. `"foo"`) is taken as the value. Otherwise, if the
88
+ # following argument does not look like a flag (i.e. it does not
89
+ # begin with a hyphen), it is taken as the value. (e.g. `-a foo`
90
+ # causes the string `"foo"` to be taken as the value.). If there is
91
+ # no following argument, or the following argument looks like a flag,
92
+ # the value is set to `true`. (Compare with `-a[VAL]`.)
93
+ # * `--abc=VAL` or `--abc VAL` : A long flag that takes a required
94
+ # value. These two forms are treated identically. If this argument
95
+ # appears with a value attached (e.g. `--abc=foo`), the attached
96
+ # string (e.g. `"foo"`) is taken as the value. Otherwise, the
97
+ # following argument is taken as the value (e.g. for `--abc foo`, the
98
+ # value is set to `"foo"`.) The following argument is treated as the
99
+ # value even if it looks like a flag (e.g. `--abc --abc` causes the
100
+ # string `"--abc"` to be taken as the value.)
101
+ # * `--abc[=VAL]` : A long flag that takes an optional value. If this
102
+ # argument appears with a value attached (e.g. `--abc=foo`), the
103
+ # attached string (e.g. `"foo"`) is taken as the value. Otherwise,
104
+ # the value is set to `true`. The following argument is never
105
+ # interpreted as the value. (Compare with `--abc [VAL]`.)
106
+ # * `--abc [VAL]` : A long flag that takes an optional value. If this
107
+ # argument appears with a value attached (e.g. `--abc=foo`), the
108
+ # attached string (e.g. `"foo"`) is taken as the value. Otherwise, if
109
+ # the following argument does not look like a flag (i.e. it does not
110
+ # begin with a hyphen), it is taken as the value. (e.g. `--abc foo`
111
+ # causes the string `"foo"` to be taken as the value.). If there is
112
+ # no following argument, or the following argument looks like a flag,
113
+ # the value is set to `true`. (Compare with `--abc=[VAL]`.)
114
+ # * `--[no-]abc` : A long boolean switch that can be turned either on
115
+ # or off. This effectively creates two flags, `--abc` which sets the
116
+ # value to `true`, and `--no-abc` which sets the falue to `false`.
117
+ #
118
+ # @param flags [String...]
119
+ # @return [self]
62
120
  #
63
121
  def flags(*flags)
64
- @flags += flags
122
+ @flags += flags.flatten
65
123
  self
66
124
  end
67
125
 
68
126
  ##
69
- # Set the OptionParser acceptor.
127
+ # Set the acceptor for this flag's values.
128
+ # You can pass either the string name of an acceptor defined in this tool
129
+ # or any of its ancestors, or any other specification recognized by
130
+ # {Toys::Acceptor.create}.
70
131
  #
71
- # @param [Object] accept
72
- # @return [Toys::DSL::Flag] self, for chaining.
132
+ # @param spec [Object]
133
+ # @param options [Hash]
134
+ # @param block [Proc]
135
+ # @return [self]
73
136
  #
74
- def accept(accept)
75
- @accept = accept
137
+ def accept(spec = nil, **options, &block)
138
+ @acceptor_spec = spec
139
+ @acceptor_options = options
140
+ @acceptor_block = block
76
141
  self
77
142
  end
78
143
 
79
144
  ##
80
145
  # Set the default value.
81
146
  #
82
- # @param [Object] default
83
- # @return [Toys::DSL::Flag] self, for chaining.
147
+ # @param default [Object]
148
+ # @return [self]
84
149
  #
85
150
  def default(default)
86
151
  @default = default
@@ -94,20 +159,63 @@ module Toys
94
159
  # should be set. You may pass the handler as a Proc (or an object
95
160
  # responding to the `call` method) or you may pass a block.
96
161
  #
97
- # @param [Proc] handler
98
- # @return [Toys::DSL::Flag] self, for chaining.
162
+ # @param handler [Proc]
163
+ # @param block [Proc]
164
+ # @return [self]
99
165
  #
100
166
  def handler(handler = nil, &block)
101
167
  @handler = handler || block
102
168
  self
103
169
  end
104
170
 
171
+ ##
172
+ # Set the shell completion strategy for flag names.
173
+ # You can pass one of the following:
174
+ #
175
+ # * The string name of a completion defined in this tool or any of its
176
+ # ancestors.
177
+ # * A hash of options to pass to the constructor of
178
+ # {Toys::Flag::DefaultCompletion}.
179
+ # * `nil` or `:default` to select the standard completion strategy
180
+ # (which is {Toys::Flag::DefaultCompletion} with no extra options).
181
+ # * Any other specification recognized by {Toys::Completion.create}.
182
+ #
183
+ # @param spec [Object]
184
+ # @param options [Hash]
185
+ # @param block [Proc]
186
+ # @return [self]
187
+ #
188
+ def complete_flags(spec = nil, **options, &block)
189
+ @flag_completion_spec = spec
190
+ @flag_completion_options = options
191
+ @flag_completion_block = block
192
+ self
193
+ end
194
+
195
+ ##
196
+ # Set the shell completion strategy for flag values.
197
+ # You can pass either the string name of a completion defined in this
198
+ # tool or any of its ancestors, or any other specification recognized by
199
+ # {Toys::Completion.create}.
200
+ #
201
+ # @param spec [Object]
202
+ # @param options [Hash]
203
+ # @param block [Proc]
204
+ # @return [self]
205
+ #
206
+ def complete_values(spec = nil, **options, &block)
207
+ @value_completion_spec = spec
208
+ @value_completion_options = options
209
+ @value_completion_block = block
210
+ self
211
+ end
212
+
105
213
  ##
106
214
  # Set whether to raise an exception if a flag is requested that is
107
215
  # already in use or marked as disabled.
108
216
  #
109
- # @param [Boolean] setting
110
- # @return [Toys::DSL::Flag] self, for chaining.
217
+ # @param setting [Boolean]
218
+ # @return [self]
111
219
  #
112
220
  def report_collisions(setting)
113
221
  @report_collisions = setting
@@ -115,11 +223,36 @@ module Toys
115
223
  end
116
224
 
117
225
  ##
118
- # Set the short description. See {Toys::DSL::Tool#desc} for the allowed
119
- # formats.
226
+ # Set the short description for the current flag. The short description
227
+ # is displayed with the flag in online help.
228
+ #
229
+ # The description is a {Toys::WrappableString}, which may be word-wrapped
230
+ # when displayed in a help screen. You may pass a {Toys::WrappableString}
231
+ # directly to this method, or you may pass any input that can be used to
232
+ # construct a wrappable string:
233
+ #
234
+ # * If you pass a String, its whitespace will be compacted (i.e. tabs,
235
+ # newlines, and multiple consecutive whitespace will be turned into a
236
+ # single space), and it will be word-wrapped on whitespace.
237
+ # * If you pass an Array of Strings, each string will be considered a
238
+ # literal word that cannot be broken, and wrapping will be done
239
+ # across the strings in the array. In this case, whitespace is not
240
+ # compacted.
241
+ #
242
+ # ## Examples
120
243
  #
121
- # @param [String,Array<String>,Toys::Utils::WrappableString] desc
122
- # @return [Toys::DSL::Flag] self, for chaining.
244
+ # If you pass in a sentence as a simple string, it may be word wrapped
245
+ # when displayed:
246
+ #
247
+ # desc "This sentence may be wrapped."
248
+ #
249
+ # To specify a sentence that should never be word-wrapped, pass it as the
250
+ # sole element of a string array:
251
+ #
252
+ # desc ["This sentence will not be wrapped."]
253
+ #
254
+ # @param desc [String,Array<String>,Toys::WrappableString]
255
+ # @return [self]
123
256
  #
124
257
  def desc(desc)
125
258
  @desc = desc
@@ -127,12 +260,27 @@ module Toys
127
260
  end
128
261
 
129
262
  ##
130
- # Adds to the long description. This may be called multiple times, and
131
- # the results are cumulative. See {Toys::DSL::Tool#long_desc} for the
132
- # allowed formats.
263
+ # Add to the long description for the current flag. The long description
264
+ # is displayed with the flag in online help. This directive may be given
265
+ # multiple times, and the results are cumulative.
266
+ #
267
+ # A long description is a series of descriptions, which are generally
268
+ # displayed in a series of lines/paragraphs. Each individual description
269
+ # uses the form described in the {#desc} documentation, and may be
270
+ # word-wrapped when displayed. To insert a blank line, include an empty
271
+ # string as one of the descriptions.
272
+ #
273
+ # ## Example
274
+ #
275
+ # long_desc "This initial paragraph might get word wrapped.",
276
+ # "This next paragraph is followed by a blank line.",
277
+ # "",
278
+ # ["This line will not be wrapped."],
279
+ # [" This indent is preserved."]
280
+ # long_desc "This line is appended to the description."
133
281
  #
134
- # @param [String,Array<String>,Toys::Utils::WrappableString...] long_desc
135
- # @return [Toys::DSL::Flag] self, for chaining.
282
+ # @param long_desc [String,Array<String>,Toys::WrappableString...]
283
+ # @return [self]
136
284
  #
137
285
  def long_desc(*long_desc)
138
286
  @long_desc += long_desc
@@ -143,8 +291,8 @@ module Toys
143
291
  # Set the group. A group may be set by name or group object. Setting
144
292
  # `nil` selects the default group.
145
293
  #
146
- # @param [String,Symbol,Toys::Definition::FlagGroup,nil] group
147
- # @return [Toys::DSL::Flag] self, for chaining.
294
+ # @param group [String,Symbol,Toys::FlagGroup,nil]
295
+ # @return [self]
148
296
  #
149
297
  def group(group)
150
298
  @group = group
@@ -152,10 +300,11 @@ module Toys
152
300
  end
153
301
 
154
302
  ##
155
- # Set the display name. This may be used in help text and error messages.
303
+ # Set the display name for this flag. This may be used in help text and
304
+ # error messages.
156
305
  #
157
- # @param [String] display_name
158
- # @return [Toys::DSL::Flag] self, for chaining.
306
+ # @param display_name [String]
307
+ # @return [self]
159
308
  #
160
309
  def display_name(display_name)
161
310
  @display_name = display_name
@@ -164,8 +313,16 @@ module Toys
164
313
 
165
314
  ## @private
166
315
  def _add_to(tool, key)
316
+ acceptor = tool.scalar_acceptor(@acceptor_spec, @acceptor_options, &@acceptor_block)
317
+ flag_completion = tool.scalar_completion(
318
+ @flag_completion_spec, @flag_completion_options, &@flag_completion_block
319
+ )
320
+ value_completion = tool.scalar_completion(
321
+ @value_completion_spec, @value_completion_options, &@value_completion_block
322
+ )
167
323
  tool.add_flag(key, @flags,
168
- accept: @accept, default: @default, handler: @handler,
324
+ accept: acceptor, default: @default, handler: @handler,
325
+ complete_flags: flag_completion, complete_values: value_completion,
169
326
  report_collisions: @report_collisions, group: @group,
170
327
  desc: @desc, long_desc: @long_desc, display_name: @display_name)
171
328
  end