#!/usr/bin/env ruby # lrep [-z] [-t SECS] [FILES...] - literate read-eval-print # -*- ruby -*- require 'pty' require 'io/console' require 'optparse' params = ARGV.getopts("t:z") zap = params["z"] $timeout = params["t"] ? params["t"].to_f : 0.04 # TODO keep each session keyed by ps1? output = input = pid = nil trap(:SIGCHLD) { input = output = nil } def readp(output, ps, timeout=$timeout) return "[DEAD]" if output == nil o = ps ? output.readpartial(1024) : "" # p [:READ, o] timedout = true loop { if o.split("\r\n").last =~ ps timedout = false break end if IO.select([output],[],[],timeout) begin x=output.readpartial(1024) # p [:READ, x] o << x rescue Errno::EIO # eof break end else break end } o = "[TIMEOUT]\r\n" + o if timedout && ps l = o.split("\r\n") if ps && l[-1] l[-1].gsub!(ps, "") l.pop if l[-1].empty? end o = l.join("\n") o end def ind(txt) txt.split("\n").map { |l| "\t#{l}\n" }.join end ps = nil while line = gets if line =~ /^\t!(.*?)(?:!(.*?))?!(.*)/ puts line begin if $1.empty? && $2.empty? output = input = pid = nil next if zap print ind(`#{$3}`) else if $1.empty? ps = nil psl = /^\t.*#{Regexp.quote $2}(.*)/ else ps = /.*?#{Regexp.union [$1,$2].compact}$/ psl = /^\t.*?#{Regexp.union [$1,$2].compact}(.*)/ end next if zap cmd = $3 silent = cmd.gsub!(/>-$/, '') output, input, pid = PTY.spawn cmd input.echo = false # more timeout to launch program o = readp(output, ps, $timeout*20) print ind(o) unless silent end rescue print ind("ERROR: #{$!.message}") end elsif line =~ psl puts line next if zap if input cmd = $1 silent = cmd.gsub!(/>-$/, '') # p [:WRITE, cmd + "\n"] input.write cmd + "\n" o = readp(output, ps) print ind(o) unless silent end elsif line =~ /^\t/ # old output, remove else puts line end end if pid n = 0 until Process.waitpid(pid, Process::WNOHANG) Process.kill(n > 10 ? "KILL" : "TERM", pid) sleep 0.1 n += 1 end end