kicker 2.0.0
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/.gitignore +10 -0
- data/.kick +20 -0
- data/LICENSE +54 -0
- data/README.rdoc +141 -0
- data/Rakefile +37 -0
- data/TODO.rdoc +1 -0
- data/VERSION.yml +4 -0
- data/bin/kicker +5 -0
- data/html/images/kikker.jpg +0 -0
- data/kicker.gemspec +95 -0
- data/lib/kicker.rb +135 -0
- data/lib/kicker/callback_chain.rb +77 -0
- data/lib/kicker/core_ext.rb +30 -0
- data/lib/kicker/growl.rb +24 -0
- data/lib/kicker/options.rb +49 -0
- data/lib/kicker/recipes/could_not_handle_file.rb +5 -0
- data/lib/kicker/recipes/dot_kick.rb +35 -0
- data/lib/kicker/recipes/execute_cli_command.rb +6 -0
- data/lib/kicker/recipes/ignore.rb +39 -0
- data/lib/kicker/recipes/jstest.rb +8 -0
- data/lib/kicker/recipes/rails.rb +54 -0
- data/lib/kicker/utils.rb +71 -0
- data/lib/kicker/validate.rb +24 -0
- data/test/callback_chain_test.rb +150 -0
- data/test/core_ext_test.rb +28 -0
- data/test/filesystem_change_test.rb +100 -0
- data/test/fixtures/a_file_thats_reloaded.rb +2 -0
- data/test/initialization_test.rb +165 -0
- data/test/options_test.rb +30 -0
- data/test/recipes/could_not_handle_file_test.rb +11 -0
- data/test/recipes/dot_kick_test.rb +26 -0
- data/test/recipes/execute_cli_command_test.rb +32 -0
- data/test/recipes/ignore_test.rb +29 -0
- data/test/recipes/jstest_test.rb +31 -0
- data/test/recipes/rails_test.rb +73 -0
- data/test/test_helper.rb +6 -0
- data/test/utils_test.rb +123 -0
- data/vendor/growlnotifier/growl.rb +170 -0
- data/vendor/growlnotifier/growl_helpers.rb +25 -0
- data/vendor/rucola/fsevents.rb +136 -0
- metadata +110 -0
data/.gitignore
ADDED
data/.kick
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
require 'ignore'
|
2
|
+
|
3
|
+
process do |files|
|
4
|
+
test_files = files.take_and_map do |file|
|
5
|
+
case file
|
6
|
+
when %r{^test/.+_test\.rb$}
|
7
|
+
file
|
8
|
+
when %r{^lib/kicker(\.rb|/validate\.rb|/growl\.rb)$}
|
9
|
+
["test/initialization_test.rb", ("test/filesystem_change_test.rb" if $1 == '.rb')]
|
10
|
+
when %r{^lib/kicker/(.+)\.rb$}
|
11
|
+
"test/#{$1}_test.rb"
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
run_ruby_tests test_files
|
16
|
+
end
|
17
|
+
|
18
|
+
process do |files|
|
19
|
+
execute("rake docs:generate && open -a Safari html/index.html") if files.delete("README.rdoc")
|
20
|
+
end
|
data/LICENSE
ADDED
@@ -0,0 +1,54 @@
|
|
1
|
+
Kicker:
|
2
|
+
|
3
|
+
Copyright (c) 2009 Eloy Duran <eloy.de.enige@gmail.com>
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
23
|
+
|
24
|
+
======================================================================
|
25
|
+
|
26
|
+
Rucola: http://github.com/alloy/rucola/tree/master
|
27
|
+
|
28
|
+
Copyright (c) 2008 Eloy Duran <eloy.de.enige@gmail.com>
|
29
|
+
|
30
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
31
|
+
a copy of this software and associated documentation files (the
|
32
|
+
"Software"), to deal in the Software without restriction, including
|
33
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
34
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
35
|
+
permit persons to whom the Software is furnished to do so, subject to
|
36
|
+
the following conditions:
|
37
|
+
|
38
|
+
The above copyright notice and this permission notice shall be
|
39
|
+
included in all copies or substantial portions of the Software.
|
40
|
+
|
41
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
42
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
43
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
44
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
45
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
46
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
47
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
48
|
+
|
49
|
+
======================================================================
|
50
|
+
|
51
|
+
growlnotifier: http://github.com/psychs/growlnotifier/tree/master
|
52
|
+
|
53
|
+
Copyright (c) 2007-2008 Satoshi Nakagawa <psychs@limechat.net>, Eloy Duran <e.duran@superalloy.nl>
|
54
|
+
You can redistribute it and/or modify it under the same terms as Ruby.
|
data/README.rdoc
ADDED
@@ -0,0 +1,141 @@
|
|
1
|
+
= Kicker
|
2
|
+
|
3
|
+
A lean, agnostic, flexible file-change watcher, using OS X FSEvents.
|
4
|
+
|
5
|
+
http://github.com/alloy/kicker/raw/master/html/images/kikker.jpg
|
6
|
+
|
7
|
+
Meet king kikker, kicking stuff in your computers is his dream come true!
|
8
|
+
|
9
|
+
<i>Drawing by Manfred Stienstra. The character is purely fictional, so if you
|
10
|
+
feel offended; live with it.</i>
|
11
|
+
|
12
|
+
== Installation
|
13
|
+
|
14
|
+
$ sudo gem install alloy-kicker -s http://gems.github.com
|
15
|
+
|
16
|
+
== The short version
|
17
|
+
|
18
|
+
Usage: kicker [options] [paths to watch]
|
19
|
+
-e, --execute [COMMAND] The command to execute.
|
20
|
+
--[no-]growl Whether or not to use Growl. Default is to use growl.
|
21
|
+
--growl-command [COMMAND] The command to execute when the Growl succeeded message is clicked.
|
22
|
+
-l, --latency [FLOAT] The time to collect file change events before acting on them. Defaults to 1.5 sec.
|
23
|
+
-r, --recipe [NAME] A named recipe to load.
|
24
|
+
|
25
|
+
Available recipes:
|
26
|
+
- ignore
|
27
|
+
- jstest
|
28
|
+
- rails
|
29
|
+
|
30
|
+
== The long version
|
31
|
+
|
32
|
+
=== Execute a shell command
|
33
|
+
|
34
|
+
Show all files, whenever a change occurs in the current work directory:
|
35
|
+
|
36
|
+
$ kicker -e "ls -l" .
|
37
|
+
|
38
|
+
Show all files, whenever a change occurs to a specific file:
|
39
|
+
|
40
|
+
$ kicker -e "ls -l" foo.txt
|
41
|
+
|
42
|
+
Or use it as a ghetto-autotest, running tests whenever files change:
|
43
|
+
|
44
|
+
$ kicker -e "ruby test/test_case.rb" test/test_case.rb lib/file.rb
|
45
|
+
|
46
|
+
Et cetera.
|
47
|
+
|
48
|
+
=== Using recipes
|
49
|
+
|
50
|
+
A recipe is a predefined handler. You can use as many as you like, by
|
51
|
+
specifying them with the <tt>--recipe</tt> (<tt>-r</tt>) option.
|
52
|
+
|
53
|
+
For instance, when in the root of a typical Ruby on Rails application, using
|
54
|
+
the <tt>rails</tt> recipe will map models, concerns, controllers, helpers, and
|
55
|
+
views to their respective test files. These will then all be ran with Ruby.
|
56
|
+
|
57
|
+
A few recipes come shipped with Kicker:
|
58
|
+
* Ruby on Rails, as aforementioned.
|
59
|
+
* JavaScript tests, needs
|
60
|
+
HeadlessSquirrel[http://github.com/Fingertips/Headless-squirrel] to run.
|
61
|
+
* Ignore, ignores logs, tmp, and svn and git files.
|
62
|
+
|
63
|
+
Add your own shared recipes to <tt>~/.kick</tt>.
|
64
|
+
|
65
|
+
=== Project specific handlers
|
66
|
+
|
67
|
+
Most of the time, you’ll want to create handlers specific to the project at
|
68
|
+
hand. This can be done by adding your handlers to a <tt>.kick</tt> file and
|
69
|
+
running Kicker from the directory containing it.
|
70
|
+
|
71
|
+
This file is reloaded once saved. No need to stop Kicker.
|
72
|
+
|
73
|
+
== Writing handlers
|
74
|
+
|
75
|
+
Whenever file-change events occur, Kicker will go through a chain of handlers
|
76
|
+
until that the files list is empty, or the end of the chain is reached.
|
77
|
+
|
78
|
+
Handlers are objects that respond to <tt>#call</tt>. These are typically Proc
|
79
|
+
objects. (If you know Rack, you’re familiar with this concept.) Every handler
|
80
|
+
gets passed a list of changed files and can decide whether or not to act on
|
81
|
+
them. Normally when handling a file, you should remove it from the files list,
|
82
|
+
unless you want to let the file fall through to another handler. In the same
|
83
|
+
way, one can add files to handler to the files list.
|
84
|
+
|
85
|
+
==== Time for a simple example
|
86
|
+
|
87
|
+
process do |files|
|
88
|
+
execute("rake docs:generate && open -a Safari html/index.html") if files.delete("README.rdoc")
|
89
|
+
end
|
90
|
+
|
91
|
+
A handler is defined by passing a block to <tt>process</tt>. Which is one of
|
92
|
+
three possible callback chains to add your handlers to, the others being:
|
93
|
+
<tt>pre_process</tt> and <tt>post_process</tt>. See Kernel for more info.
|
94
|
+
|
95
|
+
Then <tt>README.rdoc</tt> is deleted from the files array. If it did exist in
|
96
|
+
the array and was deleted, a shell command is executed which runs a rake task
|
97
|
+
to generate rdoc and open the docs with Safari.
|
98
|
+
|
99
|
+
==== Something more elaborate.
|
100
|
+
|
101
|
+
Consider a Rails application with a mailer. Since the naming convention of
|
102
|
+
mailer views tend to be fairly application specific, a specific handler has to
|
103
|
+
be added:
|
104
|
+
|
105
|
+
process do |files|
|
106
|
+
test_files = files.take_and_map do |file|
|
107
|
+
if path =~ %r{^app/views/mailer/\w+\.erb$}
|
108
|
+
'test/unit/mailer_test.rb'
|
109
|
+
|
110
|
+
# elsif ... handle more app specific stuff
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
run_ruby_tests test_files
|
115
|
+
end
|
116
|
+
|
117
|
+
The files list is iterated over with the Array#take_and_map method, which both
|
118
|
+
removes and maps the results. This is an easy way to do a common thing in
|
119
|
+
recipes. See Kicker::ArrayExt for details.
|
120
|
+
|
121
|
+
The handler then checks if the file is a mailer view and if so runs the
|
122
|
+
mailers test case. The <tt>run_ruby_tests</tt> runs them with the following
|
123
|
+
command:
|
124
|
+
|
125
|
+
execute "ruby -r #{test_files.join(' -r ')} -e ''" unless test_files.empty?
|
126
|
+
|
127
|
+
See Kernel for more info on the utility methods.
|
128
|
+
|
129
|
+
==== Addendum
|
130
|
+
|
131
|
+
The recipes directory that ships with Kicker and <tt>~/.kick</tt> are both
|
132
|
+
added to the load path, so any recipes can be required. Once they’re required
|
133
|
+
they are added to the callback chains.
|
134
|
+
|
135
|
+
As an example, say you might want to ignore files in <tt>./data</tt>:
|
136
|
+
|
137
|
+
require 'ignore'
|
138
|
+
ignore(/^data\//)
|
139
|
+
|
140
|
+
That’s basically it, just remember that the order of specifying handlers _can_
|
141
|
+
be important in your decision on where to specify handlers.
|
data/Rakefile
ADDED
@@ -0,0 +1,37 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'rake'
|
3
|
+
require 'rake/testtask'
|
4
|
+
require 'rake/rdoctask'
|
5
|
+
|
6
|
+
begin
|
7
|
+
require 'jeweler'
|
8
|
+
Jeweler::Tasks.new do |gem|
|
9
|
+
gem.name = "kicker"
|
10
|
+
gem.summary = %Q{A lean, agnostic, flexible file-change watcher, using OS X FSEvents.}
|
11
|
+
gem.email = "eloy.de.enige@gmail.com"
|
12
|
+
gem.homepage = "http://github.com/alloy/kicker"
|
13
|
+
gem.authors = ["Eloy Duran"]
|
14
|
+
gem.executables << 'kicker'
|
15
|
+
gem.files.concat FileList['vendor/**/*']
|
16
|
+
gem.require_paths = ["lib", "vendor"]
|
17
|
+
gem.has_rdoc = true
|
18
|
+
end
|
19
|
+
rescue LoadError
|
20
|
+
puts "Jeweler not available. Install it with: sudo gem install technicalpickles-jeweler -s http://gems.github.com"
|
21
|
+
end
|
22
|
+
|
23
|
+
Rake::TestTask.new do |t|
|
24
|
+
t.libs << "test"
|
25
|
+
t.test_files = FileList['test/**/*_test.rb']
|
26
|
+
t.options = '-rs'
|
27
|
+
end
|
28
|
+
|
29
|
+
namespace :docs do
|
30
|
+
Rake::RDocTask.new('generate') do |t|
|
31
|
+
t.main = "README.rdoc"
|
32
|
+
t.rdoc_files.include("README.rdoc", "lib/**/*.rb")
|
33
|
+
t.options << '--charset=utf8'
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
task :default => :test
|
data/TODO.rdoc
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
* Add a recipe which implements the basic autotest mapping API.
|
data/VERSION.yml
ADDED
data/bin/kicker
ADDED
Binary file
|
data/kicker.gemspec
ADDED
@@ -0,0 +1,95 @@
|
|
1
|
+
# Generated by jeweler
|
2
|
+
# DO NOT EDIT THIS FILE
|
3
|
+
# Instead, edit Jeweler::Tasks in Rakefile, and run `rake gemspec`
|
4
|
+
# -*- encoding: utf-8 -*-
|
5
|
+
|
6
|
+
Gem::Specification.new do |s|
|
7
|
+
s.name = %q{kicker}
|
8
|
+
s.version = "2.0.0"
|
9
|
+
|
10
|
+
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
11
|
+
s.authors = ["Eloy Duran"]
|
12
|
+
s.date = %q{2009-09-29}
|
13
|
+
s.email = %q{eloy.de.enige@gmail.com}
|
14
|
+
s.executables = ["kicker", "kicker"]
|
15
|
+
s.extra_rdoc_files = [
|
16
|
+
"LICENSE",
|
17
|
+
"README.rdoc"
|
18
|
+
]
|
19
|
+
s.files = [
|
20
|
+
".gitignore",
|
21
|
+
".kick",
|
22
|
+
"LICENSE",
|
23
|
+
"README.rdoc",
|
24
|
+
"Rakefile",
|
25
|
+
"TODO.rdoc",
|
26
|
+
"VERSION.yml",
|
27
|
+
"bin/kicker",
|
28
|
+
"html/images/kikker.jpg",
|
29
|
+
"kicker.gemspec",
|
30
|
+
"lib/kicker.rb",
|
31
|
+
"lib/kicker/callback_chain.rb",
|
32
|
+
"lib/kicker/core_ext.rb",
|
33
|
+
"lib/kicker/growl.rb",
|
34
|
+
"lib/kicker/options.rb",
|
35
|
+
"lib/kicker/recipes/could_not_handle_file.rb",
|
36
|
+
"lib/kicker/recipes/dot_kick.rb",
|
37
|
+
"lib/kicker/recipes/execute_cli_command.rb",
|
38
|
+
"lib/kicker/recipes/ignore.rb",
|
39
|
+
"lib/kicker/recipes/jstest.rb",
|
40
|
+
"lib/kicker/recipes/rails.rb",
|
41
|
+
"lib/kicker/utils.rb",
|
42
|
+
"lib/kicker/validate.rb",
|
43
|
+
"test/callback_chain_test.rb",
|
44
|
+
"test/core_ext_test.rb",
|
45
|
+
"test/filesystem_change_test.rb",
|
46
|
+
"test/fixtures/a_file_thats_reloaded.rb",
|
47
|
+
"test/initialization_test.rb",
|
48
|
+
"test/options_test.rb",
|
49
|
+
"test/recipes/could_not_handle_file_test.rb",
|
50
|
+
"test/recipes/dot_kick_test.rb",
|
51
|
+
"test/recipes/execute_cli_command_test.rb",
|
52
|
+
"test/recipes/ignore_test.rb",
|
53
|
+
"test/recipes/jstest_test.rb",
|
54
|
+
"test/recipes/rails_test.rb",
|
55
|
+
"test/test_helper.rb",
|
56
|
+
"test/utils_test.rb",
|
57
|
+
"vendor/growlnotifier/growl.rb",
|
58
|
+
"vendor/growlnotifier/growl.rb",
|
59
|
+
"vendor/growlnotifier/growl_helpers.rb",
|
60
|
+
"vendor/growlnotifier/growl_helpers.rb",
|
61
|
+
"vendor/rucola/fsevents.rb",
|
62
|
+
"vendor/rucola/fsevents.rb"
|
63
|
+
]
|
64
|
+
s.homepage = %q{http://github.com/alloy/kicker}
|
65
|
+
s.rdoc_options = ["--charset=UTF-8"]
|
66
|
+
s.require_paths = ["lib", "vendor"]
|
67
|
+
s.rubygems_version = %q{1.3.5}
|
68
|
+
s.summary = %q{A simple OS X CLI tool which uses FSEvents to run a given shell command.}
|
69
|
+
s.test_files = [
|
70
|
+
"test/callback_chain_test.rb",
|
71
|
+
"test/core_ext_test.rb",
|
72
|
+
"test/filesystem_change_test.rb",
|
73
|
+
"test/fixtures/a_file_thats_reloaded.rb",
|
74
|
+
"test/initialization_test.rb",
|
75
|
+
"test/options_test.rb",
|
76
|
+
"test/recipes/could_not_handle_file_test.rb",
|
77
|
+
"test/recipes/dot_kick_test.rb",
|
78
|
+
"test/recipes/execute_cli_command_test.rb",
|
79
|
+
"test/recipes/ignore_test.rb",
|
80
|
+
"test/recipes/jstest_test.rb",
|
81
|
+
"test/recipes/rails_test.rb",
|
82
|
+
"test/test_helper.rb",
|
83
|
+
"test/utils_test.rb"
|
84
|
+
]
|
85
|
+
|
86
|
+
if s.respond_to? :specification_version then
|
87
|
+
current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
|
88
|
+
s.specification_version = 3
|
89
|
+
|
90
|
+
if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
|
91
|
+
else
|
92
|
+
end
|
93
|
+
else
|
94
|
+
end
|
95
|
+
end
|
data/lib/kicker.rb
ADDED
@@ -0,0 +1,135 @@
|
|
1
|
+
$:.unshift File.expand_path('../../vendor', __FILE__)
|
2
|
+
require 'rucola/fsevents'
|
3
|
+
|
4
|
+
require 'kicker/callback_chain'
|
5
|
+
require 'kicker/core_ext'
|
6
|
+
require 'kicker/growl'
|
7
|
+
require 'kicker/options'
|
8
|
+
require 'kicker/utils'
|
9
|
+
require 'kicker/validate'
|
10
|
+
|
11
|
+
RECIPES_DIR = File.expand_path('../kicker/recipes', __FILE__)
|
12
|
+
$:.unshift RECIPES_DIR
|
13
|
+
require 'could_not_handle_file'
|
14
|
+
require 'execute_cli_command'
|
15
|
+
|
16
|
+
USER_RECIPES_DIR = File.expand_path('~/.kick')
|
17
|
+
$:.unshift USER_RECIPES_DIR if File.exist?(USER_RECIPES_DIR)
|
18
|
+
|
19
|
+
class Kicker #:nodoc:
|
20
|
+
class << self
|
21
|
+
attr_accessor :latency
|
22
|
+
|
23
|
+
def latency
|
24
|
+
@latency ||= 1
|
25
|
+
end
|
26
|
+
|
27
|
+
def paths
|
28
|
+
@paths ||= %w{ . }
|
29
|
+
end
|
30
|
+
|
31
|
+
def run(argv = ARGV)
|
32
|
+
options = parse_options(argv)
|
33
|
+
load_recipes(options[:recipes]) if options[:recipes]
|
34
|
+
load_dot_kick
|
35
|
+
new(options).start
|
36
|
+
end
|
37
|
+
|
38
|
+
private
|
39
|
+
|
40
|
+
def load_dot_kick
|
41
|
+
if File.exist?('.kick')
|
42
|
+
require 'dot_kick'
|
43
|
+
ReloadDotKick.save_state
|
44
|
+
load '.kick'
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def load_recipes(recipes)
|
49
|
+
recipes.each do |recipe|
|
50
|
+
raise "Recipe `#{recipe}' does not exist." unless recipe_exists?(recipe)
|
51
|
+
require recipe
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
def recipe_exists?(recipe)
|
56
|
+
File.exist?("#{RECIPES_DIR}/#{recipe}.rb") || File.exist?("#{USER_RECIPES_DIR}/#{recipe}.rb")
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
attr_reader :latency, :paths, :last_event_processed_at
|
61
|
+
|
62
|
+
def initialize(options)
|
63
|
+
@paths = (options[:paths] ? options[:paths] : Kicker.paths).map { |path| File.expand_path(path) }
|
64
|
+
@latency = options[:latency] || self.class.latency
|
65
|
+
|
66
|
+
self.class.use_growl = options[:growl]
|
67
|
+
self.class.growl_command = options[:growl_command]
|
68
|
+
|
69
|
+
finished_processing!
|
70
|
+
end
|
71
|
+
|
72
|
+
def start
|
73
|
+
validate_options!
|
74
|
+
|
75
|
+
log "Watching for changes on: #{@paths.join(', ')}"
|
76
|
+
log ''
|
77
|
+
|
78
|
+
run_watch_dog!
|
79
|
+
start_growl! if self.class.use_growl
|
80
|
+
|
81
|
+
OSX.CFRunLoopRun
|
82
|
+
end
|
83
|
+
|
84
|
+
private
|
85
|
+
|
86
|
+
def run_watch_dog!
|
87
|
+
dirs = @paths.map { |path| File.directory?(path) ? path : File.dirname(path) }
|
88
|
+
watch_dog = Rucola::FSEvents.start_watching(dirs, :latency => @latency) { |events| process(events) }
|
89
|
+
|
90
|
+
trap('INT') do
|
91
|
+
log "Exiting…"
|
92
|
+
watch_dog.stop
|
93
|
+
exit
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
def finished_processing!
|
98
|
+
@last_event_processed_at = Time.now
|
99
|
+
end
|
100
|
+
|
101
|
+
def process(events)
|
102
|
+
unless (files = changed_files(events)).empty?
|
103
|
+
full_chain.call(files)
|
104
|
+
finished_processing!
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
def changed_files(events)
|
109
|
+
make_paths_relative(events.map do |event|
|
110
|
+
files_in_directory(event.path).select { |file| file_changed_since_last_event? file }
|
111
|
+
end.flatten.uniq.sort)
|
112
|
+
end
|
113
|
+
|
114
|
+
def files_in_directory(dir)
|
115
|
+
Dir.entries(dir)[2..-1].map { |f| File.join(dir, f) }
|
116
|
+
end
|
117
|
+
|
118
|
+
def file_changed_since_last_event?(file)
|
119
|
+
File.mtime(file) > @last_event_processed_at
|
120
|
+
rescue Errno::ENOENT
|
121
|
+
false
|
122
|
+
end
|
123
|
+
|
124
|
+
def make_paths_relative(files)
|
125
|
+
return files if files.empty?
|
126
|
+
wd = Dir.pwd
|
127
|
+
files.map do |file|
|
128
|
+
if file[0..wd.length-1] == wd
|
129
|
+
file[wd.length+1..-1]
|
130
|
+
else
|
131
|
+
file
|
132
|
+
end
|
133
|
+
end
|
134
|
+
end
|
135
|
+
end
|