dreamcat4-rerun 0.3.1

Sign up to get free protection for your applications and to get access to all the features.
data/LICENSE ADDED
@@ -0,0 +1,34 @@
1
+ Rerun
2
+ Copyright (c) 2009 Alex Chaffee <alex@stinky.com>
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 <tomayko.com/about>
33
+ License as above (MIT open source).
34
+
data/README.md ADDED
@@ -0,0 +1,138 @@
1
+ # Rerun
2
+
3
+ <http://github.com/alexch/rerun>
4
+
5
+ Launches your app, then watches the filesystem. If a relevant file
6
+ changes, then it restarts your app.
7
+
8
+ Currently only *.rb files are watched. This is pretty lame so it will
9
+ change soon.
10
+
11
+ If you're on Mac OS X, it uses the built-in facilities for monitoring
12
+ the filesystem, so CPU use is very light. And if you have "growlnotify"
13
+ available on the PATH, it sends notifications to growl in addition to
14
+ the console. Here's how to install
15
+ [growlnotify](http://growl.info/documentation/growlnotify.php):
16
+
17
+ > In your shell, cd to the directory on the Growl disk image
18
+ > containing growlnotify, and type ./install.sh. That script
19
+ > will install growlnotify to /usr/local/bin and the manpage
20
+ > to /usr/local/man.
21
+
22
+ Rerun does not work on Windows. Sorry, but you can't do much relaunching
23
+ without "fork".
24
+
25
+ # Installation:
26
+
27
+ sudo gem install rerun
28
+
29
+ If you want to use the latest version, grab it off Github:
30
+
31
+ gem sources -a http://gems.github.com/
32
+ sudo gem install alexch-rerun
33
+
34
+ I'll bump the version on Github for release candidates, and deploy to
35
+ Rubyforge only when it's had some time to bake.
36
+
37
+ # Usage:
38
+
39
+ rerun [options] cmd
40
+
41
+ For example, if you're running a Sinatra app whose main file is
42
+ app.rb:
43
+
44
+ rerun app.rb
45
+
46
+ Or if you're running a Rack app that's configured in config.ru
47
+ but you want it on port 4000 and in debug mode:
48
+
49
+ rerun "thin start --debug --port=4000 -R config.ru"
50
+
51
+ # Options:
52
+
53
+ --dir directory to watch (default = ".")
54
+
55
+ Also --version and --help.
56
+
57
+ # To Do:
58
+
59
+ * If the cmd is, or starts with, a ".rb" file, then run it with ruby
60
+ * Watch arbitrary file types via globbing
61
+ * Allow arbitrary sets of directories and file types, possibly with "include" and "exclude" sets
62
+ * ".rerun" file to specify options per project or in $HOME.
63
+ * Test on Linux.
64
+ * Test on Mac without Growlnotify.
65
+
66
+ # Other projects that do similar things
67
+
68
+ Restartomatic: <http://github.com/adammck/restartomatic>
69
+
70
+ Shotgun: <http://github.com/rtomayko/shotgun>
71
+
72
+ Rack::Reloader middleware: <http://github.com/rack/rack/blob/5ca8f82fb59f0bf0e8fd438e8e91c5acf3d98e44/lib/rack/reloader.rb>
73
+
74
+ and the Sinatra FAQ has a discussion at <http://www.sinatrarb.com/faq.html#reloading>
75
+
76
+ # Why would I use this instead of Shotgun?
77
+
78
+ Shotgun does a "fork" after the web framework has loaded but before
79
+ your application is loaded. It then loads your app, processes a
80
+ single request in the child process, then exits the child process.
81
+
82
+ Rerun launches the whole app, then when it's time to restart, uses
83
+ "kill" to shut it down and starts the whole thing up again from
84
+ scratch.
85
+
86
+ So rerun takes somewhat longer than Shotgun to restart the app, but
87
+ does it much less frequently. And once it's running it behaves more
88
+ normally and consistently with your production app.
89
+
90
+ Also, Shotgun reloads the app on every request, even if it doesn't
91
+ need to. This is fine if you're loading a single file, but my web
92
+ pages all load other files (CSS, JS, media) and that adds up quickly.
93
+ The developers of shotgun are probably using caching or a front web
94
+ server so this doesn't affect them too much.
95
+
96
+ YMMV!
97
+
98
+ # Why would I use this instead of Rack::Reloader?
99
+
100
+ Rack::Reloader is certifiably beautiful code, and is a very elegant use
101
+ of Rack's middleware architecture. But because it relies on the
102
+ LOADED_FEATURES variable, it only reloads .rb files that were 'require'd,
103
+ not 'load'ed. That leaves out (non-Erector) template files, and also,
104
+ the way I was doing it, sub-actions (see
105
+ [this thread](http://groups.google.com/group/sinatrarb/browse_thread/thread/7329727a9296e96a#
106
+ )).
107
+
108
+ Rack::Reloader also doesn't reload configuration changes or redo other
109
+ things that happen during app startup. Rerun takes the attitude that if
110
+ you want to restart an app, you should just restart the whole app. You know?
111
+
112
+ # Why did you write this?
113
+
114
+ I've been using [Sinatra](http://sinatrarb.com) and loving it. In order
115
+ to simplify their system, the Rat Pack just removed auto-reloading from
116
+ Sinatra proper. I approve of this: a web application framework should be
117
+ focused on serving requests, not on munging Ruby ObjectSpace for
118
+ dev-time convenience. But I still wanted automatic reloading during
119
+ development. Shotgun wasn't working for me (see above) so I spliced
120
+ Rerun together out of code from Rspactor, FileSystemWatcher, and Shotgun
121
+ -- with a heavy amount of refactoring and rewriting.
122
+
123
+ # Credits
124
+
125
+ Rerun: Alex Chaffee, <mailto:alex@stinky.com>, <http://github.com/alexch/>
126
+
127
+ Based upon and/or inspired by:
128
+
129
+ Shotgun: <http://github.com/rtomayko/shotgun>
130
+
131
+ Rspactor: <http://github.com/mislav/rspactor>
132
+ (In turn based on http://rails.aizatto.com/2007/11/28/taming-the-autotest-beast-with-fsevents/ )
133
+
134
+ FileSystemWatcher: <http://paulhorman.com/filesystemwatcher/>
135
+
136
+ # License
137
+
138
+ Open Source MIT License. See "LICENSE" file.
data/Rakefile ADDED
@@ -0,0 +1,64 @@
1
+ require 'rake'
2
+ require 'rake/clean'
3
+ require 'rake/testtask'
4
+ require 'spec/rake/spectask'
5
+
6
+ task :default => [:spec]
7
+ task :test => :spec
8
+
9
+ desc "Run all specs"
10
+ Spec::Rake::SpecTask.new('spec') do |t|
11
+ ENV['ENV'] = "test"
12
+ t.spec_files = FileList['spec/**/*_spec.rb']
13
+ t.ruby_opts = ['-rubygems'] if defined? Gem
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
25
+ end
26
+
27
+ def package(ext='')
28
+ "pkg/#{$spec.name}-#{$spec.version}" + ext
29
+ end
30
+
31
+ desc 'Build packages'
32
+ task :package => %w[.gem .tar.gz].map { |e| package(e) }
33
+
34
+ desc 'Build and install as local gem'
35
+ task :install => package('.gem') do
36
+ sh "gem install #{package('.gem')}"
37
+ end
38
+
39
+ directory 'pkg/'
40
+ CLOBBER.include('pkg')
41
+
42
+ file package('.gem') => %W[pkg/ #{$spec.name}.gemspec] + $spec.files do |f|
43
+ sh "gem build #{$spec.name}.gemspec"
44
+ mv File.basename(f.name), f.name
45
+ end
46
+
47
+ file package('.tar.gz') => %w[pkg/] + $spec.files do |f|
48
+ cmd = <<-SH
49
+ git archive \
50
+ --prefix=#{$spec.name}-#{$spec.version}/ \
51
+ --format=tar \
52
+ HEAD | gzip > #{f.name}
53
+ SH
54
+ sh cmd.gsub(/ +/, ' ')
55
+ end
56
+
57
+ desc 'Publish gem and tarball to rubyforge'
58
+ task 'release' => [package('.gem'), package('.tar.gz')] do |t|
59
+ sh <<-end
60
+ rubyforge add_release #{$rubyforge_project} #{$spec.name} #{$spec.version} #{package('.gem')} &&
61
+ rubyforge add_file #{$rubyforge_project} #{$spec.name} #{$spec.version} #{package('.tar.gz')}
62
+ end
63
+ end
64
+ 5
data/bin/rerun ADDED
@@ -0,0 +1,56 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'rubygems'
4
+
5
+ libdir = "#{File.expand_path(File.dirname(File.dirname(__FILE__)))}/lib"
6
+ $LOAD_PATH.unshift libdir unless $LOAD_PATH.include?(libdir)
7
+
8
+ load "#{libdir}/../rerun.gemspec" # defines "$spec" variable, which we read the version from
9
+
10
+ require 'optparse'
11
+
12
+ options = {}
13
+
14
+ opts = OptionParser.new("", 24, ' ') { |opts|
15
+ opts.banner = "Usage: rerun cmd"
16
+
17
+ opts.separator ""
18
+ opts.separator "Launches an app, and restarts it when the filesystem changes."
19
+ opts.separator "See http://github.com/alexch/rerun for more info."
20
+ opts.separator ""
21
+ opts.separator "Options:"
22
+
23
+ opts.on("-d dir", "--dir dir", "directory to watch") do |dir|
24
+ options[:dir] = dir
25
+ end
26
+
27
+ opts.on_tail("-h", "--help", "--usage", "show this message") do
28
+ puts opts
29
+ exit
30
+ end
31
+
32
+ opts.on_tail("--version", "show version") do
33
+ puts $spec.version
34
+ exit
35
+ end
36
+
37
+ opts.parse! ARGV
38
+ }
39
+
40
+ #config = ARGV[0] || "config.ru"
41
+ #abort "configuration #{config} not found" unless File.exist? config
42
+ #
43
+ #if config =~ /\.ru$/ && File.read(config)[/^#\\(.*)/]
44
+ # opts.parse! $1.split(/\s+/)
45
+ #end
46
+
47
+ if ARGV.empty?
48
+ puts opts
49
+ exit
50
+ end
51
+
52
+ require 'rerun'
53
+ cmd = ARGV.join(" ")
54
+ runner = Rerun::Runner.new(cmd, options)
55
+ runner.start
56
+ runner.join
data/lib/fswatcher.rb ADDED
@@ -0,0 +1,6 @@
1
+ require 'watcher'
2
+
3
+ module Rerun
4
+ class FSWatcher < Watcher
5
+ end
6
+ end
data/lib/osxwatcher.rb ADDED
@@ -0,0 +1,95 @@
1
+ require "system"
2
+ require "watcher"
3
+
4
+ #TODO: make it notice deleted files
5
+ require "watcher"
6
+ module Rerun
7
+ class OSXWatcher < Rerun::Watcher
8
+ attr_reader :last_check, :valid_extensions
9
+ attr_reader :stream
10
+
11
+ def start
12
+ prime
13
+ timestamp_checked
14
+
15
+ dirs = Array(directories.map{|d| d.dir})
16
+
17
+ mac_callback = lambda do |stream, ctx, num_events, paths, marks, event_ids|
18
+ examine
19
+ # changed_files = extract_changed_files_from_paths(split_paths(paths, num_events))
20
+ # timestamp_checked
21
+ # puts "changed files:"
22
+ # p changed_files
23
+ # yield changed_files unless changed_files.empty?
24
+ end
25
+
26
+ @stream = OSX::FSEventStreamCreate(OSX::KCFAllocatorDefault, mac_callback, nil, dirs, OSX::KFSEventStreamEventIdSinceNow, @sleep_time, 0)
27
+ raise "Failed to create stream" unless stream
28
+
29
+ OSX::FSEventStreamScheduleWithRunLoop(stream, OSX::CFRunLoopGetCurrent(), OSX::KCFRunLoopDefaultMode)
30
+ unless OSX::FSEventStreamStart(stream)
31
+ raise "Failed to start stream"
32
+ end
33
+
34
+ @thread = Thread.new do
35
+ begin
36
+ OSX::CFRunLoopRun()
37
+ rescue Interrupt
38
+ OSX::FSEventStreamStop(stream)
39
+ OSX::FSEventStreamInvalidate(stream)
40
+ OSX::FSEventStreamRelease(stream)
41
+ @stream = nil
42
+ end
43
+ end
44
+
45
+ @thread.priority = @priority
46
+ end
47
+
48
+ def stop
49
+ @thread.kill
50
+ end
51
+
52
+ def timestamp_checked
53
+ @last_check = Time.now
54
+ end
55
+
56
+ def split_paths(paths, num_events)
57
+ paths.regard_as('*')
58
+ rpaths = []
59
+ num_events.times { |i| rpaths << paths[i] }
60
+ rpaths
61
+ end
62
+
63
+ def extract_changed_files_from_paths(paths)
64
+ changed_files = []
65
+ paths.each do |path|
66
+ next if ignore_path?(path)
67
+ Dir.glob(path + "*").each do |file|
68
+ next if ignore_file?(file)
69
+ changed_files << file if file_changed?(file)
70
+ end
71
+ end
72
+ changed_files
73
+ end
74
+
75
+ def file_changed?(file)
76
+ File.stat(file).mtime > last_check
77
+ end
78
+
79
+ def ignore_path?(path)
80
+ path =~ /(?:^|\/)\.(git|svn)/
81
+ end
82
+
83
+ def ignore_file?(file)
84
+ File.basename(file).index('.') == 0 or not valid_extension?(file)
85
+ end
86
+
87
+ def file_extension(file)
88
+ file =~ /\.(\w+)$/ and $1
89
+ end
90
+
91
+ def valid_extension?(file)
92
+ valid_extensions.nil? or valid_extensions.include?(file_extension(file))
93
+ end
94
+ end
95
+ end
data/lib/rerun.rb ADDED
@@ -0,0 +1,136 @@
1
+ require "system"
2
+ require "watcher"
3
+ require "osxwatcher"
4
+ require "fswatcher"
5
+
6
+ # todo: make sure this works in non-Mac environments (also Macs without growlnotify)
7
+ module Rerun
8
+ class Runner
9
+
10
+ include System
11
+
12
+ def initialize(run_command, options = {})
13
+ @run_command, @options = run_command, options
14
+ end
15
+
16
+ def restart
17
+ @restarting = true
18
+ stop
19
+ start
20
+ @restarting = false
21
+ end
22
+
23
+ def dir
24
+ @options[:dir] || "."
25
+ end
26
+
27
+ def start
28
+ if windows?
29
+ raise "Sorry, Rerun does not work on Windows."
30
+ end
31
+
32
+ if (!@already_running)
33
+ taglines = [
34
+ "To infinity... and beyond!",
35
+ "Charge!",
36
+ ]
37
+ notify "Launched", taglines[rand(taglines.size)]
38
+ @already_running = true
39
+ else
40
+ taglines = [
41
+ "Here we go again!",
42
+ "Once more unto the breach, dear friends, once more!",
43
+ ]
44
+ notify "Restarted", taglines[rand(taglines.size)]
45
+ end
46
+
47
+ @pid = Kernel.fork do
48
+ begin
49
+ # Signal.trap("INT") { exit }
50
+ exec(@run_command)
51
+ rescue => e
52
+ puts e
53
+ exit
54
+ end
55
+ end
56
+ Process.detach(@pid) # so if the child exits, it dies
57
+
58
+ Signal.trap("INT") do # INT = control-C
59
+ stop # first stop the child
60
+ exit
61
+ end
62
+
63
+ begin
64
+ sleep 2
65
+ rescue Interrupt => e
66
+ # in case someone hits control-C immediately
67
+ stop
68
+ exit
69
+ end
70
+
71
+ unless running?
72
+ notify "Launch Failed", "See console for error output"
73
+ @already_running = false
74
+ end
75
+
76
+ unless @watcher
77
+ watcher_class = mac? ? OSXWatcher : FSWatcher
78
+ # watcher_class = FSWatcher
79
+
80
+ watcher = watcher_class.new do
81
+ restart unless @restarting
82
+ end
83
+ puts "Watching #{dir}"
84
+ watcher.add_directory(dir, "**/*.rb")
85
+ watcher.sleep_time = 1
86
+ watcher.start
87
+ @watcher = watcher
88
+ end
89
+
90
+ end
91
+
92
+ def join
93
+ @watcher.join
94
+ end
95
+
96
+ def running?
97
+ signal(0)
98
+ end
99
+
100
+ def signal(signal)
101
+ Process.kill(signal, @pid)
102
+ true
103
+ rescue
104
+ false
105
+ end
106
+
107
+ def stop
108
+ if @pid && (@pid != 0)
109
+ notify "Stopped", "All good things must come to an end." unless @restarting
110
+ signal("KILL") && Process.wait(@pid)
111
+ end
112
+ rescue => e
113
+ false
114
+ end
115
+
116
+ def git_head_changed?
117
+ old_git_head = @git_head
118
+ read_git_head
119
+ @git_head and old_git_head and @git_head != old_git_head
120
+ end
121
+
122
+ def read_git_head
123
+ git_head_file = File.join(dir, '.git', 'HEAD')
124
+ @git_head = File.exists?(git_head_file) && File.read(git_head_file)
125
+ end
126
+
127
+ def notify(title, body)
128
+ growl title, body
129
+ puts
130
+ puts "#{Time.now.strftime("%T")} - #{app_name} #{title}"
131
+ end
132
+
133
+ end
134
+
135
+ end
136
+
data/lib/system.rb ADDED
@@ -0,0 +1,59 @@
1
+ def mac?
2
+ RUBY_PLATFORM =~ /darwin/i && !$osx_foundation_failed_to_load
3
+ end
4
+
5
+ def windows?
6
+ RUBY_PLATFORM =~ /mswin/i
7
+ end
8
+
9
+ def linux?
10
+ RUBY_PLATFORM =~ /linux/i
11
+ end
12
+
13
+ if mac?
14
+ begin
15
+ require 'osx/foundation'
16
+ OSX.require_framework '/System/Library/Frameworks/CoreServices.framework/Frameworks/CarbonCore.framework'
17
+ rescue
18
+ $osx_foundation_failed_to_load = true
19
+ end
20
+ end
21
+
22
+ module Rerun
23
+ module System
24
+
25
+ # do we have growl or not?
26
+ def growl?
27
+ mac? && (growlcmd != "")
28
+ end
29
+
30
+ def growlcmd
31
+ `which growlnotify`.chomp
32
+ end
33
+
34
+ def app_name
35
+ # todo: make sure this works in non-Mac and non-Unix environments
36
+ File.expand_path(".").gsub(/^.*\//, '').capitalize
37
+ end
38
+
39
+ def icon
40
+ libdir = "#{File.expand_path(File.dirname(File.dirname(__FILE__)))}/lib"
41
+ $LOAD_PATH.unshift libdir unless $LOAD_PATH.include?(libdir)
42
+
43
+ rails_sig_file = File.expand_path(".")+"/config/boot.rb"
44
+ puts rails_sig_file
45
+ return "#{libdir}/../icons/rails_red_sml.png" if File.exists? rails_sig_file
46
+ return nil
47
+ end
48
+
49
+ def growl(title, body, background = true)
50
+ if growl?
51
+ icon ? icon_str = "--image \"#{icon}\"" : icon_str = ""
52
+ s = "#{growlcmd} -H localhost -n \"#{app_name}\" -m \"#{body}\" \"#{app_name} #{title}\" #{icon_str}"
53
+ s += " &" if background
54
+ `#{s}`
55
+ end
56
+ end
57
+
58
+ end
59
+ end
data/lib/watcher.rb ADDED
@@ -0,0 +1,203 @@
1
+
2
+ Thread.abort_on_exception = true
3
+
4
+ # This class will watch a directory or a set of directories and alert you of
5
+ # new files, modified files, deleted files.
6
+ #
7
+ # Author: Paul Horman, http://paulhorman.com/filesystemwatcher/
8
+ # Author: Alex Chaffee
9
+ module Rerun
10
+ class Watcher
11
+ CREATED = 0
12
+ MODIFIED = 1
13
+ DELETED = 2
14
+
15
+ attr_accessor :sleep_time, :priority
16
+ attr_reader :directories
17
+
18
+ def initialize(&client_callback)
19
+ @client_callback = client_callback
20
+
21
+ @sleep_time = 1
22
+ @priority = 0
23
+
24
+ @directories = []
25
+ @files = []
26
+
27
+ @found = nil
28
+ @first_time = true
29
+ @thread = nil
30
+
31
+ end
32
+
33
+ # add a directory to be watched
34
+ # @param dir the directory to watch
35
+ # @param expression the glob pattern to search under the watched directory
36
+ def add_directory(dir, expression="**/*")
37
+ if FileTest.exists?(dir) && FileTest.readable?(dir) then
38
+ @directories << Directory.new(dir, expression)
39
+ else
40
+ raise InvalidDirectoryError, "Dir '#{dir}' either doesnt exist or isnt readable"
41
+ end
42
+ end
43
+
44
+ def remove_directory(dir)
45
+ @directories.delete(dir)
46
+ end
47
+
48
+ # add a specific file to the watch list
49
+ # @param file the file to watch
50
+ def add_file(file)
51
+ if FileTest.exists?(file) && FileTest.readable?(file) then
52
+ @files << file
53
+ else
54
+ raise InvalidFileError, "File '#{file}' either doesnt exist or isnt readable"
55
+ end
56
+ end
57
+
58
+ def remove_file(file)
59
+ @files.delete(file)
60
+ end
61
+
62
+ def prime
63
+ @first_time = true
64
+ @found = Hash.new()
65
+ examine
66
+ @first_time = false
67
+ end
68
+
69
+ def start
70
+ if @thread then
71
+ raise RuntimeError, "already started"
72
+ end
73
+
74
+ prime
75
+
76
+ @thread = Thread.new do
77
+ while true do
78
+ examine
79
+ sleep(@sleep_time)
80
+ end
81
+ end
82
+
83
+ @thread.priority = @priority
84
+
85
+ at_exit { stop } #?
86
+
87
+ end
88
+
89
+ # kill the filewatcher thread
90
+ def stop
91
+ begin
92
+ @thread.wakeup
93
+ rescue ThreadError => e
94
+ # ignore
95
+ end
96
+ begin
97
+ @thread.kill
98
+ rescue ThreadError => e
99
+ # ignore
100
+ end
101
+ end
102
+
103
+ # wait for the filewatcher to finish
104
+ def join
105
+ @thread.join() if @thread
106
+ rescue Interrupt => e
107
+ # don't care
108
+ end
109
+
110
+ private
111
+
112
+ def examine
113
+ already_examined = Hash.new()
114
+
115
+ @directories.each do |directory|
116
+ examine_files(directory.files(), already_examined)
117
+ end
118
+
119
+ examine_files(@files, already_examined) if not @files.empty?
120
+
121
+ # now diff the found files and the examined files to see if
122
+ # something has been deleted
123
+ all_found_files = @found.keys()
124
+ all_examined_files = already_examined.keys()
125
+ intersection = all_found_files - all_examined_files
126
+ intersection.each do |file_name|
127
+ @client_callback.call(DELETED, file_name)
128
+ @found.delete(file_name)
129
+ end
130
+
131
+ end
132
+
133
+ # loops over the file list check for new or modified files
134
+ def examine_files(files, already_examined)
135
+ files.each do |file_name|
136
+ # expand the file name to the fully qual path
137
+ full_file_name = File.expand_path(file_name)
138
+
139
+ # we cant do much if the file isnt readable anyway
140
+ if File.readable?(full_file_name) then
141
+ already_examined[full_file_name] = true
142
+ stat = File.stat(full_file_name)
143
+ mod_time = stat.mtime
144
+ size = stat.size
145
+
146
+ # on the first iteration just load all of the files into the foundList
147
+ if @first_time then
148
+ @found[full_file_name] = FoundFile.new(full_file_name, mod_time, size)
149
+ else
150
+ # see if we have found this file already
151
+ found_file = @found[full_file_name]
152
+ @found[full_file_name] = FoundFile.new(full_file_name, mod_time, size)
153
+
154
+ if found_file
155
+ if mod_time > found_file.mod_time || size != found_file.size then
156
+ @client_callback.call(MODIFIED, full_file_name)
157
+ end
158
+ else
159
+ @client_callback.call(CREATED, full_file_name)
160
+ end
161
+ end
162
+ end
163
+ end
164
+ end
165
+
166
+ class Directory
167
+ attr_reader :dir, :expression
168
+
169
+ def initialize(dir, expression)
170
+ @dir, @expression = dir, expression
171
+ @dir.chop! if @dir =~ %r{/$}
172
+ end
173
+
174
+ def files()
175
+ return Dir[@dir + "/" + @expression]
176
+ end
177
+ end
178
+
179
+ class FoundFile
180
+ attr_reader :status, :file_name, :mod_time, :size
181
+
182
+ def initialize(file_name, mod_time, size)
183
+ @file_name, @mod_time, @size = file_name, mod_time, size
184
+ end
185
+
186
+ def modified(mod_time)
187
+ @mod_time = mod_time
188
+ end
189
+
190
+ def to_s
191
+ "FoundFile[file_name=#{file_name}, mod_time=#{mod_time.to_i}, size=#{size}]"
192
+ end
193
+ end
194
+
195
+ # if the directory you want to watch doesnt exist or isn't readable this is thrown
196
+ class InvalidDirectoryError < StandardError;
197
+ end
198
+
199
+ # if the file you want to watch doesnt exist or isn't readable this is thrown
200
+ class InvalidFileError < StandardError;
201
+ end
202
+ end
203
+ end
data/rerun.gemspec ADDED
@@ -0,0 +1,38 @@
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.3.1'
7
+ s.date = '2009-06-16'
8
+
9
+ s.description = "Restarts your app when a file changes"
10
+ s.summary = "Launches an app, and restarts it whenever the filesystem changes."
11
+
12
+ s.authors = ["Alex Chaffee"]
13
+ s.email = "alex@stinky.com"
14
+
15
+ s.files = %w[
16
+ README.md
17
+ LICENSE
18
+ Rakefile
19
+ rerun.gemspec
20
+ bin/rerun
21
+ lib/rerun.rb
22
+ lib/fswatcher.rb
23
+ lib/osxwatcher.rb
24
+ lib/system.rb
25
+ lib/watcher.rb
26
+ ]
27
+ s.executables = ['rerun']
28
+ s.test_files = s.files.select {|path| path =~ /^spec\/.*_spec.rb/}
29
+
30
+ s.extra_rdoc_files = %w[README.md]
31
+ #s.add_dependency 'rack', '>= 0.9.1'
32
+ #s.add_dependency 'launchy', '>= 0.3.3', '< 1.0'
33
+
34
+ s.homepage = "http://github.com/alexch/rerun/"
35
+ s.require_paths = %w[lib]
36
+ s.rubyforge_project = 'pivotalrb'
37
+ s.rubygems_version = '1.1.1'
38
+ end
metadata ADDED
@@ -0,0 +1,62 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: dreamcat4-rerun
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.3.1
5
+ platform: ruby
6
+ authors:
7
+ - Alex Chaffee
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2009-06-16 00:00:00 -07:00
13
+ default_executable:
14
+ dependencies: []
15
+
16
+ description: Restarts your app when a file changes
17
+ email: alex@stinky.com
18
+ executables:
19
+ - rerun
20
+ extensions: []
21
+
22
+ extra_rdoc_files:
23
+ - README.md
24
+ files:
25
+ - README.md
26
+ - LICENSE
27
+ - Rakefile
28
+ - rerun.gemspec
29
+ - bin/rerun
30
+ - lib/rerun.rb
31
+ - lib/fswatcher.rb
32
+ - lib/osxwatcher.rb
33
+ - lib/system.rb
34
+ - lib/watcher.rb
35
+ has_rdoc: false
36
+ homepage: http://github.com/alexch/rerun/
37
+ post_install_message:
38
+ rdoc_options: []
39
+
40
+ require_paths:
41
+ - lib
42
+ required_ruby_version: !ruby/object:Gem::Requirement
43
+ requirements:
44
+ - - ">="
45
+ - !ruby/object:Gem::Version
46
+ version: "0"
47
+ version:
48
+ required_rubygems_version: !ruby/object:Gem::Requirement
49
+ requirements:
50
+ - - ">="
51
+ - !ruby/object:Gem::Version
52
+ version: "0"
53
+ version:
54
+ requirements: []
55
+
56
+ rubyforge_project: pivotalrb
57
+ rubygems_version: 1.2.0
58
+ signing_key:
59
+ specification_version: 2
60
+ summary: Launches an app, and restarts it whenever the filesystem changes.
61
+ test_files: []
62
+