Class: LLM::Command

Inherits:
Object
  • Object
show all
Defined in:
lib/llm/command.rb

Overview

Originally known as test-cmd.rb See https://github.com/altruby/test-cmd.rb

Defined Under Namespace

Classes: Pipe

IO collapse

Predicates collapse

Callbacks collapse

Instance Method Summary collapse

Constructor Details

#initialize(cmd, *argv) ⇒ LLM::Command

Parameters:

  • cmd (String) —

    A command to spawn

  • argv (Array<String>) —

    Zero or more command-line arguments



27
28
29
30
31
32
33
34
35
36
37
# File 'lib/llm/command.rb', line 27

def initialize(cmd, *argv)
  @cmd = cmd
  @argv = argv.dup
  @env = nil
  @status = nil
  @spawned = false
  @stdout = ""
  @stderr = ""
  @enoent = false
  @limit = {stdout: nil, stderr: nil}
end

Instance Method Details

#failure {|cmd| ... } ⇒ LLM::Command

Examples:

LLM::Command.new("ruby", "-e", "exit 1").failure do
  print "fail pid #{_1.pid}", "\n"
end

Yield Parameters:

Returns:



240
241
242
243
244
245
246
# File 'lib/llm/command.rb', line 240

def failure
  tap do
    spawn
    consume
    status.success? ? nil : yield(self)
  end
end

#arguments(*arguments) ⇒ LLM::Command Also known as: argv

Parameters:

  • arguments (Array<String, #to_s>) —

    Command-line arguments

Returns:



43
44
45
# File 'lib/llm/command.rb', line 43

def arguments(*arguments)
  tap { @argv.concat(arguments) }
end

#env(env) ⇒ LLM::Command

Parameters:

  • env (Hash{String => String}) —

    Environment variables to set for the spawned command

Returns:



52
53
54
# File 'lib/llm/command.rb', line 52

def env(env)
  tap { (@env ||= {}).merge!(env) }
end

#stdin(data) ⇒ LLM::Command

Presets the standard input that will be sent to the spawned process. Pass a String, or another LLM::Command whose standard output will be used as the standard input.

Examples:

cmd = LLM::Command.new("cat").stdin("hello world")
puts cmd.stdout

Parameters:

  • data (String, LLM::Command) —

    The standard input of the spawned process

Returns:



67
68
69
# File 'lib/llm/command.rb', line 67

def stdin(data)
  tap { @stdin = data }
end

#limit(stdout: nil, stderr: nil) ⇒ LLM::Command

Limits how many bytes of each stream are captured. A stream output beyond the limit is discarded, keeping memory bounded even for a command that streams a lot.

Parameters:

  • stdout (Integer, nil) (defaults to: nil) —

    Max bytes of stdout to read, or nil for unlimited

  • stderr (Integer, nil) (defaults to: nil) —

    Max bytes of stderr to read, or nil for unlimited

Returns:



80
81
82
# File 'lib/llm/command.rb', line 80

def limit(stdout: nil, stderr: nil)
  tap { @limit = {stdout:, stderr:} }
end

#spawn ⇒ LLM::Command

Spawns a command

Returns:



87
88
89
90
91
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
118
119
120
121
# File 'lib/llm/command.rb', line 87

def spawn
  return self if @spawned
  @in_r = input_pipe
  tap do
    @spawned = true
    @out, @err = Pipe.pair, Pipe.pair
    @pid = Process.spawn(
      envp,
      @cmd, *@argv.map(&:to_s),
      {in: @in_r, out: @out.w, err: @err.w}
    )
    @in_r.close unless @in_r.equal?(IO::NULL)
    @out.w.close
    @err.w.close
    @producer = Thread.new do
      @stdout = read_limited(@out.r, @limit[:stdout])
      @stderr = read_limited(@err.r, @limit[:stderr])
      @status = Process.waitpid2(@pid).last
    rescue Errno::ECHILD
      @status = failed_status
    ensure
      @out.r.close
      @err.r.close
    end
  end
rescue Errno::ENOENT => ex
  @stderr = ex.message
  @enoent = true
  ##
  # Close the read end of any input pipe so a pending
  # writer thread does not block forever.
  @in_r.close unless @in_r.equal?(IO::NULL)
  @status = failed_status
  self
end

#status ⇒ Process::Status

Returns the status of a process

Returns:

  • (Process::Status) —

    Returns the status of a process



126
127
128
129
130
# File 'lib/llm/command.rb', line 126

def status
  spawn
  consume
  @status
end

#pid ⇒ Integer

Returns the process ID of a spawned command

Returns:

  • (Integer) —

    Returns the process ID of a spawned command



135
136
137
# File 'lib/llm/command.rb', line 135

def pid
  status.pid
end

#exit_status ⇒ Integer Also known as: exitstatus

Returns the exit status of a process

Returns:

  • (Integer) —

    Returns the exit status of a process



142
143
144
# File 'lib/llm/command.rb', line 142

def exit_status
  status.exitstatus
end

#stdout ⇒ String

Returns the contents of stdout

Returns:

  • (String) —

    Returns the contents of stdout



153
154
155
156
157
# File 'lib/llm/command.rb', line 153

def stdout
  spawn
  consume
  @stdout
end

#stderr ⇒ String

Returns the contents of stderr

Returns:

  • (String) —

    Returns the contents of stderr



162
163
164
165
166
# File 'lib/llm/command.rb', line 162

def stderr
  spawn
  consume
  @stderr
end

#success? ⇒ Boolean

Returns true when a command exited successfully

Returns:

  • (Boolean) —

    Returns true when a command exited successfully



175
176
177
# File 'lib/llm/command.rb', line 175

def success?
  status.success?
end

#spawned? ⇒ Boolean

Returns true when a command has been spawned

Returns:

  • (Boolean) —

    Returns true when a command has been spawned



182
183
184
# File 'lib/llm/command.rb', line 182

def spawned?
  @spawned
end

#alive? ⇒ Boolean Also known as: running?

Returns true when a command is running

Returns:

  • (Boolean) —

    Returns true when a command is running



189
190
191
# File 'lib/llm/command.rb', line 189

def alive?
  @producer&.alive?
end

#kill!

This method returns an undefined value.

Sends SIGKILL to a running command



197
198
199
200
# File 'lib/llm/command.rb', line 197

def kill!
  return unless alive?
  Process.kill("SIGKILL", @pid)
end

#command_not_found? ⇒ Boolean Also known as: not_found?

Returns true when a command can't be found

Returns:

  • (Boolean) —

    Returns true when a command can't be found



205
206
207
208
209
# File 'lib/llm/command.rb', line 205

def command_not_found?
  spawn
  consume
  @enoent
end

#success {|cmd| ... } ⇒ LLM::Command

Examples:

LLM::Command.new("ruby", "-e", "exit 0").success do
  print "ok pid #{_1.pid}", "\n"
end

Yield Parameters:

Returns:



224
225
226
227
228
229
230
# File 'lib/llm/command.rb', line 224

def success
  tap do
    spawn
    consume
    status.success? ? yield(self) : nil
  end
end