Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

Add support for Growl Notification Transport Protocol.

  • Loading branch information...
commit 3b0e2ad30520bb8f3a0a92429c646c903c972e28 1 parent 83def50
@netzpirat netzpirat authored
Showing with 372 additions and 190 deletions.
  1. +1 −0  CHANGELOG.md
  2. +22 −10 README.md
  3. +244 −168 lib/guard/notifier.rb
  4. +105 −12 spec/guard/notifier_spec.rb
View
1  CHANGELOG.md
@@ -2,6 +2,7 @@
### Improvements
+- Add support for Growl Notification Transport Protocol. ([@netzpirat][])
- [#157](https://github.com/guard/guard/pull/157): Allow any return from the Guard watchers. ([@earlonrails][])
- [#156](https://github.com/guard/guard/pull/156): Log error and diagnostic messages to STDERR. ([@sunaku][])
- [#152](https://github.com/guard/guard/pull/152): Growl Notify API update for a graceful fail. ([@scottdavis][])
View
32 README.md
@@ -51,26 +51,38 @@ Install the rb-fsevent gem for [FSEvent](http://en.wikipedia.org/wiki/FSEvents)
$ gem install rb-fsevent
-You have two possibilities:
+You have three possibilities for getting Growl support:
-Use the [growl_notify gem](https://rubygems.org/gems/growl_notify) (recommended, compatible with Growl >= 1.3):
+Use the [growl_notify gem](https://rubygems.org/gems/growl_notify):
$ gem install growl_notify
-Use the [growlnotify](http://growl.info/extras.php#growlnotify) (cli tool for Growl <= 1.2) + the [growl gem](https://rubygems.org/gems/growl).
+The `growl_notify` gem is compatible with Growl >= 1.3 and uses AppleScript to send Growl notifications.
+The gem needs a native C extension to make use of AppleScript and does not run on JRuby and MacRuby.
+
+Use the [ruby_gntp gem](https://github.com/snaka/ruby_gntp):
+
+ $ gem install ruby_gntp
+
+The `ruby_gntp` gem is compatible with Growl >= 0.7 and uses the Growl Notification Transport Protocol to send Growl
+notifications. Guard supports multiple notification channels for customizing each notification type, but it's limited
+to the local host currently.
+
+Use the [growl gem](https://rubygems.org/gems/growl):
- $ brew install growlnotify
$ gem install growl
-And add them to your Gemfile:
+The `growl` gem is compatible with all versions of Growl and uses a command line tool [growlnotify](http://growl.info/extras.php#growlnotify)
+that must be separately downloaded and installed. You can alsi install it with HomeBrew:
+
+ $ brew install growlnotify
+
+Finally you have to add your Growl library of choice to your Gemfile:
gem 'rb-fsevent'
- gem 'growl_notify' # or gem 'growl'
+ gem 'growl_notify' # or gem 'ruby_gntp' or gem 'growl'
-The difference between growl and growl_notify is that growl_notify uses AppleScript to
-display a message, whereas growl uses the `growlnotify` command. In general the AppleScript
-approach is preferred, but you may also use the older growl gem. Have a look at the
-[Guard Wiki](https://github.com/guard/guard/wiki/Use-growl_notify-or-growl-gem) for more information.
+Have a look at the [Guard Wiki](https://github.com/guard/guard/wiki/Which-Growl-library-should-I-use) for more information.
### On Linux
View
412 lib/guard/notifier.rb
@@ -15,200 +15,276 @@ module Notifier
# Application name as shown in the specific notification settings
APPLICATION_NAME = "Guard"
- # Turn notifications off.
- #
- def self.turn_off
- ENV["GUARD_NOTIFY"] = 'false'
- end
+ class << self
- # Turn notifications on. This tries to load the platform
- # specific notification library.
- #
- # @return [Boolean] whether the notification could be enabled.
- #
- def self.turn_on
- ENV["GUARD_NOTIFY"] = 'true'
- case RbConfig::CONFIG['target_os']
- when /darwin/i
- require_growl
- when /linux/i
- require_libnotify
- when /mswin|mingw/i
- require_rbnotifu
- end
- end
+ attr_accessor :growl_library, :gntp
- # Show a message with the system notification.
- #
- # @see .image_path
- #
- # @param [String] the message to show
- # @option options [Symbol, String] image the image symbol or path to an image
- # @option options [String] title the notification title
- #
- def self.notify(message, options = {})
- if enabled?
- image = options.delete(:image) || :success
- title = options.delete(:title) || "Guard"
+ # Turn notifications off.
+ #
+ def turn_off
+ ENV["GUARD_NOTIFY"] = 'false'
+ end
+ # Turn notifications on. This tries to load the platform
+ # specific notification library.
+ #
+ # @return [Boolean] whether the notification could be enabled.
+ #
+ def turn_on
+ ENV["GUARD_NOTIFY"] = 'true'
case RbConfig::CONFIG['target_os']
- when /darwin/i
- notify_mac(title, message, image, options)
- when /linux/i
- notify_linux(title, message, image, options)
- when /mswin|mingw/i
- notify_windows(title, message, image, options)
+ when /darwin/i
+ require_growl
+ when /linux/i
+ require_libnotify
+ when /mswin|mingw/i
+ require_rbnotifu
end
end
- end
- # Test if the notifications are enabled and available.
- #
- # @return [Boolean] whether the notifications are available
- #
- def self.enabled?
- ENV["GUARD_NOTIFY"] == 'true'
- end
+ # Show a message with the system notification.
+ #
+ # @see .image_path
+ #
+ # @param [String] the message to show
+ # @option options [Symbol, String] image the image symbol or path to an image
+ # @option options [String] title the notification title
+ #
+ def notify(message, options = { })
+ if enabled?
+ image = options.delete(:image) || :success
+ title = options.delete(:title) || "Guard"
- private
-
- # Send a message to Growl either with the `growl` gem or the `growl_notify` gem.
- #
- # @param [String] title the notification title
- # @param [String] message the message to show
- # @param [Symbol, String] the image to user
- # @param [Hash] options the growl options
- #
- def self.notify_mac(title, message, image, options = {})
- require_growl # need for guard-rspec formatter that is called out of guard scope
-
- default_options = { :title => title, :icon => image_path(image), :name => APPLICATION_NAME }
- default_options.merge!(options)
-
- if defined?(GrowlNotify)
- default_options[:description] = message
- default_options[:application_name] = APPLICATION_NAME
- default_options.delete(:name)
-
- GrowlNotify.send_notification(default_options) if enabled?
- else
- Growl.notify message, default_options.merge(options) if enabled?
+ case RbConfig::CONFIG['target_os']
+ when /darwin/i
+ notify_mac(title, message, image, options)
+ when /linux/i
+ notify_linux(title, message, image, options)
+ when /mswin|mingw/i
+ notify_windows(title, message, image, options)
+ end
+ end
end
- end
- # Send a message to libnotify.
- #
- # @param [String] title the notification title
- # @param [String] message the message to show
- # @param [Symbol, String] the image to user
- # @param [Hash] options the libnotify options
- #
- def self.notify_linux(title, message, image, options = {})
- require_libnotify # need for guard-rspec formatter that is called out of guard scope
- default_options = { :body => message, :summary => title, :icon_path => image_path(image), :transient => true }
- Libnotify.show default_options.merge(options) if enabled?
- end
+ # Test if the notifications are enabled and available.
+ #
+ # @return [Boolean] whether the notifications are available
+ #
+ def enabled?
+ ENV["GUARD_NOTIFY"] == 'true'
+ end
- # Send a message to notifu.
- #
- # @param [String] title the notification title
- # @param [String] message the message to show
- # @param [Symbol, String] the image to user
- # @param [Hash] options the notifu options
- #
- def self.notify_windows(title, message, image, options = {})
- require_rbnotifu # need for guard-rspec formatter that is called out of guard scope
- default_options = { :message => message, :title => title, :type => image_level(image), :time => 3 }
- Notifu.show default_options.merge(options) if enabled?
- end
+ private
+
+ # Send a message to Growl either with the `growl` gem or the `growl_notify` gem.
+ #
+ # @param [String] title the notification title
+ # @param [String] message the message to show
+ # @param [Symbol, String] the image to user
+ # @param [Hash] options the growl options
+ #
+ def notify_mac(title, message, image, options = { })
+ require_growl # need for guard-rspec formatter that is called out of guard scope
+
+ notification = { :title => title, :icon => image_path(image) }.merge(options)
+
+ case self.growl_library
+ when :growl_notify
+ notification.delete(:name)
- # Get the image path for an image symbol.
- #
- # Known symbols are:
- #
- # - failed
- # - pending
- # - success
- #
- # @param [Symbol] image the image name
- # @return [String] the image path
- #
- def self.image_path(image)
- images_path = Pathname.new(File.dirname(__FILE__)).join('../../images')
- case image
- when :failed
- images_path.join("failed.png").to_s
- when :pending
- images_path.join("pending.png").to_s
- when :success
- images_path.join("success.png").to_s
- else
- # path given
- image
+ GrowlNotify.send_notification({
+ :description => message,
+ :application_name => APPLICATION_NAME
+ }.merge(notification))
+
+ when :ruby_gntp
+ icon = "file://#{ notification.delete(:icon) }"
+
+ self.gntp.notify({
+ :name => [:pending, :success, :failed].include?(image) ? image.to_s : 'notify',
+ :text => message,
+ :icon => icon
+ }.merge(notification))
+
+ when :growl
+ Growl.notify(message, {
+ :name => APPLICATION_NAME
+ }.merge(notification))
+ end
end
- end
- # The notification level type for the given image.
- #
- # @param [Symbol] image the image
- # @return [Symbol] the level
- #
- def self.image_level(image)
- case image
- when :failed
- :error
- when :pending
- :warn
- when :success
- :info
- else
- :info
+ # Send a message to libnotify.
+ #
+ # @param [String] title the notification title
+ # @param [String] message the message to show
+ # @param [Symbol, String] the image to user
+ # @param [Hash] options the libnotify options
+ #
+ def notify_linux(title, message, image, options = { })
+ require_libnotify # need for guard-rspec formatter that is called out of guard scope
+
+ notification = { :body => message, :summary => title, :icon_path => image_path(image), :transient => true }
+ Libnotify.show notification.merge(options)
+ end
+
+ # Send a message to notifu.
+ #
+ # @param [String] title the notification title
+ # @param [String] message the message to show
+ # @param [Symbol, String] the image to user
+ # @param [Hash] options the notifu options
+ #
+ def notify_windows(title, message, image, options = { })
+ require_rbnotifu # need for guard-rspec formatter that is called out of guard scope
+
+ notification = { :message => message, :title => title, :type => image_level(image), :time => 3 }
+ Notifu.show notification.merge(options)
+ end
+
+ # Get the image path for an image symbol.
+ #
+ # Known symbols are:
+ #
+ # - failed
+ # - pending
+ # - success
+ #
+ # @param [Symbol] image the image name
+ # @return [String] the image path
+ #
+ def image_path(image)
+ images_path = Pathname.new(File.dirname(__FILE__)).join('../../images')
+ case image
+ when :failed
+ images_path.join("failed.png").to_s
+ when :pending
+ images_path.join("pending.png").to_s
+ when :success
+ images_path.join("success.png").to_s
+ else
+ # path given
+ image
+ end
+ end
+
+ # The notification level type for the given image.
+ #
+ # @param [Symbol] image the image
+ # @return [Symbol] the level
+ #
+ def image_level(image)
+ case image
+ when :failed
+ :error
+ when :pending
+ :warn
+ when :success
+ :info
+ else
+ :info
+ end
+ end
+
+ # Try to safely load growl and turns notifications off on load failure.
+ # The Guard notifier knows three different library to handle sending
+ # Growl messages and tries to loading them in the given order:
+ #
+ # - [Growl Notify](https://github.com/scottdavis/growl_notify)
+ # - [Ruby GNTP](https://github.com/snaka/ruby_gntp)
+ # - [Growl](https://github.com/visionmedia/growl)
+ #
+ # On successful loading of any of the libraries, the active library name is
+ # accessible through `.growl_library`.
+ #
+ def require_growl
+ self.growl_library = try_growl_notify || try_ruby_gntp || try_growl
+
+ unless self.growl_library
+ turn_off
+ UI.info "Please install growl_notify or growl gem for Mac OS X notification support and add it to your Gemfile"
+ end
end
- end
- # Try to safely load growl and turns notifications
- # off on load failure.
- #
- def self.require_growl
- begin
+ # Try to load the `growl_notify` gem.
+ #
+ # @return [Symbol, nil] A symbol with the name of the loaded library
+ #
+ def try_growl_notify
require 'growl_notify'
- if GrowlNotify.application_name != APPLICATION_NAME
- GrowlNotify.config do |c|
- c.notifications = c.default_notifications = [ APPLICATION_NAME ]
- c.application_name = c.notifications.first
+ begin
+ if GrowlNotify.application_name != APPLICATION_NAME
+ GrowlNotify.config do |c|
+ c.notifications = c.default_notifications = [APPLICATION_NAME]
+ c.application_name = c.notifications.first
+ end
end
+
+ rescue ::GrowlNotify::GrowlNotFound
+ turn_off
+ UI.info "Please install Growl from http://growl.info"
end
+
+ :growl_notify
+
rescue LoadError
+ end
+
+ # Try to load the `ruby_gntp` gem and register the available
+ # notification channels.
+ #
+ # @return [Symbol, nil] A symbol with the name of the loaded library
+ #
+ def try_ruby_gntp
+ require 'ruby_gntp'
+
+ self.gntp = GNTP.new(APPLICATION_NAME)
+ self.gntp.register(:notifications => [
+ { :name => 'notify', :enabled => true },
+ { :name => 'failed', :enabled => true },
+ { :name => 'pending', :enabled => true },
+ { :name => 'success', :enabled => true }
+ ])
+
+ :ruby_gntp
+
+ rescue LoadError
+ end
+
+ # Try to load the `growl_notify` gem.
+ #
+ # @return [Symbol, nil] A symbol with the name of the loaded library
+ #
+ def try_growl
require 'growl'
- rescue ::GrowlNotify::GrowlNotFound
+
+ :growl
+
+ rescue LoadError
+ end
+
+ # Try to safely load libnotify and turns notifications
+ # off on load failure.
+ #
+ def require_libnotify
+ require 'libnotify'
+
+ rescue LoadError
turn_off
- UI.info "Please install Growl from http://growl.info"
+ UI.info "Please install libnotify gem for Linux notification support and add it to your Gemfile"
end
- rescue LoadError
- turn_off
- UI.info "Please install growl_notify or growl gem for Mac OS X notification support and add it to your Gemfile"
- end
- # Try to safely load libnotify and turns notifications
- # off on load failure.
- #
- def self.require_libnotify
- require 'libnotify'
- rescue LoadError
- turn_off
- UI.info "Please install libnotify gem for Linux notification support and add it to your Gemfile"
- end
+ # Try to safely load rb-notifu and turns notifications
+ # off on load failure.
+ #
+ def require_rbnotifu
+ require 'rb-notifu'
- # Try to safely load rb-notifu and turns notifications
- # off on load failure.
- #
- def self.require_rbnotifu
- require 'rb-notifu'
- rescue LoadError
- turn_off
- UI.info "Please install rb-notifu gem for Windows notification support and add it to your Gemfile"
- end
+ rescue LoadError
+ turn_off
+ UI.info "Please install rb-notifu gem for Windows notification support and add it to your Gemfile"
+ end
+ end
end
end
View
117 spec/guard/notifier_spec.rb
@@ -26,7 +26,14 @@ class GrowlNotFound < Exception; end
def self.config ; end
end
end
-
+
+ it "loads the library and enables the notifications" do
+ described_class.should_receive(:require).with('growl_notify').and_return true
+ GrowlNotify.should_receive(:application_name).and_return ''
+ described_class.turn_on
+ described_class.should be_enabled
+ end
+
it "should respond properly to a GrowlNotify exception" do
::GrowlNotify.should_receive(:config).and_raise ::GrowlNotify::GrowlNotFound
::GrowlNotify.should_receive(:application_name).and_return ''
@@ -34,35 +41,53 @@ def self.config ; end
described_class.should_receive(:require).with('growl_notify').and_return true
described_class.turn_on
described_class.should_not be_enabled
+ described_class.growl_library.should eql :growl_notify
+ end
+
+ after do
+ Object.send(:remove_const, :GrowlNotify)
+ end
+ end
+
+ context "with the GNTP library available" do
+ before do
+ class ::GNTP
+ def register(config) ; end
+ end
end
it "loads the library and enables the notifications" do
- described_class.should_receive(:require).with('growl_notify').and_return true
- GrowlNotify.should_receive(:application_name).and_return ''
+ described_class.should_receive(:require).with('growl_notify').and_raise LoadError
+ described_class.should_receive(:require).with('ruby_gntp').and_return true
described_class.turn_on
described_class.should be_enabled
+ described_class.growl_library.should eql :ruby_gntp
end
after do
- Object.send(:remove_const, :GrowlNotify)
+ Object.send(:remove_const, :GNTP)
end
end
context "with the Growl library available" do
it "loads the library and enables the notifications" do
described_class.should_receive(:require).with('growl_notify').and_raise LoadError
+ described_class.should_receive(:require).with('ruby_gntp').and_raise LoadError
described_class.should_receive(:require).with('growl').and_return true
described_class.turn_on
described_class.should be_enabled
+ described_class.growl_library.should eql :growl
end
end
- context "without the Growl library available" do
+ context "without a Growl library available" do
it "disables the notifications" do
described_class.should_receive(:require).with('growl_notify').and_raise LoadError
+ described_class.should_receive(:require).with('ruby_gntp').and_raise LoadError
described_class.should_receive(:require).with('growl').and_raise LoadError
described_class.turn_on
described_class.should_not be_enabled
+ described_class.growl_library.should be nil
end
end
end
@@ -117,7 +142,7 @@ def self.config ; end
context "on Mac OS" do
before do
- RbConfig::CONFIG.should_receive(:[]).with('target_os').and_return 'darwin'
+ RbConfig::CONFIG.stub(:[]).and_return 'darwin'
described_class.stub(:require_growl)
end
@@ -125,6 +150,7 @@ def self.config ; end
before do
Object.send(:remove_const, :Growl) if defined?(Growl)
Growl = Object.new
+ described_class.growl_library = :growl
end
after do
@@ -142,7 +168,8 @@ def self.config ; end
it "don't passes the notification to Growl if library is not available" do
Growl.should_not_receive(:notify)
- described_class.should_receive(:enabled?).and_return(true, false)
+ described_class.growl_library = nil
+ described_class.should_receive(:enabled?).and_return(false)
described_class.notify 'great', :title => 'Guard'
end
@@ -170,6 +197,7 @@ def self.config ; end
before do
Object.send(:remove_const, :GrowlNotify) if defined?(GrowlNotify)
GrowlNotify = Object.new
+ described_class.growl_library = :growl_notify
end
after do
@@ -188,7 +216,8 @@ def self.config ; end
it "don't passes the notification to Growl if library is not available" do
GrowlNotify.should_not_receive(:send_notification)
- described_class.should_receive(:enabled?).and_return(true, false)
+ described_class.growl_library = nil
+ described_class.should_receive(:enabled?).and_return(false)
described_class.notify 'great', :title => 'Guard'
end
@@ -213,11 +242,75 @@ def self.config ; end
described_class.notify 'great', :title => 'Guard', :name => "Guard-Cucumber"
end
end
+
+ context 'with ruby_gntp gem' do
+ before do
+ described_class.growl_library = :ruby_gntp
+ end
+
+ it "passes a success notification to Ruby GNTP" do
+ described_class.gntp.should_receive(:notify).with(
+ :name => "success",
+ :text => 'great',
+ :title => "Guard",
+ :icon => 'file://' + Pathname.new(File.dirname(__FILE__)).join('../../images/success.png').to_s
+ )
+ described_class.notify 'great', :title => 'Guard'
+ end
+
+ it "passes a pending notification to Ruby GNTP" do
+ described_class.gntp.should_receive(:notify).with(
+ :name => "pending",
+ :text => 'great',
+ :title => "Guard",
+ :icon => 'file://' + Pathname.new(File.dirname(__FILE__)).join('../../images/pending.png').to_s
+ )
+ described_class.notify 'great', :title => 'Guard', :image => :pending
+ end
+
+ it "passes a failure notification to Ruby GNTP" do
+ described_class.gntp.should_receive(:notify).with(
+ :name => "failed",
+ :text => 'great',
+ :title => "Guard",
+ :icon => 'file://' + Pathname.new(File.dirname(__FILE__)).join('../../images/failed.png').to_s
+ )
+ described_class.notify 'great', :title => 'Guard', :image => :failed
+ end
+
+ it "passes a general notification to Ruby GNTP" do
+ described_class.gntp.should_receive(:notify).with(
+ :name => "notify",
+ :text => 'great',
+ :title => "Guard",
+ :icon => 'file:///path/to/custom.png'
+ )
+ described_class.notify 'great', :title => 'Guard', :image => '/path/to/custom.png'
+ end
+
+ it "don't passes the notification to Ruby GNTP if library is not available" do
+ described_class.gntp.should_not_receive(:notify)
+ described_class.growl_library = nil
+ described_class.should_receive(:enabled?).and_return(false)
+ described_class.notify 'great', :title => 'Guard'
+ end
+
+ it "allows additional notification options" do
+ described_class.gntp.should_receive(:notify).with(
+ :name => "success",
+ :text => 'great',
+ :title => "Guard",
+ :icon => 'file://' + Pathname.new(File.dirname(__FILE__)).join('../../images/success.png').to_s,
+ :sticky => true
+ )
+ described_class.notify 'great', :title => 'Guard', :sticky => true
+ end
+ end
end
context "on Linux" do
before do
- RbConfig::CONFIG.should_receive(:[]).with('target_os').and_return 'linux'
+ RbConfig::CONFIG.stub(:[]).and_return 'linux'
described_class.stub(:require_libnotify)
Object.send(:remove_const, :Libnotify) if defined?(Libnotify)
Libnotify = Object.new
@@ -239,7 +332,7 @@ def self.config ; end
it "don't passes the notification to Libnotify if library is not available" do
Libnotify.should_not_receive(:show)
- described_class.should_receive(:enabled?).and_return(true, false)
+ described_class.should_receive(:enabled?).and_return(false)
described_class.notify 'great', :title => 'Guard'
end
@@ -267,7 +360,7 @@ def self.config ; end
context "on Windows" do
before do
- RbConfig::CONFIG.should_receive(:[]).with('target_os').and_return 'mswin'
+ RbConfig::CONFIG.stub(:[]).and_return 'mswin'
described_class.stub(:require_rbnotifu)
Object.send(:remove_const, :Notifu) if defined?(Notifu)
Notifu = Object.new
@@ -289,7 +382,7 @@ def self.config ; end
it "don't passes the notification to rb-notifu if library is not available" do
Notifu.should_not_receive(:show)
- described_class.should_receive(:enabled?).and_return(true, false)
+ described_class.should_receive(:enabled?).and_return(false)
described_class.notify 'great', :title => 'Guard'
end
Please sign in to comment.
Something went wrong with that request. Please try again.