toys-core 0.7.0 → 0.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (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