Class: Ace::Demo::Molecules::AsciinemaExecutor

Inherits:
Object
  • Object
show all
Defined in:
lib/ace/demo/molecules/asciinema_executor.rb

Constant Summary collapse

INSTALL_URL =
"https://docs.asciinema.org/getting-started/"

Instance Method Summary collapse

Constructor Details

#initialize(open3: Open3, sleeper: Kernel, pty: PTY) ⇒ AsciinemaExecutor

Returns a new instance of AsciinemaExecutor.



13
14
15
16
17
# File 'lib/ace/demo/molecules/asciinema_executor.rb', line 13

def initialize(open3: Open3, sleeper: Kernel, pty: PTY)
  @open3 = open3
  @sleeper = sleeper
  @pty = pty
end

Instance Method Details

#asciinema_available?(asciinema_bin: "asciinema") ⇒ Boolean

Returns:

  • (Boolean)


19
20
21
22
23
24
# File 'lib/ace/demo/molecules/asciinema_executor.rb', line 19

def asciinema_available?(asciinema_bin: "asciinema")
  _stdout, _stderr, status = @open3.capture3(asciinema_bin, "--version")
  status.success?
rescue Errno::ENOENT
  false
end

#run(cmd, asciinema_bin: "asciinema", chdir: nil) ⇒ Object



26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
# File 'lib/ace/demo/molecules/asciinema_executor.rb', line 26

def run(cmd, asciinema_bin: "asciinema", chdir: nil)
  effective_bin = cmd.first || asciinema_bin
  options = {}
  options[:chdir] = chdir if chdir
  stdout, stderr, status = @open3.capture3(*cmd, **options)
  result = Models::ExecutionResult.new(
    stdout: stdout.strip,
    stderr: stderr.strip,
    success: status.success?,
    exit_code: status.exitstatus
  )

  return result if result.success?

  raise AsciinemaExecutionError, "Asciinema execution failed: #{result.stderr}"
rescue Errno::ENOENT
  raise AsciinemaNotFoundError, "Asciinema not found (#{effective_bin}). Install: #{INSTALL_URL}"
end

#run_interactive(cmd, commands:, env: {}, asciinema_bin: "asciinema", chdir: nil) ⇒ Object



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
84
85
86
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
# File 'lib/ace/demo/molecules/asciinema_executor.rb', line 45

def run_interactive(cmd, commands:, env: {}, asciinema_bin: "asciinema", chdir: nil)
  effective_bin = cmd.first || asciinema_bin
  options = {}
  options[:chdir] = chdir if chdir

  stdout = +""
  read_io = nil
  write_io = nil
  pid = nil
  reader = nil
  buffer = +""
  buffer_mutex = Mutex.new
  buffer_cv = ConditionVariable.new

  begin
    read_io, write_io, pid = @pty.spawn(env, *cmd, **options)
    cols, rows = tty_size_from(cmd)
    if cols && rows && read_io.respond_to?(:winsize=)
      read_io.winsize = [rows, cols]
    end
    if cols && rows && write_io.respond_to?(:winsize=)
      write_io.winsize = [rows, cols]
    end
    reader = Thread.new do
      loop do
        chunk = read_io.readpartial(4096)
        buffer_mutex.synchronize do
          buffer << chunk
          buffer_cv.broadcast
        end
      end
    rescue EOFError, Errno::EIO
      buffer_mutex.synchronize { buffer.dup }
    end

    wait_for_prompt(buffer, buffer_mutex, buffer_cv)

    commands.each do |command|
      write_io.write("#{command.fetch(:command)}\n")
      write_io.flush
      @sleeper.sleep(command.fetch(:sleep))
    end

    write_io.write("exit\n")
    write_io.flush
    write_io.close

    _wait_pid, status = Process.wait2(pid)
    stdout = reader.value
  ensure
    read_io&.close unless read_io&.closed?
    write_io&.close unless write_io&.closed?
  end

  result = Models::ExecutionResult.new(
    stdout: stdout.strip,
    stderr: "",
    success: status.success?,
    exit_code: status.exitstatus
  )

  return result if result.success?

  raise AsciinemaExecutionError, "Asciinema execution failed: #{result.stderr}"
rescue Errno::ENOENT
  raise AsciinemaNotFoundError, "Asciinema not found (#{effective_bin}). Install: #{INSTALL_URL}"
end