command_runner_ng 0.0.2 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (3) hide show
  1. checksums.yaml +4 -4
  2. data/lib/command_runner.rb +74 -8
  3. metadata +2 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 341dc1d0132412d53c1e5d258d7a6a03e96e3a4e
4
- data.tar.gz: 94ac85a463daa4020bf069c47adba3a99fe4aebb
3
+ metadata.gz: e6de27ba13c1082bff813ee1eb2aec4c84dfe69f
4
+ data.tar.gz: 48227b83ec2908c902859d7468f10cf55bbd6975
5
5
  SHA512:
6
- metadata.gz: 175e6af549e49bf1269032c11f0881ca5b89f79706ba706f082cba51af96b1a1ef13bf04d5d7955d64c3057af39096c94be946350934c25b10e71cfc6b741aac
7
- data.tar.gz: 5308811978a0c87a00e2aef3766b95fb733da28bcf32f58f327e65f1a58f5633f9b7143eddf4f5a1e9f3fc591bdcfb78a6286026a5e40b50123a9a01e20a42f9
6
+ metadata.gz: b90ebd4992561b21a8b83c1de423254f8d114a97e79723d2b37f9a631380e9809689995d3c0b1a9a4ea98b745a870d37018429ec9be273fb764cecee78c0d9ce
7
+ data.tar.gz: d0e854031e48f9fa0f06c7f220ecadc9bd31ae98bbd041d346b3346ff3774c7f6b6a0badffb3505d4c816ccc3f06c5f06ba62ddcd3ed886a038fe14b67accfcd
@@ -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} getting SIGKILL in 3s"}
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: now + timeout, action: 'KILL'}]
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: now + t, action: action}
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: MAX_TIME, action: 0}]
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: data, status: $?}
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: data, status: $?}
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.2
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: '0'
32
+ version: 2.0.0
33
33
  required_rubygems_version: !ruby/object:Gem::Requirement
34
34
  requirements:
35
35
  - - '>='