├── .gitignore ├── .rspec ├── .travis.yml ├── Gemfile ├── LICENSE ├── README.md ├── Rakefile ├── Vagrantfile ├── bin ├── rerun └── rerun.bat ├── geminstaller.yml ├── icons ├── rails_grn_sml.png └── rails_red_sml.png ├── inc.rb ├── issues.json ├── lib ├── goo.rb ├── rerun.rb └── rerun │ ├── glob.rb │ ├── notification.rb │ ├── options.rb │ ├── runner.rb │ ├── system.rb │ └── watcher.rb ├── pulls.json ├── rerun.gemspec ├── spec ├── functional_spec.rb ├── glob_spec.rb ├── inc_process.rb ├── options_spec.rb ├── runner_spec.rb ├── spec_helper.rb └── watcher_spec.rb ├── stty.rb └── todo.md /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | pkg 3 | Gemfile.lock 4 | doc 5 | tmp 6 | node_modules 7 | .vagrant 8 | .dropbox.attr 9 | *__jb_tmp__ 10 | -------------------------------------------------------------------------------- /.rspec: -------------------------------------------------------------------------------- 1 | --color 2 | --format=documentation 3 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | --- 2 | language: ruby 3 | rvm: 4 | - 2.3.4 5 | - 2.4.1 6 | sudo: false 7 | cache: bundler 8 | 9 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | gemspec 4 | 5 | # :ruby = Unix Rubies (OSX, Linux) 6 | # but rb-fsevent is OSX-only, so how to distinguish between OSX and Linux? 7 | platform :ruby do 8 | gem 'rb-fsevent', '>= 0.9.3' 9 | end 10 | 11 | group :development do 12 | gem "rake", '>= 0.10' 13 | end 14 | 15 | group :test do 16 | gem 'rspec', ">=3.0" 17 | gem 'wrong', path: "../wrong" 18 | gem 'files', path: "../files" 19 | # gem 'wrong', github: "alexch/wrong" 20 | # gem 'files', github: "alexch/files" 21 | end 22 | 23 | gem 'wdm', '>= 0.1.0' if Gem.win_platform? 24 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Rerun 2 | Copyright (c) 2009 Alex Chaffee 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy 5 | of this software and associated documentation files (the "Software"), to 6 | deal in the Software without restriction, including without limitation the 7 | rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 8 | sell copies of the Software, and to permit persons to whom the Software is 9 | furnished to do so, subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in 12 | all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL 17 | THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 18 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 19 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 20 | 21 | --- 22 | 23 | rerun partially based on code from Rspactor 24 | Copyright (c) 2009 Mislav Marohnić 25 | License as above (MIT open source). 26 | 27 | rerun partially based on code from FileSystemWatcher 28 | http://paulhorman.com/filesystemwatcher/ 29 | No license provided; assumed public domain. 30 | 31 | rerun partially based on code from Shotgun 32 | Copyright (c) 2009 Ryan Tomayko 33 | License as above (MIT open source). 34 | 35 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Rerun 2 | 3 | 4 | 5 | Rerun launches your program, then watches the filesystem. If a relevant file 6 | changes, then it restarts your program. 7 | 8 | Rerun works for both long-running processes (e.g. apps) and short-running ones 9 | (e.g. tests). It's basically a no-frills command-line alternative to Guard, 10 | Shotgun, Autotest, etc. that doesn't require config files and works on any 11 | command, not just Ruby programs. 12 | 13 | Rerun's advantage is its simple design. Since it uses `exec` and the standard 14 | Unix `SIGINT` and `SIGKILL` signals, you're sure the restarted app is really 15 | acting just like it was when you ran it from the command line the first time. 16 | 17 | By default it watches files ending in: `rb,js,coffee,css,scss,sass,erb,html,haml,ru,yml,slim,md,feature,c,h`. 18 | Use the `--pattern` option if you want to change this. 19 | 20 | As of version 0.7.0, we use the Listen gem, which tries to use your OS's 21 | built-in facilities for monitoring the filesystem, so CPU use is very light. 22 | 23 | **UPDATE**: Now Rerun *does* work on Windows! Caveats: 24 | * not well-tested 25 | * you need to press Enter after keypress input 26 | * you may need to install the `wdm` gem manually: `gem install wdm` 27 | * You may see this persistent `INFO` error message; to remove it, use`--no-notify`: 28 | * `INFO: Could not find files for the given pattern(s)` 29 | 30 | # Installation: 31 | 32 | gem install rerun 33 | 34 | ("sudo" may be required on older systems, but try it without sudo first.) 35 | 36 | If you are using RVM you might want to put this in your global gemset so it's 37 | available to all your apps. (There really should be a better way to distinguish 38 | gems-as-libraries from gems-as-tools.) 39 | 40 | rvm @global do gem install rerun 41 | 42 | The Listen gem looks for certain platform-dependent gems, and will complain if 43 | they're not available. Unfortunately, Rubygems doesn't understand optional 44 | dependencies very well, so you may have to install extra gems (and/or put them 45 | in your Gemfile) to make Rerun work more smoothly on your system. 46 | (Learn more at .) 47 | 48 | On Mac OS X, use 49 | 50 | gem install rb-fsevent 51 | 52 | On Windows, use 53 | 54 | gem install wdm 55 | 56 | On *BSD, use 57 | 58 | gem install rb-kqueue 59 | 60 | ## Installation via Gemfile / Bundler 61 | 62 | If you are using rerun inside an existing Ruby application (like a Rails or Sinatra app), you can add it to your Gemfile: 63 | 64 | ``` ruby 65 | group :development, :test do 66 | gem "rerun" 67 | end 68 | ``` 69 | 70 | Using a Gemfile is also an easy way to use the pre-release branch, which may have bugfixes or features you want: 71 | 72 | ``` ruby 73 | group :development, :test do 74 | gem "rerun", git: "https://github.com/alexch/rerun.git" 75 | end 76 | ``` 77 | 78 | When using a Gemfile, install with `bundle install` or `bundle update`, and run using `bundle exec rerun`, to guarantee you are using the rerun version specified in the Gemfile, and not a different version in a system-wide gemset. 79 | 80 | # Usage: 81 | 82 | rerun [options] [--] cmd 83 | 84 | For example, if you're running a Sinatra app whose main file is `app.rb`: 85 | 86 | rerun ruby app.rb 87 | 88 | If the first part of the command is a `.rb` filename, then `ruby` is 89 | optional, so the above can also be accomplished like this: 90 | 91 | rerun app.rb 92 | 93 | Rails doesn't automatically notice all config file changes, so you can force it 94 | to restart when you change a config file like this: 95 | 96 | rerun --dir config rails s 97 | 98 | Or if you're using Thin to run a Rack app that's configured in config.ru 99 | but you want it on port 4000 and in debug mode, and only want to watch 100 | the `app` and `web` subdirectories: 101 | 102 | rerun --dir app,web -- thin start --debug --port=4000 -R config.ru 103 | 104 | The `--` is to separate rerun options from cmd options. You can also 105 | use a quoted string for the command, e.g. 106 | 107 | rerun --dir app "thin start --debug --port=4000 -R config.ru" 108 | 109 | Rackup can also be used to launch a Rack server, so let's try that: 110 | 111 | rerun -- rackup --port 4000 config.ru 112 | 113 | Want to mimic [autotest](https://github.com/grosser/autotest)? Try 114 | 115 | rerun -x rake 116 | 117 | or 118 | 119 | rerun -cx rspec 120 | 121 | And if you're using [Spork](https://github.com/sporkrb/spork) with Rails, you 122 | need to [restart your spork server](https://github.com/sporkrb/spork/issues/201) 123 | whenever certain Rails environment files change, so why not put this in your 124 | Rakefile... 125 | 126 | desc "run spork (via rerun)" 127 | task :spork do 128 | 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" 129 | end 130 | 131 | and start using `rake spork` to launch your spork server? 132 | 133 | (If you're using Guard instead of Rerun, check out 134 | [guard-spork](https://github.com/guard/guard-spork) 135 | for a similar solution.) 136 | 137 | How about regenerating your HTML files after every change to your 138 | [Erector](http://erector.rubyforge.org) widgets? 139 | 140 | rerun -x erector --to-html my_site.rb 141 | 142 | Use Heroku Cedar? `rerun` is now compatible with `foreman`. Run all your 143 | Procfile processes locally and restart them all when necessary. 144 | 145 | rerun foreman start 146 | 147 | # Options: 148 | 149 | These options can be specified on the command line and/or inside a `.rerun` config file (see below). 150 | 151 | `--dir` directory (or directories) to watch (default = "."). Separate multiple paths with ',' and/or use multiple `-d` options. 152 | 153 | `--pattern` glob to match inside directory. This uses the Ruby Dir glob style -- see for details. 154 | By default it watches files ending in: `rb,js,coffee,css,scss,sass,erb,html,haml,ru,yml,slim,md,feature,c,h`. 155 | On top of this, it also ignores dotfiles, `.tmp` files, and some other files and directories (like `.git` and `log`). 156 | Run `rerun --help` to see the actual list. 157 | 158 | `--ignore pattern` file glob to ignore (can be set many times). To ignore a directory, you must append `'/*'` e.g. 159 | `--ignore 'coverage/*'`. 160 | 161 | `--[no-]ignore-dotfiles` By default, on top of --pattern and --ignore, we ignore any changes to files and dirs starting with a dot. Setting `--no-ignore-dotfiles` allows you to monitor a relevant file like .env, but you may also have to explicitly --ignore more dotfiles and dotdirs. 162 | 163 | `--signal` (or `-s`) use specified signal(s) (instead of the default `TERM,INT,KILL`) to terminate the previous process. You can use a comma-delimited list if you want to try a signal, wait up to 5 seconds for the process to die, then try again with a different signal, and so on. 164 | This may be useful for forcing the respective process to terminate as quickly as possible. 165 | (`--signal KILL` is the equivalent of `kill -9`) 166 | 167 | `--wait sec` (or `-w`) after asking the process to terminate, wait this long (in seconds) before either aborting, or trying the next signal in series. Default: 2 sec 168 | 169 | `--restart` (or `-r`) expect process to restart itself, using signal HUP by default 170 | (e.g. `-r -s INT` will send a INT and then resume watching for changes) 171 | 172 | `--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. 173 | 174 | `--clear` (or -c) clear the screen before each run 175 | 176 | `--background` (or -b) disable on-the-fly commands, allowing the process to be backgrounded 177 | 178 | `--notify NOTIFIER` use `growl` or `osx` or `notify-send` for notifications (see below) 179 | 180 | `--no-notify` disable notifications 181 | 182 | `--name` set the app name (for display) 183 | 184 | `--force-polling` use polling instead of a native filesystem scan (useful for Vagrant) 185 | 186 | `--quiet` silences most messages 187 | 188 | `--verbose` enables even more messages (unless you also specified `--quiet`, which overrides `--verbose`) 189 | 190 | Also `--version` and `--help`, naturally. 191 | 192 | ## Config file 193 | 194 | If the current directory contains a file named `.rerun`, it will be parsed with the same rules as command-line arguments. Newlines are the same as any other whitespace, so you can stack options vertically, like this: 195 | 196 | ``` 197 | --quiet 198 | --pattern **/*.{rb,js,scss,sass,html,md} 199 | ``` 200 | 201 | Options specified on the command line will override those in the config file. You can negate boolean options with `--no-`, so for example, with the above config file, to re-enable logging, you could say: 202 | 203 | ```sh 204 | rerun --no-quiet rackup 205 | ``` 206 | 207 | If you're not sure what options are being overwritten, use `--verbose` and rerun will show you the final result of the parsing. 208 | 209 | # Notifications 210 | 211 | If you have `growlnotify` available on the `PATH`, it sends notifications to 212 | growl in addition to the console. 213 | 214 | If you have `terminal-notifier`, it sends notifications to 215 | the OS X notification center in addition to the console. 216 | 217 | If you have `notify-send`, it sends notifications to Freedesktop-compatible 218 | desktops in addition to the console. 219 | 220 | If you have more than one available notification program, Rerun will pick one, or you can choose between them using `--notify growl`, `--notify osx`, `--notify notify-send`, etc. 221 | 222 | If you have a notifier installed but don't want rerun to use it, 223 | set the `--no-notify` option. 224 | 225 | Download [growlnotify here](http://growl.info/downloads.php#generaldownloads) 226 | now that Growl has moved to the App Store. 227 | 228 | Install [terminal-notifier](https://github.com/julienXX/terminal-notifier) using `gem install terminal-notifier`. (You may have to put it in your system gemset and/or use `sudo` too.) Using Homebrew to install terminal-notifier is not recommended. 229 | 230 | On Debian/Ubuntu, `notify-send` is available in the `libnotify-bin` package. On other GNU/Linux systems, it might be in a package with a different name. 231 | 232 | # On-The-Fly Commands 233 | 234 | While the app is (re)running, you can make things happen by pressing keys: 235 | 236 | * **r** -- restart (as if a file had changed) 237 | * **f** -- force restart (stop and start) 238 | * **c** -- clear the screen 239 | * **x** or **q** -- exit (just like control-C) 240 | * **p** -- pause/unpause filesystem watching 241 | 242 | If you're backgrounding or using Pry or a debugger, you might not want these 243 | keys to be trapped, so use the `--background` option. 244 | 245 | # Signals 246 | 247 | The current algorithm for killing the process is: 248 | 249 | * send [SIGTERM](http://en.wikipedia.org/wiki/SIGTERM) (or the value of the `--signal` option) 250 | * if that doesn't work after 2 seconds, send SIGINT (aka control-C) 251 | * if that doesn't work after 2 more seconds, send SIGKILL (aka kill -9) 252 | 253 | This seems like the most gentle and unixy way of doing things, but it does 254 | mean that if your program ignores SIGTERM, it takes an extra 2 to 4 seconds to 255 | restart. 256 | 257 | If you want to use your own series of signals, use the `--signal` option. If you want to change the delay before attempting the next signal, use the `--wait` option. 258 | 259 | # Vagrant and VirtualBox 260 | 261 | If running inside a shared directory using Vagrant and VirtualBox, you must pass the `--force-polling` option. You may also have to pass some extra `--ignore` options too; otherwise each scan can take 10 or more seconds on directories with a large number of files or subdirectories underneath it. 262 | 263 | # Troubleshooting 264 | 265 | ## zsh ## 266 | 267 | If you are using `zsh` as your shell, and you are specifying your `--pattern` as `**/*.rb`, you may face this error 268 | ``` 269 | Errno::EACCES: Permission denied - 270 | ``` 271 | This is because `**/*.rb` gets expanded into the command by `zsh` instead of passing it through to rerun. The solution is to simply quote ('' or "") the pattern. 272 | i.e 273 | ``` 274 | rerun -p **/*.rb rake test 275 | ``` 276 | becomes 277 | ``` 278 | rerun -p "**/*.rb" rake test 279 | ``` 280 | 281 | # To Do: 282 | 283 | ## Must have for v1.0 284 | * Make sure to pass through quoted options correctly to target process [bug] 285 | * Optionally do "bundle install" before and "bundle exec" during launch 286 | 287 | ## Nice to have 288 | * ".rerun" file in $HOME 289 | * If the last element of the command is a `.ru` file and there's no other command then use `rackup` 290 | * Figure out an algorithm so "-x" is not needed (if possible) -- maybe by accepting a "--port" option or reading `config.ru` 291 | * Specify (or deduce) port to listen for to determine success of a web server launch 292 | * see also [todo.md](todo.md) 293 | 294 | ## Wacky Ideas 295 | 296 | * On OS X: 297 | * use a C library using growl's developer API 298 | * Use growl's AppleScript or SDK instead of relying on growlnotify 299 | * "Failed" icon for notifications 300 | 301 | # Other projects that do similar things 302 | 303 | * Guard: 304 | * Restartomatic: 305 | * Shotgun: 306 | * Rack::Reloader middleware: 307 | * The Sinatra FAQ has a discussion at 308 | * Kicker: 309 | * Watchr: 310 | * Autotest: 311 | 312 | # Why would I use this instead of Shotgun? 313 | 314 | Shotgun does a "fork" after the web framework has loaded but before 315 | your application is loaded. It then loads your app, processes a 316 | single request in the child process, then exits the child process. 317 | 318 | Rerun launches the whole app, then when it's time to restart, uses 319 | "kill" to shut it down and starts the whole thing up again from 320 | scratch. 321 | 322 | So rerun takes somewhat longer than Shotgun to restart the app, but 323 | does it much less frequently. And once it's running it behaves more 324 | normally and consistently with your production app. 325 | 326 | Also, Shotgun reloads the app on every request, even if it doesn't 327 | need to. This is fine if you're loading a single file, but if your web 328 | pages all load other files (CSS, JS, media) then that adds up quickly. 329 | (I can only assume that the developers of shotgun are using caching or a 330 | front web server so this isn't a pain point for them.) 331 | 332 | And hey, does Shotgun reload your Worker processes if you're using Foreman and 333 | a Procfile? I'm pretty sure it doesn't. 334 | 335 | YMMV! 336 | 337 | # Why would I use this instead of Rack::Reloader? 338 | 339 | Rack::Reloader is certifiably beautiful code, and is a very elegant use 340 | of Rack's middleware architecture. But because it relies on the 341 | LOADED_FEATURES variable, it only reloads .rb files that were 'require'd, 342 | not 'load'ed. That leaves out (non-Erector) template files, and also, 343 | at least the way I was doing it, sub-actions (see 344 | [this thread](http://groups.google.com/group/sinatrarb/browse_thread/thread/7329727a9296e96a# 345 | )). 346 | 347 | Rack::Reloader also doesn't reload configuration changes or redo other 348 | things that happen during app startup. Rerun takes the attitude that if 349 | you want to restart an app, you should just restart the whole app. You know? 350 | 351 | # Why would I use this instead of Guard? 352 | 353 | Guard is very powerful but requires some up-front configuration. 354 | Rerun is meant as a no-frills command-line alternative requiring no knowledge 355 | of Ruby nor config file syntax. 356 | 357 | # Why did you write this? 358 | 359 | I've been using [Sinatra](http://sinatrarb.com) and loving it. In order 360 | to simplify their system, the Rat Pack removed auto-reloading from 361 | Sinatra proper. I approve of this: a web application framework should be 362 | focused on serving requests, not on munging Ruby ObjectSpace for 363 | dev-time convenience. But I still wanted automatic reloading during 364 | development. Shotgun wasn't working for me (see above) so I spliced 365 | Rerun together out of code from Rspactor, FileSystemWatcher, and Shotgun 366 | -- with a heavy amount of refactoring and rewriting. In late 2012 I 367 | migrated the backend to the Listen gem, which was extracted from Guard, 368 | so it should be more reliable and performant on multiple platforms. 369 | 370 | # Credits 371 | 372 | Rerun: [Alex Chaffee](http://alexchaffee.com), , 373 | 374 | Based upon and/or inspired by: 375 | 376 | * Shotgun: 377 | * Rspactor: 378 | * (In turn based on http://rails.aizatto.com/2007/11/28/taming-the-autotest-beast-with-fsevents/ ) 379 | * FileSystemWatcher: 380 | 381 | ## Patches by: 382 | 383 | * David Billskog 384 | * Jens B 385 | * Andrés Botero 386 | * Dreamcat4 387 | * 388 | * Barry Sia 389 | * Paul Rangel 390 | * James Edward Gray II 391 | * Raul E Rangel and Antonio Terceiro 392 | * Mike Pastore 393 | * Andy Duncan 394 | * Brent Van Minnen 395 | * Matthew O'Riordan 396 | * Antonio Terceiro 397 | * 398 | * 399 | 400 | # Version History 401 | 402 | * v0.14.0 4 January 2023 403 | * Ruby 3.2 compatibility fix: .exists? no longer exists 404 | 405 | * v0.13.1 9 December 2020 406 | * --no-ignore-dotfiles option 407 | 408 | * v0.13.0 26 January 2018 409 | * bugfix: pause/unpause works again (thanks Barry!) 410 | * `.rerun` config file 411 | 412 | * v0.12.0 23 January 2018 413 | * smarter `--signal` option, allowing you to specify a series of signals to try in order; also `--wait` to change how long between tries 414 | * `--force-polling` option (thanks ajduncan) 415 | * `f` key to force stop and start (thanks mwpastore) 416 | * add `.c` and `.h` files to default ignore list 417 | * support for Windows 418 | * use `Kernel.spawn` instead of `fork` 419 | * use `wdm` gem for Windows Directory Monitor 420 | * support for notifications on GNU/Linux using [notify-send](http://www.linuxjournal.com/content/tech-tip-get-notifications-your-scripts-notify-send) (thanks terceiro) 421 | * fix `Gem::LoadError - terminal-notifier is not part of the bundle` [bug](https://github.com/alexch/rerun/issues/108) (thanks mattheworiordan) 422 | 423 | * 0.11.0 7 October 2015 424 | * better 'changed' message 425 | * `--notify osx` option 426 | * `--restart` option (with bugfix by Mike Pastore) 427 | * use Listen 3 gem 428 | * add `.feature` files to default watchlist (thanks @jmuheim) 429 | 430 | * v0.10.0 4 May 2014 431 | * add '.coffee,.slim,.md' to default pattern (thanks @xylinq) 432 | * --ignore option 433 | 434 | * v0.9.0 6 March 2014 435 | * --dir (or -d) can be specified more than once, for multiple directories (thanks again Barry!) 436 | * --name option 437 | * press 'p' to pause/unpause filesystem watching (Barry is the man!) 438 | * works with Listen 2 (note: needs 2.3 or higher) 439 | * cooldown works, thanks to patches to underlying Listen gem 440 | * ignore all dotfiles, and add actual list of ignored dirs and files 441 | 442 | * v0.8.2 443 | * 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 444 | 445 | * v0.8.1 446 | * bugfix release (#30 and #34) 447 | 448 | * v0.8.0 449 | * --background option (thanks FND!) to disable the keyboard listener 450 | * --signal option (thanks FND!) 451 | * --no-growl option 452 | * --dir supports multiple directories (thanks Barry!) 453 | 454 | * v0.7.1 455 | * bugfix: make rails icon work again 456 | 457 | * v0.7.0 458 | * uses Listen gem (which uses rb-fsevent for lightweight filesystem snooping) 459 | 460 | # License 461 | 462 | Open Source MIT License. See "LICENSE" file. 463 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | require 'rake' 2 | require 'rake/clean' 3 | require 'rake/testtask' 4 | require 'rspec/core/rake_task' 5 | 6 | task :default => [:spec] 7 | task :test => :spec 8 | 9 | desc "Run all specs" 10 | RSpec::Core::RakeTask.new('spec') do |t| 11 | ENV['ENV'] = "test" 12 | t.pattern = 'spec/**/*_spec.rb' 13 | t.rspec_opts = ['--color'] 14 | end 15 | 16 | $rubyforge_project = 'pivotalrb' 17 | 18 | $spec = 19 | begin 20 | require 'rubygems/specification' 21 | data = File.read('rerun.gemspec') 22 | spec = nil 23 | #Thread.new { spec = eval("$SAFE = 3\n#{data}") }.join 24 | spec = eval data 25 | spec 26 | end 27 | 28 | def package(ext='') 29 | "pkg/#{$spec.name}-#{$spec.version}" + ext 30 | end 31 | 32 | desc 'Exit if git is dirty' 33 | task :check_git do 34 | state = `git status 2> /dev/null | tail -n1` 35 | clean = (state =~ /working (directory|tree) clean/) 36 | unless clean 37 | warn "can't do that on an unclean git dir" 38 | exit 1 39 | end 40 | end 41 | 42 | desc 'Build packages' 43 | task :package => %w[.gem .tar.gz].map { |e| package(e) } 44 | 45 | desc 'Build and install as local gem' 46 | task :install => package('.gem') do 47 | sh "gem install #{package('.gem')}" 48 | end 49 | 50 | directory 'pkg/' 51 | CLOBBER.include('pkg') 52 | 53 | file package('.gem') => %W[pkg/ #{$spec.name}.gemspec] + $spec.files do |f| 54 | sh "gem build #{$spec.name}.gemspec" 55 | mv File.basename(f.name), f.name 56 | end 57 | 58 | file package('.tar.gz') => %w[pkg/] + $spec.files do |f| 59 | cmd = <<-SH 60 | git archive \ 61 | --prefix=#{$spec.name}-#{$spec.version}/ \ 62 | --format=tar \ 63 | HEAD | gzip > #{f.name} 64 | SH 65 | sh cmd.gsub(/ +/, ' ') 66 | end 67 | 68 | desc 'Publish gem and tarball to rubyforge' 69 | task 'release' => [:check_git, package('.gem'), package('.tar.gz')] do |t| 70 | puts "Releasing #{$spec.version}" 71 | sh "gem push #{package('.gem')}" 72 | puts "Tagging and pushing" 73 | sh "git tag v#{$spec.version}" 74 | sh "git push && git push --tags" 75 | end 76 | 77 | desc 'download github issues and pull requests' 78 | task 'github' do 79 | %w(issues pulls).each do |type| 80 | sh "curl -o #{type}.json https://api.github.com/repos/alexch/rerun/#{type}" 81 | end 82 | end 83 | -------------------------------------------------------------------------------- /Vagrantfile: -------------------------------------------------------------------------------- 1 | # -*- mode: ruby -*- 2 | # vi: set ft=ruby : 3 | 4 | # All Vagrant configuration is done below. The "2" in Vagrant.configure 5 | # configures the configuration version (we support older styles for 6 | # backwards compatibility). Please don't change it unless you know what 7 | # you're doing. 8 | Vagrant.configure(2) do |config| 9 | # The most common configuration options are documented and commented below. 10 | # For a complete reference, please see the online documentation at 11 | # https://docs.vagrantup.com. 12 | 13 | # Every Vagrant development environment requires a box. You can search for 14 | # boxes at https://atlas.hashicorp.com/search. 15 | config.vm.box = "thdengops/ubuntu-14.04-dev" # git, openjdk7, docker, gvm (go), nvm (node), rvm (ruby), jenv (java), virtualenv (python) 16 | 17 | end 18 | 19 | """ 20 | # steps after launching 21 | rvm get latest 22 | rvm install ruby-2.3.4 23 | ruby -v 24 | gem install bundler 25 | sudo apt-get install linux-headers-3.19.0-25-generic build-essential libgmp3-dev 26 | 27 | rsync -avh /vagrant/ rerun 28 | tr -d '\015' < /vagrant/bin/rerun > rerun/bin/rerun # remove ^M from shebang line 29 | cd rerun 30 | bundle update 31 | """ 32 | 33 | # Disable automatic box update checking. If you disable this, then 34 | # boxes will only be checked for updates when the user runs 35 | # `vagrant box outdated`. This is not recommended. 36 | # config.vm.box_check_update = false 37 | 38 | # Create a forwarded port mapping which allows access to a specific port 39 | # within the machine from a port on the host machine. In the example below, 40 | # accessing "localhost:8080" will access port 80 on the guest machine. 41 | # config.vm.network "forwarded_port", guest: 80, host: 8080 42 | 43 | # Create a private network, which allows host-only access to the machine 44 | # using a specific IP. 45 | # config.vm.network "private_network", ip: "192.168.33.10" 46 | 47 | # Create a public network, which generally matched to bridged network. 48 | # Bridged networks make the machine appear as another physical device on 49 | # your network. 50 | # config.vm.network "public_network" 51 | 52 | # Share an additional folder to the guest VM. The first argument is 53 | # the path on the host to the actual folder. The second argument is 54 | # the path on the guest to mount the folder. And the optional third 55 | # argument is a set of non-required options. 56 | # config.vm.synced_folder "../data", "/vagrant_data" 57 | 58 | # Provider-specific configuration so you can fine-tune various 59 | # backing providers for Vagrant. These expose provider-specific options. 60 | # Example for VirtualBox: 61 | # 62 | # config.vm.provider "virtualbox" do |vb| 63 | # # Display the VirtualBox GUI when booting the machine 64 | # vb.gui = true 65 | # 66 | # # Customize the amount of memory on the VM: 67 | # vb.memory = "1024" 68 | # end 69 | # 70 | # View the documentation for the provider you are using for more 71 | # information on available options. 72 | 73 | # Define a Vagrant Push strategy for pushing to Atlas. Other push strategies 74 | # such as FTP and Heroku are also available. See the documentation at 75 | # https://docs.vagrantup.com/v2/push/atlas.html for more information. 76 | # config.push.define "atlas" do |push| 77 | # push.app = "YOUR_ATLAS_USERNAME/YOUR_APPLICATION_NAME" 78 | # end 79 | 80 | # Enable provisioning with a shell script. Additional provisioners such as 81 | # Puppet, Chef, Ansible, Salt, and Docker are also available. Please see the 82 | # documentation for more information about their specific syntax and use. 83 | # config.vm.provision "shell", inline: <<-SHELL 84 | # sudo apt-get update 85 | # sudo apt-get install -y apache2 86 | # SHELL 87 | -------------------------------------------------------------------------------- /bin/rerun: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | require 'rubygems' 4 | libdir = "#{File.expand_path(File.dirname(File.dirname(__FILE__)))}/lib" 5 | $LOAD_PATH.unshift libdir unless $LOAD_PATH.include?(libdir) 6 | 7 | require 'rerun' 8 | require 'optparse' 9 | 10 | options = Rerun::Options.parse config_file: ".rerun" 11 | 12 | if options and options[:verbose] 13 | puts "\nrerun options:\n\t#{options}" 14 | end 15 | 16 | exit if options.nil? or options[:cmd].nil? or options[:cmd].empty? 17 | 18 | Rerun::Runner.keep_running(options[:cmd], options) 19 | -------------------------------------------------------------------------------- /bin/rerun.bat: -------------------------------------------------------------------------------- 1 | :: %~dp0 means "the directory of the current script file" 2 | :: see https://stackoverflow.com/questions/17063947/get-current-batchfile-directory 3 | ruby %~dp0\rerun %* 4 | -------------------------------------------------------------------------------- /geminstaller.yml: -------------------------------------------------------------------------------- 1 | --- 2 | gems: 3 | - name: rspec 4 | version: '>= 1.2.6' 5 | -------------------------------------------------------------------------------- /icons/rails_grn_sml.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexch/rerun/36f2d237985b670752abbe4a7f6814893cdde96f/icons/rails_grn_sml.png -------------------------------------------------------------------------------- /icons/rails_red_sml.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexch/rerun/36f2d237985b670752abbe4a7f6814893cdde96f/icons/rails_red_sml.png -------------------------------------------------------------------------------- /inc.rb: -------------------------------------------------------------------------------- 1 | STDOUT.sync = true 2 | 3 | Signal.trap("TERM") do 4 | say "exiting" 5 | exit 6 | end 7 | 8 | def say msg 9 | puts "\t[inc] #{Time.now.strftime("%T")} #{$$} #{msg}" 10 | end 11 | 12 | class Inc 13 | def initialize file 14 | @file = file 15 | end 16 | 17 | def run 18 | launched = Time.now.to_i 19 | say "launching in #{File.dirname(@file)}" 20 | i = 0 21 | while i < 100 22 | say "writing #{launched}/#{i} to #{File.basename(@file)}" 23 | File.open(@file, "w") do |f| 24 | f.puts(launched) 25 | f.puts(i) 26 | f.puts($$) 27 | f.puts(Process.ppid) 28 | end 29 | sleep 0.5 30 | i+=1 31 | end 32 | 33 | end 34 | end 35 | 36 | Inc.new(ARGV[0] || "./inc.txt").run 37 | -------------------------------------------------------------------------------- /issues.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "url": "https://api.github.com/repos/alexch/rerun/issues/63", 4 | "labels_url": "https://api.github.com/repos/alexch/rerun/issues/63/labels{/name}", 5 | "comments_url": "https://api.github.com/repos/alexch/rerun/issues/63/comments", 6 | "events_url": "https://api.github.com/repos/alexch/rerun/issues/63/events", 7 | "html_url": "https://github.com/alexch/rerun/issues/63", 8 | "id": 36379723, 9 | "number": 63, 10 | "title": "support libnotify for popup notifications on linux", 11 | "user": { 12 | "login": "abargnesi", 13 | "id": 149445, 14 | "avatar_url": "https://avatars.githubusercontent.com/u/149445?v=1", 15 | "gravatar_id": "bb63c54edecfe79a732014647b379261", 16 | "url": "https://api.github.com/users/abargnesi", 17 | "html_url": "https://github.com/abargnesi", 18 | "followers_url": "https://api.github.com/users/abargnesi/followers", 19 | "following_url": "https://api.github.com/users/abargnesi/following{/other_user}", 20 | "gists_url": "https://api.github.com/users/abargnesi/gists{/gist_id}", 21 | "starred_url": "https://api.github.com/users/abargnesi/starred{/owner}{/repo}", 22 | "subscriptions_url": "https://api.github.com/users/abargnesi/subscriptions", 23 | "organizations_url": "https://api.github.com/users/abargnesi/orgs", 24 | "repos_url": "https://api.github.com/users/abargnesi/repos", 25 | "events_url": "https://api.github.com/users/abargnesi/events{/privacy}", 26 | "received_events_url": "https://api.github.com/users/abargnesi/received_events", 27 | "type": "User", 28 | "site_admin": false 29 | }, 30 | "labels": [ 31 | 32 | ], 33 | "state": "open", 34 | "assignee": null, 35 | "milestone": null, 36 | "comments": 0, 37 | "created_at": "2014-06-24T12:41:15Z", 38 | "updated_at": "2014-06-24T12:41:15Z", 39 | "closed_at": null, 40 | "body": "Popup notifications using libnotify are common on linux. One would need a notification daemon running like gnome's `notification-daemon`, [`dunst`](https://github.com/knopwob/dunst), etc..\r\n\r\nThere is a [`libnotify gem`](https://github.com/splattael/libnotify) by Peter Suschlik that works well with my setup using dunst. This gem depends on the `ffi` gem to call C functions from libnotify." 41 | }, 42 | { 43 | "url": "https://api.github.com/repos/alexch/rerun/issues/61", 44 | "labels_url": "https://api.github.com/repos/alexch/rerun/issues/61/labels{/name}", 45 | "comments_url": "https://api.github.com/repos/alexch/rerun/issues/61/comments", 46 | "events_url": "https://api.github.com/repos/alexch/rerun/issues/61/events", 47 | "html_url": "https://github.com/alexch/rerun/pull/61", 48 | "id": 34212919, 49 | "number": 61, 50 | "title": "load .rerun file from $HOME and project directory in case it exists", 51 | "user": { 52 | "login": "dagi3d", 53 | "id": 11283, 54 | "avatar_url": "https://avatars.githubusercontent.com/u/11283?v=1", 55 | "gravatar_id": "90ea347c45cdfbc1c5767dd6304d9c10", 56 | "url": "https://api.github.com/users/dagi3d", 57 | "html_url": "https://github.com/dagi3d", 58 | "followers_url": "https://api.github.com/users/dagi3d/followers", 59 | "following_url": "https://api.github.com/users/dagi3d/following{/other_user}", 60 | "gists_url": "https://api.github.com/users/dagi3d/gists{/gist_id}", 61 | "starred_url": "https://api.github.com/users/dagi3d/starred{/owner}{/repo}", 62 | "subscriptions_url": "https://api.github.com/users/dagi3d/subscriptions", 63 | "organizations_url": "https://api.github.com/users/dagi3d/orgs", 64 | "repos_url": "https://api.github.com/users/dagi3d/repos", 65 | "events_url": "https://api.github.com/users/dagi3d/events{/privacy}", 66 | "received_events_url": "https://api.github.com/users/dagi3d/received_events", 67 | "type": "User", 68 | "site_admin": false 69 | }, 70 | "labels": [ 71 | 72 | ], 73 | "state": "open", 74 | "assignee": null, 75 | "milestone": null, 76 | "comments": 2, 77 | "created_at": "2014-05-23T21:40:31Z", 78 | "updated_at": "2014-05-27T12:52:27Z", 79 | "closed_at": null, 80 | "pull_request": { 81 | "url": "https://api.github.com/repos/alexch/rerun/pulls/61", 82 | "html_url": "https://github.com/alexch/rerun/pull/61", 83 | "diff_url": "https://github.com/alexch/rerun/pull/61.diff", 84 | "patch_url": "https://github.com/alexch/rerun/pull/61.patch" 85 | }, 86 | "body": "Now it loads the .rerun file from the project directory and $HOME in case it exists\r\nCommand line arguments have precedence over the params declared in the project\r\nParams declared in the project directory have precedence over the params in the $HOME directory\r\n\r\n" 87 | }, 88 | { 89 | "url": "https://api.github.com/repos/alexch/rerun/issues/59", 90 | "labels_url": "https://api.github.com/repos/alexch/rerun/issues/59/labels{/name}", 91 | "comments_url": "https://api.github.com/repos/alexch/rerun/issues/59/comments", 92 | "events_url": "https://api.github.com/repos/alexch/rerun/issues/59/events", 93 | "html_url": "https://github.com/alexch/rerun/issues/59", 94 | "id": 33151552, 95 | "number": 59, 96 | "title": "Feature request: send signal to cause restart", 97 | "user": { 98 | "login": "jmuheim", 99 | "id": 1814983, 100 | "avatar_url": "https://avatars.githubusercontent.com/u/1814983?v=1", 101 | "gravatar_id": "fc582698581884352e745d1d4c64699d", 102 | "url": "https://api.github.com/users/jmuheim", 103 | "html_url": "https://github.com/jmuheim", 104 | "followers_url": "https://api.github.com/users/jmuheim/followers", 105 | "following_url": "https://api.github.com/users/jmuheim/following{/other_user}", 106 | "gists_url": "https://api.github.com/users/jmuheim/gists{/gist_id}", 107 | "starred_url": "https://api.github.com/users/jmuheim/starred{/owner}{/repo}", 108 | "subscriptions_url": "https://api.github.com/users/jmuheim/subscriptions", 109 | "organizations_url": "https://api.github.com/users/jmuheim/orgs", 110 | "repos_url": "https://api.github.com/users/jmuheim/repos", 111 | "events_url": "https://api.github.com/users/jmuheim/events{/privacy}", 112 | "received_events_url": "https://api.github.com/users/jmuheim/received_events", 113 | "type": "User", 114 | "site_admin": false 115 | }, 116 | "labels": [ 117 | 118 | ], 119 | "state": "open", 120 | "assignee": null, 121 | "milestone": null, 122 | "comments": 0, 123 | "created_at": "2014-05-09T07:37:50Z", 124 | "updated_at": "2014-05-09T07:37:50Z", 125 | "closed_at": null, 126 | "body": "It would be nice to be able to send a signal to the running console which would cause a restart." 127 | }, 128 | { 129 | "url": "https://api.github.com/repos/alexch/rerun/issues/58", 130 | "labels_url": "https://api.github.com/repos/alexch/rerun/issues/58/labels{/name}", 131 | "comments_url": "https://api.github.com/repos/alexch/rerun/issues/58/comments", 132 | "events_url": "https://api.github.com/repos/alexch/rerun/issues/58/events", 133 | "html_url": "https://github.com/alexch/rerun/pull/58", 134 | "id": 31934813, 135 | "number": 58, 136 | "title": "add --ignore-dotfiles and disable ignoring by default", 137 | "user": { 138 | "login": "robbles", 139 | "id": 92927, 140 | "avatar_url": "https://avatars.githubusercontent.com/u/92927?v=1", 141 | "gravatar_id": "33cd4933e841cf3c03ee8a3aed0585f7", 142 | "url": "https://api.github.com/users/robbles", 143 | "html_url": "https://github.com/robbles", 144 | "followers_url": "https://api.github.com/users/robbles/followers", 145 | "following_url": "https://api.github.com/users/robbles/following{/other_user}", 146 | "gists_url": "https://api.github.com/users/robbles/gists{/gist_id}", 147 | "starred_url": "https://api.github.com/users/robbles/starred{/owner}{/repo}", 148 | "subscriptions_url": "https://api.github.com/users/robbles/subscriptions", 149 | "organizations_url": "https://api.github.com/users/robbles/orgs", 150 | "repos_url": "https://api.github.com/users/robbles/repos", 151 | "events_url": "https://api.github.com/users/robbles/events{/privacy}", 152 | "received_events_url": "https://api.github.com/users/robbles/received_events", 153 | "type": "User", 154 | "site_admin": false 155 | }, 156 | "labels": [ 157 | 158 | ], 159 | "state": "open", 160 | "assignee": null, 161 | "milestone": null, 162 | "comments": 6, 163 | "created_at": "2014-04-21T23:51:58Z", 164 | "updated_at": "2014-05-05T15:18:57Z", 165 | "closed_at": null, 166 | "pull_request": { 167 | "url": "https://api.github.com/repos/alexch/rerun/pulls/58", 168 | "html_url": "https://github.com/alexch/rerun/pull/58", 169 | "diff_url": "https://github.com/alexch/rerun/pull/58.diff", 170 | "patch_url": "https://github.com/alexch/rerun/pull/58.patch" 171 | }, 172 | "body": "I added an option to enable the filtering of dotfiles. It's disabled by default, because otherwise it's impossible with the current setup to detect changes to them (because of the order in which Listen finds & filters files).\r\n\r\nIf you think it should be enabled by default though, I'm open to changing this though." 173 | }, 174 | { 175 | "url": "https://api.github.com/repos/alexch/rerun/issues/57", 176 | "labels_url": "https://api.github.com/repos/alexch/rerun/issues/57/labels{/name}", 177 | "comments_url": "https://api.github.com/repos/alexch/rerun/issues/57/comments", 178 | "events_url": "https://api.github.com/repos/alexch/rerun/issues/57/events", 179 | "html_url": "https://github.com/alexch/rerun/issues/57", 180 | "id": 29895300, 181 | "number": 57, 182 | "title": "Feature request: option to run the submitted command only when the first file changes", 183 | "user": { 184 | "login": "jmuheim", 185 | "id": 1814983, 186 | "avatar_url": "https://avatars.githubusercontent.com/u/1814983?v=1", 187 | "gravatar_id": "fc582698581884352e745d1d4c64699d", 188 | "url": "https://api.github.com/users/jmuheim", 189 | "html_url": "https://github.com/jmuheim", 190 | "followers_url": "https://api.github.com/users/jmuheim/followers", 191 | "following_url": "https://api.github.com/users/jmuheim/following{/other_user}", 192 | "gists_url": "https://api.github.com/users/jmuheim/gists{/gist_id}", 193 | "starred_url": "https://api.github.com/users/jmuheim/starred{/owner}{/repo}", 194 | "subscriptions_url": "https://api.github.com/users/jmuheim/subscriptions", 195 | "organizations_url": "https://api.github.com/users/jmuheim/orgs", 196 | "repos_url": "https://api.github.com/users/jmuheim/repos", 197 | "events_url": "https://api.github.com/users/jmuheim/events{/privacy}", 198 | "received_events_url": "https://api.github.com/users/jmuheim/received_events", 199 | "type": "User", 200 | "site_admin": false 201 | }, 202 | "labels": [ 203 | 204 | ], 205 | "state": "open", 206 | "assignee": null, 207 | "milestone": null, 208 | "comments": 0, 209 | "created_at": "2014-03-21T12:01:39Z", 210 | "updated_at": "2014-05-02T16:57:57Z", 211 | "closed_at": null, 212 | "body": "...and not when running `rerun`.\r\n\r\n`--run-on-start` or something like that." 213 | }, 214 | { 215 | "url": "https://api.github.com/repos/alexch/rerun/issues/56", 216 | "labels_url": "https://api.github.com/repos/alexch/rerun/issues/56/labels{/name}", 217 | "comments_url": "https://api.github.com/repos/alexch/rerun/issues/56/comments", 218 | "events_url": "https://api.github.com/repos/alexch/rerun/issues/56/events", 219 | "html_url": "https://github.com/alexch/rerun/pull/56", 220 | "id": 29895193, 221 | "number": 56, 222 | "title": "Add feature files to default pattern and update README", 223 | "user": { 224 | "login": "jmuheim", 225 | "id": 1814983, 226 | "avatar_url": "https://avatars.githubusercontent.com/u/1814983?v=1", 227 | "gravatar_id": "fc582698581884352e745d1d4c64699d", 228 | "url": "https://api.github.com/users/jmuheim", 229 | "html_url": "https://github.com/jmuheim", 230 | "followers_url": "https://api.github.com/users/jmuheim/followers", 231 | "following_url": "https://api.github.com/users/jmuheim/following{/other_user}", 232 | "gists_url": "https://api.github.com/users/jmuheim/gists{/gist_id}", 233 | "starred_url": "https://api.github.com/users/jmuheim/starred{/owner}{/repo}", 234 | "subscriptions_url": "https://api.github.com/users/jmuheim/subscriptions", 235 | "organizations_url": "https://api.github.com/users/jmuheim/orgs", 236 | "repos_url": "https://api.github.com/users/jmuheim/repos", 237 | "events_url": "https://api.github.com/users/jmuheim/events{/privacy}", 238 | "received_events_url": "https://api.github.com/users/jmuheim/received_events", 239 | "type": "User", 240 | "site_admin": false 241 | }, 242 | "labels": [ 243 | 244 | ], 245 | "state": "open", 246 | "assignee": null, 247 | "milestone": null, 248 | "comments": 0, 249 | "created_at": "2014-03-21T11:59:27Z", 250 | "updated_at": "2014-03-21T11:59:27Z", 251 | "closed_at": null, 252 | "pull_request": { 253 | "url": "https://api.github.com/repos/alexch/rerun/pulls/56", 254 | "html_url": "https://github.com/alexch/rerun/pull/56", 255 | "diff_url": "https://github.com/alexch/rerun/pull/56.diff", 256 | "patch_url": "https://github.com/alexch/rerun/pull/56.patch" 257 | }, 258 | "body": "`xxx.feature` files are commonly used for Gherkin based stuff, e.g. Cucumber or Turnip feature tests.\r\n\r\nI also noticed that your README wasn't up2date and fixed it." 259 | }, 260 | { 261 | "url": "https://api.github.com/repos/alexch/rerun/issues/55", 262 | "labels_url": "https://api.github.com/repos/alexch/rerun/issues/55/labels{/name}", 263 | "comments_url": "https://api.github.com/repos/alexch/rerun/issues/55/comments", 264 | "events_url": "https://api.github.com/repos/alexch/rerun/issues/55/events", 265 | "html_url": "https://github.com/alexch/rerun/issues/55", 266 | "id": 29825419, 267 | "number": 55, 268 | "title": "Stop rerun when launch failed", 269 | "user": { 270 | "login": "jmuheim", 271 | "id": 1814983, 272 | "avatar_url": "https://avatars.githubusercontent.com/u/1814983?v=1", 273 | "gravatar_id": "fc582698581884352e745d1d4c64699d", 274 | "url": "https://api.github.com/users/jmuheim", 275 | "html_url": "https://github.com/jmuheim", 276 | "followers_url": "https://api.github.com/users/jmuheim/followers", 277 | "following_url": "https://api.github.com/users/jmuheim/following{/other_user}", 278 | "gists_url": "https://api.github.com/users/jmuheim/gists{/gist_id}", 279 | "starred_url": "https://api.github.com/users/jmuheim/starred{/owner}{/repo}", 280 | "subscriptions_url": "https://api.github.com/users/jmuheim/subscriptions", 281 | "organizations_url": "https://api.github.com/users/jmuheim/orgs", 282 | "repos_url": "https://api.github.com/users/jmuheim/repos", 283 | "events_url": "https://api.github.com/users/jmuheim/events{/privacy}", 284 | "received_events_url": "https://api.github.com/users/jmuheim/received_events", 285 | "type": "User", 286 | "site_admin": false 287 | }, 288 | "labels": [ 289 | 290 | ], 291 | "state": "open", 292 | "assignee": null, 293 | "milestone": null, 294 | "comments": 0, 295 | "created_at": "2014-03-20T14:57:06Z", 296 | "updated_at": "2014-03-20T14:57:06Z", 297 | "closed_at": null, 298 | "body": "I think that rerun should exit when it can't launch the app:\r\n\r\n```\r\n15:55:22 [rerun] Synaesthesia Launch Failed\r\n15:55:23 [rerun] Watching . for {Gemfile.lock,config/environment.rb,config/environments/development.rb,config/initializers/*.rb,lib/**/*.rb} using Darwin adapter\r\n```\r\n\r\nOr do you expect the next time the launch will succeed?" 299 | }, 300 | { 301 | "url": "https://api.github.com/repos/alexch/rerun/issues/53", 302 | "labels_url": "https://api.github.com/repos/alexch/rerun/issues/53/labels{/name}", 303 | "comments_url": "https://api.github.com/repos/alexch/rerun/issues/53/comments", 304 | "events_url": "https://api.github.com/repos/alexch/rerun/issues/53/events", 305 | "html_url": "https://github.com/alexch/rerun/issues/53", 306 | "id": 29714802, 307 | "number": 53, 308 | "title": "FATAL SignalException: SIGTERM", 309 | "user": { 310 | "login": "jmuheim", 311 | "id": 1814983, 312 | "avatar_url": "https://avatars.githubusercontent.com/u/1814983?v=1", 313 | "gravatar_id": "fc582698581884352e745d1d4c64699d", 314 | "url": "https://api.github.com/users/jmuheim", 315 | "html_url": "https://github.com/jmuheim", 316 | "followers_url": "https://api.github.com/users/jmuheim/followers", 317 | "following_url": "https://api.github.com/users/jmuheim/following{/other_user}", 318 | "gists_url": "https://api.github.com/users/jmuheim/gists{/gist_id}", 319 | "starred_url": "https://api.github.com/users/jmuheim/starred{/owner}{/repo}", 320 | "subscriptions_url": "https://api.github.com/users/jmuheim/subscriptions", 321 | "organizations_url": "https://api.github.com/users/jmuheim/orgs", 322 | "repos_url": "https://api.github.com/users/jmuheim/repos", 323 | "events_url": "https://api.github.com/users/jmuheim/events{/privacy}", 324 | "received_events_url": "https://api.github.com/users/jmuheim/received_events", 325 | "type": "User", 326 | "site_admin": false 327 | }, 328 | "labels": [ 329 | 330 | ], 331 | "state": "open", 332 | "assignee": null, 333 | "milestone": null, 334 | "comments": 6, 335 | "created_at": "2014-03-19T08:42:16Z", 336 | "updated_at": "2014-05-09T07:37:11Z", 337 | "closed_at": null, 338 | "body": "I try to restart webrick whenever an important file changes. This is the output:\r\n\r\n```\r\n=> Booting WEBrick\r\n=> Rails 4.0.3 application starting in development on http://0.0.0.0:3001\r\n=> Run `rails server -h` for more startup options\r\n=> Ctrl-C to shutdown server\r\n[2014-03-19 09:38:13] INFO WEBrick 1.3.1\r\n[2014-03-19 09:38:13] INFO ruby 2.1.0 (2013-12-25) [x86_64-darwin12.0]\r\n[2014-03-19 09:38:13] INFO WEBrick::HTTPServer#start: pid=9912 port=3001\r\nr09:38:31 [rerun] Restarting\r\n09:38:31 [rerun] Sending signal TERM to 9912\r\n[2014-03-19 09:38:31] FATAL SignalException: SIGTERM\r\n\t/Users/josh/.rvm/rubies/ruby-2.1.0/lib/ruby/2.1.0/webrick/server.rb:170:in `select'\r\n\t/Users/josh/.rvm/rubies/ruby-2.1.0/lib/ruby/2.1.0/webrick/server.rb:170:in `block in start'\r\n\t/Users/josh/.rvm/rubies/ruby-2.1.0/lib/ruby/2.1.0/webrick/server.rb:32:in `start'\r\n\t/Users/josh/.rvm/rubies/ruby-2.1.0/lib/ruby/2.1.0/webrick/server.rb:160:in `start'\r\n\t/Users/josh/.rvm/gems/ruby-2.1.0@base/gems/rack-1.5.2/lib/rack/handler/webrick.rb:14:in `run'\r\n\t/Users/josh/.rvm/gems/ruby-2.1.0@base/gems/rack-1.5.2/lib/rack/server.rb:264:in `start'\r\n\t/Users/josh/.rvm/gems/ruby-2.1.0@base/gems/railties-4.0.3/lib/rails/commands/server.rb:84:in `start'\r\n\t/Users/josh/.rvm/gems/ruby-2.1.0@base/gems/railties-4.0.3/lib/rails/commands.rb:76:in `block in '\r\n\t/Users/josh/.rvm/gems/ruby-2.1.0@base/gems/railties-4.0.3/lib/rails/commands.rb:71:in `tap'\r\n\t/Users/josh/.rvm/gems/ruby-2.1.0@base/gems/railties-4.0.3/lib/rails/commands.rb:71:in `'\r\n\tbin/rails:8:in `require'\r\n\tbin/rails:8:in `
'\r\n[2014-03-19 09:38:31] INFO going to shutdown ...\r\n[2014-03-19 09:38:31] INFO WEBrick::HTTPServer#start done.\r\n```\r\n\r\nI'm concerned about the \"FATAL SignalException: SIGTERM\": is this really correct? Webrick is reloaded correctly, but this looks a bit strange to me." 339 | }, 340 | { 341 | "url": "https://api.github.com/repos/alexch/rerun/issues/52", 342 | "labels_url": "https://api.github.com/repos/alexch/rerun/issues/52/labels{/name}", 343 | "comments_url": "https://api.github.com/repos/alexch/rerun/issues/52/comments", 344 | "events_url": "https://api.github.com/repos/alexch/rerun/issues/52/events", 345 | "html_url": "https://github.com/alexch/rerun/issues/52", 346 | "id": 29598993, 347 | "number": 52, 348 | "title": "Allow monitoring of changes to files starting with a dot", 349 | "user": { 350 | "login": "robbles", 351 | "id": 92927, 352 | "avatar_url": "https://avatars.githubusercontent.com/u/92927?v=1", 353 | "gravatar_id": "33cd4933e841cf3c03ee8a3aed0585f7", 354 | "url": "https://api.github.com/users/robbles", 355 | "html_url": "https://github.com/robbles", 356 | "followers_url": "https://api.github.com/users/robbles/followers", 357 | "following_url": "https://api.github.com/users/robbles/following{/other_user}", 358 | "gists_url": "https://api.github.com/users/robbles/gists{/gist_id}", 359 | "starred_url": "https://api.github.com/users/robbles/starred{/owner}{/repo}", 360 | "subscriptions_url": "https://api.github.com/users/robbles/subscriptions", 361 | "organizations_url": "https://api.github.com/users/robbles/orgs", 362 | "repos_url": "https://api.github.com/users/robbles/repos", 363 | "events_url": "https://api.github.com/users/robbles/events{/privacy}", 364 | "received_events_url": "https://api.github.com/users/robbles/received_events", 365 | "type": "User", 366 | "site_admin": false 367 | }, 368 | "labels": [ 369 | 370 | ], 371 | "state": "open", 372 | "assignee": null, 373 | "milestone": null, 374 | "comments": 4, 375 | "created_at": "2014-03-17T21:09:21Z", 376 | "updated_at": "2014-06-12T21:48:38Z", 377 | "closed_at": null, 378 | "body": "I'm trying to get rerun to pick up changes to my `.env` file, used with Foreman. Restarting my server when code changes is working just fine, but ideally I'd like to have a workflow where I can change environment vars in `.env` and have the app auto-reload with new configuration.\r\n\r\nI see that this is a deliberate built-in feature of the file globbing implementation. Turning this off could be a switch (e.g. `--no-ignore-dot`) or perhaps an implementation of the exclude option #24 could have leading dots, etc. as the default value, which could then be overridden.\r\n\r\nI'm happy to give a shot at implementing this myself. Just wondering if it's already in progress or if you're opposed to the idea." 379 | }, 380 | { 381 | "url": "https://api.github.com/repos/alexch/rerun/issues/41", 382 | "labels_url": "https://api.github.com/repos/alexch/rerun/issues/41/labels{/name}", 383 | "comments_url": "https://api.github.com/repos/alexch/rerun/issues/41/comments", 384 | "events_url": "https://api.github.com/repos/alexch/rerun/issues/41/events", 385 | "html_url": "https://github.com/alexch/rerun/issues/41", 386 | "id": 16313594, 387 | "number": 41, 388 | "title": "\"rerun foreman start\", the first re-run fails with \"Errno::EADDRINUSE\" (Ubuntu 12.10)", 389 | "user": { 390 | "login": "gridsane", 391 | "id": 1535767, 392 | "avatar_url": "https://avatars.githubusercontent.com/u/1535767?v=1", 393 | "gravatar_id": "0e0b0b176952aea9f2cb7539b3fb6c3f", 394 | "url": "https://api.github.com/users/gridsane", 395 | "html_url": "https://github.com/gridsane", 396 | "followers_url": "https://api.github.com/users/gridsane/followers", 397 | "following_url": "https://api.github.com/users/gridsane/following{/other_user}", 398 | "gists_url": "https://api.github.com/users/gridsane/gists{/gist_id}", 399 | "starred_url": "https://api.github.com/users/gridsane/starred{/owner}{/repo}", 400 | "subscriptions_url": "https://api.github.com/users/gridsane/subscriptions", 401 | "organizations_url": "https://api.github.com/users/gridsane/orgs", 402 | "repos_url": "https://api.github.com/users/gridsane/repos", 403 | "events_url": "https://api.github.com/users/gridsane/events{/privacy}", 404 | "received_events_url": "https://api.github.com/users/gridsane/received_events", 405 | "type": "User", 406 | "site_admin": false 407 | }, 408 | "labels": [ 409 | 410 | ], 411 | "state": "open", 412 | "assignee": null, 413 | "milestone": null, 414 | "comments": 4, 415 | "created_at": "2013-07-03T11:44:23Z", 416 | "updated_at": "2014-03-25T06:54:39Z", 417 | "closed_at": null, 418 | "body": "```\r\nrerun foreman start\r\n\r\n17:31:47 [rerun] Heroku.dev launched\r\n17:31:47 web.1 | started with pid 22808\r\n17:31:49 web.1 | [2013-07-03 17:31:49] INFO WEBrick 1.3.1\r\n17:31:49 web.1 | [2013-07-03 17:31:49] INFO ruby 1.9.3 (2012-04-20) [x86_64-linux]\r\n17:31:49 web.1 | [2013-07-03 17:31:49] INFO WEBrick::HTTPServer#start: pid=22811 port=5000\r\n17:31:50 [rerun] Watching . for **/*.{rb,js,css,scss,sass,erb,html,haml,ru} using Linux adapter\r\nr17:31:56 [rerun] Restarting\r\n17:31:56 [rerun] Sending signal TERM to 22799\r\nSIGTERM received\r\n17:31:56 system | sending SIGTERM to all processes\r\n17:32:00 [rerun] Sending signal INT to 22799\r\nSIGINT received\r\n17:32:01 system | sending SIGKILL to all processes\r\n17:32:02 [rerun] Sending signal KILL to 22799\r\n\r\n17:32:02 [rerun] Heroku.dev restarted\r\n17:32:02 web.1 | started with pid 22892\r\n17:32:04 web.1 | [2013-07-03 17:32:04] INFO WEBrick 1.3.1\r\n17:32:04 web.1 | [2013-07-03 17:32:04] INFO ruby 1.9.3 (2012-04-20) [x86_64-linux]\r\n17:32:04 web.1 | [2013-07-03 17:32:04] WARN TCPServer Error: Address already in use - bind(2)\r\n17:32:04 web.1 | /usr/lib/ruby/1.9.1/webrick/utils.rb:85:in `initialize': Address already in use - bind(2) (Errno::EADDRINUSE)\r\n17:32:04 web.1 | from /usr/lib/ruby/1.9.1/webrick/utils.rb:85:in `new'\r\n17:32:04 web.1 | from /usr/lib/ruby/1.9.1/webrick/utils.rb:85:in `block in create_listeners'\r\n17:32:04 web.1 | from /usr/lib/ruby/1.9.1/webrick/utils.rb:82:in `each'\r\n17:32:04 web.1 | from /usr/lib/ruby/1.9.1/webrick/utils.rb:82:in `create_listeners'\r\n17:32:04 web.1 | from /usr/lib/ruby/1.9.1/webrick/server.rb:82:in `listen'\r\n17:32:04 web.1 | from /usr/lib/ruby/1.9.1/webrick/server.rb:70:in `initialize'\r\n17:32:04 web.1 | from /usr/lib/ruby/1.9.1/webrick/httpserver.rb:45:in `initialize'\r\n17:32:04 web.1 | from /var/lib/gems/1.9.1/gems/rack-1.5.2/lib/rack/handler/webrick.rb:11:in `new'\r\n17:32:04 web.1 | from /var/lib/gems/1.9.1/gems/rack-1.5.2/lib/rack/handler/webrick.rb:11:in `run'\r\n17:32:04 web.1 | from /var/lib/gems/1.9.1/gems/rack-1.5.2/lib/rack/server.rb:264:in `start'\r\n17:32:04 web.1 | from /var/lib/gems/1.9.1/gems/rack-1.5.2/lib/rack/server.rb:141:in `start'\r\n17:32:04 web.1 | from /var/lib/gems/1.9.1/gems/rack-1.5.2/bin/rackup:4:in `'\r\n17:32:04 web.1 | from /usr/local/bin/rackup:23:in `load'\r\n17:32:04 web.1 | from /usr/local/bin/rackup:23:in `
'\r\n17:32:04 web.1 | exited with code 1\r\n17:32:04 system | sending SIGTERM to all processes\r\n```\r\n\r\n\"foreman\" recieve SIGKILL, so \"rackup\" not killed by \"foreman\" (i can see it in a process list on port 5000), so \"foreman\" can't start second time.\r\n\r\nMaybe time intervals between signals should be in options?" 419 | }, 420 | { 421 | "url": "https://api.github.com/repos/alexch/rerun/issues/40", 422 | "labels_url": "https://api.github.com/repos/alexch/rerun/issues/40/labels{/name}", 423 | "comments_url": "https://api.github.com/repos/alexch/rerun/issues/40/comments", 424 | "events_url": "https://api.github.com/repos/alexch/rerun/issues/40/events", 425 | "html_url": "https://github.com/alexch/rerun/pull/40", 426 | "id": 15551304, 427 | "number": 40, 428 | "title": "support OSX notifications as alternative to growl", 429 | "user": { 430 | "login": "RaVbaker", 431 | "id": 56023, 432 | "avatar_url": "https://avatars.githubusercontent.com/u/56023?v=1", 433 | "gravatar_id": "9764e44e1d69baa6276644e546ea9229", 434 | "url": "https://api.github.com/users/RaVbaker", 435 | "html_url": "https://github.com/RaVbaker", 436 | "followers_url": "https://api.github.com/users/RaVbaker/followers", 437 | "following_url": "https://api.github.com/users/RaVbaker/following{/other_user}", 438 | "gists_url": "https://api.github.com/users/RaVbaker/gists{/gist_id}", 439 | "starred_url": "https://api.github.com/users/RaVbaker/starred{/owner}{/repo}", 440 | "subscriptions_url": "https://api.github.com/users/RaVbaker/subscriptions", 441 | "organizations_url": "https://api.github.com/users/RaVbaker/orgs", 442 | "repos_url": "https://api.github.com/users/RaVbaker/repos", 443 | "events_url": "https://api.github.com/users/RaVbaker/events{/privacy}", 444 | "received_events_url": "https://api.github.com/users/RaVbaker/received_events", 445 | "type": "User", 446 | "site_admin": false 447 | }, 448 | "labels": [ 449 | 450 | ], 451 | "state": "open", 452 | "assignee": null, 453 | "milestone": null, 454 | "comments": 7, 455 | "created_at": "2013-06-14T10:15:21Z", 456 | "updated_at": "2014-06-07T17:46:55Z", 457 | "closed_at": null, 458 | "pull_request": { 459 | "url": "https://api.github.com/repos/alexch/rerun/pulls/40", 460 | "html_url": "https://github.com/alexch/rerun/pull/40", 461 | "diff_url": "https://github.com/alexch/rerun/pull/40.diff", 462 | "patch_url": "https://github.com/alexch/rerun/pull/40.patch" 463 | }, 464 | "body": "I have coded the OSX Notifications support for rerun. It uses [terminal-notifier](https://github.com/alloy/terminal-notifier) gem. And you can use it with two different arguments: `-on` or `--osx-notifications`. If you will use it will disable the growl notifications.\r\n\r\nRelated to issue: #33\r\n\r\nHow it looks? See here: ![OS X notifications for rerun gem](http://f.cl.ly/items/1q302F041i2I0j1l113U/Screen%20Shot%202013-06-14%20o%2012.11.40.png) " 465 | }, 466 | { 467 | "url": "https://api.github.com/repos/alexch/rerun/issues/39", 468 | "labels_url": "https://api.github.com/repos/alexch/rerun/issues/39/labels{/name}", 469 | "comments_url": "https://api.github.com/repos/alexch/rerun/issues/39/comments", 470 | "events_url": "https://api.github.com/repos/alexch/rerun/issues/39/events", 471 | "html_url": "https://github.com/alexch/rerun/issues/39", 472 | "id": 14921776, 473 | "number": 39, 474 | "title": "Monitoring shared directories inside a vagrant VM doesn't work", 475 | "user": { 476 | "login": "fields", 477 | "id": 52378, 478 | "avatar_url": "https://avatars.githubusercontent.com/u/52378?v=1", 479 | "gravatar_id": "e916fec0d7599a70f91bda465987323d", 480 | "url": "https://api.github.com/users/fields", 481 | "html_url": "https://github.com/fields", 482 | "followers_url": "https://api.github.com/users/fields/followers", 483 | "following_url": "https://api.github.com/users/fields/following{/other_user}", 484 | "gists_url": "https://api.github.com/users/fields/gists{/gist_id}", 485 | "starred_url": "https://api.github.com/users/fields/starred{/owner}{/repo}", 486 | "subscriptions_url": "https://api.github.com/users/fields/subscriptions", 487 | "organizations_url": "https://api.github.com/users/fields/orgs", 488 | "repos_url": "https://api.github.com/users/fields/repos", 489 | "events_url": "https://api.github.com/users/fields/events{/privacy}", 490 | "received_events_url": "https://api.github.com/users/fields/received_events", 491 | "type": "User", 492 | "site_admin": false 493 | }, 494 | "labels": [ 495 | 496 | ], 497 | "state": "open", 498 | "assignee": null, 499 | "milestone": null, 500 | "comments": 7, 501 | "created_at": "2013-05-30T01:52:32Z", 502 | "updated_at": "2014-03-07T23:21:24Z", 503 | "closed_at": null, 504 | "body": "We're trying to use rerun to automatically restart a sinatra process inside a vagrant VM (ubuntu). The host is a Mac, and the source tree is in a shared directory. rerun starts, but doesn't detect changes on the files in the source tree.\r\n\r\nIs there a way to get this to work? Is this a more appropriate issue for the listen gem?" 505 | }, 506 | { 507 | "url": "https://api.github.com/repos/alexch/rerun/issues/38", 508 | "labels_url": "https://api.github.com/repos/alexch/rerun/issues/38/labels{/name}", 509 | "comments_url": "https://api.github.com/repos/alexch/rerun/issues/38/comments", 510 | "events_url": "https://api.github.com/repos/alexch/rerun/issues/38/events", 511 | "html_url": "https://github.com/alexch/rerun/issues/38", 512 | "id": 14247850, 513 | "number": 38, 514 | "title": "Doesn't work when source file is mounted from Mac OSX", 515 | "user": { 516 | "login": "garbin", 517 | "id": 63785, 518 | "avatar_url": "https://avatars.githubusercontent.com/u/63785?v=1", 519 | "gravatar_id": "608bb9c89a1969d6739d1e458058c17d", 520 | "url": "https://api.github.com/users/garbin", 521 | "html_url": "https://github.com/garbin", 522 | "followers_url": "https://api.github.com/users/garbin/followers", 523 | "following_url": "https://api.github.com/users/garbin/following{/other_user}", 524 | "gists_url": "https://api.github.com/users/garbin/gists{/gist_id}", 525 | "starred_url": "https://api.github.com/users/garbin/starred{/owner}{/repo}", 526 | "subscriptions_url": "https://api.github.com/users/garbin/subscriptions", 527 | "organizations_url": "https://api.github.com/users/garbin/orgs", 528 | "repos_url": "https://api.github.com/users/garbin/repos", 529 | "events_url": "https://api.github.com/users/garbin/events{/privacy}", 530 | "received_events_url": "https://api.github.com/users/garbin/received_events", 531 | "type": "User", 532 | "site_admin": false 533 | }, 534 | "labels": [ 535 | 536 | ], 537 | "state": "open", 538 | "assignee": null, 539 | "milestone": null, 540 | "comments": 2, 541 | "created_at": "2013-05-13T07:04:40Z", 542 | "updated_at": "2014-03-06T23:50:04Z", 543 | "closed_at": null, 544 | "body": "The source file is in mac osx. I mount the dir to a Linux which is in a vmware virtual machine. In linux, I try to \"rerun 'rackup'\", the server can be started up, but when I change the source file on Mac OS X, the server doesn't auto reload. When I change the source file on linux, It works great. How can I resolve this problem?" 545 | }, 546 | { 547 | "url": "https://api.github.com/repos/alexch/rerun/issues/33", 548 | "labels_url": "https://api.github.com/repos/alexch/rerun/issues/33/labels{/name}", 549 | "comments_url": "https://api.github.com/repos/alexch/rerun/issues/33/comments", 550 | "events_url": "https://api.github.com/repos/alexch/rerun/issues/33/events", 551 | "html_url": "https://github.com/alexch/rerun/issues/33", 552 | "id": 13535519, 553 | "number": 33, 554 | "title": "support OSX notifications as alternative to growl", 555 | "user": { 556 | "login": "rkh", 557 | "id": 30442, 558 | "avatar_url": "https://avatars.githubusercontent.com/u/30442?v=1", 559 | "gravatar_id": "5c2b452f6eea4a6d84c105ebd971d2a4", 560 | "url": "https://api.github.com/users/rkh", 561 | "html_url": "https://github.com/rkh", 562 | "followers_url": "https://api.github.com/users/rkh/followers", 563 | "following_url": "https://api.github.com/users/rkh/following{/other_user}", 564 | "gists_url": "https://api.github.com/users/rkh/gists{/gist_id}", 565 | "starred_url": "https://api.github.com/users/rkh/starred{/owner}{/repo}", 566 | "subscriptions_url": "https://api.github.com/users/rkh/subscriptions", 567 | "organizations_url": "https://api.github.com/users/rkh/orgs", 568 | "repos_url": "https://api.github.com/users/rkh/repos", 569 | "events_url": "https://api.github.com/users/rkh/events{/privacy}", 570 | "received_events_url": "https://api.github.com/users/rkh/received_events", 571 | "type": "User", 572 | "site_admin": false 573 | }, 574 | "labels": [ 575 | 576 | ], 577 | "state": "open", 578 | "assignee": null, 579 | "milestone": null, 580 | "comments": 3, 581 | "created_at": "2013-04-23T13:57:27Z", 582 | "updated_at": "2013-08-28T19:07:00Z", 583 | "closed_at": null, 584 | "body": "" 585 | }, 586 | { 587 | "url": "https://api.github.com/repos/alexch/rerun/issues/31", 588 | "labels_url": "https://api.github.com/repos/alexch/rerun/issues/31/labels{/name}", 589 | "comments_url": "https://api.github.com/repos/alexch/rerun/issues/31/comments", 590 | "events_url": "https://api.github.com/repos/alexch/rerun/issues/31/events", 591 | "html_url": "https://github.com/alexch/rerun/pull/31", 592 | "id": 12609258, 593 | "number": 31, 594 | "title": "Add restart signal support", 595 | "user": { 596 | "login": "ismell", 597 | "id": 562978, 598 | "avatar_url": "https://avatars.githubusercontent.com/u/562978?v=1", 599 | "gravatar_id": "4f945a304cb19ee2404bfbad964bd85e", 600 | "url": "https://api.github.com/users/ismell", 601 | "html_url": "https://github.com/ismell", 602 | "followers_url": "https://api.github.com/users/ismell/followers", 603 | "following_url": "https://api.github.com/users/ismell/following{/other_user}", 604 | "gists_url": "https://api.github.com/users/ismell/gists{/gist_id}", 605 | "starred_url": "https://api.github.com/users/ismell/starred{/owner}{/repo}", 606 | "subscriptions_url": "https://api.github.com/users/ismell/subscriptions", 607 | "organizations_url": "https://api.github.com/users/ismell/orgs", 608 | "repos_url": "https://api.github.com/users/ismell/repos", 609 | "events_url": "https://api.github.com/users/ismell/events{/privacy}", 610 | "received_events_url": "https://api.github.com/users/ismell/received_events", 611 | "type": "User", 612 | "site_admin": false 613 | }, 614 | "labels": [ 615 | 616 | ], 617 | "state": "open", 618 | "assignee": null, 619 | "milestone": null, 620 | "comments": 1, 621 | "created_at": "2013-03-29T15:58:47Z", 622 | "updated_at": "2013-10-18T18:37:05Z", 623 | "closed_at": null, 624 | "pull_request": { 625 | "url": "https://api.github.com/repos/alexch/rerun/pulls/31", 626 | "html_url": "https://github.com/alexch/rerun/pull/31", 627 | "diff_url": "https://github.com/alexch/rerun/pull/31.diff", 628 | "patch_url": "https://github.com/alexch/rerun/pull/31.patch" 629 | }, 630 | "body": "This can be used when running with unicorn.\r\n\r\nrerun -r HUP -- unicorn -c unicorn.rb\r\n\r\nNOTE: Make sure you run unicorn with a config file\r\n otherwise it will respawn the master and you will\r\n end up in a very bad place." 631 | }, 632 | { 633 | "url": "https://api.github.com/repos/alexch/rerun/issues/24", 634 | "labels_url": "https://api.github.com/repos/alexch/rerun/issues/24/labels{/name}", 635 | "comments_url": "https://api.github.com/repos/alexch/rerun/issues/24/comments", 636 | "events_url": "https://api.github.com/repos/alexch/rerun/issues/24/events", 637 | "html_url": "https://github.com/alexch/rerun/issues/24", 638 | "id": 9274411, 639 | "number": 24, 640 | "title": "Support for a --exclude option", 641 | "user": { 642 | "login": "teddziuba", 643 | "id": 84232, 644 | "avatar_url": "https://avatars.githubusercontent.com/u/84232?v=1", 645 | "gravatar_id": "17c9d9798ac2b8f811ddc64cc7ca8c0f", 646 | "url": "https://api.github.com/users/teddziuba", 647 | "html_url": "https://github.com/teddziuba", 648 | "followers_url": "https://api.github.com/users/teddziuba/followers", 649 | "following_url": "https://api.github.com/users/teddziuba/following{/other_user}", 650 | "gists_url": "https://api.github.com/users/teddziuba/gists{/gist_id}", 651 | "starred_url": "https://api.github.com/users/teddziuba/starred{/owner}{/repo}", 652 | "subscriptions_url": "https://api.github.com/users/teddziuba/subscriptions", 653 | "organizations_url": "https://api.github.com/users/teddziuba/orgs", 654 | "repos_url": "https://api.github.com/users/teddziuba/repos", 655 | "events_url": "https://api.github.com/users/teddziuba/events{/privacy}", 656 | "received_events_url": "https://api.github.com/users/teddziuba/received_events", 657 | "type": "User", 658 | "site_admin": false 659 | }, 660 | "labels": [ 661 | 662 | ], 663 | "state": "open", 664 | "assignee": null, 665 | "milestone": null, 666 | "comments": 3, 667 | "created_at": "2012-12-14T02:28:55Z", 668 | "updated_at": "2014-04-16T22:10:35Z", 669 | "closed_at": null, 670 | "body": "I'm using Rerun for for Python development with Heroku. (Long story short, the HTTP server I use in production doesn't support automatic-reloading, so I have rerun kicking foreman when there are code changes).\r\n\r\nI use Emacs for development, with flymake mode to do on-the-fly syntax checking. It does this by copying the current buffer into a `[filename]_flymake.py` file and then running `pylint` on that file. This file creation kicks off rerun, so it's constantly bouncing my development HTTP server as I'm editing code.\r\n\r\nI'd like to add my vote for a `--exclude` option that can take a glob pattern. I'd send you a pull myself, but I don't know enough Ruby.\r\n\r\nCheers" 671 | }, 672 | { 673 | "url": "https://api.github.com/repos/alexch/rerun/issues/23", 674 | "labels_url": "https://api.github.com/repos/alexch/rerun/issues/23/labels{/name}", 675 | "comments_url": "https://api.github.com/repos/alexch/rerun/issues/23/comments", 676 | "events_url": "https://api.github.com/repos/alexch/rerun/issues/23/events", 677 | "html_url": "https://github.com/alexch/rerun/issues/23", 678 | "id": 8426275, 679 | "number": 23, 680 | "title": "Input keypresses are duplicated after 200-800ms on OS X 10.8.2 ", 681 | "user": { 682 | "login": "nathanaeljones", 683 | "id": 107935, 684 | "avatar_url": "https://avatars.githubusercontent.com/u/107935?v=1", 685 | "gravatar_id": "e246b32ac5e470afb71f8221a9759da2", 686 | "url": "https://api.github.com/users/nathanaeljones", 687 | "html_url": "https://github.com/nathanaeljones", 688 | "followers_url": "https://api.github.com/users/nathanaeljones/followers", 689 | "following_url": "https://api.github.com/users/nathanaeljones/following{/other_user}", 690 | "gists_url": "https://api.github.com/users/nathanaeljones/gists{/gist_id}", 691 | "starred_url": "https://api.github.com/users/nathanaeljones/starred{/owner}{/repo}", 692 | "subscriptions_url": "https://api.github.com/users/nathanaeljones/subscriptions", 693 | "organizations_url": "https://api.github.com/users/nathanaeljones/orgs", 694 | "repos_url": "https://api.github.com/users/nathanaeljones/repos", 695 | "events_url": "https://api.github.com/users/nathanaeljones/events{/privacy}", 696 | "received_events_url": "https://api.github.com/users/nathanaeljones/received_events", 697 | "type": "User", 698 | "site_admin": false 699 | }, 700 | "labels": [ 701 | 702 | ], 703 | "state": "open", 704 | "assignee": null, 705 | "milestone": null, 706 | "comments": 2, 707 | "created_at": "2012-11-16T17:30:44Z", 708 | "updated_at": "2013-01-31T15:46:32Z", 709 | "closed_at": null, 710 | "body": "Typing @me will product @@meme or @m@mee or something similar.\n\nMakes debugging with 'debugger' impossible.\n\nusing rerun (0.7.1)" 711 | }, 712 | { 713 | "url": "https://api.github.com/repos/alexch/rerun/issues/21", 714 | "labels_url": "https://api.github.com/repos/alexch/rerun/issues/21/labels{/name}", 715 | "comments_url": "https://api.github.com/repos/alexch/rerun/issues/21/comments", 716 | "events_url": "https://api.github.com/repos/alexch/rerun/issues/21/events", 717 | "html_url": "https://github.com/alexch/rerun/issues/21", 718 | "id": 7027235, 719 | "number": 21, 720 | "title": "use ChildProcess so it works on Windows", 721 | "user": { 722 | "login": "alexch", 723 | "id": 8524, 724 | "avatar_url": "https://avatars.githubusercontent.com/u/8524?v=1", 725 | "gravatar_id": "5a0d7f0cb2fac7858d234de7f7f01491", 726 | "url": "https://api.github.com/users/alexch", 727 | "html_url": "https://github.com/alexch", 728 | "followers_url": "https://api.github.com/users/alexch/followers", 729 | "following_url": "https://api.github.com/users/alexch/following{/other_user}", 730 | "gists_url": "https://api.github.com/users/alexch/gists{/gist_id}", 731 | "starred_url": "https://api.github.com/users/alexch/starred{/owner}{/repo}", 732 | "subscriptions_url": "https://api.github.com/users/alexch/subscriptions", 733 | "organizations_url": "https://api.github.com/users/alexch/orgs", 734 | "repos_url": "https://api.github.com/users/alexch/repos", 735 | "events_url": "https://api.github.com/users/alexch/events{/privacy}", 736 | "received_events_url": "https://api.github.com/users/alexch/received_events", 737 | "type": "User", 738 | "site_admin": false 739 | }, 740 | "labels": [ 741 | 742 | ], 743 | "state": "open", 744 | "assignee": null, 745 | "milestone": null, 746 | "comments": 1, 747 | "created_at": "2012-09-20T22:36:33Z", 748 | "updated_at": "2014-01-21T03:23:14Z", 749 | "closed_at": null, 750 | "body": "a la https://github.com/guard/guard-spork/commit/282c3413200580e36c79c5166a91863c37e9efe1" 751 | }, 752 | { 753 | "url": "https://api.github.com/repos/alexch/rerun/issues/19", 754 | "labels_url": "https://api.github.com/repos/alexch/rerun/issues/19/labels{/name}", 755 | "comments_url": "https://api.github.com/repos/alexch/rerun/issues/19/comments", 756 | "events_url": "https://api.github.com/repos/alexch/rerun/issues/19/events", 757 | "html_url": "https://github.com/alexch/rerun/issues/19", 758 | "id": 5750481, 759 | "number": 19, 760 | "title": "control-C during restart leaves something running", 761 | "user": { 762 | "login": "alexch", 763 | "id": 8524, 764 | "avatar_url": "https://avatars.githubusercontent.com/u/8524?v=1", 765 | "gravatar_id": "5a0d7f0cb2fac7858d234de7f7f01491", 766 | "url": "https://api.github.com/users/alexch", 767 | "html_url": "https://github.com/alexch", 768 | "followers_url": "https://api.github.com/users/alexch/followers", 769 | "following_url": "https://api.github.com/users/alexch/following{/other_user}", 770 | "gists_url": "https://api.github.com/users/alexch/gists{/gist_id}", 771 | "starred_url": "https://api.github.com/users/alexch/starred{/owner}{/repo}", 772 | "subscriptions_url": "https://api.github.com/users/alexch/subscriptions", 773 | "organizations_url": "https://api.github.com/users/alexch/orgs", 774 | "repos_url": "https://api.github.com/users/alexch/repos", 775 | "events_url": "https://api.github.com/users/alexch/events{/privacy}", 776 | "received_events_url": "https://api.github.com/users/alexch/received_events", 777 | "type": "User", 778 | "site_admin": false 779 | }, 780 | "labels": [ 781 | 782 | ], 783 | "state": "open", 784 | "assignee": null, 785 | "milestone": null, 786 | "comments": 2, 787 | "created_at": "2012-07-21T02:31:28Z", 788 | "updated_at": "2013-06-18T19:31:43Z", 789 | "closed_at": null, 790 | "body": "the signal handler should not only kill a running process; it should also kill a process that we're currently in the middle of starting (if the timing is just wrong it'll leave one running in the background)" 791 | }, 792 | { 793 | "url": "https://api.github.com/repos/alexch/rerun/issues/18", 794 | "labels_url": "https://api.github.com/repos/alexch/rerun/issues/18/labels{/name}", 795 | "comments_url": "https://api.github.com/repos/alexch/rerun/issues/18/comments", 796 | "events_url": "https://api.github.com/repos/alexch/rerun/issues/18/events", 797 | "html_url": "https://github.com/alexch/rerun/issues/18", 798 | "id": 5610909, 799 | "number": 18, 800 | "title": "Vote for the \"cool-down\" feature", 801 | "user": { 802 | "login": "mjones-rpx", 803 | "id": 851349, 804 | "avatar_url": "https://avatars.githubusercontent.com/u/851349?v=1", 805 | "gravatar_id": "295429f9c258add966b808a428bc68ba", 806 | "url": "https://api.github.com/users/mjones-rpx", 807 | "html_url": "https://github.com/mjones-rpx", 808 | "followers_url": "https://api.github.com/users/mjones-rpx/followers", 809 | "following_url": "https://api.github.com/users/mjones-rpx/following{/other_user}", 810 | "gists_url": "https://api.github.com/users/mjones-rpx/gists{/gist_id}", 811 | "starred_url": "https://api.github.com/users/mjones-rpx/starred{/owner}{/repo}", 812 | "subscriptions_url": "https://api.github.com/users/mjones-rpx/subscriptions", 813 | "organizations_url": "https://api.github.com/users/mjones-rpx/orgs", 814 | "repos_url": "https://api.github.com/users/mjones-rpx/repos", 815 | "events_url": "https://api.github.com/users/mjones-rpx/events{/privacy}", 816 | "received_events_url": "https://api.github.com/users/mjones-rpx/received_events", 817 | "type": "User", 818 | "site_admin": false 819 | }, 820 | "labels": [ 821 | 822 | ], 823 | "state": "open", 824 | "assignee": null, 825 | "milestone": null, 826 | "comments": 1, 827 | "created_at": "2012-07-13T18:32:07Z", 828 | "updated_at": "2014-03-06T23:55:58Z", 829 | "closed_at": null, 830 | "body": "Just a note to register a strong vote for the cool-down feature. This is working really nicely to help with restarting a Rails 3 Grape API app I'm working on. But I use RubyMine, which autosaves all the time, and this causes rerun to kick off constantly. It would even be cool if you kept it simple and we could start rerun with a flag telling it to not restart more than once every X seconds. Thanks for the great program." 831 | }, 832 | { 833 | "url": "https://api.github.com/repos/alexch/rerun/issues/15", 834 | "labels_url": "https://api.github.com/repos/alexch/rerun/issues/15/labels{/name}", 835 | "comments_url": "https://api.github.com/repos/alexch/rerun/issues/15/comments", 836 | "events_url": "https://api.github.com/repos/alexch/rerun/issues/15/events", 837 | "html_url": "https://github.com/alexch/rerun/issues/15", 838 | "id": 2282579, 839 | "number": 15, 840 | "title": "tell me when the server is listening", 841 | "user": { 842 | "login": "alexch", 843 | "id": 8524, 844 | "avatar_url": "https://avatars.githubusercontent.com/u/8524?v=1", 845 | "gravatar_id": "5a0d7f0cb2fac7858d234de7f7f01491", 846 | "url": "https://api.github.com/users/alexch", 847 | "html_url": "https://github.com/alexch", 848 | "followers_url": "https://api.github.com/users/alexch/followers", 849 | "following_url": "https://api.github.com/users/alexch/following{/other_user}", 850 | "gists_url": "https://api.github.com/users/alexch/gists{/gist_id}", 851 | "starred_url": "https://api.github.com/users/alexch/starred{/owner}{/repo}", 852 | "subscriptions_url": "https://api.github.com/users/alexch/subscriptions", 853 | "organizations_url": "https://api.github.com/users/alexch/orgs", 854 | "repos_url": "https://api.github.com/users/alexch/repos", 855 | "events_url": "https://api.github.com/users/alexch/events{/privacy}", 856 | "received_events_url": "https://api.github.com/users/alexch/received_events", 857 | "type": "User", 858 | "site_admin": false 859 | }, 860 | "labels": [ 861 | 862 | ], 863 | "state": "open", 864 | "assignee": null, 865 | "milestone": null, 866 | "comments": 3, 867 | "created_at": "2011-11-18T17:41:13Z", 868 | "updated_at": "2013-06-18T20:04:41Z", 869 | "closed_at": null, 870 | "body": "if possible, detect when the server has not only launched but is listening to the port and ready to accept requests\r\n\r\nthis may require some config.ru snooping or CL params to figure out what port to check\r\n" 871 | }, 872 | { 873 | "url": "https://api.github.com/repos/alexch/rerun/issues/12", 874 | "labels_url": "https://api.github.com/repos/alexch/rerun/issues/12/labels{/name}", 875 | "comments_url": "https://api.github.com/repos/alexch/rerun/issues/12/comments", 876 | "events_url": "https://api.github.com/repos/alexch/rerun/issues/12/events", 877 | "html_url": "https://github.com/alexch/rerun/issues/12", 878 | "id": 2282415, 879 | "number": 12, 880 | "title": "should detect a \"config.ru\" file and automatically rackup", 881 | "user": { 882 | "login": "alexch", 883 | "id": 8524, 884 | "avatar_url": "https://avatars.githubusercontent.com/u/8524?v=1", 885 | "gravatar_id": "5a0d7f0cb2fac7858d234de7f7f01491", 886 | "url": "https://api.github.com/users/alexch", 887 | "html_url": "https://github.com/alexch", 888 | "followers_url": "https://api.github.com/users/alexch/followers", 889 | "following_url": "https://api.github.com/users/alexch/following{/other_user}", 890 | "gists_url": "https://api.github.com/users/alexch/gists{/gist_id}", 891 | "starred_url": "https://api.github.com/users/alexch/starred{/owner}{/repo}", 892 | "subscriptions_url": "https://api.github.com/users/alexch/subscriptions", 893 | "organizations_url": "https://api.github.com/users/alexch/orgs", 894 | "repos_url": "https://api.github.com/users/alexch/repos", 895 | "events_url": "https://api.github.com/users/alexch/events{/privacy}", 896 | "received_events_url": "https://api.github.com/users/alexch/received_events", 897 | "type": "User", 898 | "site_admin": false 899 | }, 900 | "labels": [ 901 | 902 | ], 903 | "state": "open", 904 | "assignee": null, 905 | "milestone": null, 906 | "comments": 0, 907 | "created_at": "2011-11-18T17:24:38Z", 908 | "updated_at": "2011-11-18T17:24:38Z", 909 | "closed_at": null, 910 | "body": "...if no other parameters are specified, of course\r\n\r\n...should decide whether \"rake\" or \"rackup\" take precedence, so this feature request may be impossible\r\n" 911 | } 912 | ] 913 | -------------------------------------------------------------------------------- /lib/goo.rb: -------------------------------------------------------------------------------- 1 | goooo 2 | goooo 3 | goooo 4 | -------------------------------------------------------------------------------- /lib/rerun.rb: -------------------------------------------------------------------------------- 1 | here = File.expand_path(File.dirname(__FILE__)) 2 | $: << here unless $:.include?(here) 3 | 4 | require "listen" # pull in the Listen gem 5 | require "rerun/options" 6 | require "rerun/system" 7 | require "rerun/notification" 8 | require "rerun/runner" 9 | require "rerun/watcher" 10 | require "rerun/glob" 11 | 12 | module Rerun 13 | 14 | end 15 | 16 | -------------------------------------------------------------------------------- /lib/rerun/glob.rb: -------------------------------------------------------------------------------- 1 | # based on http://cpan.uwinnipeg.ca/htdocs/Text-Glob/Text/Glob.pm.html#glob_to_regex_string- 2 | 3 | # todo: release as separate gem 4 | # 5 | module Rerun 6 | class Glob 7 | NO_LEADING_DOT = '(?=[^\.])' # todo 8 | START_OF_FILENAME = '(\A|\/)' # beginning of string or a slash 9 | END_OF_STRING = '\z' 10 | 11 | def initialize glob_string 12 | @glob_string = glob_string 13 | end 14 | 15 | def to_regexp_string 16 | chars = @glob_string.split('') 17 | 18 | chars = smoosh(chars) 19 | 20 | curlies = 0 21 | escaping = false 22 | string = chars.map do |char| 23 | if escaping 24 | escaping = false 25 | char 26 | else 27 | case char 28 | when '**' 29 | "([^/]+/)*" 30 | when '*' 31 | ".*" 32 | when "?" 33 | "." 34 | when "." 35 | "\\." 36 | 37 | when "{" 38 | curlies += 1 39 | "(" 40 | when "}" 41 | if curlies > 0 42 | curlies -= 1 43 | ")" 44 | else 45 | char 46 | end 47 | when "," 48 | if curlies > 0 49 | "|" 50 | else 51 | char 52 | end 53 | when "\\" 54 | escaping = true 55 | "\\" 56 | 57 | else 58 | char 59 | 60 | end 61 | end 62 | end.join 63 | START_OF_FILENAME + string + END_OF_STRING 64 | end 65 | 66 | def to_regexp 67 | Regexp.new(to_regexp_string) 68 | end 69 | 70 | def smoosh chars 71 | out = [] 72 | until chars.empty? 73 | char = chars.shift 74 | if char == "*" and chars.first == "*" 75 | chars.shift 76 | chars.shift if chars.first == "/" 77 | out.push("**") 78 | else 79 | out.push(char) 80 | end 81 | end 82 | out 83 | end 84 | end 85 | end 86 | -------------------------------------------------------------------------------- /lib/rerun/notification.rb: -------------------------------------------------------------------------------- 1 | # todo: unit tests 2 | 3 | module Rerun 4 | class Notification 5 | include System 6 | 7 | attr_reader :title, :body, :options 8 | 9 | def initialize(title, body, options = Options::DEFAULTS.dup) 10 | @title = title 11 | @body = body 12 | @options = options 13 | end 14 | 15 | def command 16 | # todo: strategy or subclass 17 | 18 | s = nil 19 | 20 | if options[:notify] == true or options[:notify] == "growl" 21 | if (cmd = command_named("growlnotify")) 22 | # todo: check version of growlnotify and warn if it's too old 23 | icon_str = ("--image \"#{icon}\"" if icon) 24 | s = "#{cmd} -n \"#{app_name}\" -m \"#{body}\" \"#{app_name} #{title}\" #{icon_str}" 25 | end 26 | end 27 | 28 | if s.nil? and options[:notify] == true or options[:notify] == "osx" 29 | if (cmd = command_named("terminal-notifier")) 30 | icon_str = ("-appIcon \"#{icon}\"" if icon) 31 | s = "#{cmd} -title \"#{app_name}\" -message \"#{body}\" \"#{app_name} #{title}\" #{icon_str}" 32 | end 33 | end 34 | 35 | if s.nil? and options[:notify] == true or options[:notify] == "notify-send" 36 | if (cmd = command_named('notify-send')) 37 | icon_str = "--icon #{icon}" if icon 38 | s = "#{cmd} -t 500 --hint=int:transient:1 #{icon_str} \"#{app_name}: #{title}\" \"#{body}\"" 39 | end 40 | end 41 | 42 | s 43 | end 44 | 45 | def command_named(name) 46 | which_command = windows? ? 'where.exe %{cmd}' : 'which %{cmd} 2> /dev/null' 47 | # TODO: remove 'INFO' error message 48 | path = `#{which_command % {cmd: name}}`.chomp 49 | path.empty? ? nil : path 50 | end 51 | 52 | def send(background = true) 53 | return unless command 54 | with_clean_env do 55 | `#{command}#{" &" if background}` 56 | end 57 | end 58 | 59 | def app_name 60 | options[:name] 61 | end 62 | 63 | def icon 64 | "#{icon_dir}/rails_red_sml.png" if rails? 65 | end 66 | 67 | def icon_dir 68 | here = File.expand_path(File.dirname(__FILE__)) 69 | File.expand_path("#{here}/../../icons") 70 | end 71 | 72 | def with_clean_env 73 | return yield unless defined?(Bundler) 74 | 75 | if Bundler.respond_to?(:with_unbundled_env) 76 | Bundler.with_unbundled_env { yield } 77 | else 78 | # Deprecated on Bundler 2.1 79 | Bundler.with_clean_env { yield } 80 | end 81 | end 82 | end 83 | end 84 | -------------------------------------------------------------------------------- /lib/rerun/options.rb: -------------------------------------------------------------------------------- 1 | require 'optparse' 2 | require 'pathname' 3 | require 'rerun/watcher' 4 | require 'rerun/system' 5 | 6 | libdir = "#{File.expand_path(File.dirname(File.dirname(__FILE__)))}" 7 | 8 | $spec = Gem::Specification.load(File.join(libdir, "..", "rerun.gemspec")) 9 | 10 | module Rerun 11 | class Options 12 | 13 | extend Rerun::System 14 | 15 | # If you change the default pattern, please update the README.md file -- the list appears twice therein, which at the time of this comment are lines 17 and 119 16 | DEFAULT_PATTERN = "**/*.{rb,js,coffee,css,scss,sass,erb,html,haml,ru,yml,slim,md,feature,c,h}" 17 | DEFAULT_DIRS = ["."] 18 | 19 | DEFAULTS = { 20 | :background => false, 21 | :dir => DEFAULT_DIRS, 22 | :force_polling => false, 23 | :ignore => [], 24 | :ignore_dotfiles => true, 25 | :name => Pathname.getwd.basename.to_s.capitalize, 26 | :notify => true, 27 | :pattern => DEFAULT_PATTERN, 28 | :quiet => false, 29 | :signal => (windows? ? "TERM,KILL" : "TERM,INT,KILL"), 30 | :verbose => false, 31 | :wait => 2, 32 | } 33 | 34 | def self.parse args: ARGV, config_file: nil 35 | 36 | default_options = DEFAULTS.dup 37 | options = { 38 | ignore: [] 39 | } 40 | 41 | if config_file && File.exist?(config_file) 42 | require 'shellwords' 43 | config_args = File.read(config_file).shellsplit 44 | args = config_args + args 45 | end 46 | 47 | option_parser = OptionParser.new("", 24, ' ') do |o| 48 | o.banner = "Usage: rerun [options] [--] cmd" 49 | 50 | o.separator "" 51 | o.separator "Launches an app, and restarts it when the filesystem changes." 52 | o.separator "See http://github.com/alexch/rerun for more info." 53 | o.separator "Version: #{$spec.version}" 54 | o.separator "" 55 | o.separator "Options:" 56 | 57 | o.on("-d dir", "--dir dir", "directory to watch, default = \"#{DEFAULT_DIRS}\". Specify multiple paths with ',' or separate '-d dir' option pairs.") do |dir| 58 | elements = dir.split(",") 59 | options[:dir] = (options[:dir] || []) + elements 60 | end 61 | 62 | # todo: rename to "--watch" 63 | o.on("-p pattern", "--pattern pattern", "file glob to watch, default = \"#{DEFAULTS[:pattern]}\"") do |pattern| 64 | options[:pattern] = pattern 65 | end 66 | 67 | o.on("-i pattern", "--ignore pattern", "file glob(s) to ignore. Can be set many times. To ignore a directory, you must append '/*' e.g. --ignore 'coverage/*' . Globs do not match dotfiles by default.") do |pattern| 68 | options[:ignore] += [pattern] 69 | end 70 | 71 | o.on("--[no-]ignore-dotfiles", "by default, file globs do not match files that begin with a dot. Setting --no-ignore-dotfiles allows you to monitor a relevant file like .env, but you may also have to explicitly --ignore more dotfiles and dotdirs.") do |value| 72 | options[:ignore_dotfiles] = value 73 | end 74 | 75 | o.on("-s signal", "--signal signal", "terminate process using this signal. To try several signals in series, use a comma-delimited list. Default: \"#{DEFAULTS[:signal]}\"") do |signal| 76 | options[:signal] = signal 77 | end 78 | 79 | o.on("-w sec", "--wait sec", "after asking the process to terminate, wait this long (in seconds) before either aborting, or trying the next signal in series. Default: #{DEFAULTS[:wait]} sec") do |value| 80 | default_options[:wait] = value 81 | end 82 | 83 | o.on("-r", "--restart", "expect process to restart itself, so just send a signal and continue watching. Sends the HUP signal unless overridden using --signal") do |signal| 84 | options[:restart] = true 85 | default_options[:signal] = "HUP" 86 | end 87 | 88 | o.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 |value| 89 | options[:exit] = value 90 | end 91 | 92 | o.on("-c", "--clear", "clear screen before each run") do |value| 93 | options[:clear] = value 94 | end 95 | 96 | o.on("-b", "--background", "disable on-the-fly keypress commands, allowing the process to be backgrounded") do |value| 97 | options[:background] = value 98 | end 99 | 100 | o.on("-n name", "--name name", "name of app used in logs and notifications, default = \"#{DEFAULTS[:name]}\"") do |name| 101 | options[:name] = name 102 | end 103 | 104 | o.on("--[no-]force-polling", "use polling instead of a native filesystem scan (useful for Vagrant)") do |value| 105 | options[:force_polling] = value 106 | end 107 | 108 | o.on("--no-growl", "don't use growl [OBSOLETE]") do 109 | options[:growl] = false 110 | $stderr.puts "--no-growl is obsolete; use --no-notify instead" 111 | return 112 | end 113 | 114 | o.on("--[no-]notify [notifier]", "send messages through a desktop notification application. Supports growl (requires growlnotify), osx (requires terminal-notifier gem), and notify-send on GNU/Linux (notify-send must be installed)") do |notifier| 115 | notifier = true if notifier.nil? 116 | options[:notify] = notifier 117 | end 118 | 119 | o.on("-q", "--[no-]quiet", "don't output any logs") do |value| 120 | options[:quiet] = value 121 | end 122 | 123 | o.on("--[no-]verbose", "log extra stuff like PIDs (unless you also specified `--quiet`") do |value| 124 | options[:verbose] = value 125 | end 126 | 127 | o.on_tail("-h", "--help", "--usage", "show this message and immediately exit") do 128 | puts o 129 | return 130 | end 131 | 132 | o.on_tail("--version", "show version and immediately exit") do 133 | puts $spec.version 134 | return 135 | end 136 | 137 | end 138 | 139 | puts option_parser if args.empty? 140 | option_parser.parse! args 141 | options = default_options.merge(options) 142 | options[:cmd] = args.join(" ").strip # todo: better arg word handling 143 | 144 | options 145 | end 146 | end 147 | 148 | end 149 | -------------------------------------------------------------------------------- /lib/rerun/runner.rb: -------------------------------------------------------------------------------- 1 | require 'timeout' 2 | require 'io/wait' 3 | 4 | module Rerun 5 | class Runner 6 | 7 | # The watcher instance that wait for changes 8 | attr_reader :watcher 9 | 10 | def self.keep_running(cmd, options) 11 | runner = new(cmd, options) 12 | runner.start 13 | runner.join 14 | # apparently runner doesn't keep running anymore (as of Listen 2) so we have to sleep forever :-( 15 | sleep 10000 while true # :-( 16 | end 17 | 18 | include System 19 | include ::Timeout 20 | 21 | def initialize(run_command, options = {}) 22 | @run_command, @options = run_command, options 23 | @run_command = "ruby #{@run_command}" if @run_command.split(' ').first =~ /\.rb$/ 24 | @options[:directory] ||= options.delete(:dir) || '.' 25 | @options[:ignore] ||= [] 26 | end 27 | 28 | def start_keypress_thread 29 | return if @options[:background] 30 | 31 | @keypress_thread = Thread.new do 32 | while true 33 | if c = key_pressed 34 | case c.downcase 35 | when 'c' 36 | say "Clearing screen" 37 | clear_screen 38 | when 'r' 39 | say "Restarting" 40 | restart 41 | when 'f' 42 | say "Stopping and starting" 43 | restart(false) 44 | when 'p' 45 | toggle_pause 46 | when 'x', 'q' 47 | die 48 | break # the break will stop this thread, in case the 'die' doesn't 49 | else 50 | puts "\n#{c.inspect} pressed inside rerun" 51 | puts [["c", "clear screen"], 52 | ["r", "restart"], 53 | ["f", "forced restart (stop and start)"], 54 | ["p", "toggle pause"], 55 | ["x or q", "stop and exit"] 56 | ].map {|key, description| " #{key} -- #{description}"}.join("\n") 57 | puts 58 | end 59 | end 60 | sleep 1 # todo: use select instead of polling somehow? 61 | end 62 | end 63 | @keypress_thread.run 64 | end 65 | 66 | def stop_keypress_thread 67 | @keypress_thread.kill if @keypress_thread 68 | @keypress_thread = nil 69 | end 70 | 71 | def restart(with_signal = true) 72 | @restarting = true 73 | if @options[:restart] && with_signal 74 | restart_with_signal(@options[:signal]) 75 | else 76 | stop 77 | start 78 | end 79 | @restarting = false 80 | end 81 | 82 | def toggle_pause 83 | unless @pausing 84 | say "Pausing. Press 'p' again to resume." 85 | @watcher.pause 86 | @pausing = true 87 | else 88 | say "Resuming." 89 | @watcher.unpause 90 | @pausing = false 91 | end 92 | end 93 | 94 | def unpause 95 | @watcher.unpause 96 | end 97 | 98 | def dir 99 | @options[:directory] 100 | end 101 | 102 | def pattern 103 | @options[:pattern] 104 | end 105 | 106 | def clear? 107 | @options[:clear] 108 | end 109 | 110 | def quiet? 111 | @options[:quiet] 112 | end 113 | 114 | def verbose? 115 | @options[:verbose] 116 | end 117 | 118 | def exit? 119 | @options[:exit] 120 | end 121 | 122 | def app_name 123 | @options[:name] 124 | end 125 | 126 | def restart_with_signal(restart_signal) 127 | if @pid && (@pid != 0) 128 | notify "restarting", "We will be with you shortly." 129 | send_signal(restart_signal) 130 | end 131 | end 132 | 133 | def force_polling 134 | @options[:force_polling] 135 | end 136 | 137 | def start 138 | if @already_running 139 | taglines = [ 140 | "Here we go again!", 141 | "Keep on trucking.", 142 | "Once more unto the breach, dear friends, once more!", 143 | "The road goes ever on and on, down from the door where it began.", 144 | ] 145 | notify "restarted", taglines[rand(taglines.size)] 146 | else 147 | taglines = [ 148 | "To infinity... and beyond!", 149 | "Charge!", 150 | ] 151 | notify "launched", taglines[rand(taglines.size)] 152 | @already_running = true 153 | end 154 | 155 | clear_screen if clear? 156 | start_keypress_thread unless @keypress_thread 157 | 158 | begin 159 | @pid = run @run_command 160 | say "Rerun (#{$PID}) running #{app_name} (#{@pid})" 161 | rescue => e 162 | puts "#{e.class}: #{e.message}" 163 | exit 164 | end 165 | 166 | status_thread = Process.detach(@pid) # so if the child exits, it dies 167 | 168 | Signal.trap("INT") do # INT = control-C -- allows user to stop the top-level rerun process 169 | die 170 | end 171 | 172 | Signal.trap("TERM") do # TERM is the polite way of terminating a process 173 | die 174 | end 175 | 176 | begin 177 | sleep 2 178 | rescue Interrupt => e 179 | # in case someone hits control-C immediately ("oops!") 180 | die 181 | end 182 | 183 | if exit? 184 | status = status_thread.value 185 | if status.success? 186 | notify "succeeded", "" 187 | else 188 | notify "failed", "Exit status #{status.exitstatus}" 189 | end 190 | else 191 | if !running? 192 | notify "Launch Failed", "See console for error output" 193 | @already_running = false 194 | end 195 | end 196 | 197 | unless @watcher 198 | watcher = Watcher.new(@options) do |changes| 199 | message = change_message(changes) 200 | say "Change detected: #{message}" 201 | restart unless @restarting 202 | end 203 | watcher.start 204 | @watcher = watcher 205 | ignore = @options[:ignore] 206 | say "Watching #{dir.join(', ')} for #{pattern}" + 207 | (ignore.empty? ? "" : " (ignoring #{ignore.join(',')})") + 208 | (watcher.adapter.nil? ? "" : " with #{watcher.adapter_name} adapter") 209 | end 210 | end 211 | 212 | def run command 213 | Kernel.spawn command 214 | end 215 | 216 | def change_message(changes) 217 | message = [:modified, :added, :removed].map do |change| 218 | count = changes[change] ? changes[change].size : 0 219 | if count > 0 220 | "#{count} #{change}" 221 | end 222 | end.compact.join(", ") 223 | 224 | changed_files = changes.values.flatten 225 | if changed_files.count > 0 226 | message += ": " 227 | message += changes.values.flatten[0..3].map {|path| path.split('/').last}.join(', ') 228 | if changed_files.count > 3 229 | message += ", ..." 230 | end 231 | end 232 | message 233 | end 234 | 235 | def die 236 | #stop_keypress_thread # don't do this since we're probably *in* the keypress thread 237 | stop # stop the child process if it exists 238 | exit 0 # todo: status code param 239 | end 240 | 241 | def join 242 | @watcher.join 243 | end 244 | 245 | def running? 246 | send_signal(0) 247 | end 248 | 249 | # Send the signal to process @pid and wait for it to die. 250 | # @returns true if the process dies 251 | # @returns false if either sending the signal fails or the process fails to die 252 | def signal_and_wait(signal) 253 | 254 | signal_sent = if windows? 255 | force_kill = (signal == 'KILL') 256 | system("taskkill /T #{'/F' if force_kill} /PID #{@pid}") 257 | else 258 | send_signal(signal) 259 | end 260 | 261 | if signal_sent 262 | # the signal was successfully sent, so wait for the process to die 263 | begin 264 | timeout(@options[:wait]) do 265 | Process.wait(@pid) 266 | end 267 | process_status = $? 268 | say "Process ended: #{process_status}" if verbose? 269 | true 270 | rescue Timeout::Error 271 | false 272 | end 273 | else 274 | false 275 | end 276 | end 277 | 278 | # Send the signal to process @pid. 279 | # @returns true if the signal is sent 280 | # @returns false if sending the signal fails 281 | # If sending the signal fails, the exception will be swallowed 282 | # (and logged if verbose is true) and this method will return false. 283 | # 284 | def send_signal(signal) 285 | say "Sending signal #{signal} to #{@pid}" unless signal == 0 if verbose? 286 | Process.kill(signal, @pid) 287 | true 288 | rescue => e 289 | say "Signal #{signal} failed: #{e.class}: #{e.message}" if verbose? 290 | false 291 | end 292 | 293 | # todo: test escalation 294 | def stop 295 | if @pid && (@pid != 0) 296 | notify "stopping", "All good things must come to an end." unless @restarting 297 | @options[:signal].split(',').each do |signal| 298 | success = signal_and_wait(signal) 299 | return true if success 300 | end 301 | end 302 | rescue 303 | false 304 | end 305 | 306 | def git_head_changed? 307 | old_git_head = @git_head 308 | read_git_head 309 | @git_head and old_git_head and @git_head != old_git_head 310 | end 311 | 312 | def read_git_head 313 | git_head_file = File.join(dir, '.git', 'HEAD') 314 | @git_head = File.exist?(git_head_file) && File.read(git_head_file) 315 | end 316 | 317 | def notify(title, body, background = true) 318 | Notification.new(title, body, @options).send(background) if @options[:notify] 319 | puts 320 | say "#{app_name} #{title}" 321 | end 322 | 323 | def say msg 324 | puts "#{Time.now.strftime("%T")} [rerun] #{msg}" unless quiet? 325 | end 326 | 327 | def stty(args) 328 | system "stty #{args}" 329 | end 330 | 331 | # non-blocking stdin reader. 332 | # returns a 1-char string if a key was pressed; otherwise nil 333 | # 334 | def key_pressed 335 | return one_char if windows? 336 | begin 337 | # this "raw input" nonsense is because unix likes waiting for linefeeds before sending stdin 338 | 339 | # 'raw' means turn raw input on 340 | 341 | # restore proper output newline handling -- see stty.rb and "man stty" and /usr/include/sys/termios.h 342 | # looks like "raw" flips off the OPOST bit 0x00000001 /* enable following output processing */ 343 | # which disables #define ONLCR 0x00000002 /* map NL to CR-NL (ala CRMOD) */ 344 | # so this sets it back on again since all we care about is raw input, not raw output 345 | stty "raw opost" 346 | one_char 347 | ensure 348 | stty "-raw" # turn raw input off 349 | end 350 | 351 | # note: according to 'man tty' the proper way restore the settings is 352 | # tty_state=`stty -g` 353 | # ensure 354 | # system 'stty "#{tty_state}' 355 | # end 356 | # but this way seems fine and less confusing 357 | end 358 | 359 | def clear_screen 360 | # see http://ascii-table.com/ansi-escape-sequences-vt-100.php 361 | $stdout.print "\033[H\033[2J" 362 | end 363 | 364 | private 365 | def one_char 366 | c = nil 367 | if $stdin.ready? 368 | c = $stdin.getc 369 | end 370 | c.chr if c 371 | end 372 | 373 | end 374 | end 375 | -------------------------------------------------------------------------------- /lib/rerun/system.rb: -------------------------------------------------------------------------------- 1 | module Rerun 2 | module System 3 | 4 | def mac? 5 | RUBY_PLATFORM =~ /darwin/i 6 | end 7 | 8 | def windows? 9 | RUBY_PLATFORM =~ /(mswin|mingw32)/i 10 | end 11 | 12 | def linux? 13 | RUBY_PLATFORM =~ /linux/i 14 | end 15 | 16 | def rails? 17 | rails_sig_file = File.expand_path(".")+"/config/boot.rb" 18 | File.exist? rails_sig_file 19 | end 20 | 21 | end 22 | end 23 | -------------------------------------------------------------------------------- /lib/rerun/watcher.rb: -------------------------------------------------------------------------------- 1 | require 'listen' 2 | 3 | Thread.abort_on_exception = true 4 | 5 | # This class will watch a directory and alert you of 6 | # new files, modified files, deleted files. 7 | # 8 | # Now uses the Listen gem, but spawns its own thread on top. 9 | # We should probably be accessing the Listen thread directly. 10 | # 11 | # Author: Alex Chaffee 12 | # 13 | module Rerun 14 | class Watcher 15 | InvalidDirectoryError = Class.new(RuntimeError) 16 | 17 | #def self.default_ignore 18 | # Listen::Silencer.new(Listen::Listener.new).send :_default_ignore_patterns 19 | #end 20 | 21 | attr_reader :directory, :pattern, :priority, :ignore_dotfiles 22 | 23 | # Create a file system watcher. Start it by calling #start. 24 | # 25 | # @param options[:directory] the directory to watch (default ".") 26 | # @param options[:pattern] the glob pattern to search under the watched directory (default "**/*") 27 | # @param options[:priority] the priority of the watcher thread (default 0) 28 | # 29 | def initialize(options = {}, &client_callback) 30 | @client_callback = client_callback 31 | 32 | options = { 33 | :directory => ".", 34 | :pattern => "**/*", 35 | :priority => 0, 36 | :ignore_dotfiles => true, 37 | }.merge(options) 38 | 39 | @pattern = options[:pattern] 40 | @directories = options[:directory] 41 | @directories = sanitize_dirs(@directories) 42 | @priority = options[:priority] 43 | @force_polling = options[:force_polling] 44 | @ignore = [options[:ignore]].flatten.compact 45 | @ignore_dotfiles = options[:ignore_dotfiles] 46 | @thread = nil 47 | end 48 | 49 | def sanitize_dirs(dirs) 50 | dirs = [*dirs] 51 | dirs.map do |d| 52 | d.chomp!("/") 53 | unless FileTest.exist?(d) && FileTest.readable?(d) && FileTest.directory?(d) 54 | raise InvalidDirectoryError, "Directory '#{d}' either doesnt exist or isn't readable" 55 | end 56 | File.expand_path(d) 57 | end 58 | end 59 | 60 | def start 61 | if @thread then 62 | raise RuntimeError, "already started" 63 | end 64 | 65 | @thread = Thread.new do 66 | @listener = Listen.to(*@directories, only: watching, ignore: ignoring, wait_for_delay: 1, force_polling: @force_polling) do |modified, added, removed| 67 | count = modified.size + added.size + removed.size 68 | if count > 0 69 | @client_callback.call(:modified => modified, :added => added, :removed => removed) 70 | end 71 | end 72 | @listener.start 73 | end 74 | 75 | @thread.priority = @priority 76 | 77 | sleep 0.1 until @listener 78 | 79 | at_exit { stop } # try really hard to clean up after ourselves 80 | end 81 | 82 | def watching 83 | Rerun::Glob.new(@pattern).to_regexp 84 | end 85 | 86 | def ignoring 87 | patterns = [] 88 | if ignore_dotfiles 89 | patterns << /^\.[^.]/ # at beginning of string, a real dot followed by any other character 90 | end 91 | patterns + @ignore.map { |x| Rerun::Glob.new(x).to_regexp } 92 | end 93 | 94 | # kill the file watcher thread 95 | def stop 96 | @thread.wakeup rescue ThreadError 97 | begin 98 | @listener.stop 99 | rescue Exception => e 100 | puts "#{e.class}: #{e.message} stopping listener" 101 | end 102 | @thread.kill rescue ThreadError 103 | end 104 | 105 | # wait for the file watcher to finish 106 | def join 107 | @thread.join if @thread 108 | rescue Interrupt 109 | # don't care 110 | end 111 | 112 | def pause 113 | @listener.pause if @listener 114 | end 115 | 116 | def unpause 117 | @listener.start if @listener 118 | end 119 | 120 | def running? 121 | @listener && @listener.processing? 122 | end 123 | 124 | def adapter 125 | @listener && 126 | (backend = @listener.instance_variable_get(:@backend)) && 127 | backend.instance_variable_get(:@adapter) 128 | end 129 | 130 | def adapter_name 131 | adapter && adapter.class.name.split('::').last 132 | end 133 | end 134 | end 135 | -------------------------------------------------------------------------------- /pulls.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "url": "https://api.github.com/repos/alexch/rerun/pulls/61", 4 | "id": 16296103, 5 | "html_url": "https://github.com/alexch/rerun/pull/61", 6 | "diff_url": "https://github.com/alexch/rerun/pull/61.diff", 7 | "patch_url": "https://github.com/alexch/rerun/pull/61.patch", 8 | "issue_url": "https://api.github.com/repos/alexch/rerun/issues/61", 9 | "number": 61, 10 | "state": "open", 11 | "title": "load .rerun file from $HOME and project directory in case it exists", 12 | "user": { 13 | "login": "dagi3d", 14 | "id": 11283, 15 | "avatar_url": "https://avatars.githubusercontent.com/u/11283?v=1", 16 | "gravatar_id": "90ea347c45cdfbc1c5767dd6304d9c10", 17 | "url": "https://api.github.com/users/dagi3d", 18 | "html_url": "https://github.com/dagi3d", 19 | "followers_url": "https://api.github.com/users/dagi3d/followers", 20 | "following_url": "https://api.github.com/users/dagi3d/following{/other_user}", 21 | "gists_url": "https://api.github.com/users/dagi3d/gists{/gist_id}", 22 | "starred_url": "https://api.github.com/users/dagi3d/starred{/owner}{/repo}", 23 | "subscriptions_url": "https://api.github.com/users/dagi3d/subscriptions", 24 | "organizations_url": "https://api.github.com/users/dagi3d/orgs", 25 | "repos_url": "https://api.github.com/users/dagi3d/repos", 26 | "events_url": "https://api.github.com/users/dagi3d/events{/privacy}", 27 | "received_events_url": "https://api.github.com/users/dagi3d/received_events", 28 | "type": "User", 29 | "site_admin": false 30 | }, 31 | "body": "Now it loads the .rerun file from the project directory and $HOME in case it exists\r\nCommand line arguments have precedence over the params declared in the project\r\nParams declared in the project directory have precedence over the params in the $HOME directory\r\n\r\n", 32 | "created_at": "2014-05-23T21:40:31Z", 33 | "updated_at": "2014-06-12T21:45:05Z", 34 | "closed_at": null, 35 | "merged_at": null, 36 | "merge_commit_sha": "3f3c0816b3542de2798aea3cc16fa63064a500ea", 37 | "assignee": null, 38 | "milestone": null, 39 | "commits_url": "https://api.github.com/repos/alexch/rerun/pulls/61/commits", 40 | "review_comments_url": "https://api.github.com/repos/alexch/rerun/pulls/61/comments", 41 | "review_comment_url": "https://api.github.com/repos/alexch/rerun/pulls/comments/{number}", 42 | "comments_url": "https://api.github.com/repos/alexch/rerun/issues/61/comments", 43 | "statuses_url": "https://api.github.com/repos/alexch/rerun/statuses/dd4498d4a5236ceeb102fab2612bd5f18e7617bd", 44 | "head": { 45 | "label": "dagi3d:master", 46 | "ref": "master", 47 | "sha": "dd4498d4a5236ceeb102fab2612bd5f18e7617bd", 48 | "user": { 49 | "login": "dagi3d", 50 | "id": 11283, 51 | "avatar_url": "https://avatars.githubusercontent.com/u/11283?v=1", 52 | "gravatar_id": "90ea347c45cdfbc1c5767dd6304d9c10", 53 | "url": "https://api.github.com/users/dagi3d", 54 | "html_url": "https://github.com/dagi3d", 55 | "followers_url": "https://api.github.com/users/dagi3d/followers", 56 | "following_url": "https://api.github.com/users/dagi3d/following{/other_user}", 57 | "gists_url": "https://api.github.com/users/dagi3d/gists{/gist_id}", 58 | "starred_url": "https://api.github.com/users/dagi3d/starred{/owner}{/repo}", 59 | "subscriptions_url": "https://api.github.com/users/dagi3d/subscriptions", 60 | "organizations_url": "https://api.github.com/users/dagi3d/orgs", 61 | "repos_url": "https://api.github.com/users/dagi3d/repos", 62 | "events_url": "https://api.github.com/users/dagi3d/events{/privacy}", 63 | "received_events_url": "https://api.github.com/users/dagi3d/received_events", 64 | "type": "User", 65 | "site_admin": false 66 | }, 67 | "repo": { 68 | "id": 20039153, 69 | "name": "rerun", 70 | "full_name": "dagi3d/rerun", 71 | "owner": { 72 | "login": "dagi3d", 73 | "id": 11283, 74 | "avatar_url": "https://avatars.githubusercontent.com/u/11283?v=1", 75 | "gravatar_id": "90ea347c45cdfbc1c5767dd6304d9c10", 76 | "url": "https://api.github.com/users/dagi3d", 77 | "html_url": "https://github.com/dagi3d", 78 | "followers_url": "https://api.github.com/users/dagi3d/followers", 79 | "following_url": "https://api.github.com/users/dagi3d/following{/other_user}", 80 | "gists_url": "https://api.github.com/users/dagi3d/gists{/gist_id}", 81 | "starred_url": "https://api.github.com/users/dagi3d/starred{/owner}{/repo}", 82 | "subscriptions_url": "https://api.github.com/users/dagi3d/subscriptions", 83 | "organizations_url": "https://api.github.com/users/dagi3d/orgs", 84 | "repos_url": "https://api.github.com/users/dagi3d/repos", 85 | "events_url": "https://api.github.com/users/dagi3d/events{/privacy}", 86 | "received_events_url": "https://api.github.com/users/dagi3d/received_events", 87 | "type": "User", 88 | "site_admin": false 89 | }, 90 | "private": false, 91 | "html_url": "https://github.com/dagi3d/rerun", 92 | "description": "Restarts an app when the filesystem changes. Uses growl and FSEventStream if on OS X.", 93 | "fork": true, 94 | "url": "https://api.github.com/repos/dagi3d/rerun", 95 | "forks_url": "https://api.github.com/repos/dagi3d/rerun/forks", 96 | "keys_url": "https://api.github.com/repos/dagi3d/rerun/keys{/key_id}", 97 | "collaborators_url": "https://api.github.com/repos/dagi3d/rerun/collaborators{/collaborator}", 98 | "teams_url": "https://api.github.com/repos/dagi3d/rerun/teams", 99 | "hooks_url": "https://api.github.com/repos/dagi3d/rerun/hooks", 100 | "issue_events_url": "https://api.github.com/repos/dagi3d/rerun/issues/events{/number}", 101 | "events_url": "https://api.github.com/repos/dagi3d/rerun/events", 102 | "assignees_url": "https://api.github.com/repos/dagi3d/rerun/assignees{/user}", 103 | "branches_url": "https://api.github.com/repos/dagi3d/rerun/branches{/branch}", 104 | "tags_url": "https://api.github.com/repos/dagi3d/rerun/tags", 105 | "blobs_url": "https://api.github.com/repos/dagi3d/rerun/git/blobs{/sha}", 106 | "git_tags_url": "https://api.github.com/repos/dagi3d/rerun/git/tags{/sha}", 107 | "git_refs_url": "https://api.github.com/repos/dagi3d/rerun/git/refs{/sha}", 108 | "trees_url": "https://api.github.com/repos/dagi3d/rerun/git/trees{/sha}", 109 | "statuses_url": "https://api.github.com/repos/dagi3d/rerun/statuses/{sha}", 110 | "languages_url": "https://api.github.com/repos/dagi3d/rerun/languages", 111 | "stargazers_url": "https://api.github.com/repos/dagi3d/rerun/stargazers", 112 | "contributors_url": "https://api.github.com/repos/dagi3d/rerun/contributors", 113 | "subscribers_url": "https://api.github.com/repos/dagi3d/rerun/subscribers", 114 | "subscription_url": "https://api.github.com/repos/dagi3d/rerun/subscription", 115 | "commits_url": "https://api.github.com/repos/dagi3d/rerun/commits{/sha}", 116 | "git_commits_url": "https://api.github.com/repos/dagi3d/rerun/git/commits{/sha}", 117 | "comments_url": "https://api.github.com/repos/dagi3d/rerun/comments{/number}", 118 | "issue_comment_url": "https://api.github.com/repos/dagi3d/rerun/issues/comments/{number}", 119 | "contents_url": "https://api.github.com/repos/dagi3d/rerun/contents/{+path}", 120 | "compare_url": "https://api.github.com/repos/dagi3d/rerun/compare/{base}...{head}", 121 | "merges_url": "https://api.github.com/repos/dagi3d/rerun/merges", 122 | "archive_url": "https://api.github.com/repos/dagi3d/rerun/{archive_format}{/ref}", 123 | "downloads_url": "https://api.github.com/repos/dagi3d/rerun/downloads", 124 | "issues_url": "https://api.github.com/repos/dagi3d/rerun/issues{/number}", 125 | "pulls_url": "https://api.github.com/repos/dagi3d/rerun/pulls{/number}", 126 | "milestones_url": "https://api.github.com/repos/dagi3d/rerun/milestones{/number}", 127 | "notifications_url": "https://api.github.com/repos/dagi3d/rerun/notifications{?since,all,participating}", 128 | "labels_url": "https://api.github.com/repos/dagi3d/rerun/labels{/name}", 129 | "releases_url": "https://api.github.com/repos/dagi3d/rerun/releases{/id}", 130 | "created_at": "2014-05-21T22:10:20Z", 131 | "updated_at": "2014-05-23T21:40:31Z", 132 | "pushed_at": "2014-05-23T21:35:42Z", 133 | "git_url": "git://github.com/dagi3d/rerun.git", 134 | "ssh_url": "git@github.com:dagi3d/rerun.git", 135 | "clone_url": "https://github.com/dagi3d/rerun.git", 136 | "svn_url": "https://github.com/dagi3d/rerun", 137 | "homepage": "", 138 | "size": 328, 139 | "stargazers_count": 0, 140 | "watchers_count": 0, 141 | "language": "Ruby", 142 | "has_issues": false, 143 | "has_downloads": true, 144 | "has_wiki": true, 145 | "forks_count": 0, 146 | "mirror_url": null, 147 | "open_issues_count": 0, 148 | "forks": 0, 149 | "open_issues": 0, 150 | "watchers": 0, 151 | "default_branch": "master" 152 | } 153 | }, 154 | "base": { 155 | "label": "alexch:master", 156 | "ref": "master", 157 | "sha": "43bfadea9006da31ff965985ecbedea56fe05847", 158 | "user": { 159 | "login": "alexch", 160 | "id": 8524, 161 | "avatar_url": "https://avatars.githubusercontent.com/u/8524?v=1", 162 | "gravatar_id": "5a0d7f0cb2fac7858d234de7f7f01491", 163 | "url": "https://api.github.com/users/alexch", 164 | "html_url": "https://github.com/alexch", 165 | "followers_url": "https://api.github.com/users/alexch/followers", 166 | "following_url": "https://api.github.com/users/alexch/following{/other_user}", 167 | "gists_url": "https://api.github.com/users/alexch/gists{/gist_id}", 168 | "starred_url": "https://api.github.com/users/alexch/starred{/owner}{/repo}", 169 | "subscriptions_url": "https://api.github.com/users/alexch/subscriptions", 170 | "organizations_url": "https://api.github.com/users/alexch/orgs", 171 | "repos_url": "https://api.github.com/users/alexch/repos", 172 | "events_url": "https://api.github.com/users/alexch/events{/privacy}", 173 | "received_events_url": "https://api.github.com/users/alexch/received_events", 174 | "type": "User", 175 | "site_admin": false 176 | }, 177 | "repo": { 178 | "id": 221696, 179 | "name": "rerun", 180 | "full_name": "alexch/rerun", 181 | "owner": { 182 | "login": "alexch", 183 | "id": 8524, 184 | "avatar_url": "https://avatars.githubusercontent.com/u/8524?v=1", 185 | "gravatar_id": "5a0d7f0cb2fac7858d234de7f7f01491", 186 | "url": "https://api.github.com/users/alexch", 187 | "html_url": "https://github.com/alexch", 188 | "followers_url": "https://api.github.com/users/alexch/followers", 189 | "following_url": "https://api.github.com/users/alexch/following{/other_user}", 190 | "gists_url": "https://api.github.com/users/alexch/gists{/gist_id}", 191 | "starred_url": "https://api.github.com/users/alexch/starred{/owner}{/repo}", 192 | "subscriptions_url": "https://api.github.com/users/alexch/subscriptions", 193 | "organizations_url": "https://api.github.com/users/alexch/orgs", 194 | "repos_url": "https://api.github.com/users/alexch/repos", 195 | "events_url": "https://api.github.com/users/alexch/events{/privacy}", 196 | "received_events_url": "https://api.github.com/users/alexch/received_events", 197 | "type": "User", 198 | "site_admin": false 199 | }, 200 | "private": false, 201 | "html_url": "https://github.com/alexch/rerun", 202 | "description": "Restarts an app when the filesystem changes. Uses growl and FSEventStream if on OS X.", 203 | "fork": false, 204 | "url": "https://api.github.com/repos/alexch/rerun", 205 | "forks_url": "https://api.github.com/repos/alexch/rerun/forks", 206 | "keys_url": "https://api.github.com/repos/alexch/rerun/keys{/key_id}", 207 | "collaborators_url": "https://api.github.com/repos/alexch/rerun/collaborators{/collaborator}", 208 | "teams_url": "https://api.github.com/repos/alexch/rerun/teams", 209 | "hooks_url": "https://api.github.com/repos/alexch/rerun/hooks", 210 | "issue_events_url": "https://api.github.com/repos/alexch/rerun/issues/events{/number}", 211 | "events_url": "https://api.github.com/repos/alexch/rerun/events", 212 | "assignees_url": "https://api.github.com/repos/alexch/rerun/assignees{/user}", 213 | "branches_url": "https://api.github.com/repos/alexch/rerun/branches{/branch}", 214 | "tags_url": "https://api.github.com/repos/alexch/rerun/tags", 215 | "blobs_url": "https://api.github.com/repos/alexch/rerun/git/blobs{/sha}", 216 | "git_tags_url": "https://api.github.com/repos/alexch/rerun/git/tags{/sha}", 217 | "git_refs_url": "https://api.github.com/repos/alexch/rerun/git/refs{/sha}", 218 | "trees_url": "https://api.github.com/repos/alexch/rerun/git/trees{/sha}", 219 | "statuses_url": "https://api.github.com/repos/alexch/rerun/statuses/{sha}", 220 | "languages_url": "https://api.github.com/repos/alexch/rerun/languages", 221 | "stargazers_url": "https://api.github.com/repos/alexch/rerun/stargazers", 222 | "contributors_url": "https://api.github.com/repos/alexch/rerun/contributors", 223 | "subscribers_url": "https://api.github.com/repos/alexch/rerun/subscribers", 224 | "subscription_url": "https://api.github.com/repos/alexch/rerun/subscription", 225 | "commits_url": "https://api.github.com/repos/alexch/rerun/commits{/sha}", 226 | "git_commits_url": "https://api.github.com/repos/alexch/rerun/git/commits{/sha}", 227 | "comments_url": "https://api.github.com/repos/alexch/rerun/comments{/number}", 228 | "issue_comment_url": "https://api.github.com/repos/alexch/rerun/issues/comments/{number}", 229 | "contents_url": "https://api.github.com/repos/alexch/rerun/contents/{+path}", 230 | "compare_url": "https://api.github.com/repos/alexch/rerun/compare/{base}...{head}", 231 | "merges_url": "https://api.github.com/repos/alexch/rerun/merges", 232 | "archive_url": "https://api.github.com/repos/alexch/rerun/{archive_format}{/ref}", 233 | "downloads_url": "https://api.github.com/repos/alexch/rerun/downloads", 234 | "issues_url": "https://api.github.com/repos/alexch/rerun/issues{/number}", 235 | "pulls_url": "https://api.github.com/repos/alexch/rerun/pulls{/number}", 236 | "milestones_url": "https://api.github.com/repos/alexch/rerun/milestones{/number}", 237 | "notifications_url": "https://api.github.com/repos/alexch/rerun/notifications{?since,all,participating}", 238 | "labels_url": "https://api.github.com/repos/alexch/rerun/labels{/name}", 239 | "releases_url": "https://api.github.com/repos/alexch/rerun/releases{/id}", 240 | "created_at": "2009-06-08T15:59:27Z", 241 | "updated_at": "2014-07-28T07:08:07Z", 242 | "pushed_at": "2014-05-05T15:09:08Z", 243 | "git_url": "git://github.com/alexch/rerun.git", 244 | "ssh_url": "git@github.com:alexch/rerun.git", 245 | "clone_url": "https://github.com/alexch/rerun.git", 246 | "svn_url": "https://github.com/alexch/rerun", 247 | "homepage": "", 248 | "size": 744, 249 | "stargazers_count": 382, 250 | "watchers_count": 382, 251 | "language": "Ruby", 252 | "has_issues": true, 253 | "has_downloads": true, 254 | "has_wiki": true, 255 | "forks_count": 29, 256 | "mirror_url": null, 257 | "open_issues_count": 22, 258 | "forks": 29, 259 | "open_issues": 22, 260 | "watchers": 382, 261 | "default_branch": "master" 262 | } 263 | }, 264 | "_links": { 265 | "self": { 266 | "href": "https://api.github.com/repos/alexch/rerun/pulls/61" 267 | }, 268 | "html": { 269 | "href": "https://github.com/alexch/rerun/pull/61" 270 | }, 271 | "issue": { 272 | "href": "https://api.github.com/repos/alexch/rerun/issues/61" 273 | }, 274 | "comments": { 275 | "href": "https://api.github.com/repos/alexch/rerun/issues/61/comments" 276 | }, 277 | "review_comments": { 278 | "href": "https://api.github.com/repos/alexch/rerun/pulls/61/comments" 279 | }, 280 | "review_comment": { 281 | "href": "https://api.github.com/repos/alexch/rerun/pulls/comments/{number}" 282 | }, 283 | "commits": { 284 | "href": "https://api.github.com/repos/alexch/rerun/pulls/61/commits" 285 | }, 286 | "statuses": { 287 | "href": "https://api.github.com/repos/alexch/rerun/statuses/dd4498d4a5236ceeb102fab2612bd5f18e7617bd" 288 | } 289 | } 290 | }, 291 | { 292 | "url": "https://api.github.com/repos/alexch/rerun/pulls/58", 293 | "id": 14990763, 294 | "html_url": "https://github.com/alexch/rerun/pull/58", 295 | "diff_url": "https://github.com/alexch/rerun/pull/58.diff", 296 | "patch_url": "https://github.com/alexch/rerun/pull/58.patch", 297 | "issue_url": "https://api.github.com/repos/alexch/rerun/issues/58", 298 | "number": 58, 299 | "state": "open", 300 | "title": "add --ignore-dotfiles and disable ignoring by default", 301 | "user": { 302 | "login": "robbles", 303 | "id": 92927, 304 | "avatar_url": "https://avatars.githubusercontent.com/u/92927?v=1", 305 | "gravatar_id": "33cd4933e841cf3c03ee8a3aed0585f7", 306 | "url": "https://api.github.com/users/robbles", 307 | "html_url": "https://github.com/robbles", 308 | "followers_url": "https://api.github.com/users/robbles/followers", 309 | "following_url": "https://api.github.com/users/robbles/following{/other_user}", 310 | "gists_url": "https://api.github.com/users/robbles/gists{/gist_id}", 311 | "starred_url": "https://api.github.com/users/robbles/starred{/owner}{/repo}", 312 | "subscriptions_url": "https://api.github.com/users/robbles/subscriptions", 313 | "organizations_url": "https://api.github.com/users/robbles/orgs", 314 | "repos_url": "https://api.github.com/users/robbles/repos", 315 | "events_url": "https://api.github.com/users/robbles/events{/privacy}", 316 | "received_events_url": "https://api.github.com/users/robbles/received_events", 317 | "type": "User", 318 | "site_admin": false 319 | }, 320 | "body": "I added an option to enable the filtering of dotfiles. It's disabled by default, because otherwise it's impossible with the current setup to detect changes to them (because of the order in which Listen finds & filters files).\r\n\r\nIf you think it should be enabled by default though, I'm open to changing this though.", 321 | "created_at": "2014-04-21T23:51:58Z", 322 | "updated_at": "2014-06-12T21:43:05Z", 323 | "closed_at": null, 324 | "merged_at": null, 325 | "merge_commit_sha": "bf34d1f5520d5b2abe576b5eea8d60ad74710990", 326 | "assignee": null, 327 | "milestone": null, 328 | "commits_url": "https://api.github.com/repos/alexch/rerun/pulls/58/commits", 329 | "review_comments_url": "https://api.github.com/repos/alexch/rerun/pulls/58/comments", 330 | "review_comment_url": "https://api.github.com/repos/alexch/rerun/pulls/comments/{number}", 331 | "comments_url": "https://api.github.com/repos/alexch/rerun/issues/58/comments", 332 | "statuses_url": "https://api.github.com/repos/alexch/rerun/statuses/d5ec3d6060f3d3ed77508646a19e532a8e331bdd", 333 | "head": { 334 | "label": "robbles:master", 335 | "ref": "master", 336 | "sha": "d5ec3d6060f3d3ed77508646a19e532a8e331bdd", 337 | "user": { 338 | "login": "robbles", 339 | "id": 92927, 340 | "avatar_url": "https://avatars.githubusercontent.com/u/92927?v=1", 341 | "gravatar_id": "33cd4933e841cf3c03ee8a3aed0585f7", 342 | "url": "https://api.github.com/users/robbles", 343 | "html_url": "https://github.com/robbles", 344 | "followers_url": "https://api.github.com/users/robbles/followers", 345 | "following_url": "https://api.github.com/users/robbles/following{/other_user}", 346 | "gists_url": "https://api.github.com/users/robbles/gists{/gist_id}", 347 | "starred_url": "https://api.github.com/users/robbles/starred{/owner}{/repo}", 348 | "subscriptions_url": "https://api.github.com/users/robbles/subscriptions", 349 | "organizations_url": "https://api.github.com/users/robbles/orgs", 350 | "repos_url": "https://api.github.com/users/robbles/repos", 351 | "events_url": "https://api.github.com/users/robbles/events{/privacy}", 352 | "received_events_url": "https://api.github.com/users/robbles/received_events", 353 | "type": "User", 354 | "site_admin": false 355 | }, 356 | "repo": { 357 | "id": 18780328, 358 | "name": "rerun", 359 | "full_name": "robbles/rerun", 360 | "owner": { 361 | "login": "robbles", 362 | "id": 92927, 363 | "avatar_url": "https://avatars.githubusercontent.com/u/92927?v=1", 364 | "gravatar_id": "33cd4933e841cf3c03ee8a3aed0585f7", 365 | "url": "https://api.github.com/users/robbles", 366 | "html_url": "https://github.com/robbles", 367 | "followers_url": "https://api.github.com/users/robbles/followers", 368 | "following_url": "https://api.github.com/users/robbles/following{/other_user}", 369 | "gists_url": "https://api.github.com/users/robbles/gists{/gist_id}", 370 | "starred_url": "https://api.github.com/users/robbles/starred{/owner}{/repo}", 371 | "subscriptions_url": "https://api.github.com/users/robbles/subscriptions", 372 | "organizations_url": "https://api.github.com/users/robbles/orgs", 373 | "repos_url": "https://api.github.com/users/robbles/repos", 374 | "events_url": "https://api.github.com/users/robbles/events{/privacy}", 375 | "received_events_url": "https://api.github.com/users/robbles/received_events", 376 | "type": "User", 377 | "site_admin": false 378 | }, 379 | "private": false, 380 | "html_url": "https://github.com/robbles/rerun", 381 | "description": "Restarts an app when the filesystem changes. Uses growl and FSEventStream if on OS X.", 382 | "fork": true, 383 | "url": "https://api.github.com/repos/robbles/rerun", 384 | "forks_url": "https://api.github.com/repos/robbles/rerun/forks", 385 | "keys_url": "https://api.github.com/repos/robbles/rerun/keys{/key_id}", 386 | "collaborators_url": "https://api.github.com/repos/robbles/rerun/collaborators{/collaborator}", 387 | "teams_url": "https://api.github.com/repos/robbles/rerun/teams", 388 | "hooks_url": "https://api.github.com/repos/robbles/rerun/hooks", 389 | "issue_events_url": "https://api.github.com/repos/robbles/rerun/issues/events{/number}", 390 | "events_url": "https://api.github.com/repos/robbles/rerun/events", 391 | "assignees_url": "https://api.github.com/repos/robbles/rerun/assignees{/user}", 392 | "branches_url": "https://api.github.com/repos/robbles/rerun/branches{/branch}", 393 | "tags_url": "https://api.github.com/repos/robbles/rerun/tags", 394 | "blobs_url": "https://api.github.com/repos/robbles/rerun/git/blobs{/sha}", 395 | "git_tags_url": "https://api.github.com/repos/robbles/rerun/git/tags{/sha}", 396 | "git_refs_url": "https://api.github.com/repos/robbles/rerun/git/refs{/sha}", 397 | "trees_url": "https://api.github.com/repos/robbles/rerun/git/trees{/sha}", 398 | "statuses_url": "https://api.github.com/repos/robbles/rerun/statuses/{sha}", 399 | "languages_url": "https://api.github.com/repos/robbles/rerun/languages", 400 | "stargazers_url": "https://api.github.com/repos/robbles/rerun/stargazers", 401 | "contributors_url": "https://api.github.com/repos/robbles/rerun/contributors", 402 | "subscribers_url": "https://api.github.com/repos/robbles/rerun/subscribers", 403 | "subscription_url": "https://api.github.com/repos/robbles/rerun/subscription", 404 | "commits_url": "https://api.github.com/repos/robbles/rerun/commits{/sha}", 405 | "git_commits_url": "https://api.github.com/repos/robbles/rerun/git/commits{/sha}", 406 | "comments_url": "https://api.github.com/repos/robbles/rerun/comments{/number}", 407 | "issue_comment_url": "https://api.github.com/repos/robbles/rerun/issues/comments/{number}", 408 | "contents_url": "https://api.github.com/repos/robbles/rerun/contents/{+path}", 409 | "compare_url": "https://api.github.com/repos/robbles/rerun/compare/{base}...{head}", 410 | "merges_url": "https://api.github.com/repos/robbles/rerun/merges", 411 | "archive_url": "https://api.github.com/repos/robbles/rerun/{archive_format}{/ref}", 412 | "downloads_url": "https://api.github.com/repos/robbles/rerun/downloads", 413 | "issues_url": "https://api.github.com/repos/robbles/rerun/issues{/number}", 414 | "pulls_url": "https://api.github.com/repos/robbles/rerun/pulls{/number}", 415 | "milestones_url": "https://api.github.com/repos/robbles/rerun/milestones{/number}", 416 | "notifications_url": "https://api.github.com/repos/robbles/rerun/notifications{?since,all,participating}", 417 | "labels_url": "https://api.github.com/repos/robbles/rerun/labels{/name}", 418 | "releases_url": "https://api.github.com/repos/robbles/rerun/releases{/id}", 419 | "created_at": "2014-04-14T23:22:08Z", 420 | "updated_at": "2014-04-21T23:51:58Z", 421 | "pushed_at": "2014-04-21T23:43:38Z", 422 | "git_url": "git://github.com/robbles/rerun.git", 423 | "ssh_url": "git@github.com:robbles/rerun.git", 424 | "clone_url": "https://github.com/robbles/rerun.git", 425 | "svn_url": "https://github.com/robbles/rerun", 426 | "homepage": "", 427 | "size": 300, 428 | "stargazers_count": 0, 429 | "watchers_count": 0, 430 | "language": "Ruby", 431 | "has_issues": false, 432 | "has_downloads": true, 433 | "has_wiki": true, 434 | "forks_count": 0, 435 | "mirror_url": null, 436 | "open_issues_count": 0, 437 | "forks": 0, 438 | "open_issues": 0, 439 | "watchers": 0, 440 | "default_branch": "master" 441 | } 442 | }, 443 | "base": { 444 | "label": "alexch:master", 445 | "ref": "master", 446 | "sha": "a7fd980db79b56e9e06bafe928e003562b7a48af", 447 | "user": { 448 | "login": "alexch", 449 | "id": 8524, 450 | "avatar_url": "https://avatars.githubusercontent.com/u/8524?v=1", 451 | "gravatar_id": "5a0d7f0cb2fac7858d234de7f7f01491", 452 | "url": "https://api.github.com/users/alexch", 453 | "html_url": "https://github.com/alexch", 454 | "followers_url": "https://api.github.com/users/alexch/followers", 455 | "following_url": "https://api.github.com/users/alexch/following{/other_user}", 456 | "gists_url": "https://api.github.com/users/alexch/gists{/gist_id}", 457 | "starred_url": "https://api.github.com/users/alexch/starred{/owner}{/repo}", 458 | "subscriptions_url": "https://api.github.com/users/alexch/subscriptions", 459 | "organizations_url": "https://api.github.com/users/alexch/orgs", 460 | "repos_url": "https://api.github.com/users/alexch/repos", 461 | "events_url": "https://api.github.com/users/alexch/events{/privacy}", 462 | "received_events_url": "https://api.github.com/users/alexch/received_events", 463 | "type": "User", 464 | "site_admin": false 465 | }, 466 | "repo": { 467 | "id": 221696, 468 | "name": "rerun", 469 | "full_name": "alexch/rerun", 470 | "owner": { 471 | "login": "alexch", 472 | "id": 8524, 473 | "avatar_url": "https://avatars.githubusercontent.com/u/8524?v=1", 474 | "gravatar_id": "5a0d7f0cb2fac7858d234de7f7f01491", 475 | "url": "https://api.github.com/users/alexch", 476 | "html_url": "https://github.com/alexch", 477 | "followers_url": "https://api.github.com/users/alexch/followers", 478 | "following_url": "https://api.github.com/users/alexch/following{/other_user}", 479 | "gists_url": "https://api.github.com/users/alexch/gists{/gist_id}", 480 | "starred_url": "https://api.github.com/users/alexch/starred{/owner}{/repo}", 481 | "subscriptions_url": "https://api.github.com/users/alexch/subscriptions", 482 | "organizations_url": "https://api.github.com/users/alexch/orgs", 483 | "repos_url": "https://api.github.com/users/alexch/repos", 484 | "events_url": "https://api.github.com/users/alexch/events{/privacy}", 485 | "received_events_url": "https://api.github.com/users/alexch/received_events", 486 | "type": "User", 487 | "site_admin": false 488 | }, 489 | "private": false, 490 | "html_url": "https://github.com/alexch/rerun", 491 | "description": "Restarts an app when the filesystem changes. Uses growl and FSEventStream if on OS X.", 492 | "fork": false, 493 | "url": "https://api.github.com/repos/alexch/rerun", 494 | "forks_url": "https://api.github.com/repos/alexch/rerun/forks", 495 | "keys_url": "https://api.github.com/repos/alexch/rerun/keys{/key_id}", 496 | "collaborators_url": "https://api.github.com/repos/alexch/rerun/collaborators{/collaborator}", 497 | "teams_url": "https://api.github.com/repos/alexch/rerun/teams", 498 | "hooks_url": "https://api.github.com/repos/alexch/rerun/hooks", 499 | "issue_events_url": "https://api.github.com/repos/alexch/rerun/issues/events{/number}", 500 | "events_url": "https://api.github.com/repos/alexch/rerun/events", 501 | "assignees_url": "https://api.github.com/repos/alexch/rerun/assignees{/user}", 502 | "branches_url": "https://api.github.com/repos/alexch/rerun/branches{/branch}", 503 | "tags_url": "https://api.github.com/repos/alexch/rerun/tags", 504 | "blobs_url": "https://api.github.com/repos/alexch/rerun/git/blobs{/sha}", 505 | "git_tags_url": "https://api.github.com/repos/alexch/rerun/git/tags{/sha}", 506 | "git_refs_url": "https://api.github.com/repos/alexch/rerun/git/refs{/sha}", 507 | "trees_url": "https://api.github.com/repos/alexch/rerun/git/trees{/sha}", 508 | "statuses_url": "https://api.github.com/repos/alexch/rerun/statuses/{sha}", 509 | "languages_url": "https://api.github.com/repos/alexch/rerun/languages", 510 | "stargazers_url": "https://api.github.com/repos/alexch/rerun/stargazers", 511 | "contributors_url": "https://api.github.com/repos/alexch/rerun/contributors", 512 | "subscribers_url": "https://api.github.com/repos/alexch/rerun/subscribers", 513 | "subscription_url": "https://api.github.com/repos/alexch/rerun/subscription", 514 | "commits_url": "https://api.github.com/repos/alexch/rerun/commits{/sha}", 515 | "git_commits_url": "https://api.github.com/repos/alexch/rerun/git/commits{/sha}", 516 | "comments_url": "https://api.github.com/repos/alexch/rerun/comments{/number}", 517 | "issue_comment_url": "https://api.github.com/repos/alexch/rerun/issues/comments/{number}", 518 | "contents_url": "https://api.github.com/repos/alexch/rerun/contents/{+path}", 519 | "compare_url": "https://api.github.com/repos/alexch/rerun/compare/{base}...{head}", 520 | "merges_url": "https://api.github.com/repos/alexch/rerun/merges", 521 | "archive_url": "https://api.github.com/repos/alexch/rerun/{archive_format}{/ref}", 522 | "downloads_url": "https://api.github.com/repos/alexch/rerun/downloads", 523 | "issues_url": "https://api.github.com/repos/alexch/rerun/issues{/number}", 524 | "pulls_url": "https://api.github.com/repos/alexch/rerun/pulls{/number}", 525 | "milestones_url": "https://api.github.com/repos/alexch/rerun/milestones{/number}", 526 | "notifications_url": "https://api.github.com/repos/alexch/rerun/notifications{?since,all,participating}", 527 | "labels_url": "https://api.github.com/repos/alexch/rerun/labels{/name}", 528 | "releases_url": "https://api.github.com/repos/alexch/rerun/releases{/id}", 529 | "created_at": "2009-06-08T15:59:27Z", 530 | "updated_at": "2014-07-28T07:08:07Z", 531 | "pushed_at": "2014-05-05T15:09:08Z", 532 | "git_url": "git://github.com/alexch/rerun.git", 533 | "ssh_url": "git@github.com:alexch/rerun.git", 534 | "clone_url": "https://github.com/alexch/rerun.git", 535 | "svn_url": "https://github.com/alexch/rerun", 536 | "homepage": "", 537 | "size": 744, 538 | "stargazers_count": 382, 539 | "watchers_count": 382, 540 | "language": "Ruby", 541 | "has_issues": true, 542 | "has_downloads": true, 543 | "has_wiki": true, 544 | "forks_count": 29, 545 | "mirror_url": null, 546 | "open_issues_count": 22, 547 | "forks": 29, 548 | "open_issues": 22, 549 | "watchers": 382, 550 | "default_branch": "master" 551 | } 552 | }, 553 | "_links": { 554 | "self": { 555 | "href": "https://api.github.com/repos/alexch/rerun/pulls/58" 556 | }, 557 | "html": { 558 | "href": "https://github.com/alexch/rerun/pull/58" 559 | }, 560 | "issue": { 561 | "href": "https://api.github.com/repos/alexch/rerun/issues/58" 562 | }, 563 | "comments": { 564 | "href": "https://api.github.com/repos/alexch/rerun/issues/58/comments" 565 | }, 566 | "review_comments": { 567 | "href": "https://api.github.com/repos/alexch/rerun/pulls/58/comments" 568 | }, 569 | "review_comment": { 570 | "href": "https://api.github.com/repos/alexch/rerun/pulls/comments/{number}" 571 | }, 572 | "commits": { 573 | "href": "https://api.github.com/repos/alexch/rerun/pulls/58/commits" 574 | }, 575 | "statuses": { 576 | "href": "https://api.github.com/repos/alexch/rerun/statuses/d5ec3d6060f3d3ed77508646a19e532a8e331bdd" 577 | } 578 | } 579 | }, 580 | { 581 | "url": "https://api.github.com/repos/alexch/rerun/pulls/56", 582 | "id": 13832440, 583 | "html_url": "https://github.com/alexch/rerun/pull/56", 584 | "diff_url": "https://github.com/alexch/rerun/pull/56.diff", 585 | "patch_url": "https://github.com/alexch/rerun/pull/56.patch", 586 | "issue_url": "https://api.github.com/repos/alexch/rerun/issues/56", 587 | "number": 56, 588 | "state": "open", 589 | "title": "Add feature files to default pattern and update README", 590 | "user": { 591 | "login": "jmuheim", 592 | "id": 1814983, 593 | "avatar_url": "https://avatars.githubusercontent.com/u/1814983?v=1", 594 | "gravatar_id": "fc582698581884352e745d1d4c64699d", 595 | "url": "https://api.github.com/users/jmuheim", 596 | "html_url": "https://github.com/jmuheim", 597 | "followers_url": "https://api.github.com/users/jmuheim/followers", 598 | "following_url": "https://api.github.com/users/jmuheim/following{/other_user}", 599 | "gists_url": "https://api.github.com/users/jmuheim/gists{/gist_id}", 600 | "starred_url": "https://api.github.com/users/jmuheim/starred{/owner}{/repo}", 601 | "subscriptions_url": "https://api.github.com/users/jmuheim/subscriptions", 602 | "organizations_url": "https://api.github.com/users/jmuheim/orgs", 603 | "repos_url": "https://api.github.com/users/jmuheim/repos", 604 | "events_url": "https://api.github.com/users/jmuheim/events{/privacy}", 605 | "received_events_url": "https://api.github.com/users/jmuheim/received_events", 606 | "type": "User", 607 | "site_admin": false 608 | }, 609 | "body": "`xxx.feature` files are commonly used for Gherkin based stuff, e.g. Cucumber or Turnip feature tests.\r\n\r\nI also noticed that your README wasn't up2date and fixed it.", 610 | "created_at": "2014-03-21T11:59:27Z", 611 | "updated_at": "2014-06-19T07:01:48Z", 612 | "closed_at": null, 613 | "merged_at": null, 614 | "merge_commit_sha": "c123ff17f966c306f4afff81609279d9fcace31f", 615 | "assignee": null, 616 | "milestone": null, 617 | "commits_url": "https://api.github.com/repos/alexch/rerun/pulls/56/commits", 618 | "review_comments_url": "https://api.github.com/repos/alexch/rerun/pulls/56/comments", 619 | "review_comment_url": "https://api.github.com/repos/alexch/rerun/pulls/comments/{number}", 620 | "comments_url": "https://api.github.com/repos/alexch/rerun/issues/56/comments", 621 | "statuses_url": "https://api.github.com/repos/alexch/rerun/statuses/1092c30ea86331bddb01b6afe28cada3d6f8f7ab", 622 | "head": { 623 | "label": "jmuheim:master", 624 | "ref": "master", 625 | "sha": "1092c30ea86331bddb01b6afe28cada3d6f8f7ab", 626 | "user": { 627 | "login": "jmuheim", 628 | "id": 1814983, 629 | "avatar_url": "https://avatars.githubusercontent.com/u/1814983?v=1", 630 | "gravatar_id": "fc582698581884352e745d1d4c64699d", 631 | "url": "https://api.github.com/users/jmuheim", 632 | "html_url": "https://github.com/jmuheim", 633 | "followers_url": "https://api.github.com/users/jmuheim/followers", 634 | "following_url": "https://api.github.com/users/jmuheim/following{/other_user}", 635 | "gists_url": "https://api.github.com/users/jmuheim/gists{/gist_id}", 636 | "starred_url": "https://api.github.com/users/jmuheim/starred{/owner}{/repo}", 637 | "subscriptions_url": "https://api.github.com/users/jmuheim/subscriptions", 638 | "organizations_url": "https://api.github.com/users/jmuheim/orgs", 639 | "repos_url": "https://api.github.com/users/jmuheim/repos", 640 | "events_url": "https://api.github.com/users/jmuheim/events{/privacy}", 641 | "received_events_url": "https://api.github.com/users/jmuheim/received_events", 642 | "type": "User", 643 | "site_admin": false 644 | }, 645 | "repo": { 646 | "id": 17978433, 647 | "name": "rerun", 648 | "full_name": "jmuheim/rerun", 649 | "owner": { 650 | "login": "jmuheim", 651 | "id": 1814983, 652 | "avatar_url": "https://avatars.githubusercontent.com/u/1814983?v=1", 653 | "gravatar_id": "fc582698581884352e745d1d4c64699d", 654 | "url": "https://api.github.com/users/jmuheim", 655 | "html_url": "https://github.com/jmuheim", 656 | "followers_url": "https://api.github.com/users/jmuheim/followers", 657 | "following_url": "https://api.github.com/users/jmuheim/following{/other_user}", 658 | "gists_url": "https://api.github.com/users/jmuheim/gists{/gist_id}", 659 | "starred_url": "https://api.github.com/users/jmuheim/starred{/owner}{/repo}", 660 | "subscriptions_url": "https://api.github.com/users/jmuheim/subscriptions", 661 | "organizations_url": "https://api.github.com/users/jmuheim/orgs", 662 | "repos_url": "https://api.github.com/users/jmuheim/repos", 663 | "events_url": "https://api.github.com/users/jmuheim/events{/privacy}", 664 | "received_events_url": "https://api.github.com/users/jmuheim/received_events", 665 | "type": "User", 666 | "site_admin": false 667 | }, 668 | "private": false, 669 | "html_url": "https://github.com/jmuheim/rerun", 670 | "description": "Restarts an app when the filesystem changes. Uses growl and FSEventStream if on OS X.", 671 | "fork": true, 672 | "url": "https://api.github.com/repos/jmuheim/rerun", 673 | "forks_url": "https://api.github.com/repos/jmuheim/rerun/forks", 674 | "keys_url": "https://api.github.com/repos/jmuheim/rerun/keys{/key_id}", 675 | "collaborators_url": "https://api.github.com/repos/jmuheim/rerun/collaborators{/collaborator}", 676 | "teams_url": "https://api.github.com/repos/jmuheim/rerun/teams", 677 | "hooks_url": "https://api.github.com/repos/jmuheim/rerun/hooks", 678 | "issue_events_url": "https://api.github.com/repos/jmuheim/rerun/issues/events{/number}", 679 | "events_url": "https://api.github.com/repos/jmuheim/rerun/events", 680 | "assignees_url": "https://api.github.com/repos/jmuheim/rerun/assignees{/user}", 681 | "branches_url": "https://api.github.com/repos/jmuheim/rerun/branches{/branch}", 682 | "tags_url": "https://api.github.com/repos/jmuheim/rerun/tags", 683 | "blobs_url": "https://api.github.com/repos/jmuheim/rerun/git/blobs{/sha}", 684 | "git_tags_url": "https://api.github.com/repos/jmuheim/rerun/git/tags{/sha}", 685 | "git_refs_url": "https://api.github.com/repos/jmuheim/rerun/git/refs{/sha}", 686 | "trees_url": "https://api.github.com/repos/jmuheim/rerun/git/trees{/sha}", 687 | "statuses_url": "https://api.github.com/repos/jmuheim/rerun/statuses/{sha}", 688 | "languages_url": "https://api.github.com/repos/jmuheim/rerun/languages", 689 | "stargazers_url": "https://api.github.com/repos/jmuheim/rerun/stargazers", 690 | "contributors_url": "https://api.github.com/repos/jmuheim/rerun/contributors", 691 | "subscribers_url": "https://api.github.com/repos/jmuheim/rerun/subscribers", 692 | "subscription_url": "https://api.github.com/repos/jmuheim/rerun/subscription", 693 | "commits_url": "https://api.github.com/repos/jmuheim/rerun/commits{/sha}", 694 | "git_commits_url": "https://api.github.com/repos/jmuheim/rerun/git/commits{/sha}", 695 | "comments_url": "https://api.github.com/repos/jmuheim/rerun/comments{/number}", 696 | "issue_comment_url": "https://api.github.com/repos/jmuheim/rerun/issues/comments/{number}", 697 | "contents_url": "https://api.github.com/repos/jmuheim/rerun/contents/{+path}", 698 | "compare_url": "https://api.github.com/repos/jmuheim/rerun/compare/{base}...{head}", 699 | "merges_url": "https://api.github.com/repos/jmuheim/rerun/merges", 700 | "archive_url": "https://api.github.com/repos/jmuheim/rerun/{archive_format}{/ref}", 701 | "downloads_url": "https://api.github.com/repos/jmuheim/rerun/downloads", 702 | "issues_url": "https://api.github.com/repos/jmuheim/rerun/issues{/number}", 703 | "pulls_url": "https://api.github.com/repos/jmuheim/rerun/pulls{/number}", 704 | "milestones_url": "https://api.github.com/repos/jmuheim/rerun/milestones{/number}", 705 | "notifications_url": "https://api.github.com/repos/jmuheim/rerun/notifications{?since,all,participating}", 706 | "labels_url": "https://api.github.com/repos/jmuheim/rerun/labels{/name}", 707 | "releases_url": "https://api.github.com/repos/jmuheim/rerun/releases{/id}", 708 | "created_at": "2014-03-21T11:54:15Z", 709 | "updated_at": "2014-03-21T11:59:27Z", 710 | "pushed_at": "2014-03-21T11:58:31Z", 711 | "git_url": "git://github.com/jmuheim/rerun.git", 712 | "ssh_url": "git@github.com:jmuheim/rerun.git", 713 | "clone_url": "https://github.com/jmuheim/rerun.git", 714 | "svn_url": "https://github.com/jmuheim/rerun", 715 | "homepage": "", 716 | "size": 299, 717 | "stargazers_count": 0, 718 | "watchers_count": 0, 719 | "language": "Ruby", 720 | "has_issues": false, 721 | "has_downloads": true, 722 | "has_wiki": true, 723 | "forks_count": 0, 724 | "mirror_url": null, 725 | "open_issues_count": 0, 726 | "forks": 0, 727 | "open_issues": 0, 728 | "watchers": 0, 729 | "default_branch": "master" 730 | } 731 | }, 732 | "base": { 733 | "label": "alexch:master", 734 | "ref": "master", 735 | "sha": "a7fd980db79b56e9e06bafe928e003562b7a48af", 736 | "user": { 737 | "login": "alexch", 738 | "id": 8524, 739 | "avatar_url": "https://avatars.githubusercontent.com/u/8524?v=1", 740 | "gravatar_id": "5a0d7f0cb2fac7858d234de7f7f01491", 741 | "url": "https://api.github.com/users/alexch", 742 | "html_url": "https://github.com/alexch", 743 | "followers_url": "https://api.github.com/users/alexch/followers", 744 | "following_url": "https://api.github.com/users/alexch/following{/other_user}", 745 | "gists_url": "https://api.github.com/users/alexch/gists{/gist_id}", 746 | "starred_url": "https://api.github.com/users/alexch/starred{/owner}{/repo}", 747 | "subscriptions_url": "https://api.github.com/users/alexch/subscriptions", 748 | "organizations_url": "https://api.github.com/users/alexch/orgs", 749 | "repos_url": "https://api.github.com/users/alexch/repos", 750 | "events_url": "https://api.github.com/users/alexch/events{/privacy}", 751 | "received_events_url": "https://api.github.com/users/alexch/received_events", 752 | "type": "User", 753 | "site_admin": false 754 | }, 755 | "repo": { 756 | "id": 221696, 757 | "name": "rerun", 758 | "full_name": "alexch/rerun", 759 | "owner": { 760 | "login": "alexch", 761 | "id": 8524, 762 | "avatar_url": "https://avatars.githubusercontent.com/u/8524?v=1", 763 | "gravatar_id": "5a0d7f0cb2fac7858d234de7f7f01491", 764 | "url": "https://api.github.com/users/alexch", 765 | "html_url": "https://github.com/alexch", 766 | "followers_url": "https://api.github.com/users/alexch/followers", 767 | "following_url": "https://api.github.com/users/alexch/following{/other_user}", 768 | "gists_url": "https://api.github.com/users/alexch/gists{/gist_id}", 769 | "starred_url": "https://api.github.com/users/alexch/starred{/owner}{/repo}", 770 | "subscriptions_url": "https://api.github.com/users/alexch/subscriptions", 771 | "organizations_url": "https://api.github.com/users/alexch/orgs", 772 | "repos_url": "https://api.github.com/users/alexch/repos", 773 | "events_url": "https://api.github.com/users/alexch/events{/privacy}", 774 | "received_events_url": "https://api.github.com/users/alexch/received_events", 775 | "type": "User", 776 | "site_admin": false 777 | }, 778 | "private": false, 779 | "html_url": "https://github.com/alexch/rerun", 780 | "description": "Restarts an app when the filesystem changes. Uses growl and FSEventStream if on OS X.", 781 | "fork": false, 782 | "url": "https://api.github.com/repos/alexch/rerun", 783 | "forks_url": "https://api.github.com/repos/alexch/rerun/forks", 784 | "keys_url": "https://api.github.com/repos/alexch/rerun/keys{/key_id}", 785 | "collaborators_url": "https://api.github.com/repos/alexch/rerun/collaborators{/collaborator}", 786 | "teams_url": "https://api.github.com/repos/alexch/rerun/teams", 787 | "hooks_url": "https://api.github.com/repos/alexch/rerun/hooks", 788 | "issue_events_url": "https://api.github.com/repos/alexch/rerun/issues/events{/number}", 789 | "events_url": "https://api.github.com/repos/alexch/rerun/events", 790 | "assignees_url": "https://api.github.com/repos/alexch/rerun/assignees{/user}", 791 | "branches_url": "https://api.github.com/repos/alexch/rerun/branches{/branch}", 792 | "tags_url": "https://api.github.com/repos/alexch/rerun/tags", 793 | "blobs_url": "https://api.github.com/repos/alexch/rerun/git/blobs{/sha}", 794 | "git_tags_url": "https://api.github.com/repos/alexch/rerun/git/tags{/sha}", 795 | "git_refs_url": "https://api.github.com/repos/alexch/rerun/git/refs{/sha}", 796 | "trees_url": "https://api.github.com/repos/alexch/rerun/git/trees{/sha}", 797 | "statuses_url": "https://api.github.com/repos/alexch/rerun/statuses/{sha}", 798 | "languages_url": "https://api.github.com/repos/alexch/rerun/languages", 799 | "stargazers_url": "https://api.github.com/repos/alexch/rerun/stargazers", 800 | "contributors_url": "https://api.github.com/repos/alexch/rerun/contributors", 801 | "subscribers_url": "https://api.github.com/repos/alexch/rerun/subscribers", 802 | "subscription_url": "https://api.github.com/repos/alexch/rerun/subscription", 803 | "commits_url": "https://api.github.com/repos/alexch/rerun/commits{/sha}", 804 | "git_commits_url": "https://api.github.com/repos/alexch/rerun/git/commits{/sha}", 805 | "comments_url": "https://api.github.com/repos/alexch/rerun/comments{/number}", 806 | "issue_comment_url": "https://api.github.com/repos/alexch/rerun/issues/comments/{number}", 807 | "contents_url": "https://api.github.com/repos/alexch/rerun/contents/{+path}", 808 | "compare_url": "https://api.github.com/repos/alexch/rerun/compare/{base}...{head}", 809 | "merges_url": "https://api.github.com/repos/alexch/rerun/merges", 810 | "archive_url": "https://api.github.com/repos/alexch/rerun/{archive_format}{/ref}", 811 | "downloads_url": "https://api.github.com/repos/alexch/rerun/downloads", 812 | "issues_url": "https://api.github.com/repos/alexch/rerun/issues{/number}", 813 | "pulls_url": "https://api.github.com/repos/alexch/rerun/pulls{/number}", 814 | "milestones_url": "https://api.github.com/repos/alexch/rerun/milestones{/number}", 815 | "notifications_url": "https://api.github.com/repos/alexch/rerun/notifications{?since,all,participating}", 816 | "labels_url": "https://api.github.com/repos/alexch/rerun/labels{/name}", 817 | "releases_url": "https://api.github.com/repos/alexch/rerun/releases{/id}", 818 | "created_at": "2009-06-08T15:59:27Z", 819 | "updated_at": "2014-07-28T07:08:07Z", 820 | "pushed_at": "2014-05-05T15:09:08Z", 821 | "git_url": "git://github.com/alexch/rerun.git", 822 | "ssh_url": "git@github.com:alexch/rerun.git", 823 | "clone_url": "https://github.com/alexch/rerun.git", 824 | "svn_url": "https://github.com/alexch/rerun", 825 | "homepage": "", 826 | "size": 744, 827 | "stargazers_count": 382, 828 | "watchers_count": 382, 829 | "language": "Ruby", 830 | "has_issues": true, 831 | "has_downloads": true, 832 | "has_wiki": true, 833 | "forks_count": 29, 834 | "mirror_url": null, 835 | "open_issues_count": 22, 836 | "forks": 29, 837 | "open_issues": 22, 838 | "watchers": 382, 839 | "default_branch": "master" 840 | } 841 | }, 842 | "_links": { 843 | "self": { 844 | "href": "https://api.github.com/repos/alexch/rerun/pulls/56" 845 | }, 846 | "html": { 847 | "href": "https://github.com/alexch/rerun/pull/56" 848 | }, 849 | "issue": { 850 | "href": "https://api.github.com/repos/alexch/rerun/issues/56" 851 | }, 852 | "comments": { 853 | "href": "https://api.github.com/repos/alexch/rerun/issues/56/comments" 854 | }, 855 | "review_comments": { 856 | "href": "https://api.github.com/repos/alexch/rerun/pulls/56/comments" 857 | }, 858 | "review_comment": { 859 | "href": "https://api.github.com/repos/alexch/rerun/pulls/comments/{number}" 860 | }, 861 | "commits": { 862 | "href": "https://api.github.com/repos/alexch/rerun/pulls/56/commits" 863 | }, 864 | "statuses": { 865 | "href": "https://api.github.com/repos/alexch/rerun/statuses/1092c30ea86331bddb01b6afe28cada3d6f8f7ab" 866 | } 867 | } 868 | }, 869 | { 870 | "url": "https://api.github.com/repos/alexch/rerun/pulls/40", 871 | "id": 6322469, 872 | "html_url": "https://github.com/alexch/rerun/pull/40", 873 | "diff_url": "https://github.com/alexch/rerun/pull/40.diff", 874 | "patch_url": "https://github.com/alexch/rerun/pull/40.patch", 875 | "issue_url": "https://api.github.com/repos/alexch/rerun/issues/40", 876 | "number": 40, 877 | "state": "open", 878 | "title": "support OSX notifications as alternative to growl", 879 | "user": { 880 | "login": "RaVbaker", 881 | "id": 56023, 882 | "avatar_url": "https://avatars.githubusercontent.com/u/56023?v=1", 883 | "gravatar_id": "9764e44e1d69baa6276644e546ea9229", 884 | "url": "https://api.github.com/users/RaVbaker", 885 | "html_url": "https://github.com/RaVbaker", 886 | "followers_url": "https://api.github.com/users/RaVbaker/followers", 887 | "following_url": "https://api.github.com/users/RaVbaker/following{/other_user}", 888 | "gists_url": "https://api.github.com/users/RaVbaker/gists{/gist_id}", 889 | "starred_url": "https://api.github.com/users/RaVbaker/starred{/owner}{/repo}", 890 | "subscriptions_url": "https://api.github.com/users/RaVbaker/subscriptions", 891 | "organizations_url": "https://api.github.com/users/RaVbaker/orgs", 892 | "repos_url": "https://api.github.com/users/RaVbaker/repos", 893 | "events_url": "https://api.github.com/users/RaVbaker/events{/privacy}", 894 | "received_events_url": "https://api.github.com/users/RaVbaker/received_events", 895 | "type": "User", 896 | "site_admin": false 897 | }, 898 | "body": "I have coded the OSX Notifications support for rerun. It uses [terminal-notifier](https://github.com/alloy/terminal-notifier) gem. And you can use it with two different arguments: `-on` or `--osx-notifications`. If you will use it will disable the growl notifications.\r\n\r\nRelated to issue: #33\r\n\r\nHow it looks? See here: ![OS X notifications for rerun gem](http://f.cl.ly/items/1q302F041i2I0j1l113U/Screen%20Shot%202013-06-14%20o%2012.11.40.png) ", 899 | "created_at": "2013-06-14T10:15:21Z", 900 | "updated_at": "2014-06-15T08:58:28Z", 901 | "closed_at": null, 902 | "merged_at": null, 903 | "merge_commit_sha": "645d46a644656b6b57b36ae81e2c9ef64f447e13", 904 | "assignee": null, 905 | "milestone": null, 906 | "commits_url": "https://api.github.com/repos/alexch/rerun/pulls/40/commits", 907 | "review_comments_url": "https://api.github.com/repos/alexch/rerun/pulls/40/comments", 908 | "review_comment_url": "https://api.github.com/repos/alexch/rerun/pulls/comments/{number}", 909 | "comments_url": "https://api.github.com/repos/alexch/rerun/issues/40/comments", 910 | "statuses_url": "https://api.github.com/repos/alexch/rerun/statuses/21ce9b2cf6c2d5570891ea41ed6ad74b58560ff7", 911 | "head": { 912 | "label": "RaVbaker:master", 913 | "ref": "master", 914 | "sha": "21ce9b2cf6c2d5570891ea41ed6ad74b58560ff7", 915 | "user": { 916 | "login": "RaVbaker", 917 | "id": 56023, 918 | "avatar_url": "https://avatars.githubusercontent.com/u/56023?v=1", 919 | "gravatar_id": "9764e44e1d69baa6276644e546ea9229", 920 | "url": "https://api.github.com/users/RaVbaker", 921 | "html_url": "https://github.com/RaVbaker", 922 | "followers_url": "https://api.github.com/users/RaVbaker/followers", 923 | "following_url": "https://api.github.com/users/RaVbaker/following{/other_user}", 924 | "gists_url": "https://api.github.com/users/RaVbaker/gists{/gist_id}", 925 | "starred_url": "https://api.github.com/users/RaVbaker/starred{/owner}{/repo}", 926 | "subscriptions_url": "https://api.github.com/users/RaVbaker/subscriptions", 927 | "organizations_url": "https://api.github.com/users/RaVbaker/orgs", 928 | "repos_url": "https://api.github.com/users/RaVbaker/repos", 929 | "events_url": "https://api.github.com/users/RaVbaker/events{/privacy}", 930 | "received_events_url": "https://api.github.com/users/RaVbaker/received_events", 931 | "type": "User", 932 | "site_admin": false 933 | }, 934 | "repo": { 935 | "id": 10685919, 936 | "name": "rerun", 937 | "full_name": "RaVbaker/rerun", 938 | "owner": { 939 | "login": "RaVbaker", 940 | "id": 56023, 941 | "avatar_url": "https://avatars.githubusercontent.com/u/56023?v=1", 942 | "gravatar_id": "9764e44e1d69baa6276644e546ea9229", 943 | "url": "https://api.github.com/users/RaVbaker", 944 | "html_url": "https://github.com/RaVbaker", 945 | "followers_url": "https://api.github.com/users/RaVbaker/followers", 946 | "following_url": "https://api.github.com/users/RaVbaker/following{/other_user}", 947 | "gists_url": "https://api.github.com/users/RaVbaker/gists{/gist_id}", 948 | "starred_url": "https://api.github.com/users/RaVbaker/starred{/owner}{/repo}", 949 | "subscriptions_url": "https://api.github.com/users/RaVbaker/subscriptions", 950 | "organizations_url": "https://api.github.com/users/RaVbaker/orgs", 951 | "repos_url": "https://api.github.com/users/RaVbaker/repos", 952 | "events_url": "https://api.github.com/users/RaVbaker/events{/privacy}", 953 | "received_events_url": "https://api.github.com/users/RaVbaker/received_events", 954 | "type": "User", 955 | "site_admin": false 956 | }, 957 | "private": false, 958 | "html_url": "https://github.com/RaVbaker/rerun", 959 | "description": "Restarts an app when the filesystem changes. Uses growl and FSEventStream if on OS X.", 960 | "fork": true, 961 | "url": "https://api.github.com/repos/RaVbaker/rerun", 962 | "forks_url": "https://api.github.com/repos/RaVbaker/rerun/forks", 963 | "keys_url": "https://api.github.com/repos/RaVbaker/rerun/keys{/key_id}", 964 | "collaborators_url": "https://api.github.com/repos/RaVbaker/rerun/collaborators{/collaborator}", 965 | "teams_url": "https://api.github.com/repos/RaVbaker/rerun/teams", 966 | "hooks_url": "https://api.github.com/repos/RaVbaker/rerun/hooks", 967 | "issue_events_url": "https://api.github.com/repos/RaVbaker/rerun/issues/events{/number}", 968 | "events_url": "https://api.github.com/repos/RaVbaker/rerun/events", 969 | "assignees_url": "https://api.github.com/repos/RaVbaker/rerun/assignees{/user}", 970 | "branches_url": "https://api.github.com/repos/RaVbaker/rerun/branches{/branch}", 971 | "tags_url": "https://api.github.com/repos/RaVbaker/rerun/tags", 972 | "blobs_url": "https://api.github.com/repos/RaVbaker/rerun/git/blobs{/sha}", 973 | "git_tags_url": "https://api.github.com/repos/RaVbaker/rerun/git/tags{/sha}", 974 | "git_refs_url": "https://api.github.com/repos/RaVbaker/rerun/git/refs{/sha}", 975 | "trees_url": "https://api.github.com/repos/RaVbaker/rerun/git/trees{/sha}", 976 | "statuses_url": "https://api.github.com/repos/RaVbaker/rerun/statuses/{sha}", 977 | "languages_url": "https://api.github.com/repos/RaVbaker/rerun/languages", 978 | "stargazers_url": "https://api.github.com/repos/RaVbaker/rerun/stargazers", 979 | "contributors_url": "https://api.github.com/repos/RaVbaker/rerun/contributors", 980 | "subscribers_url": "https://api.github.com/repos/RaVbaker/rerun/subscribers", 981 | "subscription_url": "https://api.github.com/repos/RaVbaker/rerun/subscription", 982 | "commits_url": "https://api.github.com/repos/RaVbaker/rerun/commits{/sha}", 983 | "git_commits_url": "https://api.github.com/repos/RaVbaker/rerun/git/commits{/sha}", 984 | "comments_url": "https://api.github.com/repos/RaVbaker/rerun/comments{/number}", 985 | "issue_comment_url": "https://api.github.com/repos/RaVbaker/rerun/issues/comments/{number}", 986 | "contents_url": "https://api.github.com/repos/RaVbaker/rerun/contents/{+path}", 987 | "compare_url": "https://api.github.com/repos/RaVbaker/rerun/compare/{base}...{head}", 988 | "merges_url": "https://api.github.com/repos/RaVbaker/rerun/merges", 989 | "archive_url": "https://api.github.com/repos/RaVbaker/rerun/{archive_format}{/ref}", 990 | "downloads_url": "https://api.github.com/repos/RaVbaker/rerun/downloads", 991 | "issues_url": "https://api.github.com/repos/RaVbaker/rerun/issues{/number}", 992 | "pulls_url": "https://api.github.com/repos/RaVbaker/rerun/pulls{/number}", 993 | "milestones_url": "https://api.github.com/repos/RaVbaker/rerun/milestones{/number}", 994 | "notifications_url": "https://api.github.com/repos/RaVbaker/rerun/notifications{?since,all,participating}", 995 | "labels_url": "https://api.github.com/repos/RaVbaker/rerun/labels{/name}", 996 | "releases_url": "https://api.github.com/repos/RaVbaker/rerun/releases{/id}", 997 | "created_at": "2013-06-14T09:26:55Z", 998 | "updated_at": "2013-08-28T19:02:05Z", 999 | "pushed_at": "2013-06-14T10:08:45Z", 1000 | "git_url": "git://github.com/RaVbaker/rerun.git", 1001 | "ssh_url": "git@github.com:RaVbaker/rerun.git", 1002 | "clone_url": "https://github.com/RaVbaker/rerun.git", 1003 | "svn_url": "https://github.com/RaVbaker/rerun", 1004 | "homepage": "", 1005 | "size": 267, 1006 | "stargazers_count": 0, 1007 | "watchers_count": 0, 1008 | "language": "Ruby", 1009 | "has_issues": false, 1010 | "has_downloads": true, 1011 | "has_wiki": true, 1012 | "forks_count": 0, 1013 | "mirror_url": null, 1014 | "open_issues_count": 0, 1015 | "forks": 0, 1016 | "open_issues": 0, 1017 | "watchers": 0, 1018 | "default_branch": "master" 1019 | } 1020 | }, 1021 | "base": { 1022 | "label": "alexch:master", 1023 | "ref": "master", 1024 | "sha": "8db720f0f407a7e3f156b508f551bf9ef70e10c6", 1025 | "user": { 1026 | "login": "alexch", 1027 | "id": 8524, 1028 | "avatar_url": "https://avatars.githubusercontent.com/u/8524?v=1", 1029 | "gravatar_id": "5a0d7f0cb2fac7858d234de7f7f01491", 1030 | "url": "https://api.github.com/users/alexch", 1031 | "html_url": "https://github.com/alexch", 1032 | "followers_url": "https://api.github.com/users/alexch/followers", 1033 | "following_url": "https://api.github.com/users/alexch/following{/other_user}", 1034 | "gists_url": "https://api.github.com/users/alexch/gists{/gist_id}", 1035 | "starred_url": "https://api.github.com/users/alexch/starred{/owner}{/repo}", 1036 | "subscriptions_url": "https://api.github.com/users/alexch/subscriptions", 1037 | "organizations_url": "https://api.github.com/users/alexch/orgs", 1038 | "repos_url": "https://api.github.com/users/alexch/repos", 1039 | "events_url": "https://api.github.com/users/alexch/events{/privacy}", 1040 | "received_events_url": "https://api.github.com/users/alexch/received_events", 1041 | "type": "User", 1042 | "site_admin": false 1043 | }, 1044 | "repo": { 1045 | "id": 221696, 1046 | "name": "rerun", 1047 | "full_name": "alexch/rerun", 1048 | "owner": { 1049 | "login": "alexch", 1050 | "id": 8524, 1051 | "avatar_url": "https://avatars.githubusercontent.com/u/8524?v=1", 1052 | "gravatar_id": "5a0d7f0cb2fac7858d234de7f7f01491", 1053 | "url": "https://api.github.com/users/alexch", 1054 | "html_url": "https://github.com/alexch", 1055 | "followers_url": "https://api.github.com/users/alexch/followers", 1056 | "following_url": "https://api.github.com/users/alexch/following{/other_user}", 1057 | "gists_url": "https://api.github.com/users/alexch/gists{/gist_id}", 1058 | "starred_url": "https://api.github.com/users/alexch/starred{/owner}{/repo}", 1059 | "subscriptions_url": "https://api.github.com/users/alexch/subscriptions", 1060 | "organizations_url": "https://api.github.com/users/alexch/orgs", 1061 | "repos_url": "https://api.github.com/users/alexch/repos", 1062 | "events_url": "https://api.github.com/users/alexch/events{/privacy}", 1063 | "received_events_url": "https://api.github.com/users/alexch/received_events", 1064 | "type": "User", 1065 | "site_admin": false 1066 | }, 1067 | "private": false, 1068 | "html_url": "https://github.com/alexch/rerun", 1069 | "description": "Restarts an app when the filesystem changes. Uses growl and FSEventStream if on OS X.", 1070 | "fork": false, 1071 | "url": "https://api.github.com/repos/alexch/rerun", 1072 | "forks_url": "https://api.github.com/repos/alexch/rerun/forks", 1073 | "keys_url": "https://api.github.com/repos/alexch/rerun/keys{/key_id}", 1074 | "collaborators_url": "https://api.github.com/repos/alexch/rerun/collaborators{/collaborator}", 1075 | "teams_url": "https://api.github.com/repos/alexch/rerun/teams", 1076 | "hooks_url": "https://api.github.com/repos/alexch/rerun/hooks", 1077 | "issue_events_url": "https://api.github.com/repos/alexch/rerun/issues/events{/number}", 1078 | "events_url": "https://api.github.com/repos/alexch/rerun/events", 1079 | "assignees_url": "https://api.github.com/repos/alexch/rerun/assignees{/user}", 1080 | "branches_url": "https://api.github.com/repos/alexch/rerun/branches{/branch}", 1081 | "tags_url": "https://api.github.com/repos/alexch/rerun/tags", 1082 | "blobs_url": "https://api.github.com/repos/alexch/rerun/git/blobs{/sha}", 1083 | "git_tags_url": "https://api.github.com/repos/alexch/rerun/git/tags{/sha}", 1084 | "git_refs_url": "https://api.github.com/repos/alexch/rerun/git/refs{/sha}", 1085 | "trees_url": "https://api.github.com/repos/alexch/rerun/git/trees{/sha}", 1086 | "statuses_url": "https://api.github.com/repos/alexch/rerun/statuses/{sha}", 1087 | "languages_url": "https://api.github.com/repos/alexch/rerun/languages", 1088 | "stargazers_url": "https://api.github.com/repos/alexch/rerun/stargazers", 1089 | "contributors_url": "https://api.github.com/repos/alexch/rerun/contributors", 1090 | "subscribers_url": "https://api.github.com/repos/alexch/rerun/subscribers", 1091 | "subscription_url": "https://api.github.com/repos/alexch/rerun/subscription", 1092 | "commits_url": "https://api.github.com/repos/alexch/rerun/commits{/sha}", 1093 | "git_commits_url": "https://api.github.com/repos/alexch/rerun/git/commits{/sha}", 1094 | "comments_url": "https://api.github.com/repos/alexch/rerun/comments{/number}", 1095 | "issue_comment_url": "https://api.github.com/repos/alexch/rerun/issues/comments/{number}", 1096 | "contents_url": "https://api.github.com/repos/alexch/rerun/contents/{+path}", 1097 | "compare_url": "https://api.github.com/repos/alexch/rerun/compare/{base}...{head}", 1098 | "merges_url": "https://api.github.com/repos/alexch/rerun/merges", 1099 | "archive_url": "https://api.github.com/repos/alexch/rerun/{archive_format}{/ref}", 1100 | "downloads_url": "https://api.github.com/repos/alexch/rerun/downloads", 1101 | "issues_url": "https://api.github.com/repos/alexch/rerun/issues{/number}", 1102 | "pulls_url": "https://api.github.com/repos/alexch/rerun/pulls{/number}", 1103 | "milestones_url": "https://api.github.com/repos/alexch/rerun/milestones{/number}", 1104 | "notifications_url": "https://api.github.com/repos/alexch/rerun/notifications{?since,all,participating}", 1105 | "labels_url": "https://api.github.com/repos/alexch/rerun/labels{/name}", 1106 | "releases_url": "https://api.github.com/repos/alexch/rerun/releases{/id}", 1107 | "created_at": "2009-06-08T15:59:27Z", 1108 | "updated_at": "2014-07-28T07:08:07Z", 1109 | "pushed_at": "2014-05-05T15:09:08Z", 1110 | "git_url": "git://github.com/alexch/rerun.git", 1111 | "ssh_url": "git@github.com:alexch/rerun.git", 1112 | "clone_url": "https://github.com/alexch/rerun.git", 1113 | "svn_url": "https://github.com/alexch/rerun", 1114 | "homepage": "", 1115 | "size": 744, 1116 | "stargazers_count": 382, 1117 | "watchers_count": 382, 1118 | "language": "Ruby", 1119 | "has_issues": true, 1120 | "has_downloads": true, 1121 | "has_wiki": true, 1122 | "forks_count": 29, 1123 | "mirror_url": null, 1124 | "open_issues_count": 22, 1125 | "forks": 29, 1126 | "open_issues": 22, 1127 | "watchers": 382, 1128 | "default_branch": "master" 1129 | } 1130 | }, 1131 | "_links": { 1132 | "self": { 1133 | "href": "https://api.github.com/repos/alexch/rerun/pulls/40" 1134 | }, 1135 | "html": { 1136 | "href": "https://github.com/alexch/rerun/pull/40" 1137 | }, 1138 | "issue": { 1139 | "href": "https://api.github.com/repos/alexch/rerun/issues/40" 1140 | }, 1141 | "comments": { 1142 | "href": "https://api.github.com/repos/alexch/rerun/issues/40/comments" 1143 | }, 1144 | "review_comments": { 1145 | "href": "https://api.github.com/repos/alexch/rerun/pulls/40/comments" 1146 | }, 1147 | "review_comment": { 1148 | "href": "https://api.github.com/repos/alexch/rerun/pulls/comments/{number}" 1149 | }, 1150 | "commits": { 1151 | "href": "https://api.github.com/repos/alexch/rerun/pulls/40/commits" 1152 | }, 1153 | "statuses": { 1154 | "href": "https://api.github.com/repos/alexch/rerun/statuses/21ce9b2cf6c2d5570891ea41ed6ad74b58560ff7" 1155 | } 1156 | } 1157 | }, 1158 | { 1159 | "url": "https://api.github.com/repos/alexch/rerun/pulls/31", 1160 | "id": 4887127, 1161 | "html_url": "https://github.com/alexch/rerun/pull/31", 1162 | "diff_url": "https://github.com/alexch/rerun/pull/31.diff", 1163 | "patch_url": "https://github.com/alexch/rerun/pull/31.patch", 1164 | "issue_url": "https://api.github.com/repos/alexch/rerun/issues/31", 1165 | "number": 31, 1166 | "state": "open", 1167 | "title": "Add restart signal support", 1168 | "user": { 1169 | "login": "ismell", 1170 | "id": 562978, 1171 | "avatar_url": "https://avatars.githubusercontent.com/u/562978?v=1", 1172 | "gravatar_id": "4f945a304cb19ee2404bfbad964bd85e", 1173 | "url": "https://api.github.com/users/ismell", 1174 | "html_url": "https://github.com/ismell", 1175 | "followers_url": "https://api.github.com/users/ismell/followers", 1176 | "following_url": "https://api.github.com/users/ismell/following{/other_user}", 1177 | "gists_url": "https://api.github.com/users/ismell/gists{/gist_id}", 1178 | "starred_url": "https://api.github.com/users/ismell/starred{/owner}{/repo}", 1179 | "subscriptions_url": "https://api.github.com/users/ismell/subscriptions", 1180 | "organizations_url": "https://api.github.com/users/ismell/orgs", 1181 | "repos_url": "https://api.github.com/users/ismell/repos", 1182 | "events_url": "https://api.github.com/users/ismell/events{/privacy}", 1183 | "received_events_url": "https://api.github.com/users/ismell/received_events", 1184 | "type": "User", 1185 | "site_admin": false 1186 | }, 1187 | "body": "This can be used when running with unicorn.\r\n\r\nrerun -r HUP -- unicorn -c unicorn.rb\r\n\r\nNOTE: Make sure you run unicorn with a config file\r\n otherwise it will respawn the master and you will\r\n end up in a very bad place.", 1188 | "created_at": "2013-03-29T15:58:47Z", 1189 | "updated_at": "2014-06-12T20:20:17Z", 1190 | "closed_at": null, 1191 | "merged_at": null, 1192 | "merge_commit_sha": "67371ef78471e2f84ac703e765d063caf0065601", 1193 | "assignee": null, 1194 | "milestone": null, 1195 | "commits_url": "https://api.github.com/repos/alexch/rerun/pulls/31/commits", 1196 | "review_comments_url": "https://api.github.com/repos/alexch/rerun/pulls/31/comments", 1197 | "review_comment_url": "https://api.github.com/repos/alexch/rerun/pulls/comments/{number}", 1198 | "comments_url": "https://api.github.com/repos/alexch/rerun/issues/31/comments", 1199 | "statuses_url": "https://api.github.com/repos/alexch/rerun/statuses/174bc3ce2ad1ad786b98197d762f7e41fa114398", 1200 | "head": { 1201 | "label": "ismell:master", 1202 | "ref": "master", 1203 | "sha": "174bc3ce2ad1ad786b98197d762f7e41fa114398", 1204 | "user": { 1205 | "login": "ismell", 1206 | "id": 562978, 1207 | "avatar_url": "https://avatars.githubusercontent.com/u/562978?v=1", 1208 | "gravatar_id": "4f945a304cb19ee2404bfbad964bd85e", 1209 | "url": "https://api.github.com/users/ismell", 1210 | "html_url": "https://github.com/ismell", 1211 | "followers_url": "https://api.github.com/users/ismell/followers", 1212 | "following_url": "https://api.github.com/users/ismell/following{/other_user}", 1213 | "gists_url": "https://api.github.com/users/ismell/gists{/gist_id}", 1214 | "starred_url": "https://api.github.com/users/ismell/starred{/owner}{/repo}", 1215 | "subscriptions_url": "https://api.github.com/users/ismell/subscriptions", 1216 | "organizations_url": "https://api.github.com/users/ismell/orgs", 1217 | "repos_url": "https://api.github.com/users/ismell/repos", 1218 | "events_url": "https://api.github.com/users/ismell/events{/privacy}", 1219 | "received_events_url": "https://api.github.com/users/ismell/received_events", 1220 | "type": "User", 1221 | "site_admin": false 1222 | }, 1223 | "repo": { 1224 | "id": 9100150, 1225 | "name": "rerun", 1226 | "full_name": "ismell/rerun", 1227 | "owner": { 1228 | "login": "ismell", 1229 | "id": 562978, 1230 | "avatar_url": "https://avatars.githubusercontent.com/u/562978?v=1", 1231 | "gravatar_id": "4f945a304cb19ee2404bfbad964bd85e", 1232 | "url": "https://api.github.com/users/ismell", 1233 | "html_url": "https://github.com/ismell", 1234 | "followers_url": "https://api.github.com/users/ismell/followers", 1235 | "following_url": "https://api.github.com/users/ismell/following{/other_user}", 1236 | "gists_url": "https://api.github.com/users/ismell/gists{/gist_id}", 1237 | "starred_url": "https://api.github.com/users/ismell/starred{/owner}{/repo}", 1238 | "subscriptions_url": "https://api.github.com/users/ismell/subscriptions", 1239 | "organizations_url": "https://api.github.com/users/ismell/orgs", 1240 | "repos_url": "https://api.github.com/users/ismell/repos", 1241 | "events_url": "https://api.github.com/users/ismell/events{/privacy}", 1242 | "received_events_url": "https://api.github.com/users/ismell/received_events", 1243 | "type": "User", 1244 | "site_admin": false 1245 | }, 1246 | "private": false, 1247 | "html_url": "https://github.com/ismell/rerun", 1248 | "description": "Restarts an app when the filesystem changes. Uses growl and FSEventStream if on OS X.", 1249 | "fork": true, 1250 | "url": "https://api.github.com/repos/ismell/rerun", 1251 | "forks_url": "https://api.github.com/repos/ismell/rerun/forks", 1252 | "keys_url": "https://api.github.com/repos/ismell/rerun/keys{/key_id}", 1253 | "collaborators_url": "https://api.github.com/repos/ismell/rerun/collaborators{/collaborator}", 1254 | "teams_url": "https://api.github.com/repos/ismell/rerun/teams", 1255 | "hooks_url": "https://api.github.com/repos/ismell/rerun/hooks", 1256 | "issue_events_url": "https://api.github.com/repos/ismell/rerun/issues/events{/number}", 1257 | "events_url": "https://api.github.com/repos/ismell/rerun/events", 1258 | "assignees_url": "https://api.github.com/repos/ismell/rerun/assignees{/user}", 1259 | "branches_url": "https://api.github.com/repos/ismell/rerun/branches{/branch}", 1260 | "tags_url": "https://api.github.com/repos/ismell/rerun/tags", 1261 | "blobs_url": "https://api.github.com/repos/ismell/rerun/git/blobs{/sha}", 1262 | "git_tags_url": "https://api.github.com/repos/ismell/rerun/git/tags{/sha}", 1263 | "git_refs_url": "https://api.github.com/repos/ismell/rerun/git/refs{/sha}", 1264 | "trees_url": "https://api.github.com/repos/ismell/rerun/git/trees{/sha}", 1265 | "statuses_url": "https://api.github.com/repos/ismell/rerun/statuses/{sha}", 1266 | "languages_url": "https://api.github.com/repos/ismell/rerun/languages", 1267 | "stargazers_url": "https://api.github.com/repos/ismell/rerun/stargazers", 1268 | "contributors_url": "https://api.github.com/repos/ismell/rerun/contributors", 1269 | "subscribers_url": "https://api.github.com/repos/ismell/rerun/subscribers", 1270 | "subscription_url": "https://api.github.com/repos/ismell/rerun/subscription", 1271 | "commits_url": "https://api.github.com/repos/ismell/rerun/commits{/sha}", 1272 | "git_commits_url": "https://api.github.com/repos/ismell/rerun/git/commits{/sha}", 1273 | "comments_url": "https://api.github.com/repos/ismell/rerun/comments{/number}", 1274 | "issue_comment_url": "https://api.github.com/repos/ismell/rerun/issues/comments/{number}", 1275 | "contents_url": "https://api.github.com/repos/ismell/rerun/contents/{+path}", 1276 | "compare_url": "https://api.github.com/repos/ismell/rerun/compare/{base}...{head}", 1277 | "merges_url": "https://api.github.com/repos/ismell/rerun/merges", 1278 | "archive_url": "https://api.github.com/repos/ismell/rerun/{archive_format}{/ref}", 1279 | "downloads_url": "https://api.github.com/repos/ismell/rerun/downloads", 1280 | "issues_url": "https://api.github.com/repos/ismell/rerun/issues{/number}", 1281 | "pulls_url": "https://api.github.com/repos/ismell/rerun/pulls{/number}", 1282 | "milestones_url": "https://api.github.com/repos/ismell/rerun/milestones{/number}", 1283 | "notifications_url": "https://api.github.com/repos/ismell/rerun/notifications{?since,all,participating}", 1284 | "labels_url": "https://api.github.com/repos/ismell/rerun/labels{/name}", 1285 | "releases_url": "https://api.github.com/repos/ismell/rerun/releases{/id}", 1286 | "created_at": "2013-03-29T14:44:17Z", 1287 | "updated_at": "2013-10-18T18:37:05Z", 1288 | "pushed_at": "2013-04-22T17:47:13Z", 1289 | "git_url": "git://github.com/ismell/rerun.git", 1290 | "ssh_url": "git@github.com:ismell/rerun.git", 1291 | "clone_url": "https://github.com/ismell/rerun.git", 1292 | "svn_url": "https://github.com/ismell/rerun", 1293 | "homepage": "", 1294 | "size": 271, 1295 | "stargazers_count": 0, 1296 | "watchers_count": 0, 1297 | "language": "Ruby", 1298 | "has_issues": false, 1299 | "has_downloads": true, 1300 | "has_wiki": true, 1301 | "forks_count": 0, 1302 | "mirror_url": null, 1303 | "open_issues_count": 0, 1304 | "forks": 0, 1305 | "open_issues": 0, 1306 | "watchers": 0, 1307 | "default_branch": "master" 1308 | } 1309 | }, 1310 | "base": { 1311 | "label": "alexch:master", 1312 | "ref": "master", 1313 | "sha": "31d6a11b5269006d2bbb868af0271e3538ac6dd6", 1314 | "user": { 1315 | "login": "alexch", 1316 | "id": 8524, 1317 | "avatar_url": "https://avatars.githubusercontent.com/u/8524?v=1", 1318 | "gravatar_id": "5a0d7f0cb2fac7858d234de7f7f01491", 1319 | "url": "https://api.github.com/users/alexch", 1320 | "html_url": "https://github.com/alexch", 1321 | "followers_url": "https://api.github.com/users/alexch/followers", 1322 | "following_url": "https://api.github.com/users/alexch/following{/other_user}", 1323 | "gists_url": "https://api.github.com/users/alexch/gists{/gist_id}", 1324 | "starred_url": "https://api.github.com/users/alexch/starred{/owner}{/repo}", 1325 | "subscriptions_url": "https://api.github.com/users/alexch/subscriptions", 1326 | "organizations_url": "https://api.github.com/users/alexch/orgs", 1327 | "repos_url": "https://api.github.com/users/alexch/repos", 1328 | "events_url": "https://api.github.com/users/alexch/events{/privacy}", 1329 | "received_events_url": "https://api.github.com/users/alexch/received_events", 1330 | "type": "User", 1331 | "site_admin": false 1332 | }, 1333 | "repo": { 1334 | "id": 221696, 1335 | "name": "rerun", 1336 | "full_name": "alexch/rerun", 1337 | "owner": { 1338 | "login": "alexch", 1339 | "id": 8524, 1340 | "avatar_url": "https://avatars.githubusercontent.com/u/8524?v=1", 1341 | "gravatar_id": "5a0d7f0cb2fac7858d234de7f7f01491", 1342 | "url": "https://api.github.com/users/alexch", 1343 | "html_url": "https://github.com/alexch", 1344 | "followers_url": "https://api.github.com/users/alexch/followers", 1345 | "following_url": "https://api.github.com/users/alexch/following{/other_user}", 1346 | "gists_url": "https://api.github.com/users/alexch/gists{/gist_id}", 1347 | "starred_url": "https://api.github.com/users/alexch/starred{/owner}{/repo}", 1348 | "subscriptions_url": "https://api.github.com/users/alexch/subscriptions", 1349 | "organizations_url": "https://api.github.com/users/alexch/orgs", 1350 | "repos_url": "https://api.github.com/users/alexch/repos", 1351 | "events_url": "https://api.github.com/users/alexch/events{/privacy}", 1352 | "received_events_url": "https://api.github.com/users/alexch/received_events", 1353 | "type": "User", 1354 | "site_admin": false 1355 | }, 1356 | "private": false, 1357 | "html_url": "https://github.com/alexch/rerun", 1358 | "description": "Restarts an app when the filesystem changes. Uses growl and FSEventStream if on OS X.", 1359 | "fork": false, 1360 | "url": "https://api.github.com/repos/alexch/rerun", 1361 | "forks_url": "https://api.github.com/repos/alexch/rerun/forks", 1362 | "keys_url": "https://api.github.com/repos/alexch/rerun/keys{/key_id}", 1363 | "collaborators_url": "https://api.github.com/repos/alexch/rerun/collaborators{/collaborator}", 1364 | "teams_url": "https://api.github.com/repos/alexch/rerun/teams", 1365 | "hooks_url": "https://api.github.com/repos/alexch/rerun/hooks", 1366 | "issue_events_url": "https://api.github.com/repos/alexch/rerun/issues/events{/number}", 1367 | "events_url": "https://api.github.com/repos/alexch/rerun/events", 1368 | "assignees_url": "https://api.github.com/repos/alexch/rerun/assignees{/user}", 1369 | "branches_url": "https://api.github.com/repos/alexch/rerun/branches{/branch}", 1370 | "tags_url": "https://api.github.com/repos/alexch/rerun/tags", 1371 | "blobs_url": "https://api.github.com/repos/alexch/rerun/git/blobs{/sha}", 1372 | "git_tags_url": "https://api.github.com/repos/alexch/rerun/git/tags{/sha}", 1373 | "git_refs_url": "https://api.github.com/repos/alexch/rerun/git/refs{/sha}", 1374 | "trees_url": "https://api.github.com/repos/alexch/rerun/git/trees{/sha}", 1375 | "statuses_url": "https://api.github.com/repos/alexch/rerun/statuses/{sha}", 1376 | "languages_url": "https://api.github.com/repos/alexch/rerun/languages", 1377 | "stargazers_url": "https://api.github.com/repos/alexch/rerun/stargazers", 1378 | "contributors_url": "https://api.github.com/repos/alexch/rerun/contributors", 1379 | "subscribers_url": "https://api.github.com/repos/alexch/rerun/subscribers", 1380 | "subscription_url": "https://api.github.com/repos/alexch/rerun/subscription", 1381 | "commits_url": "https://api.github.com/repos/alexch/rerun/commits{/sha}", 1382 | "git_commits_url": "https://api.github.com/repos/alexch/rerun/git/commits{/sha}", 1383 | "comments_url": "https://api.github.com/repos/alexch/rerun/comments{/number}", 1384 | "issue_comment_url": "https://api.github.com/repos/alexch/rerun/issues/comments/{number}", 1385 | "contents_url": "https://api.github.com/repos/alexch/rerun/contents/{+path}", 1386 | "compare_url": "https://api.github.com/repos/alexch/rerun/compare/{base}...{head}", 1387 | "merges_url": "https://api.github.com/repos/alexch/rerun/merges", 1388 | "archive_url": "https://api.github.com/repos/alexch/rerun/{archive_format}{/ref}", 1389 | "downloads_url": "https://api.github.com/repos/alexch/rerun/downloads", 1390 | "issues_url": "https://api.github.com/repos/alexch/rerun/issues{/number}", 1391 | "pulls_url": "https://api.github.com/repos/alexch/rerun/pulls{/number}", 1392 | "milestones_url": "https://api.github.com/repos/alexch/rerun/milestones{/number}", 1393 | "notifications_url": "https://api.github.com/repos/alexch/rerun/notifications{?since,all,participating}", 1394 | "labels_url": "https://api.github.com/repos/alexch/rerun/labels{/name}", 1395 | "releases_url": "https://api.github.com/repos/alexch/rerun/releases{/id}", 1396 | "created_at": "2009-06-08T15:59:27Z", 1397 | "updated_at": "2014-07-28T07:08:07Z", 1398 | "pushed_at": "2014-05-05T15:09:08Z", 1399 | "git_url": "git://github.com/alexch/rerun.git", 1400 | "ssh_url": "git@github.com:alexch/rerun.git", 1401 | "clone_url": "https://github.com/alexch/rerun.git", 1402 | "svn_url": "https://github.com/alexch/rerun", 1403 | "homepage": "", 1404 | "size": 744, 1405 | "stargazers_count": 382, 1406 | "watchers_count": 382, 1407 | "language": "Ruby", 1408 | "has_issues": true, 1409 | "has_downloads": true, 1410 | "has_wiki": true, 1411 | "forks_count": 29, 1412 | "mirror_url": null, 1413 | "open_issues_count": 22, 1414 | "forks": 29, 1415 | "open_issues": 22, 1416 | "watchers": 382, 1417 | "default_branch": "master" 1418 | } 1419 | }, 1420 | "_links": { 1421 | "self": { 1422 | "href": "https://api.github.com/repos/alexch/rerun/pulls/31" 1423 | }, 1424 | "html": { 1425 | "href": "https://github.com/alexch/rerun/pull/31" 1426 | }, 1427 | "issue": { 1428 | "href": "https://api.github.com/repos/alexch/rerun/issues/31" 1429 | }, 1430 | "comments": { 1431 | "href": "https://api.github.com/repos/alexch/rerun/issues/31/comments" 1432 | }, 1433 | "review_comments": { 1434 | "href": "https://api.github.com/repos/alexch/rerun/pulls/31/comments" 1435 | }, 1436 | "review_comment": { 1437 | "href": "https://api.github.com/repos/alexch/rerun/pulls/comments/{number}" 1438 | }, 1439 | "commits": { 1440 | "href": "https://api.github.com/repos/alexch/rerun/pulls/31/commits" 1441 | }, 1442 | "statuses": { 1443 | "href": "https://api.github.com/repos/alexch/rerun/statuses/174bc3ce2ad1ad786b98197d762f7e41fa114398" 1444 | } 1445 | } 1446 | } 1447 | ] 1448 | -------------------------------------------------------------------------------- /rerun.gemspec: -------------------------------------------------------------------------------- 1 | $spec = Gem::Specification.new do |s| 2 | s.specification_version = 2 if s.respond_to? :specification_version= 3 | s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version= 4 | 5 | s.name = 'rerun' 6 | s.version = '0.14.0' 7 | 8 | s.description = "Restarts your app when a file changes. A no-frills, command-line alternative to Guard, Shotgun, Autotest, etc." 9 | s.summary = "Launches an app, and restarts it whenever the filesystem changes. A no-frills, command-line alternative to Guard, Shotgun, Autotest, etc." 10 | 11 | s.authors = ["Alex Chaffee"] 12 | s.email = "alexch@gmail.com" 13 | 14 | s.files = %w[ 15 | README.md 16 | LICENSE 17 | Rakefile 18 | rerun.gemspec 19 | bin/rerun 20 | icons/rails_grn_sml.png 21 | icons/rails_red_sml.png] + 22 | Dir['lib/**/*.rb'] 23 | s.executables = ['rerun'] 24 | s.test_files = s.files.select {|path| path =~ /^spec\/.*_spec.rb/} 25 | 26 | s.extra_rdoc_files = %w[README.md] 27 | 28 | s.add_runtime_dependency 'listen', '~> 3.0' 29 | 30 | s.homepage = "http://github.com/alexch/rerun/" 31 | s.require_paths = %w[lib] 32 | 33 | s.license = 'MIT' 34 | end 35 | -------------------------------------------------------------------------------- /spec/functional_spec.rb: -------------------------------------------------------------------------------- 1 | here = File.expand_path(File.dirname(__FILE__)) 2 | require "#{here}/spec_helper.rb" 3 | require "#{here}/inc_process.rb" 4 | 5 | 6 | describe "the rerun command" do 7 | before do 8 | STDOUT.sync = true 9 | 10 | @inc = IncProcess.new 11 | 12 | # one file that exists in dir1 13 | @existing_file = File.join(@inc.dir1, "foo.rb") 14 | touch @existing_file 15 | 16 | # one file that doesn't yet exist in dir1 17 | @soon_to_exist_file = File.join(@inc.dir1, "bar.rb") 18 | 19 | # one file that exists in dir2 20 | @other_existing_file = File.join(@inc.dir2, "baz.rb") 21 | 22 | @inc.launch 23 | end 24 | 25 | after do 26 | @inc.kill 27 | end 28 | 29 | def read 30 | @inc.read 31 | end 32 | 33 | def touch(file = @existing_file) 34 | puts "#{Time.now.strftime("%T")} touching #{file}" 35 | File.open(file, "w") do |f| 36 | f.puts Time.now 37 | end 38 | end 39 | 40 | def type char 41 | # todo: send a character to stdin of the rerun process 42 | end 43 | 44 | describe IncProcess do 45 | it "increments a test file at least once per second" do 46 | sleep 1 47 | x = @inc.current_count 48 | sleep 1 49 | y = @inc.current_count 50 | y.should be > x 51 | end 52 | end 53 | 54 | it "restarts its target when an app file is modified" do 55 | first_launched_at = read[:launched_at] 56 | touch @existing_file 57 | sleep 4 58 | second_launched_at = read[:launched_at] 59 | 60 | second_launched_at.should be > first_launched_at 61 | end 62 | 63 | it "restarts its target when an app file is created" do 64 | first_launched_at = read[:launched_at] 65 | touch @soon_to_exist_file 66 | sleep 4 67 | second_launched_at = read[:launched_at] 68 | 69 | second_launched_at.should be > first_launched_at 70 | end 71 | 72 | it "restarts its target when an app file is created in the second dir" do 73 | first_launched_at = read[:launched_at] 74 | touch @other_existing_file 75 | sleep 4 76 | second_launched_at = read[:launched_at] 77 | 78 | second_launched_at.should be > first_launched_at 79 | end 80 | 81 | #it "sends its child process a SIGINT to restart" 82 | 83 | include ::Rerun::System 84 | it "dies when sent a control-C (SIGINT)" do 85 | unless windows? 86 | pid = @inc.inc_parent_pid 87 | # puts "test sending INT to #{pid}" 88 | Process.kill("INT", pid) 89 | Timeout::timeout(6) { 90 | # puts "test waiting for #{pid}" 91 | Process.wait(@inc.rerun_pid) rescue Errno::ESRCH 92 | } 93 | end 94 | end 95 | 96 | #it "accepts a key press" 97 | end 98 | -------------------------------------------------------------------------------- /spec/glob_spec.rb: -------------------------------------------------------------------------------- 1 | here = File.expand_path(File.dirname(__FILE__)) 2 | require "#{here}/spec_helper.rb" 3 | 4 | require "rerun/glob" 5 | 6 | module Rerun 7 | describe Glob do 8 | 9 | describe "#to_regexp" do 10 | it "makes a regexp" do 11 | Glob.new("foo*").to_regexp.should == /#{Glob::START_OF_FILENAME}foo.*#{Glob::END_OF_STRING}/ 12 | end 13 | 14 | end 15 | 16 | describe "#to_regexp_string" do 17 | { 18 | "x" => "x", 19 | 20 | "*" => ".*", 21 | "foo*" => "foo.*", 22 | "*foo" => ".*foo", 23 | "*foo*" => ".*foo.*", 24 | 25 | "?" => ".", 26 | 27 | "." => "\\.", 28 | 29 | "{foo,bar,baz}" => "(foo|bar|baz)", 30 | "{.txt,.md}" => '(\.txt|\.md)', 31 | 32 | # pass through slash-escapes verbatim 33 | "\\x" => "\\x", 34 | "\\." => "\\.", 35 | "\\*" => "\\*", 36 | "\\\\" => "\\\\", 37 | 38 | "**/*.txt" => "([^/]+/)*.*\\.txt", 39 | 40 | }.each_pair do |glob_string, regexp_string| 41 | specify glob_string do 42 | Glob.new(glob_string).to_regexp_string.should == 43 | Glob::START_OF_FILENAME + regexp_string + Glob::END_OF_STRING 44 | end 45 | end 46 | end 47 | 48 | describe "specifically" do 49 | { 50 | "*.txt" => { 51 | :hits=> [ 52 | "foo.txt", 53 | "foo/bar.txt", 54 | "/foo/bar.txt", 55 | "bar.baz.txt", 56 | "/foo/bar.baz.txt", 57 | ], 58 | :misses => [ 59 | "foo.txt.html", 60 | "tmp/foo.txt.html", 61 | "/tmp/foo.txt.html", 62 | #"tmp/.foo.txt", 63 | ] 64 | }, 65 | "tmp/foo.*" => { 66 | :hits => [ 67 | "tmp/foo.txt", 68 | ], 69 | :misses => [ 70 | "stmp/foo.txt", 71 | "tmp/foofoo.txt", 72 | ] 73 | } 74 | }.each_pair do |glob, paths| 75 | paths[:hits].each do |path| 76 | specify "#{glob} matches #{path}" do 77 | Glob.new(glob).to_regexp.should =~ path 78 | end 79 | end 80 | paths[:misses].each do |path| 81 | specify "#{glob} doesn't match #{path}" do 82 | Glob.new(glob).to_regexp.should_not =~ path 83 | end 84 | end 85 | end 86 | end 87 | 88 | describe "#smoosh" do 89 | 90 | def check_smoosh string, array 91 | glob = Glob.new("") 92 | glob.smoosh(string.split('')).should == array 93 | end 94 | 95 | it "ignores non-stars" do 96 | check_smoosh "", [] 97 | check_smoosh "abc", ["a", "b", "c"] 98 | end 99 | 100 | it "passes solitary stars" do 101 | check_smoosh "*", ["*"] 102 | check_smoosh "a*b", ["a", "*", "b"] 103 | end 104 | 105 | it "smooshes two stars in a row into a single '**' string" do 106 | check_smoosh "**", ["**"] 107 | check_smoosh "a**b", ["a", "**", "b"] 108 | check_smoosh "**b", ["**", "b"] 109 | check_smoosh "a**", ["a", "**"] 110 | end 111 | 112 | it "treats **/ like **" do 113 | check_smoosh "**/", ["**"] 114 | check_smoosh "a**/b", ["a", "**", "b"] 115 | end 116 | end 117 | 118 | end 119 | end 120 | 121 | -------------------------------------------------------------------------------- /spec/inc_process.rb: -------------------------------------------------------------------------------- 1 | here = File.expand_path(File.dirname(__FILE__)) 2 | require 'tmpdir' 3 | require_relative('../lib/rerun/system') 4 | require 'timeout' 5 | 6 | class IncProcess 7 | 8 | include Rerun::System 9 | 10 | attr_reader :dir, :inc_output_file 11 | attr_reader :dir1, :dir2 12 | attr_reader :rerun_pid, :inc_parent_pid 13 | 14 | def initialize 15 | @dir = Dir.tmpdir + "/#{Time.now.to_i}" 16 | FileUtils.mkdir_p(@dir) 17 | 18 | @inc_output_file = "#{@dir}/inc.txt" 19 | 20 | @dir1 = File.join(@dir, "dir1") 21 | FileUtils.mkdir_p(@dir1) 22 | 23 | @dir2 = File.join(@dir, "dir2") 24 | FileUtils.mkdir_p(@dir2) 25 | 26 | end 27 | 28 | # don't call this until you're sure it's running 29 | def kill 30 | begin 31 | pids = ([@inc_pid, @inc_parent_pid, @rerun_pid] - [Process.pid]).uniq 32 | ::Timeout.timeout(5) do 33 | pids.each do |pid| 34 | if windows? 35 | system("taskkill /F /T /PID #{pid}") 36 | else 37 | # puts "Killing #{pid} gracefully" 38 | Process.kill("INT", pid) rescue Errno::ESRCH 39 | end 40 | end 41 | pids.each do |pid| 42 | # puts "waiting for #{pid}" 43 | Process.wait(pid) rescue Errno::ECHILD 44 | end 45 | end 46 | rescue Timeout::Error 47 | pids.each do |pid| 48 | # puts "Killing #{pid} forcefully" 49 | Process.kill("KILL", pid) rescue Errno::ESRCH 50 | end 51 | end 52 | 53 | end 54 | 55 | def rerun_cmd 56 | root = File.dirname(__FILE__) + "/.." 57 | "#{root}/bin/rerun -d '#{@dir1},#{@dir2}' ruby #{root}/inc.rb #{@inc_output_file}" 58 | end 59 | 60 | def launch 61 | @rerun_pid = spawn(rerun_cmd) 62 | Timeout::timeout(10) { sleep 0.5 until File.exist?(@inc_output_file) } 63 | sleep 3 # let rerun's watcher get going 64 | read 65 | end 66 | 67 | def read 68 | File.open(@inc_output_file, "r") do |f| 69 | result = { 70 | launched_at: f.gets.to_i, 71 | count: f.gets.to_i, 72 | inc_pid: f.gets.to_i, 73 | inc_parent_pid: f.gets.to_i, 74 | } 75 | @inc_pid = result[:inc_pid] 76 | @inc_parent_pid = result[:inc_parent_pid] 77 | puts "reading #{@inc_output_file}: #{result.inspect}" 78 | result 79 | end 80 | end 81 | 82 | def current_count 83 | read[:count] 84 | end 85 | 86 | def touch(file = @existing_file) 87 | puts "#{Time.now.strftime("%T")} touching #{file}" 88 | File.open(file, "w") do |f| 89 | f.puts Time.now 90 | end 91 | end 92 | 93 | def type char 94 | # todo: send a character to stdin of the rerun process 95 | end 96 | 97 | end 98 | 99 | 100 | -------------------------------------------------------------------------------- /spec/options_spec.rb: -------------------------------------------------------------------------------- 1 | here = File.expand_path(File.dirname(__FILE__)) 2 | require "#{here}/spec_helper.rb" 3 | 4 | require "rerun/options" 5 | 6 | module Rerun 7 | describe Options do 8 | it "has good defaults" do 9 | defaults = Options.parse args: ["foo"] 10 | assert {defaults[:cmd] = "foo"} 11 | 12 | assert {defaults[:dir] == ["."]} 13 | assert {defaults[:pattern] == Options::DEFAULT_PATTERN} 14 | assert {defaults[:signal].include?('KILL')} 15 | assert {defaults[:wait] == 2} 16 | assert {defaults[:notify] == true} 17 | assert {defaults[:quiet] == false} 18 | assert {defaults[:verbose] == false} 19 | assert {defaults[:name] == 'Rerun'} 20 | assert {defaults[:force_polling] == false} 21 | assert {defaults[:ignore_dotfiles] == true} 22 | 23 | assert {defaults[:clear].nil?} 24 | assert {defaults[:exit].nil?} 25 | 26 | assert {defaults[:background] == false} 27 | end 28 | 29 | ["--help", "-h", "--usage", "--version"].each do |arg| 30 | describe "when passed #{arg}" do 31 | it "returns nil" do 32 | capturing do 33 | Options.parse(args: [arg]).should be_nil 34 | end 35 | end 36 | end 37 | end 38 | 39 | it "accepts --quiet" do 40 | options = Options.parse args: ["--quiet", "foo"] 41 | assert {options[:quiet] == true} 42 | end 43 | 44 | it "accepts --verbose" do 45 | options = Options.parse args: ["--verbose", "foo"] 46 | assert {options[:verbose] == true} 47 | end 48 | 49 | it "accepts --no-ignore-dotfiles" do 50 | options = Options.parse args: ["--no-ignore-dotfiles"] 51 | assert {options[:ignore_dotfiles] == false} 52 | end 53 | 54 | it "splits directories" do 55 | options = Options.parse args: ["--dir", "a,b", "foo"] 56 | assert {options[:dir] == ["a", "b"]} 57 | end 58 | 59 | it "adds directories specified individually with --dir" do 60 | options = Options.parse args: ["--dir", "a", "--dir", "b"] 61 | assert {options[:dir] == ["a", "b"]} 62 | end 63 | 64 | it "adds directories specified individually with -d" do 65 | options = Options.parse args: ["-d", "a", "-d", "b"] 66 | assert {options[:dir] == ["a", "b"]} 67 | end 68 | 69 | it "adds directories specified individually using mixed -d and --dir" do 70 | options = Options.parse args: ["-d", "a", "--dir", "b"] 71 | assert {options[:dir] == ["a", "b"]} 72 | end 73 | 74 | it "adds individual directories and splits comma-separated ones" do 75 | options = Options.parse args: ["--dir", "a", "--dir", "b", "--dir", "foo,other"] 76 | assert {options[:dir] == ["a", "b", "foo", "other"]} 77 | end 78 | 79 | it "accepts --name for a custom application name" do 80 | options = Options.parse args: ["--name", "scheduler"] 81 | assert {options[:name] == "scheduler"} 82 | end 83 | 84 | it "accepts --force-polling to force listener polling" do 85 | options = Options.parse args: ["--force-polling"] 86 | assert {options[:force_polling] == true} 87 | end 88 | 89 | it "accepts --ignore" do 90 | options = Options.parse args: ["--ignore", "log/*"] 91 | assert {options[:ignore] == ["log/*"]} 92 | end 93 | 94 | it "accepts --ignore multiple times" do 95 | options = Options.parse args: ["--ignore", "log/*", "--ignore", "*.tmp"] 96 | assert {options[:ignore] == ["log/*", "*.tmp"]} 97 | end 98 | 99 | it "accepts --restart which allows the process to restart itself, defaulting to HUP" do 100 | options = Options.parse args: ["--restart"] 101 | assert {options[:restart]} 102 | assert {options[:signal] == "HUP"} 103 | end 104 | 105 | it "allows user to override HUP signal when --restart is specified" do 106 | options = Options.parse args: %w[--restart --signal INT] 107 | assert {options[:restart]} 108 | assert {options[:signal] == "INT"} 109 | end 110 | 111 | # notifications 112 | 113 | it "rejects --no-growl" do 114 | options = nil 115 | err = capturing(:stderr) do 116 | options = Options.parse args: %w[--no-growl echo foo] 117 | end 118 | 119 | assert {options.nil?} 120 | assert {err.include? "use --no-notify"} 121 | end 122 | 123 | it "defaults to --notify true (meaning 'use what works')" do 124 | options = Options.parse args: %w[echo foo] 125 | assert {options[:notify] == true} 126 | end 127 | 128 | it "accepts bare --notify" do 129 | options = Options.parse args: %w[--notify -- echo foo] 130 | assert {options[:notify] == true} 131 | end 132 | 133 | %w[growl osx].each do |notifier| 134 | it "accepts --notify #{notifier}" do 135 | options = Options.parse args: ["--notify", notifier, "echo foo"] 136 | assert {options[:notify] == notifier} 137 | end 138 | end 139 | 140 | it "accepts --no-notify" do 141 | options = Options.parse args: %w[--no-notify echo foo] 142 | assert {options[:notify] == false} 143 | end 144 | 145 | describe 'reading from a config file' do 146 | require 'files' 147 | include ::Files 148 | 149 | let!(:config_file) { 150 | file 'test-rerun-config', <<-TEXT 151 | --quiet 152 | --pattern **/*.foo 153 | TEXT 154 | } 155 | 156 | it 'uses the config file\'s values over the defaults' do 157 | o = Options.parse(args: [], config_file: config_file) 158 | assert { o[:quiet] } 159 | assert { o[:pattern] == '**/*.foo' } 160 | end 161 | 162 | it 'uses the command-line args over the config file\'s' do 163 | o = Options.parse(args: %w{--no-quiet --pattern **/*.bar --verbose}, 164 | config_file: config_file) 165 | assert { o[:verbose] == true } 166 | assert { not o[:quiet] } 167 | assert { o[:pattern] == '**/*.bar' } 168 | end 169 | end 170 | 171 | describe 'Usage' do 172 | it "is displayed when no args are given" do 173 | expect(Options).to receive(:puts) do |args| 174 | expect(args.to_s).to match(/^Usage/) 175 | end 176 | 177 | Options.parse(args: []) 178 | end 179 | 180 | it "is not displayed when args are given" do 181 | expect(Options).not_to receive(:puts) 182 | 183 | Options.parse args: ["--name", "echo foo"] 184 | end 185 | end 186 | end 187 | end 188 | -------------------------------------------------------------------------------- /spec/runner_spec.rb: -------------------------------------------------------------------------------- 1 | here = File.expand_path(File.dirname(__FILE__)) 2 | require "#{here}/spec_helper.rb" 3 | require 'rerun' 4 | 5 | module Rerun 6 | describe Runner do 7 | class Runner 8 | attr_reader :run_command 9 | end 10 | 11 | describe "initialization and configuration" do 12 | it "accepts a command" do 13 | runner = Runner.new("foo") 14 | runner.run_command.should == "foo" 15 | end 16 | 17 | it "If the command is a .rb file, then run it with ruby" do 18 | runner = Runner.new("foo.rb") 19 | runner.run_command.should == "ruby foo.rb" 20 | end 21 | 22 | it "If the command starts with a .rb file, then run it with ruby" do 23 | runner = Runner.new("foo.rb --param bar baz.txt") 24 | runner.run_command.should == "ruby foo.rb --param bar baz.txt" 25 | end 26 | 27 | it "clears the screen" do 28 | runner = Runner.new("foo.rb", {:clear => true}) 29 | runner.clear?.should be_truthy 30 | end 31 | 32 | it "can be quiet" do 33 | runner = Runner.new("foo.rb", {:quiet => true}) 34 | runner.quiet?.should == true 35 | end 36 | 37 | it "can be verbose" do 38 | runner = Runner.new("foo.rb", {:verbose => true}) 39 | runner.verbose?.should == true 40 | end 41 | 42 | it "starts a watcher with the given options" do 43 | options = Rerun::Options::DEFAULTS.dup.merge(ignore_dotfiles: false) 44 | runner = Runner.new("foo.rb", options) 45 | runner.start 46 | runner.watcher.ignore_dotfiles.should be_falsey 47 | end 48 | 49 | # TODO: test that quiet actually suppresses output 50 | # TODO: test that verbose actually shows more output 51 | # TODO: warn that verbose is overridden by quiet if you specify both 52 | end 53 | 54 | describe "running" do 55 | it "sends its child process a SIGINT when restarting" 56 | 57 | it "dies when sent a control-C (SIGINT)" 58 | 59 | it "accepts a key press" 60 | 61 | it "restarts with HUP" 62 | 63 | it "restarts with a different signal" 64 | end 65 | 66 | describe 'change_message' do 67 | subject { Runner.new("").change_message(changes) } 68 | [:modified, :added, :removed].each do |change_type| 69 | context "one #{change_type}" do 70 | let(:changes) { {change_type => ["foo.rb"]} } 71 | it 'says how many of each type of change' do 72 | expect(subject == "1 modified: foo.rb") 73 | end 74 | end 75 | end 76 | 77 | context "two changes" do 78 | let(:changes) { {modified: ["foo.rb", "bar.rb"]} } 79 | it 'uses a comma' do 80 | expect(subject == "2 modified: foo.rb, bar.rb") 81 | end 82 | end 83 | 84 | context "three changes" do 85 | let(:changes) { {modified: ["foo.rb", "bar.rb", "baz.rb"]} } 86 | it 'elides after the third' do 87 | expect(subject == "3 modified: foo.rb, bar.rb, baz.rb") 88 | end 89 | end 90 | 91 | context "more than three changes" do 92 | let(:changes) { {modified: ["foo.rb", "bar.rb", "baz.rb", "baf.rb"]} } 93 | it 'elides after the third' do 94 | expect(subject == "4 modified: foo.rb, bar.rb, baz.rb, ...") 95 | end 96 | end 97 | 98 | context "with a path" do 99 | let(:changes) { {modified: ["baz/bar/foo.rb"]} } 100 | it 'strips the path' do 101 | expect(subject == "1 modified: foo.rb") 102 | end 103 | end 104 | end 105 | 106 | end 107 | 108 | end 109 | -------------------------------------------------------------------------------- /spec/spec_helper.rb: -------------------------------------------------------------------------------- 1 | require "rubygems" 2 | require "rspec" 3 | #require "rspec/autorun" 4 | require 'date' # fix odd nested require glitch 5 | 6 | require "wrong/adapters/rspec" 7 | include Wrong::D 8 | 9 | here = File.expand_path(File.dirname(__FILE__)) 10 | $: << File.expand_path("#{here}/../lib") 11 | 12 | require "rerun" 13 | 14 | RSpec.configure do |config| 15 | config.expect_with(:rspec) { |c| c.syntax = %i[expect should] } 16 | end 17 | -------------------------------------------------------------------------------- /spec/watcher_spec.rb: -------------------------------------------------------------------------------- 1 | here = File.expand_path(File.dirname(__FILE__)) 2 | require "#{here}/spec_helper.rb" 3 | require 'tmpdir' 4 | require 'rerun/watcher' 5 | 6 | module Rerun 7 | describe Watcher do 8 | 9 | COOL_OFF_TIME = 2 10 | 11 | def start_watcher options = {} 12 | options = {:directory => @dir, :pattern => "*.txt"}.merge(options) 13 | @log = nil 14 | @watcher = Watcher.new(options) do |hash| 15 | @log = hash 16 | end 17 | @watcher.start 18 | sleep(COOL_OFF_TIME) # let it spin up 19 | end 20 | 21 | def stop_watcher 22 | begin 23 | @watcher.stop 24 | rescue ThreadError => e 25 | end 26 | end 27 | 28 | before do 29 | @dir = Dir.tmpdir + "/#{Time.now.to_i}-#{(rand*100000).to_i}" 30 | # fix goofy MacOS /tmp path ambiguity 31 | @dir.sub!(/^\/var/, "/private/var") 32 | FileUtils.mkdir_p(@dir) 33 | 34 | start_watcher 35 | end 36 | 37 | let(:rest) { 2 } 38 | 39 | after do 40 | stop_watcher 41 | end 42 | 43 | def create test_file, sleep = true 44 | File.open(test_file, "w") do |f| 45 | f.puts("test") 46 | end 47 | sleep(rest) if sleep 48 | end 49 | 50 | def modify test_file 51 | File.open(test_file, "a") do |f| 52 | f.puts("more more more") 53 | end 54 | sleep(rest) 55 | end 56 | 57 | def remove test_file 58 | File.delete(test_file) 59 | sleep(rest) 60 | end 61 | 62 | it "watches file changes" do 63 | test_file = "#{@dir}/test.txt" 64 | 65 | create test_file 66 | @log[:added].should == [test_file] 67 | 68 | modify test_file 69 | @log[:modified].should == [test_file] 70 | 71 | remove test_file 72 | @log[:removed].should == [test_file] 73 | end 74 | 75 | it "ignores changes to non-matching files" do 76 | non_matching_file = "#{@dir}/test.exe" 77 | 78 | create non_matching_file 79 | @log.should be_nil 80 | 81 | modify non_matching_file 82 | @log.should be_nil 83 | 84 | remove non_matching_file 85 | @log.should be_nil 86 | end 87 | 88 | it "ignores changes to dot-files" do 89 | dot_file = "#{@dir}/.ignoreme.txt" 90 | 91 | create dot_file 92 | @log.should be_nil 93 | 94 | modify dot_file 95 | @log.should be_nil 96 | 97 | remove dot_file 98 | @log.should be_nil 99 | end 100 | 101 | it "does not ignore changes to dot-files" do 102 | stop_watcher 103 | start_watcher(ignore_dotfiles: false) 104 | 105 | test_file = "#{@dir}/.test.txt" 106 | 107 | create test_file 108 | @log[:added].should == [test_file] 109 | 110 | modify test_file 111 | @log[:modified].should == [test_file] 112 | 113 | remove test_file 114 | @log[:removed].should == [test_file] 115 | end 116 | 117 | # TODO: unify with Listen::Silencer::DEFAULT_IGNORED_DIRECTORIES 118 | ignored_directories = %w[ 119 | .git .svn .hg .rbx .bundle bundle vendor/bundle log tmp vendor/ruby 120 | ] 121 | 122 | it "ignores directories named #{ignored_directories}" do 123 | ignored_directories.each do |ignored_dir| 124 | FileUtils.mkdir_p "#{@dir}/#{ignored_dir}" 125 | create [@dir, ignored_dir, "foo.txt"].join('/'), false 126 | end 127 | sleep(rest) 128 | @log.should be_nil 129 | end 130 | 131 | it "ignores files named `.DS_Store`." do 132 | create "#{@dir}/.DS_Store" 133 | @log.should be_nil 134 | end 135 | 136 | it "ignores files ending with `.tmp`." do 137 | create "#{@dir}/foo.tmp" 138 | @log.should be_nil 139 | end 140 | 141 | it "optionally ignores globs" do 142 | stop_watcher 143 | start_watcher ignore: "foo*" 144 | create "#{@dir}/foo.txt" 145 | @log.should be_nil 146 | end 147 | 148 | it "disables watcher when paused" do 149 | @watcher.pause 150 | create "#{@dir}/pause_test.txt" 151 | @log.should be_nil 152 | end 153 | 154 | it "pauses and resumes watcher" do 155 | @watcher.pause 156 | create "#{@dir}/pause_test.txt" 157 | @log.should be_nil 158 | sleep 0.5 # work around rare timing issue on Travis 159 | @watcher.unpause 160 | test_file = "#{@dir}/pause_test2.txt" 161 | create test_file 162 | @log[:added].should == [test_file] 163 | end 164 | 165 | it "can figure out the Listen adapter" do 166 | @watcher.adapter.class.should == Listen::Adapter.select 167 | end 168 | 169 | it "passes the force-polling option to Listen" do 170 | stop_watcher 171 | start_watcher force_polling: true 172 | @watcher.adapter.class.should == Listen::Adapter::Polling 173 | @watcher.adapter_name.should == "Polling" 174 | end 175 | 176 | end 177 | 178 | end 179 | -------------------------------------------------------------------------------- /stty.rb: -------------------------------------------------------------------------------- 1 | # see /usr/include/sys/termios.h 2 | # see man stty 3 | require "wrong" 4 | include Wrong 5 | 6 | def tty 7 | stty = `stty -g` 8 | stty.split(':') 9 | end 10 | 11 | def tty_setting name 12 | tty.grep(/^#{name}/).first.split('=').last 13 | end 14 | 15 | def oflag 16 | tty_setting("oflag") 17 | end 18 | 19 | normal = tty 20 | 21 | `stty raw` 22 | raw = tty 23 | 24 | `stty -raw` 25 | minus_raw = tty 26 | assert { minus_raw == normal } 27 | 28 | `stty raw opost` 29 | raw_opost = tty 30 | 31 | d { raw - normal } 32 | d { normal - raw } 33 | d { normal - raw_opost } 34 | 35 | puts "== normal" 36 | # d { tty } 37 | d{oflag} 38 | 39 | def check setting 40 | `stty #{setting}` 41 | puts "testing #{setting}:\nline\nline" 42 | print "\r\n" 43 | end 44 | 45 | check "raw" 46 | 47 | check "-raw" 48 | 49 | check "raw opost" 50 | 51 | check "-raw" 52 | 53 | check "raw gfmt1:oflag=3" 54 | 55 | # check "oflag=3" 56 | # check "lflag=200005cb" 57 | # check "iflag=2b02" 58 | `stty -raw` 59 | -------------------------------------------------------------------------------- /todo.md: -------------------------------------------------------------------------------- 1 | * use GNTP 2 | * https://github.com/snaka/ruby_gntp 3 | * https://github.com/ericgj/groem 4 | * http://growl.info/documentation/developer/gntp.php 5 | 6 | * test stty stuff on other Unixes 7 | 8 | * use childprocess 9 | 10 | * specify escalation timeout (time between sending SIGTERM and SIGINT) 11 | 12 | * also try to kill whatever process is listening to a given port 13 | (in case it's ignoring or doesn't get the signal from its parent) 14 | (see http://stackoverflow.com/questions/8105322/foreman-does-not-kill-processes for lsof example) 15 | 16 | --------------------------------------------------------------------------------