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.
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