Skip to content

Commit

Permalink
Add ability to write to STDIN
Browse files Browse the repository at this point in the history
Needs:

- Integration test
- README docs

Notably, the hard part about this might not be writing the values to stdin, but rather dealing with the fact that most interactive processes behave VERY differently when run without a TTY. There are workarounds such as using the `script` command line tool, however integrating with a TTY shall be an exercise for the reader.
  • Loading branch information
schneems committed Nov 27, 2024
1 parent 5a0c1c6 commit 3049c56
Show file tree
Hide file tree
Showing 4 changed files with 88 additions and 4 deletions.
1 change: 1 addition & 0 deletions lib/rundoc/code_command/background.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,4 @@ class Rundoc::CodeCommand::Background
require "rundoc/code_command/background/wait"
require "rundoc/code_command/background/log/clear"
require "rundoc/code_command/background/log/read"
require "rundoc/code_command/background/stdin_write"
18 changes: 17 additions & 1 deletion lib/rundoc/code_command/background/process_spawn.rb
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ def initialize(command, timeout: 5, log: Tempfile.new("log"), out: "2>&1")
@log = Pathname.new(log)
@log.dirname.mkpath
FileUtils.touch(@log)
@pipe_output, @pipe_input = IO.pipe

@command = "/usr/bin/env bash -c #{@command.shellescape} >> #{@log} #{out}"
@pid = nil
Expand All @@ -78,8 +79,23 @@ def alive?
false
end

def write(contents, timeout: timeout_value, wait: nil, ending: $/)
contents = contents + $/ if ending
begin
Timeout.timeout(Integer(timeout)) do
@pipe_input.print(contents)
@pipe_input.flush
end
rescue Timeout::Error
raise "Timeout (#{timeout}s) waiting to write #{contents} to stdin. Log contents:\n'#{log.read}'"
end
wait(wait, timeout)
contents
end

def stop
return unless alive?
@pipe_input.close
Process.kill("TERM", -Process.getpgid(@pid))
Process.wait(@pid)
end
Expand All @@ -89,7 +105,7 @@ def check_alive!
end

private def call
@pid ||= Process.spawn(@command, pgroup: true)
@pid ||= Process.spawn(@command, pgroup: true, in: @pipe_output)
end
end
end
37 changes: 37 additions & 0 deletions lib/rundoc/code_command/background/stdin_write.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
class Rundoc::CodeCommand::Background
# Will send contents to the background process via STDIN along with a newline
#
#
class StdinWrite < Rundoc::CodeCommand
def initialize(contents, name:, wait:, timeout: 5, ending: $/)
@contents = contents
@ending = ending
@spawn = Rundoc::CodeCommand::Background::ProcessSpawn.find(name)
@wait = wait
@timeout_value = Integer(timeout)
@contents_written = nil
end

# The command is rendered (`:::>-`) by the output of the `def call` method.
def to_md(env = {})
writecontents
end

# The contents produced by the command (`:::->`) are rendered by the `def to_md` method.
def call(env = {})
writecontents
output = @spawn.log.read
output
end

def writecontents
@contents_written ||= @spawn.write(
contents,
wait: @wait,
ending: @ending,
timeout: @timeout_value
)
end
end
end
Rundoc.register_code_command(:"background.writeln", Rundoc::CodeCommand::Background::Wait)
36 changes: 33 additions & 3 deletions test/rundoc/code_commands/background_test.rb
Original file line number Diff line number Diff line change
@@ -1,11 +1,41 @@
require "test_helper"

class BackgroundTest < Minitest::Test

def test_stdin_with_cat_echo
Dir.mktmpdir do |dir|
Dir.chdir(dir) do
background_start = Rundoc::CodeCommand::Background::Start.new("cat",
name: "cat",
)
output = background_start.call

output = Rundoc::CodeCommand::Background::StdinWrite.new(
"hello there",
name: "cat",
wait: "hello"
).call
assert_equal("hello there" + $/, output)

output = Rundoc::CodeCommand::Background::Log::Clear.new(
name: "cat"
).call

output = Rundoc::CodeCommand::Background::StdinWrite.new(
"general kenobi",
name: "cat",
wait: "general"
).call
assert_equal("general kenobi" + $/, output)
end
end
end

def test_process_spawn_gc
Dir.mktmpdir do |dir|
Dir.chdir(dir) do
file = "foo.txt"
`echo 'foo' >> #{file}`
run!("echo 'foo' >> #{file}")

background_start = Rundoc::CodeCommand::Background::Start.new("tail -f #{file}",
name: "tail2",
Expand All @@ -30,7 +60,7 @@ def test_background_start
Dir.mktmpdir do |dir|
Dir.chdir(dir) do
file = "foo.txt"
`echo 'foo' >> #{file}`
run!("echo 'foo' >> #{file}")

background_start = Rundoc::CodeCommand::Background::Start.new("tail -f #{file}",
name: "tail",
Expand All @@ -49,7 +79,7 @@ def test_background_start
output = log_clear.call
assert_equal("", output)

`echo 'bar' >> #{file}`
run!("echo 'bar' >> #{file}")

log_read = Rundoc::CodeCommand::Background::Log::Read.new(name: "tail")
output = log_read.call
Expand Down

0 comments on commit 3049c56

Please sign in to comment.