toys-core 0.7.0 → 0.8.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +98 -0
- data/LICENSE.md +16 -24
- data/README.md +307 -59
- data/docs/guide.md +44 -4
- data/lib/toys-core.rb +58 -49
- data/lib/toys/acceptor.rb +672 -0
- data/lib/toys/alias.rb +106 -0
- data/lib/toys/arg_parser.rb +624 -0
- data/lib/toys/cli.rb +422 -181
- data/lib/toys/compat.rb +83 -0
- data/lib/toys/completion.rb +442 -0
- data/lib/toys/context.rb +354 -0
- data/lib/toys/core_version.rb +18 -26
- data/lib/toys/dsl/flag.rb +213 -56
- data/lib/toys/dsl/flag_group.rb +237 -51
- data/lib/toys/dsl/positional_arg.rb +210 -0
- data/lib/toys/dsl/tool.rb +968 -317
- data/lib/toys/errors.rb +46 -28
- data/lib/toys/flag.rb +821 -0
- data/lib/toys/flag_group.rb +282 -0
- data/lib/toys/input_file.rb +18 -26
- data/lib/toys/loader.rb +110 -100
- data/lib/toys/middleware.rb +24 -31
- data/lib/toys/mixin.rb +90 -59
- data/lib/toys/module_lookup.rb +125 -0
- data/lib/toys/positional_arg.rb +184 -0
- data/lib/toys/source_info.rb +192 -0
- data/lib/toys/standard_middleware/add_verbosity_flags.rb +38 -43
- data/lib/toys/standard_middleware/handle_usage_errors.rb +39 -40
- data/lib/toys/standard_middleware/set_default_descriptions.rb +111 -89
- data/lib/toys/standard_middleware/show_help.rb +130 -113
- data/lib/toys/standard_middleware/show_root_version.rb +29 -35
- data/lib/toys/standard_mixins/exec.rb +116 -78
- data/lib/toys/standard_mixins/fileutils.rb +16 -24
- data/lib/toys/standard_mixins/gems.rb +29 -30
- data/lib/toys/standard_mixins/highline.rb +34 -41
- data/lib/toys/standard_mixins/terminal.rb +72 -26
- data/lib/toys/template.rb +51 -35
- data/lib/toys/tool.rb +1161 -206
- data/lib/toys/utils/completion_engine.rb +171 -0
- data/lib/toys/utils/exec.rb +279 -182
- data/lib/toys/utils/gems.rb +58 -49
- data/lib/toys/utils/help_text.rb +117 -111
- data/lib/toys/utils/terminal.rb +69 -62
- data/lib/toys/wrappable_string.rb +162 -0
- metadata +24 -22
- data/lib/toys/definition/acceptor.rb +0 -191
- data/lib/toys/definition/alias.rb +0 -112
- data/lib/toys/definition/arg.rb +0 -140
- data/lib/toys/definition/flag.rb +0 -370
- data/lib/toys/definition/flag_group.rb +0 -205
- data/lib/toys/definition/source_info.rb +0 -190
- data/lib/toys/definition/tool.rb +0 -842
- data/lib/toys/dsl/arg.rb +0 -132
- data/lib/toys/runner.rb +0 -188
- data/lib/toys/standard_middleware.rb +0 -47
- data/lib/toys/utils/module_lookup.rb +0 -135
- data/lib/toys/utils/wrappable_string.rb +0 -165
@@ -0,0 +1,192 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Copyright 2019 Daniel Azuma
|
4
|
+
#
|
5
|
+
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
# of this software and associated documentation files (the "Software"), to deal
|
7
|
+
# in the Software without restriction, including without limitation the rights
|
8
|
+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
# copies of the Software, and to permit persons to whom the Software is
|
10
|
+
# furnished to do so, subject to the following conditions:
|
11
|
+
#
|
12
|
+
# The above copyright notice and this permission notice shall be included in
|
13
|
+
# all copies or substantial portions of the Software.
|
14
|
+
#
|
15
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
20
|
+
# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
21
|
+
# IN THE SOFTWARE.
|
22
|
+
;
|
23
|
+
|
24
|
+
module Toys
|
25
|
+
##
|
26
|
+
# Information about source toys directories and files.
|
27
|
+
#
|
28
|
+
class SourceInfo
|
29
|
+
##
|
30
|
+
# Create a SourceInfo.
|
31
|
+
# @private
|
32
|
+
#
|
33
|
+
def initialize(parent, context_directory, source, source_type, source_name, data_dir_name)
|
34
|
+
@parent = parent
|
35
|
+
@context_directory = context_directory
|
36
|
+
@source = source
|
37
|
+
@source_type = source_type
|
38
|
+
@source_path = source if source.is_a?(::String)
|
39
|
+
@source_proc = source if source.is_a?(::Proc)
|
40
|
+
@source_name = source_name
|
41
|
+
@data_dir =
|
42
|
+
if data_dir_name && @source_path
|
43
|
+
dir = ::File.join(::File.dirname(@source_path), data_dir_name)
|
44
|
+
dir if ::File.directory?(dir) && ::File.readable?(dir)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
##
|
49
|
+
# The parent of this SourceInfo.
|
50
|
+
#
|
51
|
+
# @return [Toys::SourceInfo] The parent.
|
52
|
+
# @return [nil] if this SourceInfo is the root.
|
53
|
+
#
|
54
|
+
attr_reader :parent
|
55
|
+
|
56
|
+
##
|
57
|
+
# The context directory path (normally the directory containing the
|
58
|
+
# toplevel toys file or directory).
|
59
|
+
#
|
60
|
+
# @return [String] The context directory path.
|
61
|
+
# @return [nil] if there is no context directory (perhaps because the tool
|
62
|
+
# is being defined from a block)
|
63
|
+
#
|
64
|
+
attr_reader :context_directory
|
65
|
+
|
66
|
+
##
|
67
|
+
# The source, which may be a path or a proc.
|
68
|
+
#
|
69
|
+
# @return [String] Path to the source file or directory.
|
70
|
+
# @return [Proc] The block serving as the source.
|
71
|
+
#
|
72
|
+
attr_reader :source
|
73
|
+
|
74
|
+
##
|
75
|
+
# Return the type of source.
|
76
|
+
#
|
77
|
+
# @return [:file,:directory,:proc]
|
78
|
+
#
|
79
|
+
attr_reader :source_type
|
80
|
+
|
81
|
+
##
|
82
|
+
# The path of the current source file or directory.
|
83
|
+
#
|
84
|
+
# @return [String] The source path
|
85
|
+
# @return [nil] if this source is not a file system path.
|
86
|
+
#
|
87
|
+
attr_reader :source_path
|
88
|
+
|
89
|
+
##
|
90
|
+
# The source proc.
|
91
|
+
#
|
92
|
+
# @return [Proc] The source proc
|
93
|
+
# @return [nil] if this source is not a proc.
|
94
|
+
#
|
95
|
+
attr_reader :source_proc
|
96
|
+
|
97
|
+
##
|
98
|
+
# The user-visible name of this source.
|
99
|
+
#
|
100
|
+
# @return [String]
|
101
|
+
#
|
102
|
+
attr_reader :source_name
|
103
|
+
alias to_s source_name
|
104
|
+
|
105
|
+
##
|
106
|
+
# Locate the given data file or directory and return an absolute path.
|
107
|
+
#
|
108
|
+
# @param path [String] The relative path to find
|
109
|
+
# @param type [nil,:file,:directory] Type of file system object to find,
|
110
|
+
# or nil (the default) to return any type.
|
111
|
+
# @return [String] Absolute path of the resulting data.
|
112
|
+
# @return [nil] if the data was not found.
|
113
|
+
#
|
114
|
+
def find_data(path, type: nil)
|
115
|
+
if @data_dir
|
116
|
+
full_path = ::File.join(@data_dir, path)
|
117
|
+
case type
|
118
|
+
when :file
|
119
|
+
return full_path if ::File.file?(full_path)
|
120
|
+
when :directory
|
121
|
+
return full_path if ::File.directory?(full_path)
|
122
|
+
else
|
123
|
+
return full_path if ::File.readable?(full_path)
|
124
|
+
end
|
125
|
+
end
|
126
|
+
parent&.find_data(path, type: type)
|
127
|
+
end
|
128
|
+
|
129
|
+
##
|
130
|
+
# Create a child SourceInfo relative to the parent path.
|
131
|
+
# @private
|
132
|
+
#
|
133
|
+
def relative_child(filename, data_dir_name)
|
134
|
+
raise "Cannot create relative child of a proc" unless source_path
|
135
|
+
child_path = ::File.join(source_path, filename)
|
136
|
+
child_path, type = SourceInfo.check_path(child_path, true)
|
137
|
+
return nil unless child_path
|
138
|
+
SourceInfo.new(self, context_directory, child_path, type, child_path, data_dir_name)
|
139
|
+
end
|
140
|
+
|
141
|
+
##
|
142
|
+
# Create a child SourceInfo with an absolute path.
|
143
|
+
# @private
|
144
|
+
#
|
145
|
+
def absolute_child(child_path)
|
146
|
+
child_path, type = SourceInfo.check_path(child_path, false)
|
147
|
+
SourceInfo.new(self, context_directory, child_path, type, child_path, nil)
|
148
|
+
end
|
149
|
+
|
150
|
+
##
|
151
|
+
# Create a root source info for a file path.
|
152
|
+
# @private
|
153
|
+
#
|
154
|
+
def self.create_path_root(source_path)
|
155
|
+
source_path, type = check_path(source_path, false)
|
156
|
+
context_directory = ::File.dirname(source_path)
|
157
|
+
new(nil, context_directory, source_path, type, source_path, nil)
|
158
|
+
end
|
159
|
+
|
160
|
+
##
|
161
|
+
# Create a root source info for a proc.
|
162
|
+
# @private
|
163
|
+
#
|
164
|
+
def self.create_proc_root(source_proc, source_name)
|
165
|
+
new(nil, nil, source_proc, :proc, source_name, nil)
|
166
|
+
end
|
167
|
+
|
168
|
+
##
|
169
|
+
# Check a path and determine the canonical path and type.
|
170
|
+
# @private
|
171
|
+
#
|
172
|
+
def self.check_path(path, lenient)
|
173
|
+
path = ::File.expand_path(path)
|
174
|
+
unless ::File.readable?(path)
|
175
|
+
raise LoaderError, "Cannot read: #{path}" unless lenient
|
176
|
+
return [nil, nil]
|
177
|
+
end
|
178
|
+
if ::File.file?(path)
|
179
|
+
unless ::File.extname(path) == ".rb"
|
180
|
+
raise LoaderError, "File is not a ruby file: #{path}" unless lenient
|
181
|
+
return [nil, nil]
|
182
|
+
end
|
183
|
+
[path, :file]
|
184
|
+
elsif ::File.directory?(path)
|
185
|
+
[path, :directory]
|
186
|
+
else
|
187
|
+
raise LoaderError, "Unknown type: #{path}" unless lenient
|
188
|
+
[nil, nil]
|
189
|
+
end
|
190
|
+
end
|
191
|
+
end
|
192
|
+
end
|
@@ -1,32 +1,24 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
# Copyright
|
3
|
+
# Copyright 2019 Daniel Azuma
|
4
4
|
#
|
5
|
-
#
|
5
|
+
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
# of this software and associated documentation files (the "Software"), to deal
|
7
|
+
# in the Software without restriction, including without limitation the rights
|
8
|
+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
# copies of the Software, and to permit persons to whom the Software is
|
10
|
+
# furnished to do so, subject to the following conditions:
|
6
11
|
#
|
7
|
-
#
|
8
|
-
#
|
12
|
+
# The above copyright notice and this permission notice shall be included in
|
13
|
+
# all copies or substantial portions of the Software.
|
9
14
|
#
|
10
|
-
#
|
11
|
-
#
|
12
|
-
#
|
13
|
-
#
|
14
|
-
#
|
15
|
-
#
|
16
|
-
#
|
17
|
-
# derived from this software without specific prior written permission.
|
18
|
-
#
|
19
|
-
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
20
|
-
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
21
|
-
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
22
|
-
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
23
|
-
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
24
|
-
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
25
|
-
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
26
|
-
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
27
|
-
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
28
|
-
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
29
|
-
# POSSIBILITY OF SUCH DAMAGE.
|
15
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
20
|
+
# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
21
|
+
# IN THE SOFTWARE.
|
30
22
|
;
|
31
23
|
|
32
24
|
module Toys
|
@@ -36,7 +28,7 @@ module Toys
|
|
36
28
|
#
|
37
29
|
# This middleware adds `-v`, `--verbose`, `-q`, and `--quiet` flags, if
|
38
30
|
# not already defined by the tool. These flags affect the setting of
|
39
|
-
# {Toys::
|
31
|
+
# {Toys::Context::Key::VERBOSITY}, and, thus, the logger level.
|
40
32
|
#
|
41
33
|
class AddVerbosityFlags
|
42
34
|
include Middleware
|
@@ -56,7 +48,7 @@ module Toys
|
|
56
48
|
##
|
57
49
|
# Create a AddVerbosityFlags middleware.
|
58
50
|
#
|
59
|
-
# @param [Boolean,Array<String>,Proc]
|
51
|
+
# @param verbose_flags [Boolean,Array<String>,Proc] Specify flags
|
60
52
|
# to increase verbosity. The value may be any of the following:
|
61
53
|
#
|
62
54
|
# * An array of flags that increase verbosity.
|
@@ -64,7 +56,7 @@ module Toys
|
|
64
56
|
# * The `false` value to disable verbose flags.
|
65
57
|
# * A proc that takes a tool and returns any of the above.
|
66
58
|
#
|
67
|
-
# @param [Boolean,Array<String>,Proc]
|
59
|
+
# @param quiet_flags [Boolean,Array<String>,Proc] Specify flags
|
68
60
|
# to decrease verbosity. The value may be any of the following:
|
69
61
|
#
|
70
62
|
# * An array of flags that decrease verbosity.
|
@@ -79,26 +71,29 @@ module Toys
|
|
79
71
|
|
80
72
|
##
|
81
73
|
# Configure the tool flags.
|
74
|
+
# @private
|
82
75
|
#
|
83
|
-
def config(
|
84
|
-
unless
|
85
|
-
StandardMiddleware.append_common_flag_group(
|
86
|
-
add_verbose_flags(
|
87
|
-
add_quiet_flags(
|
76
|
+
def config(tool, _loader)
|
77
|
+
unless tool.argument_parsing_disabled?
|
78
|
+
StandardMiddleware.append_common_flag_group(tool)
|
79
|
+
add_verbose_flags(tool)
|
80
|
+
add_quiet_flags(tool)
|
88
81
|
end
|
89
82
|
yield
|
90
83
|
end
|
91
84
|
|
92
85
|
private
|
93
86
|
|
94
|
-
|
95
|
-
|
96
|
-
|
87
|
+
INCREMENT_HANDLER = ->(_val, cur) { cur.to_i + 1 }
|
88
|
+
DECREMENT_HANDLER = ->(_val, cur) { cur.to_i - 1 }
|
89
|
+
|
90
|
+
def add_verbose_flags(tool)
|
91
|
+
verbose_flags = resolve_flags_spec(@verbose_flags, tool, DEFAULT_VERBOSE_FLAGS)
|
97
92
|
unless verbose_flags.empty?
|
98
|
-
|
99
|
-
|
93
|
+
tool.add_flag(
|
94
|
+
Context::Key::VERBOSITY, verbose_flags,
|
100
95
|
report_collisions: false,
|
101
|
-
handler:
|
96
|
+
handler: INCREMENT_HANDLER,
|
102
97
|
desc: "Increase verbosity",
|
103
98
|
long_desc: "Increase verbosity, causing additional logging levels to display.",
|
104
99
|
group: StandardMiddleware::COMMON_FLAG_GROUP
|
@@ -106,13 +101,13 @@ module Toys
|
|
106
101
|
end
|
107
102
|
end
|
108
103
|
|
109
|
-
def add_quiet_flags(
|
110
|
-
quiet_flags = resolve_flags_spec(@quiet_flags,
|
104
|
+
def add_quiet_flags(tool)
|
105
|
+
quiet_flags = resolve_flags_spec(@quiet_flags, tool, DEFAULT_QUIET_FLAGS)
|
111
106
|
unless quiet_flags.empty?
|
112
|
-
|
113
|
-
|
107
|
+
tool.add_flag(
|
108
|
+
Context::Key::VERBOSITY, quiet_flags,
|
114
109
|
report_collisions: false,
|
115
|
-
handler:
|
110
|
+
handler: DECREMENT_HANDLER,
|
116
111
|
desc: "Decrease verbosity",
|
117
112
|
long_desc: "Decrease verbosity, causing fewer logging levels to display.",
|
118
113
|
group: StandardMiddleware::COMMON_FLAG_GROUP
|
@@ -1,32 +1,24 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
# Copyright
|
3
|
+
# Copyright 2019 Daniel Azuma
|
4
4
|
#
|
5
|
-
#
|
5
|
+
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
# of this software and associated documentation files (the "Software"), to deal
|
7
|
+
# in the Software without restriction, including without limitation the rights
|
8
|
+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
# copies of the Software, and to permit persons to whom the Software is
|
10
|
+
# furnished to do so, subject to the following conditions:
|
6
11
|
#
|
7
|
-
#
|
8
|
-
#
|
12
|
+
# The above copyright notice and this permission notice shall be included in
|
13
|
+
# all copies or substantial portions of the Software.
|
9
14
|
#
|
10
|
-
#
|
11
|
-
#
|
12
|
-
#
|
13
|
-
#
|
14
|
-
#
|
15
|
-
#
|
16
|
-
#
|
17
|
-
# derived from this software without specific prior written permission.
|
18
|
-
#
|
19
|
-
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
20
|
-
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
21
|
-
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
22
|
-
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
23
|
-
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
24
|
-
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
25
|
-
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
26
|
-
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
27
|
-
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
28
|
-
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
29
|
-
# POSSIBILITY OF SUCH DAMAGE.
|
15
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
20
|
+
# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
21
|
+
# IN THE SOFTWARE.
|
30
22
|
;
|
31
23
|
|
32
24
|
module Toys
|
@@ -40,34 +32,41 @@ module Toys
|
|
40
32
|
class HandleUsageErrors
|
41
33
|
include Middleware
|
42
34
|
|
35
|
+
##
|
36
|
+
# Exit code for usage error. (2 by convention)
|
37
|
+
# @return [Integer]
|
38
|
+
#
|
39
|
+
USAGE_ERROR_EXIT_CODE = 2
|
40
|
+
|
43
41
|
##
|
44
42
|
# Create a HandleUsageErrors middleware.
|
45
43
|
#
|
46
|
-
# @param [
|
47
|
-
# occurs. Default is
|
48
|
-
# @param [IO]
|
49
|
-
# @param [Boolean,nil]
|
44
|
+
# @param exit_code [Integer] The exit code to return if a usage error
|
45
|
+
# occurs. Default is {USAGE_ERROR_EXIT_CODE}.
|
46
|
+
# @param stream [IO] Output stream to write to. Default is stderr.
|
47
|
+
# @param styled_output [Boolean,nil] Cause the tool to display help text
|
50
48
|
# with ansi styles. If `nil`, display styles if the output stream is
|
51
49
|
# a tty. Default is `nil`.
|
52
50
|
#
|
53
|
-
def initialize(exit_code:
|
54
|
-
|
51
|
+
def initialize(exit_code: nil, stream: $stderr, styled_output: nil)
|
52
|
+
require "toys/utils/terminal"
|
53
|
+
@exit_code = exit_code || USAGE_ERROR_EXIT_CODE
|
55
54
|
@terminal = Utils::Terminal.new(output: stream, styled: styled_output)
|
56
55
|
end
|
57
56
|
|
58
57
|
##
|
59
58
|
# Intercept and handle usage errors during execution.
|
59
|
+
# @private
|
60
60
|
#
|
61
|
-
def run(
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
end
|
61
|
+
def run(context)
|
62
|
+
yield
|
63
|
+
rescue ArgParsingError => e
|
64
|
+
require "toys/utils/help_text"
|
65
|
+
help_text = Utils::HelpText.from_context(context)
|
66
|
+
@terminal.puts(e.usage_errors.join("\n"), :bright_red, :bold)
|
67
|
+
@terminal.puts("")
|
68
|
+
@terminal.puts(help_text.usage_string(wrap_width: @terminal.width))
|
69
|
+
Context.exit(@exit_code)
|
71
70
|
end
|
72
71
|
end
|
73
72
|
end
|
@@ -1,32 +1,24 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
# Copyright
|
3
|
+
# Copyright 2019 Daniel Azuma
|
4
4
|
#
|
5
|
-
#
|
5
|
+
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
# of this software and associated documentation files (the "Software"), to deal
|
7
|
+
# in the Software without restriction, including without limitation the rights
|
8
|
+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
# copies of the Software, and to permit persons to whom the Software is
|
10
|
+
# furnished to do so, subject to the following conditions:
|
6
11
|
#
|
7
|
-
#
|
8
|
-
#
|
12
|
+
# The above copyright notice and this permission notice shall be included in
|
13
|
+
# all copies or substantial portions of the Software.
|
9
14
|
#
|
10
|
-
#
|
11
|
-
#
|
12
|
-
#
|
13
|
-
#
|
14
|
-
#
|
15
|
-
#
|
16
|
-
#
|
17
|
-
# derived from this software without specific prior written permission.
|
18
|
-
#
|
19
|
-
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
20
|
-
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
21
|
-
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
22
|
-
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
23
|
-
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
24
|
-
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
25
|
-
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
26
|
-
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
27
|
-
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
28
|
-
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
29
|
-
# POSSIBILITY OF SUCH DAMAGE.
|
15
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
20
|
+
# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
21
|
+
# IN THE SOFTWARE.
|
30
22
|
;
|
31
23
|
|
32
24
|
module Toys
|
@@ -69,41 +61,27 @@ module Toys
|
|
69
61
|
" https://www.rubydoc.info/gems/toys-core for more info.",
|
70
62
|
"To replace this message, set the description and long description" \
|
71
63
|
" of the root tool, or configure the SetDefaultDescriptions" \
|
72
|
-
" middleware."
|
64
|
+
" middleware.",
|
73
65
|
].freeze
|
74
66
|
|
75
|
-
##
|
76
|
-
# A mapping of names for acceptable types
|
77
|
-
# @return [Hash]
|
78
|
-
#
|
79
|
-
ACCEPTABLE_NAMES = {
|
80
|
-
nil => "string",
|
81
|
-
::String => "nonempty string",
|
82
|
-
::TrueClass => "boolean",
|
83
|
-
::FalseClass => "boolean",
|
84
|
-
::OptionParser::DecimalInteger => "decimal integer",
|
85
|
-
::OptionParser::OctalInteger => "octal integer",
|
86
|
-
::OptionParser::DecimalNumeric => "decimal numeric"
|
87
|
-
}.freeze
|
88
|
-
|
89
67
|
##
|
90
68
|
# Create a SetDefaultDescriptions middleware given default descriptions.
|
91
69
|
#
|
92
|
-
# @param [String,nil]
|
70
|
+
# @param default_tool_desc [String,nil] The default short description for
|
93
71
|
# runnable tools, or `nil` not to set one. Defaults to
|
94
72
|
# {DEFAULT_TOOL_DESC}.
|
95
|
-
# @param [String,nil]
|
73
|
+
# @param default_tool_long_desc [String,nil] The default long description
|
96
74
|
# for runnable tools, or `nil` not to set one. Defaults to `nil`.
|
97
|
-
# @param [String,nil]
|
75
|
+
# @param default_namespace_desc [String,nil] The default short
|
98
76
|
# description for non-runnable tools, or `nil` not to set one.
|
99
77
|
# Defaults to {DEFAULT_TOOL_DESC}.
|
100
|
-
# @param [String,nil]
|
78
|
+
# @param default_namespace_long_desc [String,nil] The default long
|
101
79
|
# description for non-runnable tools, or `nil` not to set one.
|
102
80
|
# Defaults to `nil`.
|
103
|
-
# @param [String,nil]
|
81
|
+
# @param default_root_desc [String,nil] The default short description for
|
104
82
|
# the root tool, or `nil` not to set one. Defaults to
|
105
83
|
# {DEFAULT_ROOT_DESC}.
|
106
|
-
# @param [String,nil]
|
84
|
+
# @param default_root_long_desc [String,nil] The default long description
|
107
85
|
# for the root tool, or `nil` not to set one. Defaults to
|
108
86
|
# {DEFAULT_ROOT_LONG_DESC}.
|
109
87
|
#
|
@@ -123,15 +101,20 @@ module Toys
|
|
123
101
|
|
124
102
|
##
|
125
103
|
# Add default description text to tools.
|
104
|
+
# @private
|
126
105
|
#
|
127
106
|
def config(tool, loader)
|
128
107
|
data = {tool: tool, loader: loader}
|
129
|
-
tool.
|
108
|
+
tool.flags.each do |flag|
|
130
109
|
config_desc(flag, generate_flag_desc(flag, data), generate_flag_long_desc(flag, data))
|
131
110
|
end
|
132
|
-
tool.
|
111
|
+
tool.positional_args.each do |arg|
|
133
112
|
config_desc(arg, generate_arg_desc(arg, data), generate_arg_long_desc(arg, data))
|
134
113
|
end
|
114
|
+
tool.flag_groups.each do |flag_group|
|
115
|
+
config_desc(flag_group, generate_flag_group_desc(flag_group, data),
|
116
|
+
generate_flag_group_long_desc(flag_group, data))
|
117
|
+
end
|
135
118
|
config_desc(tool, generate_tool_desc(tool, data), generate_tool_long_desc(tool, data))
|
136
119
|
yield
|
137
120
|
end
|
@@ -143,13 +126,13 @@ module Toys
|
|
143
126
|
# By default, it uses the parameters given to the middleware object.
|
144
127
|
# Override this method to provide different logic.
|
145
128
|
#
|
146
|
-
# @param [Toys::Tool]
|
147
|
-
# @param [Hash]
|
129
|
+
# @param tool [Toys::Tool] The tool to document.
|
130
|
+
# @param data [Hash] Additional data that might be useful. Currently,
|
148
131
|
# the {Toys::Loader} is passed with key `:loader`. Future versions
|
149
132
|
# of Toys may provide additional information.
|
150
|
-
# @return [String,Array<String>,Toys::
|
151
|
-
#
|
152
|
-
#
|
133
|
+
# @return [String,Array<String>,Toys::WrappableString] The default
|
134
|
+
# description. See {Toys::Tool#desc=} for info on the format.
|
135
|
+
# @return [nil] if this middleware should not set the description.
|
153
136
|
#
|
154
137
|
def generate_tool_desc(tool, data)
|
155
138
|
if tool.root?
|
@@ -166,13 +149,14 @@ module Toys
|
|
166
149
|
# By default, it uses the parameters given to the middleware object.
|
167
150
|
# Override this method to provide different logic.
|
168
151
|
#
|
169
|
-
# @param [Toys::Tool]
|
170
|
-
# @param [Hash]
|
171
|
-
# the {Toys::Loader} is passed with key `:loader`. Future versions
|
172
|
-
#
|
173
|
-
# @return [Array<Toys::
|
174
|
-
#
|
175
|
-
#
|
152
|
+
# @param tool [Toys::Tool] The tool to document
|
153
|
+
# @param data [Hash] Additional data that might be useful. Currently,
|
154
|
+
# the {Toys::Loader} is passed with key `:loader`. Future versions of
|
155
|
+
# Toys may provide additional information.
|
156
|
+
# @return [Array<Toys::WrappableString,String,Array<String>>] The default
|
157
|
+
# long description. See {Toys::Tool#long_desc=} for info on the
|
158
|
+
# format.
|
159
|
+
# @return [nil] if this middleware should not set the long description.
|
176
160
|
#
|
177
161
|
def generate_tool_long_desc(tool, data)
|
178
162
|
if tool.root?
|
@@ -188,17 +172,17 @@ module Toys
|
|
188
172
|
# This method implements the logic for generating a flag description.
|
189
173
|
# Override this method to provide different logic.
|
190
174
|
#
|
191
|
-
# @param [Toys::
|
192
|
-
# @param [Hash]
|
193
|
-
# the {Toys::Tool} is passed with key `:tool`. Future
|
194
|
-
# Toys may provide additional information.
|
195
|
-
# @return [String,Array<String>,Toys::
|
196
|
-
#
|
197
|
-
#
|
175
|
+
# @param flag [Toys::Flag] The flag to document
|
176
|
+
# @param data [Hash] Additional data that might be useful. Currently,
|
177
|
+
# the {Toys::Tool} is passed with key `:tool`. Future
|
178
|
+
# versions of Toys may provide additional information.
|
179
|
+
# @return [String,Array<String>,Toys::WrappableString] The default
|
180
|
+
# description. See {Toys::Tool#desc=} for info on the format.
|
181
|
+
# @return [nil] if this middleware should not set the description.
|
198
182
|
#
|
199
183
|
def generate_flag_desc(flag, data) # rubocop:disable Lint/UnusedMethodArgument
|
200
184
|
name = flag.key.to_s.tr("_", "-").gsub(/[^\w-]/, "").downcase.inspect
|
201
|
-
acceptable = flag.flag_type == :value ?
|
185
|
+
acceptable = flag.flag_type == :value ? flag.acceptor.type_desc : "boolean flag"
|
202
186
|
default_clause = flag.default ? " (default is #{flag.default.inspect})" : ""
|
203
187
|
"Sets the #{name} option as type #{acceptable}#{default_clause}."
|
204
188
|
end
|
@@ -207,13 +191,14 @@ module Toys
|
|
207
191
|
# This method implements logic for generating a flag long description.
|
208
192
|
# Override this method to provide different logic.
|
209
193
|
#
|
210
|
-
# @param [Toys::
|
211
|
-
# @param [Hash]
|
194
|
+
# @param flag [Toys::Flag] The flag to document
|
195
|
+
# @param data [Hash] Additional data that might be useful. Currently,
|
212
196
|
# the {Toys::Tool} is passed with key `:tool`. Future versions of
|
213
|
-
# Toys may provide additional information.
|
214
|
-
# @return [Array<Toys::
|
215
|
-
#
|
216
|
-
#
|
197
|
+
# versions of Toys may provide additional information.
|
198
|
+
# @return [Array<Toys::WrappableString,String,Array<String>>] The default
|
199
|
+
# long description. See {Toys::Tool#long_desc=} for info on the
|
200
|
+
# format.
|
201
|
+
# @return [nil] if this middleware should not set the long description.
|
217
202
|
#
|
218
203
|
def generate_flag_long_desc(flag, data) # rubocop:disable Lint/UnusedMethodArgument
|
219
204
|
nil
|
@@ -223,16 +208,16 @@ module Toys
|
|
223
208
|
# This method implements the logic for generating an arg description.
|
224
209
|
# Override this method to provide different logic.
|
225
210
|
#
|
226
|
-
# @param [Toys::
|
227
|
-
# @param [Hash]
|
211
|
+
# @param arg [Toys::PositionalArg] The arg to document
|
212
|
+
# @param data [Hash] Additional data that might be useful. Currently,
|
228
213
|
# the {Toys::Tool} is passed with key `:tool`. Future versions of
|
229
214
|
# Toys may provide additional information.
|
230
|
-
# @return [String,Array<String>,Toys::
|
231
|
-
#
|
232
|
-
#
|
215
|
+
# @return [String,Array<String>,Toys::WrappableString] The default
|
216
|
+
# description. See {Toys::Tool#desc=} for info on the format.
|
217
|
+
# @return [nil] if this middleware should not set the description.
|
233
218
|
#
|
234
219
|
def generate_arg_desc(arg, data) # rubocop:disable Lint/UnusedMethodArgument
|
235
|
-
acceptable =
|
220
|
+
acceptable = arg.acceptor.type_desc
|
236
221
|
default_clause = arg.default ? " (default is #{arg.default.inspect})" : ""
|
237
222
|
case arg.type
|
238
223
|
when :required
|
@@ -248,26 +233,63 @@ module Toys
|
|
248
233
|
# This method implements logic for generating an arg long description.
|
249
234
|
# Override this method to provide different logic.
|
250
235
|
#
|
251
|
-
# @param [Toys::
|
252
|
-
# @param [Hash]
|
236
|
+
# @param arg [Toys::PositionalArg] The arg to document
|
237
|
+
# @param data [Hash] Additional data that might be useful. Currently,
|
253
238
|
# the {Toys::Tool} is passed with key `:tool`. Future versions of
|
254
239
|
# Toys may provide additional information.
|
255
|
-
# @return [Array<Toys::
|
256
|
-
#
|
257
|
-
#
|
240
|
+
# @return [Array<Toys::WrappableString,String,Array<String>>] The default
|
241
|
+
# long description. See {Toys::Tool#long_desc=} for info on the
|
242
|
+
# format.
|
243
|
+
# @return [nil] if this middleware should not set the long description.
|
258
244
|
#
|
259
245
|
def generate_arg_long_desc(arg, data) # rubocop:disable Lint/UnusedMethodArgument
|
260
246
|
nil
|
261
247
|
end
|
262
248
|
|
263
249
|
##
|
264
|
-
#
|
250
|
+
# This method implements the logic for generating a flag group
|
251
|
+
# description. Override this method to provide different logic.
|
265
252
|
#
|
266
|
-
# @param [
|
267
|
-
# @
|
253
|
+
# @param group [Toys::FlagGroup] The flag group to document
|
254
|
+
# @param data [Hash] Additional data that might be useful. Currently,
|
255
|
+
# the {Toys::Tool} is passed with key `:tool`. Future
|
256
|
+
# versions of Toys may provide additional information.
|
257
|
+
# @return [String,Array<String>,Toys::WrappableString] The default
|
258
|
+
# description. See {Toys::Tool#desc=} for info on the format.
|
259
|
+
# @return [nil] if this middleware should not set the description.
|
260
|
+
#
|
261
|
+
def generate_flag_group_desc(group, data) # rubocop:disable Lint/UnusedMethodArgument
|
262
|
+
if group.is_a?(FlagGroup::Required)
|
263
|
+
"Required Flags"
|
264
|
+
else
|
265
|
+
"Flags"
|
266
|
+
end
|
267
|
+
end
|
268
|
+
|
269
|
+
##
|
270
|
+
# This method implements the logic for generating a flag group long
|
271
|
+
# description. Override this method to provide different logic.
|
268
272
|
#
|
269
|
-
|
270
|
-
|
273
|
+
# @param group [Toys::FlagGroup] The flag group to document
|
274
|
+
# @param data [Hash] Additional data that might be useful. Currently,
|
275
|
+
# the {Toys::Tool} is passed with key `:tool`. Future
|
276
|
+
# versions of Toys may provide additional information.
|
277
|
+
# @return [Array<Toys::WrappableString,String,Array<String>>] The default
|
278
|
+
# long description. See {Toys::Tool#long_desc=} for info on the
|
279
|
+
# format.
|
280
|
+
# @return [nil] if this middleware should not set the long description.
|
281
|
+
#
|
282
|
+
def generate_flag_group_long_desc(group, data) # rubocop:disable Lint/UnusedMethodArgument
|
283
|
+
case group
|
284
|
+
when FlagGroup::Required
|
285
|
+
["These flags are required."]
|
286
|
+
when FlagGroup::ExactlyOne
|
287
|
+
["Exactly one of these flags must be set."]
|
288
|
+
when FlagGroup::AtMostOne
|
289
|
+
["At most one of these flags must be set."]
|
290
|
+
when FlagGroup::AtLeastOne
|
291
|
+
["At least one of these flags must be set."]
|
292
|
+
end
|
271
293
|
end
|
272
294
|
|
273
295
|
private
|