class Rerun::Runner
Public Class Methods
keep_running(cmd, options)
click to toggle source
# File lib/rerun/runner.rb, line 7 def self.keep_running(cmd, options) runner = new(cmd, options) runner.start runner.join # apparently runner doesn't keep running anymore (as of Listen 2) so we have to sleep forever :-( sleep 10000 while true # :-( end
new(run_command, options = {})
click to toggle source
# File lib/rerun/runner.rb, line 18 def initialize(run_command, options = {}) @run_command, @options = run_command, options @run_command = "ruby #{@run_command}" if @run_command.split(' ').first =~ /\.rb$/ end
Public Instance Methods
app_name()
click to toggle source
# File lib/rerun/runner.rb, line 125 def app_name @options[:name] end
change_message(changes)
click to toggle source
# File lib/rerun/runner.rb, line 221 def change_message(changes) message = [:modified, :added, :removed].map do |change| count = changes[change] ? changes[change].size : 0 if count > 0 "#{count} #{change}" end end.compact.join(", ") changed_files = changes.values.flatten if changed_files.count > 0 message += ": " message += changes.values.flatten[0..3].map {|path| path.split('/').last}.join(', ') if changed_files.count > 3 message += ", ..." end end message end
clear?()
click to toggle source
# File lib/rerun/runner.rb, line 109 def clear? @options[:clear] end
clear_screen()
click to toggle source
# File lib/rerun/runner.rb, line 370 def clear_screen # see http://ascii-table.com/ansi-escape-sequences-vt-100.php $stdout.print "\033[H\033[2J" end
die()
click to toggle source
# File lib/rerun/runner.rb, line 240 def die #stop_keypress_thread # don't do this since we're probably *in* the keypress thread stop # stop the child process if it exists exit 0 # todo: status code param end
dir()
click to toggle source
# File lib/rerun/runner.rb, line 93 def dir @options[:dir] end
dirs()
click to toggle source
# File lib/rerun/runner.rb, line 97 def dirs @options[:dir] || "." end
exit?()
click to toggle source
# File lib/rerun/runner.rb, line 121 def exit? @options[:exit] end
force_polling()
click to toggle source
# File lib/rerun/runner.rb, line 136 def force_polling @options[:force_polling] end
git_head_changed?()
click to toggle source
# File lib/rerun/runner.rb, line 311 def git_head_changed? old_git_head = @git_head read_git_head @git_head and old_git_head and @git_head != old_git_head end
ignore()
click to toggle source
# File lib/rerun/runner.rb, line 105 def ignore @options[:ignore] || [] end
join()
click to toggle source
# File lib/rerun/runner.rb, line 246 def join @watcher.join end
key_pressed()
click to toggle source
non-blocking stdin reader. returns a 1-char string if a key was pressed; otherwise nil
# File lib/rerun/runner.rb, line 339 def key_pressed begin # this "raw input" nonsense is because unix likes waiting for linefeeds before sending stdin # 'raw' means turn raw input on # restore proper output newline handling -- see stty.rb and "man stty" and /usr/include/sys/termios.h # looks like "raw" flips off the OPOST bit 0x00000001 /* enable following output processing */ # which disables #define ONLCR 0x00000002 /* map NL to CR-NL (ala CRMOD) */ # so this sets it back on again since all we care about is raw input, not raw output stty "raw opost" c = nil if $stdin.ready? c = $stdin.getc end c.chr if c ensure stty "-raw" # turn raw input off end # note: according to 'man tty' the proper way restore the settings is # tty_state=`stty -g` # ensure # system 'stty "#{tty_state}' # end # but this way seems fine and less confusing end
notify(title, body, background = true)
click to toggle source
# File lib/rerun/runner.rb, line 322 def notify(title, body, background = true) Notification.new(title, body, @options).send(background) if @options[:notify] puts say "#{app_name} #{title}" end
pattern()
click to toggle source
# File lib/rerun/runner.rb, line 101 def pattern @options[:pattern] end
quiet?()
click to toggle source
# File lib/rerun/runner.rb, line 113 def quiet? @options[:quiet] end
read_git_head()
click to toggle source
# File lib/rerun/runner.rb, line 317 def read_git_head git_head_file = File.join(dir, '.git', 'HEAD') @git_head = File.exists?(git_head_file) && File.read(git_head_file) end
restart(with_signal = true)
click to toggle source
# File lib/rerun/runner.rb, line 66 def restart(with_signal = true) @restarting = true if @options[:restart] && with_signal restart_with_signal(@options[:signal]) else stop start end @restarting = false end
restart_with_signal(restart_signal)
click to toggle source
# File lib/rerun/runner.rb, line 129 def restart_with_signal(restart_signal) if @pid && (@pid != 0) notify "restarting", "We will be with you shortly." send_signal(restart_signal) end end
run(command)
click to toggle source
# File lib/rerun/runner.rb, line 217 def run command Kernel.spawn command end
running?()
click to toggle source
# File lib/rerun/runner.rb, line 250 def running? send_signal(0) end
say(msg)
click to toggle source
# File lib/rerun/runner.rb, line 328 def say msg puts "#{Time.now.strftime("%T")} [rerun] #{msg}" unless quiet? end
send_signal(signal)
click to toggle source
Send the signal to process @pid. @returns true if the signal is sent @returns false if sending the signal fails If sending the signal fails, the exception will be swallowed (and logged if verbose is true) and this method will return false.
# File lib/rerun/runner.rb, line 289 def send_signal(signal) say "Sending signal #{signal} to #{@pid}" unless signal == 0 if verbose? Process.kill(signal, @pid) true rescue => e say "Signal #{signal} failed: #{e.class}: #{e.message}" if verbose? false end
signal_and_wait(signal)
click to toggle source
Send the signal to process @pid and wait for it to die. @returns true if the process dies @returns false if either sending the signal fails or the process fails to die
# File lib/rerun/runner.rb, line 257 def signal_and_wait(signal) signal_sent = if windows? force_kill = (signal == 'KILL') system("taskkill /T #{'/F' if force_kill} /PID #{@pid}") else send_signal(signal) end if signal_sent # the signal was successfully sent, so wait for the process to die begin timeout(@options[:wait]) do Process.wait(@pid) end process_status = $? say "Process ended: #{process_status}" if verbose? true rescue Timeout::Error false end else false end end
start()
click to toggle source
# File lib/rerun/runner.rb, line 140 def start if @already_running taglines = [ "Here we go again!", "Keep on trucking.", "Once more unto the breach, dear friends, once more!", "The road goes ever on and on, down from the door where it began.", ] notify "restarted", taglines[rand(taglines.size)] else taglines = [ "To infinity... and beyond!", "Charge!", ] notify "launched", taglines[rand(taglines.size)] @already_running = true end clear_screen if clear? start_keypress_thread unless @keypress_thread begin @pid = run @run_command say "Rerun (#{$PID}) running #{app_name} (#{@pid})" rescue => e puts "#{e.class}: #{e.message}" exit end status_thread = Process.detach(@pid) # so if the child exits, it dies Signal.trap("INT") do # INT = control-C -- allows user to stop the top-level rerun process die end Signal.trap("TERM") do # TERM is the polite way of terminating a process die end begin sleep 2 rescue Interrupt => e # in case someone hits control-C immediately ("oops!") die end if exit? status = status_thread.value if status.success? notify "succeeded", "" else notify "failed", "Exit status #{status.exitstatus}" end else if !running? notify "Launch Failed", "See console for error output" @already_running = false end end unless @watcher watcher = Watcher.new(:directory => dirs, :pattern => pattern, :ignore => ignore, :force_polling => force_polling) do |changes| message = change_message(changes) say "Change detected: #{message}" restart unless @restarting end watcher.start @watcher = watcher say "Watching #{dir.join(', ')} for #{pattern}" + (ignore.empty? ? "" : " (ignoring #{ignore.join(',')})") + (watcher.adapter.nil? ? "" : " with #{watcher.adapter_name} adapter") end end
start_keypress_thread()
click to toggle source
# File lib/rerun/runner.rb, line 23 def start_keypress_thread return if @options[:background] @keypress_thread = Thread.new do while true if c = key_pressed case c.downcase when 'c' say "Clearing screen" clear_screen when 'r' say "Restarting" restart when 'f' say "Stopping and starting" restart(false) when 'p' toggle_pause when 'x', 'q' die break # the break will stop this thread, in case the 'die' doesn't else puts "\n#{c.inspect} pressed inside rerun" puts [["c", "clear screen"], ["r", "restart"], ["f", "forced restart (stop and start)"], ["p", "toggle pause"], ["x or q", "stop and exit"] ].map {|key, description| " #{key} -- #{description}"}.join("\n") puts end end sleep 1 # todo: use select instead of polling somehow? end end @keypress_thread.run end
stop()
click to toggle source
todo: test escalation
# File lib/rerun/runner.rb, line 299 def stop if @pid && (@pid != 0) notify "stopping", "All good things must come to an end." unless @restarting @options[:signal].split(',').each do |signal| success = signal_and_wait(signal) return true if success end end rescue => e false end
stop_keypress_thread()
click to toggle source
# File lib/rerun/runner.rb, line 61 def stop_keypress_thread @keypress_thread.kill if @keypress_thread @keypress_thread = nil end
stty(args)
click to toggle source
# File lib/rerun/runner.rb, line 332 def stty(args) system "stty #{args}" end
toggle_pause()
click to toggle source
# File lib/rerun/runner.rb, line 77 def toggle_pause unless @pausing say "Pausing. Press 'p' again to resume." @watcher.pause @pausing = true else say "Resuming." @watcher.unpause @pausing = false end end
unpause()
click to toggle source
# File lib/rerun/runner.rb, line 89 def unpause @watcher.unpause end
verbose?()
click to toggle source
# File lib/rerun/runner.rb, line 117 def verbose? @options[:verbose] end