Add support for Growl Notification Transport Protocol.

This commit is contained in:
Michael Kessler 2011-10-12 20:54:57 +02:00
parent 83def5004f
commit 3b0e2ad305
4 changed files with 390 additions and 208 deletions

View File

@ -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 File

@ -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 File

@ -15,9 +15,13 @@ module Guard
# Application name as shown in the specific notification settings
APPLICATION_NAME = "Guard"
class << self
attr_accessor :growl_library, :gntp
# Turn notifications off.
#
def self.turn_off
def turn_off
ENV["GUARD_NOTIFY"] = 'false'
end
@ -26,7 +30,7 @@ module Guard
#
# @return [Boolean] whether the notification could be enabled.
#
def self.turn_on
def turn_on
ENV["GUARD_NOTIFY"] = 'true'
case RbConfig::CONFIG['target_os']
when /darwin/i
@ -46,7 +50,7 @@ module Guard
# @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 = {})
def notify(message, options = { })
if enabled?
image = options.delete(:image) || :success
title = options.delete(:title) || "Guard"
@ -66,7 +70,7 @@ module Guard
#
# @return [Boolean] whether the notifications are available
#
def self.enabled?
def enabled?
ENV["GUARD_NOTIFY"] == 'true'
end
@ -79,20 +83,33 @@ module Guard
# @param [Symbol, String] the image to user
# @param [Hash] options the growl options
#
def self.notify_mac(title, message, image, options = {})
def 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)
notification = { :title => title, :icon => image_path(image) }.merge(options)
if defined?(GrowlNotify)
default_options[:description] = message
default_options[:application_name] = APPLICATION_NAME
default_options.delete(:name)
case self.growl_library
when :growl_notify
notification.delete(:name)
GrowlNotify.send_notification(default_options) if enabled?
else
Growl.notify message, default_options.merge(options) if enabled?
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
@ -103,10 +120,11 @@ module Guard
# @param [Symbol, String] the image to user
# @param [Hash] options the libnotify options
#
def self.notify_linux(title, message, image, options = {})
def 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?
notification = { :body => message, :summary => title, :icon_path => image_path(image), :transient => true }
Libnotify.show notification.merge(options)
end
# Send a message to notifu.
@ -116,10 +134,11 @@ module Guard
# @param [Symbol, String] the image to user
# @param [Hash] options the notifu options
#
def self.notify_windows(title, message, image, options = {})
def 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?
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.
@ -133,7 +152,7 @@ module Guard
# @param [Symbol] image the image name
# @return [String] the image path
#
def self.image_path(image)
def image_path(image)
images_path = Pathname.new(File.dirname(__FILE__)).join('../../images')
case image
when :failed
@ -153,7 +172,7 @@ module Guard
# @param [Symbol] image the image
# @return [Symbol] the level
#
def self.image_level(image)
def image_level(image)
case image
when :failed
:error
@ -166,35 +185,90 @@ module Guard
end
end
# Try to safely load growl and turns notifications
# off on load failure.
# 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:
#
def self.require_growl
begin
# - [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
# 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'
begin
if GrowlNotify.application_name != APPLICATION_NAME
GrowlNotify.config do |c|
c.notifications = c.default_notifications = [ APPLICATION_NAME ]
c.notifications = c.default_notifications = [APPLICATION_NAME]
c.application_name = c.notifications.first
end
end
rescue LoadError
require 'growl'
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'
:growl
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
def require_libnotify
require 'libnotify'
rescue LoadError
turn_off
UI.info "Please install libnotify gem for Linux notification support and add it to your Gemfile"
@ -203,12 +277,14 @@ module Guard
# Try to safely load rb-notifu and turns notifications
# off on load failure.
#
def self.require_rbnotifu
def 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
end
end
end

View File

@ -27,15 +27,6 @@ describe Guard::Notifier do
end
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 ''
::Guard::UI.should_receive(:info)
described_class.should_receive(:require).with('growl_notify').and_return true
described_class.turn_on
described_class.should_not be_enabled
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 ''
@ -43,26 +34,60 @@ describe Guard::Notifier do
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 ''
::Guard::UI.should_receive(:info)
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_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, :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 @@ describe Guard::Notifier do
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 @@ describe Guard::Notifier do
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 @@ describe Guard::Notifier do
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 @@ describe Guard::Notifier do
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 @@ describe Guard::Notifier do
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 @@ describe Guard::Notifier do
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 @@ describe Guard::Notifier do
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 @@ describe Guard::Notifier do
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 @@ describe Guard::Notifier do
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