toys-core 0.10.5 → 0.11.4
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 +62 -36
- data/docs/guide.md +2 -0
- data/lib/toys/acceptor.rb +1 -1
- data/lib/toys/core.rb +1 -1
- data/lib/toys/dsl/flag.rb +2 -2
- data/lib/toys/dsl/flag_group.rb +2 -2
- data/lib/toys/dsl/tool.rb +27 -6
- data/lib/toys/loader.rb +71 -31
- data/lib/toys/source_info.rb +22 -23
- data/lib/toys/standard_mixins/bundler.rb +113 -56
- data/lib/toys/utils/exec.rb +126 -34
- data/lib/toys/utils/gems.rb +71 -20
- data/lib/toys/utils/help_text.rb +54 -59
- metadata +5 -5
data/lib/toys/source_info.rb
CHANGED
@@ -10,7 +10,7 @@ module Toys
|
|
10
10
|
# @private
|
11
11
|
#
|
12
12
|
def initialize(parent, context_directory, source_type, source_path, source_proc,
|
13
|
-
source_name,
|
13
|
+
source_name, data_dir_name, lib_dir_name)
|
14
14
|
@parent = parent
|
15
15
|
@context_directory = context_directory
|
16
16
|
@source_type = source_type
|
@@ -18,8 +18,10 @@ module Toys
|
|
18
18
|
@source_path = source_path
|
19
19
|
@source_proc = source_proc
|
20
20
|
@source_name = source_name
|
21
|
-
@
|
22
|
-
@
|
21
|
+
@data_dir_name = data_dir_name
|
22
|
+
@lib_dir_name = lib_dir_name
|
23
|
+
@data_dir = find_special_dir(data_dir_name)
|
24
|
+
@lib_dir = find_special_dir(lib_dir_name)
|
23
25
|
end
|
24
26
|
|
25
27
|
##
|
@@ -118,15 +120,13 @@ module Toys
|
|
118
120
|
# Create a child SourceInfo relative to the parent path.
|
119
121
|
# @private
|
120
122
|
#
|
121
|
-
def relative_child(filename
|
122
|
-
raise "
|
123
|
+
def relative_child(filename)
|
124
|
+
raise "relative_child is valid only on a directory source" unless source_type == :directory
|
123
125
|
child_path = ::File.join(source_path, filename)
|
124
126
|
child_path, type = SourceInfo.check_path(child_path, true)
|
125
127
|
return nil unless child_path
|
126
|
-
|
127
|
-
|
128
|
-
SourceInfo.new(self, context_directory, type, child_path, source_proc, child_path,
|
129
|
-
data_dir, lib_dir)
|
128
|
+
SourceInfo.new(self, context_directory, type, child_path, nil, child_path,
|
129
|
+
@data_dir_name, @lib_dir_name)
|
130
130
|
end
|
131
131
|
|
132
132
|
##
|
@@ -135,7 +135,8 @@ module Toys
|
|
135
135
|
#
|
136
136
|
def absolute_child(child_path)
|
137
137
|
child_path, type = SourceInfo.check_path(child_path, false)
|
138
|
-
SourceInfo.new(self, context_directory, type, child_path,
|
138
|
+
SourceInfo.new(self, context_directory, type, child_path, nil, child_path,
|
139
|
+
@data_dir_name, @lib_dir_name)
|
139
140
|
end
|
140
141
|
|
141
142
|
##
|
@@ -144,25 +145,26 @@ module Toys
|
|
144
145
|
#
|
145
146
|
def proc_child(child_proc, source_name = nil)
|
146
147
|
source_name ||= self.source_name
|
147
|
-
SourceInfo.new(self, context_directory, :proc, source_path, child_proc, source_name,
|
148
|
+
SourceInfo.new(self, context_directory, :proc, source_path, child_proc, source_name,
|
149
|
+
@data_dir_name, @lib_dir_name)
|
148
150
|
end
|
149
151
|
|
150
152
|
##
|
151
153
|
# Create a root source info for a file path.
|
152
154
|
# @private
|
153
155
|
#
|
154
|
-
def self.create_path_root(source_path)
|
156
|
+
def self.create_path_root(source_path, data_dir_name, lib_dir_name)
|
155
157
|
source_path, type = check_path(source_path, false)
|
156
158
|
context_directory = ::File.dirname(source_path)
|
157
|
-
new(nil, context_directory, type, source_path, nil, source_path,
|
159
|
+
new(nil, context_directory, type, source_path, nil, source_path, data_dir_name, lib_dir_name)
|
158
160
|
end
|
159
161
|
|
160
162
|
##
|
161
163
|
# Create a root source info for a proc.
|
162
164
|
# @private
|
163
165
|
#
|
164
|
-
def self.create_proc_root(source_proc, source_name)
|
165
|
-
new(nil, nil, :proc, nil, source_proc, source_name,
|
166
|
+
def self.create_proc_root(source_proc, source_name, data_dir_name, lib_dir_name)
|
167
|
+
new(nil, nil, :proc, nil, source_proc, source_name, data_dir_name, lib_dir_name)
|
166
168
|
end
|
167
169
|
|
168
170
|
##
|
@@ -189,14 +191,11 @@ module Toys
|
|
189
191
|
end
|
190
192
|
end
|
191
193
|
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
return nil if source_path.nil? || dir_name.nil?
|
198
|
-
source_path = ::File.dirname(source_path) if type == :file
|
199
|
-
dir = ::File.join(source_path, dir_name)
|
194
|
+
private
|
195
|
+
|
196
|
+
def find_special_dir(dir_name)
|
197
|
+
return nil if @source_type != :directory || dir_name.nil?
|
198
|
+
dir = ::File.join(@source_path, dir_name)
|
200
199
|
dir if ::File.directory?(dir) && ::File.readable?(dir)
|
201
200
|
end
|
202
201
|
end
|
@@ -11,32 +11,51 @@ module Toys
|
|
11
11
|
# defining the tool. If `false` (the default), installs the bundle just
|
12
12
|
# before the tool runs.
|
13
13
|
#
|
14
|
-
# * `:groups` (Array
|
14
|
+
# * `:groups` (Array\<String\>) The groups to include in setup.
|
15
15
|
#
|
16
|
-
# * `:
|
17
|
-
# Gemfile.
|
16
|
+
# * `:gemfile_path` (String) The path to the Gemfile to use. If `nil` or
|
17
|
+
# not given, the `:search_dirs` will be searched for a Gemfile.
|
18
|
+
#
|
19
|
+
# * `:search_dirs` (String,Symbol,Array\<String,Symbol\>) Directories to
|
20
|
+
# search for a Gemfile.
|
18
21
|
#
|
19
22
|
# You can pass full directory paths, and/or any of the following:
|
20
|
-
# * `:context` - the current context directory
|
21
|
-
# * `:current` - the current working directory
|
22
|
-
# * `:toys` - the Toys directory containing the tool definition
|
23
|
+
# * `:context` - the current context directory.
|
24
|
+
# * `:current` - the current working directory.
|
25
|
+
# * `:toys` - the Toys directory containing the tool definition, and
|
26
|
+
# any of its parents within the Toys directory hierarchy.
|
23
27
|
#
|
24
28
|
# The default is to search `[:toys, :context, :current]` in that order.
|
29
|
+
# See {DEFAULT_SEARCH_DIRS}.
|
30
|
+
#
|
31
|
+
# For most directories, the bundler mixin will look for the files
|
32
|
+
# ".gems.rb", "gems.rb", and "Gemfile", in that order. In `:toys`
|
33
|
+
# directories, it will look only for ".gems.rb" and "Gemfile", in that
|
34
|
+
# order. These can be overridden by setting the `:gemfile_names` and/or
|
35
|
+
# `:toys_gemfile_names` arguments.
|
36
|
+
#
|
37
|
+
# * `:gemfile_names` (Array\<String\>) File names that are recognized as
|
38
|
+
# Gemfiles when searching in directories other than Toys directories.
|
39
|
+
# Defaults to {Toys::Utils::Gems::DEFAULT_GEMFILE_NAMES}.
|
40
|
+
#
|
41
|
+
# * `:toys_gemfile_names` (Array\<String\>) File names that are
|
42
|
+
# recognized as Gemfiles when wearching in Toys directories.
|
43
|
+
# Defaults to {DEFAULT_TOYS_GEMFILE_NAMES}.
|
25
44
|
#
|
26
45
|
# * `:on_missing` (Symbol) What to do if a needed gem is not installed.
|
27
46
|
#
|
28
47
|
# Supported values:
|
29
|
-
# * `:confirm` - prompt the user on whether to install (default)
|
30
|
-
# * `:error` - raise an exception
|
31
|
-
# * `:install` - just install the gem
|
48
|
+
# * `:confirm` - prompt the user on whether to install (default).
|
49
|
+
# * `:error` - raise an exception.
|
50
|
+
# * `:install` - just install the gem.
|
32
51
|
#
|
33
52
|
# * `:on_conflict` (Symbol) What to do if bundler has already been run
|
34
53
|
# with a different Gemfile.
|
35
54
|
#
|
36
55
|
# Supported values:
|
37
|
-
# * `:error` - raise an exception (default)
|
38
|
-
# * `:ignore` - just silently proceed without bundling again
|
39
|
-
# * `:warn` - print a warning and proceed without bundling again
|
56
|
+
# * `:error` - raise an exception (default).
|
57
|
+
# * `:ignore` - just silently proceed without bundling again.
|
58
|
+
# * `:warn` - print a warning and proceed without bundling again.
|
40
59
|
#
|
41
60
|
# * `:terminal` (Toys::Utils::Terminal) Terminal to use (optional)
|
42
61
|
# * `:input` (IO) Input IO (optional, defaults to STDIN)
|
@@ -45,68 +64,106 @@ module Toys
|
|
45
64
|
module Bundler
|
46
65
|
include Mixin
|
47
66
|
|
48
|
-
on_initialize do |static: false,
|
67
|
+
on_initialize do |static: false, **kwargs|
|
49
68
|
unless static
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
self[::Toys::Context::Key::CONTEXT_DIRECTORY],
|
54
|
-
self[::Toys::Context::Key::TOOL_SOURCE]
|
55
|
-
)
|
56
|
-
::Toys::StandardMixins::Bundler.setup_bundle(search_dirs, **kwargs)
|
69
|
+
context_directory = self[::Toys::Context::Key::CONTEXT_DIRECTORY]
|
70
|
+
source_info = self[::Toys::Context::Key::TOOL_SOURCE]
|
71
|
+
::Toys::StandardMixins::Bundler.setup_bundle(context_directory, source_info, **kwargs)
|
57
72
|
end
|
58
73
|
end
|
59
74
|
|
60
|
-
on_include do |static: false,
|
75
|
+
on_include do |static: false, **kwargs|
|
61
76
|
if static
|
62
|
-
|
63
|
-
search_dirs = ::Toys::StandardMixins::Bundler.resolve_search_dirs(
|
64
|
-
search_dirs, context_directory, source_info
|
65
|
-
)
|
66
|
-
::Toys::StandardMixins::Bundler.setup_bundle(search_dirs, **kwargs)
|
77
|
+
::Toys::StandardMixins::Bundler.setup_bundle(context_directory, source_info, **kwargs)
|
67
78
|
end
|
68
79
|
end
|
69
80
|
|
70
|
-
##
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
when :context
|
76
|
-
context_dir
|
77
|
-
when :current
|
78
|
-
::Dir.getwd
|
79
|
-
when :toys
|
80
|
-
toys_dir_stack(source_info)
|
81
|
-
when ::String
|
82
|
-
dir
|
83
|
-
else
|
84
|
-
raise ::ArgumentError, "Unrecognized search_dir: #{dir.inspect}"
|
85
|
-
end
|
86
|
-
end
|
87
|
-
end
|
81
|
+
##
|
82
|
+
# Default search directories for Gemfiles.
|
83
|
+
# @return [Array<String,Symbol>]
|
84
|
+
#
|
85
|
+
DEFAULT_SEARCH_DIRS = [:toys, :context, :current].freeze
|
88
86
|
|
89
|
-
##
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
source_info = source_info.parent
|
95
|
-
end
|
96
|
-
dirs
|
97
|
-
end
|
87
|
+
##
|
88
|
+
# The gemfile names that are searched by default in Toys directories.
|
89
|
+
# @return [Array<String>]
|
90
|
+
#
|
91
|
+
DEFAULT_TOYS_GEMFILE_NAMES = [".gems.rb", "Gemfile"].freeze
|
98
92
|
|
99
|
-
|
100
|
-
def self.setup_bundle(
|
93
|
+
# @private
|
94
|
+
def self.setup_bundle(context_directory,
|
95
|
+
source_info,
|
96
|
+
gemfile_path: nil,
|
97
|
+
search_dirs: nil,
|
98
|
+
gemfile_names: nil,
|
99
|
+
toys_gemfile_names: nil,
|
101
100
|
groups: nil,
|
102
101
|
on_missing: nil,
|
103
102
|
on_conflict: nil,
|
104
103
|
terminal: nil,
|
105
104
|
input: nil,
|
106
105
|
output: nil)
|
106
|
+
require "toys/utils/gems"
|
107
|
+
gemfile_path ||= begin
|
108
|
+
gemfile_finder = GemfileFinder.new(context_directory, source_info,
|
109
|
+
gemfile_names, toys_gemfile_names)
|
110
|
+
gemfile_finder.search(search_dirs || DEFAULT_SEARCH_DIRS)
|
111
|
+
end
|
107
112
|
gems = ::Toys::Utils::Gems.new(on_missing: on_missing, on_conflict: on_conflict,
|
108
113
|
terminal: terminal, input: input, output: output)
|
109
|
-
gems.bundle(groups: groups,
|
114
|
+
gems.bundle(groups: groups, gemfile_path: gemfile_path)
|
115
|
+
end
|
116
|
+
|
117
|
+
# @private
|
118
|
+
class GemfileFinder
|
119
|
+
# @private
|
120
|
+
def initialize(context_directory, source_info, gemfile_names, toys_gemfile_names)
|
121
|
+
@context_directory = context_directory
|
122
|
+
@source_info = source_info
|
123
|
+
@gemfile_names = gemfile_names
|
124
|
+
@toys_gemfile_names = toys_gemfile_names || DEFAULT_TOYS_GEMFILE_NAMES
|
125
|
+
end
|
126
|
+
|
127
|
+
# @private
|
128
|
+
def search(search_dir)
|
129
|
+
case search_dir
|
130
|
+
when ::Array
|
131
|
+
search_array(search_dir)
|
132
|
+
when ::String
|
133
|
+
::Toys::Utils::Gems.find_gemfile(search_dir, gemfile_names: @gemfile_names)
|
134
|
+
when :context
|
135
|
+
search(@context_directory)
|
136
|
+
when :current
|
137
|
+
search(::Dir.getwd)
|
138
|
+
when :toys
|
139
|
+
search_toys
|
140
|
+
else
|
141
|
+
raise ::ArgumentError, "Unrecognized search_dir: #{dir.inspect}"
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
145
|
+
private
|
146
|
+
|
147
|
+
def search_array(search_dirs)
|
148
|
+
search_dirs.each do |search_dir|
|
149
|
+
result = search(search_dir)
|
150
|
+
return result if result
|
151
|
+
end
|
152
|
+
nil
|
153
|
+
end
|
154
|
+
|
155
|
+
def search_toys
|
156
|
+
source_info = @source_info
|
157
|
+
while source_info
|
158
|
+
if source_info.source_type == :directory
|
159
|
+
result = ::Toys::Utils::Gems.find_gemfile(source_info.source_path,
|
160
|
+
gemfile_names: @toys_gemfile_names)
|
161
|
+
return result if result
|
162
|
+
end
|
163
|
+
source_info = source_info.parent
|
164
|
+
end
|
165
|
+
nil
|
166
|
+
end
|
110
167
|
end
|
111
168
|
end
|
112
169
|
end
|
data/lib/toys/utils/exec.rb
CHANGED
@@ -250,13 +250,14 @@ module Toys
|
|
250
250
|
exec_opts = Opts.new(@default_opts).add(opts)
|
251
251
|
spawn_cmd =
|
252
252
|
if cmd.is_a?(::Array)
|
253
|
-
if cmd.size
|
254
|
-
|
253
|
+
if cmd.size > 1
|
254
|
+
binary = canonical_binary_spec(cmd.first, exec_opts)
|
255
|
+
[binary] + cmd[1..-1].map(&:to_s)
|
255
256
|
else
|
256
|
-
cmd
|
257
|
+
[canonical_binary_spec(Array(cmd.first), exec_opts)]
|
257
258
|
end
|
258
259
|
else
|
259
|
-
[cmd]
|
260
|
+
[cmd.to_s]
|
260
261
|
end
|
261
262
|
executor = Executor.new(exec_opts, spawn_cmd, block)
|
262
263
|
executor.execute
|
@@ -492,7 +493,8 @@ module Toys
|
|
492
493
|
#
|
493
494
|
class Controller
|
494
495
|
## @private
|
495
|
-
def initialize(name, controller_streams, captures, pid, join_threads,
|
496
|
+
def initialize(name, controller_streams, captures, pid, join_threads,
|
497
|
+
result_callback, mutex)
|
496
498
|
@name = name
|
497
499
|
@in = controller_streams[:in]
|
498
500
|
@out = controller_streams[:out]
|
@@ -508,6 +510,7 @@ module Toys
|
|
508
510
|
end
|
509
511
|
@join_threads = join_threads
|
510
512
|
@result_callback = result_callback
|
513
|
+
@mutex = mutex
|
511
514
|
@result = nil
|
512
515
|
end
|
513
516
|
|
@@ -547,7 +550,7 @@ module Toys
|
|
547
550
|
##
|
548
551
|
# The process ID.
|
549
552
|
#
|
550
|
-
# Exactly one of
|
553
|
+
# Exactly one of {#exception} and {#pid} will be non-nil.
|
551
554
|
#
|
552
555
|
# @return [Integer] if the process start was successful
|
553
556
|
# @return [nil] if the process could not be started.
|
@@ -557,7 +560,7 @@ module Toys
|
|
557
560
|
##
|
558
561
|
# The exception raised when the process failed to start.
|
559
562
|
#
|
560
|
-
# Exactly one of
|
563
|
+
# Exactly one of {#exception} and {#pid} will be non-nil.
|
561
564
|
#
|
562
565
|
# @return [Exception] if the process failed to start.
|
563
566
|
# @return [nil] if the process start was successful.
|
@@ -575,7 +578,10 @@ module Toys
|
|
575
578
|
stream = stream_for(which)
|
576
579
|
@join_threads << ::Thread.new do
|
577
580
|
begin
|
578
|
-
|
581
|
+
data = stream.read
|
582
|
+
@mutex.synchronize do
|
583
|
+
@captures[which] = data
|
584
|
+
end
|
579
585
|
ensure
|
580
586
|
stream.close
|
581
587
|
end
|
@@ -722,15 +728,20 @@ module Toys
|
|
722
728
|
##
|
723
729
|
# Wait for the subcommand to complete, and return a result object.
|
724
730
|
#
|
731
|
+
# Closes the control streams if present. The stdin stream is always
|
732
|
+
# closed, even if the call times out. The stdout and stderr streams are
|
733
|
+
# closed only after the command terminates.
|
734
|
+
#
|
725
735
|
# @param timeout [Numeric,nil] The timeout in seconds, or `nil` to
|
726
736
|
# wait indefinitely.
|
727
737
|
# @return [Toys::Utils::Exec::Result] The result object
|
728
738
|
# @return [nil] if a timeout occurred.
|
729
739
|
#
|
730
740
|
def result(timeout: nil)
|
741
|
+
close_streams(:in)
|
731
742
|
return nil if @wait_thread && !@wait_thread.join(timeout)
|
732
743
|
@result ||= begin
|
733
|
-
close_streams
|
744
|
+
close_streams(:out)
|
734
745
|
@join_threads.each(&:join)
|
735
746
|
Result.new(name, @captures[:out], @captures[:err], @wait_thread&.value, @exception)
|
736
747
|
.tap { |result| @result_callback&.call(result) }
|
@@ -738,13 +749,13 @@ module Toys
|
|
738
749
|
end
|
739
750
|
|
740
751
|
##
|
741
|
-
# Close
|
752
|
+
# Close the controller's streams.
|
742
753
|
# @private
|
743
754
|
#
|
744
|
-
def close_streams
|
745
|
-
@in.close if @in && !@in.closed?
|
746
|
-
@out.close if @out && !@out.closed?
|
747
|
-
@err.close if @err && !@err.closed?
|
755
|
+
def close_streams(which)
|
756
|
+
@in.close if which != :out && @in && !@in.closed?
|
757
|
+
@out.close if which != :in && @out && !@out.closed?
|
758
|
+
@err.close if which != :in && @err && !@err.closed?
|
748
759
|
self
|
749
760
|
end
|
750
761
|
|
@@ -773,7 +784,21 @@ module Toys
|
|
773
784
|
end
|
774
785
|
|
775
786
|
##
|
776
|
-
# The result returned from a subcommand execution.
|
787
|
+
# The result returned from a subcommand execution. This includes the
|
788
|
+
# identifying name of the execution (if any), the result status of the
|
789
|
+
# execution, and any captured stream output.
|
790
|
+
#
|
791
|
+
# Possible result statuses are:
|
792
|
+
#
|
793
|
+
# * The process failed to start. {Result#failed?} will return true, and
|
794
|
+
# {Result#exception} will return an exception describing the failure
|
795
|
+
# (often an errno).
|
796
|
+
# * The process executed and exited with a normal exit code. Either
|
797
|
+
# {Result#success?} or {Result#error?} will return true, and
|
798
|
+
# {Result.exit_code} will return the numeric exit code.
|
799
|
+
# * The process executed but was terminated by an uncaught signal.
|
800
|
+
# {Result#signaled?} will return true, and {Result#term_signal} will
|
801
|
+
# return the numeric signal code.
|
777
802
|
#
|
778
803
|
class Result
|
779
804
|
## @private
|
@@ -809,11 +834,13 @@ module Toys
|
|
809
834
|
attr_reader :captured_err
|
810
835
|
|
811
836
|
##
|
812
|
-
# The status
|
837
|
+
# The Ruby process status object, providing various information about
|
838
|
+
# the ending state of the process.
|
813
839
|
#
|
814
|
-
# Exactly one of
|
840
|
+
# Exactly one of {#exception} and {#status} will be non-nil.
|
815
841
|
#
|
816
|
-
# @return [Process::Status] The status
|
842
|
+
# @return [Process::Status] The status, if the process was successfully
|
843
|
+
# spanwed and terminated.
|
817
844
|
# @return [nil] if the process could not be started.
|
818
845
|
#
|
819
846
|
attr_reader :status
|
@@ -821,7 +848,7 @@ module Toys
|
|
821
848
|
##
|
822
849
|
# The exception raised if a process couldn't be started.
|
823
850
|
#
|
824
|
-
# Exactly one of
|
851
|
+
# Exactly one of {#exception} and {#status} will be non-nil.
|
825
852
|
#
|
826
853
|
# @return [Exception] The exception raised from process start.
|
827
854
|
# @return [nil] if the process started successfully.
|
@@ -829,33 +856,76 @@ module Toys
|
|
829
856
|
attr_reader :exception
|
830
857
|
|
831
858
|
##
|
832
|
-
# The numeric status code
|
859
|
+
# The numeric status code for a process that exited normally,
|
833
860
|
#
|
834
|
-
#
|
835
|
-
#
|
861
|
+
# Exactly one of {#exception}, {#exit_code}, and {#term_signal} will be
|
862
|
+
# non-nil.
|
836
863
|
#
|
837
|
-
# @return [Integer]
|
864
|
+
# @return [Integer] the numeric status code, if the process started
|
865
|
+
# successfully and exited normally.
|
866
|
+
# @return [nil] if the process did not start successfully, or was
|
867
|
+
# terminated by an uncaught signal.
|
838
868
|
#
|
839
869
|
def exit_code
|
840
|
-
status
|
870
|
+
status&.exitstatus
|
871
|
+
end
|
872
|
+
|
873
|
+
##
|
874
|
+
# The numeric signal code that caused process termination.
|
875
|
+
#
|
876
|
+
# Exactly one of {#exception}, {#exit_code}, and {#term_signal} will be
|
877
|
+
# non-nil.
|
878
|
+
#
|
879
|
+
# @return [Integer] The signal that caused the process to terminate.
|
880
|
+
# @return [nil] if the process did not start successfully, or executed
|
881
|
+
# and exited with a normal exit code.
|
882
|
+
#
|
883
|
+
def term_signal
|
884
|
+
status&.termsig
|
885
|
+
end
|
886
|
+
|
887
|
+
##
|
888
|
+
# Returns true if the subprocess failed to start, or false if the
|
889
|
+
# process was able to execute.
|
890
|
+
#
|
891
|
+
# @return [Boolean]
|
892
|
+
#
|
893
|
+
def failed?
|
894
|
+
status.nil?
|
895
|
+
end
|
896
|
+
|
897
|
+
##
|
898
|
+
# Returns true if the subprocess terminated due to an unhandled signal,
|
899
|
+
# or false if the process failed to start or exited normally.
|
900
|
+
#
|
901
|
+
# @return [Boolean]
|
902
|
+
#
|
903
|
+
def signaled?
|
904
|
+
!term_signal.nil?
|
841
905
|
end
|
842
906
|
|
843
907
|
##
|
844
|
-
# Returns true if the subprocess terminated with a zero status
|
908
|
+
# Returns true if the subprocess terminated with a zero status, or
|
909
|
+
# false if the process failed to start, terminated due to a signal, or
|
910
|
+
# returned a nonzero status.
|
845
911
|
#
|
846
912
|
# @return [Boolean]
|
847
913
|
#
|
848
914
|
def success?
|
849
|
-
exit_code
|
915
|
+
code = exit_code
|
916
|
+
!code.nil? && code.zero?
|
850
917
|
end
|
851
918
|
|
852
919
|
##
|
853
|
-
# Returns true if the subprocess terminated with a nonzero status
|
920
|
+
# Returns true if the subprocess terminated with a nonzero status, or
|
921
|
+
# false if the process failed to start, terminated due to a signal, or
|
922
|
+
# returned a zero status.
|
854
923
|
#
|
855
924
|
# @return [Boolean]
|
856
925
|
#
|
857
926
|
def error?
|
858
|
-
|
927
|
+
code = exit_code
|
928
|
+
!code.nil? && !code.zero?
|
859
929
|
end
|
860
930
|
end
|
861
931
|
|
@@ -876,6 +946,7 @@ module Toys
|
|
876
946
|
@parent_streams = []
|
877
947
|
@block = block
|
878
948
|
@default_stream = @config_opts[:background] ? :null : :inherit
|
949
|
+
@mutex = ::Mutex.new
|
879
950
|
end
|
880
951
|
|
881
952
|
def execute
|
@@ -887,10 +958,10 @@ module Toys
|
|
887
958
|
return controller if @config_opts[:background]
|
888
959
|
begin
|
889
960
|
@block&.call(controller)
|
961
|
+
controller.result
|
890
962
|
ensure
|
891
|
-
controller.close_streams
|
963
|
+
controller.close_streams(:both)
|
892
964
|
end
|
893
|
-
controller.result
|
894
965
|
end
|
895
966
|
|
896
967
|
private
|
@@ -898,12 +969,19 @@ module Toys
|
|
898
969
|
def log_command
|
899
970
|
logger = @config_opts[:logger]
|
900
971
|
if logger && @config_opts[:log_level] != false
|
901
|
-
cmd_str = @config_opts[:log_cmd]
|
902
|
-
cmd_str ||= @spawn_cmd.size == 1 ? @spawn_cmd.first : @spawn_cmd.inspect if @spawn_cmd
|
972
|
+
cmd_str = @config_opts[:log_cmd] || default_log_str(@spawn_cmd)
|
903
973
|
logger.add(@config_opts[:log_level] || ::Logger::INFO, cmd_str) if cmd_str
|
904
974
|
end
|
905
975
|
end
|
906
976
|
|
977
|
+
def default_log_str(spawn_cmd)
|
978
|
+
return nil unless spawn_cmd
|
979
|
+
return spawn_cmd.first if spawn_cmd.size == 1 && spawn_cmd.first.is_a?(::String)
|
980
|
+
cmd_binary = spawn_cmd.first
|
981
|
+
cmd_binary = cmd_binary.first if cmd_binary.is_a?(::Array)
|
982
|
+
([cmd_binary] + spawn_cmd[1..-1]).inspect
|
983
|
+
end
|
984
|
+
|
907
985
|
def start_with_controller
|
908
986
|
pid =
|
909
987
|
begin
|
@@ -913,7 +991,7 @@ module Toys
|
|
913
991
|
end
|
914
992
|
@child_streams.each(&:close)
|
915
993
|
Controller.new(@config_opts[:name], @controller_streams, @captures, pid,
|
916
|
-
@join_threads, @config_opts[:result_callback])
|
994
|
+
@join_threads, @config_opts[:result_callback], @mutex)
|
917
995
|
end
|
918
996
|
|
919
997
|
def start_process
|
@@ -1220,13 +1298,27 @@ module Toys
|
|
1220
1298
|
stream = make_out_pipe(key)
|
1221
1299
|
@join_threads << ::Thread.new do
|
1222
1300
|
begin
|
1223
|
-
|
1301
|
+
data = stream.read
|
1302
|
+
@mutex.synchronize do
|
1303
|
+
@captures[key] = data
|
1304
|
+
end
|
1224
1305
|
ensure
|
1225
1306
|
stream.close
|
1226
1307
|
end
|
1227
1308
|
end
|
1228
1309
|
end
|
1229
1310
|
end
|
1311
|
+
|
1312
|
+
private
|
1313
|
+
|
1314
|
+
def canonical_binary_spec(cmd, exec_opts)
|
1315
|
+
config_argv0 = exec_opts.config_opts[:argv0]
|
1316
|
+
return cmd.to_s if !config_argv0 && !cmd.is_a?(::Array)
|
1317
|
+
cmd = Array(cmd)
|
1318
|
+
actual_cmd = cmd.first
|
1319
|
+
argv0 = cmd[1] || config_argv0 || actual_cmd
|
1320
|
+
[actual_cmd.to_s, argv0.to_s]
|
1321
|
+
end
|
1230
1322
|
end
|
1231
1323
|
end
|
1232
1324
|
end
|