Class: Expectr::Child

Inherits:
Object
  • Object
show all
Includes:
Interface
Defined in:
lib/expectr/child.rb,
lib/expectr/errstr.rb

Overview

Internal: The Expectr::Child class contains the interface to interacting with child processes.

All methods with the prefix ‘interface_’ in their name will return a Proc designed to be defined as an instance method in the primary Expectr object. These methods will all be documented as if they are the Proc in question.

Direct Known Subclasses

Adopt

Defined Under Namespace

Modules: Errstr

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Methods included from Interface

#init_instance, #interface_prepare_interact_interface, #interface_restore_environment

Constructor Details

#initialize(cmd) ⇒ Child

Public: Initialize a new Expectr::Child object. Spawns a sub-process and attaches to STDIN and STDOUT for the new process.

cmd - A String or File referencing the application to launch.

Raises TypeError if argument is anything other than String or File.



24
25
26
27
28
29
30
31
32
# File 'lib/expectr/child.rb', line 24

def initialize(cmd)
  cmd = cmd.path if cmd.kind_of?(File)
  unless cmd.kind_of?(String)
    raise(TypeError, Errstr::STRING_FILE_EXPECTED)
  end

  @stdout,@stdin,@pid = PTY.spawn(cmd)
  @stdout.winsize = $stdout.winsize if $stdout.tty?
end

Instance Attribute Details

#pidObject (readonly)

Returns the value of attribute pid.



15
16
17
# File 'lib/expectr/child.rb', line 15

def pid
  @pid
end

#stdinObject (readonly)

Returns the value of attribute stdin.



13
14
15
# File 'lib/expectr/child.rb', line 13

def stdin
  @stdin
end

#stdoutObject (readonly)

Returns the value of attribute stdout.



14
15
16
# File 'lib/expectr/child.rb', line 14

def stdout
  @stdout
end

Class Method Details

.spawn(cmd, args = {}) ⇒ Object

Public: Present a streamlined interface to create a new Expectr instance.

cmd - A String or File referencing the application to launch. args - A Hash used to specify options for the new object, per

Expectr#initialize.

Returns a new Expectr object



160
161
162
163
# File 'lib/expectr/child.rb', line 160

def self.spawn(cmd, args = {})
  args[:interface] = :child
  Expectr.new(cmd, args)
end

Instance Method Details

#interface_interact_threadObject

Public: Create a Thread containing the loop which is responsible for handling input from the user in interact mode.

Returns a Thread containing the running loop.



123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
# File 'lib/expectr/child.rb', line 123

def interface_interact_thread
  -> {
    @interact = true
    env = prepare_interact_environment
    Thread.new do
      begin
        input = ''

        while @pid > 0 && @interact
          if select([$stdin], nil, nil, 1)
            c = $stdin.getc.chr
            send c unless c.nil?
          end
        end
      ensure
        restore_environment(env)
      end
    end
  }
end

#interface_kill!Object

Public: Send a signal to the running child process.

signal - Symbol, String or FixNum corresponding to the symbol to be sent to the running process. (default: :TERM)

Returns a boolean indicating whether the process was successfully sent the signal. Raises ProcessError if the process is not running (@pid = 0).



42
43
44
45
46
47
48
49
# File 'lib/expectr/child.rb', line 42

def interface_kill!
  ->(signal = :TERM) {
    unless @pid > 0
      raise(ProcessError, Errstr::PROCESS_NOT_RUNNING)
    end
    Process::kill(signal.to_sym, @pid) == 1
  }
end

#interface_output_loopObject

Public: Read the child process’s output, force UTF-8 encoding, then append to the internal buffer and print to $stdout if appropriate.

Returns nothing.



74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
# File 'lib/expectr/child.rb', line 74

def interface_output_loop
  -> {
    while @pid > 0
      unless select([@stdout], nil, nil, @timeout).nil?
        buf = ''

        begin
          @stdout.sysread(@buffer_size, buf)
        rescue Errno::EIO #Application is not running
          @pid = 0
          return
        end
        process_output(buf)
      end
    end
  }
end

#interface_prepare_interact_environmentObject



92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
# File 'lib/expectr/child.rb', line 92

def interface_prepare_interact_environment
  -> {
    env = {sig: {}}

    # Save old tty settings and set up the new environment
    env[:tty] = `stty -g`
    `stty -icanon min 1 time 0 -echo`

    # SIGINT should be sent to the child as \C-c
    env[:sig]['INT'] = trap 'INT' do
      send "\C-c"
    end

    # SIGTSTP should be sent to the process as \C-z
    env[:sig]['TSTP'] = trap 'TSTP' do
      send "\C-z"
    end

    # SIGWINCH should trigger an update to the child processes window size
    env[:sig]['WINCH'] = trap 'WINCH' do
      @stdout.winsize = $stdout.winsize
    end

    env
  }
end

#interface_sendObject

Public: Send input to the active child process.

str - String to be sent.

Returns nothing. Raises Expectr::ProcessError if the process is not running (@pid = 0)



57
58
59
60
61
62
63
64
65
66
67
68
# File 'lib/expectr/child.rb', line 57

def interface_send
  ->(str) {
    begin
      @stdin.syswrite str
    rescue Errno::EIO #Application is not running
      @pid = 0
    end
    unless @pid > 0
      raise(Expectr::ProcessError, Errstr::PROCESS_GONE)
    end
  }
end

#interface_winsizeObject

Public: Return the PTY’s window size.

Returns a two-element Array (same as IO#winsize)



147
148
149
150
151
# File 'lib/expectr/child.rb', line 147

def interface_winsize
  -> {
    @stdout.winsize
  }
end