rspactor 0.6.4 → 0.7.0.beta.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 +2 -2
- data/README.rdoc +62 -15
- data/bin/rspactor +10 -9
- data/ext/fsevent/extconf.rb +28 -0
- data/ext/fsevent/fsevent_watch.c +44 -0
- data/images/failed.png +0 -0
- data/images/success.png +0 -0
- data/lib/growl/growl_formatter.rb +35 -0
- data/lib/rspactor/inspector.rb +36 -102
- data/lib/rspactor/interactor.rb +24 -92
- data/lib/rspactor/listener.rb +33 -62
- data/lib/rspactor/runner.rb +33 -173
- data/lib/rspactor/ui.rb +37 -0
- data/lib/rspactor/version.rb +3 -0
- data/lib/rspactor.rb +26 -7
- metadata +79 -46
- data/.gitignore +0 -21
- data/.rspactor +0 -0
- data/Rakefile +0 -50
- data/VERSION +0 -1
- data/lib/cucumber_growler.rb +0 -61
- data/lib/rspactor/growl.rb +0 -14
- data/lib/rspactor/spork.rb +0 -60
- data/lib/rspec_growler.rb +0 -24
- data/rspactor.gemspec +0 -70
- data/spec/inspector_spec.rb +0 -131
- data/spec/listener_spec.rb +0 -39
- data/spec/runner_spec.rb +0 -265
data/LICENSE
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
Copyright (c)
|
1
|
+
Copyright (c) 2010 Thibaud Guillaume-Gentil
|
2
2
|
|
3
3
|
Permission is hereby granted, free of charge, to any person obtaining
|
4
4
|
a copy of this software and associated documentation files (the
|
@@ -17,4 +17,4 @@ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
|
17
17
|
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
18
|
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
19
|
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
-
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.rdoc
CHANGED
@@ -1,23 +1,70 @@
|
|
1
|
-
|
1
|
+
= RSpactor
|
2
2
|
|
3
|
-
|
3
|
+
RSpactor allows to automatically & intelligently launch your specs when your files are modified.
|
4
|
+
Version 0.7.x is a complete rewrite, RubyCocoa is no more needed, FSEvents are supported from scratch.
|
4
5
|
|
5
|
-
|
6
|
+
== Features
|
6
7
|
|
7
|
-
|
8
|
+
* FSEvent support (without RubyCocoa!)
|
9
|
+
* RSpec 2.0 support (from beta.14)
|
10
|
+
* Bundler support
|
11
|
+
* Super fast change detection
|
12
|
+
* Automatic _spec.rb files detection (even new file created, unlike watchr)
|
13
|
+
* Growl notification (please install {growlnotify}[http://growl.info/documentation/growlnotify.php])
|
8
14
|
|
9
|
-
|
15
|
+
== Install
|
10
16
|
|
11
|
-
|
17
|
+
At the moment, only Mac OS X (10.5+) is supported. Tested on ruby 1.8.7 & 1.9.2dev.
|
12
18
|
|
13
|
-
|
14
|
-
rspactor
|
19
|
+
Install the gem:
|
15
20
|
|
16
|
-
|
21
|
+
gem install rspactor
|
17
22
|
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
23
|
+
If you are using Bundler, please add it to your Gemfile (inside test group):
|
24
|
+
|
25
|
+
gem 'rspactor', '>= 0.7.beta.1'
|
26
|
+
|
27
|
+
== Usage
|
28
|
+
|
29
|
+
Just launch RSpactor inside your ruby/rails project with:
|
30
|
+
|
31
|
+
rspactor
|
32
|
+
|
33
|
+
Options list is available with:
|
34
|
+
|
35
|
+
rspactor -h
|
36
|
+
|
37
|
+
Signal handlers are now used to interact with RSpactor:
|
38
|
+
|
39
|
+
* Ctrl-C => Quit RSpactor or quick abort running spec(s)
|
40
|
+
* Ctrl-/ => Running all specs
|
41
|
+
|
42
|
+
== TODO
|
43
|
+
|
44
|
+
* Specific files (spec_helper, factories, fixtures...) inspections
|
45
|
+
* RSpec 1.3 support
|
46
|
+
* Inotify support (linux)
|
47
|
+
* Spork support (when {this issue}[http://github.com/timcharper/spork/issues#issue/37] will be resolved)
|
48
|
+
* Cucumber support (if really needed? {Steak}[http://github.com/cavalle/steak] works fine.)
|
49
|
+
* Other ideas?
|
50
|
+
|
51
|
+
== Development
|
52
|
+
|
53
|
+
* Source hosted at {GitHub}[http://github.com/thibaudgg/rspactor].
|
54
|
+
* Report issues/Questions/Feature requests on {GitHub Issues}[http://github.com/thibaudgg/rspactor/issues]
|
55
|
+
|
56
|
+
Pull requests are very welcome! Make sure your patches are well tested. Please create a topic branch for every separate change
|
57
|
+
you make.
|
58
|
+
|
59
|
+
== Authors
|
60
|
+
|
61
|
+
From version 0.7.x was completely rewritten by {Thibaud Guillaume-Gentil}[http://github.com/thibaudgg].
|
62
|
+
|
63
|
+
Older versions authors are:
|
64
|
+
|
65
|
+
* {Mislav Marohnić}[http://github.com/mislav]
|
66
|
+
* {Andreas Wolff}[http://github.com/phunkwork]
|
67
|
+
* {Pelle Braendgaard}[http://github.com/pelle]
|
68
|
+
* {Thibaud Guillaume-Gentil}[http://github.com/thibaudgg]
|
69
|
+
|
70
|
+
Thanks to {Giovanni Cangiani}[http://github.com/multiscan] for the IO.open/FSEvent trick & {Rémy Coutable}[http://github.com/rymai] for beta testing.
|
data/bin/rspactor
CHANGED
@@ -1,11 +1,12 @@
|
|
1
1
|
#!/usr/bin/env ruby
|
2
|
-
require '
|
2
|
+
require 'trollop'
|
3
|
+
require 'rspactor'
|
4
|
+
require 'rspactor/version'
|
3
5
|
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
:
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
})
|
6
|
+
options = Trollop::options do
|
7
|
+
version RSpactor::VERSION
|
8
|
+
|
9
|
+
opt :clear, "Clear the console beetween each spec(s) run"
|
10
|
+
end
|
11
|
+
|
12
|
+
RSpactor.start(options)
|
@@ -0,0 +1,28 @@
|
|
1
|
+
# Workaround to make Rubygems believe it builds a native gem
|
2
|
+
|
3
|
+
def emulate_extension_install(extension_name)
|
4
|
+
File.open('Makefile', 'w') { |f| f.write "all:\n\ninstall:\n\n" }
|
5
|
+
File.open('make', 'w') do |f|
|
6
|
+
f.write '#!/bin/sh'
|
7
|
+
f.chmod f.stat.mode | 0111
|
8
|
+
end
|
9
|
+
File.open(extension_name + '.so', 'w') {}
|
10
|
+
File.open(extension_name + '.dll', 'w') {}
|
11
|
+
File.open('nmake.bat', 'w') { |f| }
|
12
|
+
end
|
13
|
+
|
14
|
+
emulate_extension_install('fsevent')
|
15
|
+
|
16
|
+
# Compile the actual fsevent_watch binary
|
17
|
+
|
18
|
+
raise "Only Darwin (Mac OS X) systems are supported for the moment" unless `uname -s`.chomp == 'Darwin'
|
19
|
+
|
20
|
+
GEM_ROOT = File.expand_path(File.join('..', '..'))
|
21
|
+
DARWIN_VERSION = `uname -r`.to_i
|
22
|
+
SDK_VERSION = { 9 => '10.5', 10 => '10.6', 11 => '10.7' }[DARWIN_VERSION]
|
23
|
+
|
24
|
+
raise "Darwin #{DARWIN_VERSION} is not supported" unless SDK_VERSION
|
25
|
+
|
26
|
+
`CFLAGS='-isysroot /Developer/SDKs/MacOSX#{SDK_VERSION}.sdk -mmacosx-version-min=#{SDK_VERSION}' /usr/bin/gcc -framework CoreServices -o "#{GEM_ROOT}/bin/fsevent_watch" fsevent_watch.c`
|
27
|
+
|
28
|
+
raise "Compilation of fsevent_watch failed (see README)" unless File.executable?("#{GEM_ROOT}/bin/fsevent_watch")
|
@@ -0,0 +1,44 @@
|
|
1
|
+
#include <CoreServices/CoreServices.h>
|
2
|
+
|
3
|
+
void callback(ConstFSEventStreamRef streamRef,
|
4
|
+
void *clientCallBackInfo,
|
5
|
+
size_t numEvents,
|
6
|
+
void *eventPaths,
|
7
|
+
const FSEventStreamEventFlags eventFlags[],
|
8
|
+
const FSEventStreamEventId eventIds[]
|
9
|
+
) {
|
10
|
+
// Print modified dirs
|
11
|
+
int i;
|
12
|
+
char **paths = eventPaths;
|
13
|
+
for (i = 0; i < numEvents; i++) {
|
14
|
+
printf(paths[i]);
|
15
|
+
printf(" ");
|
16
|
+
}
|
17
|
+
printf("\n");
|
18
|
+
fflush(stdout);
|
19
|
+
}
|
20
|
+
|
21
|
+
int main (int argc, const char * argv[]) {
|
22
|
+
// Create event stream
|
23
|
+
CFStringRef pathToWatch = CFStringCreateWithCString(kCFAllocatorDefault, argv[1], kCFStringEncodingUTF8);
|
24
|
+
CFArrayRef pathsToWatch = CFArrayCreate(NULL, (const void **)&pathToWatch, 1, NULL);
|
25
|
+
void *callbackInfo = NULL;
|
26
|
+
FSEventStreamRef stream;
|
27
|
+
CFAbsoluteTime latency = 0.5;
|
28
|
+
stream = FSEventStreamCreate(
|
29
|
+
kCFAllocatorDefault,
|
30
|
+
callback,
|
31
|
+
callbackInfo,
|
32
|
+
pathsToWatch,
|
33
|
+
kFSEventStreamEventIdSinceNow,
|
34
|
+
latency,
|
35
|
+
kFSEventStreamCreateFlagNone
|
36
|
+
);
|
37
|
+
|
38
|
+
// Add stream to run loop
|
39
|
+
FSEventStreamScheduleWithRunLoop(stream, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode);
|
40
|
+
FSEventStreamStart(stream);
|
41
|
+
CFRunLoopRun();
|
42
|
+
|
43
|
+
return 2;
|
44
|
+
}
|
data/images/failed.png
CHANGED
File without changes
|
data/images/success.png
CHANGED
File without changes
|
@@ -0,0 +1,35 @@
|
|
1
|
+
require 'rspec/core/formatters/base_formatter'
|
2
|
+
require 'growl'
|
3
|
+
|
4
|
+
class GrowlFormatter < RSpec::Core::Formatters::ProgressFormatter
|
5
|
+
|
6
|
+
def dump_summary
|
7
|
+
super
|
8
|
+
failure_count = failed_examples.size
|
9
|
+
pending_count = pending_examples.size
|
10
|
+
|
11
|
+
icon = if failure_count > 0
|
12
|
+
'failed'
|
13
|
+
elsif pending_count > 0
|
14
|
+
'pending'
|
15
|
+
else
|
16
|
+
'success'
|
17
|
+
end
|
18
|
+
|
19
|
+
message = "#{@example_count} examples, #{failure_count} failures"
|
20
|
+
if pending_count > 0
|
21
|
+
message << " (#{pending_count} pending)"
|
22
|
+
end
|
23
|
+
message << "\nin #{format_seconds(duration)} seconds"
|
24
|
+
|
25
|
+
Growl.notify message, :title => "RSpec results", :icon => image_path(icon)
|
26
|
+
end
|
27
|
+
|
28
|
+
private
|
29
|
+
|
30
|
+
# failed | pending | success
|
31
|
+
def image_path(icon)
|
32
|
+
File.expand_path(File.dirname(__FILE__) + "/../../images/#{icon}.png")
|
33
|
+
end
|
34
|
+
|
35
|
+
end
|
data/lib/rspactor/inspector.rb
CHANGED
@@ -1,118 +1,52 @@
|
|
1
|
-
require 'rspactor'
|
2
|
-
|
3
1
|
module RSpactor
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
EXTENSIONS = %w(rb erb builder haml rhtml rxml yml conf opts feature)
|
8
|
-
|
9
|
-
attr_reader :runner, :root
|
10
|
-
|
11
|
-
def initialize(runner)
|
12
|
-
@runner = runner
|
13
|
-
@root = runner.dir
|
14
|
-
end
|
15
|
-
|
16
|
-
def determine_files(file)
|
17
|
-
candidates = translate(file)
|
18
|
-
cucumberable = candidates.delete('cucumber')
|
19
|
-
runner.spork.reload if candidates.delete('spork') && runner.spork?
|
2
|
+
module Inspector
|
3
|
+
class << self
|
4
|
+
attr_reader :spec_paths
|
20
5
|
|
21
|
-
|
22
|
-
|
6
|
+
def determine_spec_paths(files)
|
7
|
+
@spec_paths, @spec_files = [], nil
|
8
|
+
files.each { |file| translate(file) }
|
9
|
+
compact_spec_paths!
|
23
10
|
end
|
24
|
-
files = candidates.select { |candidate| File.exists? candidate }
|
25
11
|
|
26
|
-
|
27
|
-
|
12
|
+
def spec_paths?
|
13
|
+
@spec_paths.size > 0
|
28
14
|
end
|
29
15
|
|
30
|
-
|
31
|
-
files
|
32
|
-
end
|
33
|
-
|
34
|
-
# mappings for Rails are inspired by autotest mappings in rspec-rails
|
35
|
-
def translate(file)
|
36
|
-
file = file.sub(%r:^#{Regexp.escape(root)}/:, '')
|
37
|
-
candidates = []
|
16
|
+
private
|
38
17
|
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
candidates << 'cucumber'
|
43
|
-
candidates << 'spork' if file =~ /^features\/support\//
|
44
|
-
else
|
45
|
-
spec_file = append_spec_file_extension(file)
|
46
|
-
|
47
|
-
case file
|
48
|
-
when %r:^app/:
|
49
|
-
if file =~ %r:^app/controllers/application(_controller)?.rb$:
|
50
|
-
candidates << 'controllers'
|
51
|
-
elsif file == 'app/helpers/application_helper.rb'
|
52
|
-
candidates << 'helpers'
|
53
|
-
candidates << 'views' if runner.options[:view]
|
54
|
-
elsif file.include?("app/views/")
|
55
|
-
if runner.options[:view]
|
56
|
-
candidates << spec_file.sub('app/', '')
|
57
|
-
if file =~ %r:^app/(views/.+\.[a-z]+)\.[a-z]+$:
|
58
|
-
candidates << append_spec_file_extension($1)
|
59
|
-
end
|
60
|
-
end
|
61
|
-
else
|
62
|
-
candidates << spec_file.sub('app/', '')
|
63
|
-
if file =~ %r:app/helpers/(\w+)_helper.rb:
|
64
|
-
candidates << "views/#{$1}"
|
65
|
-
elsif file =~ /_observer.rb$/
|
66
|
-
candidates << candidates.last.sub('_observer', '')
|
67
|
-
end
|
68
|
-
end
|
69
|
-
when %r:^lib/:
|
70
|
-
candidates << spec_file
|
71
|
-
# lib/foo/bar_spec.rb -> lib/bar_spec.rb
|
72
|
-
candidates << candidates.last.sub($&, '')
|
73
|
-
# lib/bar_spec.rb -> bar_spec.rb
|
74
|
-
candidates << candidates.last.sub(%r:\w+/:, '') if candidates.last.index('/')
|
75
|
-
when 'config/routes.rb'
|
76
|
-
candidates << 'controllers' << 'helpers' << 'routing'
|
77
|
-
candidates << 'views' if runner.options[:view]
|
78
|
-
when 'config/database.yml', 'db/schema.rb', 'spec/factories.rb'
|
79
|
-
candidates << 'models'
|
80
|
-
when 'config/boot.rb', 'config/environment.rb', %r:^config/environments/:, %r:^config/initializers/:, %r:^vendor/:, 'spec/spec_helper.rb'
|
81
|
-
candidates << 'spork'
|
82
|
-
candidates << 'spec'
|
83
|
-
when %r:^config/:
|
84
|
-
# nothing
|
85
|
-
when %r:^(spec/(spec_helper|shared/.*)|config/(boot|environment(s/test)?))\.rb$:, 'spec/spec.opts', 'spec/fakewebs.rb'
|
86
|
-
candidates << 'spec'
|
18
|
+
def translate(file)
|
19
|
+
if spec_file?(file)
|
20
|
+
@spec_paths << file
|
87
21
|
else
|
88
|
-
|
22
|
+
spec_file = append_spec_file_extension(file)
|
23
|
+
case file
|
24
|
+
when %r:^lib/:
|
25
|
+
@spec_paths << @spec_files.delete(spec_file.gsub(/^lib/, 'spec'))
|
26
|
+
@spec_paths << @spec_files.delete(spec_file.gsub(/^lib/, 'spec/lib'))
|
27
|
+
when %r:^app/:
|
28
|
+
@spec_paths << @spec_files.delete(spec_file.gsub(/^app/, 'spec'))
|
29
|
+
end
|
89
30
|
end
|
90
31
|
end
|
91
32
|
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
elsif candidate.index('spec') == 0
|
96
|
-
File.join(root, candidate)
|
97
|
-
else
|
98
|
-
File.join(root, 'spec', candidate)
|
99
|
-
end
|
33
|
+
def compact_spec_paths!
|
34
|
+
@spec_paths.uniq!
|
35
|
+
@spec_paths.compact!
|
100
36
|
end
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
if File.extname(file) == ".rb"
|
105
|
-
file.sub(/.rb$/, "_spec.rb")
|
106
|
-
else
|
107
|
-
file + "_spec.rb"
|
37
|
+
|
38
|
+
def spec_file?(file)
|
39
|
+
spec_files.include?(file)
|
108
40
|
end
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
41
|
+
|
42
|
+
def spec_files
|
43
|
+
@spec_files ||= Dir.glob("spec/**/*_spec.rb")
|
44
|
+
end
|
45
|
+
|
46
|
+
def append_spec_file_extension(file)
|
47
|
+
file.sub(/(\..*)$/, "_spec.rb")
|
48
|
+
end
|
49
|
+
|
116
50
|
end
|
117
51
|
end
|
118
52
|
end
|
data/lib/rspactor/interactor.rb
CHANGED
@@ -1,101 +1,33 @@
|
|
1
|
-
require 'timeout'
|
2
|
-
|
3
1
|
module RSpactor
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
def self.ticker_msg(msg, seconds_to_wait = 3, &block)
|
14
|
-
$stdout.print msg
|
15
|
-
if block
|
16
|
-
@yielding_ticker = true
|
17
|
-
Thread.new do
|
18
|
-
loop do
|
19
|
-
$stdout.print('.') if @yielding_ticker == true
|
20
|
-
$stdout.flush
|
21
|
-
sleep 1.0
|
22
|
-
end
|
23
|
-
end
|
24
|
-
|
25
|
-
yield
|
26
|
-
@yielding_ticker = false
|
27
|
-
else
|
28
|
-
seconds_to_wait.times do
|
29
|
-
$stdout.print('.')
|
30
|
-
$stdout.flush
|
31
|
-
sleep 1
|
32
|
-
end
|
33
|
-
end
|
34
|
-
$stdout.puts "\n"
|
35
|
-
end
|
36
|
-
|
37
|
-
def wait_for_enter_key(msg, seconds_to_wait, clear = runner.options[:clear])
|
38
|
-
begin
|
39
|
-
Timeout::timeout(seconds_to_wait) do
|
40
|
-
system("clear;") if clear
|
41
|
-
ticker(:start => true, :msg => msg)
|
42
|
-
$stdin.gets
|
43
|
-
return true
|
2
|
+
module Interactor
|
3
|
+
class << self
|
4
|
+
|
5
|
+
def init_signal_traps
|
6
|
+
# Ctrl-/
|
7
|
+
Signal.trap('QUIT') do
|
8
|
+
RSpactor.listener.stop
|
9
|
+
RSpactor.runner.start(:all => true)
|
10
|
+
RSpactor.listener.start
|
44
11
|
end
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
@main_thread = Thread.current
|
54
|
-
Thread.new do
|
55
|
-
loop do
|
56
|
-
sleep 0.5
|
57
|
-
if entry = $stdin.gets
|
58
|
-
case entry
|
59
|
-
when "c\n" # Cucumber: current tagged feature
|
60
|
-
runner.run_cucumber_command
|
61
|
-
when "ca\n" # Cucumber All: ~pending tagged feature
|
62
|
-
runner.run_cucumber_command('~@wip,~@pending')
|
63
|
-
when "r\n"
|
64
|
-
runner.spork.reload if runner.spork
|
65
|
-
else
|
66
|
-
if wait_for_enter_key("** Running all specs... Hit <enter> again to exit RSpactor", 1)
|
67
|
-
@main_thread.exit
|
68
|
-
exit
|
69
|
-
end
|
70
|
-
runner.run_all_specs
|
71
|
-
end
|
12
|
+
# Ctrl-C
|
13
|
+
Signal.trap('INT') do
|
14
|
+
if RSpactor.runner.run?
|
15
|
+
UI.info "RSpec run canceled", :reset => true, :clear => RSpactor.options[:clear]
|
16
|
+
RSpactor.runner.stop
|
17
|
+
else
|
18
|
+
UI.info "Bye bye...", :reset => true
|
19
|
+
abort("\n")
|
72
20
|
end
|
73
21
|
end
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
if opts[:stop]
|
81
|
-
$stdout.puts "\n"
|
82
|
-
@pointer_running = false
|
83
|
-
elsif opts[:start]
|
84
|
-
@pointer_running = true
|
85
|
-
write(opts[:msg]) if opts[:msg]
|
86
|
-
else
|
87
|
-
Thread.new do
|
88
|
-
loop do
|
89
|
-
write('.') if @pointer_running == true
|
90
|
-
sleep 1.0
|
91
|
-
end
|
22
|
+
# Ctrl-Z
|
23
|
+
Signal.trap('TSTP') do
|
24
|
+
# UI.info "Reloading Spork...", :reset => true
|
25
|
+
RSpactor.listener.stop
|
26
|
+
# # Reload Spork
|
27
|
+
RSpactor.listener.start
|
92
28
|
end
|
93
29
|
end
|
94
|
-
|
95
|
-
|
96
|
-
def write(msg)
|
97
|
-
$stdout.print(msg)
|
98
|
-
$stdout.flush
|
30
|
+
|
99
31
|
end
|
100
32
|
end
|
101
33
|
end
|
data/lib/rspactor/listener.rb
CHANGED
@@ -1,88 +1,59 @@
|
|
1
|
-
require 'osx/foundation'
|
2
|
-
OSX.require_framework '/System/Library/Frameworks/CoreServices.framework/Frameworks/CarbonCore.framework'
|
3
|
-
|
4
1
|
module RSpactor
|
5
|
-
# based on http://rails.aizatto.com/2007/11/28/taming-the-autotest-beast-with-fsevents/
|
6
2
|
class Listener
|
7
|
-
|
3
|
+
EXTENSIONS = %w[rb erb builder haml yml]
|
8
4
|
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
@callback = lambda do |stream, ctx, num_events, paths, marks, event_ids|
|
14
|
-
changed_files = extract_changed_files_from_paths(split_paths(paths, num_events))
|
15
|
-
timestamp_checked
|
16
|
-
yield changed_files unless changed_files.empty?
|
17
|
-
end
|
5
|
+
attr_reader :last_event, :callback, :pipe
|
6
|
+
|
7
|
+
def initialize
|
8
|
+
update_last_event
|
18
9
|
end
|
19
10
|
|
20
|
-
def
|
21
|
-
|
22
|
-
stream = OSX::FSEventStreamCreate(OSX::KCFAllocatorDefault, callback, nil, dirs, OSX::KFSEventStreamEventIdSinceNow, 0.5, 0)
|
23
|
-
unless stream
|
24
|
-
$stderr.puts "Failed to create stream"
|
25
|
-
exit(1)
|
26
|
-
end
|
27
|
-
|
28
|
-
OSX::FSEventStreamScheduleWithRunLoop(stream, OSX::CFRunLoopGetCurrent(), OSX::KCFRunLoopDefaultMode)
|
29
|
-
unless OSX::FSEventStreamStart(stream)
|
30
|
-
$stderr.puts "Failed to start stream"
|
31
|
-
exit(1)
|
32
|
-
end
|
33
|
-
|
34
|
-
begin
|
35
|
-
OSX::CFRunLoopRun()
|
36
|
-
rescue Interrupt
|
37
|
-
OSX::FSEventStreamStop(stream)
|
38
|
-
OSX::FSEventStreamInvalidate(stream)
|
39
|
-
OSX::FSEventStreamRelease(stream)
|
40
|
-
end
|
11
|
+
def watch(&block)
|
12
|
+
@callback = block
|
41
13
|
end
|
42
14
|
|
43
|
-
def
|
44
|
-
@
|
15
|
+
def start
|
16
|
+
@pipe = IO.popen("#{bin_path}/fsevent_watch .")
|
17
|
+
watch_change
|
45
18
|
end
|
46
19
|
|
47
|
-
def
|
48
|
-
|
49
|
-
rpaths = []
|
50
|
-
num_events.times { |i| rpaths << paths[i] }
|
51
|
-
rpaths
|
20
|
+
def stop
|
21
|
+
Process.kill("HUP", pipe.pid) if pipe
|
52
22
|
end
|
53
23
|
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
24
|
+
private
|
25
|
+
|
26
|
+
def watch_change
|
27
|
+
while !pipe.eof?
|
28
|
+
if line = pipe.readline
|
29
|
+
modified_dirs = line.split(" ")
|
30
|
+
files = modified_files(modified_dirs)
|
31
|
+
update_last_event
|
32
|
+
callback.call(files)
|
61
33
|
end
|
62
34
|
end
|
63
|
-
changed_files
|
64
35
|
end
|
65
36
|
|
66
|
-
def
|
67
|
-
|
68
|
-
|
69
|
-
false
|
37
|
+
def modified_files(dirs)
|
38
|
+
files = potentially_modified_files(dirs).select { |file| recent_file?(file) }
|
39
|
+
files.map! { |file| file.gsub("#{Dir.pwd}/", '') }
|
70
40
|
end
|
71
41
|
|
72
|
-
def
|
73
|
-
|
42
|
+
def potentially_modified_files(dirs)
|
43
|
+
Dir.glob(dirs.map { |dir| "#{dir}*.{#{EXTENSIONS.join(',')}}" })
|
74
44
|
end
|
75
45
|
|
76
|
-
def
|
77
|
-
File.
|
46
|
+
def recent_file?(file)
|
47
|
+
File.mtime(file) >= last_event
|
78
48
|
end
|
79
49
|
|
80
|
-
def
|
81
|
-
|
50
|
+
def update_last_event
|
51
|
+
@last_event = Time.now
|
82
52
|
end
|
83
53
|
|
84
|
-
def
|
85
|
-
|
54
|
+
def bin_path
|
55
|
+
File.expand_path(File.join(File.dirname(__FILE__), '..', '..', 'bin'))
|
86
56
|
end
|
57
|
+
|
87
58
|
end
|
88
59
|
end
|