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.
Files changed (3) hide show
  1. checksums.yaml +4 -4
  2. data/lib/command_runner.rb +45 -9
  3. metadata +3 -3
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 5ec595e87ef6bc2b18247db034e7f6c16833c7ea
4
- data.tar.gz: f35ed38a143ae56e0964d5fb146f47f43217f4ae
3
+ metadata.gz: 3421bf48db312e8cd8cec1544e7c2ca673be5179
4
+ data.tar.gz: 67fcb9afc773b88e9b7c4ec283b4553863dc3966
5
5
  SHA512:
6
- metadata.gz: 2a5e1afcb667b1d18cecbeb7f5bc998df37a881b6ca3b33b986480faab4264af3189df20c12f2ae82ece3d67e251295d466f0be2ce22f3c06a184e2db039bdcd
7
- data.tar.gz: 0c9b71180c50ba117e0c75975e226886608b8eb4f4e9b7ed17d419eba7c70e4bae6148e9d0e2d9601dd993cf6b99469db935cb6f1dc822845f700648fbd51cfd
6
+ metadata.gz: 979bfd1c39af20bf5dc04e86ed14f80125229ec037d6f47b943bb5733219e25226a46b28afde0acf6689e1a130c0cffb7f911e9d0c488b9b89b4a546d7a362e2
7
+ data.tar.gz: 3931eb2093d52b9ffdf551cc6b01f8a574534e596acea48fb47f8a2520f70979a510b8921bb9fcd0bd730a14d3b6aa3562cba9ca2ff7e9a577cddbdc1abad24e
@@ -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
- def self.run(*args, timeout: nil, environment: {}, options: {:err=>[:child, :out]})
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, timeout: (timeout || @default_timeout), environment: @default_environment.merge(environment))
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.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: 2015-09-16 00:00:00.000000000 Z
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: mikkel.kamstrup@xamarin.com
14
+ email: kamikkel@microsoft.com
15
15
  executables: []
16
16
  extensions: []
17
17
  extra_rdoc_files: []