toys-core 0.11.5 → 0.13.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +62 -0
- data/LICENSE.md +1 -1
- data/README.md +5 -2
- data/docs/guide.md +1 -1
- data/lib/toys/acceptor.rb +13 -4
- data/lib/toys/arg_parser.rb +7 -7
- data/lib/toys/cli.rb +170 -120
- data/lib/toys/compat.rb +71 -23
- data/lib/toys/completion.rb +18 -6
- data/lib/toys/context.rb +24 -15
- data/lib/toys/core.rb +6 -2
- data/lib/toys/dsl/base.rb +87 -0
- data/lib/toys/dsl/flag.rb +26 -20
- data/lib/toys/dsl/flag_group.rb +18 -14
- data/lib/toys/dsl/internal.rb +206 -0
- data/lib/toys/dsl/positional_arg.rb +26 -16
- data/lib/toys/dsl/tool.rb +180 -218
- data/lib/toys/errors.rb +64 -8
- data/lib/toys/flag.rb +662 -656
- data/lib/toys/flag_group.rb +24 -10
- data/lib/toys/input_file.rb +13 -7
- data/lib/toys/loader.rb +293 -140
- data/lib/toys/middleware.rb +46 -22
- data/lib/toys/mixin.rb +10 -8
- data/lib/toys/positional_arg.rb +21 -20
- data/lib/toys/settings.rb +914 -0
- data/lib/toys/source_info.rb +147 -35
- data/lib/toys/standard_middleware/add_verbosity_flags.rb +2 -0
- data/lib/toys/standard_middleware/apply_config.rb +6 -4
- data/lib/toys/standard_middleware/handle_usage_errors.rb +1 -0
- data/lib/toys/standard_middleware/set_default_descriptions.rb +19 -18
- data/lib/toys/standard_middleware/show_help.rb +19 -5
- data/lib/toys/standard_middleware/show_root_version.rb +2 -0
- data/lib/toys/standard_mixins/bundler.rb +24 -15
- data/lib/toys/standard_mixins/exec.rb +43 -34
- data/lib/toys/standard_mixins/fileutils.rb +3 -1
- data/lib/toys/standard_mixins/gems.rb +21 -17
- data/lib/toys/standard_mixins/git_cache.rb +46 -0
- data/lib/toys/standard_mixins/highline.rb +8 -8
- data/lib/toys/standard_mixins/terminal.rb +5 -5
- data/lib/toys/standard_mixins/xdg.rb +56 -0
- data/lib/toys/template.rb +11 -9
- data/lib/toys/{tool.rb → tool_definition.rb} +292 -226
- data/lib/toys/utils/completion_engine.rb +7 -2
- data/lib/toys/utils/exec.rb +162 -132
- data/lib/toys/utils/gems.rb +85 -60
- data/lib/toys/utils/git_cache.rb +813 -0
- data/lib/toys/utils/help_text.rb +117 -37
- data/lib/toys/utils/terminal.rb +11 -3
- data/lib/toys/utils/xdg.rb +293 -0
- data/lib/toys/wrappable_string.rb +9 -2
- data/lib/toys-core.rb +18 -6
- metadata +14 -7
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 68a552244c25e03216c7bf958a7ccd2a35bb7fb3795a02e77c4a839b7720e258
|
4
|
+
data.tar.gz: 58513ce7d5d60646a1b9e51235d04df470ee5d9317a9a72048a8e95872227da0
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 299c193de07d0802046029c7b305a1e4cecac715eee32d259a917b6ecc2505975ba10a2aca90b510036076d9f32b58849a02381ae61837336eee5361cd2126ea
|
7
|
+
data.tar.gz: b42a4d81db4a71a136deafd4a62f0e7087325097a1c3e7473a5b7edda699b08ee1c8d81377765a70d8a18fd0a2693874781418f9953ca52f92cced670bec1421
|
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,67 @@
|
|
1
1
|
# Release History
|
2
2
|
|
3
|
+
### v0.13.0 / 2022-02-08
|
4
|
+
|
5
|
+
Toys-Core 0.13.0 is a major release with significant improvements to the git cache, along with compatibility improvements and bug fixes.
|
6
|
+
|
7
|
+
New functionality:
|
8
|
+
|
9
|
+
* The `load_git` directive and the underlying `Toys::Utils::GitCache` class now support updating from git based on cache age.
|
10
|
+
* The `Toys::Utils::GitCache` class supports copying git content into a provided directory, querying repo information, and deleting cache data.
|
11
|
+
* The `Toys::Utils::GitCache` class makes files read-only, to help prevent clients from interfering with one another.
|
12
|
+
* The `:terminal` mixin and the underlying `Toys::Utils::Terminal` class now honor the `NO_COLOR` environment variable.
|
13
|
+
* Added `Toys::CLI#load_tool`, which is useful for testing tools.
|
14
|
+
|
15
|
+
Fixes and compatibility updates:
|
16
|
+
|
17
|
+
* Bundler install/updates are now spawned in subprocesses for compatibility with bundler 2.3. The bundler integration also now requires bundler 2.2 or later.
|
18
|
+
* The `exec_tool` and `exec_proc` methods in the `:exec` mixin now log their execution in the same way as other exec functions.
|
19
|
+
* Minor compatibility fixes to provide partial support for TruffleRuby.
|
20
|
+
|
21
|
+
Other notes:
|
22
|
+
|
23
|
+
* The internal GitCache representation has changed significantly to support additional features and improve robustness and performance. This will force existing caches to update, but should not break existing usage.
|
24
|
+
|
25
|
+
### v0.12.2 / 2021-08-30
|
26
|
+
|
27
|
+
* FIXED: Tool context inspect string is no longer overwhelmingly long
|
28
|
+
* FIXED: Fixed an exception in GitCache when updating a changed ref
|
29
|
+
|
30
|
+
### v0.12.1 / 2021-08-17
|
31
|
+
|
32
|
+
* FIXED: Fixed a regression in 0.12.0 where bundler could use the wrong Gemfile if you set a custom context directory
|
33
|
+
|
34
|
+
### v0.12.0 / 2021-08-05
|
35
|
+
|
36
|
+
Toys-Core 0.12.0 is a major release with significant new features and bug fixes, and a few breaking interface changes. Additionally, this release now requires Ruby 2.4 or later.
|
37
|
+
|
38
|
+
Breaking interface changes:
|
39
|
+
|
40
|
+
* The Toys::Tool class has been renamed Toys::ToolDefinition so that the old name can be used for class-based tool definition.
|
41
|
+
* Tool definition now raises ToolDefinitionError if whitespace, control characters, or certain punctuation are used in a tool name.
|
42
|
+
* Toys::Loader#add_path no longer supports multiple paths. Use add_path_set instead.
|
43
|
+
* The "name" argument was renamed to "source_name" in Toys::Loader#add_block and Toys::CLI#add_config_block
|
44
|
+
|
45
|
+
New functionality:
|
46
|
+
|
47
|
+
* The DSL now supports a class-based tool definition syntax (in addition to the existing block-based syntax). Some users may prefer this new class-based style as more Ruby-like.
|
48
|
+
* You can now load tools from a remote git repository using the load_git directive.
|
49
|
+
* Whitespace is now automatically considered a name delimiter when defining tools.
|
50
|
+
* There is now an extensible settings mechanism to activate less-common tool behavior. Currently there is one setting, which causes subtools to inherit their parent's methods by default.
|
51
|
+
* The load directive can load into a new tool.
|
52
|
+
* Added a new utility class and mixin that provides XDG Base Directory information.
|
53
|
+
* Added a new utility class and mixin that provides cached access to remote git repos.
|
54
|
+
* The help text generator now supports splitting the subtool list by source.
|
55
|
+
* Loader and CLI methods that add tool configs now uniformly provide optional source_name and context_directory arguments.
|
56
|
+
* Toys::SourceInfo now supports getting the root ancestor and priority of a source.
|
57
|
+
* Toys::ToolDefinition now has a direct accessor for the source root. This is always set for a tool, even if it isn't marked as finished.
|
58
|
+
|
59
|
+
Fixes:
|
60
|
+
|
61
|
+
* Fixed some bundler integration issues that occurred when the bundle is being installed in a separate path such as a vendor directory.
|
62
|
+
* Toys::ContextualError now includes the full backtrace of the cause.
|
63
|
+
* Cleaned up some unused memory objects during tool loading and lookup.
|
64
|
+
|
3
65
|
### v0.11.5 / 2021-03-28
|
4
66
|
|
5
67
|
* BREAKING CHANGE: The exit_on_nonzero_status option to exec now exits on signals and failures to spawn, in addition to error codes.
|
data/LICENSE.md
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
# License
|
2
2
|
|
3
|
-
Copyright 2019-
|
3
|
+
Copyright 2019-2022 Daniel Azuma and the Toys contributors
|
4
4
|
|
5
5
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
6
|
of this software and associated documentation files (the "Software"), to deal
|
data/README.md
CHANGED
@@ -34,11 +34,14 @@ Install the **toys-core** gem using:
|
|
34
34
|
You may also install the **toys** gem, which brings in **toys-core** as a
|
35
35
|
dependency.
|
36
36
|
|
37
|
-
Toys-Core requires Ruby 2.
|
37
|
+
Toys-Core requires Ruby 2.4 or later.
|
38
38
|
|
39
39
|
Most parts of Toys-Core work on JRuby. However, JRuby is not recommended
|
40
40
|
because of JVM boot latency, lack of support for Kernel#fork, and other issues.
|
41
41
|
|
42
|
+
Most parts of Toys-Core work on TruffleRuby. However, TruffleRuby is not
|
43
|
+
recommended because it has a few known bugs that affect Toys.
|
44
|
+
|
42
45
|
### Create a new executable
|
43
46
|
|
44
47
|
We'll start by creating an executable Ruby script. Using your favorite text
|
@@ -338,7 +341,7 @@ templates, and middleware, in the
|
|
338
341
|
|
339
342
|
## License
|
340
343
|
|
341
|
-
Copyright 2019-
|
344
|
+
Copyright 2019-2022 Daniel Azuma and the Toys contributors
|
342
345
|
|
343
346
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
344
347
|
of this software and associated documentation files (the "Software"), to deal
|
data/docs/guide.md
CHANGED
@@ -31,7 +31,7 @@ sophisticated command line tools.
|
|
31
31
|
|
32
32
|
Toys-Core is a command line *framework* in the traditional sense. It is
|
33
33
|
intended to be used to write custom command line executables in Ruby. The
|
34
|
-
framework provides common facilities such as
|
34
|
+
framework provides common facilities such as argument parsing and online help,
|
35
35
|
while your executable chooses and configures those facilities, and implements
|
36
36
|
the actual behavior.
|
37
37
|
|
data/lib/toys/acceptor.rb
CHANGED
@@ -178,6 +178,7 @@ module Toys
|
|
178
178
|
|
179
179
|
##
|
180
180
|
# Overrides {Toys::Acceptor::Base#match} to use the given function.
|
181
|
+
#
|
181
182
|
# @private
|
182
183
|
#
|
183
184
|
def match(str)
|
@@ -188,6 +189,7 @@ module Toys
|
|
188
189
|
##
|
189
190
|
# Overrides {Toys::Acceptor::Base#convert} to use the given function's
|
190
191
|
# result.
|
192
|
+
#
|
191
193
|
# @private
|
192
194
|
#
|
193
195
|
def convert(_str, result)
|
@@ -234,6 +236,7 @@ module Toys
|
|
234
236
|
|
235
237
|
##
|
236
238
|
# Overrides {Toys::Acceptor::Base#match} to use the given regex.
|
239
|
+
#
|
237
240
|
# @private
|
238
241
|
#
|
239
242
|
def match(str)
|
@@ -242,6 +245,7 @@ module Toys
|
|
242
245
|
|
243
246
|
##
|
244
247
|
# Overrides {Toys::Acceptor::Base#convert} to use the given converter.
|
248
|
+
#
|
245
249
|
# @private
|
246
250
|
#
|
247
251
|
def convert(str, *extra)
|
@@ -285,6 +289,7 @@ module Toys
|
|
285
289
|
|
286
290
|
##
|
287
291
|
# Overrides {Toys::Acceptor::Base#match} to find the value.
|
292
|
+
#
|
288
293
|
# @private
|
289
294
|
#
|
290
295
|
def match(str)
|
@@ -294,6 +299,7 @@ module Toys
|
|
294
299
|
##
|
295
300
|
# Overrides {Toys::Acceptor::Base#convert} to return the actual enum
|
296
301
|
# element.
|
302
|
+
#
|
297
303
|
# @private
|
298
304
|
#
|
299
305
|
def convert(_str, elem)
|
@@ -303,6 +309,7 @@ module Toys
|
|
303
309
|
##
|
304
310
|
# Overrides {Toys::Acceptor::Base#suggestions} to return close matches
|
305
311
|
# from the enum.
|
312
|
+
#
|
306
313
|
# @private
|
307
314
|
#
|
308
315
|
def suggestions(str)
|
@@ -513,7 +520,9 @@ module Toys
|
|
513
520
|
internal_create(spec, options, block)
|
514
521
|
end
|
515
522
|
|
516
|
-
##
|
523
|
+
##
|
524
|
+
# @private
|
525
|
+
#
|
517
526
|
def scalarize_spec(spec, options, block)
|
518
527
|
spec ||= block
|
519
528
|
if options.empty?
|
@@ -602,11 +611,11 @@ module Toys
|
|
602
611
|
Simple.new(type_desc: "boolean", well_known_spec: spec) do |s|
|
603
612
|
if s.nil?
|
604
613
|
default
|
614
|
+
elsif s.empty?
|
615
|
+
REJECT
|
605
616
|
else
|
606
617
|
s = s.downcase
|
607
|
-
if
|
608
|
-
REJECT
|
609
|
-
elsif TRUE_STRINGS.any? { |t| t.start_with?(s) }
|
618
|
+
if TRUE_STRINGS.any? { |t| t.start_with?(s) }
|
610
619
|
true
|
611
620
|
elsif FALSE_STRINGS.any? { |f| f.start_with?(s) }
|
612
621
|
false
|
data/lib/toys/arg_parser.rb
CHANGED
@@ -261,7 +261,7 @@ module Toys
|
|
261
261
|
# @param message [String] The message. Required.
|
262
262
|
#
|
263
263
|
def initialize(message)
|
264
|
-
super(message)
|
264
|
+
super(message, name: nil)
|
265
265
|
end
|
266
266
|
end
|
267
267
|
|
@@ -269,7 +269,7 @@ module Toys
|
|
269
269
|
# Create an argument parser for a particular tool.
|
270
270
|
#
|
271
271
|
# @param cli [Toys::CLI] The CLI in effect.
|
272
|
-
# @param tool [Toys::
|
272
|
+
# @param tool [Toys::ToolDefinition] The tool defining the argument format.
|
273
273
|
# @param default_data [Hash] Additional initial data (such as verbosity).
|
274
274
|
# @param require_exact_flag_match [Boolean] Whether to require flag matches
|
275
275
|
# be exact (not partial). Default is false.
|
@@ -295,7 +295,7 @@ module Toys
|
|
295
295
|
|
296
296
|
##
|
297
297
|
# The tool definition governing this parser.
|
298
|
-
# @return [Toys::
|
298
|
+
# @return [Toys::ToolDefinition]
|
299
299
|
#
|
300
300
|
attr_reader :tool
|
301
301
|
|
@@ -417,6 +417,7 @@ module Toys
|
|
417
417
|
|
418
418
|
REMAINING_HANDLER = ->(val, prev) { prev.is_a?(::Array) ? prev << val : [val] }
|
419
419
|
ARG_HANDLER = ->(val, _prev) { val }
|
420
|
+
private_constant :REMAINING_HANDLER, :ARG_HANDLER
|
420
421
|
|
421
422
|
def initial_data(cli, tool, default_data)
|
422
423
|
data = {
|
@@ -429,7 +430,7 @@ module Toys
|
|
429
430
|
Context::Key::TOOL_NAME => tool.full_name,
|
430
431
|
Context::Key::USAGE_ERRORS => [],
|
431
432
|
}
|
432
|
-
|
433
|
+
tool.default_data.each { |k, v| data[k] = v.clone }
|
433
434
|
default_data.each { |k, v| data[k] ||= v }
|
434
435
|
data
|
435
436
|
end
|
@@ -450,7 +451,7 @@ module Toys
|
|
450
451
|
case arg
|
451
452
|
when "--"
|
452
453
|
@flags_allowed = false
|
453
|
-
when /\A(--\w[
|
454
|
+
when /\A(--\w[?\w-]*)=(.*)\z/
|
454
455
|
handle_valued_flag(::Regexp.last_match(1), ::Regexp.last_match(2))
|
455
456
|
when /\A--.+\z/
|
456
457
|
handle_plain_flag(arg)
|
@@ -474,8 +475,7 @@ module Toys
|
|
474
475
|
return "" unless flag_def
|
475
476
|
@seen_flag_keys << flag_def.key
|
476
477
|
if flag_def.flag_type == :boolean
|
477
|
-
add_data(flag_def.key, flag_def.handler, nil, !flag_result.unique_flag_negative?,
|
478
|
-
:flag, name)
|
478
|
+
add_data(flag_def.key, flag_def.handler, nil, !flag_result.unique_flag_negative?, :flag, name)
|
479
479
|
elsif following.empty?
|
480
480
|
if flag_def.value_type == :required || flag_result.unique_flag_syntax.value_delim == " "
|
481
481
|
@active_flag_def = flag_def
|
data/lib/toys/cli.rb
CHANGED
@@ -78,8 +78,8 @@ module Toys
|
|
78
78
|
# with different verbosity settings (since the logger cannot have
|
79
79
|
# multiple level settings simultaneously). In that case, do not set a
|
80
80
|
# global logger, but use the `logger_factory` parameter instead.
|
81
|
-
# @param logger_factory [Proc] A proc that takes a {Toys::
|
82
|
-
# argument, and returns a `Logger` to use when running that tool.
|
81
|
+
# @param logger_factory [Proc] A proc that takes a {Toys::ToolDefinition}
|
82
|
+
# as an argument, and returns a `Logger` to use when running that tool.
|
83
83
|
# Optional. If not provided (and no global logger is set), CLI will use
|
84
84
|
# a default factory that writes generates loggers writing formatted
|
85
85
|
# output to `STDERR`, as defined by {Toys::CLI.default_logger_factory}.
|
@@ -175,26 +175,24 @@ module Toys
|
|
175
175
|
# Optional. If not provided, lib directories are disabled.
|
176
176
|
# Note: the standard toys executable sets this to `".lib"`.
|
177
177
|
#
|
178
|
-
def initialize( # rubocop:disable Metrics/MethodLength
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
completion: nil
|
197
|
-
)
|
178
|
+
def initialize(executable_name: nil, # rubocop:disable Metrics/MethodLength
|
179
|
+
middleware_stack: nil,
|
180
|
+
extra_delimiters: "",
|
181
|
+
config_dir_name: nil,
|
182
|
+
config_file_name: nil,
|
183
|
+
index_file_name: nil,
|
184
|
+
preload_file_name: nil,
|
185
|
+
preload_dir_name: nil,
|
186
|
+
data_dir_name: nil,
|
187
|
+
lib_dir_name: nil,
|
188
|
+
mixin_lookup: nil,
|
189
|
+
middleware_lookup: nil,
|
190
|
+
template_lookup: nil,
|
191
|
+
logger_factory: nil,
|
192
|
+
logger: nil,
|
193
|
+
base_level: nil,
|
194
|
+
error_handler: nil,
|
195
|
+
completion: nil)
|
198
196
|
@executable_name = executable_name || ::File.basename($PROGRAM_NAME)
|
199
197
|
@middleware_stack = middleware_stack || CLI.default_middleware_stack
|
200
198
|
@mixin_lookup = mixin_lookup || CLI.default_mixin_lookup
|
@@ -321,10 +319,22 @@ module Toys
|
|
321
319
|
# a Toys directory.
|
322
320
|
# @param high_priority [Boolean] Add the config at the head of the priority
|
323
321
|
# list rather than the tail.
|
322
|
+
# @param source_name [String] A custom name for the root source. Optional.
|
323
|
+
# @param context_directory [String,nil,:path,:parent] The context directory
|
324
|
+
# for tools loaded from this path. You can pass a directory path as a
|
325
|
+
# string, `:path` to denote the given path, `:parent` to denote the
|
326
|
+
# given path's parent directory, or `nil` to denote no context.
|
327
|
+
# Defaults to `:parent`.
|
324
328
|
# @return [self]
|
325
329
|
#
|
326
|
-
def add_config_path(path,
|
327
|
-
|
330
|
+
def add_config_path(path,
|
331
|
+
high_priority: false,
|
332
|
+
source_name: nil,
|
333
|
+
context_directory: :parent)
|
334
|
+
@loader.add_path(path,
|
335
|
+
high_priority: high_priority,
|
336
|
+
source_name: source_name,
|
337
|
+
context_directory: context_directory)
|
328
338
|
self
|
329
339
|
end
|
330
340
|
|
@@ -336,15 +346,24 @@ module Toys
|
|
336
346
|
#
|
337
347
|
# @param high_priority [Boolean] Add the config at the head of the priority
|
338
348
|
# list rather than the tail.
|
339
|
-
# @param
|
340
|
-
# for tools defined in this block. If omitted, a default
|
341
|
-
# will be generated.
|
349
|
+
# @param source_name [String] The source name that will be shown in
|
350
|
+
# documentation for tools defined in this block. If omitted, a default
|
351
|
+
# unique string will be generated.
|
342
352
|
# @param block [Proc] The block of configuration, executed in the context
|
343
353
|
# of the tool DSL {Toys::DSL::Tool}.
|
354
|
+
# @param context_directory [String,nil] The context directory for tools
|
355
|
+
# loaded from this block. You can pass a directory path as a string, or
|
356
|
+
# `nil` to denote no context. Defaults to `nil`.
|
344
357
|
# @return [self]
|
345
358
|
#
|
346
|
-
def add_config_block(high_priority: false,
|
347
|
-
|
359
|
+
def add_config_block(high_priority: false,
|
360
|
+
source_name: nil,
|
361
|
+
context_directory: nil,
|
362
|
+
&block)
|
363
|
+
@loader.add_block(high_priority: high_priority,
|
364
|
+
source_name: source_name,
|
365
|
+
context_directory: context_directory,
|
366
|
+
&block)
|
348
367
|
self
|
349
368
|
end
|
350
369
|
|
@@ -358,19 +377,28 @@ module Toys
|
|
358
377
|
# @param search_path [String] A path to search for configs.
|
359
378
|
# @param high_priority [Boolean] Add the configs at the head of the
|
360
379
|
# priority list rather than the tail.
|
380
|
+
# @param context_directory [String,nil,:path,:parent] The context directory
|
381
|
+
# for tools loaded from this path. You can pass a directory path as a
|
382
|
+
# string, `:path` to denote the given path, `:parent` to denote the
|
383
|
+
# given path's parent directory, or `nil` to denote no context.
|
384
|
+
# Defaults to `:path`.
|
361
385
|
# @return [self]
|
362
386
|
#
|
363
|
-
def add_search_path(search_path,
|
387
|
+
def add_search_path(search_path,
|
388
|
+
high_priority: false,
|
389
|
+
context_directory: :path)
|
364
390
|
paths = []
|
365
391
|
if @config_file_name
|
366
392
|
file_path = ::File.join(search_path, @config_file_name)
|
367
|
-
paths <<
|
393
|
+
paths << @config_file_name if !::File.directory?(file_path) && ::File.readable?(file_path)
|
368
394
|
end
|
369
395
|
if @config_dir_name
|
370
396
|
dir_path = ::File.join(search_path, @config_dir_name)
|
371
|
-
paths <<
|
397
|
+
paths << @config_dir_name if ::File.directory?(dir_path) && ::File.readable?(dir_path)
|
372
398
|
end
|
373
|
-
@loader.
|
399
|
+
@loader.add_path_set(search_path, paths,
|
400
|
+
high_priority: high_priority,
|
401
|
+
context_directory: context_directory)
|
374
402
|
self
|
375
403
|
end
|
376
404
|
|
@@ -416,6 +444,9 @@ module Toys
|
|
416
444
|
# run and what arguments to pass to it. You may pass either a single
|
417
445
|
# array of strings, or a series of string arguments.
|
418
446
|
# @param verbosity [Integer] Initial verbosity. Default is 0.
|
447
|
+
# @param delegated_from [Toys::Context] The context from which this
|
448
|
+
# execution is delegated. Optional. Should be set only if this is a
|
449
|
+
# delegated execution.
|
419
450
|
#
|
420
451
|
# @return [Integer] The resulting process status code (i.e. 0 for success).
|
421
452
|
#
|
@@ -427,101 +458,32 @@ module Toys
|
|
427
458
|
"Error during tool execution!", tool.source_info&.source_path,
|
428
459
|
tool_name: tool.full_name, tool_args: remaining
|
429
460
|
) do
|
430
|
-
|
431
|
-
|
432
|
-
|
433
|
-
|
434
|
-
run_tool(tool, remaining, default_data)
|
461
|
+
context = build_context(tool, remaining,
|
462
|
+
verbosity: verbosity,
|
463
|
+
delegated_from: delegated_from)
|
464
|
+
execute_tool(tool, context, &:run)
|
435
465
|
end
|
436
466
|
rescue ContextualError, ::Interrupt => e
|
437
467
|
@error_handler.call(e).to_i
|
438
468
|
end
|
439
469
|
|
440
|
-
private
|
441
|
-
|
442
470
|
##
|
443
|
-
#
|
444
|
-
#
|
471
|
+
# Prepare a tool to be run, but just execute the given block rather than
|
472
|
+
# performing a full run of the tool. This is intended for testing tools.
|
473
|
+
# Unlike {#run}, this does not catch errors and perform error handling.
|
445
474
|
#
|
446
|
-
# @param
|
447
|
-
#
|
448
|
-
#
|
449
|
-
# @
|
475
|
+
# @param args [String...] Command line arguments specifying which tool to
|
476
|
+
# run and what arguments to pass to it. You may pass either a single
|
477
|
+
# array of strings, or a series of string arguments.
|
478
|
+
# @yieldparam context [Toys::Context] Yields the tool context.
|
450
479
|
#
|
451
|
-
|
452
|
-
|
453
|
-
|
454
|
-
|
455
|
-
|
456
|
-
context
|
457
|
-
|
458
|
-
tool.run_initializers(context)
|
459
|
-
|
460
|
-
cur_logger = context[Context::Key::LOGGER]
|
461
|
-
if cur_logger
|
462
|
-
original_level = cur_logger.level
|
463
|
-
cur_logger.level = (base_level || original_level) - context[Context::Key::VERBOSITY].to_i
|
464
|
-
end
|
465
|
-
begin
|
466
|
-
execute_tool_in_context(context, tool)
|
467
|
-
ensure
|
468
|
-
cur_logger.level = original_level if cur_logger
|
469
|
-
end
|
470
|
-
end
|
471
|
-
|
472
|
-
def execute_tool_in_context(context, tool)
|
473
|
-
executor = proc do
|
474
|
-
begin
|
475
|
-
if !context[Context::Key::USAGE_ERRORS].empty?
|
476
|
-
handle_usage_errors(context, tool)
|
477
|
-
elsif !tool.runnable?
|
478
|
-
raise NotRunnableError, "No implementation for tool #{tool.display_name.inspect}"
|
479
|
-
else
|
480
|
-
context.run
|
481
|
-
end
|
482
|
-
rescue ::Interrupt => e
|
483
|
-
raise e unless tool.handles_interrupts?
|
484
|
-
handle_interrupt(context, tool.interrupt_handler, e)
|
485
|
-
end
|
486
|
-
end
|
487
|
-
tool.built_middleware.reverse_each do |middleware|
|
488
|
-
executor = make_executor(middleware, context, executor)
|
489
|
-
end
|
490
|
-
catch(:result) do
|
491
|
-
executor.call
|
492
|
-
0
|
493
|
-
end
|
494
|
-
end
|
495
|
-
|
496
|
-
def handle_usage_errors(context, tool)
|
497
|
-
usage_errors = context[Context::Key::USAGE_ERRORS]
|
498
|
-
handler = tool.usage_error_handler
|
499
|
-
raise ArgParsingError, usage_errors if handler.nil?
|
500
|
-
handler = context.method(handler).to_proc if handler.is_a?(::Symbol)
|
501
|
-
if handler.arity.zero?
|
502
|
-
context.instance_exec(&handler)
|
503
|
-
else
|
504
|
-
context.instance_exec(usage_errors, &handler)
|
505
|
-
end
|
506
|
-
end
|
507
|
-
|
508
|
-
def handle_interrupt(context, handler, exception)
|
509
|
-
handler = context.method(handler).to_proc if handler.is_a?(::Symbol)
|
510
|
-
if handler.arity.zero?
|
511
|
-
context.instance_exec(&handler)
|
512
|
-
else
|
513
|
-
context.instance_exec(exception, &handler)
|
514
|
-
end
|
515
|
-
rescue ::Interrupt => e
|
516
|
-
raise e if e.equal?(exception)
|
517
|
-
handle_interrupt(context, handler, e)
|
518
|
-
end
|
519
|
-
|
520
|
-
def make_executor(middleware, context, next_executor)
|
521
|
-
if middleware.respond_to?(:run)
|
522
|
-
proc { middleware.run(context, &next_executor) }
|
523
|
-
else
|
524
|
-
next_executor
|
480
|
+
# @return [Object] The value returned from the block.
|
481
|
+
#
|
482
|
+
def load_tool(*args)
|
483
|
+
tool, remaining = @loader.lookup(args.flatten)
|
484
|
+
context = build_context(tool, remaining)
|
485
|
+
execute_tool(tool, context) do |ctx|
|
486
|
+
ctx.exit(yield ctx)
|
525
487
|
end
|
526
488
|
end
|
527
489
|
|
@@ -727,5 +689,93 @@ module Toys
|
|
727
689
|
"#{styled_header} #{msg}\n"
|
728
690
|
end
|
729
691
|
end
|
692
|
+
|
693
|
+
private
|
694
|
+
|
695
|
+
def build_context(tool, args, verbosity: 0, delegated_from: nil)
|
696
|
+
default_data = {
|
697
|
+
Context::Key::VERBOSITY => verbosity,
|
698
|
+
Context::Key::DELEGATED_FROM => delegated_from,
|
699
|
+
}
|
700
|
+
arg_parser = ArgParser.new(self, tool,
|
701
|
+
default_data: default_data,
|
702
|
+
require_exact_flag_match: tool.exact_flag_match_required?)
|
703
|
+
arg_parser.parse(args).finish
|
704
|
+
tool.tool_class.new(arg_parser.data)
|
705
|
+
end
|
706
|
+
|
707
|
+
def execute_tool(tool, context)
|
708
|
+
tool.source_info&.apply_lib_paths
|
709
|
+
tool.run_initializers(context)
|
710
|
+
cur_logger = context[Context::Key::LOGGER]
|
711
|
+
if cur_logger
|
712
|
+
original_level = cur_logger.level
|
713
|
+
cur_logger.level = (base_level || original_level) - context[Context::Key::VERBOSITY].to_i
|
714
|
+
end
|
715
|
+
begin
|
716
|
+
executor = build_executor(tool, context) do
|
717
|
+
yield context
|
718
|
+
end
|
719
|
+
catch(:result) do
|
720
|
+
executor.call
|
721
|
+
0
|
722
|
+
end
|
723
|
+
ensure
|
724
|
+
cur_logger.level = original_level if cur_logger
|
725
|
+
end
|
726
|
+
end
|
727
|
+
|
728
|
+
def build_executor(tool, context)
|
729
|
+
executor = proc do
|
730
|
+
begin
|
731
|
+
if !context[Context::Key::USAGE_ERRORS].empty?
|
732
|
+
handle_usage_errors(context, tool)
|
733
|
+
elsif !tool.runnable?
|
734
|
+
raise NotRunnableError, "No implementation for tool #{tool.display_name.inspect}"
|
735
|
+
else
|
736
|
+
yield
|
737
|
+
end
|
738
|
+
rescue ::Interrupt => e
|
739
|
+
raise e unless tool.handles_interrupts?
|
740
|
+
handle_interrupt(context, tool.interrupt_handler, e)
|
741
|
+
end
|
742
|
+
end
|
743
|
+
tool.built_middleware.reverse_each do |middleware|
|
744
|
+
executor = make_executor(middleware, context, executor)
|
745
|
+
end
|
746
|
+
executor
|
747
|
+
end
|
748
|
+
|
749
|
+
def handle_usage_errors(context, tool)
|
750
|
+
usage_errors = context[Context::Key::USAGE_ERRORS]
|
751
|
+
handler = tool.usage_error_handler
|
752
|
+
raise ArgParsingError, usage_errors if handler.nil?
|
753
|
+
handler = context.method(handler).to_proc if handler.is_a?(::Symbol)
|
754
|
+
if handler.arity.zero?
|
755
|
+
context.instance_exec(&handler)
|
756
|
+
else
|
757
|
+
context.instance_exec(usage_errors, &handler)
|
758
|
+
end
|
759
|
+
end
|
760
|
+
|
761
|
+
def handle_interrupt(context, handler, exception)
|
762
|
+
handler = context.method(handler).to_proc if handler.is_a?(::Symbol)
|
763
|
+
if handler.arity.zero?
|
764
|
+
context.instance_exec(&handler)
|
765
|
+
else
|
766
|
+
context.instance_exec(exception, &handler)
|
767
|
+
end
|
768
|
+
rescue ::Interrupt => e
|
769
|
+
raise e if e.equal?(exception)
|
770
|
+
handle_interrupt(context, handler, e)
|
771
|
+
end
|
772
|
+
|
773
|
+
def make_executor(middleware, context, next_executor)
|
774
|
+
if middleware.respond_to?(:run)
|
775
|
+
proc { middleware.run(context, &next_executor) }
|
776
|
+
else
|
777
|
+
next_executor
|
778
|
+
end
|
779
|
+
end
|
730
780
|
end
|
731
781
|
end
|