toys 0.3.1 → 0.3.2
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/.yardopts +0 -2
- data/CHANGELOG.md +20 -0
- data/README.md +4 -24
- data/bin/toys +1 -1
- data/{lib/toys/builtins → builtins}/do.rb +1 -1
- data/{lib/toys/builtins → builtins}/system.rb +0 -0
- data/lib/toys.rb +9 -14
- data/lib/toys/standard_cli.rb +151 -0
- data/lib/toys/version.rb +2 -2
- metadata +19 -28
- data/lib/toys/cli.rb +0 -271
- data/lib/toys/config_dsl.rb +0 -432
- data/lib/toys/context.rb +0 -278
- data/lib/toys/errors.rb +0 -42
- data/lib/toys/helpers.rb +0 -52
- data/lib/toys/helpers/exec.rb +0 -469
- data/lib/toys/helpers/file_utils.rb +0 -39
- data/lib/toys/loader.rb +0 -423
- data/lib/toys/middleware.rb +0 -55
- data/lib/toys/middleware/base.rb +0 -51
- data/lib/toys/middleware/set_verbosity.rb +0 -54
- data/lib/toys/middleware/show_group_usage.rb +0 -68
- data/lib/toys/middleware/show_tool_usage.rb +0 -64
- data/lib/toys/middleware/show_usage_errors.rb +0 -57
- data/lib/toys/template.rb +0 -123
- data/lib/toys/templates.rb +0 -55
- data/lib/toys/templates/clean.rb +0 -80
- data/lib/toys/templates/gem_build.rb +0 -115
- data/lib/toys/templates/minitest.rb +0 -108
- data/lib/toys/templates/rubocop.rb +0 -81
- data/lib/toys/templates/yardoc.rb +0 -95
- data/lib/toys/tool.rb +0 -831
- data/lib/toys/utils/module_lookup.rb +0 -101
- data/lib/toys/utils/usage.rb +0 -163
data/lib/toys/tool.rb
DELETED
@@ -1,831 +0,0 @@
|
|
1
|
-
# Copyright 2018 Daniel Azuma
|
2
|
-
#
|
3
|
-
# All rights reserved.
|
4
|
-
#
|
5
|
-
# Redistribution and use in source and binary forms, with or without
|
6
|
-
# modification, are permitted provided that the following conditions are met:
|
7
|
-
#
|
8
|
-
# * Redistributions of source code must retain the above copyright notice,
|
9
|
-
# this list of conditions and the following disclaimer.
|
10
|
-
# * Redistributions in binary form must reproduce the above copyright notice,
|
11
|
-
# this list of conditions and the following disclaimer in the documentation
|
12
|
-
# and/or other materials provided with the distribution.
|
13
|
-
# * Neither the name of the copyright holder, nor the names of any other
|
14
|
-
# contributors to this software, may be used to endorse or promote products
|
15
|
-
# derived from this software without specific prior written permission.
|
16
|
-
#
|
17
|
-
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
18
|
-
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
19
|
-
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
20
|
-
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
21
|
-
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
22
|
-
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
23
|
-
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
24
|
-
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
25
|
-
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
26
|
-
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
27
|
-
# POSSIBILITY OF SUCH DAMAGE.
|
28
|
-
;
|
29
|
-
|
30
|
-
require "optparse"
|
31
|
-
|
32
|
-
module Toys
|
33
|
-
##
|
34
|
-
# A Tool is a single command that can be invoked using Toys.
|
35
|
-
# It has a name, a series of one or more words that you use to identify
|
36
|
-
# the tool on the command line. It also has a set of formal switches and
|
37
|
-
# command line arguments supported, and a block that gets run when the
|
38
|
-
# tool is executed.
|
39
|
-
#
|
40
|
-
class Tool
|
41
|
-
##
|
42
|
-
# Create a new tool.
|
43
|
-
#
|
44
|
-
# @param [Array<String>] full_name The name of the tool
|
45
|
-
#
|
46
|
-
def initialize(full_name)
|
47
|
-
@full_name = full_name.dup.freeze
|
48
|
-
@middleware_stack = []
|
49
|
-
|
50
|
-
@definition_path = nil
|
51
|
-
@cur_path = nil
|
52
|
-
@alias_target = nil
|
53
|
-
@definition_finished = false
|
54
|
-
|
55
|
-
@desc = nil
|
56
|
-
@long_desc = nil
|
57
|
-
|
58
|
-
@default_data = {}
|
59
|
-
@switch_definitions = []
|
60
|
-
@required_arg_definitions = []
|
61
|
-
@optional_arg_definitions = []
|
62
|
-
@remaining_args_definition = nil
|
63
|
-
|
64
|
-
@helpers = {}
|
65
|
-
@modules = []
|
66
|
-
@executor = nil
|
67
|
-
end
|
68
|
-
|
69
|
-
##
|
70
|
-
# Return the name of the tool as an array of strings.
|
71
|
-
# This array may not be modified.
|
72
|
-
# @return [Array<String>]
|
73
|
-
#
|
74
|
-
attr_reader :full_name
|
75
|
-
|
76
|
-
##
|
77
|
-
# Return a list of all defined switches.
|
78
|
-
# @return [Array<Toys::Tool::SwitchDefinition>]
|
79
|
-
#
|
80
|
-
attr_reader :switch_definitions
|
81
|
-
|
82
|
-
##
|
83
|
-
# Return a list of all defined required positional arguments.
|
84
|
-
# @return [Array<Toys::Tool::ArgDefinition>]
|
85
|
-
#
|
86
|
-
attr_reader :required_arg_definitions
|
87
|
-
|
88
|
-
##
|
89
|
-
# Return a list of all defined optional positional arguments.
|
90
|
-
# @return [Array<Toys::Tool::ArgDefinition>]
|
91
|
-
#
|
92
|
-
attr_reader :optional_arg_definitions
|
93
|
-
|
94
|
-
##
|
95
|
-
# Return the remaining arguments specification, or `nil` if remaining
|
96
|
-
# arguments are currently not supported by this tool.
|
97
|
-
# @return [Toys::Tool::ArgDefinition,nil]
|
98
|
-
#
|
99
|
-
attr_reader :remaining_args_definition
|
100
|
-
|
101
|
-
##
|
102
|
-
# Return the default argument data.
|
103
|
-
# @return [Hash]
|
104
|
-
#
|
105
|
-
attr_reader :default_data
|
106
|
-
|
107
|
-
##
|
108
|
-
# Return a list of modules that will be available during execution.
|
109
|
-
# @return [Array<Module>]
|
110
|
-
#
|
111
|
-
attr_reader :modules
|
112
|
-
|
113
|
-
##
|
114
|
-
# Return a list of helper methods that will be available during execution.
|
115
|
-
# @return [Hash{Symbol => Proc}]
|
116
|
-
#
|
117
|
-
attr_reader :helpers
|
118
|
-
|
119
|
-
##
|
120
|
-
# Return the executor block, or `nil` if not present.
|
121
|
-
# @return [Proc,nil]
|
122
|
-
#
|
123
|
-
attr_reader :executor
|
124
|
-
|
125
|
-
##
|
126
|
-
# If this tool is an alias, return the alias target as a local name (i.e.
|
127
|
-
# a single word identifying a sibling of this tool). Returns `nil` if this
|
128
|
-
# tool is not an alias.
|
129
|
-
# @return [String,nil]
|
130
|
-
#
|
131
|
-
attr_reader :alias_target
|
132
|
-
|
133
|
-
##
|
134
|
-
# Returns the middleware stack
|
135
|
-
# @return [Array<Object>]
|
136
|
-
#
|
137
|
-
attr_reader :middleware_stack
|
138
|
-
|
139
|
-
##
|
140
|
-
# Returns the path to the file that contains the definition of this tool.
|
141
|
-
# @return [String]
|
142
|
-
#
|
143
|
-
attr_reader :definition_path
|
144
|
-
|
145
|
-
##
|
146
|
-
# Returns the local name of this tool.
|
147
|
-
# @return [String]
|
148
|
-
#
|
149
|
-
def simple_name
|
150
|
-
full_name.last
|
151
|
-
end
|
152
|
-
|
153
|
-
##
|
154
|
-
# Returns a displayable name of this tool, generally the full name
|
155
|
-
# delimited by spaces.
|
156
|
-
# @return [String]
|
157
|
-
#
|
158
|
-
def display_name
|
159
|
-
full_name.join(" ")
|
160
|
-
end
|
161
|
-
|
162
|
-
##
|
163
|
-
# Returns true if this tool is a root tool.
|
164
|
-
# @return [Boolean]
|
165
|
-
#
|
166
|
-
def root?
|
167
|
-
full_name.empty?
|
168
|
-
end
|
169
|
-
|
170
|
-
##
|
171
|
-
# Returns true if this tool has an executor defined.
|
172
|
-
# @return [Boolean]
|
173
|
-
#
|
174
|
-
def includes_executor?
|
175
|
-
executor.is_a?(::Proc)
|
176
|
-
end
|
177
|
-
|
178
|
-
##
|
179
|
-
# Returns true if this tool is an alias.
|
180
|
-
# @return [Boolean]
|
181
|
-
#
|
182
|
-
def alias?
|
183
|
-
!alias_target.nil?
|
184
|
-
end
|
185
|
-
|
186
|
-
##
|
187
|
-
# Returns the effective short description for this tool. This will be
|
188
|
-
# displayed when this tool is listed in a command list.
|
189
|
-
# @return [String]
|
190
|
-
#
|
191
|
-
def effective_desc
|
192
|
-
@desc || default_desc
|
193
|
-
end
|
194
|
-
|
195
|
-
##
|
196
|
-
# Returns the effective long description for this tool. This will be
|
197
|
-
# displayed as part of the usage for this particular tool.
|
198
|
-
# @return [String]
|
199
|
-
#
|
200
|
-
def effective_long_desc
|
201
|
-
@long_desc || @desc || default_desc
|
202
|
-
end
|
203
|
-
|
204
|
-
##
|
205
|
-
# Returns true if there is a specific description set for this tool.
|
206
|
-
# @return [Boolean]
|
207
|
-
#
|
208
|
-
def includes_description?
|
209
|
-
!@long_desc.nil? || !@desc.nil?
|
210
|
-
end
|
211
|
-
|
212
|
-
##
|
213
|
-
# Returns true if at least one switch or positional argument is defined
|
214
|
-
# for this tool.
|
215
|
-
# @return [Boolean]
|
216
|
-
#
|
217
|
-
def includes_arguments?
|
218
|
-
!default_data.empty? || !switch_definitions.empty? ||
|
219
|
-
!required_arg_definitions.empty? || !optional_arg_definitions.empty? ||
|
220
|
-
!remaining_args_definition.nil?
|
221
|
-
end
|
222
|
-
|
223
|
-
##
|
224
|
-
# Returns true if at least one helper method or module is added to this
|
225
|
-
# tool.
|
226
|
-
# @return [Boolean]
|
227
|
-
#
|
228
|
-
def includes_helpers?
|
229
|
-
!helpers.empty? || !modules.empty?
|
230
|
-
end
|
231
|
-
|
232
|
-
##
|
233
|
-
# Returns true if this tool has any definition information.
|
234
|
-
# @return [Boolean]
|
235
|
-
#
|
236
|
-
def includes_definition?
|
237
|
-
alias? || includes_arguments? || includes_executor? || includes_helpers?
|
238
|
-
end
|
239
|
-
|
240
|
-
##
|
241
|
-
# Returns a list of switch flags used by this tool.
|
242
|
-
# @return [Array<String>]
|
243
|
-
#
|
244
|
-
def used_switches
|
245
|
-
@switch_definitions.reduce([]) { |used, sdef| used + sdef.switches }.uniq
|
246
|
-
end
|
247
|
-
|
248
|
-
##
|
249
|
-
# Make this tool an alias of the sibling tool with the given local name.
|
250
|
-
#
|
251
|
-
# @param [String] target_word The name of the alias target
|
252
|
-
#
|
253
|
-
def make_alias_of(target_word)
|
254
|
-
if root?
|
255
|
-
raise ToolDefinitionError, "Cannot make the root tool an alias"
|
256
|
-
end
|
257
|
-
if includes_description? || includes_definition?
|
258
|
-
raise ToolDefinitionError, "Tool #{display_name.inspect} already has" \
|
259
|
-
" a definition and cannot be made an alias"
|
260
|
-
end
|
261
|
-
@alias_target = target_word
|
262
|
-
self
|
263
|
-
end
|
264
|
-
|
265
|
-
##
|
266
|
-
# Set the short description.
|
267
|
-
#
|
268
|
-
# @param [String] str The short description
|
269
|
-
#
|
270
|
-
def desc=(str)
|
271
|
-
check_definition_state
|
272
|
-
@desc = str
|
273
|
-
end
|
274
|
-
|
275
|
-
##
|
276
|
-
# Set the long description.
|
277
|
-
#
|
278
|
-
# @param [String] str The long description
|
279
|
-
#
|
280
|
-
def long_desc=(str)
|
281
|
-
check_definition_state
|
282
|
-
@long_desc = str
|
283
|
-
end
|
284
|
-
|
285
|
-
##
|
286
|
-
# Define a helper method that will be available during execution.
|
287
|
-
# Pass the name of the method in the argument, and provide a block with
|
288
|
-
# the method body. Note the method name may not start with an underscore.
|
289
|
-
#
|
290
|
-
# @param [String] name The method name
|
291
|
-
#
|
292
|
-
def add_helper(name, &block)
|
293
|
-
check_definition_state
|
294
|
-
name_str = name.to_s
|
295
|
-
unless name_str =~ /^[a-z]\w+$/
|
296
|
-
raise ToolDefinitionError, "Illegal helper name: #{name_str.inspect}"
|
297
|
-
end
|
298
|
-
@helpers[name.to_sym] = block
|
299
|
-
self
|
300
|
-
end
|
301
|
-
|
302
|
-
##
|
303
|
-
# Mix in the given module during execution. You may provide the module
|
304
|
-
# itself, or the name of a well-known module under {Toys::Helpers}.
|
305
|
-
#
|
306
|
-
# @param [Module,String] name The module or module name.
|
307
|
-
#
|
308
|
-
def use_module(name)
|
309
|
-
check_definition_state
|
310
|
-
case name
|
311
|
-
when ::Module
|
312
|
-
@modules << name
|
313
|
-
when ::Symbol
|
314
|
-
mod = Helpers.lookup(name.to_s)
|
315
|
-
if mod.nil?
|
316
|
-
raise ToolDefinitionError, "Module not found: #{name.inspect}"
|
317
|
-
end
|
318
|
-
@modules << mod
|
319
|
-
else
|
320
|
-
raise ToolDefinitionError, "Illegal helper module name: #{name.inspect}"
|
321
|
-
end
|
322
|
-
self
|
323
|
-
end
|
324
|
-
|
325
|
-
##
|
326
|
-
# Add a switch to the current tool. Each switch must specify a key which
|
327
|
-
# the executor may use to obtain the switch value from the context.
|
328
|
-
# You may then provide the switches themselves in `OptionParser` form.
|
329
|
-
#
|
330
|
-
# @param [Symbol] key The key to use to retrieve the value from the
|
331
|
-
# execution context.
|
332
|
-
# @param [String...] switches The switches in OptionParser format.
|
333
|
-
# @param [Object,nil] accept An OptionParser acceptor. Optional.
|
334
|
-
# @param [Object] default The default value. This is the value that will
|
335
|
-
# be set in the context if this switch is not provided on the command
|
336
|
-
# line. Defaults to `nil`.
|
337
|
-
# @param [String,nil] doc The documentation for the switch, which appears
|
338
|
-
# in the usage documentation. Defaults to `nil` for no documentation.
|
339
|
-
# @param [Boolean] only_unique If true, any switches that are already
|
340
|
-
# defined in this tool are removed from this switch. For example, if
|
341
|
-
# an earlier switch uses `-a`, and this switch wants to use both
|
342
|
-
# `-a` and `-b`, then only `-b` will be assigned to this switch.
|
343
|
-
# Defaults to false.
|
344
|
-
# @param [Proc,nil] handler An optional handler for setting/updating the
|
345
|
-
# value. If given, it should take two arguments, the new given value
|
346
|
-
# and the previous value, and it should return the new value that
|
347
|
-
# should be set. The default handler simply replaces the previous
|
348
|
-
# value. i.e. the default is effectively `-> (val, _prev) { val }`.
|
349
|
-
#
|
350
|
-
def add_switch(key, *switches,
|
351
|
-
accept: nil, default: nil, doc: nil, only_unique: false, handler: nil)
|
352
|
-
check_definition_state
|
353
|
-
switches << "--#{Tool.canonical_switch(key)}=VALUE" if switches.empty?
|
354
|
-
bad_switch = switches.find { |s| Tool.extract_switch(s).empty? }
|
355
|
-
if bad_switch
|
356
|
-
raise ToolDefinitionError, "Illegal switch: #{bad_switch.inspect}"
|
357
|
-
end
|
358
|
-
switch_info = SwitchDefinition.new(key, switches + Array(accept) + Array(doc), handler)
|
359
|
-
if only_unique
|
360
|
-
switch_info.remove_switches(used_switches)
|
361
|
-
end
|
362
|
-
if switch_info.active?
|
363
|
-
@default_data[key] = default
|
364
|
-
@switch_definitions << switch_info
|
365
|
-
end
|
366
|
-
self
|
367
|
-
end
|
368
|
-
|
369
|
-
##
|
370
|
-
# Add a required positional argument to the current tool. You must specify
|
371
|
-
# a key which the executor may use to obtain the argument value from the
|
372
|
-
# context.
|
373
|
-
#
|
374
|
-
# @param [Symbol] key The key to use to retrieve the value from the
|
375
|
-
# execution context.
|
376
|
-
# @param [Object,nil] accept An OptionParser acceptor. Optional.
|
377
|
-
# @param [String,nil] doc The documentation for the switch, which appears
|
378
|
-
# in the usage documentation. Defaults to `nil` for no documentation.
|
379
|
-
#
|
380
|
-
def add_required_arg(key, accept: nil, doc: nil)
|
381
|
-
check_definition_state
|
382
|
-
@default_data[key] = nil
|
383
|
-
@required_arg_definitions << ArgDefinition.new(key, accept, Array(doc))
|
384
|
-
self
|
385
|
-
end
|
386
|
-
|
387
|
-
##
|
388
|
-
# Add an optional positional argument to the current tool. You must specify
|
389
|
-
# a key which the executor may use to obtain the argument value from the
|
390
|
-
# context. If an optional argument is not given on the command line, the
|
391
|
-
# value is set to the given default.
|
392
|
-
#
|
393
|
-
# @param [Symbol] key The key to use to retrieve the value from the
|
394
|
-
# execution context.
|
395
|
-
# @param [Object,nil] accept An OptionParser acceptor. Optional.
|
396
|
-
# @param [Object] default The default value. This is the value that will
|
397
|
-
# be set in the context if this argument is not provided on the command
|
398
|
-
# line. Defaults to `nil`.
|
399
|
-
# @param [String,nil] doc The documentation for the argument, which appears
|
400
|
-
# in the usage documentation. Defaults to `nil` for no documentation.
|
401
|
-
#
|
402
|
-
def add_optional_arg(key, accept: nil, default: nil, doc: nil)
|
403
|
-
check_definition_state
|
404
|
-
@default_data[key] = default
|
405
|
-
@optional_arg_definitions << ArgDefinition.new(key, accept, Array(doc))
|
406
|
-
self
|
407
|
-
end
|
408
|
-
|
409
|
-
##
|
410
|
-
# Specify what should be done with unmatched positional arguments. You must
|
411
|
-
# specify a key which the executor may use to obtain the remaining args
|
412
|
-
# from the context.
|
413
|
-
#
|
414
|
-
# @param [Symbol] key The key to use to retrieve the value from the
|
415
|
-
# execution context.
|
416
|
-
# @param [Object,nil] accept An OptionParser acceptor. Optional.
|
417
|
-
# @param [Object] default The default value. This is the value that will
|
418
|
-
# be set in the context if no unmatched arguments are provided on the
|
419
|
-
# command line. Defaults to the empty array `[]`.
|
420
|
-
# @param [String,nil] doc The documentation for the remaining arguments,
|
421
|
-
# which appears in the usage documentation. Defaults to `nil` for no
|
422
|
-
# documentation.
|
423
|
-
#
|
424
|
-
def set_remaining_args(key, accept: nil, default: [], doc: nil)
|
425
|
-
check_definition_state
|
426
|
-
@default_data[key] = default
|
427
|
-
@remaining_args_definition = ArgDefinition.new(key, accept, Array(doc))
|
428
|
-
self
|
429
|
-
end
|
430
|
-
|
431
|
-
##
|
432
|
-
# Set the executor for this tool. This is a proc that will be called,
|
433
|
-
# with `self` set to a {Toys::Context}.
|
434
|
-
#
|
435
|
-
# @param [Proc] executor The executor for this tool.
|
436
|
-
#
|
437
|
-
def executor=(executor)
|
438
|
-
check_definition_state
|
439
|
-
@executor = executor
|
440
|
-
end
|
441
|
-
|
442
|
-
##
|
443
|
-
# Execute this tool in the given context.
|
444
|
-
#
|
445
|
-
# @param [Toys::Context::Base] context_base The execution context
|
446
|
-
# @param [Array<String>] args The arguments to pass to the tool. Should
|
447
|
-
# not include the tool name.
|
448
|
-
# @param [Integer] verbosity The starting verbosity. Defaults to 0.
|
449
|
-
#
|
450
|
-
# @return [Integer] The result code.
|
451
|
-
#
|
452
|
-
def execute(context_base, args, verbosity: 0)
|
453
|
-
finish_definition unless @definition_finished
|
454
|
-
Execution.new(self).execute(context_base, args, verbosity: verbosity)
|
455
|
-
end
|
456
|
-
|
457
|
-
##
|
458
|
-
# Declare that this tool is now defined in the given path
|
459
|
-
#
|
460
|
-
# @private
|
461
|
-
#
|
462
|
-
def defining_from(path)
|
463
|
-
raise ToolDefinitionError, "Already being defined" if @cur_path
|
464
|
-
@cur_path = path
|
465
|
-
begin
|
466
|
-
yield
|
467
|
-
ensure
|
468
|
-
@definition_path = @cur_path if includes_description? || includes_definition?
|
469
|
-
@cur_path = nil
|
470
|
-
end
|
471
|
-
end
|
472
|
-
|
473
|
-
##
|
474
|
-
# Relinquish the current path declaration
|
475
|
-
#
|
476
|
-
# @private
|
477
|
-
#
|
478
|
-
def yield_definition
|
479
|
-
saved_path = @cur_path
|
480
|
-
@cur_path = nil
|
481
|
-
begin
|
482
|
-
yield
|
483
|
-
ensure
|
484
|
-
@cur_path = saved_path
|
485
|
-
end
|
486
|
-
end
|
487
|
-
|
488
|
-
##
|
489
|
-
# Complete definition and run middleware configs
|
490
|
-
#
|
491
|
-
# @private
|
492
|
-
#
|
493
|
-
def finish_definition
|
494
|
-
if !alias? && !@definition_finished
|
495
|
-
config_proc = proc {}
|
496
|
-
middleware_stack.reverse.each do |middleware|
|
497
|
-
config_proc = make_config_proc(middleware, config_proc)
|
498
|
-
end
|
499
|
-
config_proc.call
|
500
|
-
end
|
501
|
-
@definition_finished = true
|
502
|
-
self
|
503
|
-
end
|
504
|
-
|
505
|
-
private
|
506
|
-
|
507
|
-
def make_config_proc(middleware, next_config)
|
508
|
-
proc { middleware.config(self, &next_config) }
|
509
|
-
end
|
510
|
-
|
511
|
-
def default_desc
|
512
|
-
if alias?
|
513
|
-
"(Alias of #{@alias_target.inspect})"
|
514
|
-
elsif includes_executor?
|
515
|
-
"(No description available)"
|
516
|
-
else
|
517
|
-
"(A group of commands)"
|
518
|
-
end
|
519
|
-
end
|
520
|
-
|
521
|
-
def check_definition_state
|
522
|
-
if alias?
|
523
|
-
raise ToolDefinitionError, "Tool #{display_name.inspect} is an alias"
|
524
|
-
end
|
525
|
-
if @definition_path
|
526
|
-
in_clause = @cur_path ? "in #{@cur_path} " : ""
|
527
|
-
raise ToolDefinitionError,
|
528
|
-
"Cannot redefine tool #{display_name.inspect} #{in_clause}" \
|
529
|
-
"(already defined in #{@definition_path})"
|
530
|
-
end
|
531
|
-
if @definition_finished
|
532
|
-
raise ToolDefinitionError,
|
533
|
-
"Defintion of tool #{display_name.inspect} is already finished"
|
534
|
-
end
|
535
|
-
end
|
536
|
-
|
537
|
-
class << self
|
538
|
-
## @private
|
539
|
-
def canonical_switch(name)
|
540
|
-
name.to_s.downcase.tr("_", "-").gsub(/[^a-z0-9-]/, "")
|
541
|
-
end
|
542
|
-
|
543
|
-
## @private
|
544
|
-
def extract_switch(str)
|
545
|
-
if !str.is_a?(String)
|
546
|
-
[]
|
547
|
-
elsif str =~ /^(-[\?\w])(\s?\w+)?$/
|
548
|
-
[$1]
|
549
|
-
elsif str =~ /^--\[no-\](\w[\?\w-]*)$/
|
550
|
-
["--#{$1}", "--no-#{$1}"]
|
551
|
-
elsif str =~ /^(--\w[\?\w-]*)([=\s]\w+)?$/
|
552
|
-
[$1]
|
553
|
-
else
|
554
|
-
[]
|
555
|
-
end
|
556
|
-
end
|
557
|
-
end
|
558
|
-
|
559
|
-
##
|
560
|
-
# Representation of a formal switch.
|
561
|
-
#
|
562
|
-
class SwitchDefinition
|
563
|
-
##
|
564
|
-
# Create a SwitchDefinition
|
565
|
-
#
|
566
|
-
# @param [Symbol] key This switch will set the given context key.
|
567
|
-
# @param [Array<String>] optparse_info The switch definition in
|
568
|
-
# OptionParser format
|
569
|
-
# @param [Proc,nil] handler An optional handler for setting/updating the
|
570
|
-
# value. If given, it should take two arguments, the new given value
|
571
|
-
# and the previous value, and it should return the new value that
|
572
|
-
# should be set. If `nil`, uses a default handler that just replaces
|
573
|
-
# the previous value. i.e. the default is effectively
|
574
|
-
# `-> (val, _prev) { val }`.
|
575
|
-
#
|
576
|
-
def initialize(key, optparse_info, handler = nil)
|
577
|
-
@key = key
|
578
|
-
@optparse_info = optparse_info
|
579
|
-
@handler = handler || ->(val, _prev) { val }
|
580
|
-
@switches = nil
|
581
|
-
end
|
582
|
-
|
583
|
-
##
|
584
|
-
# Returns the key.
|
585
|
-
# @return [Symbol]
|
586
|
-
#
|
587
|
-
attr_reader :key
|
588
|
-
|
589
|
-
##
|
590
|
-
# Returns the OptionParser definition.
|
591
|
-
# @return [Array<String>]
|
592
|
-
#
|
593
|
-
attr_reader :optparse_info
|
594
|
-
|
595
|
-
##
|
596
|
-
# Returns the handler.
|
597
|
-
# @return [Proc]
|
598
|
-
#
|
599
|
-
attr_reader :handler
|
600
|
-
|
601
|
-
##
|
602
|
-
# Returns the list of switches used.
|
603
|
-
# @return [Array<String>]
|
604
|
-
#
|
605
|
-
def switches
|
606
|
-
@switches ||= optparse_info.map { |s| Tool.extract_switch(s) }.flatten
|
607
|
-
end
|
608
|
-
|
609
|
-
##
|
610
|
-
# Returns true if this switch is active. That is, it has a nonempty
|
611
|
-
# switches list.
|
612
|
-
# @return [Boolean]
|
613
|
-
#
|
614
|
-
def active?
|
615
|
-
!switches.empty?
|
616
|
-
end
|
617
|
-
|
618
|
-
##
|
619
|
-
# Removes the given switches.
|
620
|
-
# @param [Array<String>] switches
|
621
|
-
#
|
622
|
-
def remove_switches(switches)
|
623
|
-
@optparse_info.select! do |s|
|
624
|
-
Tool.extract_switch(s).all? { |ss| !switches.include?(ss) }
|
625
|
-
end
|
626
|
-
@switches = nil
|
627
|
-
self
|
628
|
-
end
|
629
|
-
end
|
630
|
-
|
631
|
-
##
|
632
|
-
# Representation of a formal positional argument
|
633
|
-
#
|
634
|
-
class ArgDefinition
|
635
|
-
##
|
636
|
-
# Create an ArgDefinition
|
637
|
-
#
|
638
|
-
# @param [Symbol] key This argument will set the given context key.
|
639
|
-
# @param [Object] accept An OptionParser acceptor
|
640
|
-
# @param [Array<String>] doc An array of documentation strings
|
641
|
-
#
|
642
|
-
def initialize(key, accept, doc)
|
643
|
-
@key = key
|
644
|
-
@accept = accept
|
645
|
-
@doc = doc
|
646
|
-
end
|
647
|
-
|
648
|
-
##
|
649
|
-
# Returns the key.
|
650
|
-
# @return [Symbol]
|
651
|
-
#
|
652
|
-
attr_reader :key
|
653
|
-
|
654
|
-
##
|
655
|
-
# Returns the acceptor.
|
656
|
-
# @return [Object]
|
657
|
-
#
|
658
|
-
attr_reader :accept
|
659
|
-
|
660
|
-
##
|
661
|
-
# Returns the documentation strings.
|
662
|
-
# @return [Array<String>]
|
663
|
-
#
|
664
|
-
attr_reader :doc
|
665
|
-
|
666
|
-
##
|
667
|
-
# Return a canonical name for this arg. Used in usage documentation.
|
668
|
-
#
|
669
|
-
# @return [String]
|
670
|
-
#
|
671
|
-
def canonical_name
|
672
|
-
Tool.canonical_switch(key)
|
673
|
-
end
|
674
|
-
|
675
|
-
##
|
676
|
-
# Process the given value through the acceptor.
|
677
|
-
#
|
678
|
-
# @private
|
679
|
-
#
|
680
|
-
def process_value(val)
|
681
|
-
return val unless accept
|
682
|
-
n = canonical_name
|
683
|
-
result = val
|
684
|
-
optparse = ::OptionParser.new
|
685
|
-
optparse.on("--#{n}=VALUE", accept) { |v| result = v }
|
686
|
-
optparse.parse(["--#{n}", val])
|
687
|
-
result
|
688
|
-
end
|
689
|
-
end
|
690
|
-
|
691
|
-
##
|
692
|
-
# An internal class that manages execution of a tool
|
693
|
-
# @private
|
694
|
-
#
|
695
|
-
class Execution
|
696
|
-
def initialize(tool)
|
697
|
-
@tool = tool
|
698
|
-
@data = @tool.default_data.dup
|
699
|
-
@data[Context::TOOL] = tool
|
700
|
-
@data[Context::TOOL_NAME] = tool.full_name
|
701
|
-
end
|
702
|
-
|
703
|
-
def execute(context_base, args, verbosity: 0)
|
704
|
-
return execute_alias(context_base, args) if @tool.alias?
|
705
|
-
|
706
|
-
parse_args(args, verbosity)
|
707
|
-
context = create_child_context(context_base)
|
708
|
-
|
709
|
-
original_level = context.logger.level
|
710
|
-
context.logger.level = context_base.base_level - @data[Context::VERBOSITY]
|
711
|
-
begin
|
712
|
-
perform_execution(context)
|
713
|
-
ensure
|
714
|
-
context.logger.level = original_level
|
715
|
-
end
|
716
|
-
end
|
717
|
-
|
718
|
-
private
|
719
|
-
|
720
|
-
def parse_args(args, base_verbosity)
|
721
|
-
optparse = create_option_parser
|
722
|
-
@data[Context::VERBOSITY] = base_verbosity
|
723
|
-
@data[Context::ARGS] = args
|
724
|
-
@data[Context::USAGE_ERROR] = nil
|
725
|
-
remaining = optparse.parse(args)
|
726
|
-
remaining = parse_required_args(remaining, args)
|
727
|
-
remaining = parse_optional_args(remaining)
|
728
|
-
parse_remaining_args(remaining, args)
|
729
|
-
rescue ::OptionParser::ParseError => e
|
730
|
-
@data[Context::USAGE_ERROR] = e.message
|
731
|
-
end
|
732
|
-
|
733
|
-
def create_option_parser
|
734
|
-
optparse = ::OptionParser.new
|
735
|
-
# The following clears out the Officious (hidden default switches).
|
736
|
-
optparse.remove
|
737
|
-
optparse.remove
|
738
|
-
optparse.new
|
739
|
-
optparse.new
|
740
|
-
@tool.switch_definitions.each do |switch|
|
741
|
-
optparse.on(*switch.optparse_info) do |val|
|
742
|
-
@data[switch.key] = switch.handler.call(val, @data[switch.key])
|
743
|
-
end
|
744
|
-
end
|
745
|
-
optparse
|
746
|
-
end
|
747
|
-
|
748
|
-
def parse_required_args(remaining, args)
|
749
|
-
@tool.required_arg_definitions.each do |arg_info|
|
750
|
-
if remaining.empty?
|
751
|
-
reason = "No value given for required argument named <#{arg_info.canonical_name}>"
|
752
|
-
raise create_parse_error(args, reason)
|
753
|
-
end
|
754
|
-
@data[arg_info.key] = arg_info.process_value(remaining.shift)
|
755
|
-
end
|
756
|
-
remaining
|
757
|
-
end
|
758
|
-
|
759
|
-
def parse_optional_args(remaining)
|
760
|
-
@tool.optional_arg_definitions.each do |arg_info|
|
761
|
-
break if remaining.empty?
|
762
|
-
@data[arg_info.key] = arg_info.process_value(remaining.shift)
|
763
|
-
end
|
764
|
-
remaining
|
765
|
-
end
|
766
|
-
|
767
|
-
def parse_remaining_args(remaining, args)
|
768
|
-
return if remaining.empty?
|
769
|
-
unless @tool.remaining_args_definition
|
770
|
-
if @tool.includes_executor?
|
771
|
-
raise create_parse_error(remaining, "Extra arguments provided")
|
772
|
-
else
|
773
|
-
raise create_parse_error(@tool.full_name + args, "Tool not found")
|
774
|
-
end
|
775
|
-
end
|
776
|
-
@data[@tool.remaining_args_definition.key] =
|
777
|
-
remaining.map { |arg| @tool.remaining_args_definition.process_value(arg) }
|
778
|
-
end
|
779
|
-
|
780
|
-
def create_parse_error(path, reason)
|
781
|
-
OptionParser::ParseError.new(*path).tap do |e|
|
782
|
-
e.reason = reason
|
783
|
-
end
|
784
|
-
end
|
785
|
-
|
786
|
-
def create_child_context(context_base)
|
787
|
-
context = context_base.create_context(@data)
|
788
|
-
@tool.modules.each do |mod|
|
789
|
-
context.extend(mod)
|
790
|
-
end
|
791
|
-
@tool.helpers.each do |name, block|
|
792
|
-
context.define_singleton_method(name, &block)
|
793
|
-
end
|
794
|
-
context
|
795
|
-
end
|
796
|
-
|
797
|
-
def perform_execution(context)
|
798
|
-
executor = proc do
|
799
|
-
if @tool.includes_executor?
|
800
|
-
context.instance_eval(&@tool.executor)
|
801
|
-
else
|
802
|
-
context.logger.fatal("No implementation for #{@tool.display_name.inspect}")
|
803
|
-
context.exit(-1)
|
804
|
-
end
|
805
|
-
end
|
806
|
-
@tool.middleware_stack.reverse.each do |middleware|
|
807
|
-
executor = make_executor(middleware, context, executor)
|
808
|
-
end
|
809
|
-
catch(:result) do
|
810
|
-
executor.call
|
811
|
-
0
|
812
|
-
end
|
813
|
-
end
|
814
|
-
|
815
|
-
def make_executor(middleware, context, next_executor)
|
816
|
-
proc { middleware.execute(context, &next_executor) }
|
817
|
-
end
|
818
|
-
|
819
|
-
def execute_alias(context_base, args)
|
820
|
-
target_name = @tool.full_name.slice(0..-2) + [@tool.alias_target]
|
821
|
-
target_tool = context_base.loader.lookup(target_name)
|
822
|
-
if target_tool.full_name == target_name
|
823
|
-
target_tool.execute(context_base, args)
|
824
|
-
else
|
825
|
-
context_base.logger.fatal("Alias target #{@tool.alias_target.inspect} not found")
|
826
|
-
-1
|
827
|
-
end
|
828
|
-
end
|
829
|
-
end
|
830
|
-
end
|
831
|
-
end
|