kicker 3.0.0pre1 → 3.0.0pre2
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/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?
|