toys 0.3.1 → 0.3.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.yardopts +0 -2
- data/CHANGELOG.md +20 -0
- data/README.md +4 -24
- data/bin/toys +1 -1
- data/{lib/toys/builtins → builtins}/do.rb +1 -1
- data/{lib/toys/builtins → builtins}/system.rb +0 -0
- data/lib/toys.rb +9 -14
- data/lib/toys/standard_cli.rb +151 -0
- data/lib/toys/version.rb +2 -2
- metadata +19 -28
- data/lib/toys/cli.rb +0 -271
- data/lib/toys/config_dsl.rb +0 -432
- data/lib/toys/context.rb +0 -278
- data/lib/toys/errors.rb +0 -42
- data/lib/toys/helpers.rb +0 -52
- data/lib/toys/helpers/exec.rb +0 -469
- data/lib/toys/helpers/file_utils.rb +0 -39
- data/lib/toys/loader.rb +0 -423
- data/lib/toys/middleware.rb +0 -55
- data/lib/toys/middleware/base.rb +0 -51
- data/lib/toys/middleware/set_verbosity.rb +0 -54
- data/lib/toys/middleware/show_group_usage.rb +0 -68
- data/lib/toys/middleware/show_tool_usage.rb +0 -64
- data/lib/toys/middleware/show_usage_errors.rb +0 -57
- data/lib/toys/template.rb +0 -123
- data/lib/toys/templates.rb +0 -55
- data/lib/toys/templates/clean.rb +0 -80
- data/lib/toys/templates/gem_build.rb +0 -115
- data/lib/toys/templates/minitest.rb +0 -108
- data/lib/toys/templates/rubocop.rb +0 -81
- data/lib/toys/templates/yardoc.rb +0 -95
- data/lib/toys/tool.rb +0 -831
- data/lib/toys/utils/module_lookup.rb +0 -101
- data/lib/toys/utils/usage.rb +0 -163
data/lib/toys/config_dsl.rb
DELETED
@@ -1,432 +0,0 @@
|
|
1
|
-
# Copyright 2018 Daniel Azuma
|
2
|
-
#
|
3
|
-
# All rights reserved.
|
4
|
-
#
|
5
|
-
# Redistribution and use in source and binary forms, with or without
|
6
|
-
# modification, are permitted provided that the following conditions are met:
|
7
|
-
#
|
8
|
-
# * Redistributions of source code must retain the above copyright notice,
|
9
|
-
# this list of conditions and the following disclaimer.
|
10
|
-
# * Redistributions in binary form must reproduce the above copyright notice,
|
11
|
-
# this list of conditions and the following disclaimer in the documentation
|
12
|
-
# and/or other materials provided with the distribution.
|
13
|
-
# * Neither the name of the copyright holder, nor the names of any other
|
14
|
-
# contributors to this software, may be used to endorse or promote products
|
15
|
-
# derived from this software without specific prior written permission.
|
16
|
-
#
|
17
|
-
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
18
|
-
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
19
|
-
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
20
|
-
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
21
|
-
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
22
|
-
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
23
|
-
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
24
|
-
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
25
|
-
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
26
|
-
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
27
|
-
# POSSIBILITY OF SUCH DAMAGE.
|
28
|
-
;
|
29
|
-
|
30
|
-
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 [String] path The path to the config file being evaluated
|
71
|
-
# @param [Toys::Tool] tool The tool being defined at the top level
|
72
|
-
# @param [Array<String>,nil] remaining_words Arguments remaining in the
|
73
|
-
# current lookup.
|
74
|
-
# @param [Integer] priority Priority of this configuration
|
75
|
-
# @param [Toys::Loader] loader Current active loader
|
76
|
-
# @param [:tool,:append,:group] type Type of tool being configured
|
77
|
-
#
|
78
|
-
# @return [Toys::ConfigDSL]
|
79
|
-
#
|
80
|
-
def initialize(path, tool, remaining_words, priority, loader, type)
|
81
|
-
@path = path
|
82
|
-
@tool = tool
|
83
|
-
@remaining_words = remaining_words
|
84
|
-
@priority = priority
|
85
|
-
@loader = loader
|
86
|
-
@type = type
|
87
|
-
end
|
88
|
-
|
89
|
-
##
|
90
|
-
# Create a subtool.
|
91
|
-
#
|
92
|
-
# If the subtool is not an alias, you must provide a block defining the
|
93
|
-
# subtool.
|
94
|
-
#
|
95
|
-
# If the subtool is already defined (either as a tool or a group), the old
|
96
|
-
# definition is discarded and replaced with the new definition. If the old
|
97
|
-
# tool was a group, all its descendants are also discarded, recursively.
|
98
|
-
#
|
99
|
-
# @param [String] word The name of the subtool
|
100
|
-
# @param [String,nil] alias_of If set, this subtool is set to be an alias
|
101
|
-
# of the given subtool name. Defaults to `nil`, indicating the subtool
|
102
|
-
# is not an alias.
|
103
|
-
#
|
104
|
-
def tool(word, alias_of: nil, &block)
|
105
|
-
word = word.to_s
|
106
|
-
subtool = @loader.get_or_create_tool(@tool.full_name + [word], @priority, assume_parent: true)
|
107
|
-
return self if subtool.nil?
|
108
|
-
if alias_of
|
109
|
-
if block
|
110
|
-
raise ToolDefinitionError, "Cannot take a block with alias_of"
|
111
|
-
end
|
112
|
-
subtool.make_alias_of_word(alias_of.to_s)
|
113
|
-
return self
|
114
|
-
end
|
115
|
-
next_remaining = Loader.next_remaining_words(@remaining_words, word)
|
116
|
-
ConfigDSL.evaluate(@path, subtool, next_remaining, @priority, @loader, :tool, block)
|
117
|
-
self
|
118
|
-
end
|
119
|
-
alias name tool
|
120
|
-
|
121
|
-
##
|
122
|
-
# Append subtools to an existing group.
|
123
|
-
#
|
124
|
-
# Pass a group name to this method to "reopen" that group. You must provide
|
125
|
-
# a block. In that block, you may not modify any properties of the group
|
126
|
-
# itself, but you may add or replace subtools within the group.
|
127
|
-
#
|
128
|
-
# @param [String] word The name of the group.
|
129
|
-
#
|
130
|
-
def append(word, &block)
|
131
|
-
word = word.to_s
|
132
|
-
subtool = @loader.get_or_create_tool(@tool.full_name + [word], nil, assume_parent: true)
|
133
|
-
next_remaining = Loader.next_remaining_words(@remaining_words, word)
|
134
|
-
ConfigDSL.evaluate(@path, subtool, next_remaining, @priority, @loader, :append, block)
|
135
|
-
self
|
136
|
-
end
|
137
|
-
|
138
|
-
##
|
139
|
-
# Create a group subtool. You must provide a block defining the group's
|
140
|
-
# properties and contents.
|
141
|
-
#
|
142
|
-
# If the subtool is already defined (either as a tool or a group), the old
|
143
|
-
# definition is discarded and replaced with the new definition.
|
144
|
-
#
|
145
|
-
# @param [String] word The name of the group
|
146
|
-
#
|
147
|
-
def group(word, &block)
|
148
|
-
word = word.to_s
|
149
|
-
subtool = @loader.get_or_create_tool(@tool.full_name + [word], @priority, assume_parent: true)
|
150
|
-
return self if subtool.nil?
|
151
|
-
next_remaining = Loader.next_remaining_words(@remaining_words, word)
|
152
|
-
ConfigDSL.evaluate(@path, subtool, next_remaining, @priority, @loader, :group, block)
|
153
|
-
self
|
154
|
-
end
|
155
|
-
|
156
|
-
##
|
157
|
-
# Create an alias of the current tool.
|
158
|
-
#
|
159
|
-
# @param [String] word The name of the alias
|
160
|
-
#
|
161
|
-
def alias_as(word)
|
162
|
-
if @tool.root?
|
163
|
-
raise ToolDefinitionError, "Cannot make an alias of the root tool"
|
164
|
-
end
|
165
|
-
if @type == :group || @type == :append
|
166
|
-
raise ToolDefinitionError, "Cannot make an alias of a group"
|
167
|
-
end
|
168
|
-
alias_name = @tool.full_name.slice(0..-2) + [word.to_s]
|
169
|
-
alias_tool = @loader.get_or_create_tool(alias_name, @priority)
|
170
|
-
alias_tool.make_alias_of(@tool.simple_name) if alias_tool
|
171
|
-
self
|
172
|
-
end
|
173
|
-
|
174
|
-
##
|
175
|
-
# Make the current tool an alias of the given sibling
|
176
|
-
#
|
177
|
-
# @param [String] word The name of the sibling tool to alias
|
178
|
-
#
|
179
|
-
def alias_of(word)
|
180
|
-
if @tool.root?
|
181
|
-
raise ToolDefinitionError, "Cannot make the root tool an alias"
|
182
|
-
end
|
183
|
-
if @type == :group || @type == :append
|
184
|
-
raise ToolDefinitionError, "Cannot make a group an alias"
|
185
|
-
end
|
186
|
-
@tool.make_alias_of(word.to_s)
|
187
|
-
self
|
188
|
-
end
|
189
|
-
|
190
|
-
##
|
191
|
-
# Include another config file or directory at the current location.
|
192
|
-
#
|
193
|
-
# @param [String] path The file or directory to include.
|
194
|
-
#
|
195
|
-
def include(path)
|
196
|
-
@tool.yield_definition do
|
197
|
-
@loader.include_path(path, @tool.full_name, @remaining_words, @priority)
|
198
|
-
end
|
199
|
-
self
|
200
|
-
end
|
201
|
-
|
202
|
-
##
|
203
|
-
# Expand the given template in the current location.
|
204
|
-
#
|
205
|
-
# The template may be specified as a class or a well-known template name.
|
206
|
-
# You may also provide arguments to pass to the template.
|
207
|
-
#
|
208
|
-
# @param [Class,String,Symbol] template_class The template, either as a
|
209
|
-
# class or a well-known name.
|
210
|
-
# @param [Object...] args Template arguments
|
211
|
-
#
|
212
|
-
def expand(template_class, *args)
|
213
|
-
unless template_class.is_a?(::Class)
|
214
|
-
name = template_class.to_s
|
215
|
-
template_class = Templates.lookup(name)
|
216
|
-
if template_class.nil?
|
217
|
-
raise ToolDefinitionError, "Template not found: #{name.inspect}"
|
218
|
-
end
|
219
|
-
end
|
220
|
-
template = template_class.new(*args)
|
221
|
-
yield template if block_given?
|
222
|
-
instance_exec(template, &template_class.expander)
|
223
|
-
self
|
224
|
-
end
|
225
|
-
|
226
|
-
##
|
227
|
-
# Set the long description for the current tool. The long description is
|
228
|
-
# displayed in the usage documentation for the tool itself.
|
229
|
-
#
|
230
|
-
# @param [String] desc The long description string.
|
231
|
-
#
|
232
|
-
def long_desc(desc)
|
233
|
-
if @type == :append
|
234
|
-
raise ToolDefinitionError, "Cannot set the description when appending"
|
235
|
-
end
|
236
|
-
@tool.long_desc = desc
|
237
|
-
self
|
238
|
-
end
|
239
|
-
|
240
|
-
##
|
241
|
-
# Set the short description for the current tool. The short description is
|
242
|
-
# displayed with the tool in a command list. You may also use the
|
243
|
-
# equivalent method `short_desc`.
|
244
|
-
#
|
245
|
-
# @param [String] desc The short description string.
|
246
|
-
#
|
247
|
-
def desc(desc)
|
248
|
-
if @type == :append
|
249
|
-
raise ToolDefinitionError, "Cannot set the description when appending"
|
250
|
-
end
|
251
|
-
@tool.desc = desc
|
252
|
-
self
|
253
|
-
end
|
254
|
-
alias short_desc desc
|
255
|
-
|
256
|
-
##
|
257
|
-
# Add a switch to the current tool. Each switch must specify a key which
|
258
|
-
# the executor may use to obtain the switch value from the context.
|
259
|
-
# You may then provide the switches themselves in `OptionParser` form.
|
260
|
-
#
|
261
|
-
# @param [Symbol] key The key to use to retrieve the value from the
|
262
|
-
# execution context.
|
263
|
-
# @param [String...] switches The switches in OptionParser format.
|
264
|
-
# @param [Object,nil] accept An OptionParser acceptor. Optional.
|
265
|
-
# @param [Object] default The default value. This is the value that will
|
266
|
-
# be set in the context if this switch is not provided on the command
|
267
|
-
# line. Defaults to `nil`.
|
268
|
-
# @param [String,nil] doc The documentation for the switch, which appears
|
269
|
-
# in the usage documentation. Defaults to `nil` for no documentation.
|
270
|
-
# @param [Boolean] only_unique If true, any switches that are already
|
271
|
-
# defined in this tool are removed from this switch. For example, if
|
272
|
-
# an earlier switch uses `-a`, and this switch wants to use both
|
273
|
-
# `-a` and `-b`, then only `-b` will be assigned to this switch.
|
274
|
-
# Defaults to false.
|
275
|
-
# @param [Proc,nil] handler An optional handler for setting/updating the
|
276
|
-
# value. If given, it should take two arguments, the new given value
|
277
|
-
# and the previous value, and it should return the new value that
|
278
|
-
# should be set. The default handler simply replaces the previous
|
279
|
-
# value. i.e. the default is effectively `-> (val, _prev) { val }`.
|
280
|
-
#
|
281
|
-
def switch(key, *switches,
|
282
|
-
accept: nil, default: nil, doc: nil, only_unique: false, handler: nil)
|
283
|
-
if @type == :append
|
284
|
-
raise ToolDefinitionError, "Cannot add a switch when appending"
|
285
|
-
end
|
286
|
-
@tool.add_switch(key, *switches,
|
287
|
-
accept: accept, default: default, doc: doc,
|
288
|
-
only_unique: only_unique, handler: handler)
|
289
|
-
self
|
290
|
-
end
|
291
|
-
|
292
|
-
##
|
293
|
-
# Add a required positional argument to the current tool. You must specify
|
294
|
-
# a key which the executor may use to obtain the argument value from the
|
295
|
-
# context.
|
296
|
-
#
|
297
|
-
# @param [Symbol] key The key to use to retrieve the value from the
|
298
|
-
# execution context.
|
299
|
-
# @param [Object,nil] accept An OptionParser acceptor. Optional.
|
300
|
-
# @param [String,nil] doc The documentation for the switch, which appears
|
301
|
-
# in the usage documentation. Defaults to `nil` for no documentation.
|
302
|
-
#
|
303
|
-
def required_arg(key, accept: nil, doc: nil)
|
304
|
-
if @type == :append
|
305
|
-
raise ToolDefinitionError, "Cannot add an argument when appending"
|
306
|
-
end
|
307
|
-
@tool.add_required_arg(key, accept: accept, doc: doc)
|
308
|
-
self
|
309
|
-
end
|
310
|
-
|
311
|
-
##
|
312
|
-
# Add an optional positional argument to the current tool. You must specify
|
313
|
-
# a key which the executor may use to obtain the argument value from the
|
314
|
-
# context. If an optional argument is not given on the command line, the
|
315
|
-
# value is set to the given default.
|
316
|
-
#
|
317
|
-
# @param [Symbol] key The key to use to retrieve the value from the
|
318
|
-
# execution context.
|
319
|
-
# @param [Object,nil] accept An OptionParser acceptor. Optional.
|
320
|
-
# @param [Object] default The default value. This is the value that will
|
321
|
-
# be set in the context if this argument is not provided on the command
|
322
|
-
# line. Defaults to `nil`.
|
323
|
-
# @param [String,nil] doc The documentation for the argument, which appears
|
324
|
-
# in the usage documentation. Defaults to `nil` for no documentation.
|
325
|
-
#
|
326
|
-
def optional_arg(key, accept: nil, default: nil, doc: nil)
|
327
|
-
if @type == :append
|
328
|
-
raise ToolDefinitionError, "Cannot add an argument when appending"
|
329
|
-
end
|
330
|
-
@tool.add_optional_arg(key, accept: accept, default: default, doc: doc)
|
331
|
-
self
|
332
|
-
end
|
333
|
-
|
334
|
-
##
|
335
|
-
# Specify what should be done with unmatched positional arguments. You must
|
336
|
-
# specify a key which the executor may use to obtain the remaining args
|
337
|
-
# from the context.
|
338
|
-
#
|
339
|
-
# @param [Symbol] key The key to use to retrieve the value from the
|
340
|
-
# execution context.
|
341
|
-
# @param [Object,nil] accept An OptionParser acceptor. Optional.
|
342
|
-
# @param [Object] default The default value. This is the value that will
|
343
|
-
# be set in the context if no unmatched arguments are provided on the
|
344
|
-
# command line. Defaults to the empty array `[]`.
|
345
|
-
# @param [String,nil] doc The documentation for the remaining arguments,
|
346
|
-
# which appears in the usage documentation. Defaults to `nil` for no
|
347
|
-
# documentation.
|
348
|
-
#
|
349
|
-
def remaining_args(key, accept: nil, default: [], doc: nil)
|
350
|
-
if @type == :append
|
351
|
-
raise ToolDefinitionError, "Cannot add an argument when appending"
|
352
|
-
end
|
353
|
-
@tool.set_remaining_args(key, accept: accept, default: default, doc: doc)
|
354
|
-
self
|
355
|
-
end
|
356
|
-
|
357
|
-
##
|
358
|
-
# Specify the executor for this tool. This is a block that will be called,
|
359
|
-
# with `self` set to a {Toys::Context}.
|
360
|
-
#
|
361
|
-
def execute(&block)
|
362
|
-
if @type == :group || @type == :append
|
363
|
-
raise ToolDefinitionError, "Cannot set the executor of a group"
|
364
|
-
end
|
365
|
-
@tool.executor = block
|
366
|
-
self
|
367
|
-
end
|
368
|
-
|
369
|
-
##
|
370
|
-
# Define a helper method that may be called from this tool's executor.
|
371
|
-
# You must provide a name for the method, and a block for the method
|
372
|
-
# definition.
|
373
|
-
#
|
374
|
-
# @param [String,Symbol] name Name of the method. May not begin with an
|
375
|
-
# underscore.
|
376
|
-
#
|
377
|
-
def helper(name, &block)
|
378
|
-
if @type == :group || @type == :append
|
379
|
-
raise ToolDefinitionError, "Cannot define a helper method to a group"
|
380
|
-
end
|
381
|
-
@tool.add_helper(name, &block)
|
382
|
-
self
|
383
|
-
end
|
384
|
-
|
385
|
-
##
|
386
|
-
# Specify that the given module should be mixed in to this tool's executor.
|
387
|
-
# Effectively, the module is added to the {Toys::Context} object.
|
388
|
-
# You may either provide a module directly, or specify the name of a
|
389
|
-
# well-known module.
|
390
|
-
#
|
391
|
-
# @param [Module,Symbol] mod Module or name of well-known module.
|
392
|
-
#
|
393
|
-
def use(mod)
|
394
|
-
if @type == :group || @type == :append
|
395
|
-
raise ToolDefinitionError, "Cannot use a helper module in a group"
|
396
|
-
end
|
397
|
-
@tool.use_module(mod)
|
398
|
-
self
|
399
|
-
end
|
400
|
-
|
401
|
-
## @private
|
402
|
-
def _binding
|
403
|
-
binding
|
404
|
-
end
|
405
|
-
|
406
|
-
## @private
|
407
|
-
def self.evaluate(path, tool, remaining_words, priority, loader, type, source)
|
408
|
-
dsl = new(path, tool, remaining_words, priority, loader, type)
|
409
|
-
if type == :append
|
410
|
-
eval_source(dsl, path, source)
|
411
|
-
else
|
412
|
-
tool.defining_from(path) do
|
413
|
-
eval_source(dsl, path, source)
|
414
|
-
tool.finish_definition
|
415
|
-
end
|
416
|
-
end
|
417
|
-
tool
|
418
|
-
end
|
419
|
-
|
420
|
-
## @private
|
421
|
-
def self.eval_source(dsl, path, source)
|
422
|
-
case source
|
423
|
-
when String
|
424
|
-
# rubocop:disable Security/Eval
|
425
|
-
eval(source, dsl._binding, path, 1)
|
426
|
-
# rubocop:enable Security/Eval
|
427
|
-
when ::Proc
|
428
|
-
dsl.instance_eval(&source)
|
429
|
-
end
|
430
|
-
end
|
431
|
-
end
|
432
|
-
end
|
data/lib/toys/context.rb
DELETED
@@ -1,278 +0,0 @@
|
|
1
|
-
# Copyright 2018 Daniel Azuma
|
2
|
-
#
|
3
|
-
# All rights reserved.
|
4
|
-
#
|
5
|
-
# Redistribution and use in source and binary forms, with or without
|
6
|
-
# modification, are permitted provided that the following conditions are met:
|
7
|
-
#
|
8
|
-
# * Redistributions of source code must retain the above copyright notice,
|
9
|
-
# this list of conditions and the following disclaimer.
|
10
|
-
# * Redistributions in binary form must reproduce the above copyright notice,
|
11
|
-
# this list of conditions and the following disclaimer in the documentation
|
12
|
-
# and/or other materials provided with the distribution.
|
13
|
-
# * Neither the name of the copyright holder, nor the names of any other
|
14
|
-
# contributors to this software, may be used to endorse or promote products
|
15
|
-
# derived from this software without specific prior written permission.
|
16
|
-
#
|
17
|
-
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
18
|
-
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
19
|
-
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
20
|
-
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
21
|
-
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
22
|
-
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
23
|
-
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
24
|
-
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
25
|
-
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
26
|
-
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
27
|
-
# POSSIBILITY OF SUCH DAMAGE.
|
28
|
-
;
|
29
|
-
|
30
|
-
require "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
|
-
# Create a Context object. Applications generally will not need to create
|
99
|
-
# these objects directly; they are created by the tool when it is preparing
|
100
|
-
# for execution.
|
101
|
-
# @private
|
102
|
-
#
|
103
|
-
# @param [Toys::Context::Base] context_base
|
104
|
-
# @param [Hash] data
|
105
|
-
#
|
106
|
-
def initialize(context_base, data)
|
107
|
-
@_context_base = context_base
|
108
|
-
@_data = data
|
109
|
-
@_data[LOADER] = context_base.loader
|
110
|
-
@_data[BINARY_NAME] = context_base.binary_name
|
111
|
-
@_data[LOGGER] = context_base.logger
|
112
|
-
end
|
113
|
-
|
114
|
-
##
|
115
|
-
# Return the verbosity as an integer.
|
116
|
-
# @return [Integer]
|
117
|
-
#
|
118
|
-
def verbosity
|
119
|
-
@_data[VERBOSITY]
|
120
|
-
end
|
121
|
-
|
122
|
-
##
|
123
|
-
# Return the tool being executed.
|
124
|
-
# @return [Toys::Tool]
|
125
|
-
#
|
126
|
-
def tool
|
127
|
-
@_data[TOOL]
|
128
|
-
end
|
129
|
-
|
130
|
-
##
|
131
|
-
# Return the name of the tool being executed, as an array of strings.
|
132
|
-
# @return [Array[String]]
|
133
|
-
#
|
134
|
-
def tool_name
|
135
|
-
@_data[TOOL_NAME]
|
136
|
-
end
|
137
|
-
|
138
|
-
##
|
139
|
-
# Return the raw arguments passed to the tool, as an array of strings.
|
140
|
-
# This does not include the tool name itself.
|
141
|
-
# @return [Array[String]]
|
142
|
-
#
|
143
|
-
def args
|
144
|
-
@_data[ARGS]
|
145
|
-
end
|
146
|
-
|
147
|
-
##
|
148
|
-
# Return any usage error detected during argument parsing, or `nil` if
|
149
|
-
# no error was detected.
|
150
|
-
# @return [String,nil]
|
151
|
-
#
|
152
|
-
def usage_error
|
153
|
-
@_data[USAGE_ERROR]
|
154
|
-
end
|
155
|
-
|
156
|
-
##
|
157
|
-
# Return the logger for this execution.
|
158
|
-
# @return [Logger]
|
159
|
-
#
|
160
|
-
def logger
|
161
|
-
@_data[LOGGER]
|
162
|
-
end
|
163
|
-
|
164
|
-
##
|
165
|
-
# Return the active loader that can be used to get other tools.
|
166
|
-
# @return [Toys::Loader]
|
167
|
-
#
|
168
|
-
def loader
|
169
|
-
@_data[LOADER]
|
170
|
-
end
|
171
|
-
|
172
|
-
##
|
173
|
-
# Return the name of the binary that was executed.
|
174
|
-
# @return [String]
|
175
|
-
#
|
176
|
-
def binary_name
|
177
|
-
@_data[BINARY_NAME]
|
178
|
-
end
|
179
|
-
|
180
|
-
##
|
181
|
-
# Return a piece of raw data by key.
|
182
|
-
#
|
183
|
-
# @param [Symbol] key
|
184
|
-
# @return [Object]
|
185
|
-
#
|
186
|
-
def [](key)
|
187
|
-
@_data[key]
|
188
|
-
end
|
189
|
-
|
190
|
-
##
|
191
|
-
# Set a piece of data by key.
|
192
|
-
#
|
193
|
-
# Most tools themselves will not need to do this directly. It is generally
|
194
|
-
# used by middleware to modify the context and affect execution of tools
|
195
|
-
# that use it.
|
196
|
-
#
|
197
|
-
# @param [Symbol] key
|
198
|
-
# @param [Object] value
|
199
|
-
#
|
200
|
-
def []=(key, value)
|
201
|
-
@_data[key] = value
|
202
|
-
end
|
203
|
-
|
204
|
-
##
|
205
|
-
# Return an option value by key.
|
206
|
-
#
|
207
|
-
# @param [Symbol] key
|
208
|
-
# @return [Object]
|
209
|
-
#
|
210
|
-
def option(key)
|
211
|
-
@_data[key]
|
212
|
-
end
|
213
|
-
|
214
|
-
##
|
215
|
-
# Returns the subset of the context that does not include well-known keys
|
216
|
-
# such as tool and verbosity. Technically, this includes all keys that do
|
217
|
-
# not begin with two underscores.
|
218
|
-
#
|
219
|
-
# @return [Hash]
|
220
|
-
#
|
221
|
-
def options
|
222
|
-
@_data.select do |k, _v|
|
223
|
-
!k.is_a?(::Symbol) || !k.to_s.start_with?("__")
|
224
|
-
end
|
225
|
-
end
|
226
|
-
|
227
|
-
##
|
228
|
-
# Execute another tool, given by the provided arguments.
|
229
|
-
#
|
230
|
-
# @param [String...] args Command line arguments defining another tool
|
231
|
-
# to run, along with parameters and switches.
|
232
|
-
# @param [Boolean] exit_on_nonzero_status If true, exit immediately if the
|
233
|
-
# run returns a nonzero error code.
|
234
|
-
# @return [Integer] The resulting status code
|
235
|
-
#
|
236
|
-
def run(*args, exit_on_nonzero_status: false)
|
237
|
-
code = @_context_base.run(args.flatten, verbosity: @_data[VERBOSITY])
|
238
|
-
exit(code) if exit_on_nonzero_status && !code.zero?
|
239
|
-
code
|
240
|
-
end
|
241
|
-
|
242
|
-
##
|
243
|
-
# Exit immediately with the given status code
|
244
|
-
#
|
245
|
-
# @param [Integer] code The status code, which should be 0 for no error,
|
246
|
-
# or nonzero for an error condition.
|
247
|
-
#
|
248
|
-
def exit(code)
|
249
|
-
throw :result, code
|
250
|
-
end
|
251
|
-
|
252
|
-
##
|
253
|
-
# Common context data
|
254
|
-
# @private
|
255
|
-
#
|
256
|
-
class Base
|
257
|
-
def initialize(loader, binary_name, logger)
|
258
|
-
@loader = loader
|
259
|
-
@binary_name = binary_name || ::File.basename($PROGRAM_NAME)
|
260
|
-
@logger = logger || ::Logger.new(::STDERR)
|
261
|
-
@base_level = @logger.level
|
262
|
-
end
|
263
|
-
|
264
|
-
attr_reader :loader
|
265
|
-
attr_reader :binary_name
|
266
|
-
attr_reader :logger
|
267
|
-
attr_reader :base_level
|
268
|
-
|
269
|
-
def run(args, verbosity: 0)
|
270
|
-
@loader.execute(self, args, verbosity: verbosity)
|
271
|
-
end
|
272
|
-
|
273
|
-
def create_context(data)
|
274
|
-
Context.new(self, data)
|
275
|
-
end
|
276
|
-
end
|
277
|
-
end
|
278
|
-
end
|