Class: Progeny::Command
- Inherits:
-
Object
- Object
- Progeny::Command
- Defined in:
- lib/progeny/command.rb
Instance Attribute Summary collapse
-
#err ⇒ Object
readonly
All data written to the child process’s stderr stream as a String.
-
#input ⇒ Object
readonly
All data written to the child process’s stdin stream as a String.
-
#out ⇒ Object
readonly
All data written to the child process’s stdout stream as a String.
-
#pid ⇒ Object
readonly
The pid of the spawned child process.
-
#runtime ⇒ Object
readonly
Total command execution time (wall-clock time).
-
#status ⇒ Object
readonly
A Process::Status object with information on how the child exited.
Class Method Summary collapse
-
.build(*args) ⇒ Object
Set up a new process to spawn, but do not actually spawn it.
-
.spawn_with_pipes(*argv) ⇒ Object
Spawn a child process with all standard IO streams piped in and out of the spawning process.
Instance Method Summary collapse
-
#exec! ⇒ Object
Execute command, write input, and read output.
-
#initialize(*args) ⇒ Command
constructor
Spawn a new process, write all input and read all output, and wait for the program to exit.
-
#success? ⇒ Boolean
Determine if the process did exit with a zero exit status.
Constructor Details
#initialize(*args) ⇒ Command
Spawn a new process, write all input and read all output, and wait for the program to exit. Supports the standard spawn interface:
new([env], command, [argv1, ...], [options])
The following options are supported in addition to the standard Process.spawn options:
:input => str Write str to the new process's standard input.
:timeout => int Maximum number of seconds to allow the process
to execute before aborting with a TimeoutExceeded
exception.
:max => total Maximum number of bytes of output to allow the
process to generate before aborting with a
MaximumOutputExceeded exception.
:pgroup_kill => bool Boolean specifying whether to kill the process
group (true) or individual process (false, default).
Setting this option true implies :pgroup => true.
Returns a new Command instance whose underlying process has already executed to completion. The out, err, and status attributes are immediately available.
74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 |
# File 'lib/progeny/command.rb', line 74 def initialize(*args) if args.last.is_a?(Hash) = args.pop.dup else = {} end if args.first.is_a?(Hash) @env = args.shift else @env = {} end @env.merge!(.delete(:env)) if .key?(:env) @argv = args @options = .dup @input = @options.delete(:input) @timeout = @options.delete(:timeout) @max = @options.delete(:max) if @options.delete(:pgroup_kill) @pgroup_kill = true @options[:pgroup] = true end @options.delete(:chdir) if @options[:chdir].nil? exec! if !@options.delete(:noexec) end |
Instance Attribute Details
#err ⇒ Object (readonly)
All data written to the child process’s stderr stream as a String.
164 165 166 |
# File 'lib/progeny/command.rb', line 164 def err @err end |
#input ⇒ Object (readonly)
All data written to the child process’s stdin stream as a String.
158 159 160 |
# File 'lib/progeny/command.rb', line 158 def input @input end |
#out ⇒ Object (readonly)
All data written to the child process’s stdout stream as a String.
161 162 163 |
# File 'lib/progeny/command.rb', line 161 def out @out end |
#pid ⇒ Object (readonly)
The pid of the spawned child process. This is unlikely to be a valid current pid since Command#exec! doesn’t return until the process finishes and is reaped.
175 176 177 |
# File 'lib/progeny/command.rb', line 175 def pid @pid end |
#runtime ⇒ Object (readonly)
Total command execution time (wall-clock time)
170 171 172 |
# File 'lib/progeny/command.rb', line 170 def runtime @runtime end |
#status ⇒ Object (readonly)
A Process::Status object with information on how the child exited.
167 168 169 |
# File 'lib/progeny/command.rb', line 167 def status @status end |
Class Method Details
.build(*args) ⇒ Object
Set up a new process to spawn, but do not actually spawn it.
Invoke this just like the normal constructor to set up a process to be run. Call ‘exec!` to actually run the child process, send the input, read the output, and wait for completion. Use this alternative way of constructing a Progency::Command if you want to read any partial output from the child process even after an exception.
child = Progency::Command.build(... arguments ...)
child.exec!
The arguments are the same as the regular constructor.
Returns a new Command instance but does not run the underlying process.
115 116 117 118 119 120 121 122 123 |
# File 'lib/progeny/command.rb', line 115 def self.build(*args) = if args.last.is_a?(Hash) args.pop.dup else {} end new(*(args + [{ :noexec => true }.merge()])) end |
.spawn_with_pipes(*argv) ⇒ Object
Spawn a child process with all standard IO streams piped in and out of the spawning process. Supports the standard ‘Process.spawn` interface.
Returns a [pid, stdin, stdout, stderr] tuple, where pid is the new process’s pid, stdin is a writeable IO object, and stdout / stderr are readable IO objects. The caller should take care to close all IO objects when finished and the child process’s status must be collected by a call to Process::waitpid or equivalent.
133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 |
# File 'lib/progeny/command.rb', line 133 def self.spawn_with_pipes(*argv) if argv.last.is_a?(Hash) opts = argv.pop.dup else opts = {} end ird, iwr = IO.pipe ord, owr = IO.pipe erd, ewr = IO.pipe opts = opts.merge( # redirect fds # close other sides :in => ird, iwr => :close, :out => owr, ord => :close, :err => ewr, erd => :close ) pid = spawn(*(argv + [opts])) [pid, iwr, ord, erd] ensure # we're in the parent, close child-side fds [ird, owr, ewr].each { |fd| fd.close if fd } end |
Instance Method Details
#exec! ⇒ Object
Execute command, write input, and read output. This is called immediately when a new instance of this object is created, or can be called explicitly when creating the Command via ‘build`.
185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 |
# File 'lib/progeny/command.rb', line 185 def exec! pid, stdin, stdout, stderr = self.class.spawn_with_pipes(@env, *@argv, @options) @pid = pid # async read from all streams into buffers read_and_write(@input, stdin, stdout, stderr, @timeout, @max) # wait for the termination of the process and return exit status @status = waitpid(pid) rescue Object [stdin, stdout, stderr].each { |fd| fd.close rescue nil } if @status.nil? if !@pgroup_kill ::Process.kill('TERM', pid) rescue nil else ::Process.kill('-TERM', pid) rescue nil end @status = waitpid(pid) rescue nil end raise ensure # let's be absolutely certain these are closed [stdin, stdout, stderr].each { |fd| fd.close rescue nil } end |
#success? ⇒ Boolean
Determine if the process did exit with a zero exit status.
178 179 180 |
# File 'lib/progeny/command.rb', line 178 def success? @status && @status.success? end |