toys-core 0.7.0 → 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/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
@@ -1,205 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
# Copyright 2018 Daniel Azuma
|
4
|
-
#
|
5
|
-
# All rights reserved.
|
6
|
-
#
|
7
|
-
# Redistribution and use in source and binary forms, with or without
|
8
|
-
# modification, are permitted provided that the following conditions are met:
|
9
|
-
#
|
10
|
-
# * Redistributions of source code must retain the above copyright notice,
|
11
|
-
# this list of conditions and the following disclaimer.
|
12
|
-
# * Redistributions in binary form must reproduce the above copyright notice,
|
13
|
-
# this list of conditions and the following disclaimer in the documentation
|
14
|
-
# and/or other materials provided with the distribution.
|
15
|
-
# * Neither the name of the copyright holder, nor the names of any other
|
16
|
-
# contributors to this software, may be used to endorse or promote products
|
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.
|
30
|
-
;
|
31
|
-
|
32
|
-
module Toys
|
33
|
-
module Definition
|
34
|
-
##
|
35
|
-
# Representation of a group of flags with the same requirement settings.
|
36
|
-
#
|
37
|
-
class FlagGroup
|
38
|
-
##
|
39
|
-
# Create a flag group.
|
40
|
-
# @private
|
41
|
-
#
|
42
|
-
def initialize(name, desc, long_desc)
|
43
|
-
@name = name
|
44
|
-
@desc = Utils::WrappableString.make(desc || default_desc)
|
45
|
-
@long_desc = Utils::WrappableString.make_array(long_desc || default_long_desc)
|
46
|
-
@flag_definitions = []
|
47
|
-
end
|
48
|
-
|
49
|
-
##
|
50
|
-
# Returns the symbolic name for this group
|
51
|
-
# @return [String,Symbol,nil]
|
52
|
-
#
|
53
|
-
attr_reader :name
|
54
|
-
|
55
|
-
##
|
56
|
-
# Returns the short description string.
|
57
|
-
# @return [Toys::Utils::WrappableString]
|
58
|
-
#
|
59
|
-
attr_reader :desc
|
60
|
-
|
61
|
-
##
|
62
|
-
# Returns the long description strings as an array.
|
63
|
-
# @return [Array<Toys::Utils::WrappableString>]
|
64
|
-
#
|
65
|
-
attr_reader :long_desc
|
66
|
-
|
67
|
-
##
|
68
|
-
# Returns an array of flags that are in this group.
|
69
|
-
# Do not modify the returned array.
|
70
|
-
# @return [Array<Toys::Definition::Flag>]
|
71
|
-
#
|
72
|
-
attr_reader :flag_definitions
|
73
|
-
|
74
|
-
##
|
75
|
-
# Returns true if this group is empty
|
76
|
-
# @return [Boolean]
|
77
|
-
#
|
78
|
-
def empty?
|
79
|
-
flag_definitions.empty?
|
80
|
-
end
|
81
|
-
|
82
|
-
## @private
|
83
|
-
def <<(flag)
|
84
|
-
flag_definitions << flag
|
85
|
-
end
|
86
|
-
|
87
|
-
## @private
|
88
|
-
def default_desc
|
89
|
-
"Flags"
|
90
|
-
end
|
91
|
-
|
92
|
-
## @private
|
93
|
-
def default_long_desc
|
94
|
-
nil
|
95
|
-
end
|
96
|
-
|
97
|
-
## @private
|
98
|
-
def validation_error(_seen)
|
99
|
-
nil
|
100
|
-
end
|
101
|
-
|
102
|
-
##
|
103
|
-
# A FlagGroup containing all required flags
|
104
|
-
#
|
105
|
-
class Required < FlagGroup
|
106
|
-
## @private
|
107
|
-
def validation_error(seen)
|
108
|
-
flag_definitions.each do |flag|
|
109
|
-
unless seen.include?(flag.key)
|
110
|
-
return "Flag \"#{flag.display_name}\" is required"
|
111
|
-
end
|
112
|
-
end
|
113
|
-
nil
|
114
|
-
end
|
115
|
-
|
116
|
-
## @private
|
117
|
-
def default_desc
|
118
|
-
"Required Flags"
|
119
|
-
end
|
120
|
-
|
121
|
-
## @private
|
122
|
-
def default_long_desc
|
123
|
-
"These flags are required."
|
124
|
-
end
|
125
|
-
end
|
126
|
-
|
127
|
-
##
|
128
|
-
# A FlagGroup containing all optional flags
|
129
|
-
#
|
130
|
-
class Optional < FlagGroup
|
131
|
-
end
|
132
|
-
|
133
|
-
##
|
134
|
-
# A FlagGroup in which exactly one flag must be set
|
135
|
-
#
|
136
|
-
class ExactlyOne < FlagGroup
|
137
|
-
## @private
|
138
|
-
def validation_error(seen)
|
139
|
-
set_flag = nil
|
140
|
-
flag_definitions.each do |flag|
|
141
|
-
if seen.include?(flag.key)
|
142
|
-
if set_flag
|
143
|
-
return "Exactly one out of group \"#{desc}\" is required, but both" \
|
144
|
-
" \"#{set_flag.display_name}\" and \"#{flag.display_name}\" were set"
|
145
|
-
else
|
146
|
-
set_flag = flag
|
147
|
-
end
|
148
|
-
end
|
149
|
-
end
|
150
|
-
return "Exactly one out of group \"#{desc}\" is required" unless set_flag
|
151
|
-
nil
|
152
|
-
end
|
153
|
-
|
154
|
-
## @private
|
155
|
-
def default_long_desc
|
156
|
-
"Exactly one of these flags must be set."
|
157
|
-
end
|
158
|
-
end
|
159
|
-
|
160
|
-
##
|
161
|
-
# A FlagGroup in which at most one flag must be set
|
162
|
-
#
|
163
|
-
class AtMostOne < FlagGroup
|
164
|
-
## @private
|
165
|
-
def validation_error(seen)
|
166
|
-
set_flag = nil
|
167
|
-
flag_definitions.each do |flag|
|
168
|
-
if seen.include?(flag.key)
|
169
|
-
if set_flag
|
170
|
-
return "At most one out of group \"#{desc}\" is required, but both" \
|
171
|
-
" \"#{set_flag.display_name}\" and \"#{flag.display_name}\" were set"
|
172
|
-
else
|
173
|
-
set_flag = flag
|
174
|
-
end
|
175
|
-
end
|
176
|
-
end
|
177
|
-
nil
|
178
|
-
end
|
179
|
-
|
180
|
-
## @private
|
181
|
-
def default_long_desc
|
182
|
-
"At most one of these flags must be set."
|
183
|
-
end
|
184
|
-
end
|
185
|
-
|
186
|
-
##
|
187
|
-
# A FlagGroup in which at least one flag must be set
|
188
|
-
#
|
189
|
-
class AtLeastOne < FlagGroup
|
190
|
-
## @private
|
191
|
-
def validation_error(seen)
|
192
|
-
flag_definitions.each do |flag|
|
193
|
-
return nil if seen.include?(flag.key)
|
194
|
-
end
|
195
|
-
"At least one out of group \"#{desc}\" is required"
|
196
|
-
end
|
197
|
-
|
198
|
-
## @private
|
199
|
-
def default_long_desc
|
200
|
-
"At least one of these flags must be set."
|
201
|
-
end
|
202
|
-
end
|
203
|
-
end
|
204
|
-
end
|
205
|
-
end
|
@@ -1,190 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
# Copyright 2018 Daniel Azuma
|
4
|
-
#
|
5
|
-
# All rights reserved.
|
6
|
-
#
|
7
|
-
# Redistribution and use in source and binary forms, with or without
|
8
|
-
# modification, are permitted provided that the following conditions are met:
|
9
|
-
#
|
10
|
-
# * Redistributions of source code must retain the above copyright notice,
|
11
|
-
# this list of conditions and the following disclaimer.
|
12
|
-
# * Redistributions in binary form must reproduce the above copyright notice,
|
13
|
-
# this list of conditions and the following disclaimer in the documentation
|
14
|
-
# and/or other materials provided with the distribution.
|
15
|
-
# * Neither the name of the copyright holder, nor the names of any other
|
16
|
-
# contributors to this software, may be used to endorse or promote products
|
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.
|
30
|
-
;
|
31
|
-
|
32
|
-
module Toys
|
33
|
-
module Definition
|
34
|
-
##
|
35
|
-
# Information about source toys directories and files.
|
36
|
-
#
|
37
|
-
class SourceInfo
|
38
|
-
##
|
39
|
-
# Create a SourceInfo.
|
40
|
-
# @private
|
41
|
-
#
|
42
|
-
def initialize(parent, context_directory, source, source_type, source_name, data_dir_name)
|
43
|
-
@parent = parent
|
44
|
-
@context_directory = context_directory
|
45
|
-
@source = source
|
46
|
-
@source_type = source_type
|
47
|
-
@source_path = source if source.is_a?(::String)
|
48
|
-
@source_proc = source if source.is_a?(::Proc)
|
49
|
-
@source_name = source_name
|
50
|
-
@data_dir =
|
51
|
-
if data_dir_name && @source_path
|
52
|
-
dir = ::File.join(::File.dirname(@source_path), data_dir_name)
|
53
|
-
dir if ::File.directory?(dir) && ::File.readable?(dir)
|
54
|
-
end
|
55
|
-
end
|
56
|
-
|
57
|
-
##
|
58
|
-
# Return the parent SourceInfo, or nil if this is the root.
|
59
|
-
# @return [Toys::Definition::SourceInfo,nil]
|
60
|
-
#
|
61
|
-
attr_reader :parent
|
62
|
-
|
63
|
-
##
|
64
|
-
# Return the context directory path (normally the directory containing
|
65
|
-
# the toplevel toys file or directory). May return nil if there is no
|
66
|
-
# context (e.g. the tool is being defined from a block).
|
67
|
-
# @return [String,nil]
|
68
|
-
#
|
69
|
-
attr_reader :context_directory
|
70
|
-
|
71
|
-
##
|
72
|
-
# Return the source, which may be a path or a proc.
|
73
|
-
# @return [String,Proc]
|
74
|
-
#
|
75
|
-
attr_reader :source
|
76
|
-
|
77
|
-
##
|
78
|
-
# Return the type of source.
|
79
|
-
# @return [:file,:directory,:proc]
|
80
|
-
#
|
81
|
-
attr_reader :source_type
|
82
|
-
|
83
|
-
##
|
84
|
-
# Return the path of the current source file or directory, or nil if this
|
85
|
-
# source is not a file system path.
|
86
|
-
# @return [String,nil]
|
87
|
-
#
|
88
|
-
attr_reader :source_path
|
89
|
-
|
90
|
-
##
|
91
|
-
# Return the source proc, or nil if this source is not a proc.
|
92
|
-
# @return [Proc,nil]
|
93
|
-
#
|
94
|
-
attr_reader :source_proc
|
95
|
-
|
96
|
-
##
|
97
|
-
# Return the user-visible name of this source.
|
98
|
-
# @return [String]
|
99
|
-
#
|
100
|
-
attr_reader :source_name
|
101
|
-
alias to_s source_name
|
102
|
-
|
103
|
-
##
|
104
|
-
# Return the absolute path to the given data file or directory.
|
105
|
-
#
|
106
|
-
# @param [String] path The relative path to find
|
107
|
-
# @param [nil,:file,:directory] type Type of file system object to find,
|
108
|
-
# or nil to return any type.
|
109
|
-
# @return [String,nil] Absolute path of the result, or nil if not found.
|
110
|
-
#
|
111
|
-
def find_data(path, type: nil)
|
112
|
-
if @data_dir
|
113
|
-
full_path = ::File.join(@data_dir, path)
|
114
|
-
case type
|
115
|
-
when :file
|
116
|
-
return full_path if ::File.file?(full_path)
|
117
|
-
when :directory
|
118
|
-
return full_path if ::File.directory?(full_path)
|
119
|
-
else
|
120
|
-
return full_path if ::File.readable?(full_path)
|
121
|
-
end
|
122
|
-
end
|
123
|
-
parent&.find_data(path, type: type)
|
124
|
-
end
|
125
|
-
|
126
|
-
##
|
127
|
-
# Create a child SourceInfo relative to the parent path.
|
128
|
-
# @private
|
129
|
-
#
|
130
|
-
def relative_child(filename, data_dir_name)
|
131
|
-
raise "Cannot create relative child of a proc" unless source_path
|
132
|
-
child_path = ::File.join(source_path, filename)
|
133
|
-
child_path, type = SourceInfo.check_path(child_path, true)
|
134
|
-
return nil unless child_path
|
135
|
-
SourceInfo.new(self, context_directory, child_path, type, child_path, data_dir_name)
|
136
|
-
end
|
137
|
-
|
138
|
-
##
|
139
|
-
# Create a child SourceInfo with an absolute path.
|
140
|
-
# @private
|
141
|
-
#
|
142
|
-
def absolute_child(child_path)
|
143
|
-
child_path, type = SourceInfo.check_path(child_path, false)
|
144
|
-
SourceInfo.new(self, context_directory, child_path, type, child_path, nil)
|
145
|
-
end
|
146
|
-
|
147
|
-
##
|
148
|
-
# Create a root source info for a file path.
|
149
|
-
# @private
|
150
|
-
#
|
151
|
-
def self.create_path_root(source_path)
|
152
|
-
source_path, type = check_path(source_path, false)
|
153
|
-
context_directory = ::File.dirname(source_path)
|
154
|
-
new(nil, context_directory, source_path, type, source_path, nil)
|
155
|
-
end
|
156
|
-
|
157
|
-
##
|
158
|
-
# Create a root source info for a proc.
|
159
|
-
# @private
|
160
|
-
#
|
161
|
-
def self.create_proc_root(source_proc, source_name)
|
162
|
-
new(nil, nil, source_proc, :proc, source_name, nil)
|
163
|
-
end
|
164
|
-
|
165
|
-
##
|
166
|
-
# Check a path and determine the canonical path and type.
|
167
|
-
# @private
|
168
|
-
#
|
169
|
-
def self.check_path(path, lenient)
|
170
|
-
path = ::File.expand_path(path)
|
171
|
-
unless ::File.readable?(path)
|
172
|
-
raise LoaderError, "Cannot read: #{path}" unless lenient
|
173
|
-
return [nil, nil]
|
174
|
-
end
|
175
|
-
if ::File.file?(path)
|
176
|
-
unless ::File.extname(path) == ".rb"
|
177
|
-
raise LoaderError, "File is not a ruby file: #{path}" unless lenient
|
178
|
-
return [nil, nil]
|
179
|
-
end
|
180
|
-
[path, :file]
|
181
|
-
elsif ::File.directory?(path)
|
182
|
-
[path, :directory]
|
183
|
-
else
|
184
|
-
raise LoaderError, "Unknown type: #{path}" unless lenient
|
185
|
-
[nil, nil]
|
186
|
-
end
|
187
|
-
end
|
188
|
-
end
|
189
|
-
end
|
190
|
-
end
|
data/lib/toys/definition/tool.rb
DELETED
@@ -1,842 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
# Copyright 2018 Daniel Azuma
|
4
|
-
#
|
5
|
-
# All rights reserved.
|
6
|
-
#
|
7
|
-
# Redistribution and use in source and binary forms, with or without
|
8
|
-
# modification, are permitted provided that the following conditions are met:
|
9
|
-
#
|
10
|
-
# * Redistributions of source code must retain the above copyright notice,
|
11
|
-
# this list of conditions and the following disclaimer.
|
12
|
-
# * Redistributions in binary form must reproduce the above copyright notice,
|
13
|
-
# this list of conditions and the following disclaimer in the documentation
|
14
|
-
# and/or other materials provided with the distribution.
|
15
|
-
# * Neither the name of the copyright holder, nor the names of any other
|
16
|
-
# contributors to this software, may be used to endorse or promote products
|
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.
|
30
|
-
;
|
31
|
-
|
32
|
-
require "optparse"
|
33
|
-
require "set"
|
34
|
-
|
35
|
-
module Toys
|
36
|
-
module Definition
|
37
|
-
##
|
38
|
-
# A Tool is a single command that can be invoked using Toys.
|
39
|
-
# It has a name, a series of one or more words that you use to identify
|
40
|
-
# the tool on the command line. It also has a set of formal flags and
|
41
|
-
# command line arguments supported, and a block that gets run when the
|
42
|
-
# tool is executed.
|
43
|
-
#
|
44
|
-
class Tool
|
45
|
-
##
|
46
|
-
# Built-in acceptors (i.e. those recognized by OptionParser).
|
47
|
-
# You can reference these acceptors directly. Otherwise, you have to add
|
48
|
-
# one explicitly to the tool using {Tool#add_acceptor}.
|
49
|
-
#
|
50
|
-
OPTPARSER_ACCEPTORS = ::Set.new(
|
51
|
-
[
|
52
|
-
::Object,
|
53
|
-
::NilClass,
|
54
|
-
::String,
|
55
|
-
::Integer,
|
56
|
-
::Float,
|
57
|
-
::Numeric,
|
58
|
-
::TrueClass,
|
59
|
-
::FalseClass,
|
60
|
-
::Array,
|
61
|
-
::Regexp,
|
62
|
-
::OptionParser::DecimalInteger,
|
63
|
-
::OptionParser::OctalInteger,
|
64
|
-
::OptionParser::DecimalNumeric
|
65
|
-
]
|
66
|
-
).freeze
|
67
|
-
|
68
|
-
##
|
69
|
-
# Create a new tool.
|
70
|
-
# @private
|
71
|
-
#
|
72
|
-
def initialize(loader, parent, full_name, priority, middleware_stack)
|
73
|
-
@parent = parent
|
74
|
-
@full_name = full_name.dup.freeze
|
75
|
-
@priority = priority
|
76
|
-
@middleware_stack = middleware_stack
|
77
|
-
|
78
|
-
@acceptors = {}
|
79
|
-
@mixins = {}
|
80
|
-
@templates = {}
|
81
|
-
|
82
|
-
reset_definition(loader)
|
83
|
-
end
|
84
|
-
|
85
|
-
##
|
86
|
-
# Reset the definition of this tool, deleting all definition data but
|
87
|
-
# leaving named acceptors, mixins, and templates intact.
|
88
|
-
# Should be called only from the DSL.
|
89
|
-
# @private
|
90
|
-
#
|
91
|
-
def reset_definition(loader)
|
92
|
-
@tool_class = DSL::Tool.new_class(@full_name, @priority, loader)
|
93
|
-
|
94
|
-
@source_info = nil
|
95
|
-
@definition_finished = false
|
96
|
-
|
97
|
-
@desc = Utils::WrappableString.new("")
|
98
|
-
@long_desc = []
|
99
|
-
|
100
|
-
@default_data = {}
|
101
|
-
@used_flags = []
|
102
|
-
@initializers = []
|
103
|
-
|
104
|
-
default_flag_group = Definition::FlagGroup.new(nil, nil, nil)
|
105
|
-
@flag_groups = [default_flag_group]
|
106
|
-
@flag_group_names = {nil => default_flag_group}
|
107
|
-
|
108
|
-
@flag_definitions = []
|
109
|
-
@required_arg_definitions = []
|
110
|
-
@optional_arg_definitions = []
|
111
|
-
@remaining_args_definition = nil
|
112
|
-
|
113
|
-
@disable_argument_parsing = false
|
114
|
-
@includes_modules = false
|
115
|
-
@custom_context_directory = nil
|
116
|
-
end
|
117
|
-
|
118
|
-
##
|
119
|
-
# Return the name of the tool as an array of strings.
|
120
|
-
# This array may not be modified.
|
121
|
-
# @return [Array<String>]
|
122
|
-
#
|
123
|
-
attr_reader :full_name
|
124
|
-
|
125
|
-
##
|
126
|
-
# Return the priority of this tool definition.
|
127
|
-
# @return [Integer]
|
128
|
-
#
|
129
|
-
attr_reader :priority
|
130
|
-
|
131
|
-
##
|
132
|
-
# Return the tool class.
|
133
|
-
# @return [Class]
|
134
|
-
#
|
135
|
-
attr_reader :tool_class
|
136
|
-
|
137
|
-
##
|
138
|
-
# Returns the short description string.
|
139
|
-
# @return [Toys::Utils::WrappableString]
|
140
|
-
#
|
141
|
-
attr_reader :desc
|
142
|
-
|
143
|
-
##
|
144
|
-
# Returns the long description strings as an array.
|
145
|
-
# @return [Array<Toys::Utils::WrappableString>]
|
146
|
-
#
|
147
|
-
attr_reader :long_desc
|
148
|
-
|
149
|
-
##
|
150
|
-
# Return a list of all defined flag groups, in order.
|
151
|
-
# @return [Array<Toys::Definition::FlagGroup>]
|
152
|
-
#
|
153
|
-
attr_reader :flag_groups
|
154
|
-
|
155
|
-
##
|
156
|
-
# Return a list of all defined flags.
|
157
|
-
# @return [Array<Toys::Definition::Flag>]
|
158
|
-
#
|
159
|
-
attr_reader :flag_definitions
|
160
|
-
|
161
|
-
##
|
162
|
-
# Return a list of all defined required positional arguments.
|
163
|
-
# @return [Array<Toys::Definition::Arg>]
|
164
|
-
#
|
165
|
-
attr_reader :required_arg_definitions
|
166
|
-
|
167
|
-
##
|
168
|
-
# Return a list of all defined optional positional arguments.
|
169
|
-
# @return [Array<Toys::Definition::Arg>]
|
170
|
-
#
|
171
|
-
attr_reader :optional_arg_definitions
|
172
|
-
|
173
|
-
##
|
174
|
-
# Return the remaining arguments specification, or `nil` if remaining
|
175
|
-
# arguments are currently not supported by this tool.
|
176
|
-
# @return [Toys::Definition::Arg,nil]
|
177
|
-
#
|
178
|
-
attr_reader :remaining_args_definition
|
179
|
-
|
180
|
-
##
|
181
|
-
# Return a list of flags that have been used in the flag definitions.
|
182
|
-
# @return [Array<String>]
|
183
|
-
#
|
184
|
-
attr_reader :used_flags
|
185
|
-
|
186
|
-
##
|
187
|
-
# Return the default argument data.
|
188
|
-
# @return [Hash]
|
189
|
-
#
|
190
|
-
attr_reader :default_data
|
191
|
-
|
192
|
-
##
|
193
|
-
# Returns the middleware stack
|
194
|
-
# @return [Array<Object>]
|
195
|
-
#
|
196
|
-
attr_reader :middleware_stack
|
197
|
-
|
198
|
-
##
|
199
|
-
# Returns info on the source of this tool, or nil if the source is not
|
200
|
-
# defined.
|
201
|
-
# @return [Toys::Definition::SourceInfo,nil]
|
202
|
-
#
|
203
|
-
attr_reader :source_info
|
204
|
-
|
205
|
-
##
|
206
|
-
# Returns the custom context directory set for this tool, or nil if none
|
207
|
-
# is set.
|
208
|
-
# @return [String,nil]
|
209
|
-
#
|
210
|
-
attr_reader :custom_context_directory
|
211
|
-
|
212
|
-
##
|
213
|
-
# Returns the local name of this tool.
|
214
|
-
# @return [String]
|
215
|
-
#
|
216
|
-
def simple_name
|
217
|
-
full_name.last
|
218
|
-
end
|
219
|
-
|
220
|
-
##
|
221
|
-
# Returns a displayable name of this tool, generally the full name
|
222
|
-
# delimited by spaces.
|
223
|
-
# @return [String]
|
224
|
-
#
|
225
|
-
def display_name
|
226
|
-
full_name.join(" ")
|
227
|
-
end
|
228
|
-
|
229
|
-
##
|
230
|
-
# Returns true if this tool is a root tool.
|
231
|
-
# @return [Boolean]
|
232
|
-
#
|
233
|
-
def root?
|
234
|
-
full_name.empty?
|
235
|
-
end
|
236
|
-
|
237
|
-
##
|
238
|
-
# Returns true if this tool is marked as runnable.
|
239
|
-
# @return [Boolean]
|
240
|
-
#
|
241
|
-
def runnable?
|
242
|
-
tool_class.public_instance_methods(false).include?(:run)
|
243
|
-
end
|
244
|
-
|
245
|
-
##
|
246
|
-
# Returns true if this tool has at least one included module.
|
247
|
-
# @return [Boolean]
|
248
|
-
#
|
249
|
-
def includes_modules?
|
250
|
-
@includes_modules
|
251
|
-
end
|
252
|
-
|
253
|
-
##
|
254
|
-
# Returns true if there is a specific description set for this tool.
|
255
|
-
# @return [Boolean]
|
256
|
-
#
|
257
|
-
def includes_description?
|
258
|
-
!long_desc.empty? || !desc.empty?
|
259
|
-
end
|
260
|
-
|
261
|
-
##
|
262
|
-
# Returns true if at least one flag or positional argument is defined
|
263
|
-
# for this tool.
|
264
|
-
# @return [Boolean]
|
265
|
-
#
|
266
|
-
def includes_arguments?
|
267
|
-
!default_data.empty? || !flag_definitions.empty? ||
|
268
|
-
!required_arg_definitions.empty? || !optional_arg_definitions.empty? ||
|
269
|
-
!remaining_args_definition.nil?
|
270
|
-
end
|
271
|
-
|
272
|
-
##
|
273
|
-
# Returns true if this tool has any definition information.
|
274
|
-
# @return [Boolean]
|
275
|
-
#
|
276
|
-
def includes_definition?
|
277
|
-
includes_arguments? || runnable? || argument_parsing_disabled? ||
|
278
|
-
includes_modules? || includes_description?
|
279
|
-
end
|
280
|
-
|
281
|
-
##
|
282
|
-
# Returns true if this tool's definition has been finished and is locked.
|
283
|
-
# @return [Boolean]
|
284
|
-
#
|
285
|
-
def definition_finished?
|
286
|
-
@definition_finished
|
287
|
-
end
|
288
|
-
|
289
|
-
##
|
290
|
-
# Returns true if this tool has disabled argument parsing.
|
291
|
-
# @return [Boolean]
|
292
|
-
#
|
293
|
-
def argument_parsing_disabled?
|
294
|
-
@disable_argument_parsing
|
295
|
-
end
|
296
|
-
|
297
|
-
##
|
298
|
-
# Returns all arg definitions in order: required, optional, remaining.
|
299
|
-
# @return [Array<Toys::Definition::Arg>]
|
300
|
-
#
|
301
|
-
def arg_definitions
|
302
|
-
result = required_arg_definitions + optional_arg_definitions
|
303
|
-
result << remaining_args_definition if remaining_args_definition
|
304
|
-
result
|
305
|
-
end
|
306
|
-
|
307
|
-
##
|
308
|
-
# Returns a list of all custom acceptors used by this tool.
|
309
|
-
# @return [Array<Toys::Definition::Acceptor>]
|
310
|
-
#
|
311
|
-
def custom_acceptors
|
312
|
-
result = []
|
313
|
-
flag_definitions.each do |f|
|
314
|
-
result << f.accept if f.accept.is_a?(Acceptor)
|
315
|
-
end
|
316
|
-
arg_definitions.each do |a|
|
317
|
-
result << a.accept if a.accept.is_a?(Acceptor)
|
318
|
-
end
|
319
|
-
result.uniq
|
320
|
-
end
|
321
|
-
|
322
|
-
##
|
323
|
-
# Resolve the given acceptor. You may pass in a
|
324
|
-
# {Toys::Definition::Acceptor}, an acceptor name, a well-known acceptor
|
325
|
-
# understood by OptionParser, or `nil`.
|
326
|
-
#
|
327
|
-
# Returns either `nil` or an acceptor that is usable by OptionParser.
|
328
|
-
#
|
329
|
-
# If an acceptor name is given, it may be resolved by this tool or any of
|
330
|
-
# its ancestors. Raises {Toys::ToolDefinitionError} if the name is not
|
331
|
-
# recognized.
|
332
|
-
#
|
333
|
-
# @param [Object] accept An acceptor input.
|
334
|
-
# @return [Object] The resolved acceptor.
|
335
|
-
#
|
336
|
-
def resolve_acceptor(accept)
|
337
|
-
return accept if accept.nil? || accept.is_a?(Acceptor)
|
338
|
-
name = accept
|
339
|
-
accept = @acceptors.fetch(name) do |k|
|
340
|
-
if @parent
|
341
|
-
@parent.resolve_acceptor(k)
|
342
|
-
elsif OPTPARSER_ACCEPTORS.include?(k)
|
343
|
-
k
|
344
|
-
end
|
345
|
-
end
|
346
|
-
if accept.nil?
|
347
|
-
raise ToolDefinitionError, "Unknown acceptor: #{name.inspect}"
|
348
|
-
end
|
349
|
-
accept
|
350
|
-
end
|
351
|
-
|
352
|
-
##
|
353
|
-
# Get the named template from this tool or its ancestors.
|
354
|
-
#
|
355
|
-
# @param [String] name The template name
|
356
|
-
# @return [Class,nil] The template class, or `nil` if not found.
|
357
|
-
#
|
358
|
-
def resolve_template(name)
|
359
|
-
@templates.fetch(name.to_s) { |k| @parent ? @parent.resolve_template(k) : nil }
|
360
|
-
end
|
361
|
-
|
362
|
-
##
|
363
|
-
# Get the named mixin from this tool or its ancestors.
|
364
|
-
#
|
365
|
-
# @param [String] name The mixin name
|
366
|
-
# @return [Module,nil] The mixin module, or `nil` if not found.
|
367
|
-
#
|
368
|
-
def resolve_mixin(name)
|
369
|
-
@mixins.fetch(name.to_s) { |k| @parent ? @parent.resolve_mixin(k) : nil }
|
370
|
-
end
|
371
|
-
|
372
|
-
##
|
373
|
-
# Include the given mixin in the tool class.
|
374
|
-
#
|
375
|
-
# @param [String,Symbol,Module] name The mixin name or module
|
376
|
-
#
|
377
|
-
def include_mixin(name)
|
378
|
-
tool_class.include(name)
|
379
|
-
self
|
380
|
-
end
|
381
|
-
|
382
|
-
##
|
383
|
-
# Sets the path to the file that defines this tool.
|
384
|
-
# A tool may be defined from at most one path. If a different path is
|
385
|
-
# already set, raises {Toys::ToolDefinitionError}
|
386
|
-
#
|
387
|
-
# @param [Toys::Definition::SourceInfo] source Source info
|
388
|
-
#
|
389
|
-
def lock_source(source)
|
390
|
-
if source_info && source_info.source != source.source
|
391
|
-
raise ToolDefinitionError,
|
392
|
-
"Cannot redefine tool #{display_name.inspect} in #{source.source_name}" \
|
393
|
-
" (already defined in #{source_info.source_name})"
|
394
|
-
end
|
395
|
-
@source_info = source
|
396
|
-
end
|
397
|
-
|
398
|
-
##
|
399
|
-
# Set the short description string.
|
400
|
-
#
|
401
|
-
# The description may be provided as a {Toys::Utils::WrappableString}, a
|
402
|
-
# single string (which will be wrapped), or an array of strings, which will
|
403
|
-
# be interpreted as string fragments that will be concatenated and wrapped.
|
404
|
-
#
|
405
|
-
# @param [Toys::Utils::WrappableString,String,Array<String>] desc
|
406
|
-
#
|
407
|
-
def desc=(desc)
|
408
|
-
check_definition_state
|
409
|
-
@desc = Utils::WrappableString.make(desc)
|
410
|
-
end
|
411
|
-
|
412
|
-
##
|
413
|
-
# Set the long description strings.
|
414
|
-
#
|
415
|
-
# Each string may be provided as a {Toys::Utils::WrappableString}, a single
|
416
|
-
# string (which will be wrapped), or an array of strings, which will be
|
417
|
-
# interpreted as string fragments that will be concatenated and wrapped.
|
418
|
-
#
|
419
|
-
# @param [Array<Toys::Utils::WrappableString,String,Array<String>>] long_desc
|
420
|
-
#
|
421
|
-
def long_desc=(long_desc)
|
422
|
-
check_definition_state
|
423
|
-
@long_desc = Utils::WrappableString.make_array(long_desc)
|
424
|
-
end
|
425
|
-
|
426
|
-
##
|
427
|
-
# Append long description strings.
|
428
|
-
#
|
429
|
-
# Each string may be provided as a {Toys::Utils::WrappableString}, a single
|
430
|
-
# string (which will be wrapped), or an array of strings, which will be
|
431
|
-
# interpreted as string fragments that will be concatenated and wrapped.
|
432
|
-
#
|
433
|
-
# @param [Array<Toys::Utils::WrappableString,String,Array<String>>] long_desc
|
434
|
-
#
|
435
|
-
def append_long_desc(long_desc)
|
436
|
-
check_definition_state
|
437
|
-
@long_desc += Utils::WrappableString.make_array(long_desc)
|
438
|
-
end
|
439
|
-
|
440
|
-
##
|
441
|
-
# Add an acceptor to the tool. This acceptor may be refereneced by name
|
442
|
-
# when adding a flag or an arg.
|
443
|
-
#
|
444
|
-
# @param [Toys::Definition::Acceptor] acceptor The acceptor to add.
|
445
|
-
#
|
446
|
-
def add_acceptor(acceptor)
|
447
|
-
if @acceptors.key?(acceptor.name)
|
448
|
-
raise ToolDefinitionError,
|
449
|
-
"An acceptor named #{acceptor.name.inspect} has already been" \
|
450
|
-
" defined in tool #{display_name.inspect}."
|
451
|
-
end
|
452
|
-
@acceptors[acceptor.name] = acceptor
|
453
|
-
self
|
454
|
-
end
|
455
|
-
|
456
|
-
##
|
457
|
-
# Add a named mixin module to this tool.
|
458
|
-
#
|
459
|
-
# @param [String] name The name of the mixin.
|
460
|
-
# @param [Module] mixin_module The mixin module.
|
461
|
-
#
|
462
|
-
def add_mixin(name, mixin_module)
|
463
|
-
name = name.to_s
|
464
|
-
if @mixins.key?(name)
|
465
|
-
raise ToolDefinitionError,
|
466
|
-
"A mixin named #{name.inspect} has already been defined in tool" \
|
467
|
-
" #{display_name.inspect}."
|
468
|
-
end
|
469
|
-
@mixins[name] = mixin_module
|
470
|
-
self
|
471
|
-
end
|
472
|
-
|
473
|
-
##
|
474
|
-
# Add a named template class to this tool.
|
475
|
-
#
|
476
|
-
# @param [String] name The name of the template.
|
477
|
-
# @param [Class] template_class The template class.
|
478
|
-
#
|
479
|
-
def add_template(name, template_class)
|
480
|
-
name = name.to_s
|
481
|
-
if @templates.key?(name)
|
482
|
-
raise ToolDefinitionError,
|
483
|
-
"A template named #{name.inspect} has already been defined in tool" \
|
484
|
-
" #{display_name.inspect}."
|
485
|
-
end
|
486
|
-
@templates[name] = template_class
|
487
|
-
self
|
488
|
-
end
|
489
|
-
|
490
|
-
##
|
491
|
-
# Disable argument parsing for this tool
|
492
|
-
#
|
493
|
-
def disable_argument_parsing
|
494
|
-
check_definition_state
|
495
|
-
if includes_arguments?
|
496
|
-
raise ToolDefinitionError,
|
497
|
-
"Cannot disable argument parsing for tool #{display_name.inspect}" \
|
498
|
-
" because arguments have already been defined."
|
499
|
-
end
|
500
|
-
@disable_argument_parsing = true
|
501
|
-
self
|
502
|
-
end
|
503
|
-
|
504
|
-
##
|
505
|
-
# Add a flag group to the group list.
|
506
|
-
#
|
507
|
-
# @param [Symbol] type The type of group. Allowed values: `:required`,
|
508
|
-
# `:optional`, `:exactly_one`, `:at_most_one`, `:at_least_one`.
|
509
|
-
# Default is `:optional`.
|
510
|
-
# @param [String,Array<String>,Toys::Utils::WrappableString] desc Short
|
511
|
-
# description for the group. See {Toys::Definition::Tool#desc=} for a
|
512
|
-
# description of allowed formats. Defaults to `"Flags"`.
|
513
|
-
# @param [Array<String,Array<String>,Toys::Utils::WrappableString>] long_desc
|
514
|
-
# Long description for the flag group. See
|
515
|
-
# {Toys::Definition::Tool#long_desc=} for a description of allowed
|
516
|
-
# formats. Defaults to the empty array.
|
517
|
-
# @param [String,Symbol,nil] name The name of the group, or nil for no
|
518
|
-
# name.
|
519
|
-
# @param [Boolean] report_collisions If `true`, raise an exception if a
|
520
|
-
# the given name is already taken. If `false`, ignore. Default is
|
521
|
-
# `true`.
|
522
|
-
# @param [Boolean] prepend If `true`, prepend rather than append the
|
523
|
-
# group to the list. Default is `false`.
|
524
|
-
#
|
525
|
-
def add_flag_group(type: :optional, desc: nil, long_desc: nil,
|
526
|
-
name: nil, report_collisions: true, prepend: false)
|
527
|
-
if !name.nil? && @flag_group_names.key?(name)
|
528
|
-
return self unless report_collisions
|
529
|
-
raise ToolDefinitionError, "Flag group #{name} already exists"
|
530
|
-
end
|
531
|
-
unless type.is_a?(::Class)
|
532
|
-
type = Utils::ModuleLookup.to_module_name(type)
|
533
|
-
type = Definition::FlagGroup.const_get(type)
|
534
|
-
end
|
535
|
-
group = type.new(name, desc, long_desc)
|
536
|
-
@flag_group_names[name] = group unless name.nil?
|
537
|
-
if prepend
|
538
|
-
@flag_groups.unshift(group)
|
539
|
-
else
|
540
|
-
@flag_groups.push(group)
|
541
|
-
end
|
542
|
-
self
|
543
|
-
end
|
544
|
-
|
545
|
-
##
|
546
|
-
# Add a flag to the current tool. Each flag must specify a key which
|
547
|
-
# the script may use to obtain the flag value from the context.
|
548
|
-
# You may then provide the flags themselves in `OptionParser` form.
|
549
|
-
#
|
550
|
-
# @param [String,Symbol] key The key to use to retrieve the value from
|
551
|
-
# the execution context.
|
552
|
-
# @param [Array<String>] flags The flags in OptionParser format.
|
553
|
-
# @param [Object] accept An acceptor that validates and/or converts the
|
554
|
-
# value. You may provide either the name of an acceptor you have
|
555
|
-
# defined, or one of the default acceptors provided by OptionParser.
|
556
|
-
# Optional. If not specified, accepts any value as a string.
|
557
|
-
# @param [Object] default The default value. This is the value that will
|
558
|
-
# be set in the context if this flag is not provided on the command
|
559
|
-
# line. Defaults to `nil`.
|
560
|
-
# @param [Proc,nil] handler An optional handler for setting/updating the
|
561
|
-
# value. If given, it should take two arguments, the new given value
|
562
|
-
# and the previous value, and it should return the new value that
|
563
|
-
# should be set. The default handler simply replaces the previous
|
564
|
-
# value. i.e. the default is effectively `-> (val, _prev) { val }`.
|
565
|
-
# @param [Boolean] report_collisions Raise an exception if a flag is
|
566
|
-
# requested that is already in use or marked as disabled. Default is
|
567
|
-
# true.
|
568
|
-
# @param [Toys::Definition::FlagGroup,String,Symbol,nil] group Group for
|
569
|
-
# this flag. You may provide a group name, a FlagGroup object, or
|
570
|
-
# `nil` which denotes the default group.
|
571
|
-
# @param [String,Array<String>,Toys::Utils::WrappableString] desc Short
|
572
|
-
# description for the flag. See {Toys::Definition::Tool#desc=} for a
|
573
|
-
# description of allowed formats. Defaults to the empty string.
|
574
|
-
# @param [Array<String,Array<String>,Toys::Utils::WrappableString>] long_desc
|
575
|
-
# Long description for the flag. See
|
576
|
-
# {Toys::Definition::Tool#long_desc=} for a description of allowed
|
577
|
-
# formats. Defaults to the empty array.
|
578
|
-
# @param [String] display_name A display name for this flag, used in help
|
579
|
-
# text and error messages.
|
580
|
-
#
|
581
|
-
def add_flag(key, flags = [],
|
582
|
-
accept: nil, default: nil, handler: nil,
|
583
|
-
report_collisions: true, group: nil,
|
584
|
-
desc: nil, long_desc: nil, display_name: nil)
|
585
|
-
unless group.is_a?(Definition::FlagGroup)
|
586
|
-
group_name = group
|
587
|
-
group = @flag_group_names[group_name]
|
588
|
-
raise ToolDefinitionError, "No such flag group: #{group_name.inspect}" if group.nil?
|
589
|
-
end
|
590
|
-
check_definition_state(is_arg: true)
|
591
|
-
accept = resolve_acceptor(accept)
|
592
|
-
flag_def = Definition::Flag.new(key, flags, @used_flags, report_collisions,
|
593
|
-
accept, handler, default, display_name, group)
|
594
|
-
flag_def.desc = desc if desc
|
595
|
-
flag_def.long_desc = long_desc if long_desc
|
596
|
-
if flag_def.active?
|
597
|
-
@flag_definitions << flag_def
|
598
|
-
group << flag_def
|
599
|
-
end
|
600
|
-
@default_data[key] = default
|
601
|
-
self
|
602
|
-
end
|
603
|
-
|
604
|
-
##
|
605
|
-
# Mark one or more flags as disabled, preventing their use by any
|
606
|
-
# subsequent flag definition. This may be used to prevent middleware from
|
607
|
-
# defining a particular flag.
|
608
|
-
#
|
609
|
-
# @param [String...] flags The flags to disable
|
610
|
-
#
|
611
|
-
def disable_flag(*flags)
|
612
|
-
check_definition_state(is_arg: true)
|
613
|
-
flags = flags.uniq
|
614
|
-
intersection = @used_flags & flags
|
615
|
-
unless intersection.empty?
|
616
|
-
raise ToolDefinitionError, "Cannot disable flags already used: #{intersection.inspect}"
|
617
|
-
end
|
618
|
-
@used_flags.concat(flags)
|
619
|
-
self
|
620
|
-
end
|
621
|
-
|
622
|
-
##
|
623
|
-
# Add a required positional argument to the current tool. You must specify
|
624
|
-
# a key which the script may use to obtain the argument value from the
|
625
|
-
# context.
|
626
|
-
#
|
627
|
-
# @param [String,Symbol] key The key to use to retrieve the value from
|
628
|
-
# the execution context.
|
629
|
-
# @param [Object] accept An acceptor that validates and/or converts the
|
630
|
-
# value. You may provide either the name of an acceptor you have
|
631
|
-
# defined, or one of the default acceptors provided by OptionParser.
|
632
|
-
# Optional. If not specified, accepts any value as a string.
|
633
|
-
# @param [String] display_name A name to use for display (in help text and
|
634
|
-
# error reports). Defaults to the key in upper case.
|
635
|
-
# @param [String,Array<String>,Toys::Utils::WrappableString] desc Short
|
636
|
-
# description for the arg. See {Toys::Definition::Tool#desc=} for a
|
637
|
-
# description of allowed formats. Defaults to the empty string.
|
638
|
-
# @param [Array<String,Array<String>,Toys::Utils::WrappableString>] long_desc
|
639
|
-
# Long description for the arg. See
|
640
|
-
# {Toys::Definition::Tool#long_desc=} for a description of allowed
|
641
|
-
# formats. Defaults to the empty array.
|
642
|
-
#
|
643
|
-
def add_required_arg(key, accept: nil, display_name: nil, desc: nil, long_desc: nil)
|
644
|
-
check_definition_state(is_arg: true)
|
645
|
-
accept = resolve_acceptor(accept)
|
646
|
-
arg_def = Definition::Arg.new(key, :required, accept, nil, desc, long_desc, display_name)
|
647
|
-
@required_arg_definitions << arg_def
|
648
|
-
self
|
649
|
-
end
|
650
|
-
|
651
|
-
##
|
652
|
-
# Add an optional positional argument to the current tool. You must specify
|
653
|
-
# a key which the script may use to obtain the argument value from the
|
654
|
-
# context. If an optional argument is not given on the command line, the
|
655
|
-
# value is set to the given default.
|
656
|
-
#
|
657
|
-
# @param [String,Symbol] key The key to use to retrieve the value from
|
658
|
-
# the execution context.
|
659
|
-
# @param [Object] default The default value. This is the value that will
|
660
|
-
# be set in the context if this argument is not provided on the command
|
661
|
-
# line. Defaults to `nil`.
|
662
|
-
# @param [Object] accept An acceptor that validates and/or converts the
|
663
|
-
# value. You may provide either the name of an acceptor you have
|
664
|
-
# defined, or one of the default acceptors provided by OptionParser.
|
665
|
-
# Optional. If not specified, accepts any value as a string.
|
666
|
-
# @param [String] display_name A name to use for display (in help text and
|
667
|
-
# error reports). Defaults to the key in upper case.
|
668
|
-
# @param [String,Array<String>,Toys::Utils::WrappableString] desc Short
|
669
|
-
# description for the arg. See {Toys::Definition::Tool#desc=} for a
|
670
|
-
# description of allowed formats. Defaults to the empty string.
|
671
|
-
# @param [Array<String,Array<String>,Toys::Utils::WrappableString>] long_desc
|
672
|
-
# Long description for the arg. See
|
673
|
-
# {Toys::Definition::Tool#long_desc=} for a description of allowed
|
674
|
-
# formats. Defaults to the empty array.
|
675
|
-
#
|
676
|
-
def add_optional_arg(key, default: nil, accept: nil, display_name: nil,
|
677
|
-
desc: nil, long_desc: nil)
|
678
|
-
check_definition_state(is_arg: true)
|
679
|
-
accept = resolve_acceptor(accept)
|
680
|
-
arg_def = Definition::Arg.new(key, :optional, accept, default,
|
681
|
-
desc, long_desc, display_name)
|
682
|
-
@optional_arg_definitions << arg_def
|
683
|
-
@default_data[key] = default
|
684
|
-
self
|
685
|
-
end
|
686
|
-
|
687
|
-
##
|
688
|
-
# Specify what should be done with unmatched positional arguments. You must
|
689
|
-
# specify a key which the script may use to obtain the remaining args
|
690
|
-
# from the context.
|
691
|
-
#
|
692
|
-
# @param [String,Symbol] key The key to use to retrieve the value from
|
693
|
-
# the execution context.
|
694
|
-
# @param [Object] default The default value. This is the value that will
|
695
|
-
# be set in the context if no unmatched arguments are provided on the
|
696
|
-
# command line. Defaults to the empty array `[]`.
|
697
|
-
# @param [Object] accept An acceptor that validates and/or converts the
|
698
|
-
# value. You may provide either the name of an acceptor you have
|
699
|
-
# defined, or one of the default acceptors provided by OptionParser.
|
700
|
-
# Optional. If not specified, accepts any value as a string.
|
701
|
-
# @param [String] display_name A name to use for display (in help text and
|
702
|
-
# error reports). Defaults to the key in upper case.
|
703
|
-
# @param [String,Array<String>,Toys::Utils::WrappableString] desc Short
|
704
|
-
# description for the arg. See {Toys::Definition::Tool#desc=} for a
|
705
|
-
# description of allowed formats. Defaults to the empty string.
|
706
|
-
# @param [Array<String,Array<String>,Toys::Utils::WrappableString>] long_desc
|
707
|
-
# Long description for the arg. See
|
708
|
-
# {Toys::Definition::Tool#long_desc=} for a description of allowed
|
709
|
-
# formats. Defaults to the empty array.
|
710
|
-
#
|
711
|
-
def set_remaining_args(key, default: [], accept: nil, display_name: nil,
|
712
|
-
desc: nil, long_desc: nil)
|
713
|
-
check_definition_state(is_arg: true)
|
714
|
-
accept = resolve_acceptor(accept)
|
715
|
-
arg_def = Definition::Arg.new(key, :remaining, accept, default,
|
716
|
-
desc, long_desc, display_name)
|
717
|
-
@remaining_args_definition = arg_def
|
718
|
-
@default_data[key] = default
|
719
|
-
self
|
720
|
-
end
|
721
|
-
|
722
|
-
##
|
723
|
-
# Set the runnable block
|
724
|
-
#
|
725
|
-
# @param [Proc] proc The runnable block
|
726
|
-
#
|
727
|
-
def runnable=(proc)
|
728
|
-
@tool_class.to_run(&proc)
|
729
|
-
end
|
730
|
-
|
731
|
-
##
|
732
|
-
# Add an initializer.
|
733
|
-
#
|
734
|
-
# @param [Proc] proc The initializer block
|
735
|
-
# @param [Object...] args Arguments to pass to the initializer
|
736
|
-
#
|
737
|
-
def add_initializer(proc, *args)
|
738
|
-
check_definition_state
|
739
|
-
@initializers << [proc, args]
|
740
|
-
self
|
741
|
-
end
|
742
|
-
|
743
|
-
##
|
744
|
-
# Set the custom context directory.
|
745
|
-
#
|
746
|
-
# @param [String] dir
|
747
|
-
#
|
748
|
-
def custom_context_directory=(dir)
|
749
|
-
check_definition_state
|
750
|
-
@custom_context_directory = dir
|
751
|
-
end
|
752
|
-
|
753
|
-
##
|
754
|
-
# Return the effective context directory.
|
755
|
-
# If there is a custom context directory, uses that. Otherwise, looks for
|
756
|
-
# a custom context directory up the tool ancestor chain. If none is
|
757
|
-
# found, uses the default context directory from the source info. It is
|
758
|
-
# possible for there to be no context directory at all, in which case,
|
759
|
-
# returns nil.
|
760
|
-
#
|
761
|
-
# @return [String,nil]
|
762
|
-
#
|
763
|
-
def context_directory
|
764
|
-
lookup_custom_context_directory || source_info&.context_directory
|
765
|
-
end
|
766
|
-
|
767
|
-
##
|
768
|
-
# Lookup the custom context directory in this tool and its ancestors.
|
769
|
-
# @private
|
770
|
-
#
|
771
|
-
def lookup_custom_context_directory
|
772
|
-
custom_context_directory || @parent&.lookup_custom_context_directory
|
773
|
-
end
|
774
|
-
|
775
|
-
##
|
776
|
-
# Mark this tool as having at least one module included
|
777
|
-
# @private
|
778
|
-
#
|
779
|
-
def mark_includes_modules
|
780
|
-
check_definition_state
|
781
|
-
@includes_modules = true
|
782
|
-
self
|
783
|
-
end
|
784
|
-
|
785
|
-
##
|
786
|
-
# Complete definition and run middleware configs. Should be called from
|
787
|
-
# the Loader only.
|
788
|
-
# @private
|
789
|
-
#
|
790
|
-
def finish_definition(loader)
|
791
|
-
unless @definition_finished
|
792
|
-
ContextualError.capture("Error installing tool middleware!", tool_name: full_name) do
|
793
|
-
config_proc = proc {}
|
794
|
-
middleware_stack.reverse_each do |middleware|
|
795
|
-
config_proc = make_config_proc(middleware, loader, config_proc)
|
796
|
-
end
|
797
|
-
config_proc.call
|
798
|
-
end
|
799
|
-
flag_groups.each do |flag_group|
|
800
|
-
flag_group.flag_definitions.sort_by!(&:sort_str)
|
801
|
-
end
|
802
|
-
@definition_finished = true
|
803
|
-
end
|
804
|
-
self
|
805
|
-
end
|
806
|
-
|
807
|
-
##
|
808
|
-
# Run all initializers against a tool. Should be called from the Runner
|
809
|
-
# only.
|
810
|
-
# @private
|
811
|
-
#
|
812
|
-
def run_initializers(tool)
|
813
|
-
@initializers.each do |func, args|
|
814
|
-
tool.instance_exec(*args, &func)
|
815
|
-
end
|
816
|
-
end
|
817
|
-
|
818
|
-
##
|
819
|
-
# Check that the tool can still be defined. Should be called internally
|
820
|
-
# or from the DSL only.
|
821
|
-
# @private
|
822
|
-
#
|
823
|
-
def check_definition_state(is_arg: false)
|
824
|
-
if @definition_finished
|
825
|
-
raise ToolDefinitionError,
|
826
|
-
"Defintion of tool #{display_name.inspect} is already finished"
|
827
|
-
end
|
828
|
-
if is_arg && argument_parsing_disabled?
|
829
|
-
raise ToolDefinitionError,
|
830
|
-
"Tool #{display_name.inspect} has disabled argument parsing"
|
831
|
-
end
|
832
|
-
self
|
833
|
-
end
|
834
|
-
|
835
|
-
private
|
836
|
-
|
837
|
-
def make_config_proc(middleware, loader, next_config)
|
838
|
-
proc { middleware.config(self, loader, &next_config) }
|
839
|
-
end
|
840
|
-
end
|
841
|
-
end
|
842
|
-
end
|