command_runner_ng 0.1.1 → 0.1.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/command_runner.rb +45 -9
- metadata +3 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 3421bf48db312e8cd8cec1544e7c2ca673be5179
|
4
|
+
data.tar.gz: 67fcb9afc773b88e9b7c4ec283b4553863dc3966
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 979bfd1c39af20bf5dc04e86ed14f80125229ec037d6f47b943bb5733219e25226a46b28afde0acf6689e1a130c0cffb7f911e9d0c488b9b89b4a546d7a362e2
|
7
|
+
data.tar.gz: 3931eb2093d52b9ffdf551cc6b01f8a574534e596acea48fb47f8a2520f70979a510b8921bb9fcd0bd730a14d3b6aa3562cba9ca2ff7e9a577cddbdc1abad24e
|
data/lib/command_runner.rb
CHANGED
@@ -2,6 +2,8 @@ module CommandRunner
|
|
2
2
|
|
3
3
|
MAX_TIME = Time.new(2**63 - 1)
|
4
4
|
|
5
|
+
DEFAULT_OPTIONS = {:err=>[:child, :out]}
|
6
|
+
|
5
7
|
# Like IO.popen(), but block until the child completes.
|
6
8
|
#
|
7
9
|
# For convenience allows you to pass > 1 string args without a boxing array,
|
@@ -28,8 +30,9 @@ module CommandRunner
|
|
28
30
|
# Takes an optional environment parameter (a Hash). The environment is
|
29
31
|
# populated with the keys/values of this parameter.
|
30
32
|
#
|
31
|
-
# Returns a Hash with :out and :status. :out is a string with stdout
|
32
|
-
# and stderr merged, and :status is a Process::Status.
|
33
|
+
# Returns a Hash with :out, :pid, and :status. :out is a string with stdout
|
34
|
+
# and stderr merged, and :status is a Process::Status. The :pid field is the
|
35
|
+
# the PID the child process had when it ran.
|
33
36
|
#
|
34
37
|
# As a special case - if an action Proc raises an exception, the child
|
35
38
|
# will be killed with SIGKILL, cleaned up, and the exception rethrown
|
@@ -44,7 +47,16 @@ module CommandRunner
|
|
44
47
|
#
|
45
48
|
# All Kernel.spawn features, like setting umasks, process group, and are supported through the options hash.
|
46
49
|
#
|
47
|
-
|
50
|
+
# Debugging: To help debugging your app you can set the debug_log parameter. It can be any old object responding
|
51
|
+
# to :puts. Fx. $stderr, $stdout, or the write end of an IO.pipe. CommandRunnerNG will put some info about
|
52
|
+
# all process start, stop, and timeouts here. To enable debug logging for all commands call
|
53
|
+
# CommandRunner.set_debug_log!($stderr) (or with some other object responding to :puts).
|
54
|
+
#
|
55
|
+
def self.run(*args, timeout: nil, environment: {}, debug_log: nil, options: DEFAULT_OPTIONS)
|
56
|
+
if debug_log.nil?
|
57
|
+
debug_log = @@global_debug_log
|
58
|
+
end
|
59
|
+
|
48
60
|
# If args is an array of strings, allow that as a shorthand for [arg1, arg2, arg3]
|
49
61
|
if args.length > 1 && args.all? {|arg| arg.is_a? String}
|
50
62
|
args = [args]
|
@@ -78,6 +90,7 @@ module CommandRunner
|
|
78
90
|
|
79
91
|
# Spawn child, merging stderr into stdout
|
80
92
|
io = IO.popen(environment, *args, options)
|
93
|
+
debug_log.puts("CommandRunnerNG spawn: args=#{args}, timeout=#{timeout}, options: #{options}, PID: #{io.pid}") if debug_log.respond_to?(:puts)
|
81
94
|
data = ""
|
82
95
|
|
83
96
|
# Run through all deadlines until command completes.
|
@@ -88,7 +101,8 @@ module CommandRunner
|
|
88
101
|
while Time.now < point[:deadline]
|
89
102
|
if Process.wait(io.pid, Process::WNOHANG)
|
90
103
|
read_nonblock_safe!(io, data, tick)
|
91
|
-
result = {:out => data, :status =>
|
104
|
+
result = {:out => data, :status => $?, pid: io.pid}
|
105
|
+
debug_log.puts("CommandRunnerNG exit: PID: #{io.pid}, code: #{result[:status].exitstatus}") if debug_log.respond_to?(:puts)
|
92
106
|
io.close
|
93
107
|
return result
|
94
108
|
elsif !eof
|
@@ -98,6 +112,7 @@ module CommandRunner
|
|
98
112
|
|
99
113
|
# Deadline for this point reached. Fire the action.
|
100
114
|
action = point[:action]
|
115
|
+
debug_log.puts("CommandRunnerNG timeout: PID: #{io.pid}, action: #{action}") if debug_log.respond_to?(:puts)
|
101
116
|
if action.is_a? String or action.is_a? Integer
|
102
117
|
Process.kill(action, io.pid)
|
103
118
|
elsif action.is_a? Proc
|
@@ -123,7 +138,9 @@ module CommandRunner
|
|
123
138
|
# Either we didn't have a deadline, or none of the deadlines killed off the child.
|
124
139
|
Process.wait(io.pid)
|
125
140
|
read_nonblock_safe!(io, data, tick)
|
126
|
-
result = {:out => data, :status =>
|
141
|
+
result = {:out => data, :status => $?, pid: io.pid}
|
142
|
+
debug_log.puts("CommandRunnerNG exit: PID: #{io.pid}, code: #{result[:status].exitstatus}") if debug_log.respond_to?(:puts)
|
143
|
+
|
127
144
|
io.close
|
128
145
|
|
129
146
|
result
|
@@ -140,15 +157,28 @@ module CommandRunner
|
|
140
157
|
# git.run(:pull, 'origin', 'master')
|
141
158
|
# git.run(:pull, 'origin', 'master', timeout: 2) # override default timeout of 10
|
142
159
|
# git.run(:status) # will raise an error because :status is not in list of allowed commands
|
143
|
-
def self.create(*args, timeout: nil, environment: {}, allowed_sub_commands: [])
|
144
|
-
CommandInstance.new(args, timeout, environment, allowed_sub_commands)
|
160
|
+
def self.create(*args, timeout: nil, environment: {}, allowed_sub_commands: [], debug_log: nil, options: DEFAULT_OPTIONS)
|
161
|
+
CommandInstance.new(args, timeout, environment, allowed_sub_commands, debug_log, options)
|
162
|
+
end
|
163
|
+
|
164
|
+
# Log all command line invocations to a logger object responding to :puts. Set to nil to disable.
|
165
|
+
# Setting the :debug_log keyword argument on individual invocations of CommandRunner.run() overrides this value.
|
166
|
+
def self.set_debug_log!(logger)
|
167
|
+
if logger.respond_to?(:puts) || logger.nil?
|
168
|
+
@@global_debug_log = logger
|
169
|
+
else
|
170
|
+
raise ArgumentError.new("Logger '#{logger}' not responding to :puts")
|
171
|
+
end
|
145
172
|
end
|
146
173
|
|
147
174
|
private
|
148
175
|
|
176
|
+
# Set to an object that respond to :puts to enable global debug logging for all
|
177
|
+
@@global_debug_log = nil
|
178
|
+
|
149
179
|
class CommandInstance
|
150
180
|
|
151
|
-
def initialize(default_args, default_timeout, default_environment, allowed_sub_commands)
|
181
|
+
def initialize(default_args, default_timeout, default_environment, allowed_sub_commands, debug_log, options)
|
152
182
|
unless default_args.first.is_a? Array
|
153
183
|
raise "First argument must be an array of command line args. Found #{default_args}"
|
154
184
|
end
|
@@ -157,6 +187,8 @@ module CommandRunner
|
|
157
187
|
@default_timeout = default_timeout
|
158
188
|
@default_environment = default_environment
|
159
189
|
@allowed_sub_commands = allowed_sub_commands
|
190
|
+
@debug_log = debug_log
|
191
|
+
@options = options
|
160
192
|
end
|
161
193
|
|
162
194
|
def run(*args, timeout: nil, environment: {})
|
@@ -178,7 +210,11 @@ module CommandRunner
|
|
178
210
|
|
179
211
|
full_args = @default_args.dup
|
180
212
|
full_args[0] += args_list.map {|arg| arg.to_s }
|
181
|
-
CommandRunner.run(*full_args,
|
213
|
+
CommandRunner.run(*full_args,
|
214
|
+
timeout: (timeout || @default_timeout),
|
215
|
+
environment: @default_environment.merge(environment),
|
216
|
+
debug_log: @debug_log,
|
217
|
+
options: @options)
|
182
218
|
end
|
183
219
|
|
184
220
|
end
|
metadata
CHANGED
@@ -1,17 +1,17 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: command_runner_ng
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Mikkel Kamstrup Erlandsen
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2017-04-05 00:00:00.000000000 Z
|
12
12
|
dependencies: []
|
13
13
|
description: Helper APIs for advanced interactions with subprocesses and shell commands
|
14
|
-
email:
|
14
|
+
email: kamikkel@microsoft.com
|
15
15
|
executables: []
|
16
16
|
extensions: []
|
17
17
|
extra_rdoc_files: []
|