module TTY::Command::ChildProcess
Public Class Methods
Close all streams @api private
# File lib/tty/command/child_process.rb, line 88 def close_fds(*fds) fds.each { |fd| fd && !fd.closed? && fd.close } end
Convert option pari to recognized spawn option pair
@api private
# File lib/tty/command/child_process.rb, line 131 def convert(spawn_key, spawn_value) key = fd_to_process_key(spawn_key) value = spawn_value if key.to_s == "in" value = convert_to_fd(spawn_value) end if fd?(spawn_value) value = fd_to_process_key(spawn_value) value = [:child, value] # redirect in child process end [key, value] end
Convert file name to file handle
@api private
# File lib/tty/command/child_process.rb, line 191 def convert_to_fd(object) return object if fd?(object) if object.is_a?(::String) && ::File.exist?(object) return object end tmp = ::Tempfile.new(::SecureRandom.uuid.split("-")[0]) content = try_reading(object) tmp.write(content) tmp.rewind tmp end
Determine if object is a fd
@return [Boolean]
@api private
# File lib/tty/command/child_process.rb, line 152 def fd?(object) case object when :stdin, :stdout, :stderr, :in, :out, :err, STDIN, STDOUT, STDERR, $stdin, $stdout, $stderr, ::IO true when ::Integer object >= 0 else respond_to?(:to_i) && !object.to_io.nil? end end
Convert fd to name :in, :out, :err
@api private
# File lib/tty/command/child_process.rb, line 168 def fd_to_process_key(object) case object when STDIN, $stdin, :in, :stdin, 0 :in when STDOUT, $stdout, :out, :stdout, 1 :out when STDERR, $stderr, :err, :stderr, 2 :err when Integer object >= 0 ? IO.for_fd(object) : nil when IO object when respond_to?(:to_io) object.to_io else raise ExecuteError, "Wrong execute redirect: #{object.inspect}" end end
Normalize spawn fd into :in, :out, :err keys.
@return [Hash]
@api private
# File lib/tty/command/child_process.rb, line 112 def normalize_redirect_options(options) options.reduce({}) do |opts, (key, value)| if fd?(key) spawn_key, spawn_value = convert(key, value) opts[spawn_key] = spawn_value elsif key.is_a?(Array) && key.all?(&method(:fd?)) key.each do |k| spawn_key, spawn_value = convert(k, value) opts[spawn_key] = spawn_value end end opts end end
Execute command in a child process with all IO streams piped in and out. The interface is similar to Process.spawn
The caller should ensure that all IO objects are closed when the child process is finished. However, when block is provided this will be taken care of automatically.
@param [Cmd] cmd
the command to spawn
@return [pid, stdin, stdout, stderr]
@api public
# File lib/tty/command/child_process.rb, line 23 def spawn(cmd) process_opts = normalize_redirect_options(cmd.options) binmode = cmd.options[:binmode] || false pty = cmd.options[:pty] || false verbose = cmd.options[:verbose] pty = try_loading_pty(verbose) if pty require("pty") if pty # load within this scope # Create pipes in_rd, in_wr = pty ? PTY.open : IO.pipe("utf-8") # reading out_rd, out_wr = pty ? PTY.open : IO.pipe("utf-8") # writing err_rd, err_wr = pty ? PTY.open : IO.pipe("utf-8") # error in_wr.sync = true if binmode in_wr.binmode out_rd.binmode err_rd.binmode end if pty in_wr.raw! out_wr.raw! err_wr.raw! end # redirect fds opts = { in: in_rd, out: out_wr, err: err_wr } unless TTY::Command.windows? close_child_fds = { in_wr => :close, out_rd => :close, err_rd => :close } opts.merge!(close_child_fds) end opts.merge!(process_opts) pid = Process.spawn(cmd.to_command, opts) # close streams in parent process talking to the child close_fds(in_rd, out_wr, err_wr) tuple = [pid, in_wr, out_rd, err_rd] if block_given? begin return yield(*tuple) ensure # ensure parent pipes are closed close_fds(in_wr, out_rd, err_rd) end else tuple end end
Try loading pty module
@return [Boolean]
@api private
# File lib/tty/command/child_process.rb, line 98 def try_loading_pty(verbose = false) require 'pty' true rescue LoadError warn("Requested PTY device but the system doesn't support it.") if verbose false end
Attempts to read object content
@api private
# File lib/tty/command/child_process.rb, line 209 def try_reading(object) if object.respond_to?(:read) object.read elsif object.respond_to?(:to_s) object.to_s else object end end
Private Instance Methods
Close all streams @api private
# File lib/tty/command/child_process.rb, line 88 def close_fds(*fds) fds.each { |fd| fd && !fd.closed? && fd.close } end
Convert option pari to recognized spawn option pair
@api private
# File lib/tty/command/child_process.rb, line 131 def convert(spawn_key, spawn_value) key = fd_to_process_key(spawn_key) value = spawn_value if key.to_s == "in" value = convert_to_fd(spawn_value) end if fd?(spawn_value) value = fd_to_process_key(spawn_value) value = [:child, value] # redirect in child process end [key, value] end
Convert file name to file handle
@api private
# File lib/tty/command/child_process.rb, line 191 def convert_to_fd(object) return object if fd?(object) if object.is_a?(::String) && ::File.exist?(object) return object end tmp = ::Tempfile.new(::SecureRandom.uuid.split("-")[0]) content = try_reading(object) tmp.write(content) tmp.rewind tmp end
Determine if object is a fd
@return [Boolean]
@api private
# File lib/tty/command/child_process.rb, line 152 def fd?(object) case object when :stdin, :stdout, :stderr, :in, :out, :err, STDIN, STDOUT, STDERR, $stdin, $stdout, $stderr, ::IO true when ::Integer object >= 0 else respond_to?(:to_i) && !object.to_io.nil? end end
Convert fd to name :in, :out, :err
@api private
# File lib/tty/command/child_process.rb, line 168 def fd_to_process_key(object) case object when STDIN, $stdin, :in, :stdin, 0 :in when STDOUT, $stdout, :out, :stdout, 1 :out when STDERR, $stderr, :err, :stderr, 2 :err when Integer object >= 0 ? IO.for_fd(object) : nil when IO object when respond_to?(:to_io) object.to_io else raise ExecuteError, "Wrong execute redirect: #{object.inspect}" end end
Normalize spawn fd into :in, :out, :err keys.
@return [Hash]
@api private
# File lib/tty/command/child_process.rb, line 112 def normalize_redirect_options(options) options.reduce({}) do |opts, (key, value)| if fd?(key) spawn_key, spawn_value = convert(key, value) opts[spawn_key] = spawn_value elsif key.is_a?(Array) && key.all?(&method(:fd?)) key.each do |k| spawn_key, spawn_value = convert(k, value) opts[spawn_key] = spawn_value end end opts end end
Execute command in a child process with all IO streams piped in and out. The interface is similar to Process.spawn
The caller should ensure that all IO objects are closed when the child process is finished. However, when block is provided this will be taken care of automatically.
@param [Cmd] cmd
the command to spawn
@return [pid, stdin, stdout, stderr]
@api public
# File lib/tty/command/child_process.rb, line 23 def spawn(cmd) process_opts = normalize_redirect_options(cmd.options) binmode = cmd.options[:binmode] || false pty = cmd.options[:pty] || false verbose = cmd.options[:verbose] pty = try_loading_pty(verbose) if pty require("pty") if pty # load within this scope # Create pipes in_rd, in_wr = pty ? PTY.open : IO.pipe("utf-8") # reading out_rd, out_wr = pty ? PTY.open : IO.pipe("utf-8") # writing err_rd, err_wr = pty ? PTY.open : IO.pipe("utf-8") # error in_wr.sync = true if binmode in_wr.binmode out_rd.binmode err_rd.binmode end if pty in_wr.raw! out_wr.raw! err_wr.raw! end # redirect fds opts = { in: in_rd, out: out_wr, err: err_wr } unless TTY::Command.windows? close_child_fds = { in_wr => :close, out_rd => :close, err_rd => :close } opts.merge!(close_child_fds) end opts.merge!(process_opts) pid = Process.spawn(cmd.to_command, opts) # close streams in parent process talking to the child close_fds(in_rd, out_wr, err_wr) tuple = [pid, in_wr, out_rd, err_rd] if block_given? begin return yield(*tuple) ensure # ensure parent pipes are closed close_fds(in_wr, out_rd, err_rd) end else tuple end end
Try loading pty module
@return [Boolean]
@api private
# File lib/tty/command/child_process.rb, line 98 def try_loading_pty(verbose = false) require 'pty' true rescue LoadError warn("Requested PTY device but the system doesn't support it.") if verbose false end
Attempts to read object content
@api private
# File lib/tty/command/child_process.rb, line 209 def try_reading(object) if object.respond_to?(:read) object.read elsif object.respond_to?(:to_s) object.to_s else object end end