kicker 3.0.0pre1 → 3.0.0pre2
Sign up to get free protection for your applications and to get access to all the features.
- data/README.rdoc +10 -4
- data/lib/kicker.rb +26 -20
- data/lib/kicker/callback_chain.rb +14 -14
- data/lib/kicker/core_ext.rb +1 -1
- data/lib/kicker/fsevents.rb +6 -4
- data/lib/kicker/job.rb +57 -0
- data/lib/kicker/notification.rb +13 -24
- data/lib/kicker/options.rb +26 -22
- data/lib/kicker/recipes.rb +19 -16
- data/lib/kicker/recipes/could_not_handle_file.rb +1 -1
- data/lib/kicker/recipes/dot_kick.rb +8 -8
- data/lib/kicker/recipes/execute_cli_command.rb +1 -1
- data/lib/kicker/recipes/ignore.rb +4 -4
- data/lib/kicker/recipes/jstest.rb +1 -1
- data/lib/kicker/recipes/rails.rb +11 -11
- data/lib/kicker/recipes/ruby.rb +23 -23
- data/lib/kicker/utils.rb +44 -50
- data/lib/kicker/version.rb +1 -1
- metadata +108 -136
- data/vendor/terminal-notifier_v1.0/README.markdown +0 -70
- data/vendor/terminal-notifier_v1.0/terminal-notifier.app/Contents/Info.plist +0 -52
- data/vendor/terminal-notifier_v1.0/terminal-notifier.app/Contents/MacOS/terminal-notifier +0 -0
- data/vendor/terminal-notifier_v1.0/terminal-notifier.app/Contents/PkgInfo +0 -1
- data/vendor/terminal-notifier_v1.0/terminal-notifier.app/Contents/Resources/Terminal.icns +0 -0
- data/vendor/terminal-notifier_v1.0/terminal-notifier.app/Contents/Resources/en.lproj/Credits.rtf +0 -29
- data/vendor/terminal-notifier_v1.0/terminal-notifier.app/Contents/Resources/en.lproj/InfoPlist.strings +0 -0
- data/vendor/terminal-notifier_v1.0/terminal-notifier.app/Contents/Resources/en.lproj/MainMenu.nib +0 -0
- data/vendor/terminal-notifier_v1.0/terminal-notifier.app/Contents/_CodeSignature/CodeResources +0 -61
data/README.rdoc
CHANGED
@@ -11,6 +11,11 @@ Meet king kikker, kicking stuff in your computers is his dream come true!
|
|
11
11
|
<i>Drawing by Manfred Stienstra. The character is purely fictional, so if you
|
12
12
|
feel offended; live with it.</i>
|
13
13
|
|
14
|
+
== Ruby Version support
|
15
|
+
Beginning with 3.0.0 we are dropping Ruby 1.8 support.
|
16
|
+
We are not running tests automatically so nothing is guaranteed.
|
17
|
+
But it still might work. If it does not work for you. Use latest 2.x release.
|
18
|
+
|
14
19
|
== Installation
|
15
20
|
|
16
21
|
$ gem install kicker -s http://gemcutter.org
|
@@ -18,9 +23,9 @@ feel offended; live with it.</i>
|
|
18
23
|
== The short version
|
19
24
|
|
20
25
|
Usage: ./bin/kicker [options] [paths to watch]
|
21
|
-
|
26
|
+
|
22
27
|
Available recipes: ignore, jstest, rails, ruby.
|
23
|
-
|
28
|
+
|
24
29
|
-s, --silent Keep output to a minimum.
|
25
30
|
-q, --quiet Quiet output. Don't print timestamps when logging.
|
26
31
|
-c, --clear Clear console before each run.
|
@@ -64,7 +69,8 @@ A few recipes come shipped with Kicker:
|
|
64
69
|
HeadlessSquirrel[http://github.com/Fingertips/Headless-squirrel].
|
65
70
|
* Ignore, ignores logs, tmp, and svn and git files.
|
66
71
|
|
67
|
-
Add your own shared recipes to <tt>~/.kick</tt
|
72
|
+
Add your own shared recipes to <tt>~/.kick</tt> folder or
|
73
|
+
current working directory <tt>.kick</tt>.
|
68
74
|
|
69
75
|
=== Project specific handlers
|
70
76
|
|
@@ -110,7 +116,7 @@ be added:
|
|
110
116
|
test_files = files.take_and_map do |file|
|
111
117
|
if path =~ %r{^app/views/mailer/\w+\.erb$}
|
112
118
|
'test/unit/mailer_test.rb'
|
113
|
-
|
119
|
+
|
114
120
|
# elsif ... handle more app specific stuff
|
115
121
|
end
|
116
122
|
end
|
data/lib/kicker.rb
CHANGED
@@ -2,6 +2,7 @@ require 'kicker/version'
|
|
2
2
|
require 'kicker/fsevents'
|
3
3
|
require 'kicker/callback_chain'
|
4
4
|
require 'kicker/core_ext'
|
5
|
+
require 'kicker/job'
|
5
6
|
require 'kicker/notification'
|
6
7
|
require 'kicker/options'
|
7
8
|
require 'kicker/utils'
|
@@ -10,43 +11,49 @@ require 'kicker/recipes'
|
|
10
11
|
class Kicker #:nodoc:
|
11
12
|
def self.run(argv = ARGV)
|
12
13
|
Kicker::Options.parse(argv)
|
13
|
-
new.start
|
14
|
+
new.start.loop!
|
14
15
|
end
|
15
|
-
|
16
|
+
|
16
17
|
attr_reader :last_event_processed_at
|
17
|
-
|
18
|
+
|
18
19
|
def initialize
|
19
20
|
finished_processing!
|
20
21
|
end
|
21
|
-
|
22
|
+
|
22
23
|
def paths
|
23
24
|
@paths ||= Kicker.paths.map { |path| File.expand_path(path) }
|
24
25
|
end
|
25
|
-
|
26
|
+
|
26
27
|
def start
|
27
28
|
validate_options!
|
28
|
-
|
29
|
+
|
29
30
|
log "Watching for changes on: #{paths.join(', ')}"
|
30
31
|
log ''
|
31
|
-
|
32
|
+
|
32
33
|
run_startup_chain
|
33
34
|
run_watch_dog!
|
35
|
+
|
36
|
+
self
|
37
|
+
end
|
38
|
+
|
39
|
+
def loop!
|
40
|
+
(Thread.list - [Thread.current, Thread.main]).each(&:join)
|
34
41
|
end
|
35
|
-
|
42
|
+
|
36
43
|
private
|
37
|
-
|
44
|
+
|
38
45
|
def validate_options!
|
39
46
|
validate_paths_and_command!
|
40
47
|
validate_paths_exist!
|
41
48
|
end
|
42
|
-
|
49
|
+
|
43
50
|
def validate_paths_and_command!
|
44
51
|
if startup_chain.empty? && process_chain.empty? && pre_process_chain.empty?
|
45
52
|
puts Kicker::Options.parser.help
|
46
53
|
exit
|
47
54
|
end
|
48
55
|
end
|
49
|
-
|
56
|
+
|
50
57
|
def validate_paths_exist!
|
51
58
|
paths.each do |path|
|
52
59
|
unless File.exist?(path)
|
@@ -55,28 +62,27 @@ class Kicker #:nodoc:
|
|
55
62
|
end
|
56
63
|
end
|
57
64
|
end
|
58
|
-
|
65
|
+
|
59
66
|
def run_watch_dog!
|
60
67
|
dirs = @paths.map { |path| File.directory?(path) ? path : File.dirname(path) }
|
61
68
|
watch_dog = Kicker::FSEvents.start_watching(dirs, :latency => self.class.latency) do |events|
|
62
69
|
process events
|
63
70
|
end
|
64
|
-
|
65
71
|
trap('INT') do
|
66
72
|
log "Exiting ..."
|
67
73
|
watch_dog.stop
|
68
74
|
exit
|
69
75
|
end
|
70
76
|
end
|
71
|
-
|
77
|
+
|
72
78
|
def run_startup_chain
|
73
79
|
startup_chain.call([], false)
|
74
80
|
end
|
75
|
-
|
81
|
+
|
76
82
|
def finished_processing!
|
77
83
|
@last_event_processed_at = Time.now
|
78
84
|
end
|
79
|
-
|
85
|
+
|
80
86
|
def process(events)
|
81
87
|
unless (files = changed_files(events)).empty?
|
82
88
|
Utils.should_clear_screen = true
|
@@ -84,25 +90,25 @@ class Kicker #:nodoc:
|
|
84
90
|
finished_processing!
|
85
91
|
end
|
86
92
|
end
|
87
|
-
|
93
|
+
|
88
94
|
def changed_files(events)
|
89
95
|
make_paths_relative(events.map do |event|
|
90
96
|
files_in_directory(event.path).select { |file| file_changed_since_last_event? file }
|
91
97
|
end.flatten.uniq.sort)
|
92
98
|
end
|
93
|
-
|
99
|
+
|
94
100
|
def files_in_directory(dir)
|
95
101
|
Dir.entries(dir).sort[2..-1].map { |f| File.join(dir, f) }
|
96
102
|
rescue Errno::ENOENT
|
97
103
|
[]
|
98
104
|
end
|
99
|
-
|
105
|
+
|
100
106
|
def file_changed_since_last_event?(file)
|
101
107
|
File.mtime(file) > @last_event_processed_at
|
102
108
|
rescue Errno::ENOENT
|
103
109
|
false
|
104
110
|
end
|
105
|
-
|
111
|
+
|
106
112
|
def make_paths_relative(files)
|
107
113
|
return files if files.empty?
|
108
114
|
wd = Dir.pwd
|
@@ -2,7 +2,7 @@ class Kicker
|
|
2
2
|
class CallbackChain < Array #:nodoc:
|
3
3
|
alias_method :append_callback, :push
|
4
4
|
alias_method :prepend_callback, :unshift
|
5
|
-
|
5
|
+
|
6
6
|
def call(files, stop_when_empty = true)
|
7
7
|
each do |callback|
|
8
8
|
break if stop_when_empty and files.empty?
|
@@ -10,50 +10,50 @@ class Kicker
|
|
10
10
|
end
|
11
11
|
end
|
12
12
|
end
|
13
|
-
|
13
|
+
|
14
14
|
class << self
|
15
15
|
attr_writer :startup_chain
|
16
16
|
def startup_chain
|
17
17
|
@startup_chain ||= CallbackChain.new
|
18
18
|
end
|
19
|
-
|
19
|
+
|
20
20
|
attr_writer :pre_process_chain
|
21
21
|
def pre_process_chain
|
22
22
|
@pre_process_chain ||= CallbackChain.new
|
23
23
|
end
|
24
|
-
|
24
|
+
|
25
25
|
attr_writer :process_chain
|
26
26
|
def process_chain
|
27
27
|
@process_chain ||= CallbackChain.new
|
28
28
|
end
|
29
|
-
|
29
|
+
|
30
30
|
attr_writer :post_process_chain
|
31
31
|
def post_process_chain
|
32
32
|
@post_process_chain ||= CallbackChain.new
|
33
33
|
end
|
34
|
-
|
34
|
+
|
35
35
|
attr_writer :full_chain
|
36
36
|
def full_chain
|
37
37
|
@full_chain ||= CallbackChain.new([pre_process_chain, process_chain, post_process_chain])
|
38
38
|
end
|
39
39
|
end
|
40
|
-
|
40
|
+
|
41
41
|
def startup_chain
|
42
42
|
self.class.startup_chain
|
43
43
|
end
|
44
|
-
|
44
|
+
|
45
45
|
def pre_process_chain
|
46
46
|
self.class.pre_process_chain
|
47
47
|
end
|
48
|
-
|
48
|
+
|
49
49
|
def process_chain
|
50
50
|
self.class.process_chain
|
51
51
|
end
|
52
|
-
|
52
|
+
|
53
53
|
def post_process_chain
|
54
54
|
self.class.post_process_chain
|
55
55
|
end
|
56
|
-
|
56
|
+
|
57
57
|
def full_chain
|
58
58
|
self.class.full_chain
|
59
59
|
end
|
@@ -68,7 +68,7 @@ module Kernel
|
|
68
68
|
def startup(callback = nil, &block)
|
69
69
|
Kicker.startup_chain.append_callback(block ? block : callback)
|
70
70
|
end
|
71
|
-
|
71
|
+
|
72
72
|
# Adds a handler to the pre_process chain. This chain is ran before the
|
73
73
|
# process chain and is processed from first to last.
|
74
74
|
#
|
@@ -76,7 +76,7 @@ module Kernel
|
|
76
76
|
def pre_process(callback = nil, &block)
|
77
77
|
Kicker.pre_process_chain.append_callback(block ? block : callback)
|
78
78
|
end
|
79
|
-
|
79
|
+
|
80
80
|
# Adds a handler to the process chain. This chain is ran in between the
|
81
81
|
# pre_process and post_process chains. It is processed from first to last.
|
82
82
|
#
|
@@ -84,7 +84,7 @@ module Kernel
|
|
84
84
|
def process(callback = nil, &block)
|
85
85
|
Kicker.process_chain.append_callback(block ? block : callback)
|
86
86
|
end
|
87
|
-
|
87
|
+
|
88
88
|
# Adds a handler to the post_process chain. This chain is ran after the
|
89
89
|
# process chain and is processed from last to first.
|
90
90
|
#
|
data/lib/kicker/core_ext.rb
CHANGED
data/lib/kicker/fsevents.rb
CHANGED
@@ -6,11 +6,11 @@ class Kicker
|
|
6
6
|
class FSEvents
|
7
7
|
class FSEvent
|
8
8
|
attr_reader :path
|
9
|
-
|
9
|
+
|
10
10
|
def initialize(path)
|
11
11
|
@path = path
|
12
12
|
end
|
13
|
-
|
13
|
+
|
14
14
|
def files
|
15
15
|
Dir.glob("#{File.expand_path(path)}/*").map do |filename|
|
16
16
|
begin
|
@@ -21,14 +21,16 @@ class Kicker
|
|
21
21
|
end.compact.sort.reverse.map { |_, filename| filename }
|
22
22
|
end
|
23
23
|
end
|
24
|
-
|
24
|
+
|
25
25
|
def self.start_watching(paths, options={}, &block)
|
26
26
|
listener = Listen.to(*(paths.dup << options))
|
27
27
|
listener.change do |modified, added, removed|
|
28
28
|
files = modified + added + removed
|
29
29
|
directories = files.map { |file| File.dirname(file) }.uniq
|
30
30
|
yield directories.map { |directory| Kicker::FSEvents::FSEvent.new(directory) }
|
31
|
-
end
|
31
|
+
end
|
32
|
+
listener.start
|
33
|
+
listener
|
32
34
|
end
|
33
35
|
end
|
34
36
|
end
|
data/lib/kicker/job.rb
ADDED
@@ -0,0 +1,57 @@
|
|
1
|
+
class Kicker
|
2
|
+
class Job
|
3
|
+
def self.attr_with_default(name, merge_hash = false, &default)
|
4
|
+
# If `nil` this returns the `default`, unless explicitely set to `nil` by
|
5
|
+
# the user.
|
6
|
+
define_method(name) do
|
7
|
+
if instance_variable_get("@#{name}_assigned")
|
8
|
+
if assigned_value = instance_variable_get("@#{name}")
|
9
|
+
merge_hash ? instance_eval(&default).merge(assigned_value) : assigned_value
|
10
|
+
end
|
11
|
+
else
|
12
|
+
instance_eval(&default)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
define_method("#{name}=") do |value|
|
16
|
+
instance_variable_set("@#{name}_assigned", true)
|
17
|
+
instance_variable_set("@#{name}", value)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
attr_accessor :command, :exit_code, :output
|
22
|
+
|
23
|
+
def initialize(attributes)
|
24
|
+
@exit_code = 0
|
25
|
+
@output = ''
|
26
|
+
attributes.each { |k,v| send("#{k}=", v) }
|
27
|
+
end
|
28
|
+
|
29
|
+
def success?
|
30
|
+
exit_code == 0
|
31
|
+
end
|
32
|
+
|
33
|
+
attr_with_default(:print_before) do
|
34
|
+
"Executing: #{command}"
|
35
|
+
end
|
36
|
+
|
37
|
+
attr_with_default(:print_after) do
|
38
|
+
# Show all output if it wasn't shown before and the command fails.
|
39
|
+
"\n#{output}\n\n" if Kicker.silent? && !success?
|
40
|
+
end
|
41
|
+
|
42
|
+
# TODO default titles??
|
43
|
+
|
44
|
+
attr_with_default(:notify_before, true) do
|
45
|
+
{ :title => "Kicker: Executing", :message => command }
|
46
|
+
end
|
47
|
+
|
48
|
+
attr_with_default(:notify_after, true) do
|
49
|
+
message = Kicker.silent? ? "" : output
|
50
|
+
if success?
|
51
|
+
{ :title => "Kicker: Success", :message => message }
|
52
|
+
else
|
53
|
+
{ :title => "Kicker: Failed (#{exit_code})", :message => message }
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
data/lib/kicker/notification.rb
CHANGED
@@ -1,37 +1,26 @@
|
|
1
|
+
require 'notify'
|
2
|
+
|
1
3
|
class Kicker
|
2
4
|
module Notification #:nodoc:
|
3
|
-
|
4
|
-
TERMINAL_NOTIFICATION_BIN = File.join(vendor, 'terminal-notifier_v1.0/terminal-notifier.app/Contents/MacOS/terminal-notifier')
|
5
|
+
TITLE = 'Kicker'
|
5
6
|
|
6
7
|
class << self
|
7
8
|
attr_accessor :use, :app_bundle_identifier
|
8
9
|
alias_method :use?, :use
|
9
10
|
|
10
|
-
def
|
11
|
-
|
12
|
-
end
|
13
|
-
|
14
|
-
def change_occured(status)
|
15
|
-
notify('Kicker: Executing', status.command)
|
16
|
-
end
|
17
|
-
|
18
|
-
def result(status)
|
19
|
-
status.success? ? succeeded(status) : failed(status)
|
20
|
-
end
|
11
|
+
def notify(options)
|
12
|
+
return unless use?
|
21
13
|
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
end
|
14
|
+
unless message = options.delete(:message)
|
15
|
+
raise "A notification requires a `:message'"
|
16
|
+
end
|
26
17
|
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
end
|
18
|
+
options = {
|
19
|
+
:group => Dir.pwd,
|
20
|
+
:activate => app_bundle_identifier
|
21
|
+
}.merge(options)
|
32
22
|
|
33
|
-
|
34
|
-
`'#{TERMINAL_NOTIFICATION_BIN}' #{Dir.pwd} '#{title}' '#{message}' '#{app_bundle_identifier}'`
|
23
|
+
Notify.notify(TITLE, message, options)
|
35
24
|
end
|
36
25
|
end
|
37
26
|
end
|
data/lib/kicker/options.rb
CHANGED
@@ -3,79 +3,83 @@ require 'optparse'
|
|
3
3
|
class Kicker
|
4
4
|
class << self
|
5
5
|
attr_accessor :latency, :paths, :silent, :quiet, :clear_console
|
6
|
-
|
6
|
+
|
7
7
|
def silent?
|
8
8
|
@silent
|
9
9
|
end
|
10
|
-
|
10
|
+
|
11
11
|
def quiet?
|
12
12
|
@quiet
|
13
13
|
end
|
14
|
-
|
14
|
+
|
15
15
|
def clear_console?
|
16
16
|
@clear_console
|
17
17
|
end
|
18
|
+
|
19
|
+
|
20
|
+
def osx?
|
21
|
+
RUBY_PLATFORM.downcase.include?("darwin")
|
22
|
+
end
|
18
23
|
end
|
19
|
-
|
24
|
+
|
20
25
|
self.latency = 1
|
21
26
|
self.paths = %w{ . }
|
22
27
|
self.silent = false
|
23
28
|
self.quiet = false
|
24
29
|
self.clear_console = false
|
25
|
-
|
30
|
+
|
26
31
|
module Options #:nodoc:
|
27
32
|
DONT_SHOW_RECIPES = %w{ could_not_handle_file execute_cli_command dot_kick }
|
28
|
-
|
33
|
+
|
29
34
|
def self.recipes_for_display
|
30
35
|
Kicker::Recipes.recipe_files.map { |f| File.basename(f, '.rb') } - DONT_SHOW_RECIPES
|
31
36
|
end
|
32
|
-
|
37
|
+
|
33
38
|
def self.parser
|
34
39
|
@parser ||= OptionParser.new do |opt|
|
35
40
|
opt.banner = "Usage: #{$0} [options] [paths to watch]"
|
36
41
|
opt.separator " "
|
37
42
|
opt.separator " Available recipes: #{recipes_for_display.join(", ")}."
|
38
43
|
opt.separator " "
|
39
|
-
|
44
|
+
|
40
45
|
opt.on('-v', 'Print the Kicker version') do
|
41
|
-
puts VERSION
|
46
|
+
puts Kicker::VERSION
|
42
47
|
exit
|
43
48
|
end
|
44
49
|
|
45
50
|
opt.on('-s', '--silent', 'Keep output to a minimum.') do |silent|
|
46
51
|
Kicker.silent = true
|
47
52
|
end
|
48
|
-
|
53
|
+
|
49
54
|
opt.on('-q', '--quiet', "Quiet output. Don't print timestamps when logging.") do |quiet|
|
50
55
|
Kicker.silent = Kicker.quiet = true
|
51
56
|
end
|
52
|
-
|
57
|
+
|
53
58
|
opt.on('-c', '--clear', "Clear console before each run.") do |clear|
|
54
59
|
Kicker.clear_console = true
|
55
60
|
end
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
61
|
+
|
62
|
+
|
63
|
+
opt.on('--[no-]notification', 'Whether or not to send user notifications (on Mac OS X). Defaults to enabled.') do |notifications|
|
64
|
+
Notification.use = notifications
|
65
|
+
end
|
66
|
+
|
67
|
+
if Kicker.osx?
|
62
68
|
opt.on('--activate-app [BUNDLE ID]', "The application to activate when a notification is clicked. Defaults to `com.apple.Terminal'.") do |bundle_id|
|
63
69
|
Kicker::Notification.app_bundle_identifier = bundle_id
|
64
70
|
end
|
65
|
-
else
|
66
|
-
Kicker::Notification.use = false
|
67
71
|
end
|
68
|
-
|
72
|
+
|
69
73
|
opt.on('-l', '--latency [FLOAT]', "The time to collect file change events before acting on them. Defaults to #{Kicker.latency} second.") do |latency|
|
70
74
|
Kicker.latency = Float(latency)
|
71
75
|
end
|
72
|
-
|
76
|
+
|
73
77
|
opt.on('-r', '--recipe [NAME]', 'A named recipe to load.') do |name|
|
74
78
|
recipe(name)
|
75
79
|
end
|
76
80
|
end
|
77
81
|
end
|
78
|
-
|
82
|
+
|
79
83
|
def self.parse(argv)
|
80
84
|
parser.parse!(argv)
|
81
85
|
Kicker.paths = argv unless argv.empty?
|