Module: TTY::Command::ChildProcess

Defined in:
lib/tty/command/child_process.rb

Class Method Summary collapse

Class Method Details

.close_fds(*fds) ⇒ Object

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Close all streams



88
89
90
# File 'lib/tty/command/child_process.rb', line 88

def close_fds(*fds)
  fds.each { |fd| fd && !fd.closed? && fd.close }
end

.convert(spawn_key, spawn_value) ⇒ Object

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Convert option pari to recognized spawn option pair



131
132
133
134
135
136
137
138
139
140
141
142
143
144
# 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_to_fd(object) ⇒ Object

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Convert file name to file handle



191
192
193
194
195
196
197
198
199
200
201
202
203
# 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

.fd?(object) ⇒ Boolean

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Determine if object is a fd

Returns:

  • (Boolean)


152
153
154
155
156
157
158
159
160
161
162
# 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

.fd_to_process_key(object) ⇒ Object

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Convert fd to name :in, :out, :err



168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
# 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_redirect_options(options) ⇒ Hash

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Normalize spawn fd into :in, :out, :err keys.

Returns:

  • (Hash)


112
113
114
115
116
117
118
119
120
121
122
123
124
125
# 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

.spawn(cmd) ⇒ pid, ...

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.

Parameters:

  • cmd (Cmd)

    the command to spawn

Returns:

  • (pid, stdin, stdout, stderr)


23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
# 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(verbose = false) ⇒ Boolean

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Try loading pty module

Returns:

  • (Boolean)


98
99
100
101
102
103
104
# 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

.try_reading(object) ⇒ Object

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Attempts to read object content



209
210
211
212
213
214
215
216
217
# 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