toys 0.3.1 → 0.3.2

Sign up to get free protection for your applications and to get access to all the features.
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