kicker 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,73 @@
1
+ require File.expand_path('../../test_helper', __FILE__)
2
+
3
+ before = Kicker.process_chain.dup
4
+ require 'kicker/recipes/rails'
5
+ RAILS = (Kicker.process_chain - before).first
6
+
7
+ describe "The Rails helper module" do
8
+ it "should return all functional tests" do
9
+ Dir.expects(:glob).with("test/functional/**/*_test.rb").returns(%w{ test.rb })
10
+ Rails.all_functional_tests.should == %w{ test.rb }
11
+ end
12
+ end
13
+
14
+ describe "The rails handler" do
15
+ before do
16
+ @files = %w{ Rakefile }
17
+ end
18
+
19
+ it "should match any test case files" do
20
+ should_match %w{ test/1_test.rb test/namespace/2_test.rb },
21
+ %w{ test/1_test.rb test/namespace/2_test.rb }
22
+ end
23
+
24
+ it "should map model files to test/unit" do
25
+ should_match %w{ app/models/member.rb app/models/article.rb },
26
+ %w{ test/unit/member_test.rb test/unit/article_test.rb }
27
+ end
28
+
29
+ it "should map concern files to test/unit/concerns" do
30
+ should_match %w{ app/concerns/authenticate.rb app/concerns/nested_resource.rb },
31
+ %w{ test/unit/concerns/authenticate_test.rb test/unit/concerns/nested_resource_test.rb }
32
+ end
33
+
34
+ it "should map helper files to test/unit/helpers" do
35
+ should_match %w{ app/helpers/members_helper.rb app/helpers/articles_helper.rb },
36
+ %w{ test/unit/helpers/members_helper_test.rb test/unit/helpers/articles_helper_test.rb }
37
+ end
38
+
39
+ it "should map controller files to test/functional" do
40
+ should_match %w{ app/controllers/application_controller.rb app/controllers/members_controller.rb },
41
+ %w{ test/functional/application_controller_test.rb test/functional/members_controller_test.rb }
42
+ end
43
+
44
+ it "should map view templates to test/functional" do
45
+ should_match %w{ app/views/members/index.html.erb app/views/admin/articles/show.html.erb },
46
+ %w{ test/functional/members_controller_test.rb test/functional/admin/articles_controller_test.rb }
47
+ end
48
+
49
+ it "should run all functional tests when config/routes.rb is saved" do
50
+ tests = %w{ test/functional/members_controller_test.rb test/functional/admin/articles_controller_test.rb }
51
+ Rails.expects(:all_functional_tests).returns(tests)
52
+ should_match %w{ config/routes.rb }, tests
53
+ end
54
+
55
+ it "should map lib files to test/lib" do
56
+ should_match %w{ lib/money.rb lib/views/date.rb },
57
+ %w{ test/lib/money_test.rb test/lib/views/date_test.rb }
58
+ end
59
+
60
+ private
61
+
62
+ def should_match(files, tests)
63
+ @files += files
64
+
65
+ tests.each do |test|
66
+ File.stubs(:exist?).with(test).returns(true)
67
+ end
68
+
69
+ Kicker::Utils.expects(:run_ruby_tests).with(tests)
70
+ RAILS.call(@files)
71
+ @files.should == %w{ Rakefile }
72
+ end
73
+ end
@@ -0,0 +1,6 @@
1
+ require 'rubygems'
2
+ require 'test/spec'
3
+ require 'mocha'
4
+
5
+ $:.unshift File.expand_path('../../lib', __FILE__)
6
+ require 'kicker'
@@ -0,0 +1,123 @@
1
+ require File.expand_path('../test_helper', __FILE__)
2
+
3
+ describe "A Kicker instance, concerning its utility methods" do
4
+ before do
5
+ Kicker.stubs(:growl)
6
+ utils.stubs(:last_command_succeeded?).returns(true)
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
+ utils.expects(:puts).with("[#{now}] the message")
14
+ utils.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
+ utils.stubs(:`).returns("line 1\nline 2")
19
+
20
+ utils.expects(:log).with('Change occured, executing command: ls')
21
+ utils.expects(:log).with(' line 1')
22
+ utils.expects(:log).with(' line 2')
23
+ utils.expects(:log).with('Command succeeded')
24
+ utils.execute('ls')
25
+
26
+ utils.stubs(:last_command_succeeded?).returns(false)
27
+ utils.stubs(:last_command_status).returns(123)
28
+ utils.expects(:log).with('Change occured, executing command: ls')
29
+ utils.expects(:log).with(' line 1')
30
+ utils.expects(:log).with(' line 2')
31
+ utils.expects(:log).with('Command failed (123)')
32
+ utils.execute('ls')
33
+ end
34
+
35
+ it "should send the Growl messages with the default click callback" do
36
+ utils.stubs(:log)
37
+
38
+ utils.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:', 'ls')
44
+ Kicker.expects(:growl).with(Kicker::GROWL_NOTIFICATIONS[:succeeded], 'Kicker: Command succeeded', "line 1\nline 2").yields
45
+ utils.execute('ls')
46
+
47
+ utils.stubs(:last_command_succeeded?).returns(false)
48
+ utils.stubs(:last_command_status).returns(123)
49
+ Kicker.expects(:growl).with(Kicker::GROWL_NOTIFICATIONS[:change], 'Kicker: Change occured, executing command:', 'ls')
50
+ Kicker.expects(:growl).with(Kicker::GROWL_NOTIFICATIONS[:failed], 'Kicker: Command failed (123)', "line 1\nline 2").yields
51
+ utils.execute('ls')
52
+ end
53
+
54
+ it "should send the Growl messages with a click callback which executes the specified growl command when succeeded" do
55
+ utils.stubs(:log)
56
+
57
+ utils.stubs(:`).returns("line 1\nline 2")
58
+ Kicker.use_growl = true
59
+ Kicker.growl_command = 'ls -l'
60
+
61
+ utils.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:', 'ls')
65
+ Kicker.expects(:growl).with(Kicker::GROWL_NOTIFICATIONS[:succeeded], 'Kicker: Command succeeded', "line 1\nline 2").yields
66
+ utils.execute('ls')
67
+
68
+ utils.stubs(:last_command_succeeded?).returns(false)
69
+ utils.stubs(:last_command_status).returns(123)
70
+ Kicker.expects(:growl).with(Kicker::GROWL_NOTIFICATIONS[:change], 'Kicker: Change occured, executing command:', 'ls')
71
+ Kicker.expects(:growl).with(Kicker::GROWL_NOTIFICATIONS[:failed], 'Kicker: Command failed (123)', "line 1\nline 2").yields
72
+ utils.execute('ls')
73
+ end
74
+
75
+ it "should store the last executed command" do
76
+ utils.stubs(:log)
77
+ utils.execute('date')
78
+ utils.last_command.should == 'date'
79
+ end
80
+
81
+ private
82
+
83
+ def utils
84
+ Kicker::Utils
85
+ end
86
+ end
87
+
88
+ describe "Kernel utility methods" do
89
+ before do
90
+ utils.stubs(:last_command_succeeded?).returns(true)
91
+ end
92
+
93
+ it "should forward log calls to the Kicker::Utils module" do
94
+ utils.expects(:log).with('the message')
95
+ log 'the message'
96
+ end
97
+
98
+ it "should forward execute calls to the Kicker::Utils module" do
99
+ utils.expects(:execute).with('ls')
100
+ execute 'ls'
101
+ end
102
+
103
+ it "should return the last_command" do
104
+ utils.stubs(:last_command).returns('abcde')
105
+ last_command.should == 'abcde'
106
+ end
107
+
108
+ it "should call execute with the appropriate command to execute Ruby tests" do
109
+ utils.expects(:execute).with("ruby -r test/1.rb -r test/2.rb -e ''")
110
+ run_ruby_tests %w{ test/1.rb test/2.rb }
111
+ end
112
+
113
+ it "should not execute anything if an empty array is given to run_ruby_tests" do
114
+ utils.expects(:execute).never
115
+ run_ruby_tests []
116
+ end
117
+
118
+ private
119
+
120
+ def utils
121
+ Kicker::Utils
122
+ end
123
+ end
@@ -0,0 +1,170 @@
1
+ require 'osx/cocoa'
2
+
3
+ module Growl
4
+ class Notifier < OSX::NSObject
5
+ VERSION = '1.0.2'
6
+
7
+ GROWL_IS_READY = "Lend Me Some Sugar; I Am Your Neighbor!"
8
+ GROWL_NOTIFICATION_CLICKED = "GrowlClicked!"
9
+ GROWL_NOTIFICATION_TIMED_OUT = "GrowlTimedOut!"
10
+ GROWL_KEY_CLICKED_CONTEXT = "ClickedContext"
11
+
12
+ PRIORITIES = {
13
+ :emergency => 2,
14
+ :high => 1,
15
+ :normal => 0,
16
+ :moderate => -1,
17
+ :very_low => -2,
18
+ }
19
+
20
+ class << self
21
+ # Returns the singleton instance of Growl::Notifier with which you register and send your Growl notifications.
22
+ def sharedInstance
23
+ @sharedInstance ||= alloc.init
24
+ end
25
+ end
26
+
27
+ attr_reader :application_name, :application_icon, :notifications, :default_notifications
28
+ attr_accessor :delegate
29
+
30
+ # Set to +true+ if you want to receive delegate callback messages,
31
+ # <tt>growlNotifierClicked_context</tt> & <tt>growlNotifierTimedOut_context</tt>,
32
+ # without the need to specify a <tt>:click_context</tt>.
33
+ #
34
+ # The default is +false+, which means your application won't receive any delegate
35
+ # callback messages if the <tt>:click_context</tt> is omitted.
36
+ attr_accessor :always_callback
37
+
38
+ # Registers the applications metadata and the notifications, that your application might send, to Growl.
39
+ # The +default_notifications+ are notifications that will be enabled by default, the regular +notifications+ are
40
+ # optional and should be enabled by the user in the Growl system preferences.
41
+ #
42
+ # Register the applications name and the notifications that will be used.
43
+ # * +default_notifications+ defaults to the regular +notifications+.
44
+ # * +application_icon+ defaults to OSX::NSApplication.sharedApplication.applicationIconImage.
45
+ #
46
+ # Growl::Notifier.sharedInstance.register 'FoodApp', ['YourHamburgerIsReady', 'OhSomeoneElseAteIt']
47
+ #
48
+ # Register the applications name, the notifications plus the default notifications that will be used and the icon that's to be used in the Growl notifications.
49
+ #
50
+ # Growl::Notifier.sharedInstance.register 'FoodApp', ['YourHamburgerIsReady', 'OhSomeoneElseAteIt'], ['DefaultNotification], OSX::NSImage.imageNamed('GreasyHamburger')
51
+ def register(application_name, notifications, default_notifications = nil, application_icon = nil)
52
+ @application_name, @application_icon = application_name, (application_icon || OSX::NSApplication.sharedApplication.applicationIconImage)
53
+ @notifications, @default_notifications = notifications, (default_notifications || notifications)
54
+ @callbacks = {}
55
+ send_registration!
56
+ end
57
+
58
+ # Sends a Growl notification.
59
+ #
60
+ # * +notification_name+ : the name of one of the notifcations that your apllication registered with Growl. See register for more info.
61
+ # * +title+ : the title that should be used in the Growl notification.
62
+ # * +description+ : the body of the Grow notification.
63
+ # * +options+ : specifies a few optional options:
64
+ # * <tt>:sticky</tt> : indicates if the Grow notification should "stick" to the screen. Defaults to +false+.
65
+ # * <tt>:priority</tt> : sets the priority level of the Growl notification. Defaults to 0.
66
+ # * <tt>:click_context</tt> : a string describing the context of the notification. This is send back to the delegate so you can check what kind of notification it was. If omitted, no delegate messages will be send. You can disable this behaviour by setting always_callback to +true+.
67
+ # * <tt>:icon</tt> : specifies the icon to be used in the Growl notification. Defaults to the registered +application_icon+, see register for more info.
68
+ #
69
+ # Simple example:
70
+ #
71
+ # name = 'YourHamburgerIsReady'
72
+ # title = 'Your hamburger is ready for consumption!'
73
+ # description = 'Please pick it up at isle 4.'
74
+ #
75
+ # Growl::Notifier.sharedInstance.notify(name, title, description)
76
+ #
77
+ # Example with optional options:
78
+ #
79
+ # Growl::Notifier.sharedInstance.notify(name, title, description, :sticky => true, :priority => 1, :icon => OSX::NSImage.imageNamed('SuperBigHamburger'))
80
+ #
81
+ # When you pass notify a block, that block will be used as the callback handler if the Growl notification was clicked. Eg:
82
+ #
83
+ # Growl::Notifier.sharedInstance.notify(name, title, description, :sticky => true) do
84
+ # user_clicked_notification_so_do_something!
85
+ # end
86
+ def notify(notification_name, title, description, options = {}, &callback)
87
+ dict = {
88
+ :ApplicationName => @application_name,
89
+ :ApplicationPID => pid,
90
+ :NotificationName => notification_name,
91
+ :NotificationTitle => title,
92
+ :NotificationDescription => description,
93
+ :NotificationPriority => PRIORITIES[options[:priority]] || options[:priority] || 0
94
+ }
95
+ dict[:NotificationIcon] = options[:icon].TIFFRepresentation if options[:icon]
96
+ dict[:NotificationSticky] = 1 if options[:sticky]
97
+
98
+ context = {}
99
+ context[:user_click_context] = options[:click_context] if options[:click_context]
100
+ if block_given?
101
+ @callbacks[callback.object_id] = callback
102
+ context[:callback_object_id] = callback.object_id.to_s
103
+ end
104
+ dict[:NotificationClickContext] = context if always_callback || !context.empty?
105
+
106
+ notification_center.postNotificationName_object_userInfo_deliverImmediately(:GrowlNotification, nil, dict, true)
107
+ end
108
+
109
+ def onReady(notification)
110
+ send_registration!
111
+ end
112
+
113
+ def onClicked(notification)
114
+ user_context = nil
115
+ if context = notification.userInfo[GROWL_KEY_CLICKED_CONTEXT]
116
+ user_context = context[:user_click_context]
117
+ if callback_object_id = context[:callback_object_id]
118
+ @callbacks.delete(callback_object_id.to_i).call
119
+ end
120
+ end
121
+
122
+ @delegate.growlNotifierClicked_context(self, user_context) if @delegate && @delegate.respond_to?(:growlNotifierClicked_context)
123
+ end
124
+
125
+ def onTimeout(notification)
126
+ user_context = nil
127
+ if context = notification.userInfo[GROWL_KEY_CLICKED_CONTEXT]
128
+ @callbacks.delete(context[:callback_object_id].to_i) if context[:callback_object_id]
129
+ user_context = context[:user_click_context]
130
+ end
131
+
132
+ @delegate.growlNotifierTimedOut_context(self, user_context) if @delegate && @delegate.respond_to?(:growlNotifierTimedOut_context)
133
+ end
134
+
135
+ private
136
+
137
+ def pid
138
+ OSX::NSProcessInfo.processInfo.processIdentifier.to_i
139
+ end
140
+
141
+ def notification_center
142
+ OSX::NSDistributedNotificationCenter.defaultCenter
143
+ end
144
+
145
+ def send_registration!
146
+ add_observer 'onReady:', GROWL_IS_READY, false
147
+ add_observer 'onClicked:', GROWL_NOTIFICATION_CLICKED, true
148
+ add_observer 'onTimeout:', GROWL_NOTIFICATION_TIMED_OUT, true
149
+
150
+ dict = {
151
+ :ApplicationName => @application_name,
152
+ :ApplicationIcon => application_icon.TIFFRepresentation,
153
+ :AllNotifications => @notifications,
154
+ :DefaultNotifications => @default_notifications
155
+ }
156
+
157
+ notification_center.objc_send(
158
+ :postNotificationName, :GrowlApplicationRegistrationNotification,
159
+ :object, nil,
160
+ :userInfo, dict,
161
+ :deliverImmediately, true
162
+ )
163
+ end
164
+
165
+ def add_observer(selector, name, prepend_name_and_pid)
166
+ name = "#{@application_name}-#{pid}-#{name}" if prepend_name_and_pid
167
+ notification_center.addObserver_selector_name_object self, selector, name, nil
168
+ end
169
+ end
170
+ end
@@ -0,0 +1,25 @@
1
+ require File.expand_path('../growl', __FILE__)
2
+
3
+ # Defines a few convenience methods that you can use in your class if you include the Growl module.
4
+ # Eg:
5
+ #
6
+ # class FoodReporter < OSX::NSObject
7
+ # include Growl
8
+ #
9
+ # def hamburger_time!
10
+ # growl 'YourHamburgerIsReady', 'Your hamburger is ready for consumption!', 'Please pick it up at isle 4.', :priority => 1 do
11
+ # throw_it_away_before_user_reaches_counter!
12
+ # end
13
+ # end
14
+ # end
15
+ module Growl
16
+ # Sends a Growl notification. See Growl::Notifier#notify for more info.
17
+ def growl(name, title, description, options = {}, &callback)
18
+ Growl::Notifier.sharedInstance.notify name, title, description, options, &callback
19
+ end
20
+
21
+ # Sends a sticky Growl notification. See Growl::Notifier#notify for more info.
22
+ def sticky_growl(name, title, description, options = {}, &callback)
23
+ growl name, title, description, options.merge!(:sticky => true), &callback
24
+ end
25
+ end
@@ -0,0 +1,136 @@
1
+ require 'osx/cocoa'
2
+ OSX.require_framework '/System/Library/Frameworks/CoreServices.framework/Frameworks/CarbonCore.framework'
3
+
4
+ module Rucola
5
+ class FSEvents
6
+ class FSEvent
7
+ attr_reader :fsevents_object
8
+ attr_reader :id
9
+ attr_reader :path
10
+ def initialize(fsevents_object, id, path)
11
+ @fsevents_object, @id, @path = fsevents_object, id, path
12
+ end
13
+
14
+ # Returns an array of the files/dirs in the path that the event occurred in.
15
+ # The files are sorted by the modification time, the first entry is the last modified file.
16
+ def files
17
+ Dir.glob("#{File.expand_path(path)}/*").map do |filename|
18
+ begin
19
+ [File.mtime(filename), filename]
20
+ rescue Errno::ENOENT
21
+ nil
22
+ end
23
+ end.compact.sort.reverse.map { |mtime, filename| filename }
24
+ end
25
+
26
+ # Returns the last modified file in the path that the event occurred in.
27
+ def last_modified_file
28
+ files.first
29
+ end
30
+ end
31
+
32
+ class StreamError < StandardError; end
33
+
34
+ attr_reader :paths
35
+ attr_reader :stream
36
+
37
+ attr_accessor :allocator
38
+ attr_accessor :context
39
+ attr_accessor :since
40
+ attr_accessor :latency
41
+ attr_accessor :flags
42
+
43
+ # Initializes a new FSEvents `watchdog` object and starts watching the directories you specify for events. The
44
+ # block is used as a handler for events, which are passed as the block's argument. This method is the easiest
45
+ # way to start watching some directories if you don't care about the details of setting up the event stream.
46
+ #
47
+ # Rucola::FSEvents.start_watching('/tmp') do |events|
48
+ # events.each { |event| log.debug("#{event.files.inspect} were changed.") }
49
+ # end
50
+ #
51
+ # Rucola::FSEvents.start_watching('/var/log/system.log', '/var/log/secure.log', :since => last_id, :latency => 5) do
52
+ # Growl.notify("Something was added to your log files!")
53
+ # end
54
+ #
55
+ # Note that the method also returns the FSEvents object. This enables you to control the event stream if you want to.
56
+ #
57
+ # fsevents = Rucola::FSEvents.start_watching('/Volumes') do |events|
58
+ # events.each { |event| Growl.notify("Volume changes: #{event.files.to_sentence}") }
59
+ # end
60
+ # fsevents.stop
61
+ def self.start_watching(*params, &block)
62
+ fsevents = new(*params, &block)
63
+ fsevents.create_stream
64
+ fsevents.start
65
+ fsevents
66
+ end
67
+
68
+ # Creates a new FSEvents `watchdog` object. You can specify a list of paths to watch and options to control the
69
+ # behaviour of the watchdog. The block you pass serves as a callback when an event is generated on one of the
70
+ # specified paths.
71
+ #
72
+ # fsevents = FSEvents.new('/etc/passwd') { Mailer.send_mail("Someone touched the password file!") }
73
+ # fsevents.create_stream
74
+ # fsevents.start
75
+ #
76
+ # fsevents = FSEvents.new('/home/upload', :since => UploadWatcher.last_event_id) do |events|
77
+ # events.each do |event|
78
+ # UploadWatcher.last_event_id = event.id
79
+ # event.files.each do |file|
80
+ # UploadWatcher.logfile.append("#{file} was changed")
81
+ # end
82
+ # end
83
+ # end
84
+ #
85
+ # *:since: The service will report events that have happened after the supplied event ID. Never use 0 because that
86
+ # will cause every fsevent since the "beginning of time" to be reported. Use OSX::KFSEventStreamEventIdSinceNow
87
+ # if you want to receive events that have happened after this call. (Default: OSX::KFSEventStreamEventIdSinceNow).
88
+ # You can find the ID's passed with :since in the events passed to your block.
89
+ # *:latency: Number of seconds to wait until an FSEvent is reported, this allows the service to bundle events. (Default: 0.0)
90
+ #
91
+ # Please refer to the Cocoa documentation for the rest of the options.
92
+ def initialize(*params, &block)
93
+ raise ArgumentError, 'No callback block was specified.' unless block_given?
94
+
95
+ options = params.last.kind_of?(Hash) ? params.pop : {}
96
+ @paths = params.flatten
97
+
98
+ paths.each { |path| raise ArgumentError, "The specified path (#{path}) does not exist." unless File.exist?(path) }
99
+
100
+ @allocator = options[:allocator] || OSX::KCFAllocatorDefault
101
+ @context = options[:context] || nil
102
+ @since = options[:since] || OSX::KFSEventStreamEventIdSinceNow
103
+ @latency = options[:latency] || 0.0
104
+ @flags = options[:flags] || 0
105
+ @stream = options[:stream] || nil
106
+
107
+ @user_callback = block
108
+ @callback = Proc.new do |stream, client_callback_info, number_of_events, paths_pointer, event_flags, event_ids|
109
+ paths_pointer.regard_as('*')
110
+ events = []
111
+ number_of_events.times {|i| events << Rucola::FSEvents::FSEvent.new(self, event_ids[i], paths_pointer[i]) }
112
+ @user_callback.call(events)
113
+ end
114
+ end
115
+
116
+ # Create the stream.
117
+ # Raises a Rucola::FSEvents::StreamError if the stream could not be created.
118
+ def create_stream
119
+ @stream = OSX.FSEventStreamCreate(@allocator, @callback, @context, @paths, @since, @latency, @flags)
120
+ raise(StreamError, 'Unable to create FSEvents stream.') unless @stream
121
+ OSX.FSEventStreamScheduleWithRunLoop(@stream, OSX.CFRunLoopGetCurrent, OSX::KCFRunLoopDefaultMode)
122
+ end
123
+
124
+ # Start the stream.
125
+ # Raises a Rucola::FSEvents::StreamError if the stream could not be started.
126
+ def start
127
+ raise(StreamError, 'Unable to start FSEvents stream.') unless OSX.FSEventStreamStart(@stream)
128
+ end
129
+
130
+ # Stop the stream.
131
+ # You can resume it by calling `start` again.
132
+ def stop
133
+ OSX.FSEventStreamStop(@stream)
134
+ end
135
+ end
136
+ end