toys-core 0.19.1 → 0.20.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 +25 -0
- data/README.md +3 -3
- data/lib/toys/cli.rb +1 -1
- data/lib/toys/compat.rb +63 -0
- data/lib/toys/core.rb +1 -1
- data/lib/toys/errors.rb +12 -11
- data/lib/toys/standard_mixins/bundler.rb +56 -8
- data/lib/toys/standard_mixins/exec.rb +6 -5
- data/lib/toys/utils/completion_engine.rb +88 -32
- data/lib/toys/utils/exec.rb +100 -41
- data/lib/toys/utils/gems.rb +2 -0
- data/lib/toys/utils/pager.rb +5 -1
- metadata +4 -4
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: f391c9aad35948c882c03809a80079b592c59208547080ae68bf2e0052896f9c
|
|
4
|
+
data.tar.gz: 4aacba23fe5a5a1560b4f38c6a109e5af7b6c98b9777b0c1ce3906bd6e42810c
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: c8a4e6ec594002577f1e81b937c1fdd23aed6ffe9adb2535073eba4d2eabe1c0e9e1e0062ea135062e7169734f65f20d0c9a3138dceca4b514ad86a67c215016
|
|
7
|
+
data.tar.gz: c00f4cf11e0af3807cdc0559275ab79f3af4f1fe1eff83cedcda125eedabc04f7ec439d95fa65b7f3763ca86b10e30fd6a5cf8d6412f02cd2e8dae9170d9e7fd
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,30 @@
|
|
|
1
1
|
# Release History
|
|
2
2
|
|
|
3
|
+
### v0.20.0 / 2026-03-09
|
|
4
|
+
|
|
5
|
+
Toys-core 0.20 is a major release with several new features and a number of fixes, including a few minor breaking changes.
|
|
6
|
+
|
|
7
|
+
Major changes:
|
|
8
|
+
|
|
9
|
+
* NEW: Native tab completion for zsh.
|
|
10
|
+
* NEW: The `:bundler` mixin supports "manual" bundle setup, allowing bundler decisions to be deferred to execution time
|
|
11
|
+
|
|
12
|
+
Updates to the Exec mixin and library:
|
|
13
|
+
|
|
14
|
+
* NEW: The new `Toys::Utils::Exec::Result#effective_result` method provides a reasonable integer result code even when a process terminates via signal or fails to start at all.
|
|
15
|
+
* BREAKING API CHANGE: `:cli` is no longer a legal config option.
|
|
16
|
+
* BREAKING API CHANGE: An options hash is no longer passed to the proc when executing a proc using a fork.
|
|
17
|
+
* BREAKING API CHANGE: Passing an IO object as an input or output stream no longer closes it afterward.
|
|
18
|
+
* BREAKING API CHANGE: `Toys::Utils::Exec::Controller#result` no longer preemptively (and prematurely) closes the controller input stream
|
|
19
|
+
* FIXED: The `:unsetenv_others` option now works properly when executing a proc using a fork.
|
|
20
|
+
* FIXED: Environment variable values specified as nil are now correctly unset when executing a proc using a fork.
|
|
21
|
+
* FIXED: Fixed a rare concurrency issue if multiple threads concurrently get the result from a controller.
|
|
22
|
+
|
|
23
|
+
Minor fixes for TruffleRuby compatibility:
|
|
24
|
+
|
|
25
|
+
* FIXED: The `:bundler` mixin will not attempt to add the `pathname` gem to generated Gemfiles when running on TruffleRuby. This caused issues because TruffleRuby includes a special version of the gem and cannot install the one from Rubygems.
|
|
26
|
+
* FIXED: `ContextualError` no longer overrides `Exception#cause`, which could confuse TruffleRuby.
|
|
27
|
+
|
|
3
28
|
### v0.19.1 / 2026-01-06
|
|
4
29
|
|
|
5
30
|
* DOCS: Some formatting fixes in the user guide
|
data/README.md
CHANGED
|
@@ -265,8 +265,8 @@ Navigate to the simple-gem example:
|
|
|
265
265
|
|
|
266
266
|
$ cd toys-core/examples/simple-gem
|
|
267
267
|
|
|
268
|
-
This example wraps the simple "greet" executable that we
|
|
269
|
-
|
|
268
|
+
This example wraps the simple "greet" executable that we covered earlier, in a
|
|
269
|
+
gem. You can see the
|
|
270
270
|
[executable file](https://github.com/dazuma/toys/tree/main/toys-core/examples/simple-gem/bin/toys-core-simple-example)
|
|
271
271
|
in the bin directory.
|
|
272
272
|
|
|
@@ -348,7 +348,7 @@ recommended because it has a few known bugs that affect Toys.
|
|
|
348
348
|
|
|
349
349
|
## License
|
|
350
350
|
|
|
351
|
-
Copyright 2019-
|
|
351
|
+
Copyright 2019-2026 Daniel Azuma and the Toys contributors
|
|
352
352
|
|
|
353
353
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
354
354
|
of this software and associated documentation files (the "Software"), to deal
|
data/lib/toys/cli.rb
CHANGED
|
@@ -552,7 +552,7 @@ module Toys
|
|
|
552
552
|
#
|
|
553
553
|
def default_error_handler
|
|
554
554
|
proc do |error|
|
|
555
|
-
cause = error.cause
|
|
555
|
+
cause = error.respond_to?(:underlying_error) ? error.underlying_error : error.cause
|
|
556
556
|
raise cause.is_a?(::SignalException) ? cause : error
|
|
557
557
|
end
|
|
558
558
|
end
|
data/lib/toys/compat.rb
CHANGED
|
@@ -13,35 +13,72 @@ module Toys
|
|
|
13
13
|
parts = ::RUBY_VERSION.split(".")
|
|
14
14
|
ruby_version = (parts[0].to_i * 10000) + (parts[1].to_i * 100) + parts[2].to_i
|
|
15
15
|
|
|
16
|
+
##
|
|
16
17
|
# @private
|
|
18
|
+
# An integer representation of the Ruby version, guaranteed to have the
|
|
19
|
+
# correct ordering. Currently, this is `major*10000 + minor*100 + patch`.
|
|
20
|
+
#
|
|
21
|
+
# @return [Integer]
|
|
22
|
+
#
|
|
17
23
|
RUBY_VERSION_CODE = ruby_version
|
|
18
24
|
|
|
25
|
+
##
|
|
19
26
|
# @private
|
|
27
|
+
# Whether the current Ruby implementation is JRuby
|
|
28
|
+
#
|
|
29
|
+
# @return [boolean]
|
|
30
|
+
#
|
|
20
31
|
def self.jruby?
|
|
21
32
|
::RUBY_ENGINE == "jruby"
|
|
22
33
|
end
|
|
23
34
|
|
|
35
|
+
##
|
|
24
36
|
# @private
|
|
37
|
+
# Whether the current Ruby implementation is TruffleRuby
|
|
38
|
+
#
|
|
39
|
+
# @return [boolean]
|
|
40
|
+
#
|
|
25
41
|
def self.truffleruby?
|
|
26
42
|
::RUBY_ENGINE == "truffleruby"
|
|
27
43
|
end
|
|
28
44
|
|
|
45
|
+
##
|
|
29
46
|
# @private
|
|
47
|
+
# Whether we are running on Windows
|
|
48
|
+
#
|
|
49
|
+
# @return [boolean]
|
|
50
|
+
#
|
|
30
51
|
def self.windows?
|
|
31
52
|
::RbConfig::CONFIG["host_os"] =~ /mswin|msys|mingw|cygwin|bccwin|wince|emc/
|
|
32
53
|
end
|
|
33
54
|
|
|
55
|
+
##
|
|
34
56
|
# @private
|
|
57
|
+
# Whether we are running on Mac OS
|
|
58
|
+
#
|
|
59
|
+
# @return [boolean]
|
|
60
|
+
#
|
|
35
61
|
def self.macos?
|
|
36
62
|
::RbConfig::CONFIG["host_os"] =~ /darwin/
|
|
37
63
|
end
|
|
38
64
|
|
|
65
|
+
##
|
|
39
66
|
# @private
|
|
67
|
+
# Whether fork is supported on the current Ruby and OS
|
|
68
|
+
#
|
|
69
|
+
# @return [boolean]
|
|
70
|
+
#
|
|
40
71
|
def self.allow_fork?
|
|
41
72
|
!jruby? && !truffleruby? && !windows?
|
|
42
73
|
end
|
|
43
74
|
|
|
75
|
+
##
|
|
44
76
|
# @private
|
|
77
|
+
# Whether it is possible to get suggestions from DidYouMean. If this
|
|
78
|
+
# returns false, {Compat.suggestions} will always return the empty array.
|
|
79
|
+
#
|
|
80
|
+
# @return [boolean]
|
|
81
|
+
#
|
|
45
82
|
def self.supports_suggestions?
|
|
46
83
|
unless defined?(@supports_suggestions)
|
|
47
84
|
begin
|
|
@@ -59,7 +96,16 @@ module Toys
|
|
|
59
96
|
@supports_suggestions
|
|
60
97
|
end
|
|
61
98
|
|
|
99
|
+
##
|
|
62
100
|
# @private
|
|
101
|
+
# A list of suggestions from DidYouMean.
|
|
102
|
+
#
|
|
103
|
+
# @param word [String] A value that seems wrong
|
|
104
|
+
# @param list [Array<String>] A list of valid values
|
|
105
|
+
#
|
|
106
|
+
# @return [Array<String>] A possibly empty array of suggestions from the
|
|
107
|
+
# valid list that could match the given word.
|
|
108
|
+
#
|
|
63
109
|
def self.suggestions(word, list)
|
|
64
110
|
if supports_suggestions?
|
|
65
111
|
::DidYouMean::SpellChecker.new(dictionary: list).correct(word)
|
|
@@ -67,5 +113,22 @@ module Toys
|
|
|
67
113
|
[]
|
|
68
114
|
end
|
|
69
115
|
end
|
|
116
|
+
|
|
117
|
+
##
|
|
118
|
+
# @private
|
|
119
|
+
# A list of gems that should generally not be included in a bundle, usually
|
|
120
|
+
# because the Ruby implementation handles the library specially and cannot
|
|
121
|
+
# install the real gem. Currently, this includes the `pathname` gem for
|
|
122
|
+
# TruffleRuby, since TruffleRuby includes a special version of it.
|
|
123
|
+
#
|
|
124
|
+
# @return [Array<String>]
|
|
125
|
+
#
|
|
126
|
+
def self.gems_to_omit_from_bundles
|
|
127
|
+
if truffleruby?
|
|
128
|
+
["pathname"]
|
|
129
|
+
else
|
|
130
|
+
[]
|
|
131
|
+
end
|
|
132
|
+
end
|
|
70
133
|
end
|
|
71
134
|
end
|
data/lib/toys/core.rb
CHANGED
data/lib/toys/errors.rb
CHANGED
|
@@ -51,24 +51,25 @@ module Toys
|
|
|
51
51
|
#
|
|
52
52
|
# @private This interface is internal and subject to change without warning.
|
|
53
53
|
#
|
|
54
|
-
def initialize(
|
|
54
|
+
def initialize(underlying_error, banner,
|
|
55
55
|
config_path: nil, config_line: nil,
|
|
56
56
|
tool_name: nil, tool_args: nil)
|
|
57
|
-
super("#{banner} : #{
|
|
58
|
-
@
|
|
57
|
+
super("#{banner} : #{underlying_error.message} (#{underlying_error.class})")
|
|
58
|
+
@underlying_error = underlying_error
|
|
59
59
|
@banner = banner
|
|
60
60
|
@config_path = config_path
|
|
61
61
|
@config_line = config_line
|
|
62
62
|
@tool_name = tool_name
|
|
63
63
|
@tool_args = tool_args
|
|
64
|
-
set_backtrace(
|
|
64
|
+
set_backtrace(underlying_error.backtrace)
|
|
65
65
|
end
|
|
66
66
|
|
|
67
67
|
##
|
|
68
|
-
# The underlying exception
|
|
68
|
+
# The underlying exception.
|
|
69
|
+
# Generally the same as `Exception#cause`.
|
|
69
70
|
# @return [::StandardError]
|
|
70
71
|
#
|
|
71
|
-
attr_reader :
|
|
72
|
+
attr_reader :underlying_error
|
|
72
73
|
|
|
73
74
|
##
|
|
74
75
|
# An overall banner message
|
|
@@ -141,10 +142,10 @@ module Toys
|
|
|
141
142
|
end
|
|
142
143
|
raise e
|
|
143
144
|
rescue ::ScriptError, ::StandardError, ::SignalException => e
|
|
144
|
-
|
|
145
|
-
add_fields_if_missing(
|
|
146
|
-
add_config_path_if_missing(
|
|
147
|
-
raise
|
|
145
|
+
ce = ContextualError.new(e, banner)
|
|
146
|
+
add_fields_if_missing(ce, opts)
|
|
147
|
+
add_config_path_if_missing(ce, path)
|
|
148
|
+
raise ce
|
|
148
149
|
end
|
|
149
150
|
|
|
150
151
|
##
|
|
@@ -172,7 +173,7 @@ module Toys
|
|
|
172
173
|
|
|
173
174
|
def add_config_path_if_missing(error, path)
|
|
174
175
|
if error.config_path.nil? && error.config_line.nil?
|
|
175
|
-
l = (error.
|
|
176
|
+
l = (error.underlying_error.backtrace_locations || []).find do |b|
|
|
176
177
|
b.absolute_path == path || b.path == path
|
|
177
178
|
end
|
|
178
179
|
if l
|
|
@@ -22,9 +22,21 @@ module Toys
|
|
|
22
22
|
#
|
|
23
23
|
# The following parameters can be passed when including this mixin:
|
|
24
24
|
#
|
|
25
|
-
# * `:static` (Boolean)
|
|
26
|
-
#
|
|
27
|
-
#
|
|
25
|
+
# * `:static` (Boolean) Has the same effect as passing `:static` to the
|
|
26
|
+
# `:setup` parameter.
|
|
27
|
+
#
|
|
28
|
+
# * `:setup` (:auto,:manual,:static) A symbol indicating when the bundle
|
|
29
|
+
# should be installed. Possible values are:
|
|
30
|
+
#
|
|
31
|
+
# * `:auto` - (Default) Installs the bundle just before the tool runs.
|
|
32
|
+
# * `:static` - Installs the bundle immediately when defining the
|
|
33
|
+
# tool.
|
|
34
|
+
# * `:manual` - Does not install the bundle, but defines the methods
|
|
35
|
+
# `bundler_setup` and `bundler_setup?` in the tool. The tool can
|
|
36
|
+
# call `bundler_setup` to install the bundle, optionally passing
|
|
37
|
+
# any of the remaining keyword arguments below to override the
|
|
38
|
+
# corresponding mixin parameters. The `bundler_setup?` method can
|
|
39
|
+
# be queried to determine whether the bundle has been set up yet.
|
|
28
40
|
#
|
|
29
41
|
# * `:groups` (Array\<String\>) The groups to include in setup.
|
|
30
42
|
#
|
|
@@ -76,7 +88,9 @@ module Toys
|
|
|
76
88
|
# (optional)
|
|
77
89
|
#
|
|
78
90
|
# * `:terminal` (Toys::Utils::Terminal) Terminal to use (optional)
|
|
91
|
+
#
|
|
79
92
|
# * `:input` (IO) Input IO (optional, defaults to STDIN)
|
|
93
|
+
#
|
|
80
94
|
# * `:output` (IO) Output IO (optional, defaults to STDOUT)
|
|
81
95
|
#
|
|
82
96
|
module Bundler
|
|
@@ -94,6 +108,14 @@ module Toys
|
|
|
94
108
|
#
|
|
95
109
|
DEFAULT_TOYS_GEMFILE_NAMES = [".gems.rb", "Gemfile"].freeze
|
|
96
110
|
|
|
111
|
+
##
|
|
112
|
+
# @private
|
|
113
|
+
# Context key for the mixin parameters when using manual setup. The value
|
|
114
|
+
# will be a hash of parameters if the bundle has not been set up yet, or
|
|
115
|
+
# nil if the bundle has already been set up.
|
|
116
|
+
#
|
|
117
|
+
SETUP_PARAMS_KEY = Object.new.freeze
|
|
118
|
+
|
|
97
119
|
##
|
|
98
120
|
# @private
|
|
99
121
|
#
|
|
@@ -121,17 +143,43 @@ module Toys
|
|
|
121
143
|
gems.bundle(groups: groups, gemfile_path: gemfile_path, retries: retries)
|
|
122
144
|
end
|
|
123
145
|
|
|
124
|
-
on_initialize do |static: false, **kwargs|
|
|
125
|
-
|
|
146
|
+
on_initialize do |static: false, setup: nil, **kwargs|
|
|
147
|
+
setup ||= (static ? :static : :auto)
|
|
148
|
+
case setup
|
|
149
|
+
when :auto
|
|
126
150
|
context_directory = self[::Toys::Context::Key::CONTEXT_DIRECTORY]
|
|
127
151
|
source_info = self[::Toys::Context::Key::TOOL_SOURCE]
|
|
128
152
|
::Toys::StandardMixins::Bundler.setup_bundle(context_directory, source_info, **kwargs)
|
|
153
|
+
when :manual
|
|
154
|
+
self[::Toys::StandardMixins::Bundler::SETUP_PARAMS_KEY] = kwargs
|
|
129
155
|
end
|
|
130
156
|
end
|
|
131
157
|
|
|
132
|
-
on_include do |static: false, **kwargs|
|
|
133
|
-
|
|
158
|
+
on_include do |static: false, setup: nil, **kwargs|
|
|
159
|
+
setup ||= (static ? :static : :auto)
|
|
160
|
+
case setup
|
|
161
|
+
when :static
|
|
134
162
|
::Toys::StandardMixins::Bundler.setup_bundle(context_directory, source_info, **kwargs)
|
|
163
|
+
when :manual
|
|
164
|
+
# @private
|
|
165
|
+
def bundler_setup(**kwargs)
|
|
166
|
+
original_kwargs = self[::Toys::StandardMixins::Bundler::SETUP_PARAMS_KEY]
|
|
167
|
+
raise ::Toys::Utils::Gems::AlreadyBundledError unless original_kwargs
|
|
168
|
+
context_directory = self[::Toys::Context::Key::CONTEXT_DIRECTORY]
|
|
169
|
+
source_info = self[::Toys::Context::Key::TOOL_SOURCE]
|
|
170
|
+
final_kwargs = original_kwargs.merge(kwargs)
|
|
171
|
+
::Toys::StandardMixins::Bundler.setup_bundle(context_directory, source_info, **final_kwargs)
|
|
172
|
+
self[::Toys::StandardMixins::Bundler::SETUP_PARAMS_KEY] = nil
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
# @private
|
|
176
|
+
def bundler_setup?
|
|
177
|
+
self[::Toys::StandardMixins::Bundler::SETUP_PARAMS_KEY].nil?
|
|
178
|
+
end
|
|
179
|
+
when :auto
|
|
180
|
+
# Do nothing at this point
|
|
181
|
+
else
|
|
182
|
+
raise ::ArgumentError, "Unrecognized setup type: #{setup.inspect}"
|
|
135
183
|
end
|
|
136
184
|
end
|
|
137
185
|
|
|
@@ -165,7 +213,7 @@ module Toys
|
|
|
165
213
|
when :toys
|
|
166
214
|
search_toys
|
|
167
215
|
else
|
|
168
|
-
raise ::ArgumentError, "Unrecognized search_dir: #{
|
|
216
|
+
raise ::ArgumentError, "Unrecognized search_dir: #{search_dir.inspect}"
|
|
169
217
|
end
|
|
170
218
|
end
|
|
171
219
|
|
|
@@ -109,7 +109,8 @@ module Toys
|
|
|
109
109
|
# an existing File stream. Unlike `Process#spawn`, this works for IO
|
|
110
110
|
# objects that do not have a corresponding file descriptor (such as
|
|
111
111
|
# StringIO objects). In such a case, a thread will be spawned to pipe
|
|
112
|
-
# the IO data through to the child process.
|
|
112
|
+
# the IO data through to the child process. Note that the IO object
|
|
113
|
+
# will _not_ be closed on completion.
|
|
113
114
|
#
|
|
114
115
|
# * **Redirect to a pipe:** You can redirect to a pipe created using
|
|
115
116
|
# `IO.pipe` (i.e. a two-element array of read and write IO objects) by
|
|
@@ -434,7 +435,7 @@ module Toys
|
|
|
434
435
|
# the foreground.
|
|
435
436
|
#
|
|
436
437
|
def exec_tool(cmd, **opts, &block)
|
|
437
|
-
func = Exec._make_tool_caller(cmd)
|
|
438
|
+
func = Exec._make_tool_caller(cmd, self[Context::Key::CLI])
|
|
438
439
|
opts = Exec._setup_exec_opts(opts, self)
|
|
439
440
|
opts = {log_cmd: "exec tool: #{cmd.inspect}"}.merge(opts)
|
|
440
441
|
self[KEY].exec_proc(func, **opts, &block)
|
|
@@ -621,7 +622,7 @@ module Toys
|
|
|
621
622
|
# @return [String] What was written to standard out.
|
|
622
623
|
#
|
|
623
624
|
def capture_tool(cmd, **opts, &block)
|
|
624
|
-
func = Exec._make_tool_caller(cmd)
|
|
625
|
+
func = Exec._make_tool_caller(cmd, self[Context::Key::CLI])
|
|
625
626
|
opts = Exec._setup_exec_opts(opts, self)
|
|
626
627
|
self[KEY].capture_proc(func, **opts, &block)
|
|
627
628
|
end
|
|
@@ -750,9 +751,9 @@ module Toys
|
|
|
750
751
|
##
|
|
751
752
|
# @private
|
|
752
753
|
#
|
|
753
|
-
def self._make_tool_caller(cmd)
|
|
754
|
+
def self._make_tool_caller(cmd, cli)
|
|
754
755
|
cmd = ::Shellwords.split(cmd) if cmd.is_a?(::String)
|
|
755
|
-
proc {
|
|
756
|
+
proc { ::Kernel.exit(cli.run(*cmd)) }
|
|
756
757
|
end
|
|
757
758
|
|
|
758
759
|
##
|
|
@@ -10,24 +10,27 @@ module Toys
|
|
|
10
10
|
#
|
|
11
11
|
module CompletionEngine
|
|
12
12
|
##
|
|
13
|
-
#
|
|
13
|
+
# Base class for shell completion engines that use a
|
|
14
|
+
# `COMP_LINE` / `COMP_POINT` protocol.
|
|
15
|
+
#
|
|
16
|
+
# Subclasses must implement the private methods `#shell_name` and
|
|
17
|
+
# `#output_completions`.
|
|
14
18
|
#
|
|
15
|
-
class
|
|
19
|
+
class Base
|
|
16
20
|
##
|
|
17
|
-
# Create a
|
|
21
|
+
# Create a completion engine.
|
|
18
22
|
#
|
|
19
23
|
# @param cli [Toys::CLI] The CLI.
|
|
20
24
|
#
|
|
21
25
|
def initialize(cli)
|
|
22
|
-
require "shellwords"
|
|
23
26
|
@cli = cli
|
|
24
27
|
end
|
|
25
28
|
|
|
26
29
|
##
|
|
27
30
|
# Perform completion in the current shell environment, which must
|
|
28
31
|
# include settings for the `COMP_LINE` and `COMP_POINT` environment
|
|
29
|
-
# variables. Prints out completion candidates
|
|
30
|
-
#
|
|
32
|
+
# variables. Prints out completion candidates and returns a status code
|
|
33
|
+
# indicating the result.
|
|
31
34
|
#
|
|
32
35
|
# * **0** for success.
|
|
33
36
|
# * **1** if completion failed.
|
|
@@ -42,9 +45,9 @@ module Toys
|
|
|
42
45
|
point = ::ENV["COMP_POINT"].to_i
|
|
43
46
|
point = line.length if point.negative?
|
|
44
47
|
line = line[0, point]
|
|
45
|
-
|
|
46
|
-
if
|
|
47
|
-
|
|
48
|
+
result = run_internal(line)
|
|
49
|
+
if result
|
|
50
|
+
output_completions(*result)
|
|
48
51
|
0
|
|
49
52
|
else
|
|
50
53
|
1
|
|
@@ -53,6 +56,8 @@ module Toys
|
|
|
53
56
|
|
|
54
57
|
##
|
|
55
58
|
# Internal completion method designed for testing.
|
|
59
|
+
# Returns `[quote_type, Array<Toys::Completion::Candidate>]`, or `nil`
|
|
60
|
+
# if the line cannot be parsed (e.g. the executable name is missing).
|
|
56
61
|
#
|
|
57
62
|
# @private
|
|
58
63
|
#
|
|
@@ -68,39 +73,39 @@ module Toys
|
|
|
68
73
|
end
|
|
69
74
|
context = Completion::Context.new(
|
|
70
75
|
cli: @cli, previous_words: words, fragment_prefix: prefix, fragment: last,
|
|
71
|
-
params: {shell:
|
|
76
|
+
params: { shell: shell_name, quote_type: quote_type }
|
|
72
77
|
)
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
78
|
+
[quote_type, @cli.completion.call(context).uniq.sort]
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
private
|
|
82
|
+
|
|
83
|
+
def shell_name
|
|
84
|
+
raise ::NotImplementedError
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def output_completions(_quote_type, _candidates)
|
|
88
|
+
raise ::NotImplementedError
|
|
77
89
|
end
|
|
78
90
|
end
|
|
79
91
|
|
|
80
|
-
|
|
92
|
+
##
|
|
93
|
+
# A completion engine for bash.
|
|
94
|
+
#
|
|
95
|
+
class Bash < Base
|
|
81
96
|
##
|
|
82
|
-
#
|
|
97
|
+
# Create a bash completion engine.
|
|
83
98
|
#
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
raise ArgumentError, "Didn't expect garbage: #{line.inspect}" if garbage
|
|
90
|
-
field << field_str(word, sqw, dqw, esc)
|
|
91
|
-
quote_type = update_quote_type(quote_type, sqw, dqw)
|
|
92
|
-
if sep
|
|
93
|
-
words << [quote_type, field]
|
|
94
|
-
quote_type = nil
|
|
95
|
-
field = sep.empty? ? nil : ::String.new
|
|
96
|
-
end
|
|
97
|
-
end
|
|
98
|
-
words << [quote_type, field] if field
|
|
99
|
-
words
|
|
99
|
+
# @param cli [Toys::CLI] The CLI.
|
|
100
|
+
#
|
|
101
|
+
def initialize(cli)
|
|
102
|
+
require "shellwords"
|
|
103
|
+
super
|
|
100
104
|
end
|
|
101
105
|
|
|
102
106
|
##
|
|
103
107
|
# @private
|
|
108
|
+
# Accessible only for testing
|
|
104
109
|
#
|
|
105
110
|
def format_candidate(candidate, quote_type)
|
|
106
111
|
str = candidate.to_s
|
|
@@ -120,6 +125,57 @@ module Toys
|
|
|
120
125
|
|
|
121
126
|
private
|
|
122
127
|
|
|
128
|
+
def shell_name
|
|
129
|
+
:bash
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
def output_completions(quote_type, candidates)
|
|
133
|
+
candidates.each { |c| puts format_candidate(c, quote_type) }
|
|
134
|
+
end
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
##
|
|
138
|
+
# A completion engine for zsh.
|
|
139
|
+
#
|
|
140
|
+
class Zsh < Base
|
|
141
|
+
private
|
|
142
|
+
|
|
143
|
+
def shell_name
|
|
144
|
+
:zsh
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
def output_completions(_quote_type, candidates)
|
|
148
|
+
finals, partials = candidates.partition(&:final?)
|
|
149
|
+
finals.each { |c| puts c.string unless c.string.empty? }
|
|
150
|
+
puts ""
|
|
151
|
+
partials.each { |c| puts c.string unless c.string.empty? }
|
|
152
|
+
end
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
class << self
|
|
156
|
+
##
|
|
157
|
+
# @private
|
|
158
|
+
#
|
|
159
|
+
def split(line)
|
|
160
|
+
words = []
|
|
161
|
+
field = ::String.new
|
|
162
|
+
quote_type = nil
|
|
163
|
+
line.scan(split_regex) do |word, sqw, dqw, esc, garbage, sep|
|
|
164
|
+
raise ArgumentError, "Didn't expect garbage: #{line.inspect}" if garbage
|
|
165
|
+
field << field_str(word, sqw, dqw, esc)
|
|
166
|
+
quote_type = update_quote_type(quote_type, sqw, dqw)
|
|
167
|
+
if sep
|
|
168
|
+
words << [quote_type, field]
|
|
169
|
+
quote_type = nil
|
|
170
|
+
field = sep.empty? ? nil : ::String.new
|
|
171
|
+
end
|
|
172
|
+
end
|
|
173
|
+
words << [quote_type, field] if field
|
|
174
|
+
words
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
private
|
|
178
|
+
|
|
123
179
|
def split_regex
|
|
124
180
|
word_re = "([^\\s\\\\\\'\\\"]+)"
|
|
125
181
|
sq_re = "'([^\\']*)(?:'|\\z)"
|
data/lib/toys/utils/exec.rb
CHANGED
|
@@ -109,7 +109,8 @@ module Toys
|
|
|
109
109
|
# an existing File stream. Unlike `Process#spawn`, this works for IO
|
|
110
110
|
# objects that do not have a corresponding file descriptor (such as
|
|
111
111
|
# StringIO objects). In such a case, a thread will be spawned to pipe
|
|
112
|
-
# the IO data through to the child process.
|
|
112
|
+
# the IO data through to the child process. Note that the IO object
|
|
113
|
+
# will _not_ be closed on completion.
|
|
113
114
|
#
|
|
114
115
|
# * **Redirect to a pipe:** You can redirect to a pipe created using
|
|
115
116
|
# `IO.pipe` (i.e. a two-element array of read and write IO objects) by
|
|
@@ -159,7 +160,7 @@ module Toys
|
|
|
159
160
|
#
|
|
160
161
|
# A subprocess result is represented by a {Toys::Utils::Exec::Result}
|
|
161
162
|
# object, which includes the exit code, the content of any captured output
|
|
162
|
-
# streams, and any
|
|
163
|
+
# streams, and any exception raised when attempting to run the process.
|
|
163
164
|
# When you run a process in the foreground, the method will return a result
|
|
164
165
|
# object. When you run a process in the background, you can obtain the
|
|
165
166
|
# result from the controller once the process completes.
|
|
@@ -192,7 +193,7 @@ module Toys
|
|
|
192
193
|
# ### Configuration options
|
|
193
194
|
#
|
|
194
195
|
# A variety of options can be used to control subprocesses. These can be
|
|
195
|
-
# provided to any method that starts a subprocess.
|
|
196
|
+
# provided to any method that starts a subprocess. You can also set
|
|
196
197
|
# defaults by calling {Toys::Utils::Exec#configure_defaults}.
|
|
197
198
|
#
|
|
198
199
|
# Options that affect the behavior of subprocesses:
|
|
@@ -432,8 +433,8 @@ module Toys
|
|
|
432
433
|
end
|
|
433
434
|
|
|
434
435
|
##
|
|
435
|
-
# Execute the given string in a shell. Returns
|
|
436
|
-
# Cannot be run in the background.
|
|
436
|
+
# Execute the given string in a shell. Returns an effective exit code
|
|
437
|
+
# that is always an integer. Cannot be run in the background.
|
|
437
438
|
#
|
|
438
439
|
# If a block is provided, a {Toys::Utils::Exec::Controller} will be
|
|
439
440
|
# yielded to it.
|
|
@@ -444,11 +445,12 @@ module Toys
|
|
|
444
445
|
# @yieldparam controller [Toys::Utils::Exec::Controller] A controller
|
|
445
446
|
# for the subprocess streams.
|
|
446
447
|
#
|
|
447
|
-
# @return [Integer]
|
|
448
|
+
# @return [Integer] An effective exit code. See
|
|
449
|
+
# {Toys::Utils::Exec::Result#effective_code}.
|
|
448
450
|
#
|
|
449
451
|
def sh(cmd, **opts, &block)
|
|
450
452
|
opts = opts.merge(background: false)
|
|
451
|
-
exec(cmd, **opts, &block).
|
|
453
|
+
exec(cmd, **opts, &block).effective_code
|
|
452
454
|
end
|
|
453
455
|
|
|
454
456
|
##
|
|
@@ -523,7 +525,7 @@ module Toys
|
|
|
523
525
|
stream = stream_for(which)
|
|
524
526
|
@join_threads << ::Thread.new do
|
|
525
527
|
data = stream.read
|
|
526
|
-
@
|
|
528
|
+
@captures_mutex.synchronize do
|
|
527
529
|
@captures[which] = data
|
|
528
530
|
end
|
|
529
531
|
ensure
|
|
@@ -560,6 +562,10 @@ module Toys
|
|
|
560
562
|
# provide the mode and permissions for the call to `File#open`. You can
|
|
561
563
|
# also specify the value `:null` to indicate the null file.
|
|
562
564
|
#
|
|
565
|
+
# If the stream is redirected to an IO-like object, it is _not_ closed
|
|
566
|
+
# when the process is completed. (If it is redirected to a file
|
|
567
|
+
# specified by path, the file is closed on completion.)
|
|
568
|
+
#
|
|
563
569
|
# After calling this, do not interact directly with the stream.
|
|
564
570
|
#
|
|
565
571
|
# @param which [:in,:out,:err] Which stream to redirect
|
|
@@ -570,9 +576,11 @@ module Toys
|
|
|
570
576
|
#
|
|
571
577
|
def redirect(which, io, *io_args)
|
|
572
578
|
io = ::File::NULL if io == :null
|
|
579
|
+
close_afterward = false
|
|
573
580
|
if io.is_a?(::String)
|
|
574
581
|
io_args = which == :in ? ["r"] : ["w"] if io_args.empty?
|
|
575
582
|
io = ::File.open(io, *io_args)
|
|
583
|
+
close_afterward = true
|
|
576
584
|
end
|
|
577
585
|
stream = stream_for(which, allow_in: true)
|
|
578
586
|
@join_threads << ::Thread.new do
|
|
@@ -583,7 +591,7 @@ module Toys
|
|
|
583
591
|
end
|
|
584
592
|
ensure
|
|
585
593
|
stream.close
|
|
586
|
-
io.close
|
|
594
|
+
io.close if close_afterward
|
|
587
595
|
end
|
|
588
596
|
self
|
|
589
597
|
end
|
|
@@ -669,47 +677,48 @@ module Toys
|
|
|
669
677
|
##
|
|
670
678
|
# Wait for the subcommand to complete, and return a result object.
|
|
671
679
|
#
|
|
672
|
-
# Closes the control streams if present. The stdin stream is always
|
|
673
|
-
# closed, even if the call times out. The stdout and stderr streams are
|
|
674
|
-
# closed only after the command terminates.
|
|
675
|
-
#
|
|
676
680
|
# @param timeout [Numeric,nil] The timeout in seconds, or `nil` to
|
|
677
681
|
# wait indefinitely.
|
|
678
682
|
# @return [Toys::Utils::Exec::Result] The result object
|
|
679
683
|
# @return [nil] if a timeout occurred.
|
|
680
684
|
#
|
|
681
685
|
def result(timeout: nil)
|
|
682
|
-
close_streams(:in)
|
|
683
686
|
return nil if @wait_thread && !@wait_thread.join(timeout)
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
@
|
|
687
|
-
|
|
688
|
-
|
|
687
|
+
should_run_callback = false
|
|
688
|
+
@result_mutex.synchronize do
|
|
689
|
+
@result ||= begin
|
|
690
|
+
should_run_callback = true
|
|
691
|
+
close_streams(:both)
|
|
692
|
+
@join_threads.each(&:join)
|
|
693
|
+
Result.new(name, @captures[:out], @captures[:err], @wait_thread&.value, @exception)
|
|
694
|
+
end
|
|
689
695
|
end
|
|
696
|
+
@result_callback&.call(@result) if should_run_callback
|
|
697
|
+
@result
|
|
690
698
|
end
|
|
691
699
|
|
|
692
700
|
##
|
|
693
701
|
# @private
|
|
694
702
|
#
|
|
695
|
-
def initialize(name
|
|
696
|
-
result_callback
|
|
703
|
+
def initialize(name:, controller_streams:, captures:, pid_or_exception:,
|
|
704
|
+
join_threads:, result_callback:, captures_mutex:)
|
|
697
705
|
@name = name
|
|
698
706
|
@in = controller_streams[:in]
|
|
699
707
|
@out = controller_streams[:out]
|
|
700
708
|
@err = controller_streams[:err]
|
|
701
709
|
@captures = captures
|
|
702
710
|
@pid = @exception = @wait_thread = nil
|
|
703
|
-
case
|
|
711
|
+
case pid_or_exception
|
|
704
712
|
when ::Integer
|
|
705
|
-
@pid =
|
|
706
|
-
@wait_thread = ::Process.detach(pid)
|
|
713
|
+
@pid = pid_or_exception
|
|
714
|
+
@wait_thread = ::Process.detach(@pid)
|
|
707
715
|
when ::Exception
|
|
708
|
-
@exception =
|
|
716
|
+
@exception = pid_or_exception
|
|
709
717
|
end
|
|
710
718
|
@join_threads = join_threads
|
|
711
719
|
@result_callback = result_callback
|
|
712
|
-
@
|
|
720
|
+
@captures_mutex = captures_mutex
|
|
721
|
+
@result_mutex = ::Mutex.new
|
|
713
722
|
@result = nil
|
|
714
723
|
end
|
|
715
724
|
|
|
@@ -797,7 +806,7 @@ module Toys
|
|
|
797
806
|
# Exactly one of {#exception} and {#status} will be non-nil.
|
|
798
807
|
#
|
|
799
808
|
# @return [Process::Status] The status, if the process was successfully
|
|
800
|
-
#
|
|
809
|
+
# spawned and terminated.
|
|
801
810
|
# @return [nil] if the process could not be started.
|
|
802
811
|
#
|
|
803
812
|
attr_reader :status
|
|
@@ -888,6 +897,42 @@ module Toys
|
|
|
888
897
|
!code.nil? && !code.zero?
|
|
889
898
|
end
|
|
890
899
|
|
|
900
|
+
##
|
|
901
|
+
# Returns an "effective" exit code, which is always an integer if the
|
|
902
|
+
# process has terminated for any reason. In general, this code will be:
|
|
903
|
+
#
|
|
904
|
+
# * The same as {#exit_code} if the process terminated normally with an
|
|
905
|
+
# exit code,
|
|
906
|
+
# * The convention of `128+signalnum` if the process terminated due to
|
|
907
|
+
# a signal,
|
|
908
|
+
# * The convention of 126 if the process could not start due to lack of
|
|
909
|
+
# execution permissions,
|
|
910
|
+
# * The convention of 127 if the process could not start because the
|
|
911
|
+
# command was not recognized or could not be found, or
|
|
912
|
+
# * An undefined value between 1 and 255 for other failures.
|
|
913
|
+
#
|
|
914
|
+
# Note that the normal exit code and signal number cases are stable,
|
|
915
|
+
# but any other cases are subject to change on future releases.
|
|
916
|
+
#
|
|
917
|
+
# @return [Integer]
|
|
918
|
+
#
|
|
919
|
+
def effective_code
|
|
920
|
+
code = exit_code
|
|
921
|
+
return code unless code.nil?
|
|
922
|
+
code = signal_code
|
|
923
|
+
return code + 128 unless code.nil?
|
|
924
|
+
case exception
|
|
925
|
+
when ::Errno::ENOENT
|
|
926
|
+
127
|
|
927
|
+
else
|
|
928
|
+
# This is the intended result for ENOEXEC/EACCES.
|
|
929
|
+
# For now, any other error (e.g. EBADARCH on MacOS) will also map
|
|
930
|
+
# to this result. We can change this in the future since the
|
|
931
|
+
# documentation explicitly allows it.
|
|
932
|
+
126
|
|
933
|
+
end
|
|
934
|
+
end
|
|
935
|
+
|
|
891
936
|
##
|
|
892
937
|
# @private
|
|
893
938
|
#
|
|
@@ -916,7 +961,6 @@ module Toys
|
|
|
916
961
|
CONFIG_KEYS = [
|
|
917
962
|
:argv0,
|
|
918
963
|
:background,
|
|
919
|
-
:cli,
|
|
920
964
|
:env,
|
|
921
965
|
:err,
|
|
922
966
|
:in,
|
|
@@ -1012,6 +1056,10 @@ module Toys
|
|
|
1012
1056
|
#
|
|
1013
1057
|
def initialize(exec_opts, spawn_cmd, block)
|
|
1014
1058
|
@fork_func = spawn_cmd.respond_to?(:call) ? spawn_cmd : nil
|
|
1059
|
+
if @fork_func && !::Process.respond_to?(:fork)
|
|
1060
|
+
raise ::NotImplementedError,
|
|
1061
|
+
"Executing a proc is not available because fork is not supported on the current Ruby platform"
|
|
1062
|
+
end
|
|
1015
1063
|
@spawn_cmd = spawn_cmd.respond_to?(:call) ? nil : spawn_cmd
|
|
1016
1064
|
@config_opts = exec_opts.config_opts
|
|
1017
1065
|
@spawn_opts = exec_opts.spawn_opts
|
|
@@ -1022,7 +1070,7 @@ module Toys
|
|
|
1022
1070
|
@parent_streams = []
|
|
1023
1071
|
@block = block
|
|
1024
1072
|
@default_stream = @config_opts[:background] ? :null : :inherit
|
|
1025
|
-
@
|
|
1073
|
+
@captures_mutex = ::Mutex.new
|
|
1026
1074
|
end
|
|
1027
1075
|
|
|
1028
1076
|
##
|
|
@@ -1037,6 +1085,7 @@ module Toys
|
|
|
1037
1085
|
return controller if @config_opts[:background]
|
|
1038
1086
|
begin
|
|
1039
1087
|
@block&.call(controller)
|
|
1088
|
+
controller.close_streams(:in)
|
|
1040
1089
|
controller.result
|
|
1041
1090
|
ensure
|
|
1042
1091
|
controller.close_streams(:both)
|
|
@@ -1068,15 +1117,20 @@ module Toys
|
|
|
1068
1117
|
end
|
|
1069
1118
|
|
|
1070
1119
|
def start_with_controller
|
|
1071
|
-
|
|
1120
|
+
pid_or_exception =
|
|
1072
1121
|
begin
|
|
1073
1122
|
@fork_func ? start_fork : start_process
|
|
1074
1123
|
rescue ::StandardError => e
|
|
1075
1124
|
e
|
|
1076
1125
|
end
|
|
1077
1126
|
@child_streams.each(&:close)
|
|
1078
|
-
Controller.new(@config_opts[:name],
|
|
1079
|
-
|
|
1127
|
+
Controller.new(name: @config_opts[:name],
|
|
1128
|
+
controller_streams: @controller_streams,
|
|
1129
|
+
captures: @captures,
|
|
1130
|
+
pid_or_exception: pid_or_exception,
|
|
1131
|
+
join_threads: @join_threads,
|
|
1132
|
+
result_callback: @config_opts[:result_callback],
|
|
1133
|
+
captures_mutex: @captures_mutex)
|
|
1080
1134
|
end
|
|
1081
1135
|
|
|
1082
1136
|
def start_process
|
|
@@ -1106,21 +1160,28 @@ module Toys
|
|
|
1106
1160
|
def run_fork_func
|
|
1107
1161
|
catch(:result) do
|
|
1108
1162
|
if @spawn_opts[:chdir]
|
|
1109
|
-
::Dir.chdir(@spawn_opts[:chdir]) { @fork_func.call
|
|
1163
|
+
::Dir.chdir(@spawn_opts[:chdir]) { @fork_func.call }
|
|
1110
1164
|
else
|
|
1111
|
-
@fork_func.call
|
|
1165
|
+
@fork_func.call
|
|
1112
1166
|
end
|
|
1113
1167
|
0
|
|
1114
1168
|
end
|
|
1115
1169
|
end
|
|
1116
1170
|
|
|
1117
1171
|
def setup_env_within_fork
|
|
1118
|
-
|
|
1172
|
+
env = @config_opts[:env] || {}
|
|
1173
|
+
if @spawn_opts[:unsetenv_others]
|
|
1119
1174
|
::ENV.each_key do |k|
|
|
1120
|
-
::ENV.delete(k) unless
|
|
1175
|
+
::ENV.delete(k) unless env.key?(k)
|
|
1176
|
+
end
|
|
1177
|
+
end
|
|
1178
|
+
env.each do |k, v|
|
|
1179
|
+
if v.nil?
|
|
1180
|
+
::ENV.delete(k.to_s)
|
|
1181
|
+
else
|
|
1182
|
+
::ENV[k.to_s] = v.to_s
|
|
1121
1183
|
end
|
|
1122
1184
|
end
|
|
1123
|
-
(@config_opts[:env] || {}).each { |k, v| ::ENV[k.to_s] = v.to_s }
|
|
1124
1185
|
end
|
|
1125
1186
|
|
|
1126
1187
|
def setup_streams_within_fork
|
|
@@ -1436,7 +1497,7 @@ module Toys
|
|
|
1436
1497
|
when :close
|
|
1437
1498
|
io.close rescue nil # rubocop:disable Style/RescueModifier
|
|
1438
1499
|
when :capture
|
|
1439
|
-
@
|
|
1500
|
+
@captures_mutex.synchronize do
|
|
1440
1501
|
@captures[key] = io.string
|
|
1441
1502
|
end
|
|
1442
1503
|
end
|
|
@@ -1507,7 +1568,6 @@ module Toys
|
|
|
1507
1568
|
::IO.copy_stream(io, stream)
|
|
1508
1569
|
ensure
|
|
1509
1570
|
stream.close
|
|
1510
|
-
io.close
|
|
1511
1571
|
end
|
|
1512
1572
|
end
|
|
1513
1573
|
|
|
@@ -1517,7 +1577,6 @@ module Toys
|
|
|
1517
1577
|
::IO.copy_stream(stream, io)
|
|
1518
1578
|
ensure
|
|
1519
1579
|
stream.close
|
|
1520
|
-
io.close
|
|
1521
1580
|
end
|
|
1522
1581
|
end
|
|
1523
1582
|
|
|
@@ -1525,7 +1584,7 @@ module Toys
|
|
|
1525
1584
|
stream = make_out_pipe(key)
|
|
1526
1585
|
@join_threads << ::Thread.new do
|
|
1527
1586
|
data = stream.read
|
|
1528
|
-
@
|
|
1587
|
+
@captures_mutex.synchronize do
|
|
1529
1588
|
@captures[key] = data
|
|
1530
1589
|
end
|
|
1531
1590
|
ensure
|
data/lib/toys/utils/gems.rb
CHANGED
|
@@ -413,6 +413,8 @@ module Toys
|
|
|
413
413
|
end
|
|
414
414
|
|
|
415
415
|
loaded_gems = ::Gem.loaded_specs.values.sort_by(&:name)
|
|
416
|
+
omit_list = ::Toys::Compat.gems_to_omit_from_bundles
|
|
417
|
+
loaded_gems.delete_if { |spec| omit_list.include?(spec.name) } unless omit_list.empty?
|
|
416
418
|
content << "toys_loaded_gems = #{loaded_gems.map(&:name).inspect}"
|
|
417
419
|
content << "dependencies.delete_if { |dep| toys_loaded_gems.include?(dep.name) }"
|
|
418
420
|
loaded_gems.each do |spec|
|
data/lib/toys/utils/pager.rb
CHANGED
|
@@ -66,7 +66,11 @@ module Toys
|
|
|
66
66
|
result = @exec_service.exec(@command, in: :controller) do |controller|
|
|
67
67
|
yield controller.in if controller.pid
|
|
68
68
|
rescue ::Errno::EPIPE => e
|
|
69
|
-
|
|
69
|
+
if @rescue_broken_pipes
|
|
70
|
+
return 1
|
|
71
|
+
else
|
|
72
|
+
raise e
|
|
73
|
+
end
|
|
70
74
|
end
|
|
71
75
|
return result.exit_code unless result.failed?
|
|
72
76
|
end
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: toys-core
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.20.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Daniel Azuma
|
|
@@ -92,10 +92,10 @@ homepage: https://github.com/dazuma/toys
|
|
|
92
92
|
licenses:
|
|
93
93
|
- MIT
|
|
94
94
|
metadata:
|
|
95
|
-
changelog_uri: https://dazuma.github.io/toys/gems/toys-core/v0.
|
|
96
|
-
source_code_uri: https://github.com/dazuma/toys/tree/
|
|
95
|
+
changelog_uri: https://dazuma.github.io/toys/gems/toys-core/v0.20.0/file.CHANGELOG.html
|
|
96
|
+
source_code_uri: https://github.com/dazuma/toys/tree/toys-core/v0.20.0/toys-core
|
|
97
97
|
bug_tracker_uri: https://github.com/dazuma/toys/issues
|
|
98
|
-
documentation_uri: https://dazuma.github.io/toys/gems/toys-core/v0.
|
|
98
|
+
documentation_uri: https://dazuma.github.io/toys/gems/toys-core/v0.20.0
|
|
99
99
|
rdoc_options: []
|
|
100
100
|
require_paths:
|
|
101
101
|
- lib
|