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,356 @@
|
|
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
|
+
##
|
32
|
+
# This class defines the DSL for a toys configuration file.
|
33
|
+
#
|
34
|
+
# A toys configuration defines one or more named tools. It provides syntax
|
35
|
+
# for setting the description, defining switches and arguments, specifying
|
36
|
+
# how to execute the tool, and requesting helper modules and other services.
|
37
|
+
# It also lets you define subtools, nested arbitrarily deep, using blocks.
|
38
|
+
#
|
39
|
+
# Generally ConfigDSL is invoked from the {Loader}. Applications should not
|
40
|
+
# need to create instances of ConfigDSL directly.
|
41
|
+
#
|
42
|
+
# ## Simple example
|
43
|
+
#
|
44
|
+
# Create a file called `.toys.rb` in the current directory, with the
|
45
|
+
# following contents:
|
46
|
+
#
|
47
|
+
# tool "greet" do
|
48
|
+
# desc "Prints a simple greeting"
|
49
|
+
#
|
50
|
+
# optional_arg :recipient, default: "world"
|
51
|
+
#
|
52
|
+
# execute do
|
53
|
+
# puts "Hello, #{self[:recipient]}!"
|
54
|
+
# end
|
55
|
+
# end
|
56
|
+
#
|
57
|
+
# Now you can execute it using:
|
58
|
+
#
|
59
|
+
# toys greet
|
60
|
+
#
|
61
|
+
# or try:
|
62
|
+
#
|
63
|
+
# toys greet rubyists
|
64
|
+
#
|
65
|
+
class ConfigDSL
|
66
|
+
##
|
67
|
+
# Create an instance of the DSL.
|
68
|
+
# @private
|
69
|
+
#
|
70
|
+
# @param [Array<String>] words Full name of the current tool.
|
71
|
+
# @param [Array<String>,nil] remaining_words Arguments remaining in the
|
72
|
+
# current lookup.
|
73
|
+
# @param [Integer] priority Priority of this configuration
|
74
|
+
# @param [Toys::Loader] loader Current active loader
|
75
|
+
# @param [String] path The path to the config file being evaluated
|
76
|
+
#
|
77
|
+
# @return [Toys::ConfigDSL]
|
78
|
+
#
|
79
|
+
def initialize(words, remaining_words, priority, loader, path)
|
80
|
+
@words = words
|
81
|
+
@remaining_words = remaining_words
|
82
|
+
@priority = priority
|
83
|
+
@loader = loader
|
84
|
+
@path = path
|
85
|
+
end
|
86
|
+
|
87
|
+
##
|
88
|
+
# Create a subtool. You must provide a block defining the subtool.
|
89
|
+
#
|
90
|
+
# If the subtool is already defined (either as a tool or a group), the old
|
91
|
+
# definition is discarded and replaced with the new definition. If the old
|
92
|
+
# tool was a group, all its descendants are also discarded, recursively.
|
93
|
+
#
|
94
|
+
# @param [String] word The name of the subtool
|
95
|
+
#
|
96
|
+
def tool(word, &block)
|
97
|
+
word = word.to_s
|
98
|
+
subtool_words = @words + [word]
|
99
|
+
next_remaining = Loader.next_remaining_words(@remaining_words, word)
|
100
|
+
ConfigDSL.evaluate(subtool_words, next_remaining, @priority, @loader, @path, block)
|
101
|
+
self
|
102
|
+
end
|
103
|
+
alias name tool
|
104
|
+
|
105
|
+
##
|
106
|
+
# Create an alias in the current group.
|
107
|
+
#
|
108
|
+
# @param [String] word The name of the alias
|
109
|
+
# @param [String] target The target of the alias
|
110
|
+
#
|
111
|
+
def alias_tool(word, target)
|
112
|
+
@loader.make_alias(@words + [word.to_s], @words + [target.to_s], @priority)
|
113
|
+
self
|
114
|
+
end
|
115
|
+
|
116
|
+
##
|
117
|
+
# Create an alias of the current tool.
|
118
|
+
#
|
119
|
+
# @param [String] word The name of the alias
|
120
|
+
#
|
121
|
+
def alias_as(word)
|
122
|
+
if @words.empty?
|
123
|
+
raise ToolDefinitionError, "Cannot make an alias of the root."
|
124
|
+
end
|
125
|
+
@loader.make_alias(@words[0..-2] + [word.to_s], @words, @priority)
|
126
|
+
self
|
127
|
+
end
|
128
|
+
|
129
|
+
##
|
130
|
+
# Include another config file or directory at the current location.
|
131
|
+
#
|
132
|
+
# @param [String] path The file or directory to include.
|
133
|
+
#
|
134
|
+
def include(path)
|
135
|
+
@loader.include_path(path, @words, @remaining_words, @priority)
|
136
|
+
self
|
137
|
+
end
|
138
|
+
|
139
|
+
##
|
140
|
+
# Expand the given template in the current location.
|
141
|
+
#
|
142
|
+
# The template may be specified as a class or a well-known template name.
|
143
|
+
# You may also provide arguments to pass to the template.
|
144
|
+
#
|
145
|
+
# @param [Class,String,Symbol] template_class The template, either as a
|
146
|
+
# class or a well-known name.
|
147
|
+
# @param [Object...] args Template arguments
|
148
|
+
#
|
149
|
+
def expand(template_class, *args)
|
150
|
+
unless template_class.is_a?(::Class)
|
151
|
+
name = template_class.to_s
|
152
|
+
template_class = Templates.lookup(name)
|
153
|
+
if template_class.nil?
|
154
|
+
raise ToolDefinitionError, "Template not found: #{name.inspect}"
|
155
|
+
end
|
156
|
+
end
|
157
|
+
template = template_class.new(*args)
|
158
|
+
yield template if block_given?
|
159
|
+
instance_exec(template, &template_class.expander)
|
160
|
+
self
|
161
|
+
end
|
162
|
+
|
163
|
+
##
|
164
|
+
# Set the long description for the current tool. The long description is
|
165
|
+
# displayed in the usage documentation for the tool itself.
|
166
|
+
#
|
167
|
+
# @param [String] desc The long description string.
|
168
|
+
#
|
169
|
+
def long_desc(desc)
|
170
|
+
return self if _cur_tool.nil?
|
171
|
+
_cur_tool.definition_path = @path
|
172
|
+
_cur_tool.long_desc = desc
|
173
|
+
self
|
174
|
+
end
|
175
|
+
|
176
|
+
##
|
177
|
+
# Set the short description for the current tool. The short description is
|
178
|
+
# displayed with the tool in a command list. You may also use the
|
179
|
+
# equivalent method `short_desc`.
|
180
|
+
#
|
181
|
+
# @param [String] desc The short description string.
|
182
|
+
#
|
183
|
+
def desc(desc)
|
184
|
+
return self if _cur_tool.nil?
|
185
|
+
_cur_tool.definition_path = @path
|
186
|
+
_cur_tool.desc = desc
|
187
|
+
self
|
188
|
+
end
|
189
|
+
alias short_desc desc
|
190
|
+
|
191
|
+
##
|
192
|
+
# Add a switch to the current tool. Each switch must specify a key which
|
193
|
+
# the executor may use to obtain the switch value from the context.
|
194
|
+
# You may then provide the switches themselves in `OptionParser` form.
|
195
|
+
#
|
196
|
+
# @param [Symbol] key The key to use to retrieve the value from the
|
197
|
+
# execution context.
|
198
|
+
# @param [String...] switches The switches in OptionParser format.
|
199
|
+
# @param [Object,nil] accept An OptionParser acceptor. Optional.
|
200
|
+
# @param [Object] default The default value. This is the value that will
|
201
|
+
# be set in the context if this switch is not provided on the command
|
202
|
+
# line. Defaults to `nil`.
|
203
|
+
# @param [String,nil] doc The documentation for the switch, which appears
|
204
|
+
# in the usage documentation. Defaults to `nil` for no documentation.
|
205
|
+
# @param [Boolean] only_unique If true, any switches that are already
|
206
|
+
# defined in this tool are removed from this switch. For example, if
|
207
|
+
# an earlier switch uses `-a`, and this switch wants to use both
|
208
|
+
# `-a` and `-b`, then only `-b` will be assigned to this switch.
|
209
|
+
# Defaults to false.
|
210
|
+
# @param [Proc,nil] handler An optional handler for setting/updating the
|
211
|
+
# value. If given, it should take two arguments, the new given value
|
212
|
+
# and the previous value, and it should return the new value that
|
213
|
+
# should be set. The default handler simply replaces the previous
|
214
|
+
# value. i.e. the default is effectively `-> (val, _prev) { val }`.
|
215
|
+
#
|
216
|
+
def switch(key, *switches,
|
217
|
+
accept: nil, default: nil, doc: nil, only_unique: false, handler: nil)
|
218
|
+
return self if _cur_tool.nil?
|
219
|
+
_cur_tool.definition_path = @path
|
220
|
+
_cur_tool.add_switch(key, *switches,
|
221
|
+
accept: accept, default: default, doc: doc,
|
222
|
+
only_unique: only_unique, handler: handler)
|
223
|
+
self
|
224
|
+
end
|
225
|
+
|
226
|
+
##
|
227
|
+
# Add a required positional argument to the current tool. You must specify
|
228
|
+
# a key which the executor may use to obtain the argument value from the
|
229
|
+
# context.
|
230
|
+
#
|
231
|
+
# @param [Symbol] key The key to use to retrieve the value from the
|
232
|
+
# execution context.
|
233
|
+
# @param [Object,nil] accept An OptionParser acceptor. Optional.
|
234
|
+
# @param [String,nil] doc The documentation for the switch, which appears
|
235
|
+
# in the usage documentation. Defaults to `nil` for no documentation.
|
236
|
+
#
|
237
|
+
def required_arg(key, accept: nil, doc: nil)
|
238
|
+
return self if _cur_tool.nil?
|
239
|
+
_cur_tool.definition_path = @path
|
240
|
+
_cur_tool.add_required_arg(key, accept: accept, doc: doc)
|
241
|
+
self
|
242
|
+
end
|
243
|
+
|
244
|
+
##
|
245
|
+
# Add an optional positional argument to the current tool. You must specify
|
246
|
+
# a key which the executor may use to obtain the argument value from the
|
247
|
+
# context. If an optional argument is not given on the command line, the
|
248
|
+
# value is set to the given default.
|
249
|
+
#
|
250
|
+
# @param [Symbol] key The key to use to retrieve the value from the
|
251
|
+
# execution context.
|
252
|
+
# @param [Object,nil] accept An OptionParser acceptor. Optional.
|
253
|
+
# @param [Object] default The default value. This is the value that will
|
254
|
+
# be set in the context if this argument is not provided on the command
|
255
|
+
# line. Defaults to `nil`.
|
256
|
+
# @param [String,nil] doc The documentation for the argument, which appears
|
257
|
+
# in the usage documentation. Defaults to `nil` for no documentation.
|
258
|
+
#
|
259
|
+
def optional_arg(key, accept: nil, default: nil, doc: nil)
|
260
|
+
return self if _cur_tool.nil?
|
261
|
+
_cur_tool.definition_path = @path
|
262
|
+
_cur_tool.add_optional_arg(key, accept: accept, default: default, doc: doc)
|
263
|
+
self
|
264
|
+
end
|
265
|
+
|
266
|
+
##
|
267
|
+
# Specify what should be done with unmatched positional arguments. You must
|
268
|
+
# specify a key which the executor may use to obtain the remaining args
|
269
|
+
# from the context.
|
270
|
+
#
|
271
|
+
# @param [Symbol] key The key to use to retrieve the value from the
|
272
|
+
# execution context.
|
273
|
+
# @param [Object,nil] accept An OptionParser acceptor. Optional.
|
274
|
+
# @param [Object] default The default value. This is the value that will
|
275
|
+
# be set in the context if no unmatched arguments are provided on the
|
276
|
+
# command line. Defaults to the empty array `[]`.
|
277
|
+
# @param [String,nil] doc The documentation for the remaining arguments,
|
278
|
+
# which appears in the usage documentation. Defaults to `nil` for no
|
279
|
+
# documentation.
|
280
|
+
#
|
281
|
+
def remaining_args(key, accept: nil, default: [], doc: nil)
|
282
|
+
return self if _cur_tool.nil?
|
283
|
+
_cur_tool.definition_path = @path
|
284
|
+
_cur_tool.set_remaining_args(key, accept: accept, default: default, doc: doc)
|
285
|
+
self
|
286
|
+
end
|
287
|
+
|
288
|
+
##
|
289
|
+
# Specify the executor for this tool. This is a block that will be called,
|
290
|
+
# with `self` set to a {Toys::Context}.
|
291
|
+
#
|
292
|
+
def execute(&block)
|
293
|
+
return self if _cur_tool.nil?
|
294
|
+
_cur_tool.definition_path = @path
|
295
|
+
_cur_tool.executor = block
|
296
|
+
self
|
297
|
+
end
|
298
|
+
|
299
|
+
##
|
300
|
+
# Define a helper method that may be called from this tool's executor.
|
301
|
+
# You must provide a name for the method, and a block for the method
|
302
|
+
# definition.
|
303
|
+
#
|
304
|
+
# @param [String,Symbol] name Name of the method. May not begin with an
|
305
|
+
# underscore.
|
306
|
+
#
|
307
|
+
def helper(name, &block)
|
308
|
+
return self if _cur_tool.nil?
|
309
|
+
_cur_tool.definition_path = @path
|
310
|
+
_cur_tool.add_helper(name, &block)
|
311
|
+
self
|
312
|
+
end
|
313
|
+
|
314
|
+
##
|
315
|
+
# Specify that the given module should be mixed in to this tool's executor.
|
316
|
+
# Effectively, the module is added to the {Toys::Context} object.
|
317
|
+
# You may either provide a module directly, or specify the name of a
|
318
|
+
# well-known module.
|
319
|
+
#
|
320
|
+
# @param [Module,Symbol] mod Module or name of well-known module.
|
321
|
+
#
|
322
|
+
def use(mod)
|
323
|
+
return self if _cur_tool.nil?
|
324
|
+
_cur_tool.definition_path = @path
|
325
|
+
_cur_tool.use_module(mod)
|
326
|
+
self
|
327
|
+
end
|
328
|
+
|
329
|
+
## @private
|
330
|
+
def _binding
|
331
|
+
binding
|
332
|
+
end
|
333
|
+
|
334
|
+
## @private
|
335
|
+
def _cur_tool
|
336
|
+
unless defined? @_cur_tool
|
337
|
+
@_cur_tool = @loader.get_or_create_tool(@words, priority: @priority)
|
338
|
+
end
|
339
|
+
@_cur_tool
|
340
|
+
end
|
341
|
+
|
342
|
+
## @private
|
343
|
+
def self.evaluate(words, remaining_words, priority, loader, path, source)
|
344
|
+
dsl = new(words, remaining_words, priority, loader, path)
|
345
|
+
case source
|
346
|
+
when String
|
347
|
+
# rubocop:disable Security/Eval
|
348
|
+
eval(source, dsl._binding, path, 1)
|
349
|
+
# rubocop:enable Security/Eval
|
350
|
+
when ::Proc
|
351
|
+
dsl.instance_eval(&source)
|
352
|
+
end
|
353
|
+
nil
|
354
|
+
end
|
355
|
+
end
|
356
|
+
end
|
data/lib/toys/context.rb
ADDED
@@ -0,0 +1,278 @@
|
|
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 "logger"
|
31
|
+
|
32
|
+
module Toys
|
33
|
+
##
|
34
|
+
# The object context in effect during the execution of a tool.
|
35
|
+
#
|
36
|
+
# The context is generally a hash of key-value pairs.
|
37
|
+
# Keys that begin with two underscores are reserved common elements of the
|
38
|
+
# context such as the tool being executed, or the verbosity level.
|
39
|
+
# Other keys are available for use by your tool. Generally, they are set
|
40
|
+
# by switches and arguments in your tool. Context values may also be set
|
41
|
+
# by middleware. By convention, middleware-set keys begin with a single
|
42
|
+
# underscore.
|
43
|
+
#
|
44
|
+
class Context
|
45
|
+
##
|
46
|
+
# Context key for the verbosity value. Verbosity is an integer defaulting
|
47
|
+
# to 0, with higher values meaning more verbose and lower meaning quieter.
|
48
|
+
# @return [Symbol]
|
49
|
+
#
|
50
|
+
VERBOSITY = :__verbosity
|
51
|
+
|
52
|
+
##
|
53
|
+
# Context key for the `Toys::Tool` object being executed.
|
54
|
+
# @return [Symbol]
|
55
|
+
#
|
56
|
+
TOOL = :__tool
|
57
|
+
|
58
|
+
##
|
59
|
+
# Context key for the full name of the tool being executed. Value is an
|
60
|
+
# array of strings.
|
61
|
+
# @return [Symbol]
|
62
|
+
#
|
63
|
+
TOOL_NAME = :__tool_name
|
64
|
+
|
65
|
+
##
|
66
|
+
# Context key for the active `Toys::Loader` object.
|
67
|
+
# @return [Symbol]
|
68
|
+
#
|
69
|
+
LOADER = :__loader
|
70
|
+
|
71
|
+
##
|
72
|
+
# Context key for the active `Logger` object.
|
73
|
+
# @return [Symbol]
|
74
|
+
#
|
75
|
+
LOGGER = :__logger
|
76
|
+
|
77
|
+
##
|
78
|
+
# Context key for the name of the toys binary. Value is a string.
|
79
|
+
# @return [Symbol]
|
80
|
+
#
|
81
|
+
BINARY_NAME = :__binary_name
|
82
|
+
|
83
|
+
##
|
84
|
+
# Context key for the argument list passed to the current tool. Value is
|
85
|
+
# an array of strings.
|
86
|
+
# @return [Symbol]
|
87
|
+
#
|
88
|
+
ARGS = :__args
|
89
|
+
|
90
|
+
##
|
91
|
+
# Context key for the usage error raised. Value is a string if there was
|
92
|
+
# an error, or nil if there was no error.
|
93
|
+
# @return [Symbol]
|
94
|
+
#
|
95
|
+
USAGE_ERROR = :__usage_error
|
96
|
+
|
97
|
+
##
|
98
|
+
# Context key for whether nonzero exit codes from subprocesses should cause
|
99
|
+
# an immediate exit. Value is a truthy or falsy value.
|
100
|
+
# @return [Symbol]
|
101
|
+
#
|
102
|
+
EXIT_ON_NONZERO_STATUS = :__exit_on_nonzero_status
|
103
|
+
|
104
|
+
##
|
105
|
+
# Create a Context object. Applications generally will not need to create
|
106
|
+
# these objects directly; they are created by the tool when it is preparing
|
107
|
+
# for execution.
|
108
|
+
# @private
|
109
|
+
#
|
110
|
+
# @param [Toys::CLI] cli
|
111
|
+
# @param [Hash] data
|
112
|
+
#
|
113
|
+
def initialize(cli, data)
|
114
|
+
@_cli = cli
|
115
|
+
@_data = data
|
116
|
+
@_data[LOADER] = cli.loader
|
117
|
+
@_data[BINARY_NAME] = cli.binary_name
|
118
|
+
@_data[LOGGER] = cli.logger
|
119
|
+
end
|
120
|
+
|
121
|
+
##
|
122
|
+
# Return the verbosity as an integer.
|
123
|
+
# @return [Integer]
|
124
|
+
#
|
125
|
+
def verbosity
|
126
|
+
@_data[VERBOSITY]
|
127
|
+
end
|
128
|
+
|
129
|
+
##
|
130
|
+
# Return the tool being executed.
|
131
|
+
# @return [Toys::Tool]
|
132
|
+
#
|
133
|
+
def tool
|
134
|
+
@_data[TOOL]
|
135
|
+
end
|
136
|
+
|
137
|
+
##
|
138
|
+
# Return the name of the tool being executed, as an array of strings.
|
139
|
+
# @return [Array[String]]
|
140
|
+
#
|
141
|
+
def tool_name
|
142
|
+
@_data[TOOL_NAME]
|
143
|
+
end
|
144
|
+
|
145
|
+
##
|
146
|
+
# Return the raw arguments passed to the tool, as an array of strings.
|
147
|
+
# This does not include the tool name itself.
|
148
|
+
# @return [Array[String]]
|
149
|
+
#
|
150
|
+
def args
|
151
|
+
@_data[ARGS]
|
152
|
+
end
|
153
|
+
|
154
|
+
##
|
155
|
+
# Return any usage error detected during argument parsing, or `nil` if
|
156
|
+
# no error was detected.
|
157
|
+
# @return [String,nil]
|
158
|
+
#
|
159
|
+
def usage_error
|
160
|
+
@_data[USAGE_ERROR]
|
161
|
+
end
|
162
|
+
|
163
|
+
##
|
164
|
+
# Return the logger for this execution.
|
165
|
+
# @return [Logger]
|
166
|
+
#
|
167
|
+
def logger
|
168
|
+
@_data[LOGGER]
|
169
|
+
end
|
170
|
+
|
171
|
+
##
|
172
|
+
# Return the active loader that can be used to get other tools.
|
173
|
+
# @return [Toys::Loader]
|
174
|
+
#
|
175
|
+
def loader
|
176
|
+
@_data[LOADER]
|
177
|
+
end
|
178
|
+
|
179
|
+
##
|
180
|
+
# Return the name of the binary that was executed.
|
181
|
+
# @return [String]
|
182
|
+
#
|
183
|
+
def binary_name
|
184
|
+
@_data[BINARY_NAME]
|
185
|
+
end
|
186
|
+
|
187
|
+
##
|
188
|
+
# Return an option or other piece of data by key.
|
189
|
+
#
|
190
|
+
# @param [Symbol] key
|
191
|
+
# @return [Object]
|
192
|
+
#
|
193
|
+
def [](key)
|
194
|
+
@_data[key]
|
195
|
+
end
|
196
|
+
alias get []
|
197
|
+
|
198
|
+
##
|
199
|
+
# Set an option or other piece of data by key.
|
200
|
+
#
|
201
|
+
# @param [Symbol] key
|
202
|
+
# @param [Object] value
|
203
|
+
#
|
204
|
+
def []=(key, value)
|
205
|
+
@_data[key] = value
|
206
|
+
end
|
207
|
+
|
208
|
+
##
|
209
|
+
# Set an option or other piece of data by key.
|
210
|
+
#
|
211
|
+
# @param [Symbol] key
|
212
|
+
# @param [Object] value
|
213
|
+
#
|
214
|
+
def set(key, value = nil)
|
215
|
+
if key.is_a?(::Hash)
|
216
|
+
@_data.merge!(key)
|
217
|
+
else
|
218
|
+
@_data[key] = value
|
219
|
+
end
|
220
|
+
self
|
221
|
+
end
|
222
|
+
|
223
|
+
##
|
224
|
+
# Returns the subset of the context that does not include well-known keys
|
225
|
+
# such as tool and verbosity. Technically, this includes all keys that do
|
226
|
+
# not begin with two underscores.
|
227
|
+
#
|
228
|
+
# @return [Hash]
|
229
|
+
#
|
230
|
+
def options
|
231
|
+
@_data.select do |k, _v|
|
232
|
+
!k.is_a?(::Symbol) || !k.to_s.start_with?("__")
|
233
|
+
end
|
234
|
+
end
|
235
|
+
|
236
|
+
##
|
237
|
+
# Execute another tool, given by the provided arguments.
|
238
|
+
#
|
239
|
+
# @param [String...] args Command line arguments defining another tool
|
240
|
+
# to run, along with parameters and switches.
|
241
|
+
# @param [Toys::CLI,nil] cli The CLI to use to execute the tool. If `nil`
|
242
|
+
# (the default), uses the current CLI.
|
243
|
+
# @param [Boolean] exit_on_nonzero_status If true, exit immediately if the
|
244
|
+
# run returns a nonzero error code.
|
245
|
+
# @return [Integer] The resulting status code
|
246
|
+
#
|
247
|
+
def run(*args, cli: nil, exit_on_nonzero_status: nil)
|
248
|
+
cli ||= @_cli
|
249
|
+
exit_on_nonzero_status = @_data[EXIT_ON_NONZERO_STATUS] if exit_on_nonzero_status.nil?
|
250
|
+
code = cli.run(args.flatten, verbosity: @_data[VERBOSITY])
|
251
|
+
exit(code) if exit_on_nonzero_status && !code.zero?
|
252
|
+
code
|
253
|
+
end
|
254
|
+
|
255
|
+
##
|
256
|
+
# Return a new CLI with the same settings as the currnet CLI but no paths.
|
257
|
+
# This can be used to run a toys "sub-instance". Add any new paths to the
|
258
|
+
# returned CLI, then call {#run}, passing in the CLI, to execute a tool.
|
259
|
+
#
|
260
|
+
# @return [Toys::CLI]
|
261
|
+
#
|
262
|
+
def new_cli
|
263
|
+
cli = @_cli.empty_clone
|
264
|
+
yield cli if block_given?
|
265
|
+
cli
|
266
|
+
end
|
267
|
+
|
268
|
+
##
|
269
|
+
# Exit immediately with the given status code
|
270
|
+
#
|
271
|
+
# @param [Integer] code The status code, which should be 0 for no error,
|
272
|
+
# or nonzero for an error condition.
|
273
|
+
#
|
274
|
+
def exit(code)
|
275
|
+
throw :result, code
|
276
|
+
end
|
277
|
+
end
|
278
|
+
end
|