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.
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
  - - '>='