dreamcat4-rerun 0.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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
+