Skip to content

Commit

Permalink
handle more raw terminal scenarios
Browse files Browse the repository at this point in the history
  • Loading branch information
ddollar committed Aug 6, 2024
1 parent 9ee0820 commit 2a00775
Show file tree
Hide file tree
Showing 4 changed files with 48 additions and 23 deletions.
8 changes: 6 additions & 2 deletions lib/foreman/buffer.rb
Original file line number Diff line number Diff line change
@@ -1,16 +1,19 @@
ANSI_TOKEN = /\e\[(?:\??\d{1,4}(?:;\d{0,4})*)?[A-Za-z]/
ANSI_TOKEN = /\e\[(?:\??\d{1,4}(?:;\d{0,4})*)?[A-Za-z]|\e=|\e>/
NEWLINE_TOKEN = /\n/
TOKENIZER = Regexp.new("(#{ANSI_TOKEN}|#{NEWLINE_TOKEN})")

class Buffer
@buffer = ''

def initialize(initial = '')
@buffer = initial
@buffer = initial.dup
@fd = File.open("/tmp/buffer.#{initial}.log", "w+")
@fd.sync = true
end

def each_token
remainder = ''
@fd.puts @buffer.split(TOKENIZER).inspect
@buffer.split(TOKENIZER).each do |token|
if token.include?("\e") && !token.match(ANSI_TOKEN)
remainder << token
Expand All @@ -30,5 +33,6 @@ def gets

def write(data)
@buffer << data
@fd.puts "write: #{data.inspect}"
end
end
25 changes: 11 additions & 14 deletions lib/foreman/engine.rb
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ def initialize(options={})
@shutdown = false

# Self-pipe for deferred signal-handling (ala djb: http://cr.yp.to/docs/selfpipe.html)
reader, writer = create_pipe
reader, writer = self.class.create_pipe
reader.close_on_exec = true if reader.respond_to?(:close_on_exec)
writer.close_on_exec = true if writer.respond_to?(:close_on_exec)
@selfpipe = { :reader => reader, :writer => writer }
Expand Down Expand Up @@ -312,7 +312,7 @@ def shutdown

## Helpers ##########################################################

def create_pipe
def self.create_pipe
IO.method(:pipe).arity.zero? ? IO.pipe : IO.pipe("BINARY")
end

Expand Down Expand Up @@ -364,24 +364,21 @@ def termination_message_for(status)
def spawn_processes
@processes.each do |process|
1.upto(formation[@names[process]]) do |n|
reader, writer = process.interactive? ? PTY.open : create_pipe
begin
pid = process.run(
input: process.interactive? ? $stdin : :close,
output: writer,
env: {
'PORT' => port_for(process, n).to_s,
'PS' => name_for_index(process, n)
}
)
writer.puts "started with pid #{pid}"
# writer.puts "started with pid #{pid}"
rescue Errno::ENOENT
writer.puts "unknown command: #{process.command}"
# writer.puts "unknown command: #{process.command}"
end
@buffers[reader] = Buffer.new
@prefixed[reader] = false
@buffers[process.reader] = Buffer.new(@names[process])
@prefixed[process.reader] = false
@running[pid] = [process, n]
@readers[pid] = reader
@readers[pid] = process.reader
end
end
end
Expand Down Expand Up @@ -419,16 +416,18 @@ def handle_io_interactive(reader)
indent = prefix(name).gsub(ANSI_TOKEN, "").length

loop do
@buffers[reader].write(reader.read_nonblock(4096))
@buffers[reader].write(reader.read_nonblock(10))

@buffers[reader].each_token do |token|
case token
when /^\e\[(\d+)G$/
output_partial "\e[#{Regexp.last_match(1).to_i + indent}G"
when ANSI_TOKEN
output_partial token
when "\r"
output_partial "\e[#{indent+1}G"
when "\n"
output_partial token
output_partial "\r\n"
@prefixed[reader] = false
else
unless @prefixed[reader]
Expand All @@ -445,8 +444,6 @@ def handle_io_interactive(reader)
return if done
rescue EOFError
end
ensure
output_partial "\n"
end

def handle_io_noninteractive(reader)
Expand Down
4 changes: 3 additions & 1 deletion lib/foreman/engine/cli.rb
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
require "foreman/engine"
require "io/console"

class Foreman::Engine::CLI < Foreman::Engine

Expand Down Expand Up @@ -56,7 +57,7 @@ def startup
def output(name, data)
data.to_s.lines.map(&:chomp).each do |message|
$stdout.write prefix(name)
$stdout.puts message
$stdout.puts message + "\r"
$stdout.flush
end
rescue Errno::EPIPE
Expand All @@ -78,6 +79,7 @@ def prefix(name)
end

def shutdown
$stdin.cooked!
end

private
Expand Down
34 changes: 28 additions & 6 deletions lib/foreman/process.rb
Original file line number Diff line number Diff line change
@@ -1,10 +1,18 @@
require "foreman"
require "io/console"
require "shellwords"

class Foreman::Process

@noninteractive_stdin = $stdin

class << self
attr_accessor :noninteractive_stdin
end

attr_reader :command
attr_reader :env
attr_reader :reader

# Create a Process
#
Expand All @@ -19,6 +27,8 @@ def initialize(command, options={})
@options = options.dup

@options[:env] ||= {}

self.class.noninteractive_stdin = :close if options[:interactive]
end

# Get environment-expanded command for a +Process+
Expand All @@ -40,19 +50,31 @@ def expanded_command(custom_env={})
#
# @param [Hash] options
#
# @option options :env ({}) Environment variables to set for this execution
# @option options :output ($stdout) The output stream
# @option options :env ({}) Environment variables to set for this execution
#
# @returns [Fixnum] pid The +pid+ of the process
#
def run(options={})
env = @options[:env].merge(options[:env] || {})
input = options[:input] || $stdin
output = options[:output] || $stdout
runner = "#{Foreman.runner}".shellescape

Dir.chdir(cwd) do
Process.spawn env, expanded_command(env), :in => input, :out => output, :err => output
if interactive?
$stdin.raw!
@reader, tty = PTY.open
Thread.new do
loop do
data = $stdin.readpartial(4096)
if data.include?("\03")
Process.kill("INT", Process.pid)
data.gsub!("\03", "")
end
@reader.write(data)
end
end
Process.spawn env, expanded_command(env), chdir: cwd, in: tty, out: tty, err: tty
else
@reader, writer = Foreman::Engine::create_pipe
Process.spawn env, expanded_command(env), chdir: cwd, in: self.class.noninteractive_stdin, out: writer, err: writer
end
end

Expand Down

0 comments on commit 2a00775

Please sign in to comment.