toys-core 0.10.5 → 0.11.4
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 -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
|