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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +98 -0
- data/LICENSE.md +16 -24
- data/README.md +307 -59
- data/docs/guide.md +44 -4
- data/lib/toys-core.rb +58 -49
- data/lib/toys/acceptor.rb +672 -0
- data/lib/toys/alias.rb +106 -0
- data/lib/toys/arg_parser.rb +624 -0
- data/lib/toys/cli.rb +422 -181
- data/lib/toys/compat.rb +83 -0
- data/lib/toys/completion.rb +442 -0
- data/lib/toys/context.rb +354 -0
- data/lib/toys/core_version.rb +18 -26
- data/lib/toys/dsl/flag.rb +213 -56
- data/lib/toys/dsl/flag_group.rb +237 -51
- data/lib/toys/dsl/positional_arg.rb +210 -0
- data/lib/toys/dsl/tool.rb +968 -317
- data/lib/toys/errors.rb +46 -28
- data/lib/toys/flag.rb +821 -0
- data/lib/toys/flag_group.rb +282 -0
- data/lib/toys/input_file.rb +18 -26
- data/lib/toys/loader.rb +110 -100
- data/lib/toys/middleware.rb +24 -31
- data/lib/toys/mixin.rb +90 -59
- data/lib/toys/module_lookup.rb +125 -0
- data/lib/toys/positional_arg.rb +184 -0
- data/lib/toys/source_info.rb +192 -0
- data/lib/toys/standard_middleware/add_verbosity_flags.rb +38 -43
- data/lib/toys/standard_middleware/handle_usage_errors.rb +39 -40
- data/lib/toys/standard_middleware/set_default_descriptions.rb +111 -89
- data/lib/toys/standard_middleware/show_help.rb +130 -113
- data/lib/toys/standard_middleware/show_root_version.rb +29 -35
- data/lib/toys/standard_mixins/exec.rb +116 -78
- data/lib/toys/standard_mixins/fileutils.rb +16 -24
- data/lib/toys/standard_mixins/gems.rb +29 -30
- data/lib/toys/standard_mixins/highline.rb +34 -41
- data/lib/toys/standard_mixins/terminal.rb +72 -26
- data/lib/toys/template.rb +51 -35
- data/lib/toys/tool.rb +1161 -206
- data/lib/toys/utils/completion_engine.rb +171 -0
- data/lib/toys/utils/exec.rb +279 -182
- data/lib/toys/utils/gems.rb +58 -49
- data/lib/toys/utils/help_text.rb +117 -111
- data/lib/toys/utils/terminal.rb +69 -62
- data/lib/toys/wrappable_string.rb +162 -0
- metadata +24 -22
- data/lib/toys/definition/acceptor.rb +0 -191
- data/lib/toys/definition/alias.rb +0 -112
- data/lib/toys/definition/arg.rb +0 -140
- data/lib/toys/definition/flag.rb +0 -370
- data/lib/toys/definition/flag_group.rb +0 -205
- data/lib/toys/definition/source_info.rb +0 -190
- data/lib/toys/definition/tool.rb +0 -842
- data/lib/toys/dsl/arg.rb +0 -132
- data/lib/toys/runner.rb +0 -188
- data/lib/toys/standard_middleware.rb +0 -47
- data/lib/toys/utils/module_lookup.rb +0 -135
- data/lib/toys/utils/wrappable_string.rb +0 -165
data/lib/toys/tool.rb
CHANGED
@@ -1,322 +1,1277 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
# Copyright
|
3
|
+
# Copyright 2019 Daniel Azuma
|
4
4
|
#
|
5
|
-
#
|
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
|
-
#
|
8
|
-
#
|
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
|
-
#
|
11
|
-
#
|
12
|
-
#
|
13
|
-
#
|
14
|
-
#
|
15
|
-
#
|
16
|
-
#
|
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
|
-
require "
|
24
|
+
require "set"
|
33
25
|
|
34
26
|
module Toys
|
35
27
|
##
|
36
|
-
#
|
37
|
-
#
|
38
|
-
#
|
39
|
-
#
|
40
|
-
#
|
41
|
-
#
|
42
|
-
# Keys that are neither strings nor symbols are by convention used for other
|
43
|
-
# context information, including:
|
44
|
-
#
|
45
|
-
# * Common information such as the {Toys::Definition::Tool} object being
|
46
|
-
# executed, the arguments originally passed to it, or the usage error
|
47
|
-
# string. These well-known keys can be accessed via constants in the
|
48
|
-
# {Toys::Tool::Keys} module.
|
49
|
-
# * Common settings such as the verbosity level, and whether to exit
|
50
|
-
# immediately if a subprocess exits with a nonzero result. These keys are
|
51
|
-
# also present as {Toys::Context} constants.
|
52
|
-
# * Private information used internally by middleware and mixins.
|
53
|
-
#
|
54
|
-
# This class provides convenience accessors for common keys and settings, and
|
55
|
-
# you can retrieve argument-set keys using the {#options} hash.
|
28
|
+
# A Tool describes a single command that can be invoked using Toys.
|
29
|
+
# It has a name, a series of one or more words that you use to identify
|
30
|
+
# the tool on the command line. It also has a set of formal flags and
|
31
|
+
# command line arguments supported, and a block that gets run when the
|
32
|
+
# tool is executed.
|
56
33
|
#
|
57
34
|
class Tool
|
58
35
|
##
|
59
|
-
#
|
36
|
+
# Create a new tool.
|
37
|
+
# Should be created only from the DSL via the Loader.
|
38
|
+
# @private
|
60
39
|
#
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
CLI = ::Object.new.freeze
|
40
|
+
def initialize(loader, parent, full_name, priority, middleware_stack)
|
41
|
+
@parent = parent
|
42
|
+
@full_name = full_name.dup.freeze
|
43
|
+
@priority = priority
|
44
|
+
@middleware_stack = middleware_stack
|
67
45
|
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
#
|
73
|
-
VERBOSITY = ::Object.new.freeze
|
46
|
+
@acceptors = {}
|
47
|
+
@mixins = {}
|
48
|
+
@templates = {}
|
49
|
+
@completions = {}
|
74
50
|
|
75
|
-
|
76
|
-
|
77
|
-
# @return [Object]
|
78
|
-
#
|
79
|
-
TOOL_DEFINITION = ::Object.new.freeze
|
51
|
+
reset_definition(loader)
|
52
|
+
end
|
80
53
|
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
54
|
+
##
|
55
|
+
# Reset the definition of this tool, deleting all definition data but
|
56
|
+
# leaving named acceptors, mixins, and templates intact.
|
57
|
+
# Should be called only from the DSL.
|
58
|
+
# @private
|
59
|
+
#
|
60
|
+
def reset_definition(loader)
|
61
|
+
@tool_class = DSL::Tool.new_class(@full_name, @priority, loader)
|
87
62
|
|
88
|
-
|
89
|
-
|
90
|
-
# array of strings.
|
91
|
-
# @return [Object]
|
92
|
-
#
|
93
|
-
TOOL_NAME = ::Object.new.freeze
|
63
|
+
@source_info = nil
|
64
|
+
@definition_finished = false
|
94
65
|
|
95
|
-
|
96
|
-
|
97
|
-
# @return [Object]
|
98
|
-
#
|
99
|
-
LOADER = ::Object.new.freeze
|
66
|
+
@desc = WrappableString.new("")
|
67
|
+
@long_desc = []
|
100
68
|
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
#
|
105
|
-
LOGGER = ::Object.new.freeze
|
69
|
+
@default_data = {}
|
70
|
+
@used_flags = []
|
71
|
+
@initializers = []
|
106
72
|
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
#
|
111
|
-
BINARY_NAME = ::Object.new.freeze
|
73
|
+
default_flag_group = FlagGroup::Base.new(nil, nil, nil)
|
74
|
+
@flag_groups = [default_flag_group]
|
75
|
+
@flag_group_names = {nil => default_flag_group}
|
112
76
|
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
#
|
118
|
-
ARGS = ::Object.new.freeze
|
77
|
+
@flags = []
|
78
|
+
@required_args = []
|
79
|
+
@optional_args = []
|
80
|
+
@remaining_arg = nil
|
119
81
|
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
82
|
+
@disable_argument_parsing = false
|
83
|
+
@enforce_flags_before_args = false
|
84
|
+
@require_exact_flag_match = false
|
85
|
+
@includes_modules = false
|
86
|
+
@custom_context_directory = nil
|
87
|
+
|
88
|
+
@interrupt_handler = nil
|
89
|
+
@usage_error_handler = nil
|
90
|
+
|
91
|
+
@completion = DefaultCompletion.new
|
126
92
|
end
|
127
93
|
|
128
94
|
##
|
129
|
-
#
|
130
|
-
#
|
131
|
-
#
|
132
|
-
# @
|
95
|
+
# The name of the tool as an array of strings.
|
96
|
+
# This array may not be modified.
|
97
|
+
#
|
98
|
+
# @return [Array<String>]
|
99
|
+
#
|
100
|
+
attr_reader :full_name
|
101
|
+
|
102
|
+
##
|
103
|
+
# The priority of this tool definition.
|
104
|
+
#
|
105
|
+
# @return [Integer]
|
106
|
+
#
|
107
|
+
attr_reader :priority
|
108
|
+
|
109
|
+
##
|
110
|
+
# The tool class.
|
111
|
+
#
|
112
|
+
# @return [Class]
|
113
|
+
#
|
114
|
+
attr_reader :tool_class
|
115
|
+
|
116
|
+
##
|
117
|
+
# The short description string.
|
118
|
+
#
|
119
|
+
# When reading, this is always returned as a {Toys::WrappableString}.
|
120
|
+
#
|
121
|
+
# When setting, the description may be provided as any of the following:
|
122
|
+
# * A {Toys::WrappableString}.
|
123
|
+
# * A normal String, which will be transformed into a
|
124
|
+
# {Toys::WrappableString} using spaces as word delimiters.
|
125
|
+
# * An Array of String, which will be transformed into a
|
126
|
+
# {Toys::WrappableString} where each array element represents an
|
127
|
+
# individual word for wrapping.
|
128
|
+
#
|
129
|
+
# @return [Toys::WrappableString]
|
130
|
+
#
|
131
|
+
attr_reader :desc
|
132
|
+
|
133
|
+
##
|
134
|
+
# The long description strings.
|
135
|
+
#
|
136
|
+
# When reading, this is returned as an Array of {Toys::WrappableString}
|
137
|
+
# representing the lines in the description.
|
138
|
+
#
|
139
|
+
# When setting, the description must be provided as an Array where *each
|
140
|
+
# element* may be any of the following:
|
141
|
+
# * A {Toys::WrappableString} representing one line.
|
142
|
+
# * A normal String representing a line. This will be transformed into a
|
143
|
+
# {Toys::WrappableString} using spaces as word delimiters.
|
144
|
+
# * An Array of String representing a line. This will be transformed into
|
145
|
+
# a {Toys::WrappableString} where each array element represents an
|
146
|
+
# individual word for wrapping.
|
147
|
+
#
|
148
|
+
# @return [Array<Toys::WrappableString>]
|
149
|
+
#
|
150
|
+
attr_reader :long_desc
|
151
|
+
|
152
|
+
##
|
153
|
+
# A list of all defined flag groups, in order.
|
154
|
+
#
|
155
|
+
# @return [Array<Toys::FlagGroup>]
|
156
|
+
#
|
157
|
+
attr_reader :flag_groups
|
158
|
+
|
159
|
+
##
|
160
|
+
# A list of all defined flags.
|
161
|
+
#
|
162
|
+
# @return [Array<Toys::Flag>]
|
163
|
+
#
|
164
|
+
attr_reader :flags
|
165
|
+
|
166
|
+
##
|
167
|
+
# A list of all defined required positional arguments.
|
168
|
+
#
|
169
|
+
# @return [Array<Toys::PositionalArg>]
|
170
|
+
#
|
171
|
+
attr_reader :required_args
|
172
|
+
|
173
|
+
##
|
174
|
+
# A list of all defined optional positional arguments.
|
175
|
+
#
|
176
|
+
# @return [Array<Toys::PositionalArg>]
|
177
|
+
#
|
178
|
+
attr_reader :optional_args
|
179
|
+
|
180
|
+
##
|
181
|
+
# The remaining arguments specification.
|
182
|
+
#
|
183
|
+
# @return [Toys::PositionalArg] The argument definition
|
184
|
+
# @return [nil] if remaining arguments are not supported by this tool.
|
185
|
+
#
|
186
|
+
attr_reader :remaining_arg
|
187
|
+
|
188
|
+
##
|
189
|
+
# A list of flags that have been used in the flag definitions.
|
190
|
+
#
|
191
|
+
# @return [Array<String>]
|
192
|
+
#
|
193
|
+
attr_reader :used_flags
|
194
|
+
|
195
|
+
##
|
196
|
+
# The default context data set by arguments.
|
197
|
+
#
|
198
|
+
# @return [Hash]
|
199
|
+
#
|
200
|
+
attr_reader :default_data
|
201
|
+
|
202
|
+
##
|
203
|
+
# The middleware stack active for this tool.
|
204
|
+
#
|
205
|
+
# @return [Array<Toys::Middleware>]
|
206
|
+
#
|
207
|
+
attr_reader :middleware_stack
|
208
|
+
|
209
|
+
##
|
210
|
+
# Info on the source of this tool.
|
211
|
+
#
|
212
|
+
# @return [Toys::SourceInfo] The source info
|
213
|
+
# @return [nil] if the source is not defined.
|
214
|
+
#
|
215
|
+
attr_reader :source_info
|
216
|
+
|
217
|
+
##
|
218
|
+
# The custom context directory set for this tool.
|
219
|
+
#
|
220
|
+
# @return [String] The directory path
|
221
|
+
# @return [nil] if no custom context directory is set.
|
222
|
+
#
|
223
|
+
attr_reader :custom_context_directory
|
224
|
+
|
225
|
+
##
|
226
|
+
# The completion strategy for this tool.
|
227
|
+
#
|
228
|
+
# When reading, this may return an instance of one of the subclasses of
|
229
|
+
# {Toys::Completion::Base}, or a Proc that duck-types it. Generally, this
|
230
|
+
# defaults to a {Toys::Tool::DefaultCompletion}, providing a standard
|
231
|
+
# algorithm that finds appropriate completions from flags, positional
|
232
|
+
# arguments, and subtools.
|
233
|
+
#
|
234
|
+
# When setting, you may pass any of the following:
|
235
|
+
# * `nil` or `:default` which sets the value to a default instance.
|
236
|
+
# * A Hash of options to pass to the {Toys::Tool::DefaultCompletion}
|
237
|
+
# constructor.
|
238
|
+
# * Any other form recognized by {Toys::Completion.create}.
|
239
|
+
#
|
240
|
+
# @return [Toys::Completion::Base,Proc]
|
241
|
+
#
|
242
|
+
attr_reader :completion
|
243
|
+
|
244
|
+
##
|
245
|
+
# The interrupt handler.
|
246
|
+
#
|
247
|
+
# @return [Proc] The interrupt handler proc
|
248
|
+
# @return [Symbol] The name of a method to call
|
249
|
+
# @return [nil] if there is no interrupt handler
|
133
250
|
#
|
134
|
-
|
135
|
-
|
251
|
+
attr_reader :interrupt_handler
|
252
|
+
|
253
|
+
##
|
254
|
+
# The usage error handler.
|
255
|
+
#
|
256
|
+
# @return [Proc] The usage error handler proc
|
257
|
+
# @return [Symbol] The name of a method to call
|
258
|
+
# @return [nil] if there is no usage error handler
|
259
|
+
#
|
260
|
+
attr_reader :usage_error_handler
|
261
|
+
|
262
|
+
##
|
263
|
+
# The local name of this tool, i.e. the last element of the full name.
|
136
264
|
#
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
@__data[Keys::BINARY_NAME] = cli.binary_name
|
142
|
-
@__data[Keys::LOGGER] = cli.logger
|
265
|
+
# @return [String]
|
266
|
+
#
|
267
|
+
def simple_name
|
268
|
+
full_name.last
|
143
269
|
end
|
144
270
|
|
145
271
|
##
|
146
|
-
#
|
147
|
-
#
|
272
|
+
# A displayable name of this tool, generally the full name delimited by
|
273
|
+
# spaces.
|
274
|
+
#
|
275
|
+
# @return [String]
|
148
276
|
#
|
149
|
-
def
|
150
|
-
|
277
|
+
def display_name
|
278
|
+
full_name.join(" ")
|
151
279
|
end
|
152
280
|
|
153
281
|
##
|
154
|
-
#
|
155
|
-
# @return [
|
282
|
+
# Returns true if this tool is a root tool.
|
283
|
+
# @return [Boolean]
|
156
284
|
#
|
157
|
-
def
|
158
|
-
|
285
|
+
def root?
|
286
|
+
full_name.empty?
|
159
287
|
end
|
160
288
|
|
161
289
|
##
|
162
|
-
#
|
163
|
-
# @return [
|
290
|
+
# Returns true if this tool is marked as runnable.
|
291
|
+
# @return [Boolean]
|
164
292
|
#
|
165
|
-
def
|
166
|
-
|
293
|
+
def runnable?
|
294
|
+
tool_class.public_instance_methods(false).include?(:run)
|
167
295
|
end
|
168
296
|
|
169
297
|
##
|
170
|
-
#
|
171
|
-
# @return [
|
298
|
+
# Returns true if this tool handles interrupts.
|
299
|
+
# @return [Boolean]
|
172
300
|
#
|
173
|
-
def
|
174
|
-
|
301
|
+
def handles_interrupts?
|
302
|
+
!interrupt_handler.nil?
|
175
303
|
end
|
176
304
|
|
177
305
|
##
|
178
|
-
#
|
179
|
-
# @return [
|
306
|
+
# Returns true if this tool handles usage errors.
|
307
|
+
# @return [Boolean]
|
180
308
|
#
|
181
|
-
def
|
182
|
-
|
309
|
+
def handles_usage_errors?
|
310
|
+
!usage_error_handler.nil?
|
183
311
|
end
|
184
312
|
|
185
313
|
##
|
186
|
-
#
|
187
|
-
#
|
188
|
-
# @return [Array[String]]
|
314
|
+
# Returns true if this tool has at least one included module.
|
315
|
+
# @return [Boolean]
|
189
316
|
#
|
190
|
-
def
|
191
|
-
@
|
317
|
+
def includes_modules?
|
318
|
+
@includes_modules
|
192
319
|
end
|
193
320
|
|
194
321
|
##
|
195
|
-
#
|
196
|
-
#
|
197
|
-
# @return [String,nil]
|
322
|
+
# Returns true if there is a specific description set for this tool.
|
323
|
+
# @return [Boolean]
|
198
324
|
#
|
199
|
-
def
|
200
|
-
|
325
|
+
def includes_description?
|
326
|
+
!long_desc.empty? || !desc.empty?
|
201
327
|
end
|
202
328
|
|
203
329
|
##
|
204
|
-
#
|
205
|
-
#
|
330
|
+
# Returns true if at least one flag or positional argument is defined
|
331
|
+
# for this tool.
|
332
|
+
# @return [Boolean]
|
206
333
|
#
|
207
|
-
def
|
208
|
-
|
334
|
+
def includes_arguments?
|
335
|
+
!default_data.empty? || !flags.empty? ||
|
336
|
+
!required_args.empty? || !optional_args.empty? ||
|
337
|
+
!remaining_arg.nil? || flags_before_args_enforced?
|
209
338
|
end
|
210
339
|
|
211
340
|
##
|
212
|
-
#
|
213
|
-
# @return [
|
341
|
+
# Returns true if this tool has any definition information.
|
342
|
+
# @return [Boolean]
|
214
343
|
#
|
215
|
-
def
|
216
|
-
|
344
|
+
def includes_definition?
|
345
|
+
includes_arguments? || runnable? || argument_parsing_disabled? ||
|
346
|
+
includes_modules? || includes_description?
|
217
347
|
end
|
218
348
|
|
219
349
|
##
|
220
|
-
#
|
221
|
-
# @return [
|
350
|
+
# Returns true if this tool's definition has been finished and is locked.
|
351
|
+
# @return [Boolean]
|
352
|
+
#
|
353
|
+
def definition_finished?
|
354
|
+
@definition_finished
|
355
|
+
end
|
356
|
+
|
357
|
+
##
|
358
|
+
# Returns true if this tool has disabled argument parsing.
|
359
|
+
# @return [Boolean]
|
360
|
+
#
|
361
|
+
def argument_parsing_disabled?
|
362
|
+
@disable_argument_parsing
|
363
|
+
end
|
364
|
+
|
365
|
+
##
|
366
|
+
# Returns true if this tool enforces flags before args.
|
367
|
+
# @return [Boolean]
|
368
|
+
#
|
369
|
+
def flags_before_args_enforced?
|
370
|
+
@enforce_flags_before_args
|
371
|
+
end
|
372
|
+
|
373
|
+
##
|
374
|
+
# Returns true if this tool requires exact flag matches.
|
375
|
+
# @return [Boolean]
|
376
|
+
#
|
377
|
+
def exact_flag_match_required?
|
378
|
+
@require_exact_flag_match
|
379
|
+
end
|
380
|
+
|
381
|
+
##
|
382
|
+
# All arg definitions in order: required, optional, remaining.
|
383
|
+
#
|
384
|
+
# @return [Array<Toys::PositionalArg>]
|
385
|
+
#
|
386
|
+
def positional_args
|
387
|
+
result = required_args + optional_args
|
388
|
+
result << remaining_arg if remaining_arg
|
389
|
+
result
|
390
|
+
end
|
391
|
+
|
392
|
+
##
|
393
|
+
# Resolve the given flag given the flag string. Returns an object that
|
394
|
+
# describes the resolution result, including whether the resolution
|
395
|
+
# matched a unique flag, the specific flag syntax that was matched, and
|
396
|
+
# additional information.
|
397
|
+
#
|
398
|
+
# @param str [String] Flag string
|
399
|
+
# @return [Toys::Flag::Resolution]
|
400
|
+
#
|
401
|
+
def resolve_flag(str)
|
402
|
+
result = Flag::Resolution.new(str)
|
403
|
+
flags.each do |flag_def|
|
404
|
+
result.merge!(flag_def.resolve(str))
|
405
|
+
end
|
406
|
+
result
|
407
|
+
end
|
408
|
+
|
409
|
+
##
|
410
|
+
# Get the named acceptor from this tool or its ancestors.
|
411
|
+
#
|
412
|
+
# @param name [String] The acceptor name.
|
413
|
+
# @return [Tool::Acceptor::Base] The acceptor.
|
414
|
+
# @return [nil] if no acceptor of the given name is found.
|
415
|
+
#
|
416
|
+
def lookup_acceptor(name)
|
417
|
+
@acceptors.fetch(name.to_s) { |k| @parent ? @parent.lookup_acceptor(k) : nil }
|
418
|
+
end
|
419
|
+
|
420
|
+
##
|
421
|
+
# Get the named template from this tool or its ancestors.
|
422
|
+
#
|
423
|
+
# @param name [String] The template name.
|
424
|
+
# @return [Class,nil] The template class.
|
425
|
+
# @return [nil] if no template of the given name is found.
|
426
|
+
#
|
427
|
+
def lookup_template(name)
|
428
|
+
@templates.fetch(name.to_s) { |k| @parent ? @parent.lookup_template(k) : nil }
|
429
|
+
end
|
430
|
+
|
431
|
+
##
|
432
|
+
# Get the named mixin from this tool or its ancestors.
|
433
|
+
#
|
434
|
+
# @param name [String] The mixin name.
|
435
|
+
# @return [Module] The mixin module.
|
436
|
+
# @return [nil] if no mixin of the given name is found.
|
437
|
+
#
|
438
|
+
def lookup_mixin(name)
|
439
|
+
@mixins.fetch(name.to_s) { |k| @parent ? @parent.lookup_mixin(k) : nil }
|
440
|
+
end
|
441
|
+
|
442
|
+
##
|
443
|
+
# Get the named completion from this tool or its ancestors.
|
444
|
+
#
|
445
|
+
# @param name [String] The completion name
|
446
|
+
# @return [Tool::Completion::Base,Proc] The completion proc.
|
447
|
+
# @return [nil] if no completion of the given name is found.
|
222
448
|
#
|
223
|
-
def
|
224
|
-
@
|
449
|
+
def lookup_completion(name)
|
450
|
+
@completions.fetch(name.to_s) { |k| @parent ? @parent.lookup_completion(k) : nil }
|
225
451
|
end
|
226
452
|
|
227
453
|
##
|
228
|
-
#
|
454
|
+
# Include the given mixin in the tool class.
|
229
455
|
#
|
230
|
-
# @param [Symbol]
|
231
|
-
# @return [
|
456
|
+
# @param name [String,Symbol,Module] The mixin name or module
|
457
|
+
# @return [self]
|
232
458
|
#
|
233
|
-
def
|
234
|
-
|
459
|
+
def include_mixin(name)
|
460
|
+
tool_class.include(name)
|
461
|
+
self
|
235
462
|
end
|
236
|
-
alias get []
|
237
463
|
|
238
464
|
##
|
239
|
-
#
|
465
|
+
# Sets the path to the file that defines this tool.
|
466
|
+
# A tool may be defined from at most one path. If a different path is
|
467
|
+
# already set, raises {Toys::ToolDefinitionError}
|
240
468
|
#
|
241
|
-
# @param [
|
242
|
-
# @
|
469
|
+
# @param source [Toys::SourceInfo] Source info
|
470
|
+
# @return [self]
|
243
471
|
#
|
244
|
-
def
|
245
|
-
|
472
|
+
def lock_source(source)
|
473
|
+
if source_info && source_info.source != source.source
|
474
|
+
raise ToolDefinitionError,
|
475
|
+
"Cannot redefine tool #{display_name.inspect} in #{source.source_name}" \
|
476
|
+
" (already defined in #{source_info.source_name})"
|
477
|
+
end
|
478
|
+
@source_info = source
|
479
|
+
self
|
246
480
|
end
|
247
481
|
|
248
482
|
##
|
249
|
-
# Set
|
483
|
+
# Set the short description string.
|
484
|
+
#
|
485
|
+
# See {#desc} for details.
|
250
486
|
#
|
251
|
-
# @param [
|
252
|
-
# @param [Object] value
|
487
|
+
# @param desc [Toys::WrappableString,String,Array<String>]
|
253
488
|
#
|
254
|
-
def
|
255
|
-
|
256
|
-
|
489
|
+
def desc=(desc)
|
490
|
+
check_definition_state
|
491
|
+
@desc = WrappableString.make(desc)
|
492
|
+
end
|
493
|
+
|
494
|
+
##
|
495
|
+
# Set the long description strings.
|
496
|
+
#
|
497
|
+
# See {#long_desc} for details.
|
498
|
+
#
|
499
|
+
# @param long_desc [Array<Toys::WrappableString,String,Array<String>>]
|
500
|
+
#
|
501
|
+
def long_desc=(long_desc)
|
502
|
+
check_definition_state
|
503
|
+
@long_desc = WrappableString.make_array(long_desc)
|
504
|
+
end
|
505
|
+
|
506
|
+
##
|
507
|
+
# Append long description strings.
|
508
|
+
#
|
509
|
+
# You must pass an array of lines in the long description. See {#long_desc}
|
510
|
+
# for details on how each line may be represented.
|
511
|
+
#
|
512
|
+
# @param long_desc [Array<Toys::WrappableString,String,Array<String>>]
|
513
|
+
# @return [self]
|
514
|
+
#
|
515
|
+
def append_long_desc(long_desc)
|
516
|
+
check_definition_state
|
517
|
+
@long_desc.concat(WrappableString.make_array(long_desc))
|
518
|
+
self
|
519
|
+
end
|
520
|
+
|
521
|
+
##
|
522
|
+
# Add a named acceptor to the tool. This acceptor may be refereneced by
|
523
|
+
# name when adding a flag or an arg. See {Toys::Acceptor.create} for
|
524
|
+
# detailed information on how to specify an acceptor.
|
525
|
+
#
|
526
|
+
# @param name [String] The name of the acceptor.
|
527
|
+
# @param acceptor [Toys::Acceptor::Base,Object] The acceptor to add. You
|
528
|
+
# can provide either an acceptor object, or a spec understood by
|
529
|
+
# {Toys::Acceptor.create}.
|
530
|
+
# @param type_desc [String] Type description string, shown in help.
|
531
|
+
# Defaults to the acceptor name.
|
532
|
+
# @param block [Proc] Optional block used to create an acceptor. See
|
533
|
+
# {Toys::Acceptor.create}.
|
534
|
+
# @return [self]
|
535
|
+
#
|
536
|
+
def add_acceptor(name, acceptor = nil, type_desc: nil, &block)
|
537
|
+
name = name.to_s
|
538
|
+
if @acceptors.key?(name)
|
539
|
+
raise ToolDefinitionError,
|
540
|
+
"An acceptor named #{name.inspect} has already been defined in tool" \
|
541
|
+
" #{display_name.inspect}."
|
542
|
+
end
|
543
|
+
@acceptors[name] = Toys::Acceptor.create(acceptor, type_desc: type_desc, &block)
|
544
|
+
self
|
545
|
+
end
|
546
|
+
|
547
|
+
##
|
548
|
+
# Add a named mixin module to this tool.
|
549
|
+
# You may provide a mixin module or a block that configures one.
|
550
|
+
#
|
551
|
+
# @param name [String] The name of the mixin.
|
552
|
+
# @param mixin_module [Module] The mixin module.
|
553
|
+
# @param block [Proc] Define the mixin module here if a `mixin_module` is
|
554
|
+
# not provided directly.
|
555
|
+
# @return [self]
|
556
|
+
#
|
557
|
+
def add_mixin(name, mixin_module = nil, &block)
|
558
|
+
name = name.to_s
|
559
|
+
if @mixins.key?(name)
|
560
|
+
raise ToolDefinitionError,
|
561
|
+
"A mixin named #{name.inspect} has already been defined in tool" \
|
562
|
+
" #{display_name.inspect}."
|
563
|
+
end
|
564
|
+
@mixins[name] = mixin_module || Mixin.create(&block)
|
565
|
+
self
|
566
|
+
end
|
567
|
+
|
568
|
+
##
|
569
|
+
# Add a named completion proc to this tool. The completion may be
|
570
|
+
# referenced by name when adding a flag or an arg. See
|
571
|
+
# {Toys::Completion.create} for detailed information on how to specify a
|
572
|
+
# completion.
|
573
|
+
#
|
574
|
+
# @param name [String] The name of the completion.
|
575
|
+
# @param completion [Proc,Tool::Completion::Base,Object] The completion to
|
576
|
+
# add. You can provide either a completion object, or a spec understood
|
577
|
+
# by {Toys::Completion.create}.
|
578
|
+
# @param options [Hash] Additional options to pass to the completion.
|
579
|
+
# @param block [Proc] Optional block used to create a completion. See
|
580
|
+
# {Toys::Completion.create}.
|
581
|
+
# @return [self]
|
582
|
+
#
|
583
|
+
def add_completion(name, completion = nil, **options, &block)
|
584
|
+
name = name.to_s
|
585
|
+
if @completions.key?(name)
|
586
|
+
raise ToolDefinitionError,
|
587
|
+
"A completion named #{name.inspect} has already been defined in tool" \
|
588
|
+
" #{display_name.inspect}."
|
589
|
+
end
|
590
|
+
@completions[name] = Toys::Completion.create(completion, options, &block)
|
591
|
+
self
|
592
|
+
end
|
593
|
+
|
594
|
+
##
|
595
|
+
# Add a named template class to this tool.
|
596
|
+
# You may provide a template class or a block that configures one.
|
597
|
+
#
|
598
|
+
# @param name [String] The name of the template.
|
599
|
+
# @param template_class [Class] The template class.
|
600
|
+
# @param block [Proc] Define the template class here if a `template_class`
|
601
|
+
# is not provided directly.
|
602
|
+
# @return [self]
|
603
|
+
#
|
604
|
+
def add_template(name, template_class = nil, &block)
|
605
|
+
name = name.to_s
|
606
|
+
if @templates.key?(name)
|
607
|
+
raise ToolDefinitionError,
|
608
|
+
"A template named #{name.inspect} has already been defined in tool" \
|
609
|
+
" #{display_name.inspect}."
|
610
|
+
end
|
611
|
+
@templates[name] = template_class || Template.create(&block)
|
612
|
+
self
|
613
|
+
end
|
614
|
+
|
615
|
+
##
|
616
|
+
# Disable argument parsing for this tool.
|
617
|
+
#
|
618
|
+
# @return [self]
|
619
|
+
#
|
620
|
+
def disable_argument_parsing
|
621
|
+
check_definition_state
|
622
|
+
if includes_arguments?
|
623
|
+
raise ToolDefinitionError,
|
624
|
+
"Cannot disable argument parsing for tool #{display_name.inspect}" \
|
625
|
+
" because arguments have already been defined."
|
626
|
+
end
|
627
|
+
@disable_argument_parsing = true
|
628
|
+
self
|
629
|
+
end
|
630
|
+
|
631
|
+
##
|
632
|
+
# Enforce that flags must come before args for this tool.
|
633
|
+
# You may disable enforcement by passoing `false` for the state.
|
634
|
+
#
|
635
|
+
# @param state [Boolean]
|
636
|
+
# @return [self]
|
637
|
+
#
|
638
|
+
def enforce_flags_before_args(state = true)
|
639
|
+
check_definition_state
|
640
|
+
if argument_parsing_disabled?
|
641
|
+
raise ToolDefinitionError,
|
642
|
+
"Cannot enforce flags before args for tool #{display_name.inspect}" \
|
643
|
+
" because parsing is disabled."
|
644
|
+
end
|
645
|
+
@enforce_flags_before_args = state
|
646
|
+
self
|
647
|
+
end
|
648
|
+
|
649
|
+
##
|
650
|
+
# Require that flags must match exactly. (If false, flags can match an
|
651
|
+
# unambiguous substring.)
|
652
|
+
#
|
653
|
+
# @param state [Boolean]
|
654
|
+
# @return [self]
|
655
|
+
#
|
656
|
+
def require_exact_flag_match(state = true)
|
657
|
+
check_definition_state
|
658
|
+
if argument_parsing_disabled?
|
659
|
+
raise ToolDefinitionError,
|
660
|
+
"Cannot require exact flag match for tool" \
|
661
|
+
" #{display_name.inspect} because parsing is disabled."
|
662
|
+
end
|
663
|
+
@require_exact_flag_match = state
|
664
|
+
self
|
665
|
+
end
|
666
|
+
|
667
|
+
##
|
668
|
+
# Add a flag group to the group list.
|
669
|
+
#
|
670
|
+
# The type should be one of the following symbols:
|
671
|
+
# * `:optional` All flags in the group are optional
|
672
|
+
# * `:required` All flags in the group are required
|
673
|
+
# * `:exactly_one` Exactly one flag in the group must be provided
|
674
|
+
# * `:at_least_one` At least one flag in the group must be provided
|
675
|
+
# * `:at_most_one` At most one flag in the group must be provided
|
676
|
+
#
|
677
|
+
# @param type [Symbol] The type of group. Default is `:optional`.
|
678
|
+
# @param desc [String,Array<String>,Toys::WrappableString] Short
|
679
|
+
# description for the group. See {Toys::Tool#desc=} for a description
|
680
|
+
# of allowed formats. Defaults to `"Flags"`.
|
681
|
+
# @param long_desc [Array<String,Array<String>,Toys::WrappableString>]
|
682
|
+
# Long description for the flag group. See {Toys::Tool#long_desc=} for
|
683
|
+
# a description of allowed formats. Defaults to the empty array.
|
684
|
+
# @param name [String,Symbol,nil] The name of the group, or nil for no
|
685
|
+
# name.
|
686
|
+
# @param report_collisions [Boolean] If `true`, raise an exception if a
|
687
|
+
# the given name is already taken. If `false`, ignore. Default is
|
688
|
+
# `true`.
|
689
|
+
# @param prepend [Boolean] If `true`, prepend rather than append the
|
690
|
+
# group to the list. Default is `false`.
|
691
|
+
# @return [self]
|
692
|
+
#
|
693
|
+
def add_flag_group(type: :optional, desc: nil, long_desc: nil,
|
694
|
+
name: nil, report_collisions: true, prepend: false)
|
695
|
+
if !name.nil? && @flag_group_names.key?(name)
|
696
|
+
return self unless report_collisions
|
697
|
+
raise ToolDefinitionError, "Flag group #{name} already exists"
|
698
|
+
end
|
699
|
+
group = FlagGroup.create(type: type, name: name, desc: desc, long_desc: long_desc)
|
700
|
+
@flag_group_names[name] = group unless name.nil?
|
701
|
+
if prepend
|
702
|
+
@flag_groups.unshift(group)
|
257
703
|
else
|
258
|
-
@
|
704
|
+
@flag_groups.push(group)
|
259
705
|
end
|
260
706
|
self
|
261
707
|
end
|
262
708
|
|
263
709
|
##
|
264
|
-
#
|
265
|
-
#
|
266
|
-
#
|
267
|
-
# private context values used by middleware or mixins.
|
710
|
+
# Add a flag to the current tool. Each flag must specify a key which
|
711
|
+
# the script may use to obtain the flag value from the context.
|
712
|
+
# You may then provide the flags themselves in `OptionParser` form.
|
268
713
|
#
|
269
|
-
# @
|
714
|
+
# @param key [String,Symbol] The key to use to retrieve the value from
|
715
|
+
# the execution context.
|
716
|
+
# @param flags [Array<String>] The flags in OptionParser format. If empty,
|
717
|
+
# a flag will be inferred from the key.
|
718
|
+
# @param accept [Object] An acceptor that validates and/or converts the
|
719
|
+
# value. You may provide either the name of an acceptor you have
|
720
|
+
# defined, or one of the default acceptors provided by OptionParser.
|
721
|
+
# Optional. If not specified, accepts any value as a string.
|
722
|
+
# @param default [Object] The default value. This is the value that will
|
723
|
+
# be set in the context if this flag is not provided on the command
|
724
|
+
# line. Defaults to `nil`.
|
725
|
+
# @param handler [Proc,nil,:set,:push] An optional handler for
|
726
|
+
# setting/updating the value. A handler is a proc taking two
|
727
|
+
# arguments, the given value and the previous value, returning the
|
728
|
+
# new value that should be set. You may also specify a predefined
|
729
|
+
# named handler. The `:set` handler (the default) replaces the
|
730
|
+
# previous value (effectively `-> (val, _prev) { val }`). The
|
731
|
+
# `:push` handler expects the previous value to be an array and
|
732
|
+
# pushes the given value onto it; it should be combined with setting
|
733
|
+
# `default: []` and is intended for "multi-valued" flags.
|
734
|
+
# @param complete_flags [Object] A specifier for shell tab completion
|
735
|
+
# for flag names associated with this flag. By default, a
|
736
|
+
# {Toys::Flag::DefaultCompletion} is used, which provides the flag's
|
737
|
+
# names as completion candidates. To customize completion, set this to
|
738
|
+
# a hash of options to pass to the constructor for
|
739
|
+
# {Toys::Flag::DefaultCompletion}, or pass any other spec recognized
|
740
|
+
# by {Toys::Completion.create}.
|
741
|
+
# @param complete_values [Object] A specifier for shell tab completion
|
742
|
+
# for flag values associated with this flag. Pass any spec
|
743
|
+
# recognized by {Toys::Completion.create}.
|
744
|
+
# @param report_collisions [Boolean] Raise an exception if a flag is
|
745
|
+
# requested that is already in use or marked as disabled. Default is
|
746
|
+
# true.
|
747
|
+
# @param group [Toys::FlagGroup,String,Symbol,nil] Group for
|
748
|
+
# this flag. You may provide a group name, a FlagGroup object, or
|
749
|
+
# `nil` which denotes the default group.
|
750
|
+
# @param desc [String,Array<String>,Toys::WrappableString] Short
|
751
|
+
# description for the flag. See {Toys::Tool#desc=} for a description of
|
752
|
+
# allowed formats. Defaults to the empty string.
|
753
|
+
# @param long_desc [Array<String,Array<String>,Toys::WrappableString>]
|
754
|
+
# Long description for the flag. See {Toys::Tool#long_desc=} for a
|
755
|
+
# description of allowed formats. Defaults to the empty array.
|
756
|
+
# @param display_name [String] A display name for this flag, used in help
|
757
|
+
# text and error messages.
|
758
|
+
# @return [self]
|
270
759
|
#
|
271
|
-
def
|
272
|
-
|
273
|
-
|
760
|
+
def add_flag(key, flags = [],
|
761
|
+
accept: nil, default: nil, handler: nil, complete_flags: nil,
|
762
|
+
complete_values: nil, report_collisions: true, group: nil, desc: nil,
|
763
|
+
long_desc: nil, display_name: nil)
|
764
|
+
unless group.is_a?(FlagGroup::Base)
|
765
|
+
group_name = group
|
766
|
+
group = @flag_group_names[group_name]
|
767
|
+
raise ToolDefinitionError, "No such flag group: #{group_name.inspect}" if group.nil?
|
274
768
|
end
|
769
|
+
check_definition_state(is_arg: true)
|
770
|
+
accept = resolve_acceptor_name(accept)
|
771
|
+
complete_flags = resolve_completion_name(complete_flags)
|
772
|
+
complete_values = resolve_completion_name(complete_values)
|
773
|
+
flag_def = Flag.new(key, flags, @used_flags, report_collisions, accept, handler, default,
|
774
|
+
complete_flags, complete_values, desc, long_desc, display_name, group)
|
775
|
+
if flag_def.active?
|
776
|
+
@flags << flag_def
|
777
|
+
group << flag_def
|
778
|
+
end
|
779
|
+
@default_data[key] = default
|
780
|
+
self
|
781
|
+
end
|
782
|
+
|
783
|
+
##
|
784
|
+
# Mark one or more flags as disabled, preventing their use by any
|
785
|
+
# subsequent flag definition. This may be used to prevent middleware from
|
786
|
+
# defining a particular flag.
|
787
|
+
#
|
788
|
+
# @param flags [String...] The flags to disable
|
789
|
+
# @return [self]
|
790
|
+
#
|
791
|
+
def disable_flag(*flags)
|
792
|
+
check_definition_state(is_arg: true)
|
793
|
+
flags = flags.uniq
|
794
|
+
intersection = @used_flags & flags
|
795
|
+
unless intersection.empty?
|
796
|
+
raise ToolDefinitionError, "Cannot disable flags already used: #{intersection.inspect}"
|
797
|
+
end
|
798
|
+
@used_flags.concat(flags)
|
799
|
+
self
|
800
|
+
end
|
801
|
+
|
802
|
+
##
|
803
|
+
# Add a required positional argument to the current tool. You must specify
|
804
|
+
# a key which the script may use to obtain the argument value from the
|
805
|
+
# context.
|
806
|
+
#
|
807
|
+
# @param key [String,Symbol] The key to use to retrieve the value from
|
808
|
+
# the execution context.
|
809
|
+
# @param accept [Object] An acceptor that validates and/or converts the
|
810
|
+
# value. You may provide either the name of an acceptor you have
|
811
|
+
# defined, or one of the default acceptors provided by OptionParser.
|
812
|
+
# Optional. If not specified, accepts any value as a string.
|
813
|
+
# @param complete [Object] A specifier for shell tab completion. See
|
814
|
+
# {Toys::Completion.create} for recognized formats.
|
815
|
+
# @param display_name [String] A name to use for display (in help text and
|
816
|
+
# error reports). Defaults to the key in upper case.
|
817
|
+
# @param desc [String,Array<String>,Toys::WrappableString] Short
|
818
|
+
# description for the arg. See {Toys::Tool#desc=} for a description of
|
819
|
+
# allowed formats. Defaults to the empty string.
|
820
|
+
# @param long_desc [Array<String,Array<String>,Toys::WrappableString>]
|
821
|
+
# Long description for the arg. See {Toys::Tool#long_desc=} for a
|
822
|
+
# description of allowed formats. Defaults to the empty array.
|
823
|
+
# @return [self]
|
824
|
+
#
|
825
|
+
def add_required_arg(key, accept: nil, complete: nil, display_name: nil,
|
826
|
+
desc: nil, long_desc: nil)
|
827
|
+
check_definition_state(is_arg: true)
|
828
|
+
accept = resolve_acceptor_name(accept)
|
829
|
+
complete = resolve_completion_name(complete)
|
830
|
+
arg_def = PositionalArg.new(key, :required, accept, nil, complete,
|
831
|
+
desc, long_desc, display_name)
|
832
|
+
@required_args << arg_def
|
833
|
+
self
|
834
|
+
end
|
835
|
+
|
836
|
+
##
|
837
|
+
# Add an optional positional argument to the current tool. You must specify
|
838
|
+
# a key which the script may use to obtain the argument value from the
|
839
|
+
# context. If an optional argument is not given on the command line, the
|
840
|
+
# value is set to the given default.
|
841
|
+
#
|
842
|
+
# @param key [String,Symbol] The key to use to retrieve the value from
|
843
|
+
# the execution context.
|
844
|
+
# @param default [Object] The default value. This is the value that will
|
845
|
+
# be set in the context if this argument is not provided on the command
|
846
|
+
# line. Defaults to `nil`.
|
847
|
+
# @param accept [Object] An acceptor that validates and/or converts the
|
848
|
+
# value. You may provide either the name of an acceptor you have
|
849
|
+
# defined, or one of the default acceptors provided by OptionParser.
|
850
|
+
# Optional. If not specified, accepts any value as a string.
|
851
|
+
# @param complete [Object] A specifier for shell tab completion. See
|
852
|
+
# {Toys::Completion.create} for recognized formats.
|
853
|
+
# @param display_name [String] A name to use for display (in help text and
|
854
|
+
# error reports). Defaults to the key in upper case.
|
855
|
+
# @param desc [String,Array<String>,Toys::WrappableString] Short
|
856
|
+
# description for the arg. See {Toys::Tool#desc=} for a description of
|
857
|
+
# allowed formats. Defaults to the empty string.
|
858
|
+
# @param long_desc [Array<String,Array<String>,Toys::WrappableString>]
|
859
|
+
# Long description for the arg. See {Toys::Tool#long_desc=} for a
|
860
|
+
# description of allowed formats. Defaults to the empty array.
|
861
|
+
# @return [self]
|
862
|
+
#
|
863
|
+
def add_optional_arg(key, default: nil, accept: nil, complete: nil,
|
864
|
+
display_name: nil, desc: nil, long_desc: nil)
|
865
|
+
check_definition_state(is_arg: true)
|
866
|
+
accept = resolve_acceptor_name(accept)
|
867
|
+
complete = resolve_completion_name(complete)
|
868
|
+
arg_def = PositionalArg.new(key, :optional, accept, default, complete,
|
869
|
+
desc, long_desc, display_name)
|
870
|
+
@optional_args << arg_def
|
871
|
+
@default_data[key] = default
|
872
|
+
self
|
275
873
|
end
|
276
874
|
|
277
875
|
##
|
278
|
-
#
|
876
|
+
# Specify what should be done with unmatched positional arguments. You must
|
877
|
+
# specify a key which the script may use to obtain the remaining args
|
878
|
+
# from the context.
|
279
879
|
#
|
280
|
-
# @param [String]
|
281
|
-
#
|
282
|
-
#
|
283
|
-
#
|
880
|
+
# @param key [String,Symbol] The key to use to retrieve the value from
|
881
|
+
# the execution context.
|
882
|
+
# @param default [Object] The default value. This is the value that will
|
883
|
+
# be set in the context if no unmatched arguments are provided on the
|
884
|
+
# command line. Defaults to the empty array `[]`.
|
885
|
+
# @param accept [Object] An acceptor that validates and/or converts the
|
886
|
+
# value. You may provide either the name of an acceptor you have
|
887
|
+
# defined, or one of the default acceptors provided by OptionParser.
|
888
|
+
# Optional. If not specified, accepts any value as a string.
|
889
|
+
# @param complete [Object] A specifier for shell tab completion. See
|
890
|
+
# {Toys::Completion.create} for recognized formats.
|
891
|
+
# @param display_name [String] A name to use for display (in help text and
|
892
|
+
# error reports). Defaults to the key in upper case.
|
893
|
+
# @param desc [String,Array<String>,Toys::WrappableString] Short
|
894
|
+
# description for the arg. See {Toys::Tool#desc=} for a description of
|
895
|
+
# allowed formats. Defaults to the empty string.
|
896
|
+
# @param long_desc [Array<String,Array<String>,Toys::WrappableString>]
|
897
|
+
# Long description for the arg. See {Toys::Tool#long_desc=} for a
|
898
|
+
# description of allowed formats. Defaults to the empty array.
|
899
|
+
# @return [self]
|
284
900
|
#
|
285
|
-
def
|
286
|
-
|
901
|
+
def set_remaining_args(key, default: [], accept: nil, complete: nil,
|
902
|
+
display_name: nil, desc: nil, long_desc: nil)
|
903
|
+
check_definition_state(is_arg: true)
|
904
|
+
accept = resolve_acceptor_name(accept)
|
905
|
+
complete = resolve_completion_name(complete)
|
906
|
+
arg_def = PositionalArg.new(key, :remaining, accept, default, complete,
|
907
|
+
desc, long_desc, display_name)
|
908
|
+
@remaining_arg = arg_def
|
909
|
+
@default_data[key] = default
|
910
|
+
self
|
287
911
|
end
|
288
912
|
|
289
913
|
##
|
290
|
-
#
|
291
|
-
# to the directory containing the toys config directory structure being
|
292
|
-
# read, but it may be changed by setting a different context directory
|
293
|
-
# for the tool.
|
294
|
-
# May return nil if there is no context.
|
914
|
+
# Set the run handler block
|
295
915
|
#
|
296
|
-
# @
|
916
|
+
# @param proc [Proc] The runnable block
|
917
|
+
#
|
918
|
+
def run_handler=(proc)
|
919
|
+
check_definition_state
|
920
|
+
@tool_class.to_run(&proc)
|
921
|
+
end
|
922
|
+
|
923
|
+
##
|
924
|
+
# Set the interrupt handler.
|
925
|
+
#
|
926
|
+
# @param handler [Proc,Symbol] The interrupt handler
|
927
|
+
#
|
928
|
+
def interrupt_handler=(handler)
|
929
|
+
check_definition_state
|
930
|
+
if !handler.is_a?(::Proc) && !handler.is_a?(::Symbol) && !handler.nil?
|
931
|
+
raise ToolDefinitionError, "Interrupt handler must be a proc or symbol"
|
932
|
+
end
|
933
|
+
@interrupt_handler = handler
|
934
|
+
end
|
935
|
+
|
936
|
+
##
|
937
|
+
# Set the usage error handler.
|
938
|
+
#
|
939
|
+
# @param handler [Proc,Symbol] The usage error handler
|
940
|
+
#
|
941
|
+
def usage_error_handler=(handler)
|
942
|
+
check_definition_state
|
943
|
+
if !handler.is_a?(::Proc) && !handler.is_a?(::Symbol) && !handler.nil?
|
944
|
+
raise ToolDefinitionError, "Usage error handler must be a proc or symbol"
|
945
|
+
end
|
946
|
+
@usage_error_handler = handler
|
947
|
+
end
|
948
|
+
|
949
|
+
##
|
950
|
+
# Add an initializer.
|
951
|
+
#
|
952
|
+
# @param proc [Proc] The initializer block
|
953
|
+
# @param args [Object...] Arguments to pass to the initializer
|
954
|
+
# @return [self]
|
955
|
+
#
|
956
|
+
def add_initializer(proc, *args)
|
957
|
+
check_definition_state
|
958
|
+
@initializers << [proc, args]
|
959
|
+
self
|
960
|
+
end
|
961
|
+
|
962
|
+
##
|
963
|
+
# Set the custom context directory.
|
964
|
+
#
|
965
|
+
# See {#custom_context_directory} for details.
|
966
|
+
#
|
967
|
+
# @param dir [String]
|
968
|
+
#
|
969
|
+
def custom_context_directory=(dir)
|
970
|
+
check_definition_state
|
971
|
+
@custom_context_directory = dir
|
972
|
+
end
|
973
|
+
|
974
|
+
##
|
975
|
+
# Set the completion strategy for this Tool.
|
976
|
+
#
|
977
|
+
# See {#completion} for details.
|
978
|
+
#
|
979
|
+
# @param spec [Object]
|
980
|
+
#
|
981
|
+
def completion=(spec)
|
982
|
+
@completion =
|
983
|
+
case spec
|
984
|
+
when nil, :default
|
985
|
+
DefaultCompletion.new
|
986
|
+
when ::Hash
|
987
|
+
DefaultCompletion.new(spec)
|
988
|
+
else
|
989
|
+
Completion.create(spec)
|
990
|
+
end
|
991
|
+
end
|
992
|
+
|
993
|
+
##
|
994
|
+
# Return the effective context directory.
|
995
|
+
# If there is a custom context directory, uses that. Otherwise, looks for
|
996
|
+
# a custom context directory up the tool ancestor chain. If none is
|
997
|
+
# found, uses the default context directory from the source info. It is
|
998
|
+
# possible for there to be no context directory at all, in which case,
|
999
|
+
# returns nil.
|
1000
|
+
#
|
1001
|
+
# @return [String] The effective context directory path.
|
1002
|
+
# @return [nil] if there is no effective context directory.
|
297
1003
|
#
|
298
1004
|
def context_directory
|
299
|
-
|
1005
|
+
lookup_custom_context_directory || source_info&.context_directory
|
300
1006
|
end
|
301
1007
|
|
302
1008
|
##
|
303
|
-
#
|
1009
|
+
# Lookup the custom context directory in this tool and its ancestors.
|
1010
|
+
# @private
|
304
1011
|
#
|
305
|
-
|
306
|
-
|
1012
|
+
def lookup_custom_context_directory
|
1013
|
+
custom_context_directory || @parent&.lookup_custom_context_directory
|
1014
|
+
end
|
1015
|
+
|
1016
|
+
## @private
|
1017
|
+
def scalar_acceptor(spec = nil, type_desc: nil, &block)
|
1018
|
+
Acceptor.create(resolve_acceptor_name(spec), type_desc: type_desc, &block)
|
1019
|
+
end
|
1020
|
+
|
1021
|
+
## @private
|
1022
|
+
def scalar_completion(spec = nil, **options, &block)
|
1023
|
+
if spec.nil? && block.nil? || spec == :default
|
1024
|
+
options
|
1025
|
+
else
|
1026
|
+
Completion.create(resolve_completion_name(spec), options, &block)
|
1027
|
+
end
|
1028
|
+
end
|
1029
|
+
|
1030
|
+
##
|
1031
|
+
# Mark this tool as having at least one module included.
|
1032
|
+
# @private
|
307
1033
|
#
|
308
|
-
def
|
309
|
-
|
1034
|
+
def mark_includes_modules
|
1035
|
+
check_definition_state
|
1036
|
+
@includes_modules = true
|
1037
|
+
self
|
310
1038
|
end
|
311
1039
|
|
312
1040
|
##
|
313
|
-
#
|
1041
|
+
# Complete definition and run middleware configs. Should be called from
|
1042
|
+
# the Loader only.
|
1043
|
+
# @private
|
314
1044
|
#
|
315
|
-
|
316
|
-
|
1045
|
+
def finish_definition(loader)
|
1046
|
+
unless @definition_finished
|
1047
|
+
ContextualError.capture("Error installing tool middleware!", tool_name: full_name) do
|
1048
|
+
config_proc = proc {}
|
1049
|
+
middleware_stack.reverse_each do |middleware|
|
1050
|
+
config_proc = make_config_proc(middleware, loader, config_proc)
|
1051
|
+
end
|
1052
|
+
config_proc.call
|
1053
|
+
end
|
1054
|
+
flag_groups.each do |flag_group|
|
1055
|
+
flag_group.flags.sort_by!(&:sort_str)
|
1056
|
+
end
|
1057
|
+
@definition_finished = true
|
1058
|
+
end
|
1059
|
+
self
|
1060
|
+
end
|
1061
|
+
|
1062
|
+
##
|
1063
|
+
# Run all initializers against a context. Called from the Runner.
|
1064
|
+
# @private
|
1065
|
+
#
|
1066
|
+
def run_initializers(context)
|
1067
|
+
@initializers.each do |func, args|
|
1068
|
+
context.instance_exec(*args, &func)
|
1069
|
+
end
|
1070
|
+
end
|
1071
|
+
|
1072
|
+
##
|
1073
|
+
# Check that the tool can still be defined. Should be called internally
|
1074
|
+
# or from the DSL only.
|
1075
|
+
# @private
|
317
1076
|
#
|
318
|
-
def
|
319
|
-
|
1077
|
+
def check_definition_state(is_arg: false)
|
1078
|
+
if @definition_finished
|
1079
|
+
raise ToolDefinitionError,
|
1080
|
+
"Defintion of tool #{display_name.inspect} is already finished"
|
1081
|
+
end
|
1082
|
+
if is_arg && argument_parsing_disabled?
|
1083
|
+
raise ToolDefinitionError,
|
1084
|
+
"Tool #{display_name.inspect} has disabled argument parsing"
|
1085
|
+
end
|
1086
|
+
self
|
1087
|
+
end
|
1088
|
+
|
1089
|
+
##
|
1090
|
+
# A Completion that implements the default algorithm for a tool.
|
1091
|
+
#
|
1092
|
+
class DefaultCompletion < Completion::Base
|
1093
|
+
##
|
1094
|
+
# Create a completion given configuration options.
|
1095
|
+
#
|
1096
|
+
# @param complete_subtools [Boolean] Whether to complete subtool names
|
1097
|
+
# @param include_hidden_subtools [Boolean] Whether to include hidden
|
1098
|
+
# subtools (i.e. those beginning with an underscore)
|
1099
|
+
# @param complete_args [Boolean] Whether to complete positional args
|
1100
|
+
# @param complete_flags [Boolean] Whether to complete flag names
|
1101
|
+
# @param complete_flag_values [Boolean] Whether to complete flag values
|
1102
|
+
#
|
1103
|
+
def initialize(complete_subtools: true, include_hidden_subtools: false,
|
1104
|
+
complete_args: true, complete_flags: true, complete_flag_values: true)
|
1105
|
+
@complete_subtools = complete_subtools
|
1106
|
+
@include_hidden_subtools = include_hidden_subtools
|
1107
|
+
@complete_flags = complete_flags
|
1108
|
+
@complete_args = complete_args
|
1109
|
+
@complete_flag_values = complete_flag_values
|
1110
|
+
end
|
1111
|
+
|
1112
|
+
##
|
1113
|
+
# Whether to complete subtool names
|
1114
|
+
# @return [Boolean]
|
1115
|
+
#
|
1116
|
+
def complete_subtools?
|
1117
|
+
@complete_subtools
|
1118
|
+
end
|
1119
|
+
|
1120
|
+
##
|
1121
|
+
# Whether to include hidden subtools
|
1122
|
+
# @return [Boolean]
|
1123
|
+
#
|
1124
|
+
def include_hidden_subtools?
|
1125
|
+
@include_hidden_subtools
|
1126
|
+
end
|
1127
|
+
|
1128
|
+
##
|
1129
|
+
# Whether to complete flags
|
1130
|
+
# @return [Boolean]
|
1131
|
+
#
|
1132
|
+
def complete_flags?
|
1133
|
+
@complete_flags
|
1134
|
+
end
|
1135
|
+
|
1136
|
+
##
|
1137
|
+
# Whether to complete positional args
|
1138
|
+
# @return [Boolean]
|
1139
|
+
#
|
1140
|
+
def complete_args?
|
1141
|
+
@complete_args
|
1142
|
+
end
|
1143
|
+
|
1144
|
+
##
|
1145
|
+
# Whether to complete flag values
|
1146
|
+
# @return [Boolean]
|
1147
|
+
#
|
1148
|
+
def complete_flag_values?
|
1149
|
+
@complete_flag_values
|
1150
|
+
end
|
1151
|
+
|
1152
|
+
##
|
1153
|
+
# Returns candidates for the current completion.
|
1154
|
+
#
|
1155
|
+
# @param context [Toys::Completion::Context] the current completion
|
1156
|
+
# context including the string fragment.
|
1157
|
+
# @return [Array<Toys::Completion::Candidate>] an array of candidates
|
1158
|
+
#
|
1159
|
+
def call(context)
|
1160
|
+
candidates = valued_flag_candidates(context)
|
1161
|
+
return candidates if candidates
|
1162
|
+
candidates = subtool_or_arg_candidates(context)
|
1163
|
+
candidates += plain_flag_candidates(context)
|
1164
|
+
candidates += flag_value_candidates(context)
|
1165
|
+
candidates
|
1166
|
+
end
|
1167
|
+
|
1168
|
+
private
|
1169
|
+
|
1170
|
+
def valued_flag_candidates(context)
|
1171
|
+
return unless @complete_flag_values
|
1172
|
+
arg_parser = context.arg_parser
|
1173
|
+
return unless arg_parser.flags_allowed?
|
1174
|
+
active_flag_def = arg_parser.active_flag_def
|
1175
|
+
return if active_flag_def && active_flag_def.value_type == :required
|
1176
|
+
match = /\A(--\w[\?\w-]*)=(.*)\z/.match(context.fragment_prefix)
|
1177
|
+
return unless match
|
1178
|
+
flag_value_context = context.with(fragment_prefix: match[2])
|
1179
|
+
flag_def = flag_value_context.tool.resolve_flag(match[1]).unique_flag
|
1180
|
+
return [] unless flag_def
|
1181
|
+
flag_def.value_completion.call(flag_value_context)
|
1182
|
+
end
|
1183
|
+
|
1184
|
+
def subtool_or_arg_candidates(context)
|
1185
|
+
return [] if context.arg_parser.active_flag_def
|
1186
|
+
return [] if context.arg_parser.flags_allowed? && context.fragment.start_with?("-")
|
1187
|
+
subtool_candidates(context) || arg_candidates(context)
|
1188
|
+
end
|
1189
|
+
|
1190
|
+
def subtool_candidates(context)
|
1191
|
+
return if !@complete_subtools || !context.args.empty?
|
1192
|
+
tool_name, prefix, fragment = analyze_subtool_fragment(context)
|
1193
|
+
return unless tool_name
|
1194
|
+
subtools = context.cli.loader.list_subtools(tool_name,
|
1195
|
+
include_hidden: @include_hidden_subtools)
|
1196
|
+
return if subtools.empty?
|
1197
|
+
candidates = []
|
1198
|
+
subtools.each do |subtool|
|
1199
|
+
name = subtool.simple_name
|
1200
|
+
candidates << Completion::Candidate.new("#{prefix}#{name}") if name.start_with?(fragment)
|
1201
|
+
end
|
1202
|
+
candidates
|
1203
|
+
end
|
1204
|
+
|
1205
|
+
def analyze_subtool_fragment(context)
|
1206
|
+
tool_name = context.tool.full_name
|
1207
|
+
prefix = ""
|
1208
|
+
fragment = context.fragment
|
1209
|
+
delims = context.cli.extra_delimiters
|
1210
|
+
unless context.fragment_prefix.empty?
|
1211
|
+
if !context.fragment_prefix.end_with?(":") || !delims.include?(":")
|
1212
|
+
return [nil, nil, nil]
|
1213
|
+
end
|
1214
|
+
tool_name += context.fragment_prefix.split(":")
|
1215
|
+
end
|
1216
|
+
unless delims.empty?
|
1217
|
+
delims_regex = ::Regexp.escape(delims)
|
1218
|
+
if (match = /\A((.+)[#{delims_regex}])(.*)\z/.match(fragment))
|
1219
|
+
fragment = match[3]
|
1220
|
+
tool_name += match[2].split(/[#{delims_regex}]/)
|
1221
|
+
prefix = match[1]
|
1222
|
+
end
|
1223
|
+
end
|
1224
|
+
[tool_name, prefix, fragment]
|
1225
|
+
end
|
1226
|
+
|
1227
|
+
def arg_candidates(context)
|
1228
|
+
return unless @complete_args
|
1229
|
+
arg_def = context.arg_parser.next_arg_def
|
1230
|
+
return [] unless arg_def
|
1231
|
+
arg_def.completion.call(context)
|
1232
|
+
end
|
1233
|
+
|
1234
|
+
def plain_flag_candidates(context)
|
1235
|
+
return [] if !@complete_flags || context[:disable_flags]
|
1236
|
+
arg_parser = context.arg_parser
|
1237
|
+
return [] unless arg_parser.flags_allowed?
|
1238
|
+
flag_def = arg_parser.active_flag_def
|
1239
|
+
return [] if flag_def && flag_def.value_type == :required
|
1240
|
+
return [] if context.fragment =~ /\A[^-]/ || !context.fragment_prefix.empty?
|
1241
|
+
context.tool.flags.flat_map do |flag|
|
1242
|
+
flag.flag_completion.call(context)
|
1243
|
+
end
|
1244
|
+
end
|
1245
|
+
|
1246
|
+
def flag_value_candidates(context)
|
1247
|
+
return unless @complete_flag_values
|
1248
|
+
arg_parser = context.arg_parser
|
1249
|
+
flag_def = arg_parser.active_flag_def
|
1250
|
+
return [] unless flag_def
|
1251
|
+
return [] if @complete_flags && arg_parser.flags_allowed? &&
|
1252
|
+
flag_def.value_type == :optional && context.fragment.start_with?("-")
|
1253
|
+
flag_def.value_completion.call(context)
|
1254
|
+
end
|
1255
|
+
end
|
1256
|
+
|
1257
|
+
private
|
1258
|
+
|
1259
|
+
def make_config_proc(middleware, loader, next_config)
|
1260
|
+
proc { middleware.config(self, loader, &next_config) }
|
1261
|
+
end
|
1262
|
+
|
1263
|
+
def resolve_acceptor_name(name)
|
1264
|
+
return name unless name.is_a?(::String)
|
1265
|
+
accept = lookup_acceptor(name)
|
1266
|
+
raise ToolDefinitionError, "Unknown acceptor: #{name.inspect}" if accept.nil?
|
1267
|
+
accept
|
1268
|
+
end
|
1269
|
+
|
1270
|
+
def resolve_completion_name(name)
|
1271
|
+
return name unless name.is_a?(::String)
|
1272
|
+
completion = lookup_completion(name)
|
1273
|
+
raise ToolDefinitionError, "Unknown completion: #{name.inspect}" if completion.nil?
|
1274
|
+
completion
|
320
1275
|
end
|
321
1276
|
end
|
322
1277
|
end
|