pax_global_header00006660000000000000000000000064123317245560014522gustar00rootroot0000000000000052 comment=43bfadea9006da31ff965985ecbedea56fe05847 rerun-0.10.0/000077500000000000000000000000001233172455600127335ustar00rootroot00000000000000rerun-0.10.0/.gitignore000066400000000000000000000000371233172455600147230ustar00rootroot00000000000000.idea pkg Gemfile.lock doc tmp rerun-0.10.0/.rspec000066400000000000000000000000371233172455600140500ustar00rootroot00000000000000--color --format=documentation rerun-0.10.0/Gemfile000066400000000000000000000004561233172455600142330ustar00rootroot00000000000000source 'https://rubygems.org' gemspec # :ruby = Unix Rubies (OSX, Linux) # but rb-fsevent is OSX-only, so how to distinguish between OSX and Linux? platform :ruby do gem 'rb-fsevent', '>= 0.9.3' end group :development do gem "rake" end group :test do gem 'rspec' gem 'wrong', ">=0.6.2" end rerun-0.10.0/LICENSE000066400000000000000000000026551233172455600137500ustar00rootroot00000000000000Rerun Copyright (c) 2009 Alex Chaffee Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. --- rerun partially based on code from Rspactor Copyright (c) 2009 Mislav Marohnić License as above (MIT open source). rerun partially based on code from FileSystemWatcher http://paulhorman.com/filesystemwatcher/ No license provided; assumed public domain. rerun partially based on code from Shotgun Copyright (c) 2009 Ryan Tomayko License as above (MIT open source). rerun-0.10.0/README.md000066400000000000000000000310271233172455600142150ustar00rootroot00000000000000# Rerun Rerun launches your program, then watches the filesystem. If a relevant file changes, then it restarts your program. Rerun works for both long-running processes (e.g. apps) and short-running ones (e.g. tests). It's basically a no-frills command-line alternative to Guard, Shotgun, Autotest, etc. that doesn't require config files and works on any command, not just Ruby programs. Rerun's advantage is its simple design. Since it uses `exec` and the standard Unix `SIGINT` and `SIGKILL` signals, you're sure the restarted app is really acting just like it was when you ran it from the command line the first time. By default only `*.{rb,js,css,coffee,scss,sass,erb,html,haml,ru,slim,md}` files are watched. Use the `--pattern` option if you want to change this. As of version 0.7.0, we use the Listen gem, which tries to use your OS's built-in facilities for monitoring the filesystem, so CPU use is very light. Rerun does not work on Windows. Sorry, but you can't do much relaunching without "fork". # Installation: gem install rerun ("sudo" may be required on older systems, but try it without sudo first.) If you are using RVM you might want to put this in your global gemset so it's available to all your apps. (There really should be a better way to distinguish gems-as-libraries from gems-as-tools.) rvm @global do gem install rerun The Listen gem looks for certain platform-dependent gems, and will complain if they're not available. Unfortunately, Rubygems doesn't understand optional dependencies very well, so you may have to install extra gems (and/or put them in your Gemfile) to make Rerun work more smoothly on your system. (Learn more at .) For example, on Mac OS X, use gem install rb-fsevent # Usage: rerun [options] [--] cmd For example, if you're running a Sinatra app whose main file is `app.rb`: rerun ruby app.rb If the first part of the command is a `.rb` filename, then `ruby` is optional, so the above can also be accomplished like this: rerun app.rb Rails doesn't automatically notice all config file changes, so you can force it to restart when you change a config file like this: rerun --dir config rails s Or if you're using Thin to run a Rack app that's configured in config.ru but you want it on port 4000 and in debug mode, and only want to watch the `app` and `web` subdirectories: rerun --dir app,web -- thin start --debug --port=4000 -R config.ru The `--` is to separate rerun options from cmd options. You can also use a quoted string for the command, e.g. rerun --dir app "thin start --debug --port=4000 -R config.ru" Rackup can also be used to launch a Rack server, so let's try that: rerun -- rackup --port 4000 config.ru Want to mimic [autotest](https://github.com/grosser/autotest)? Try rerun -x rake or rerun -cx rspec And if you're using [Spork](https://github.com/sporkrb/spork) with Rails, you need to [restart your spork server](https://github.com/sporkrb/spork/issues/201) whenever certain Rails environment files change, so why not put this in your Rakefile... desc "run spork (via rerun)" task :spork do sh "rerun --pattern '{Gemfile,Gemfile.lock,spec/spec_helper.rb,.rspec,spec/factories/**,config/environment.rb,config/environments/test.rb,config/initializers/*.rb,lib/**/*.rb}' -- spork" end and start using `rake spork` to launch your spork server? (If you're using Guard instead of Rerun, check out [guard-spork](https://github.com/guard/guard-spork) for a similar solution.) How about regenerating your HTML files after every change to your [Erector](http://erector.rubyforge.org) widgets? rerun -x erector --to-html my_site.rb Use Heroku Cedar? `rerun` is now compatible with `foreman`. Run all your Procfile processes locally and restart them all when necessary. rerun foreman start # Options: `--dir` directory (or directories) to watch (default = "."). Separate multiple paths with ',' and/or use multiple `-d` options. `--pattern` glob to match inside directory. This uses the Ruby Dir glob style -- see for details. By default it watches files ending in: `rb,js,css,coffee,scss,sass,erb,html,haml,ru,slim,md`. On top of this, it also ignores dotfiles, `.tmp` files, and some other files and directories (like `.git` and `log`). Run `rerun --help` to see the actual list. `--ignore pattern` file glob to ignore (can be set many times). To ignore a directory, you must append '/*' e.g. `--ignore 'coverage/*'`. *On top of --pattern and --ignore, we ignore any changes to files and dirs starting with a dot.* `--signal` (or -s) use specified signal (instead of the default SIGTERM) to terminate the previous process. This may be useful for forcing the respective process to terminate as quickly as possible. (`--signal KILL` is the equivalent of `kill -9`) `--clear` (or -c) clear the screen before each run `--exit` (or -x) expect the program to exit. With this option, rerun checks the return value; without it, rerun checks that the launched process is still running. `--background` (or -b) disable on-the-fly commands, allowing the process to be backgrounded `--no-growl` don't use growl `--name` set the app name (for display) Also `--version` and `--help`, naturally. # Growl Notifications If you have `growlnotify` available on the `PATH`, it sends notifications to growl in addition to the console. If you have growl but don't want rerun to use it, set the `--no-growl` option. Download [growlnotify here](http://growl.info/downloads.php#generaldownloads) now that Growl has moved to the App Store. # On-The-Fly Commands While the app is (re)running, you can make things happen by pressing keys: * **r** -- restart (as if a file had changed) * **c** -- clear the screen * **x** or **q** -- exit (just like control-C) * **p** -- pause/unpause filesystem watching If you're backgrounding or using Pry or a debugger, you might not want these keys to be trapped, so use the `--background` option. # Signals The current algorithm for killing the process is: * send [SIGTERM](http://en.wikipedia.org/wiki/SIGTERM) (or the value of the `--signal` option) * if that doesn't work after 4 seconds, send SIGINT (aka control-C) * if that doesn't work after 2 more seconds, send SIGKILL (aka kill -9) This seems like the most gentle and unixy way of doing things, but it does mean that if your program ignores SIGTERM, it takes an extra 4 to 6 seconds to restart. # To Do: ## Must have for v1.0 * ".rerun" file to specify options per project or in $HOME. * Make sure to pass through quoted options correctly to target process [bug] * Optionally do "bundle install" before and "bundle exec" during launch ## Nice to have * Smarter --signal option (specifying signal to try and a timeout to wait, repeated) * If the last element of the command is a `.ru` file and there's no other command then use `rackup` * Figure out an algorithm so "-x" is not needed (if possible) -- maybe by accepting a "--port" option or reading `config.ru` * Specify (or deduce) port to listen for to determine success of a web server launch ## Wacky Ideas * Make it work on Windows, like Guard now does. See * https://github.com/guard/guard/issues/59 * https://github.com/guard/guard/issues/27 * On OS X: * use a C library using growl's developer API * Use growl's AppleScript or SDK instead of relying on growlnotify * Use OS X notifications * "Failed" icon for notifications * On Linux: * Test on Linux. * Use libnotify or notify-send http://www.linuxjournal.com/content/tech-tip-get-notifications-your-scripts-notify-send # Other projects that do similar things * Restartomatic: * Shotgun: * Rack::Reloader middleware: * The Sinatra FAQ has a discussion at * Kicker: * Watchr: * Guard: * Autotest: # Why would I use this instead of Shotgun? Shotgun does a "fork" after the web framework has loaded but before your application is loaded. It then loads your app, processes a single request in the child process, then exits the child process. Rerun launches the whole app, then when it's time to restart, uses "kill" to shut it down and starts the whole thing up again from scratch. So rerun takes somewhat longer than Shotgun to restart the app, but does it much less frequently. And once it's running it behaves more normally and consistently with your production app. Also, Shotgun reloads the app on every request, even if it doesn't need to. This is fine if you're loading a single file, but if your web pages all load other files (CSS, JS, media) then that adds up quickly. (I can only assume that the developers of shotgun are using caching or a front web server so this isn't a pain point for them.) And hey, does Shotgun reload your Worker processes if you're using Foreman and a Procfile? I'm pretty sure it doesn't. YMMV! # Why would I use this instead of Rack::Reloader? Rack::Reloader is certifiably beautiful code, and is a very elegant use of Rack's middleware architecture. But because it relies on the LOADED_FEATURES variable, it only reloads .rb files that were 'require'd, not 'load'ed. That leaves out (non-Erector) template files, and also, at least the way I was doing it, sub-actions (see [this thread](http://groups.google.com/group/sinatrarb/browse_thread/thread/7329727a9296e96a# )). Rack::Reloader also doesn't reload configuration changes or redo other things that happen during app startup. Rerun takes the attitude that if you want to restart an app, you should just restart the whole app. You know? # Why would I use this instead of Guard? Guard is very powerful but requires some up-front configuration. Rerun is meant as a no-frills command-line alternative requiring no knowledge of Ruby nor config file syntax. # Why did you write this? I've been using [Sinatra](http://sinatrarb.com) and loving it. In order to simplify their system, the Rat Pack removed auto-reloading from Sinatra proper. I approve of this: a web application framework should be focused on serving requests, not on munging Ruby ObjectSpace for dev-time convenience. But I still wanted automatic reloading during development. Shotgun wasn't working for me (see above) so I spliced Rerun together out of code from Rspactor, FileSystemWatcher, and Shotgun -- with a heavy amount of refactoring and rewriting. In late 2012 I migrated the backend to the Listen gem, which was extracted from Guard, so it should be more reliable and performant on multiple platforms. # Credits Rerun: [Alex Chaffee](http://alexchaffee.com), , Based upon and/or inspired by: * Shotgun: * Rspactor: * (In turn based on http://rails.aizatto.com/2007/11/28/taming-the-autotest-beast-with-fsevents/ ) * FileSystemWatcher: ## Patches by: * David Billskog * Jens B * Andrés Botero * Dreamcat4 * * Barry Sia * Paul Rangel # Version History * v0.10.0 4 May 2014 * add '.coffee,.slim,.md' to default pattern (thanks @xylinq) * --ignore option * v0.9.0 6 March 2014 * --dir (or -d) can be specified more than once, for multiple directories (thanks again Barry!) * --name option * press 'p' to pause/unpause filesystem watching (Barry is the man!) * works with Listen 2 (note: needs 2.3 or higher) * cooldown works, thanks to patches to underlying Listen gem * ignore all dotfiles, and add actual list of ignored dirs and files * v0.8.2 * bugfix, forcing Rerun to use Listen v1.0.3 while we work out the troubles we're having with Listen 1.3 and 2.1 * v0.8.1 * bugfix release (#30 and #34) * v0.8.0 * --background option (thanks FND!) to disable the keyboard listener * --signal option (thanks FND!) * --no-growl option * --dir supports multiple directories (thanks Barry!) * v0.7.1 * bugfix: make rails icon work again * v0.7.0 * uses Listen gem (which uses rb-fsevent for lightweight filesystem snooping) # License Open Source MIT License. See "LICENSE" file. rerun-0.10.0/Rakefile000066400000000000000000000033011233172455600143750ustar00rootroot00000000000000require 'rake' require 'rake/clean' require 'rake/testtask' require 'rspec/core/rake_task' task :default => [:spec] task :test => :spec desc "Run all specs" RSpec::Core::RakeTask.new('spec') do |t| ENV['ENV'] = "test" t.pattern = 'spec/**/*_spec.rb' t.rspec_opts = ['--color'] end $rubyforge_project = 'pivotalrb' $spec = begin require 'rubygems/specification' data = File.read('rerun.gemspec') spec = nil #Thread.new { spec = eval("$SAFE = 3\n#{data}") }.join spec = eval data spec end def package(ext='') "pkg/#{$spec.name}-#{$spec.version}" + ext end desc 'Exit if git is dirty' task :check_git do state = `git status 2> /dev/null | tail -n1` clean = (state =~ /working directory clean/) unless clean warn "can't do that on an unclean git dir" exit 1 end end desc 'Build packages' task :package => %w[.gem .tar.gz].map { |e| package(e) } desc 'Build and install as local gem' task :install => package('.gem') do sh "gem install #{package('.gem')}" end directory 'pkg/' CLOBBER.include('pkg') file package('.gem') => %W[pkg/ #{$spec.name}.gemspec] + $spec.files do |f| sh "gem build #{$spec.name}.gemspec" mv File.basename(f.name), f.name end file package('.tar.gz') => %w[pkg/] + $spec.files do |f| cmd = <<-SH git archive \ --prefix=#{$spec.name}-#{$spec.version}/ \ --format=tar \ HEAD | gzip > #{f.name} SH sh cmd.gsub(/ +/, ' ') end desc 'Publish gem and tarball to rubyforge' task 'release' => [:check_git, package('.gem'), package('.tar.gz')] do |t| puts "Releasing #{$spec.version}" sh "gem push #{package('.gem')}" puts "Tagging and pushing" sh "git tag v#{$spec.version}" sh "git push && git push --tags" end rerun-0.10.0/bin/000077500000000000000000000000001233172455600135035ustar00rootroot00000000000000rerun-0.10.0/bin/rerun000077500000000000000000000005031233172455600145620ustar00rootroot00000000000000#!/usr/bin/env ruby require 'rubygems' libdir = "#{File.expand_path(File.dirname(File.dirname(__FILE__)))}/lib" $LOAD_PATH.unshift libdir unless $LOAD_PATH.include?(libdir) require 'rerun' require 'optparse' options = Rerun::Options.parse exit if options.nil? runner = Rerun::Runner.keep_running(options[:cmd], options) rerun-0.10.0/geminstaller.yml000066400000000000000000000000561233172455600161450ustar00rootroot00000000000000--- gems: - name: rspec version: '>= 1.2.6' rerun-0.10.0/icons/000077500000000000000000000000001233172455600140465ustar00rootroot00000000000000rerun-0.10.0/icons/rails_grn_sml.png000066400000000000000000000034141233172455600174110ustar00rootroot00000000000000PNG  IHDR2@o IsBITOPLTEջʭù⼢㶠ߩְ֚ܢՍrՌ{rg{so^spgk_ZjwUc]ROWZZGJ^=PDpiN8K?P4NNK.d]OEB*M)F dXL@aFG'L6? @*A#Q pHYs  ~tEXtSoftwareMacromedia Fireworks 8hxIDATHic<z (j27*r$ݵ<$L&YBnC<@l,uI5< 9Xyy8~Ϯq Ȓ_]96!Jyw<2]&d@7fD[ T9IqΛznV8Ȁ . yGLȈn Js…qp_|XdBTͅ#c֓wTtF$+ 'D-90A$qHH#Nʛˉw֎;mPչԇjmе=Byp[ Əl>or" I]iggsab(JbN0\n½B&+M33 I:&3f!74?JRs G|!  [` 4Ɠ 3TW5IjEאIAD@̱(I-iV+(=HG1D`d,r3M jHC"<k1$30yFʖAI !57W^,0#A&3WeC=]Ap!@c+4wHz~ߘĒ+Obabe I)ԫC`tca*}I㫐I L,K4Z*lڴ|wpԧwX(,rkVIw,ړuCk"A5~erwĿPq[rv CkX=%eyO,vB~8+ FgFaТ U<]2ϓ߈q}6&B'}gF4Ӑ'# ɯ߷"3{oD0#:3NEexLw:Df>do1~q#F}8xt:Ћ*<_lj|+4CȃV-X]l(1>މ L>:v`3Cވ2+st/Kِ|~y@ F ȧ>}r-1Ipi"?9>nD7P:+g7$8(_w&m ۭb\2eE8|%/QPe/߷Ȳ"(M Jz %:ݧǬ_ީ_ f"ҵ˘}V宴{W!#" !_)<0d{p8Nb|Yɶ1'AC/Pú00%,±eiPKae" /pIDATxڔ[cJ `xEEDET`=If@ypw-_33(@Pt]UU]7?U'_.I* T CՕaqe?+ٶ$ _L@y3d-5ϛOW$W ?,M.G%oA@R<$a$;Ncܜß~+"6d(0$ !!%մbWV ]ysD Zl\}X#!1`eO+ M xCKN)> iM(AR8f?{gNqKp6/eF JK4Yz :sS&l\ >(Ӂ/G$̜N4ey!@L39Mgx !II@ Af 94*dO^#gapdYgE@$|>'KHj/ @Kis(=XHn6Daqqs} LjA!< GE πX< #nK+A9mLr̒/mؑCAH3$%fK[=Im:3ϛIY40H7YnAS[s 7>Y<Ӱs]MB^} JvX5邏Ɗ!͙QQ7o,Q9I_!|K M;HsY(JN[nF|W,@~=?y]엯,"+>>_H(-YA[M~~X=%.O!F E`V@(E!8]rA߈\{4+'Q|#&iHCJύ +:Sgtf,ɎM۩f yI@;]v$r#Q[i3^T|,աNLCDjl($띄Wg1NG"Vt.;t!+@`ZmHU>>JC.D6f C \W}L⭛P;dD6Fsr^BT'xbםA^b<47aEY ~ I/׋(aHs a-\+lIH$c5[Ixp*DbC>;_Dڀ˚hH^?^QOxt$vHIC٧'krH ;N?=@]1r{h黆,^8i;dC!! BR$$'][k$r M.6N4#RwEK~u b6És`EuX柒N$£EwyR+FυxDx [[_%BKj(;WTO,#yM+\c$[m_؉p!SIv+pH M 3eIENDB`rerun-0.10.0/inc.rb000066400000000000000000000007741233172455600140410ustar00rootroot00000000000000STDOUT.sync = true Signal.trap("TERM") do say "exiting" exit end def say msg puts "\t[inc] #{Time.now.strftime("%T")} #{$$} #{msg}" end class Inc def initialize file @file = file end def run launched = Time.now.to_i say "launching" i = 0 while i < 100 say "writing #{launched}/#{i} to #{@file}" File.open(@file, "w") do |f| f.puts(launched) f.puts(i) end sleep 0.5 i+=1 end end end Inc.new(ARGV[0] || "./inc.txt").run rerun-0.10.0/lib/000077500000000000000000000000001233172455600135015ustar00rootroot00000000000000rerun-0.10.0/lib/rerun.rb000066400000000000000000000004071233172455600151620ustar00rootroot00000000000000here = File.expand_path(File.dirname(__FILE__)) $: << here unless $:.include?(here) require "listen" # pull in the Listen gem require "rerun/options" require "rerun/system" require "rerun/runner" require "rerun/watcher" require "rerun/glob" module Rerun end rerun-0.10.0/lib/rerun/000077500000000000000000000000001233172455600146345ustar00rootroot00000000000000rerun-0.10.0/lib/rerun/glob.rb000066400000000000000000000033011233172455600161010ustar00rootroot00000000000000# based on http://cpan.uwinnipeg.ca/htdocs/Text-Glob/Text/Glob.pm.html#glob_to_regex_string- # todo: release as separate gem # module Rerun class Glob NO_LEADING_DOT = '(?=[^\.])' # todo START_OF_FILENAME = '(\A|\/)' # beginning of string or a slash END_OF_STRING = '\z' def initialize glob_string @glob_string = glob_string end def to_regexp_string chars = @glob_string.split('') chars = smoosh(chars) curlies = 0 escaping = false string = chars.map do |char| if escaping escaping = false char else case char when '**' "([^/]+/)*" when '*' ".*" when "?" "." when "." "\\." when "{" curlies += 1 "(" when "}" if curlies > 0 curlies -= 1 ")" else char end when "," if curlies > 0 "|" else char end when "\\" escaping = true "\\" else char end end end.join START_OF_FILENAME + string + END_OF_STRING end def to_regexp Regexp.new(to_regexp_string) end def smoosh chars out = [] until chars.empty? char = chars.shift if char == "*" and chars.first == "*" chars.shift chars.shift if chars.first == "/" out.push("**") else out.push(char) end end out end end end rerun-0.10.0/lib/rerun/options.rb000066400000000000000000000063341233172455600166620ustar00rootroot00000000000000require 'optparse' require 'pathname' require 'rerun/watcher' libdir = "#{File.expand_path(File.dirname(File.dirname(__FILE__)))}" $spec = Gem::Specification.load(File.join(libdir, "..", "rerun.gemspec")) module Rerun class Options DEFAULT_PATTERN = "**/*.{rb,js,coffee,css,scss,sass,erb,html,haml,ru,yml,slim,md}" DEFAULT_DIRS = ["."] DEFAULTS = { :pattern => DEFAULT_PATTERN, :signal => "TERM", :growl => true, :name => Pathname.getwd.basename.to_s.capitalize, :ignore => [] } def self.parse args = ARGV options = DEFAULTS.dup opts = OptionParser.new("", 24, ' ') do |opts| opts.banner = "Usage: rerun [options] [--] cmd" opts.separator "" opts.separator "Launches an app, and restarts it when the filesystem changes." opts.separator "See http://github.com/alexch/rerun for more info." opts.separator "Version: #{$spec.version}" opts.separator "" opts.separator "Options:" opts.on("-d dir", "--dir dir", "directory to watch, default = \"#{DEFAULT_DIRS}\". Specify multiple paths with ',' or separate '-d dir' option pairs.") do |dir| elements = dir.split(",") options[:dir] = (options[:dir] || []) + elements end # todo: rename to "--watch" opts.on("-p pattern", "--pattern pattern", "file glob to watch, default = \"#{DEFAULTS[:pattern]}\"") do |pattern| options[:pattern] = pattern end opts.on("-i pattern", "--ignore pattern", "file glob to ignore (can be set many times). To ignore a directory, you must append '/*' e.g. --ignore 'coverage/*'") do |pattern| options[:ignore] += [pattern] end opts.on("-s signal", "--signal signal", "terminate process using this signal, default = \"#{DEFAULTS[:signal]}\"") do |signal| options[:signal] = signal end opts.on("-c", "--clear", "clear screen before each run") do options[:clear] = true end opts.on("-x", "--exit", "expect the program to exit. With this option, rerun checks the return value; without it, rerun checks that the process is running.") do |dir| options[:exit] = true end opts.on("-b", "--background", "disable on-the-fly commands, allowing the process to be backgrounded") do options[:background] = true end opts.on("-n name", "--name name", "name of app used in logs and notifications, default = \"#{DEFAULTS[:name]}\"") do |name| options[:name] = name end opts.on("--no-growl", "don't use growl") do options[:growl] = false end opts.on_tail("-h", "--help", "--usage", "show this message") do puts opts return end opts.on_tail("--version", "show version") do puts $spec.version return end opts.on_tail "" opts.on_tail "On top of --pattern and --ignore, we ignore any changes to files and dirs starting with a dot." end if args.empty? puts opts nil else opts.parse! args options[:cmd] = args.join(" ") options[:dir] ||= DEFAULT_DIRS options end end end end rerun-0.10.0/lib/rerun/runner.rb000066400000000000000000000174051233172455600165010ustar00rootroot00000000000000require 'timeout' require 'io/wait' module Rerun class Runner 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 include System def initialize(run_command, options = {}) @run_command, @options = run_command, options @run_command = "ruby #{@run_command}" if @run_command.split(' ').first =~ /\.rb$/ end 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 'p' toggle_pause if watcher_running? 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"], ["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 def stop_keypress_thread @keypress_thread.kill if @keypress_thread @keypress_thread = nil end def restart @restarting = true stop start @restarting = false end def watcher_running? @watcher && @watcher.running? end 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 def unpause @watcher.unpause end def dir @options[:dir] end def dirs @options[:dir] || "." end def pattern @options[:pattern] end def ignore @options[:ignore] || [] end def clear? @options[:clear] end def exit? @options[:exit] end def app_name @options[:name] end def start if windows? raise "Sorry, Rerun does not work on Windows." end if (!@already_running) taglines = [ "To infinity... and beyond!", "Charge!", ] notify "launched", taglines[rand(taglines.size)] @already_running = true else 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)] end clear_screen if clear? start_keypress_thread unless @keypress_thread @pid = Kernel.fork do begin exec(@run_command) rescue => e puts "#{e.class}: #{e.message}" exit end 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) do |changes| message = [:modified, :added, :removed].map do |change| count = changes[change].size if count and count > 0 "#{count} #{change}" end end.compact.join(", ") say "Change detected: #{message}" restart unless @restarting end watcher.start @watcher = watcher say "Watching #{dir.join(', ')} for #{pattern}" + (ignore.empty? ? "" : " (ignoring #{ignore.join(',')})") + " using #{watcher.adapter.class.name.split('::').last} adapter" end end 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 def join @watcher.join end def running? signal(0) end def signal(signal) say "Sending signal #{signal} to #{@pid}" unless signal == 0 Process.kill(signal, @pid) true rescue false end # todo: test escalation def stop default_signal = @options[:signal] || "TERM" if @pid && (@pid != 0) notify "stopping", "All good things must come to an end." unless @restarting begin timeout(5) do # todo: escalation timeout setting # start with a polite SIGTERM signal(default_signal) && Process.wait(@pid) end rescue Timeout::Error begin timeout(5) do # escalate to SIGINT aka control-C since some foolish process may be ignoring SIGTERM signal("INT") && Process.wait(@pid) end rescue Timeout::Error # escalate to SIGKILL aka "kill -9" which cannot be ignored signal("KILL") && Process.wait(@pid) end end end rescue => e false end 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 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 def notify(title, body) growl title, body if @options[:growl] puts say "#{app_name} #{title}" end def say msg puts "#{Time.now.strftime("%T")} [rerun] #{msg}" end # non-blocking stdin reader. # returns a 1-char string if a key was pressed; otherwise nil # 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 system("stty raw opost") c = nil if $stdin.ready? c = $stdin.getc end c.chr if c ensure system "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 def clear_screen # see http://ascii-table.com/ansi-escape-sequences-vt-100.php $stdout.print "\033[H\033[2J" end end end rerun-0.10.0/lib/rerun/system.rb000066400000000000000000000017661233172455600165170ustar00rootroot00000000000000module Rerun module System def mac? RUBY_PLATFORM =~ /darwin/i end def windows? RUBY_PLATFORM =~ /mswin/i end def linux? RUBY_PLATFORM =~ /linux/i end # do we have growl or not? def growl_available? mac? && (growlcmd != "") end def growlcmd growlnotify = `which growlnotify`.chomp # todo: check version of growlnotify and warn if it's too old growlnotify end def icon here = File.expand_path(File.dirname(__FILE__)) icondir = File.expand_path("#{here}/../../icons") rails_sig_file = File.expand_path(".")+"/config/boot.rb" "#{icondir}/rails_red_sml.png" if File.exists? rails_sig_file end def growl(title, body, background = true) if growl_available? icon_str = ("--image \"#{icon}\"" if icon) s = "#{growlcmd} -n \"#{app_name}\" -m \"#{body}\" \"#{app_name} #{title}\" #{icon_str}" s += " &" if background `#{s}` end end end end rerun-0.10.0/lib/rerun/watcher.rb000066400000000000000000000064561233172455600166310ustar00rootroot00000000000000require 'listen' Thread.abort_on_exception = true # This class will watch a directory and alert you of # new files, modified files, deleted files. # # Now uses the Listen gem, but spawns its own thread on top. # We should probably be accessing the Listen thread directly. # # Author: Alex Chaffee # module Rerun class Watcher InvalidDirectoryError = Class.new(RuntimeError) #def self.default_ignore # Listen::Silencer.new(Listen::Listener.new).send :_default_ignore_patterns #end attr_reader :directory, :pattern, :priority # Create a file system watcher. Start it by calling #start. # # @param options[:directory] the directory to watch (default ".") # @param options[:pattern] the glob pattern to search under the watched directory (default "**/*") # @param options[:priority] the priority of the watcher thread (default 0) # def initialize(options = {}, &client_callback) @client_callback = client_callback options = { :directory => ".", :pattern => "**/*", :priority => 0, }.merge(options) @pattern = options[:pattern] @directories = options[:directory] @directories = sanitize_dirs(@directories) @priority = options[:priority] @ignore = [options[:ignore]].flatten.compact @thread = nil end def sanitize_dirs(dirs) dirs = [*dirs] dirs.map do |d| d.chomp!("/") unless FileTest.exists?(d) && FileTest.readable?(d) && FileTest.directory?(d) raise InvalidDirectoryError, "Directory '#{d}' either doesnt exist or isn't readable" end File.expand_path(d) end end def start if @thread then raise RuntimeError, "already started" end @thread = Thread.new do @listener = Listen.to(*@directories, only: watching, ignore: ignoring, wait_for_delay: 1) do |modified, added, removed| if((modified.size + added.size + removed.size) > 0) @client_callback.call(:modified => modified, :added => added, :removed => removed) end end @listener.start end @thread.priority = @priority sleep 0.1 until @listener at_exit { stop } # try really hard to clean up after ourselves end def watching Rerun::Glob.new(@pattern).to_regexp end def ignoring # todo: --no-ignore-dotfiles dotfiles = /^\.[^.]/ # at beginning of string, a real dot followed by any other character [dotfiles] + @ignore.map { |x| Rerun::Glob.new(x).to_regexp } end def adapter @listener.registry[:adapter] || (timeout(4) do sleep 1 until adapter = @listener.registry[:adapter] adapter end) end # kill the file watcher thread def stop @thread.wakeup rescue ThreadError begin @listener.stop rescue Exception => e puts "#{e.class}: #{e.message} stopping listener" end @thread.kill rescue ThreadError end # wait for the file watcher to finish def join @thread.join if @thread rescue Interrupt => e # don't care end def pause @listener.pause if @listener end def unpause @listener.unpause if @listener end def running? @listener && @listener.instance_variable_get(:@adapter) end end end rerun-0.10.0/rerun.gemspec000066400000000000000000000020671233172455600154400ustar00rootroot00000000000000$spec = Gem::Specification.new do |s| s.specification_version = 2 if s.respond_to? :specification_version= s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version= s.name = 'rerun' s.version = '0.10.0' s.description = "Restarts your app when a file changes. A no-frills, command-line alternative to Guard, Shotgun, Autotest, etc." s.summary = "Launches an app, and restarts it whenever the filesystem changes. A no-frills, command-line alternative to Guard, Shotgun, Autotest, etc." s.authors = ["Alex Chaffee"] s.email = "alex@stinky.com" s.files = %w[ README.md LICENSE Rakefile rerun.gemspec bin/rerun icons/rails_grn_sml.png icons/rails_red_sml.png] + Dir['lib/**/*.rb'] s.executables = ['rerun'] s.test_files = s.files.select {|path| path =~ /^spec\/.*_spec.rb/} s.extra_rdoc_files = %w[README.md] s.add_runtime_dependency 'listen', '~> 2.7', '>= 2.7.3' s.homepage = "http://github.com/alexch/rerun/" s.require_paths = %w[lib] s.license = 'MIT' end rerun-0.10.0/spec/000077500000000000000000000000001233172455600136655ustar00rootroot00000000000000rerun-0.10.0/spec/functional_spec.rb000066400000000000000000000053401233172455600173700ustar00rootroot00000000000000here = File.expand_path(File.dirname(__FILE__)) require "#{here}/spec_helper.rb" require 'tmpdir' describe "the rerun command" do before do STDOUT.sync = true @dir = Dir.tmpdir + "/#{Time.now.to_i}" FileUtils.mkdir_p(@dir) @inc_output_file = "#{@dir}/inc.txt" @dir1 = File.join(@dir, "dir1") FileUtils.mkdir_p(@dir1) # one file that exists in dir1 @watched_file1 = File.join(@dir1, "foo.rb") touch @watched_file1 # one file that doesn't yet exist in dir1 @watched_file2 = File.join(@dir1, "bar.rb") @dir2 = File.join(@dir, "dir2") FileUtils.mkdir_p(@dir2) # one file that exists in dir2 @watched_file3 = File.join(@dir2, "baz.rb") launch_inc end after do timeout(4) { Process.kill("INT", @pid) && Process.wait(@pid) rescue Errno::ESRCH } end def launch_inc @pid = fork do root = File.dirname(__FILE__) + "/.." exec("#{root}/bin/rerun -d '#{@dir1},#{@dir2}' ruby #{root}/inc.rb #{@inc_output_file}") end timeout(10) { sleep 0.5 until File.exist?(@inc_output_file) } sleep 4 # let rerun's watcher get going end def read File.open(@inc_output_file, "r") do |f| launched_at = f.gets.to_i count = f.gets.to_i result = [launched_at, count] puts "reading #{@inc_output_file}: #{result.join("\t")}" result end end def current_count launched_at, count = read count end def touch(file = @watched_file1) puts "#{Time.now.strftime("%T")} touching #{file}" File.open(file, "w") do |f| f.puts Time.now end end def type char # todo: send a character to stdin of the rerun process end it "increments a test file at least once per second" do sleep 1 x = current_count sleep 1 y = current_count y.should be > x end it "restarts its target when an app file is modified" do first_launched_at, count = read touch @watched_file1 sleep 4 second_launched_at, count = read second_launched_at.should be > first_launched_at end it "restarts its target when an app file is created" do first_launched_at, count = read touch @watched_file2 sleep 4 second_launched_at, count = read second_launched_at.should be > first_launched_at end it "restarts its target when an app file is created in the second dir" do first_launched_at, count = read touch @watched_file3 sleep 4 second_launched_at, count = read second_launched_at.should be > first_launched_at end #it "sends its child process a SIGINT to restart" it "dies when sent a control-C (SIGINT)" do Process.kill("INT", @pid) timeout(6) { Process.wait(@pid) rescue Errno::ESRCH } end #it "accepts a key press" end rerun-0.10.0/spec/glob_spec.rb000066400000000000000000000054101233172455600161470ustar00rootroot00000000000000here = File.expand_path(File.dirname(__FILE__)) require "#{here}/spec_helper.rb" require "rerun/glob" module Rerun describe Glob do describe "#to_regexp" do it "makes a regexp" do Glob.new("foo*").to_regexp.should == /#{Glob::START_OF_FILENAME}foo.*#{Glob::END_OF_STRING}/ end end describe "#to_regexp_string" do { "x" => "x", "*" => ".*", "foo*" => "foo.*", "*foo" => ".*foo", "*foo*" => ".*foo.*", "?" => ".", "." => "\\.", "{foo,bar,baz}" => "(foo|bar|baz)", "{.txt,.md}" => '(\.txt|\.md)', # pass through slash-escapes verbatim "\\x" => "\\x", "\\." => "\\.", "\\*" => "\\*", "\\\\" => "\\\\", "**/*.txt" => "([^/]+/)*.*\\.txt", }.each_pair do |glob_string, regexp_string| specify glob_string do Glob.new(glob_string).to_regexp_string.should == Glob::START_OF_FILENAME + regexp_string + Glob::END_OF_STRING end end end describe "specifically" do { "*.txt" => { :hits=> [ "foo.txt", "foo/bar.txt", "/foo/bar.txt", "bar.baz.txt", "/foo/bar.baz.txt", ], :misses => [ "foo.txt.html", "tmp/foo.txt.html", "/tmp/foo.txt.html", #"tmp/.foo.txt", ] }, "tmp/foo.*" => { :hits => [ "tmp/foo.txt", ], :misses => [ "stmp/foo.txt", "tmp/foofoo.txt", ] } }.each_pair do |glob, paths| paths[:hits].each do |path| specify "#{glob} matches #{path}" do Glob.new(glob).to_regexp.should =~ path end end paths[:misses].each do |path| specify "#{glob} doesn't match #{path}" do Glob.new(glob).to_regexp.should_not =~ path end end end end describe "#smoosh" do def check_smoosh string, array glob = Glob.new("") glob.smoosh(string.split('')).should == array end it "ignores non-stars" do check_smoosh "", [] check_smoosh "abc", ["a", "b", "c"] end it "passes solitary stars" do check_smoosh "*", ["*"] check_smoosh "a*b", ["a", "*", "b"] end it "smooshes two stars in a row into a single '**' string" do check_smoosh "**", ["**"] check_smoosh "a**b", ["a", "**", "b"] check_smoosh "**b", ["**", "b"] check_smoosh "a**", ["a", "**"] end it "treats **/ like **" do check_smoosh "**/", ["**"] check_smoosh "a**/b", ["a", "**", "b"] end end end end rerun-0.10.0/spec/options_spec.rb000066400000000000000000000045241233172455600167240ustar00rootroot00000000000000here = File.expand_path(File.dirname(__FILE__)) require "#{here}/spec_helper.rb" require "rerun/options" module Rerun describe Options do it "has good defaults" do defaults = Options.parse ["foo"] assert { defaults[:cmd] = "foo" } assert { defaults[:dir] == ["."] } assert { defaults[:pattern] == Options::DEFAULT_PATTERN } assert { defaults[:signal] == "TERM" } assert { defaults[:growl] == true } assert { defaults[:name] == 'Rerun' } assert { defaults[:clear].nil? } assert { defaults[:exit].nil? } assert { defaults[:background].nil? } end ["--help", "-h", "--usage", "--version", "-v"].each do |arg| describe "when passed #{arg}" do it "returns nil" do capturing do Options.parse([arg]).should be_nil end end end end it "accepts --no-growl" do options = Options.parse ["--no-growl", "foo"] assert { options[:growl] == false } end it "splits directories" do options = Options.parse ["--dir", "a,b", "foo"] assert { options[:dir] == ["a", "b"] } end it "adds directories specified individually with --dir" do options = Options.parse ["--dir", "a", "--dir", "b"] assert { options[:dir] == ["a", "b"] } end it "adds directories specified individually with -d" do options = Options.parse ["-d", "a", "-d", "b"] assert { options[:dir] == ["a", "b"] } end it "adds directories specified individually using mixed -d and --dir" do options = Options.parse ["-d", "a", "--dir", "b"] assert { options[:dir] == ["a", "b"] } end it "adds individual directories and splits comma-separated ones" do options = Options.parse ["--dir", "a", "--dir", "b", "--dir", "foo,other"] assert { options[:dir] == ["a", "b", "foo", "other"] } end it "accepts --name for a custom application name" do options = Options.parse ["--name", "scheduler"] assert { options[:name] == "scheduler" } end it "accepts --ignore" do options = Options.parse ["--ignore", "log/*"] assert { options[:ignore] == ["log/*"] } end it "accepts --ignore multiple times" do options = Options.parse ["--ignore", "log/*", "--ignore", "*.tmp"] assert { options[:ignore] == ["log/*", "*.tmp"] } end end end rerun-0.10.0/spec/runner_spec.rb000066400000000000000000000020571233172455600165410ustar00rootroot00000000000000here = File.expand_path(File.dirname(__FILE__)) require "#{here}/spec_helper.rb" require 'rerun' module Rerun describe Runner do class Runner attr_reader :run_command end describe "initialization and configuration" do it "accepts a command" do runner = Runner.new("foo") runner.run_command.should == "foo" end it "If the command is a .rb file, then run it with ruby" do runner = Runner.new("foo.rb") runner.run_command.should == "ruby foo.rb" end it "If the command starts with a .rb file, then run it with ruby" do runner = Runner.new("foo.rb --param bar baz.txt") runner.run_command.should == "ruby foo.rb --param bar baz.txt" end it "clears the screen" do runner = Runner.new("foo.rb", {:clear => true}) runner.clear?.should be_true end end describe "running" do it "sends its child process a SIGINT when restarting" it "dies when sent a control-C (SIGINT)" it "accepts a key press" end end end rerun-0.10.0/spec/spec_helper.rb000066400000000000000000000003301233172455600164770ustar00rootroot00000000000000require "rubygems" require "rspec" #require "rspec/autorun" require "wrong/adapters/rspec" include Wrong::D here = File.expand_path(File.dirname(__FILE__)) $: << File.expand_path("#{here}/../lib") require "rerun" rerun-0.10.0/spec/watcher_spec.rb000066400000000000000000000061161233172455600166650ustar00rootroot00000000000000here = File.expand_path(File.dirname(__FILE__)) require "#{here}/spec_helper.rb" require 'tmpdir' require 'rerun/watcher' module Rerun describe Watcher do COOL_OFF_TIME = 2 def start_watcher options = {} options = {:directory => @dir, :pattern => "*.txt"}.merge(options) @log = nil @watcher = Watcher.new(options) do |hash| @log = hash end @watcher.start sleep(COOL_OFF_TIME) # let it spin up end def stop_watcher begin @watcher.stop rescue ThreadError => e end end before do @dir = Dir.tmpdir + "/#{Time.now.to_i}-#{(rand*100000).to_i}" # fix goofy MacOS /tmp path ambiguity @dir.sub!(/^\/var/, "/private/var") FileUtils.mkdir_p(@dir) start_watcher end let(:rest) { 2 } after do stop_watcher end def create test_file, sleep = true File.open(test_file, "w") do |f| f.puts("test") end sleep(rest) if sleep end def modify test_file File.open(test_file, "a") do |f| f.puts("more more more") end sleep(rest) end def remove test_file File.delete(test_file) sleep(rest) end it "watches file changes" do test_file = "#{@dir}/test.txt" create test_file @log[:added].should == [test_file] modify test_file @log[:modified].should == [test_file] remove test_file @log[:removed].should == [test_file] end it "ignores changes to non-matching files" do non_matching_file = "#{@dir}/test.exe" create non_matching_file @log.should be_nil modify non_matching_file @log.should be_nil remove non_matching_file @log.should be_nil end it "ignores changes to dot-files" do dot_file = "#{@dir}/.ignoreme.txt" create dot_file @log.should be_nil modify dot_file @log.should be_nil remove dot_file @log.should be_nil end ignored_directories = Listen::Silencer::DEFAULT_IGNORED_DIRECTORIES it "ignores directories named #{ignored_directories}" do ignored_directories.each do |ignored_dir| FileUtils.mkdir_p "#{@dir}/#{ignored_dir}" create [@dir, ignored_dir, "foo.txt"].join('/'), false end sleep(rest) @log.should be_nil end it "ignores files named `.DS_Store`." do create "#{@dir}/.DS_Store" @log.should be_nil end it "ignores files ending with `.tmp`." do create "#{@dir}/foo.tmp" @log.should be_nil end it "optionally ignores globs" do stop_watcher start_watcher ignore: "foo*" create "#{@dir}/foo.txt" @log.should be_nil end it "disables watcher when paused" do @watcher.pause create "#{@dir}/pause_test.txt" @log.should be_nil end it "pauses and resumes watcher" do @watcher.pause create "#{@dir}/pause_test.txt" @log.should be_nil @watcher.unpause test_file = "#{@dir}/pause_test2.txt" create test_file @log[:added].should == [test_file] end end end rerun-0.10.0/stty.rb000066400000000000000000000013361233172455600142660ustar00rootroot00000000000000# see /usr/include/sys/termios.h # see man stty require "wrong" include Wrong def tty stty = `stty -g` stty.split(':') end def tty_setting name tty.grep(/^#{name}/).first.split('=').last end def oflag tty_setting("oflag") end normal = tty `stty raw` raw = tty `stty -raw` minus_raw = tty assert { minus_raw == normal } `stty raw opost` raw_opost = tty d { raw - normal } d { normal - raw } d { normal - raw_opost } puts "== normal" # d { tty } d{oflag} def check setting `stty #{setting}` puts "testing #{setting}:\nline\nline" print "\r\n" end check "raw" check "-raw" check "raw opost" check "-raw" check "raw gfmt1:oflag=3" # check "oflag=3" # check "lflag=200005cb" # check "iflag=2b02" `stty -raw` rerun-0.10.0/todo.md000066400000000000000000000005761233172455600142320ustar00rootroot00000000000000* use https://github.com/guard/listen * or rb-inotify or rb-inotify * or http://github.com/spicycode/fsevent * use GNTP * https://github.com/snaka/ruby_gntp * https://github.com/ericgj/groem * http://growl.info/documentation/developer/gntp.php * test stty stuff on other Unixes * use childprocess * specify escalation timeout (time between sending SIGTERM and SIGINT)