alloy-kicker 1.1.2 → 1.9.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/README.rdoc CHANGED
@@ -35,4 +35,19 @@ And for fun, ghetto-autotest:
35
35
 
36
36
  == Installation
37
37
 
38
- $ sudo gem install alloy-kicker -s http://gems.github.com
38
+ $ sudo gem install alloy-kicker -s http://gems.github.com
39
+
40
+ == TODO
41
+
42
+ 15:12 sandbags: ideally i would like all events within a small window (e.g. 2s) to be batched into one
43
+ 15:12 sandbags: depending upon whether what is being reported is "file X changed" or "some files in directory X" changed
44
+ 15:12 sandbags: i forget the granularity FSEvents supports
45
+ 15:13 alloy: Yeah the window can be changed, which is a good idea
46
+ 15:13 alloy: FSEvents simply reports changes to files in dir X. nothing more
47
+ 15:14 sandbags: btw.. what is your rationale for using "!" in method names?
48
+ 15:14 alloy: So in this case we should simply keep the time at which an event occurs and then grep the dirs for files with an higher mtime
49
+ 15:14 sandbags: works for me
50
+ 15:16 alloy: Well the rationale in these cases was either it could raise something (validate) or it would run something external. But really it was just for kicks :)
51
+ 15:16 alloy: It could be cleaned up, as I agree it doesn't help much
52
+ 15:17 sandbags: i think it would be easy to grok without them
53
+ 15:17 alloy: Yeah indeed
data/Rakefile CHANGED
@@ -22,7 +22,7 @@ end
22
22
 
23
23
  Rake::TestTask.new do |t|
24
24
  t.libs << "test"
25
- t.test_files = FileList['test/*_test.rb']
25
+ t.test_files = FileList['test/**/*_test.rb']
26
26
  t.options = '-rs'
27
27
  end
28
28
 
data/VERSION.yml CHANGED
@@ -1,4 +1,4 @@
1
1
  ---
2
2
  :major: 1
3
- :minor: 1
4
- :patch: 2
3
+ :minor: 9
4
+ :patch: 0
data/bin/kicker CHANGED
@@ -1,4 +1,4 @@
1
1
  #!/usr/bin/env ruby
2
2
 
3
3
  require File.expand_path('../../lib/kicker', __FILE__)
4
- Kicker.run!
4
+ Kicker.run
data/lib/kicker.rb CHANGED
@@ -1,65 +1,53 @@
1
1
  $:.unshift File.expand_path('../../vendor', __FILE__)
2
2
  require 'rucola/fsevents'
3
- require 'growlnotifier/growl_helpers'
4
- require 'optparse'
3
+
4
+ require 'kicker/callback_chain'
5
+ require 'kicker/growl'
6
+ require 'kicker/options'
7
+ require 'kicker/utils'
8
+ require 'kicker/validate'
9
+
10
+ require 'kicker/recipes/could_not_handle_file'
11
+ require 'kicker/recipes/execute_cli_command'
5
12
 
6
13
  class Kicker
7
- OPTION_PARSER = lambda do |options|
8
- OptionParser.new do |opts|
9
- opts.banner = "Usage: #{$0} [options] -e [command] [paths to watch]"
10
-
11
- opts.on('-e', '--execute [COMMAND]', 'The command to execute.') do |command|
12
- options[:command] = command
13
- end
14
-
15
- opts.on('--[no-]growl', 'Whether or not to use Growl. Default is to use growl.') do |growl|
16
- options[:growl] = growl
17
- end
18
-
19
- opts.on('--growl-command [COMMAND]', 'The command to execute when the Growl succeeded message is clicked.') do |command|
20
- options[:growl_command] = command
21
- end
14
+ class << self
15
+ attr_accessor :latency
16
+
17
+ def latency
18
+ @latency ||= 1.5
19
+ end
20
+
21
+ def paths
22
+ @paths ||= %w{ . }
23
+ end
24
+
25
+ def run(argv = ARGV)
26
+ load '.kick' if File.exist?('.kick')
27
+ new(parse_options(argv)).start
22
28
  end
23
29
  end
24
30
 
25
- def self.parse_options(argv)
26
- argv = argv.dup
27
- options = { :growl => true }
28
- OPTION_PARSER.call(options).parse!(argv)
29
- options[:paths] = argv
30
- options
31
- end
32
-
33
- def self.run!(argv = ARGV)
34
- new(parse_options(argv)).start
35
- end
36
-
37
- include Growl
38
- GROWL_NOTIFICATIONS = {
39
- :change => 'Change occured',
40
- :succeeded => 'Command succeeded',
41
- :failed => 'Command failed'
42
- }
43
- GROWL_DEFAULT_CALLBACK = lambda do
44
- OSX::NSWorkspace.sharedWorkspace.launchApplication('Terminal')
45
- end
46
-
47
- attr_writer :command
48
- attr_reader :paths
49
- attr_accessor :use_growl, :growl_command
31
+ attr_reader :latency, :paths, :last_event_processed_at
50
32
 
51
33
  def initialize(options)
52
- @paths = options[:paths].map { |path| File.expand_path(path) }
53
- @command = options[:command]
34
+ @paths = (options[:paths] ? options[:paths] : Kicker.paths).map { |path| File.expand_path(path) }
35
+
36
+ @latency = options[:latency] || self.class.latency
54
37
  @use_growl = options[:growl]
55
38
  @growl_command = options[:growl_command]
39
+
40
+ finished_processing!
41
+ end
42
+
43
+ def callback_chain
44
+ self.class.callback_chain
56
45
  end
57
46
 
58
47
  def start
59
48
  validate_options!
60
49
 
61
50
  log "Watching for changes on: #{@paths.join(', ')}"
62
- log "With command: #{command}"
63
51
  log ''
64
52
 
65
53
  run_watch_dog!
@@ -68,52 +56,11 @@ class Kicker
68
56
  OSX.CFRunLoopRun
69
57
  end
70
58
 
71
- def command
72
- "sh -c #{@command.inspect}"
73
- end
74
-
75
59
  private
76
60
 
77
- def validate_options!
78
- validate_paths_and_command!
79
- validate_paths_exist!
80
- end
81
-
82
- def validate_paths_and_command!
83
- if @paths.empty? && @command.nil?
84
- puts OPTION_PARSER.call(nil).help
85
- exit
86
- end
87
- end
88
-
89
- def validate_paths_exist!
90
- @paths.each do |path|
91
- unless File.exist?(path)
92
- puts "The given path `#{path}' does not exist"
93
- exit 1
94
- end
95
- end
96
- end
97
-
98
- def log(message)
99
- puts "[#{Time.now}] #{message}"
100
- end
101
-
102
- def last_command_succeeded?
103
- $?.success?
104
- end
105
-
106
- def last_command_status
107
- $?.to_i
108
- end
109
-
110
- def start_growl!
111
- Growl::Notifier.sharedInstance.register('Kicker', Kicker::GROWL_NOTIFICATIONS.values)
112
- end
113
-
114
61
  def run_watch_dog!
115
62
  dirs = @paths.map { |path| File.directory?(path) ? path : File.dirname(path) }
116
- watch_dog = Rucola::FSEvents.start_watching(*dirs) { |events| process(events) }
63
+ watch_dog = Rucola::FSEvents.start_watching(dirs, :latency => @latency) { |events| process(events) }
117
64
 
118
65
  trap('INT') do
119
66
  log "Cleaning up…"
@@ -122,30 +69,20 @@ class Kicker
122
69
  end
123
70
  end
124
71
 
125
- def process(events)
126
- events.each do |event|
127
- @paths.each do |path|
128
- return execute! if event.last_modified_file =~ /^#{path}/
129
- end
130
- end
72
+ def finished_processing!
73
+ @last_event_processed_at = Time.now
131
74
  end
132
75
 
133
- def execute!
134
- log "Change occured. Executing command:"
135
- growl(GROWL_NOTIFICATIONS[:change], 'Kicker: Change occured', 'Executing command') if @use_growl
136
-
137
- output = `#{command}`
138
- output.strip.split("\n").each { |line| log " #{line}" }
139
-
140
- log "Command #{last_command_succeeded? ? 'succeeded' : "failed (#{last_command_status})"}"
141
-
142
- if @use_growl
143
- if last_command_succeeded?
144
- callback = @growl_command.nil? ? GROWL_DEFAULT_CALLBACK : lambda { system(@growl_command) }
145
- growl(GROWL_NOTIFICATIONS[:succeeded], "Kicker: Command succeeded", output, &callback)
146
- else
147
- growl(GROWL_NOTIFICATIONS[:failed], "Kicker: Command failed (#{last_command_status})", output, &GROWL_DEFAULT_CALLBACK)
148
- end
76
+ def changed_files(events)
77
+ events.map do |event|
78
+ event.files.select { |file| File.mtime(file) > @last_event_processed_at }
79
+ end.flatten
80
+ end
81
+
82
+ def process(events)
83
+ unless (files = changed_files(events)).empty?
84
+ callback_chain.run(self, files)
85
+ finished_processing!
149
86
  end
150
87
  end
151
88
  end
@@ -0,0 +1,21 @@
1
+ class Kicker
2
+ class CallbackChain < Array
3
+ alias_method :append_callback, :push
4
+ alias_method :prepend_callback, :unshift
5
+
6
+ def run(kicker, files)
7
+ each do |callback|
8
+ files = callback.call(kicker, files)
9
+ break if !files.is_a?(Array) || files.empty?
10
+ end
11
+ end
12
+ end
13
+
14
+ def self.callback_chain
15
+ @callback_chain ||= CallbackChain.new
16
+ end
17
+
18
+ def self.callback=(callback)
19
+ callback_chain.prepend_callback(callback)
20
+ end
21
+ end
@@ -0,0 +1,23 @@
1
+ require 'growlnotifier/growl_helpers'
2
+
3
+ class Kicker
4
+ include Growl
5
+
6
+ attr_accessor :use_growl, :growl_command
7
+
8
+ GROWL_NOTIFICATIONS = {
9
+ :change => 'Change occured',
10
+ :succeeded => 'Command succeeded',
11
+ :failed => 'Command failed'
12
+ }
13
+
14
+ GROWL_DEFAULT_CALLBACK = lambda do
15
+ OSX::NSWorkspace.sharedWorkspace.launchApplication('Terminal')
16
+ end
17
+
18
+ private
19
+
20
+ def start_growl!
21
+ Growl::Notifier.sharedInstance.register('Kicker', Kicker::GROWL_NOTIFICATIONS.values)
22
+ end
23
+ end
@@ -0,0 +1,33 @@
1
+ require 'optparse'
2
+
3
+ class Kicker
4
+ def self.option_parser
5
+ @option_parser ||= OptionParser.new do |opt|
6
+ opt.banner = "Usage: #{$0} [options] [paths to watch]"
7
+ end
8
+ end
9
+
10
+ OPTION_PARSER_CALLBACK = lambda do |options|
11
+ option_parser.on('--[no-]growl', 'Whether or not to use Growl. Default is to use growl.') do |growl|
12
+ options[:growl] = growl
13
+ end
14
+
15
+ option_parser.on('--growl-command [COMMAND]', 'The command to execute when the Growl succeeded message is clicked.') do |command|
16
+ options[:growl_command] = command
17
+ end
18
+
19
+ option_parser.on('-l', '--latency [FLOAT]', 'FSEvent grouping latency') do |latency|
20
+ options[:latency] = Float(latency)
21
+ end
22
+
23
+ option_parser
24
+ end
25
+
26
+ def self.parse_options(argv)
27
+ argv = argv.dup
28
+ options = { :growl => true }
29
+ OPTION_PARSER_CALLBACK.call(options).parse!(argv)
30
+ options[:paths] = argv unless argv.empty?
31
+ options
32
+ end
33
+ end
@@ -0,0 +1,7 @@
1
+ class Kicker
2
+ COULD_NOT_HANDLE_CALLBACK = lambda do |kicker, files|
3
+ kicker.log("Could not handle: #{files.join(', ')}")
4
+ end
5
+
6
+ self.callback = COULD_NOT_HANDLE_CALLBACK
7
+ end
@@ -0,0 +1,7 @@
1
+ class Kicker
2
+ option_parser.on('-e', '--execute [COMMAND]', 'The command to execute.') do |command|
3
+ Kicker.callback = lambda do |kicker, _|
4
+ kicker.execute_command "sh -c #{command.inspect}"
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,34 @@
1
+ class Kicker
2
+ def execute_command(command)
3
+ log "Change occured. Executing command:"
4
+ growl(GROWL_NOTIFICATIONS[:change], 'Kicker: Change occured', 'Executing command') if @use_growl
5
+
6
+ output = `#{command}`
7
+ output.strip.split("\n").each { |line| log " #{line}" }
8
+
9
+ log "Command #{last_command_succeeded? ? 'succeeded' : "failed (#{last_command_status})"}"
10
+
11
+ if @use_growl
12
+ if last_command_succeeded?
13
+ callback = @growl_command.nil? ? GROWL_DEFAULT_CALLBACK : lambda { system(@growl_command) }
14
+ growl(GROWL_NOTIFICATIONS[:succeeded], "Kicker: Command succeeded", output, &callback)
15
+ else
16
+ growl(GROWL_NOTIFICATIONS[:failed], "Kicker: Command failed (#{last_command_status})", output, &GROWL_DEFAULT_CALLBACK)
17
+ end
18
+ end
19
+ end
20
+
21
+ def log(message)
22
+ puts "[#{Time.now}] #{message}"
23
+ end
24
+
25
+ private
26
+
27
+ def last_command_succeeded?
28
+ $?.success?
29
+ end
30
+
31
+ def last_command_status
32
+ $?.to_i
33
+ end
34
+ end
@@ -0,0 +1,24 @@
1
+ class Kicker
2
+ private
3
+
4
+ def validate_options!
5
+ validate_paths_and_command!
6
+ validate_paths_exist!
7
+ end
8
+
9
+ def validate_paths_and_command!
10
+ if callback_chain.length == 1
11
+ puts OPTION_PARSER_CALLBACK.call(nil).help
12
+ exit
13
+ end
14
+ end
15
+
16
+ def validate_paths_exist!
17
+ @paths.each do |path|
18
+ unless File.exist?(path)
19
+ puts "The given path `#{path}' does not exist"
20
+ exit 1
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,103 @@
1
+ require File.expand_path('../test_helper', __FILE__)
2
+
3
+ describe "Kicker, concerning its callback chain" do
4
+ it "should return the callback chain instance" do
5
+ Kicker.callback_chain.should.be.instance_of Kicker::CallbackChain
6
+ end
7
+
8
+ it "should provide a shortcut method which prepends a callback" do
9
+ Kicker.callback = lambda { :from_callback }
10
+ Kicker.callback_chain.first.call.should == :from_callback
11
+ end
12
+
13
+ it "should be accessible by an instance" do
14
+ kicker = Kicker.new({})
15
+ kicker.callback_chain.should.be Kicker.callback_chain
16
+ end
17
+ end
18
+
19
+ describe "Kicker::CallbackChain" do
20
+ it "should be a subclass of Array" do
21
+ Kicker::CallbackChain.superclass.should.be Array
22
+ end
23
+ end
24
+
25
+ describe "An instance of Kicker::CallbackChain, concerning it's API" do
26
+ before do
27
+ @chain = Kicker::CallbackChain.new
28
+
29
+ @callback1 = lambda {}
30
+ @callback2 = lambda {}
31
+ end
32
+
33
+ it "should append a callback" do
34
+ @chain << @callback1
35
+ @chain.append_callback(@callback2)
36
+
37
+ @chain.should == [@callback1, @callback2]
38
+ end
39
+
40
+ it "should prepend a callback" do
41
+ @chain << @callback1
42
+ @chain.prepend_callback(@callback2)
43
+
44
+ @chain.should == [@callback2, @callback1]
45
+ end
46
+ end
47
+
48
+ describe "An instance of Kicker::CallbackChain, when running the chain" do
49
+ before do
50
+ @kicker = Kicker.new({})
51
+
52
+ @chain = Kicker::CallbackChain.new
53
+ @result = []
54
+ end
55
+
56
+ it "should call the callbacks from first to last" do
57
+ @chain.append_callback lambda { @result << 1 }
58
+ @chain.append_callback lambda { @result << 2 }
59
+ @chain.run(@kicker, [])
60
+ @result.should == [1, 2]
61
+ end
62
+
63
+ it "should pass in the Kicker instance with each yield" do
64
+ kicker = nil
65
+ @chain.append_callback lambda { |x, _| kicker = x }
66
+ @chain.run(@kicker, [])
67
+ kicker.should.be @kicker
68
+ end
69
+
70
+ it "should pass the files array given to run to the first callback and pass the result array of that call to the next callback and so on" do
71
+ @chain.append_callback lambda { |_, files|
72
+ @result.concat(files)
73
+ %w{ /file/3 /file/4 }
74
+ }
75
+
76
+ @chain.append_callback lambda { |_, files|
77
+ @result.concat(files)
78
+ []
79
+ }
80
+
81
+ @chain.run(@kicker, %w{ /file/1 /file/2 })
82
+ @result.should == %w{ /file/1 /file/2 /file/3 /file/4 }
83
+ end
84
+
85
+ it "should halt the callback chain once an empty array is returned from a callback" do
86
+ @chain.append_callback lambda { @result << 1; [] }
87
+ @chain.append_callback lambda { @result << 2 }
88
+ @chain.run(@kicker, %w{ /file/1 /file/2 })
89
+ @result.should == [1]
90
+ end
91
+
92
+ it "should halt the callback chain if not an Array instance is returned from a callback" do
93
+ [nil, false, ''].each do |object|
94
+ @chain.clear
95
+ @result.clear
96
+
97
+ @chain.append_callback lambda { @result << 1; object }
98
+ @chain.append_callback lambda { @result << 2 }
99
+ @chain.run(@kicker, %w{ /file/1 /file/2 })
100
+ @result.should == [1]
101
+ end
102
+ end
103
+ end
@@ -0,0 +1,75 @@
1
+ require File.expand_path('../test_helper', __FILE__)
2
+
3
+ describe "Kicker, when a change occurs" do
4
+ before do
5
+ Kicker.any_instance.stubs(:last_command_succeeded?).returns(true)
6
+ Kicker.any_instance.stubs(:log)
7
+ @kicker = Kicker.new({})
8
+ end
9
+
10
+ it "should store the current time as when the last change occurred" do
11
+ now = Time.now
12
+ Time.stubs(:now).returns(now)
13
+
14
+ @kicker.send(:finished_processing!)
15
+ @kicker.last_event_processed_at.should.be now
16
+ end
17
+
18
+ it "should return an array of files that have changed since the last event" do
19
+ file1 = touch('1')
20
+ file2 = touch('2')
21
+ file3 = touch('3')
22
+ file4 = touch('4')
23
+ @kicker.send(:finished_processing!)
24
+
25
+ events = [event(file1, file2), event(file3, file4)]
26
+
27
+ @kicker.send(:changed_files, events).should == []
28
+ @kicker.send(:finished_processing!)
29
+
30
+ sleep(1)
31
+ touch('2')
32
+
33
+ @kicker.send(:changed_files, events).should == [file2]
34
+ @kicker.send(:finished_processing!)
35
+
36
+ sleep(1)
37
+ touch('1')
38
+ touch('3')
39
+
40
+ @kicker.send(:changed_files, events).should == [file1, file3]
41
+ end
42
+
43
+ it "should run the callback chain with all changed files" do
44
+ files = %w{ /file/1 /file/2 }
45
+ events = [event('/file/1'), event('/file/2')]
46
+
47
+ @kicker.expects(:changed_files).with(events).returns(files)
48
+ @kicker.callback_chain.expects(:run).with(@kicker, files)
49
+ @kicker.expects(:finished_processing!)
50
+
51
+ @kicker.send(:process, events)
52
+ end
53
+
54
+ it "should not run the callback chain if there were no changed files" do
55
+ @kicker.stubs(:changed_files).returns([])
56
+ @kicker.callback_chain.expects(:run).never
57
+ @kicker.expects(:finished_processing!).never
58
+
59
+ @kicker.send(:process, [event()])
60
+ end
61
+
62
+ private
63
+
64
+ def touch(file)
65
+ file = "/tmp/kicker_test_tmp_#{file}"
66
+ `touch #{file}`
67
+ file
68
+ end
69
+
70
+ def event(*files)
71
+ event = stub('FSEvent')
72
+ event.stubs(:files).returns(files)
73
+ event
74
+ end
75
+ end
@@ -0,0 +1,133 @@
1
+ require File.expand_path('../test_helper', __FILE__)
2
+
3
+ describe "Kicker" do
4
+ it "should return the default paths to watch" do
5
+ Kicker.paths.should == %w{ . }
6
+ end
7
+
8
+ it "should check if a .kick file exists and if so load it before running" do
9
+ Kicker.any_instance.stubs(:start)
10
+
11
+ File.expects(:exist?).with('.kick').returns(true)
12
+ Kicker.expects(:load).with('.kick')
13
+ Kicker.run
14
+ end
15
+ end
16
+
17
+ describe "Kicker, when initializing" do
18
+ before do
19
+ @now = Time.now
20
+ Time.stubs(:now).returns(@now)
21
+
22
+ @kicker = Kicker.new(:paths => %w{ /some/dir a/relative/path })
23
+ end
24
+
25
+ it "should return the extended paths to watch" do
26
+ @kicker.paths.should == ['/some/dir', File.expand_path('a/relative/path')]
27
+ end
28
+
29
+ it "should have assigned the current time to last_event_processed_at" do
30
+ @kicker.last_event_processed_at.should == @now
31
+ end
32
+
33
+ it "should use the default paths if no paths were given" do
34
+ Kicker.new({}).paths.should == [File.expand_path('.')]
35
+ end
36
+
37
+ it "should use the default FSEvents latency if none was given" do
38
+ @kicker.latency.should == 1.5
39
+ end
40
+
41
+ it "should use the given FSEvents latency if one was given" do
42
+ Kicker.new(:latency => 3.5).latency.should == 3.5
43
+ end
44
+ end
45
+
46
+ describe "Kicker, when starting" do
47
+ before do
48
+ @kicker = Kicker.new(:paths => %w{ /some/file.rb })
49
+ @kicker.stubs(:log)
50
+ Rucola::FSEvents.stubs(:start_watching)
51
+ OSX.stubs(:CFRunLoopRun)
52
+ end
53
+
54
+ it "should show the usage banner and exit when there is no extra callback defined" do
55
+ @kicker.stubs(:validate_paths_exist!)
56
+ Kicker.stubs(:callback_chain).returns([1])
57
+
58
+ Kicker::OPTION_PARSER_CALLBACK.stubs(:call).returns(mock('OptionParser', :help => 'help'))
59
+ @kicker.expects(:puts).with("help")
60
+ @kicker.expects(:exit)
61
+
62
+ @kicker.start
63
+ end
64
+
65
+ it "should warn the user and exit if any of the given paths doesn't exist" do
66
+ @kicker.stubs(:validate_paths_and_command!)
67
+
68
+ @kicker.expects(:puts).with("The given path `/some/file.rb' does not exist")
69
+ @kicker.expects(:exit).with(1)
70
+
71
+ @kicker.start
72
+ end
73
+
74
+ it "should start a FSEvents stream with the assigned latency" do
75
+ @kicker.stubs(:validate_options!)
76
+
77
+ Rucola::FSEvents.expects(:start_watching).with(['/some'], :latency => @kicker.latency)
78
+ @kicker.start
79
+ end
80
+
81
+ it "should start a FSEvents stream which watches all paths, but the dirnames of paths if they're files" do
82
+ @kicker.stubs(:validate_options!)
83
+ File.stubs(:directory?).with('/some/file.rb').returns(false)
84
+
85
+ Rucola::FSEvents.expects(:start_watching).with(['/some'], :latency => @kicker.latency)
86
+ @kicker.start
87
+ end
88
+
89
+ it "should start a FSEvents stream with a block which calls #process with any generated events" do
90
+ @kicker.stubs(:validate_options!)
91
+
92
+ Rucola::FSEvents.expects(:start_watching).yields(['event'])
93
+ @kicker.expects(:process).with(['event'])
94
+
95
+ @kicker.start
96
+ end
97
+
98
+ it "should setup a signal handler for `INT' which stops the FSEvents stream and exits" do
99
+ @kicker.stubs(:validate_options!)
100
+
101
+ watch_dog = stub('Rucola::FSEvents')
102
+ Rucola::FSEvents.stubs(:start_watching).returns(watch_dog)
103
+
104
+ @kicker.expects(:trap).with('INT').yields
105
+ watch_dog.expects(:stop)
106
+ @kicker.expects(:exit)
107
+
108
+ @kicker.start
109
+ end
110
+
111
+ it "should start a CFRunLoop" do
112
+ @kicker.stubs(:validate_options!)
113
+
114
+ OSX.expects(:CFRunLoopRun)
115
+ @kicker.start
116
+ end
117
+
118
+ it "should register with growl if growl should be used" do
119
+ @kicker.stubs(:validate_options!)
120
+ @kicker.use_growl = true
121
+
122
+ Growl::Notifier.sharedInstance.expects(:register).with('Kicker', Kicker::GROWL_NOTIFICATIONS.values)
123
+ @kicker.start
124
+ end
125
+
126
+ it "should _not_ register with growl if growl should not be used" do
127
+ @kicker.stubs(:validate_options!)
128
+ @kicker.use_growl = false
129
+
130
+ Growl::Notifier.sharedInstance.expects(:register).never
131
+ @kicker.start
132
+ end
133
+ end
@@ -0,0 +1,25 @@
1
+ require File.expand_path('../test_helper', __FILE__)
2
+
3
+ describe "Kicker.parse_options" do
4
+ it "should parse the paths" do
5
+ Kicker.parse_options([])[:paths].should.be nil
6
+
7
+ Kicker.parse_options(%w{ /some/file.rb })[:paths].should == %w{ /some/file.rb }
8
+ Kicker.parse_options(%w{ /some/file.rb /a/dir /and/some/other/file.rb })[:paths].should ==
9
+ %w{ /some/file.rb /a/dir /and/some/other/file.rb }
10
+ end
11
+
12
+ it "should parse if growl shouldn't be used" do
13
+ Kicker.parse_options([])[:growl].should == true
14
+ Kicker.parse_options(%w{ --no-growl })[:growl].should == false
15
+ end
16
+
17
+ it "should parse the Growl command to use when the user clicks the Growl succeeded message" do
18
+ Kicker.parse_options(%w{ --growl-command ls })[:growl_command].should == 'ls'
19
+ end
20
+
21
+ it "should parse the latency to pass to FSEvents" do
22
+ Kicker.parse_options(%w{ -l 2.5 })[:latency].should == 2.5
23
+ Kicker.parse_options(%w{ --latency 3.5 })[:latency].should == 3.5
24
+ end
25
+ end
@@ -0,0 +1,9 @@
1
+ require File.expand_path('../../test_helper', __FILE__)
2
+
3
+ describe "Kicker, concerning the default `could not handle file' callback" do
4
+ it "should log that it could not handle the given files" do
5
+ kicker = Kicker.new({})
6
+ kicker.expects(:log).with("Could not handle: /file/1, /file/2")
7
+ Kicker.callback_chain.last.call(kicker, %w{ /file/1 /file/2 })
8
+ end
9
+ end
@@ -0,0 +1,25 @@
1
+ require File.expand_path('../../test_helper', __FILE__)
2
+
3
+ describe "Kicker, concerning the `execute a command-line' callback" do
4
+ it "should parse the command and add the callback" do
5
+ before = Kicker.callback_chain.length
6
+
7
+ Kicker.parse_options(%w{ -e ls })
8
+ Kicker.callback_chain.length.should == before + 1
9
+
10
+ Kicker.parse_options(%w{ --execute ls })
11
+ Kicker.callback_chain.length.should == before + 2
12
+ end
13
+
14
+ it "should call execute_command with the given command" do
15
+ Kicker.parse_options(%w{ -e ls })
16
+
17
+ callback = Kicker.callback_chain.first
18
+ callback.should.be.instance_of Proc
19
+
20
+ kicker = Kicker.new({})
21
+ kicker.expects(:execute_command).with('sh -c "ls"')
22
+
23
+ callback.call(kicker, %w{ /file/1 /file/2 }).should.not.be.instance_of Array
24
+ end
25
+ end
@@ -0,0 +1,74 @@
1
+ require File.expand_path('../test_helper', __FILE__)
2
+
3
+ describe "A Kicker instance, concerning its utility methods" do
4
+ before do
5
+ Kicker.any_instance.stubs(:last_command_succeeded?).returns(true)
6
+ @kicker = Kicker.new(:paths => %w{ /some/dir }, :command => 'ls -l')
7
+ end
8
+
9
+ it "should print a log entry with timestamp" do
10
+ now = Time.now
11
+ Time.stubs(:now).returns(now)
12
+
13
+ @kicker.expects(:puts).with("[#{now}] the message")
14
+ @kicker.send(:log, 'the message')
15
+ end
16
+
17
+ it "should log the output of the command indented by 2 spaces and whether or not the command succeeded" do
18
+ @kicker.stubs(:`).returns("line 1\nline 2")
19
+
20
+ @kicker.expects(:log).with('Change occured. Executing command:')
21
+ @kicker.expects(:log).with(' line 1')
22
+ @kicker.expects(:log).with(' line 2')
23
+ @kicker.expects(:log).with('Command succeeded')
24
+ @kicker.execute_command('')
25
+
26
+ @kicker.stubs(:last_command_succeeded?).returns(false)
27
+ @kicker.stubs(:last_command_status).returns(123)
28
+ @kicker.expects(:log).with('Change occured. Executing command:')
29
+ @kicker.expects(:log).with(' line 1')
30
+ @kicker.expects(:log).with(' line 2')
31
+ @kicker.expects(:log).with('Command failed (123)')
32
+ @kicker.execute_command('')
33
+ end
34
+
35
+ it "should send the Growl messages with the default click callback" do
36
+ @kicker.stubs(:log)
37
+
38
+ @kicker.stubs(:`).returns("line 1\nline 2")
39
+ @kicker.use_growl = true
40
+
41
+ OSX::NSWorkspace.sharedWorkspace.expects(:launchApplication).with('Terminal').times(2)
42
+
43
+ @kicker.expects(:growl).with(Kicker::GROWL_NOTIFICATIONS[:change], 'Kicker: Change occured', 'Executing command')
44
+ @kicker.expects(:growl).with(Kicker::GROWL_NOTIFICATIONS[:succeeded], 'Kicker: Command succeeded', "line 1\nline 2").yields
45
+ @kicker.execute_command('')
46
+
47
+ @kicker.stubs(:last_command_succeeded?).returns(false)
48
+ @kicker.stubs(:last_command_status).returns(123)
49
+ @kicker.expects(:growl).with(Kicker::GROWL_NOTIFICATIONS[:change], 'Kicker: Change occured', 'Executing command')
50
+ @kicker.expects(:growl).with(Kicker::GROWL_NOTIFICATIONS[:failed], 'Kicker: Command failed (123)', "line 1\nline 2").yields
51
+ @kicker.execute_command('')
52
+ end
53
+
54
+ it "should send the Growl messages with a click callback which executes the specified growl command when succeeded" do
55
+ @kicker.stubs(:log)
56
+
57
+ @kicker.stubs(:`).returns("line 1\nline 2")
58
+ @kicker.use_growl = true
59
+ @kicker.growl_command = 'ls -l'
60
+
61
+ @kicker.expects(:system).with('ls -l').times(1)
62
+ OSX::NSWorkspace.sharedWorkspace.expects(:launchApplication).with('Terminal').times(1)
63
+
64
+ @kicker.expects(:growl).with(Kicker::GROWL_NOTIFICATIONS[:change], 'Kicker: Change occured', 'Executing command')
65
+ @kicker.expects(:growl).with(Kicker::GROWL_NOTIFICATIONS[:succeeded], 'Kicker: Command succeeded', "line 1\nline 2").yields
66
+ @kicker.execute_command('')
67
+
68
+ @kicker.stubs(:last_command_succeeded?).returns(false)
69
+ @kicker.stubs(:last_command_status).returns(123)
70
+ @kicker.expects(:growl).with(Kicker::GROWL_NOTIFICATIONS[:change], 'Kicker: Change occured', 'Executing command')
71
+ @kicker.expects(:growl).with(Kicker::GROWL_NOTIFICATIONS[:failed], 'Kicker: Command failed (123)', "line 1\nline 2").yields
72
+ @kicker.execute_command('')
73
+ end
74
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: alloy-kicker
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.1.2
4
+ version: 1.9.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Eloy Duran
@@ -9,7 +9,7 @@ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
11
 
12
- date: 2009-06-08 00:00:00 -07:00
12
+ date: 2009-06-25 00:00:00 -07:00
13
13
  default_executable:
14
14
  dependencies: []
15
15
 
@@ -30,8 +30,21 @@ files:
30
30
  - VERSION.yml
31
31
  - bin/kicker
32
32
  - lib/kicker.rb
33
- - test/kicker_test.rb
33
+ - lib/kicker/callback_chain.rb
34
+ - lib/kicker/growl.rb
35
+ - lib/kicker/options.rb
36
+ - lib/kicker/recipes/could_not_handle_file.rb
37
+ - lib/kicker/recipes/execute_cli_command.rb
38
+ - lib/kicker/utils.rb
39
+ - lib/kicker/validate.rb
40
+ - test/callback_chain_test.rb
41
+ - test/filesystem_change_test.rb
42
+ - test/initialization_test.rb
43
+ - test/options_test.rb
44
+ - test/recipes/could_not_handle_file_test.rb
45
+ - test/recipes/execute_cli_command_test.rb
34
46
  - test/test_helper.rb
47
+ - test/utils_test.rb
35
48
  - vendor/growlnotifier/growl.rb
36
49
  - vendor/growlnotifier/growl_helpers.rb
37
50
  - vendor/rucola/fsevents.rb
@@ -63,5 +76,11 @@ signing_key:
63
76
  specification_version: 2
64
77
  summary: A simple OS X CLI tool which uses FSEvents to run a given shell command.
65
78
  test_files:
66
- - test/kicker_test.rb
79
+ - test/callback_chain_test.rb
80
+ - test/filesystem_change_test.rb
81
+ - test/initialization_test.rb
82
+ - test/options_test.rb
83
+ - test/recipes/could_not_handle_file_test.rb
84
+ - test/recipes/execute_cli_command_test.rb
67
85
  - test/test_helper.rb
86
+ - test/utils_test.rb
data/test/kicker_test.rb DELETED
@@ -1,221 +0,0 @@
1
- require File.expand_path('../test_helper', __FILE__)
2
-
3
- describe "Kicker.parse_options" do
4
- it "should parse the paths" do
5
- Kicker.parse_options(%w{ /some/file.rb })[:paths].should == %w{ /some/file.rb }
6
- Kicker.parse_options(%w{ /some/file.rb /a/dir /and/some/other/file.rb })[:paths].should ==
7
- %w{ /some/file.rb /a/dir /and/some/other/file.rb }
8
- end
9
-
10
- it "should parse the command" do
11
- Kicker.parse_options(%w{ -e ls })[:command].should == 'ls'
12
- Kicker.parse_options(%w{ --execute ls })[:command].should == 'ls'
13
- end
14
-
15
- it "should parse if growl shouldn't be used" do
16
- Kicker.parse_options([])[:growl].should == true
17
- Kicker.parse_options(%w{ --no-growl })[:growl].should == false
18
- end
19
-
20
- it "should parse the Growl command to use when the user clicks the Growl succeeded message" do
21
- Kicker.parse_options(%w{ --growl-command ls })[:growl_command].should == 'ls'
22
- end
23
- end
24
-
25
- describe "Kicker, when initializing" do
26
- before do
27
- @kicker = Kicker.new(:paths => %w{ /some/dir a/relative/path }, :command => 'ls -l')
28
- end
29
-
30
- it "should return the extended paths to watch" do
31
- @kicker.paths.should == ['/some/dir', File.expand_path('a/relative/path')]
32
- end
33
-
34
- it "should return the command to execute once a change occurs" do
35
- @kicker.command.should == 'sh -c "ls -l"'
36
- end
37
- end
38
-
39
- describe "Kicker, when starting" do
40
- before do
41
- @kicker = Kicker.new(:paths => %w{ /some/file.rb }, :command => 'ls -l')
42
- @kicker.stubs(:log)
43
- Rucola::FSEvents.stubs(:start_watching)
44
- OSX.stubs(:CFRunLoopRun)
45
- end
46
-
47
- it "should show the usage banner and exit when there are no paths and a command" do
48
- @kicker.instance_variable_set("@paths", [])
49
- @kicker.command = nil
50
- @kicker.stubs(:validate_paths_exist!)
51
-
52
- Kicker::OPTION_PARSER.stubs(:call).returns(mock('OptionParser', :help => 'help'))
53
- @kicker.expects(:puts).with("help")
54
- @kicker.expects(:exit)
55
-
56
- @kicker.start
57
- end
58
-
59
- it "should warn the user and exit if any of the given paths doesn't exist" do
60
- @kicker.expects(:puts).with("The given path `/some/file.rb' does not exist")
61
- @kicker.expects(:exit).with(1)
62
-
63
- @kicker.start
64
- end
65
-
66
- it "should start a FSEvents stream which watches all paths, but the dirnames of paths if they're files" do
67
- @kicker.stubs(:validate_options!)
68
- File.stubs(:directory?).with('/some/file.rb').returns(false)
69
-
70
- Rucola::FSEvents.expects(:start_watching).with('/some')
71
- @kicker.start
72
- end
73
-
74
- it "should start a FSEvents stream with a block which calls #process with any generated events" do
75
- @kicker.stubs(:validate_options!)
76
-
77
- Rucola::FSEvents.expects(:start_watching).yields(['event'])
78
- @kicker.expects(:process).with(['event'])
79
-
80
- @kicker.start
81
- end
82
-
83
- it "should setup a signal handler for `INT' which stops the FSEvents stream and exits" do
84
- @kicker.stubs(:validate_options!)
85
-
86
- watch_dog = stub('Rucola::FSEvents')
87
- Rucola::FSEvents.stubs(:start_watching).returns(watch_dog)
88
-
89
- @kicker.expects(:trap).with('INT').yields
90
- watch_dog.expects(:stop)
91
- @kicker.expects(:exit)
92
-
93
- @kicker.start
94
- end
95
-
96
- it "should start a CFRunLoop" do
97
- @kicker.stubs(:validate_options!)
98
-
99
- OSX.expects(:CFRunLoopRun)
100
- @kicker.start
101
- end
102
-
103
- it "should register with growl if growl should be used" do
104
- @kicker.stubs(:validate_options!)
105
- @kicker.use_growl = true
106
-
107
- Growl::Notifier.sharedInstance.expects(:register).with('Kicker', Kicker::GROWL_NOTIFICATIONS.values)
108
- @kicker.start
109
- end
110
-
111
- it "should _not_ register with growl if growl should not be used" do
112
- @kicker.stubs(:validate_options!)
113
- @kicker.use_growl = false
114
-
115
- Growl::Notifier.sharedInstance.expects(:register).never
116
- @kicker.start
117
- end
118
- end
119
-
120
- describe "Kicker, when a change occurs" do
121
- before do
122
- Kicker.any_instance.stubs(:last_command_succeeded?).returns(true)
123
- Kicker.any_instance.stubs(:log)
124
- @kicker = Kicker.new(:paths => %w{ /some/file.rb /some/dir }, :command => 'ls -l')
125
- end
126
-
127
- it "should execute the command if a change occured to a watched path which is a file" do
128
- event = stub('Event', :last_modified_file => '/some/file.rb')
129
-
130
- @kicker.expects(:`).with(@kicker.command).returns('')
131
- @kicker.send(:process, [event])
132
- end
133
-
134
- it "should execute the command if a change occured to some file in watched path which is a directory" do
135
- event = stub('Event', :last_modified_file => '/some/dir/with/file.rb')
136
-
137
- @kicker.expects(:`).with(@kicker.command).returns('')
138
- @kicker.send(:process, [event])
139
- end
140
-
141
- it "should _not_ execute the command if a change occured to a file that isn't being watched" do
142
- event1 = stub('Event', :last_modified_file => '/some/other_file.rb')
143
- event2 = stub('Event', :last_modified_file => '/some/not/watched/dir/with/file.rb')
144
-
145
- @kicker.expects(:`).never
146
- @kicker.send(:process, [event1, event2])
147
- end
148
- end
149
-
150
- describe "Kicker, in general" do
151
- before do
152
- Kicker.any_instance.stubs(:last_command_succeeded?).returns(true)
153
- @kicker = Kicker.new(:paths => %w{ /some/dir }, :command => 'ls -l')
154
- end
155
-
156
- it "should print a log entry with timestamp" do
157
- now = Time.now
158
- Time.stubs(:now).returns(now)
159
-
160
- @kicker.expects(:puts).with("[#{now}] the message")
161
- @kicker.send(:log, 'the message')
162
- end
163
-
164
- it "should log the output of the command indented by 2 spaces and whether or not the command succeeded" do
165
- @kicker.stubs(:`).returns("line 1\nline 2")
166
-
167
- @kicker.expects(:log).with('Change occured. Executing command:')
168
- @kicker.expects(:log).with(' line 1')
169
- @kicker.expects(:log).with(' line 2')
170
- @kicker.expects(:log).with('Command succeeded')
171
- @kicker.send(:execute!)
172
-
173
- @kicker.stubs(:last_command_succeeded?).returns(false)
174
- @kicker.stubs(:last_command_status).returns(123)
175
- @kicker.expects(:log).with('Change occured. Executing command:')
176
- @kicker.expects(:log).with(' line 1')
177
- @kicker.expects(:log).with(' line 2')
178
- @kicker.expects(:log).with('Command failed (123)')
179
- @kicker.send(:execute!)
180
- end
181
-
182
- it "should send the Growl messages with the default click callback" do
183
- @kicker.stubs(:log)
184
-
185
- @kicker.stubs(:`).returns("line 1\nline 2")
186
- @kicker.use_growl = true
187
-
188
- OSX::NSWorkspace.sharedWorkspace.expects(:launchApplication).with('Terminal').times(2)
189
-
190
- @kicker.expects(:growl).with(Kicker::GROWL_NOTIFICATIONS[:change], 'Kicker: Change occured', 'Executing command')
191
- @kicker.expects(:growl).with(Kicker::GROWL_NOTIFICATIONS[:succeeded], 'Kicker: Command succeeded', "line 1\nline 2").yields
192
- @kicker.send(:execute!)
193
-
194
- @kicker.stubs(:last_command_succeeded?).returns(false)
195
- @kicker.stubs(:last_command_status).returns(123)
196
- @kicker.expects(:growl).with(Kicker::GROWL_NOTIFICATIONS[:change], 'Kicker: Change occured', 'Executing command')
197
- @kicker.expects(:growl).with(Kicker::GROWL_NOTIFICATIONS[:failed], 'Kicker: Command failed (123)', "line 1\nline 2").yields
198
- @kicker.send(:execute!)
199
- end
200
-
201
- it "should send the Growl messages with a click callback which executes the specified growl command when succeeded" do
202
- @kicker.stubs(:log)
203
-
204
- @kicker.stubs(:`).returns("line 1\nline 2")
205
- @kicker.use_growl = true
206
- @kicker.growl_command = 'ls -l'
207
-
208
- @kicker.expects(:system).with('ls -l').times(1)
209
- OSX::NSWorkspace.sharedWorkspace.expects(:launchApplication).with('Terminal').times(1)
210
-
211
- @kicker.expects(:growl).with(Kicker::GROWL_NOTIFICATIONS[:change], 'Kicker: Change occured', 'Executing command')
212
- @kicker.expects(:growl).with(Kicker::GROWL_NOTIFICATIONS[:succeeded], 'Kicker: Command succeeded', "line 1\nline 2").yields
213
- @kicker.send(:execute!)
214
-
215
- @kicker.stubs(:last_command_succeeded?).returns(false)
216
- @kicker.stubs(:last_command_status).returns(123)
217
- @kicker.expects(:growl).with(Kicker::GROWL_NOTIFICATIONS[:change], 'Kicker: Change occured', 'Executing command')
218
- @kicker.expects(:growl).with(Kicker::GROWL_NOTIFICATIONS[:failed], 'Kicker: Command failed (123)', "line 1\nline 2").yields
219
- @kicker.send(:execute!)
220
- end
221
- end