toys-core 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 +7 -0
- data/.yardopts +9 -0
- data/CHANGELOG.md +21 -0
- data/LICENSE.md +29 -0
- data/README.md +30 -0
- data/lib/toys-core.rb +54 -0
- data/lib/toys/alias.rb +94 -0
- data/lib/toys/cli.rb +268 -0
- data/lib/toys/config_dsl.rb +356 -0
- data/lib/toys/context.rb +278 -0
- data/lib/toys/core_version.rb +36 -0
- data/lib/toys/errors.rb +42 -0
- data/lib/toys/helpers.rb +52 -0
- data/lib/toys/helpers/exec.rb +469 -0
- data/lib/toys/helpers/file_utils.rb +39 -0
- data/lib/toys/loader.rb +381 -0
- data/lib/toys/middleware.rb +124 -0
- data/lib/toys/middleware/add_verbosity_switches.rb +99 -0
- data/lib/toys/middleware/base.rb +51 -0
- data/lib/toys/middleware/handle_usage_errors.rb +67 -0
- data/lib/toys/middleware/set_default_descriptions.rb +131 -0
- data/lib/toys/middleware/show_usage.rb +170 -0
- data/lib/toys/middleware/show_version.rb +99 -0
- data/lib/toys/template.rb +123 -0
- data/lib/toys/templates.rb +55 -0
- data/lib/toys/templates/clean.rb +82 -0
- data/lib/toys/templates/gem_build.rb +121 -0
- data/lib/toys/templates/minitest.rb +126 -0
- data/lib/toys/templates/rubocop.rb +86 -0
- data/lib/toys/templates/yardoc.rb +101 -0
- data/lib/toys/tool.rb +749 -0
- data/lib/toys/utils/module_lookup.rb +101 -0
- data/lib/toys/utils/usage.rb +196 -0
- metadata +146 -0
@@ -0,0 +1,126 @@
|
|
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
|
+
module Toys
|
31
|
+
module Templates
|
32
|
+
##
|
33
|
+
# A template for tools that run minitest
|
34
|
+
#
|
35
|
+
class Minitest
|
36
|
+
include Template
|
37
|
+
|
38
|
+
##
|
39
|
+
# Default tool name
|
40
|
+
# @return [String]
|
41
|
+
#
|
42
|
+
DEFAULT_TOOL_NAME = "test".freeze
|
43
|
+
|
44
|
+
##
|
45
|
+
# Default set of library paths
|
46
|
+
# @return [Array<String>]
|
47
|
+
#
|
48
|
+
DEFAULT_LIBS = ["lib"].freeze
|
49
|
+
|
50
|
+
##
|
51
|
+
# Default set of test file globs
|
52
|
+
# @return [Array<String>]
|
53
|
+
#
|
54
|
+
DEFAULT_FILES = ["test/**/test*.rb"].freeze
|
55
|
+
|
56
|
+
##
|
57
|
+
# Create the template settings for the Minitest template.
|
58
|
+
#
|
59
|
+
# @param [String] name Name of the tool to create. Defaults to
|
60
|
+
# {DEFAULT_TOOL_NAME}.
|
61
|
+
# @param [Array<String>] libs An array of library paths to add to the
|
62
|
+
# ruby require path. Defaults to {DEFAULT_LIBS}.
|
63
|
+
# @param [Array<String>] files An array of globs indicating the test
|
64
|
+
# files to load. Defaults to {DEFAULT_FILES}.
|
65
|
+
# @param [Boolean] warnings If true, runs tests with Ruby warnings.
|
66
|
+
# Defaults to true.
|
67
|
+
#
|
68
|
+
def initialize(name: DEFAULT_TOOL_NAME,
|
69
|
+
libs: DEFAULT_LIBS,
|
70
|
+
files: DEFAULT_FILES,
|
71
|
+
warnings: true)
|
72
|
+
@name = name
|
73
|
+
@libs = libs
|
74
|
+
@files = files
|
75
|
+
@warnings = warnings
|
76
|
+
end
|
77
|
+
|
78
|
+
attr_accessor :name
|
79
|
+
attr_accessor :libs
|
80
|
+
attr_accessor :files
|
81
|
+
attr_accessor :warnings
|
82
|
+
|
83
|
+
to_expand do |template|
|
84
|
+
tool(template.name) do
|
85
|
+
desc "Run minitest on the current project."
|
86
|
+
|
87
|
+
use :exec
|
88
|
+
|
89
|
+
switch(
|
90
|
+
:warnings, "-w", "--[no-]warnings",
|
91
|
+
default: template.warnings,
|
92
|
+
doc: "Turn on Ruby warnings (defaults to #{template.warnings})"
|
93
|
+
)
|
94
|
+
remaining_args(:tests, doc: "Paths to the tests to run (defaults to all tests)")
|
95
|
+
|
96
|
+
execute do
|
97
|
+
ruby_args = []
|
98
|
+
unless template.libs.empty?
|
99
|
+
lib_path = template.libs.join(::File::PATH_SEPARATOR)
|
100
|
+
ruby_args << "-I#{lib_path}"
|
101
|
+
end
|
102
|
+
ruby_args << "-w" if self[:warnings]
|
103
|
+
|
104
|
+
tests = self[:tests]
|
105
|
+
if tests.empty?
|
106
|
+
Array(template.files).each do |pattern|
|
107
|
+
tests.concat(::Dir.glob(pattern))
|
108
|
+
end
|
109
|
+
tests.uniq!
|
110
|
+
end
|
111
|
+
|
112
|
+
result = ruby(ruby_args, in_from: :controller) do |controller|
|
113
|
+
tests.each do |file|
|
114
|
+
controller.in.puts("load '#{file}'")
|
115
|
+
end
|
116
|
+
end
|
117
|
+
if result.error?
|
118
|
+
logger.error("Minitest failed!")
|
119
|
+
exit(result.exit_code)
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|
123
|
+
end
|
124
|
+
end
|
125
|
+
end
|
126
|
+
end
|
@@ -0,0 +1,86 @@
|
|
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
|
+
module Toys
|
31
|
+
module Templates
|
32
|
+
##
|
33
|
+
# A template for tools that run rubocop
|
34
|
+
#
|
35
|
+
class Rubocop
|
36
|
+
include Template
|
37
|
+
|
38
|
+
##
|
39
|
+
# Default tool name
|
40
|
+
# @return [String]
|
41
|
+
#
|
42
|
+
DEFAULT_TOOL_NAME = "rubocop".freeze
|
43
|
+
|
44
|
+
##
|
45
|
+
# Create the template settings for the Rubocop template.
|
46
|
+
#
|
47
|
+
# @param [String] name Name of the tool to create. Defaults to
|
48
|
+
# {DEFAULT_TOOL_NAME}.
|
49
|
+
# @param [Boolean] fail_on_error If true, exits with a nonzero code if
|
50
|
+
# Rubocop fails. Defaults to true.
|
51
|
+
# @param [Array<String>] options Additional options passed to the Rubocop
|
52
|
+
# CLI.
|
53
|
+
#
|
54
|
+
def initialize(name: DEFAULT_TOOL_NAME,
|
55
|
+
fail_on_error: true,
|
56
|
+
options: [])
|
57
|
+
@name = name
|
58
|
+
@fail_on_error = fail_on_error
|
59
|
+
@options = options
|
60
|
+
end
|
61
|
+
|
62
|
+
attr_accessor :name
|
63
|
+
attr_accessor :fail_on_error
|
64
|
+
attr_accessor :options
|
65
|
+
|
66
|
+
to_expand do |template|
|
67
|
+
tool(template.name) do
|
68
|
+
desc "Run rubocop on the current project."
|
69
|
+
|
70
|
+
use :exec
|
71
|
+
|
72
|
+
execute do
|
73
|
+
require "rubocop"
|
74
|
+
cli = ::RuboCop::CLI.new
|
75
|
+
logger.info "Running RuboCop..."
|
76
|
+
result = cli.run(template.options)
|
77
|
+
if result.nonzero?
|
78
|
+
logger.error "RuboCop failed!"
|
79
|
+
exit(1) if template.fail_on_error
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
@@ -0,0 +1,101 @@
|
|
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
|
+
module Toys
|
31
|
+
module Templates
|
32
|
+
##
|
33
|
+
# A template for tools that run yardoc
|
34
|
+
#
|
35
|
+
class Yardoc
|
36
|
+
include Template
|
37
|
+
|
38
|
+
##
|
39
|
+
# Default tool name
|
40
|
+
# @return [String]
|
41
|
+
#
|
42
|
+
DEFAULT_TOOL_NAME = "yardoc".freeze
|
43
|
+
|
44
|
+
##
|
45
|
+
# Create the template settings for the Yardoc template.
|
46
|
+
#
|
47
|
+
# @param [String] name Name of the tool to create. Defaults to
|
48
|
+
# {DEFAULT_TOOL_NAME}.
|
49
|
+
# @param [Array<String>] files An array of globs indicating the files
|
50
|
+
# to document.
|
51
|
+
# @param [Array<String>] options Additional options passed to YARD
|
52
|
+
# @param [Array<String>] stats_options Additional options passed to YARD
|
53
|
+
# stats
|
54
|
+
#
|
55
|
+
def initialize(name: DEFAULT_TOOL_NAME,
|
56
|
+
files: [],
|
57
|
+
options: [],
|
58
|
+
stats_options: [])
|
59
|
+
@name = name
|
60
|
+
@files = files
|
61
|
+
@options = options
|
62
|
+
@stats_options = stats_options
|
63
|
+
end
|
64
|
+
|
65
|
+
attr_accessor :name
|
66
|
+
attr_accessor :files
|
67
|
+
attr_accessor :options
|
68
|
+
attr_accessor :stats_options
|
69
|
+
|
70
|
+
to_expand do |template|
|
71
|
+
tool(template.name) do
|
72
|
+
desc "Run yardoc on the current project."
|
73
|
+
|
74
|
+
use :exec
|
75
|
+
|
76
|
+
execute do
|
77
|
+
require "yard"
|
78
|
+
files = []
|
79
|
+
patterns = Array(template.files)
|
80
|
+
patterns = ["lib/**/*.rb"] if patterns.empty?
|
81
|
+
patterns.each do |pattern|
|
82
|
+
files.concat(::Dir.glob(pattern))
|
83
|
+
end
|
84
|
+
files.uniq!
|
85
|
+
|
86
|
+
unless template.stats_options.empty?
|
87
|
+
template.options << "--no-stats"
|
88
|
+
template.stats_options << "--use-cache"
|
89
|
+
end
|
90
|
+
|
91
|
+
yardoc = ::YARD::CLI::Yardoc.new
|
92
|
+
yardoc.run(*(template.options + files))
|
93
|
+
unless template.stats_options.empty?
|
94
|
+
::YARD::CLI::Stats.run(*template.stats_options)
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
data/lib/toys/tool.rb
ADDED
@@ -0,0 +1,749 @@
|
|
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
|
+
@definition_finished = false
|
52
|
+
|
53
|
+
@desc = nil
|
54
|
+
@long_desc = nil
|
55
|
+
|
56
|
+
@default_data = {}
|
57
|
+
@switch_definitions = []
|
58
|
+
@required_arg_definitions = []
|
59
|
+
@optional_arg_definitions = []
|
60
|
+
@remaining_args_definition = nil
|
61
|
+
|
62
|
+
@helpers = {}
|
63
|
+
@modules = []
|
64
|
+
@executor = nil
|
65
|
+
end
|
66
|
+
|
67
|
+
##
|
68
|
+
# Return the name of the tool as an array of strings.
|
69
|
+
# This array may not be modified.
|
70
|
+
# @return [Array<String>]
|
71
|
+
#
|
72
|
+
attr_reader :full_name
|
73
|
+
|
74
|
+
##
|
75
|
+
# Return a list of all defined switches.
|
76
|
+
# @return [Array<Toys::Tool::SwitchDefinition>]
|
77
|
+
#
|
78
|
+
attr_reader :switch_definitions
|
79
|
+
|
80
|
+
##
|
81
|
+
# Return a list of all defined required positional arguments.
|
82
|
+
# @return [Array<Toys::Tool::ArgDefinition>]
|
83
|
+
#
|
84
|
+
attr_reader :required_arg_definitions
|
85
|
+
|
86
|
+
##
|
87
|
+
# Return a list of all defined optional positional arguments.
|
88
|
+
# @return [Array<Toys::Tool::ArgDefinition>]
|
89
|
+
#
|
90
|
+
attr_reader :optional_arg_definitions
|
91
|
+
|
92
|
+
##
|
93
|
+
# Return the remaining arguments specification, or `nil` if remaining
|
94
|
+
# arguments are currently not supported by this tool.
|
95
|
+
# @return [Toys::Tool::ArgDefinition,nil]
|
96
|
+
#
|
97
|
+
attr_reader :remaining_args_definition
|
98
|
+
|
99
|
+
##
|
100
|
+
# Return the default argument data.
|
101
|
+
# @return [Hash]
|
102
|
+
#
|
103
|
+
attr_reader :default_data
|
104
|
+
|
105
|
+
##
|
106
|
+
# Return a list of modules that will be available during execution.
|
107
|
+
# @return [Array<Module>]
|
108
|
+
#
|
109
|
+
attr_reader :modules
|
110
|
+
|
111
|
+
##
|
112
|
+
# Return a list of helper methods that will be available during execution.
|
113
|
+
# @return [Hash{Symbol => Proc}]
|
114
|
+
#
|
115
|
+
attr_reader :helpers
|
116
|
+
|
117
|
+
##
|
118
|
+
# Return the executor block, or `nil` if not present.
|
119
|
+
# @return [Proc,nil]
|
120
|
+
#
|
121
|
+
attr_reader :executor
|
122
|
+
|
123
|
+
##
|
124
|
+
# Returns the middleware stack
|
125
|
+
# @return [Array<Object>]
|
126
|
+
#
|
127
|
+
attr_reader :middleware_stack
|
128
|
+
|
129
|
+
##
|
130
|
+
# Returns the path to the file that contains the definition of this tool.
|
131
|
+
# @return [String]
|
132
|
+
#
|
133
|
+
attr_reader :definition_path
|
134
|
+
|
135
|
+
##
|
136
|
+
# Returns the local name of this tool.
|
137
|
+
# @return [String]
|
138
|
+
#
|
139
|
+
def simple_name
|
140
|
+
full_name.last
|
141
|
+
end
|
142
|
+
|
143
|
+
##
|
144
|
+
# Returns a displayable name of this tool, generally the full name
|
145
|
+
# delimited by spaces.
|
146
|
+
# @return [String]
|
147
|
+
#
|
148
|
+
def display_name
|
149
|
+
full_name.join(" ")
|
150
|
+
end
|
151
|
+
|
152
|
+
##
|
153
|
+
# Returns true if this tool is a root tool.
|
154
|
+
# @return [Boolean]
|
155
|
+
#
|
156
|
+
def root?
|
157
|
+
full_name.empty?
|
158
|
+
end
|
159
|
+
|
160
|
+
##
|
161
|
+
# Returns true if this tool has an executor defined.
|
162
|
+
# @return [Boolean]
|
163
|
+
#
|
164
|
+
def includes_executor?
|
165
|
+
executor.is_a?(::Proc)
|
166
|
+
end
|
167
|
+
|
168
|
+
##
|
169
|
+
# Returns the effective short description for this tool. This will be
|
170
|
+
# displayed when this tool is listed in a command list.
|
171
|
+
# @return [String]
|
172
|
+
#
|
173
|
+
def effective_desc
|
174
|
+
@desc || ""
|
175
|
+
end
|
176
|
+
|
177
|
+
##
|
178
|
+
# Returns the effective long description for this tool. This will be
|
179
|
+
# displayed as part of the usage for this particular tool.
|
180
|
+
# @return [String]
|
181
|
+
#
|
182
|
+
def effective_long_desc
|
183
|
+
@long_desc || @desc || ""
|
184
|
+
end
|
185
|
+
|
186
|
+
##
|
187
|
+
# Returns true if there is a specific description set for this tool.
|
188
|
+
# @return [Boolean]
|
189
|
+
#
|
190
|
+
def includes_description?
|
191
|
+
!@long_desc.nil? || !@desc.nil?
|
192
|
+
end
|
193
|
+
|
194
|
+
##
|
195
|
+
# Returns true if at least one switch or positional argument is defined
|
196
|
+
# for this tool.
|
197
|
+
# @return [Boolean]
|
198
|
+
#
|
199
|
+
def includes_arguments?
|
200
|
+
!default_data.empty? || !switch_definitions.empty? ||
|
201
|
+
!required_arg_definitions.empty? || !optional_arg_definitions.empty? ||
|
202
|
+
!remaining_args_definition.nil?
|
203
|
+
end
|
204
|
+
|
205
|
+
##
|
206
|
+
# Returns true if at least one helper method or module is added to this
|
207
|
+
# tool.
|
208
|
+
# @return [Boolean]
|
209
|
+
#
|
210
|
+
def includes_helpers?
|
211
|
+
!helpers.empty? || !modules.empty?
|
212
|
+
end
|
213
|
+
|
214
|
+
##
|
215
|
+
# Returns true if this tool has any definition information.
|
216
|
+
# @return [Boolean]
|
217
|
+
#
|
218
|
+
def includes_definition?
|
219
|
+
includes_arguments? || includes_executor? || includes_helpers?
|
220
|
+
end
|
221
|
+
|
222
|
+
##
|
223
|
+
# Returns a list of switch flags used by this tool.
|
224
|
+
# @return [Array<String>]
|
225
|
+
#
|
226
|
+
def used_switches
|
227
|
+
@switch_definitions.reduce([]) { |used, sdef| used + sdef.switches }.uniq
|
228
|
+
end
|
229
|
+
|
230
|
+
##
|
231
|
+
# Sets the path to the file that defines this tool.
|
232
|
+
# A tool may be defined from at most one path. If a different path is
|
233
|
+
# already set, raises {Toys::ToolDefinitionError}
|
234
|
+
#
|
235
|
+
# @param [String] path The path to the file defining this tool
|
236
|
+
#
|
237
|
+
def definition_path=(path)
|
238
|
+
if @definition_path && @definition_path != path
|
239
|
+
raise ToolDefinitionError,
|
240
|
+
"Cannot redefine tool #{display_name.inspect} in #{path}" \
|
241
|
+
" (already defined in #{@definition_path})"
|
242
|
+
end
|
243
|
+
@definition_path = path
|
244
|
+
end
|
245
|
+
|
246
|
+
##
|
247
|
+
# Set the short description.
|
248
|
+
#
|
249
|
+
# @param [String] str The short description
|
250
|
+
#
|
251
|
+
def desc=(str)
|
252
|
+
check_definition_state
|
253
|
+
@desc = str
|
254
|
+
end
|
255
|
+
|
256
|
+
##
|
257
|
+
# Set the long description.
|
258
|
+
#
|
259
|
+
# @param [String] str The long description
|
260
|
+
#
|
261
|
+
def long_desc=(str)
|
262
|
+
check_definition_state
|
263
|
+
@long_desc = str
|
264
|
+
end
|
265
|
+
|
266
|
+
##
|
267
|
+
# Define a helper method that will be available during execution.
|
268
|
+
# Pass the name of the method in the argument, and provide a block with
|
269
|
+
# the method body. Note the method name may not start with an underscore.
|
270
|
+
#
|
271
|
+
# @param [String] name The method name
|
272
|
+
#
|
273
|
+
def add_helper(name, &block)
|
274
|
+
check_definition_state
|
275
|
+
name_str = name.to_s
|
276
|
+
unless name_str =~ /^[a-z]\w+$/
|
277
|
+
raise ToolDefinitionError, "Illegal helper name: #{name_str.inspect}"
|
278
|
+
end
|
279
|
+
@helpers[name.to_sym] = block
|
280
|
+
self
|
281
|
+
end
|
282
|
+
|
283
|
+
##
|
284
|
+
# Mix in the given module during execution. You may provide the module
|
285
|
+
# itself, or the name of a well-known module under {Toys::Helpers}.
|
286
|
+
#
|
287
|
+
# @param [Module,String] name The module or module name.
|
288
|
+
#
|
289
|
+
def use_module(name)
|
290
|
+
check_definition_state
|
291
|
+
case name
|
292
|
+
when ::Module
|
293
|
+
@modules << name
|
294
|
+
when ::Symbol
|
295
|
+
mod = Helpers.lookup(name.to_s)
|
296
|
+
if mod.nil?
|
297
|
+
raise ToolDefinitionError, "Module not found: #{name.inspect}"
|
298
|
+
end
|
299
|
+
@modules << mod
|
300
|
+
else
|
301
|
+
raise ToolDefinitionError, "Illegal helper module name: #{name.inspect}"
|
302
|
+
end
|
303
|
+
self
|
304
|
+
end
|
305
|
+
|
306
|
+
##
|
307
|
+
# Add a switch to the current tool. Each switch must specify a key which
|
308
|
+
# the executor may use to obtain the switch value from the context.
|
309
|
+
# You may then provide the switches themselves in `OptionParser` form.
|
310
|
+
#
|
311
|
+
# @param [Symbol] key The key to use to retrieve the value from the
|
312
|
+
# execution context.
|
313
|
+
# @param [String...] switches The switches in OptionParser format.
|
314
|
+
# @param [Object,nil] accept An OptionParser acceptor. Optional.
|
315
|
+
# @param [Object] default The default value. This is the value that will
|
316
|
+
# be set in the context if this switch is not provided on the command
|
317
|
+
# line. Defaults to `nil`.
|
318
|
+
# @param [String,nil] doc The documentation for the switch, which appears
|
319
|
+
# in the usage documentation. Defaults to `nil` for no documentation.
|
320
|
+
# @param [Boolean] only_unique If true, any switches that are already
|
321
|
+
# defined in this tool are removed from this switch. For example, if
|
322
|
+
# an earlier switch uses `-a`, and this switch wants to use both
|
323
|
+
# `-a` and `-b`, then only `-b` will be assigned to this switch.
|
324
|
+
# Defaults to false.
|
325
|
+
# @param [Proc,nil] handler An optional handler for setting/updating the
|
326
|
+
# value. If given, it should take two arguments, the new given value
|
327
|
+
# and the previous value, and it should return the new value that
|
328
|
+
# should be set. The default handler simply replaces the previous
|
329
|
+
# value. i.e. the default is effectively `-> (val, _prev) { val }`.
|
330
|
+
#
|
331
|
+
def add_switch(key, *switches,
|
332
|
+
accept: nil, default: nil, doc: nil, only_unique: false, handler: nil)
|
333
|
+
check_definition_state
|
334
|
+
switches << "--#{Tool.canonical_switch(key)}=VALUE" if switches.empty?
|
335
|
+
bad_switch = switches.find { |s| Tool.extract_switch(s).empty? }
|
336
|
+
if bad_switch
|
337
|
+
raise ToolDefinitionError, "Illegal switch: #{bad_switch.inspect}"
|
338
|
+
end
|
339
|
+
switch_info = SwitchDefinition.new(key, switches + Array(accept) + Array(doc), handler)
|
340
|
+
if only_unique
|
341
|
+
switch_info.remove_switches(used_switches)
|
342
|
+
end
|
343
|
+
if switch_info.active?
|
344
|
+
@default_data[key] = default
|
345
|
+
@switch_definitions << switch_info
|
346
|
+
end
|
347
|
+
self
|
348
|
+
end
|
349
|
+
|
350
|
+
##
|
351
|
+
# Add a required positional argument to the current tool. You must specify
|
352
|
+
# a key which the executor may use to obtain the argument value from the
|
353
|
+
# context.
|
354
|
+
#
|
355
|
+
# @param [Symbol] key The key to use to retrieve the value from the
|
356
|
+
# execution context.
|
357
|
+
# @param [Object,nil] accept An OptionParser acceptor. Optional.
|
358
|
+
# @param [String,nil] doc The documentation for the switch, which appears
|
359
|
+
# in the usage documentation. Defaults to `nil` for no documentation.
|
360
|
+
#
|
361
|
+
def add_required_arg(key, accept: nil, doc: nil)
|
362
|
+
check_definition_state
|
363
|
+
@default_data[key] = nil
|
364
|
+
@required_arg_definitions << ArgDefinition.new(key, accept, Array(doc))
|
365
|
+
self
|
366
|
+
end
|
367
|
+
|
368
|
+
##
|
369
|
+
# Add an optional positional argument to the current tool. You must specify
|
370
|
+
# a key which the executor may use to obtain the argument value from the
|
371
|
+
# context. If an optional argument is not given on the command line, the
|
372
|
+
# value is set to the given default.
|
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 [Object] default The default value. This is the value that will
|
378
|
+
# be set in the context if this argument is not provided on the command
|
379
|
+
# line. Defaults to `nil`.
|
380
|
+
# @param [String,nil] doc The documentation for the argument, which appears
|
381
|
+
# in the usage documentation. Defaults to `nil` for no documentation.
|
382
|
+
#
|
383
|
+
def add_optional_arg(key, accept: nil, default: nil, doc: nil)
|
384
|
+
check_definition_state
|
385
|
+
@default_data[key] = default
|
386
|
+
@optional_arg_definitions << ArgDefinition.new(key, accept, Array(doc))
|
387
|
+
self
|
388
|
+
end
|
389
|
+
|
390
|
+
##
|
391
|
+
# Specify what should be done with unmatched positional arguments. You must
|
392
|
+
# specify a key which the executor may use to obtain the remaining args
|
393
|
+
# from the context.
|
394
|
+
#
|
395
|
+
# @param [Symbol] key The key to use to retrieve the value from the
|
396
|
+
# execution context.
|
397
|
+
# @param [Object,nil] accept An OptionParser acceptor. Optional.
|
398
|
+
# @param [Object] default The default value. This is the value that will
|
399
|
+
# be set in the context if no unmatched arguments are provided on the
|
400
|
+
# command line. Defaults to the empty array `[]`.
|
401
|
+
# @param [String,nil] doc The documentation for the remaining arguments,
|
402
|
+
# which appears in the usage documentation. Defaults to `nil` for no
|
403
|
+
# documentation.
|
404
|
+
#
|
405
|
+
def set_remaining_args(key, accept: nil, default: [], doc: nil)
|
406
|
+
check_definition_state
|
407
|
+
@default_data[key] = default
|
408
|
+
@remaining_args_definition = ArgDefinition.new(key, accept, Array(doc))
|
409
|
+
self
|
410
|
+
end
|
411
|
+
|
412
|
+
##
|
413
|
+
# Set the executor for this tool. This is a proc that will be called,
|
414
|
+
# with `self` set to a {Toys::Context}.
|
415
|
+
#
|
416
|
+
# @param [Proc] executor The executor for this tool.
|
417
|
+
#
|
418
|
+
def executor=(executor)
|
419
|
+
check_definition_state
|
420
|
+
@executor = executor
|
421
|
+
end
|
422
|
+
|
423
|
+
##
|
424
|
+
# Execute this tool in the given context.
|
425
|
+
#
|
426
|
+
# @param [Toys::CLI] cli The CLI execution context
|
427
|
+
# @param [Array<String>] args The arguments to pass to the tool. Should
|
428
|
+
# not include the tool name.
|
429
|
+
# @param [Integer] verbosity The starting verbosity. Defaults to 0.
|
430
|
+
#
|
431
|
+
# @return [Integer] The result code.
|
432
|
+
#
|
433
|
+
def execute(cli, args, verbosity: 0)
|
434
|
+
finish_definition unless @definition_finished
|
435
|
+
Execution.new(self).execute(cli, args, verbosity: verbosity)
|
436
|
+
end
|
437
|
+
|
438
|
+
##
|
439
|
+
# Complete definition and run middleware configs
|
440
|
+
#
|
441
|
+
# @private
|
442
|
+
#
|
443
|
+
def finish_definition
|
444
|
+
unless @definition_finished
|
445
|
+
config_proc = proc {}
|
446
|
+
middleware_stack.reverse.each do |middleware|
|
447
|
+
config_proc = make_config_proc(middleware, config_proc)
|
448
|
+
end
|
449
|
+
config_proc.call
|
450
|
+
@definition_finished = true
|
451
|
+
end
|
452
|
+
self
|
453
|
+
end
|
454
|
+
|
455
|
+
private
|
456
|
+
|
457
|
+
def make_config_proc(middleware, next_config)
|
458
|
+
proc { middleware.config(self, &next_config) }
|
459
|
+
end
|
460
|
+
|
461
|
+
def check_definition_state
|
462
|
+
if @definition_finished
|
463
|
+
raise ToolDefinitionError,
|
464
|
+
"Defintion of tool #{display_name.inspect} is already finished"
|
465
|
+
end
|
466
|
+
end
|
467
|
+
|
468
|
+
class << self
|
469
|
+
## @private
|
470
|
+
def canonical_switch(name)
|
471
|
+
name.to_s.downcase.tr("_", "-").gsub(/[^a-z0-9-]/, "")
|
472
|
+
end
|
473
|
+
|
474
|
+
## @private
|
475
|
+
def extract_switch(str)
|
476
|
+
if !str.is_a?(String)
|
477
|
+
[]
|
478
|
+
elsif str =~ /^(-[\?\w])(\s?\w+)?$/
|
479
|
+
[$1]
|
480
|
+
elsif str =~ /^--\[no-\](\w[\?\w-]*)$/
|
481
|
+
["--#{$1}", "--no-#{$1}"]
|
482
|
+
elsif str =~ /^(--\w[\?\w-]*)([=\s]\w+)?$/
|
483
|
+
[$1]
|
484
|
+
else
|
485
|
+
[]
|
486
|
+
end
|
487
|
+
end
|
488
|
+
end
|
489
|
+
|
490
|
+
##
|
491
|
+
# Representation of a formal switch.
|
492
|
+
#
|
493
|
+
class SwitchDefinition
|
494
|
+
##
|
495
|
+
# Create a SwitchDefinition
|
496
|
+
#
|
497
|
+
# @param [Symbol] key This switch will set the given context key.
|
498
|
+
# @param [Array<String>] optparse_info The switch definition in
|
499
|
+
# OptionParser format
|
500
|
+
# @param [Proc,nil] handler An optional handler for setting/updating the
|
501
|
+
# value. If given, it should take two arguments, the new given value
|
502
|
+
# and the previous value, and it should return the new value that
|
503
|
+
# should be set. If `nil`, uses a default handler that just replaces
|
504
|
+
# the previous value. i.e. the default is effectively
|
505
|
+
# `-> (val, _prev) { val }`.
|
506
|
+
#
|
507
|
+
def initialize(key, optparse_info, handler = nil)
|
508
|
+
@key = key
|
509
|
+
@optparse_info = optparse_info
|
510
|
+
@handler = handler || ->(val, _prev) { val }
|
511
|
+
@switches = nil
|
512
|
+
end
|
513
|
+
|
514
|
+
##
|
515
|
+
# Returns the key.
|
516
|
+
# @return [Symbol]
|
517
|
+
#
|
518
|
+
attr_reader :key
|
519
|
+
|
520
|
+
##
|
521
|
+
# Returns the OptionParser definition.
|
522
|
+
# @return [Array<String>]
|
523
|
+
#
|
524
|
+
attr_reader :optparse_info
|
525
|
+
|
526
|
+
##
|
527
|
+
# Returns the handler.
|
528
|
+
# @return [Proc]
|
529
|
+
#
|
530
|
+
attr_reader :handler
|
531
|
+
|
532
|
+
##
|
533
|
+
# Returns the list of switches used.
|
534
|
+
# @return [Array<String>]
|
535
|
+
#
|
536
|
+
def switches
|
537
|
+
@switches ||= optparse_info.map { |s| Tool.extract_switch(s) }.flatten
|
538
|
+
end
|
539
|
+
|
540
|
+
##
|
541
|
+
# Returns true if this switch is active. That is, it has a nonempty
|
542
|
+
# switches list.
|
543
|
+
# @return [Boolean]
|
544
|
+
#
|
545
|
+
def active?
|
546
|
+
!switches.empty?
|
547
|
+
end
|
548
|
+
|
549
|
+
##
|
550
|
+
# Removes the given switches.
|
551
|
+
# @param [Array<String>] switches
|
552
|
+
#
|
553
|
+
def remove_switches(switches)
|
554
|
+
@optparse_info.select! do |s|
|
555
|
+
Tool.extract_switch(s).all? { |ss| !switches.include?(ss) }
|
556
|
+
end
|
557
|
+
@switches = nil
|
558
|
+
self
|
559
|
+
end
|
560
|
+
end
|
561
|
+
|
562
|
+
##
|
563
|
+
# Representation of a formal positional argument
|
564
|
+
#
|
565
|
+
class ArgDefinition
|
566
|
+
##
|
567
|
+
# Create an ArgDefinition
|
568
|
+
#
|
569
|
+
# @param [Symbol] key This argument will set the given context key.
|
570
|
+
# @param [Object] accept An OptionParser acceptor
|
571
|
+
# @param [Array<String>] doc An array of documentation strings
|
572
|
+
#
|
573
|
+
def initialize(key, accept, doc)
|
574
|
+
@key = key
|
575
|
+
@accept = accept
|
576
|
+
@doc = doc
|
577
|
+
end
|
578
|
+
|
579
|
+
##
|
580
|
+
# Returns the key.
|
581
|
+
# @return [Symbol]
|
582
|
+
#
|
583
|
+
attr_reader :key
|
584
|
+
|
585
|
+
##
|
586
|
+
# Returns the acceptor.
|
587
|
+
# @return [Object]
|
588
|
+
#
|
589
|
+
attr_reader :accept
|
590
|
+
|
591
|
+
##
|
592
|
+
# Returns the documentation strings.
|
593
|
+
# @return [Array<String>]
|
594
|
+
#
|
595
|
+
attr_reader :doc
|
596
|
+
|
597
|
+
##
|
598
|
+
# Return a canonical name for this arg. Used in usage documentation.
|
599
|
+
#
|
600
|
+
# @return [String]
|
601
|
+
#
|
602
|
+
def canonical_name
|
603
|
+
Tool.canonical_switch(key)
|
604
|
+
end
|
605
|
+
|
606
|
+
##
|
607
|
+
# Process the given value through the acceptor.
|
608
|
+
#
|
609
|
+
# @private
|
610
|
+
#
|
611
|
+
def process_value(val)
|
612
|
+
return val unless accept
|
613
|
+
n = canonical_name
|
614
|
+
result = val
|
615
|
+
optparse = ::OptionParser.new
|
616
|
+
optparse.on("--#{n}=VALUE", accept) { |v| result = v }
|
617
|
+
optparse.parse(["--#{n}", val])
|
618
|
+
result
|
619
|
+
end
|
620
|
+
end
|
621
|
+
|
622
|
+
##
|
623
|
+
# An internal class that manages execution of a tool
|
624
|
+
# @private
|
625
|
+
#
|
626
|
+
class Execution
|
627
|
+
def initialize(tool)
|
628
|
+
@tool = tool
|
629
|
+
@data = @tool.default_data.dup
|
630
|
+
@data[Context::TOOL] = tool
|
631
|
+
@data[Context::TOOL_NAME] = tool.full_name
|
632
|
+
end
|
633
|
+
|
634
|
+
def execute(cli, args, verbosity: 0)
|
635
|
+
parse_args(args, verbosity)
|
636
|
+
context = create_child_context(cli)
|
637
|
+
|
638
|
+
original_level = context.logger.level
|
639
|
+
context.logger.level = cli.base_level - @data[Context::VERBOSITY]
|
640
|
+
begin
|
641
|
+
perform_execution(context)
|
642
|
+
ensure
|
643
|
+
context.logger.level = original_level
|
644
|
+
end
|
645
|
+
end
|
646
|
+
|
647
|
+
private
|
648
|
+
|
649
|
+
def parse_args(args, base_verbosity)
|
650
|
+
optparse = create_option_parser
|
651
|
+
@data[Context::VERBOSITY] = base_verbosity
|
652
|
+
@data[Context::ARGS] = args
|
653
|
+
@data[Context::USAGE_ERROR] = nil
|
654
|
+
remaining = optparse.parse(args)
|
655
|
+
remaining = parse_required_args(remaining, args)
|
656
|
+
remaining = parse_optional_args(remaining)
|
657
|
+
parse_remaining_args(remaining, args)
|
658
|
+
rescue ::OptionParser::ParseError => e
|
659
|
+
@data[Context::USAGE_ERROR] = e.message
|
660
|
+
end
|
661
|
+
|
662
|
+
def create_option_parser
|
663
|
+
optparse = ::OptionParser.new
|
664
|
+
# The following clears out the Officious (hidden default switches).
|
665
|
+
optparse.remove
|
666
|
+
optparse.remove
|
667
|
+
optparse.new
|
668
|
+
optparse.new
|
669
|
+
@tool.switch_definitions.each do |switch|
|
670
|
+
optparse.on(*switch.optparse_info) do |val|
|
671
|
+
@data[switch.key] = switch.handler.call(val, @data[switch.key])
|
672
|
+
end
|
673
|
+
end
|
674
|
+
optparse
|
675
|
+
end
|
676
|
+
|
677
|
+
def parse_required_args(remaining, args)
|
678
|
+
@tool.required_arg_definitions.each do |arg_info|
|
679
|
+
if remaining.empty?
|
680
|
+
reason = "No value given for required argument named <#{arg_info.canonical_name}>"
|
681
|
+
raise create_parse_error(args, reason)
|
682
|
+
end
|
683
|
+
@data[arg_info.key] = arg_info.process_value(remaining.shift)
|
684
|
+
end
|
685
|
+
remaining
|
686
|
+
end
|
687
|
+
|
688
|
+
def parse_optional_args(remaining)
|
689
|
+
@tool.optional_arg_definitions.each do |arg_info|
|
690
|
+
break if remaining.empty?
|
691
|
+
@data[arg_info.key] = arg_info.process_value(remaining.shift)
|
692
|
+
end
|
693
|
+
remaining
|
694
|
+
end
|
695
|
+
|
696
|
+
def parse_remaining_args(remaining, args)
|
697
|
+
return if remaining.empty?
|
698
|
+
unless @tool.remaining_args_definition
|
699
|
+
if @tool.includes_executor?
|
700
|
+
raise create_parse_error(remaining, "Extra arguments provided")
|
701
|
+
else
|
702
|
+
raise create_parse_error(@tool.full_name + args, "Tool not found")
|
703
|
+
end
|
704
|
+
end
|
705
|
+
@data[@tool.remaining_args_definition.key] =
|
706
|
+
remaining.map { |arg| @tool.remaining_args_definition.process_value(arg) }
|
707
|
+
end
|
708
|
+
|
709
|
+
def create_parse_error(path, reason)
|
710
|
+
OptionParser::ParseError.new(*path).tap do |e|
|
711
|
+
e.reason = reason
|
712
|
+
end
|
713
|
+
end
|
714
|
+
|
715
|
+
def create_child_context(cli)
|
716
|
+
context = Context.new(cli, @data)
|
717
|
+
@tool.modules.each do |mod|
|
718
|
+
context.extend(mod)
|
719
|
+
end
|
720
|
+
@tool.helpers.each do |name, block|
|
721
|
+
context.define_singleton_method(name, &block)
|
722
|
+
end
|
723
|
+
context
|
724
|
+
end
|
725
|
+
|
726
|
+
def perform_execution(context)
|
727
|
+
executor = proc do
|
728
|
+
if @tool.includes_executor?
|
729
|
+
context.instance_eval(&@tool.executor)
|
730
|
+
else
|
731
|
+
context.logger.fatal("No implementation for #{@tool.display_name.inspect}")
|
732
|
+
context.exit(-1)
|
733
|
+
end
|
734
|
+
end
|
735
|
+
@tool.middleware_stack.reverse.each do |middleware|
|
736
|
+
executor = make_executor(middleware, context, executor)
|
737
|
+
end
|
738
|
+
catch(:result) do
|
739
|
+
executor.call
|
740
|
+
0
|
741
|
+
end
|
742
|
+
end
|
743
|
+
|
744
|
+
def make_executor(middleware, context, next_executor)
|
745
|
+
proc { middleware.execute(context, &next_executor) }
|
746
|
+
end
|
747
|
+
end
|
748
|
+
end
|
749
|
+
end
|