Skip to content

Commit 2a00775

Browse files
committed
handle more raw terminal scenarios
1 parent 9ee0820 commit 2a00775

File tree

4 files changed

+48
-23
lines changed

4 files changed

+48
-23
lines changed

lib/foreman/buffer.rb

+6-2
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,19 @@
1-
ANSI_TOKEN = /\e\[(?:\??\d{1,4}(?:;\d{0,4})*)?[A-Za-z]/
1+
ANSI_TOKEN = /\e\[(?:\??\d{1,4}(?:;\d{0,4})*)?[A-Za-z]|\e=|\e>/
22
NEWLINE_TOKEN = /\n/
33
TOKENIZER = Regexp.new("(#{ANSI_TOKEN}|#{NEWLINE_TOKEN})")
44

55
class Buffer
66
@buffer = ''
77

88
def initialize(initial = '')
9-
@buffer = initial
9+
@buffer = initial.dup
10+
@fd = File.open("/tmp/buffer.#{initial}.log", "w+")
11+
@fd.sync = true
1012
end
1113

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

3134
def write(data)
3235
@buffer << data
36+
@fd.puts "write: #{data.inspect}"
3337
end
3438
end

lib/foreman/engine.rb

+11-14
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ def initialize(options={})
4343
@shutdown = false
4444

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

313313
## Helpers ##########################################################
314314

315-
def create_pipe
315+
def self.create_pipe
316316
IO.method(:pipe).arity.zero? ? IO.pipe : IO.pipe("BINARY")
317317
end
318318

@@ -364,24 +364,21 @@ def termination_message_for(status)
364364
def spawn_processes
365365
@processes.each do |process|
366366
1.upto(formation[@names[process]]) do |n|
367-
reader, writer = process.interactive? ? PTY.open : create_pipe
368367
begin
369368
pid = process.run(
370-
input: process.interactive? ? $stdin : :close,
371-
output: writer,
372369
env: {
373370
'PORT' => port_for(process, n).to_s,
374371
'PS' => name_for_index(process, n)
375372
}
376373
)
377-
writer.puts "started with pid #{pid}"
374+
# writer.puts "started with pid #{pid}"
378375
rescue Errno::ENOENT
379-
writer.puts "unknown command: #{process.command}"
376+
# writer.puts "unknown command: #{process.command}"
380377
end
381-
@buffers[reader] = Buffer.new
382-
@prefixed[reader] = false
378+
@buffers[process.reader] = Buffer.new(@names[process])
379+
@prefixed[process.reader] = false
383380
@running[pid] = [process, n]
384-
@readers[pid] = reader
381+
@readers[pid] = process.reader
385382
end
386383
end
387384
end
@@ -419,16 +416,18 @@ def handle_io_interactive(reader)
419416
indent = prefix(name).gsub(ANSI_TOKEN, "").length
420417

421418
loop do
422-
@buffers[reader].write(reader.read_nonblock(4096))
419+
@buffers[reader].write(reader.read_nonblock(10))
423420

424421
@buffers[reader].each_token do |token|
425422
case token
426423
when /^\e\[(\d+)G$/
427424
output_partial "\e[#{Regexp.last_match(1).to_i + indent}G"
428425
when ANSI_TOKEN
429426
output_partial token
427+
when "\r"
428+
output_partial "\e[#{indent+1}G"
430429
when "\n"
431-
output_partial token
430+
output_partial "\r\n"
432431
@prefixed[reader] = false
433432
else
434433
unless @prefixed[reader]
@@ -445,8 +444,6 @@ def handle_io_interactive(reader)
445444
return if done
446445
rescue EOFError
447446
end
448-
ensure
449-
output_partial "\n"
450447
end
451448

452449
def handle_io_noninteractive(reader)

lib/foreman/engine/cli.rb

+3-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
require "foreman/engine"
2+
require "io/console"
23

34
class Foreman::Engine::CLI < Foreman::Engine
45

@@ -56,7 +57,7 @@ def startup
5657
def output(name, data)
5758
data.to_s.lines.map(&:chomp).each do |message|
5859
$stdout.write prefix(name)
59-
$stdout.puts message
60+
$stdout.puts message + "\r"
6061
$stdout.flush
6162
end
6263
rescue Errno::EPIPE
@@ -78,6 +79,7 @@ def prefix(name)
7879
end
7980

8081
def shutdown
82+
$stdin.cooked!
8183
end
8284

8385
private

lib/foreman/process.rb

+28-6
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,18 @@
11
require "foreman"
2+
require "io/console"
23
require "shellwords"
34

45
class Foreman::Process
56

7+
@noninteractive_stdin = $stdin
8+
9+
class << self
10+
attr_accessor :noninteractive_stdin
11+
end
12+
613
attr_reader :command
714
attr_reader :env
15+
attr_reader :reader
816

917
# Create a Process
1018
#
@@ -19,6 +27,8 @@ def initialize(command, options={})
1927
@options = options.dup
2028

2129
@options[:env] ||= {}
30+
31+
self.class.noninteractive_stdin = :close if options[:interactive]
2232
end
2333

2434
# Get environment-expanded command for a +Process+
@@ -40,19 +50,31 @@ def expanded_command(custom_env={})
4050
#
4151
# @param [Hash] options
4252
#
43-
# @option options :env ({}) Environment variables to set for this execution
44-
# @option options :output ($stdout) The output stream
53+
# @option options :env ({}) Environment variables to set for this execution
4554
#
4655
# @returns [Fixnum] pid The +pid+ of the process
4756
#
4857
def run(options={})
4958
env = @options[:env].merge(options[:env] || {})
50-
input = options[:input] || $stdin
51-
output = options[:output] || $stdout
5259
runner = "#{Foreman.runner}".shellescape
5360

54-
Dir.chdir(cwd) do
55-
Process.spawn env, expanded_command(env), :in => input, :out => output, :err => output
61+
if interactive?
62+
$stdin.raw!
63+
@reader, tty = PTY.open
64+
Thread.new do
65+
loop do
66+
data = $stdin.readpartial(4096)
67+
if data.include?("\03")
68+
Process.kill("INT", Process.pid)
69+
data.gsub!("\03", "")
70+
end
71+
@reader.write(data)
72+
end
73+
end
74+
Process.spawn env, expanded_command(env), chdir: cwd, in: tty, out: tty, err: tty
75+
else
76+
@reader, writer = Foreman::Engine::create_pipe
77+
Process.spawn env, expanded_command(env), chdir: cwd, in: self.class.noninteractive_stdin, out: writer, err: writer
5678
end
5779
end
5880

0 commit comments

Comments
 (0)