From 3049c56bac9caf6042ea414e310928f92a0ded84 Mon Sep 17 00:00:00 2001 From: Schneems Date: Wed, 27 Nov 2024 13:35:30 -0600 Subject: [PATCH] Add ability to write to STDIN 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. --- lib/rundoc/code_command/background.rb | 1 + .../code_command/background/process_spawn.rb | 18 ++++++++- .../code_command/background/stdin_write.rb | 37 +++++++++++++++++++ test/rundoc/code_commands/background_test.rb | 36 ++++++++++++++++-- 4 files changed, 88 insertions(+), 4 deletions(-) create mode 100644 lib/rundoc/code_command/background/stdin_write.rb diff --git a/lib/rundoc/code_command/background.rb b/lib/rundoc/code_command/background.rb index f7a9122..3727831 100644 --- a/lib/rundoc/code_command/background.rb +++ b/lib/rundoc/code_command/background.rb @@ -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" diff --git a/lib/rundoc/code_command/background/process_spawn.rb b/lib/rundoc/code_command/background/process_spawn.rb index 8f8eeec..ad2f153 100644 --- a/lib/rundoc/code_command/background/process_spawn.rb +++ b/lib/rundoc/code_command/background/process_spawn.rb @@ -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 @@ -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 @@ -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 diff --git a/lib/rundoc/code_command/background/stdin_write.rb b/lib/rundoc/code_command/background/stdin_write.rb new file mode 100644 index 0000000..915ec42 --- /dev/null +++ b/lib/rundoc/code_command/background/stdin_write.rb @@ -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) diff --git a/test/rundoc/code_commands/background_test.rb b/test/rundoc/code_commands/background_test.rb index 6317876..9156d19 100644 --- a/test/rundoc/code_commands/background_test.rb +++ b/test/rundoc/code_commands/background_test.rb @@ -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", @@ -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", @@ -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