command_runner_ng 0.0.2 → 0.1.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/lib/command_runner.rb +74 -8
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: e6de27ba13c1082bff813ee1eb2aec4c84dfe69f
|
4
|
+
data.tar.gz: 48227b83ec2908c902859d7468f10cf55bbd6975
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: b90ebd4992561b21a8b83c1de423254f8d114a97e79723d2b37f9a631380e9809689995d3c0b1a9a4ea98b745a870d37018429ec9be273fb764cecee78c0d9ce
|
7
|
+
data.tar.gz: d0e854031e48f9fa0f06c7f220ecadc9bd31ae98bbd041d346b3346ff3774c7f6b6a0badffb3505d4c816ccc3f06c5f06ba62ddcd3ed886a038fe14b67accfcd
|
data/lib/command_runner.rb
CHANGED
@@ -3,22 +3,31 @@ module CommandRunner
|
|
3
3
|
MAX_TIME = Time.new(2**63 -1)
|
4
4
|
|
5
5
|
# Like IO.popen(), but block until the child completes.
|
6
|
+
#
|
7
|
+
# For convenience allows you to pass > 1 string args without a boxing array,
|
8
|
+
# to execute a command with arguments without a subshell. See examples below.
|
9
|
+
#
|
6
10
|
# Takes an optional timeout parameter. If timeout is a
|
7
11
|
# number the child will be killed after that many seconds
|
8
12
|
# if it haven't completed. Alternatively it can be a Hash
|
9
13
|
# of timeouts to actions. Each action can be a string or integer
|
10
14
|
# specifying the signal to send, or a Proc to execute. The Proc
|
11
15
|
# will be called with the child PID as argument.
|
16
|
+
#
|
12
17
|
# These examples are equivalent:
|
13
18
|
# run('sleep 10', timeout: 5) # With a subshell
|
19
|
+
# run('sleep', '10', timeout: 5) # Without subshell. Convenience API to avoid array boxing as below
|
14
20
|
# run(['sleep', '10'], timeout: 5) # No subshell in this one and the rest
|
15
21
|
# run(['sleep', '10'], timeout: {5 => 'KILL'})
|
16
22
|
# run(['sleep', '10'], timeout: {5 => Proc.new { |pid| Process.kill('KILL', pid)}})
|
17
23
|
# run(['sleep', '10'], timeout: {
|
18
24
|
# 5 => 'KILL',
|
19
|
-
# 2 => Proc.new {|pid| puts "PID #{pid}
|
25
|
+
# 2 => Proc.new {|pid| puts "PID #{pid} geting SIGKILL in 3s"}
|
20
26
|
# })
|
21
27
|
#
|
28
|
+
# Takes an optional environment parameter (a Hash). The environment is
|
29
|
+
# populated with the keys/values of this parameter.
|
30
|
+
#
|
22
31
|
# Returns a Hash with :out and :status. :out is a string with stdout
|
23
32
|
# and stderr merged, and :status is a Process::Status.
|
24
33
|
#
|
@@ -26,7 +35,12 @@ module CommandRunner
|
|
26
35
|
# will be killed with SIGKILL, cleaned up, and the exception rethrown
|
27
36
|
# to the caller of run.
|
28
37
|
#
|
29
|
-
def self.run(*args, timeout: nil)
|
38
|
+
def self.run(*args, timeout: nil, environment: {})
|
39
|
+
# If args is an array of strings, allow that as a shorthand for [arg1, arg2, arg3]
|
40
|
+
if args.length > 1 && args.all? {|arg| arg.is_a? String}
|
41
|
+
args = [args]
|
42
|
+
end
|
43
|
+
|
30
44
|
# This could be tweakable through vararg opts
|
31
45
|
tick = 0.1
|
32
46
|
|
@@ -35,7 +49,7 @@ module CommandRunner
|
|
35
49
|
# Build deadline_sequence. A list of deadlines and corresponding actions to take
|
36
50
|
if timeout
|
37
51
|
if timeout.is_a? Numeric
|
38
|
-
deadline_sequence = [{deadline
|
52
|
+
deadline_sequence = [{:deadline => now + timeout, :action => 'KILL'}]
|
39
53
|
elsif timeout.is_a? Hash
|
40
54
|
deadline_sequence = timeout.collect do |t, action|
|
41
55
|
unless action.is_a? Integer or action.is_a? String or action.is_a? Proc
|
@@ -44,17 +58,17 @@ module CommandRunner
|
|
44
58
|
unless t.is_a? Numeric
|
45
59
|
raise "Unsupported timeout value '#{t}'. Must be a Numeric"
|
46
60
|
end
|
47
|
-
{deadline
|
61
|
+
{:deadline => now + t, :action => action}
|
48
62
|
end.sort! { |a, b| a[:deadline] <=> b[:deadline]}
|
49
63
|
else
|
50
64
|
raise "Unsupported type for timeout paramter: #{timeout.class}"
|
51
65
|
end
|
52
66
|
else
|
53
|
-
deadline_sequence = [{deadline
|
67
|
+
deadline_sequence = [{:deadline => MAX_TIME, :action => 0}]
|
54
68
|
end
|
55
69
|
|
56
70
|
# Spawn child, merging stderr into stdout
|
57
|
-
io = IO.popen(*args, :err=>[:child, :out])
|
71
|
+
io = IO.popen(environment, *args, {:err=>[:child, :out]})
|
58
72
|
data = ""
|
59
73
|
|
60
74
|
# Run through all deadlines until command completes.
|
@@ -65,7 +79,7 @@ module CommandRunner
|
|
65
79
|
while Time.now < point[:deadline]
|
66
80
|
if Process.wait(io.pid, Process::WNOHANG)
|
67
81
|
read_nonblock_safe!(io, data, tick)
|
68
|
-
result = {out
|
82
|
+
result = {:out => data, :status => $?}
|
69
83
|
io.close
|
70
84
|
return result
|
71
85
|
elsif !eof
|
@@ -100,14 +114,66 @@ module CommandRunner
|
|
100
114
|
# Either we didn't have a deadline, or none of the deadlines killed off the child.
|
101
115
|
Process.wait(io.pid)
|
102
116
|
read_nonblock_safe!(io, data, tick)
|
103
|
-
result = {out
|
117
|
+
result = {:out => data, :status => $?}
|
104
118
|
io.close
|
105
119
|
|
106
120
|
result
|
107
121
|
end
|
108
122
|
|
123
|
+
# Create a helper instance to launch a command with a given configuration.
|
124
|
+
# Invoke the command with the run() method. The configuration given to create()
|
125
|
+
# can be overriden on each invocation of run().
|
126
|
+
#
|
127
|
+
# The run() method of the helper instance must be invoked with a
|
128
|
+
#
|
129
|
+
# Examples:
|
130
|
+
# git = CommandRunner.create(['sudo', 'git'], timeout: 10, allowed_sub_commands: [:commit, :pull, :push])
|
131
|
+
# git.run(:pull, 'origin', 'master')
|
132
|
+
# git.run(:pull, 'origin', 'master', timeout: 2) # override default timeout of 10
|
133
|
+
# git.run(:status) # will raise an error because :status is not in list of allowed commands
|
134
|
+
def self.create(*args, timeout: nil, environment: {}, allowed_sub_commands: [])
|
135
|
+
CommandInstance.new(args, timeout, environment, allowed_sub_commands)
|
136
|
+
end
|
137
|
+
|
109
138
|
private
|
110
139
|
|
140
|
+
class CommandInstance
|
141
|
+
|
142
|
+
def initialize(default_args, default_timeout, default_environment, allowed_sub_commands)
|
143
|
+
unless default_args.first.is_a? Array
|
144
|
+
raise "First argument must be an array of command line args. Found #{default_args}"
|
145
|
+
end
|
146
|
+
|
147
|
+
@default_args = default_args
|
148
|
+
@default_timeout = default_timeout
|
149
|
+
@default_environment = default_environment
|
150
|
+
@allowed_sub_commands = allowed_sub_commands
|
151
|
+
end
|
152
|
+
|
153
|
+
def run(*args, timeout: nil, environment: {})
|
154
|
+
args_list = *args
|
155
|
+
|
156
|
+
if !args_list.nil? && !args_list.empty? && args_list.first.is_a?(Array)
|
157
|
+
if args_list.length > 1
|
158
|
+
raise "Unsupported args list length: #{args_list.length}"
|
159
|
+
else
|
160
|
+
args_list = args_list.first
|
161
|
+
end
|
162
|
+
end
|
163
|
+
|
164
|
+
# Check sub command if needed
|
165
|
+
if !args_list.nil? && !args_list.empty? &&
|
166
|
+
!@allowed_sub_commands.empty? && !@allowed_sub_commands.include?(args_list.first)
|
167
|
+
raise "Illegal sub command '#{args_list.first}'. Expected #{allowed_sub_commands} (#{allowed_sub_commands.include?(args_list.first)})"
|
168
|
+
end
|
169
|
+
|
170
|
+
full_args = @default_args.dup
|
171
|
+
full_args[0] += args_list.map {|arg| arg.to_s }
|
172
|
+
CommandRunner.run(*full_args, timeout: (timeout || @default_timeout), environment: @default_environment.merge(environment))
|
173
|
+
end
|
174
|
+
|
175
|
+
end
|
176
|
+
|
111
177
|
# Read data async, appending to data_out,
|
112
178
|
# returning true on EOF, false otherwise
|
113
179
|
def self.read_nonblock_safe!(io, data_out, tick)
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: command_runner_ng
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0
|
4
|
+
version: 0.1.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Mikkel Kamstrup Erlandsen
|
@@ -29,7 +29,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
29
29
|
requirements:
|
30
30
|
- - '>='
|
31
31
|
- !ruby/object:Gem::Version
|
32
|
-
version:
|
32
|
+
version: 2.0.0
|
33
33
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
34
34
|
requirements:
|
35
35
|
- - '>='
|