Skip to content

Commit

Permalink
Merge e333e2f into 3279ee2
Browse files Browse the repository at this point in the history
  • Loading branch information
e2 committed Dec 27, 2014
2 parents 3279ee2 + e333e2f commit 7ae30b0
Show file tree
Hide file tree
Showing 34 changed files with 135 additions and 4,016 deletions.
1 change: 1 addition & 0 deletions guard.gemspec
Expand Up @@ -23,6 +23,7 @@ Gem::Specification.new do |s|
s.add_runtime_dependency "formatador", ">= 0.2.4"
s.add_runtime_dependency "nenv", "~> 0.1"
s.add_runtime_dependency "shellany", "~> 0.0"
s.add_runtime_dependency "notiffany", "~> 0.0"

s.files = `git ls-files -z`.split("\x0").select do |f|
/^(?:bin|images|lib)\/.*$/ =~ f
Expand Down
5 changes: 2 additions & 3 deletions lib/guard/dsl.rb
Expand Up @@ -72,9 +72,8 @@ class Error < RuntimeError
#
# @see Guard::Notifier for available notifier and its options.
#
def notification(notifier, options = {})
# TODO: remove dependency on Notifier (let session handle this)
Notifier.add(notifier.to_sym, options.merge(silent: false))
def notification(notifier, opts = {})
Guard.state.session.guardfile_notification = { notifier.to_sym => opts }
end

# Sets the interactor options or disable the interactor.
Expand Down
28 changes: 8 additions & 20 deletions lib/guard/dsl_describer.rb
Expand Up @@ -107,22 +107,20 @@ def show
# @see CLI#show
#
def notifiers
supported = ::Guard::Notifier::SUPPORTED
Notifier.connect(notify: false)
detected = Notifier.notifiers
supported = Notifier.supported
Notifier.connect(notify: true, silent: true)
detected = Notifier.detected
Notifier.disconnect

merged_notifiers = supported.inject(:merge)
final_rows = merged_notifiers.each_with_object([]) do |definition, rows|
detected_names = detected.map { |item| item[:name] }

final_rows = supported.each_with_object([]) do |(name, _), rows|
available = detected_names.include?(name) ? "✔" : "✘"

name = definition[0]
clazz = definition[1]
available = clazz.available?(silent: true) ? "✔" : "✘"
notifier = detected.detect { |n| n[:name] == name }
used = notifier ? "✔" : "✘"

options = _merge_options(clazz, notifier)
options.delete(:silent)
options = notifier ? notifier[:options] : {}

if options.empty?
rows << :split
Expand All @@ -149,16 +147,6 @@ def notifiers

private

def _merge_options(klass, notifier)
notify_options = notifier ? notifier[:options] : {}

if klass.const_defined?(:DEFAULTS)
klass.const_get(:DEFAULTS).merge(notify_options)
else
notify_options
end
end

def _add_row(rows, name, available, used, option, value)
rows << {
Name: name,
Expand Down
14 changes: 13 additions & 1 deletion lib/guard/internals/session.rb
Expand Up @@ -68,6 +68,8 @@ def initialize(new_options)
@guardfile_group_scope = []
@guardfile_ignore = []
@guardfile_ignore_bang = []

@guardfile_notifier_options = {}
end

def guardfile_scope(scope)
Expand Down Expand Up @@ -142,7 +144,17 @@ def evaluator_options
end

def notify_options
{ notify: @options[:notify] }
names = @guardfile_notifier_options.keys
return { notify: false } if names.include?(:off)

{
notify: @options[:notify],
notifiers: @guardfile_notifier_options
}
end

def guardfile_notification=(config)
@guardfile_notifier_options.merge!(config)
end

def interactor_name
Expand Down
261 changes: 42 additions & 219 deletions lib/guard/notifier.rb
@@ -1,246 +1,69 @@
require "yaml"
require "rbconfig"
require "pathname"
require "nenv"

require_relative "notifier/detected"

require_relative "ui"
require "notiffany/notifier"
require "guard/ui"

module Guard
# The notifier handles sending messages to different notifiers. Currently the
# following libraries are supported:
#
# * Ruby GNTP
# * Growl
# * Libnotify
# * rb-notifu
# * emacs
# * Terminal Notifier
# * Terminal Title
# * Tmux
#
# Please see the documentation of each notifier for more information about
# the requirements
# and configuration possibilities.
#
# Guard knows four different notification types:
#
# * success
# * pending
# * failed
# * notify
#
# The notification type selection is based on the image option that is
# sent to {#notify}. Each image type has its own notification type, and
# notifications with custom images goes all sent as type `notify`. The
# `gntp` notifier is able to register these types
# at Growl and allows customization of each notification type.
#
# Guard can be configured to make use of more than one notifier at once.
#
# @see Guard::Dsl
#
# TODO: rename to plural
module Notifier
extend self

NOTIFICATIONS_DISABLED = "Notifications disabled by GUARD_NOTIFY" +
" environment variable"

USING_NOTIFIER = "Guard is using %s to send notifications."

ONLY_NOTIFY = "Only notify() is available from a child process"

DEPRECTED_IMPLICIT_CONNECT = "Calling Guard::Notifier.notify()" +
" without a prior Notifier.connect() is deprecated"

# List of available notifiers, grouped by functionality
SUPPORTED = [
{
gntp: GNTP,
growl: Growl,
terminal_notifier: TerminalNotifier,
libnotify: Libnotify,
notifysend: NotifySend,
notifu: Notifu
},
{ emacs: Emacs },
{ tmux: Tmux },
{ terminal_title: TerminalTitle },
{ file: FileNotifier }
]

Env = Nenv::Builder.build do
create_method(:notify?) { |data| data != "false" }
create_method(:notify_pid) { |data| data && Integer(data) }
create_method(:notify_pid=)
create_method(:notify_active?)
create_method(:notify_active=)
class Notifier
def self.connect(options = {})
@notifier ||= nil
fail "Already connected!" if @notifier
begin
opts = options.merge(namespace: "guard", logger: UI)
@notifier = Notiffany.connect(opts)
rescue Notiffany::Notifier::Detected::UnknownNotifier => e
UI.error "Failed to setup notification: #{e.message}"
fail
end
end

class NotServer < RuntimeError
def self.disconnect
@notifier.disconnect
@notifier = nil
end

def connect(options = {})
@detected = Detected.new(SUPPORTED)
return if _client?

_env.notify_pid = $$

fail "Already connected" if active?

return unless enabled? && options[:notify]

@detected.detect

turn_on
rescue Detected::NoneAvailableError => e
::Guard::UI.info e.to_s
end
DEPRECATED_IMPLICIT_CONNECT = "Calling Notiffany::Notifier.notify()" +
" without a prior Notifier.connect() is deprecated"

def disconnect
if _client?
@detected = nil
return
def self.notify(message, options)
unless @notifier
# TODO: reenable again?
# UI.deprecation(DEPRECTED_IMPLICIT_CONNECT)
connect(notify: true)
end

turn_off if active?
@detected.reset unless @detected.nil?
_env.notify_pid = nil
@detected = nil
@notifier.notify(message, options)
rescue RuntimeError => e
UI.error "Notification failed for #{notifier.name}: #{e.message}"
UI.debug e.backtrace.join("\n")
end

# Turn notifications on.
#
# @param [Hash] options the turn_on options
# @option options [Boolean] silent disable any logging
#
def turn_on(options = {})
_check_server!
return unless enabled?

fail "Already active!" if active?

silent = options[:silent]

@detected.available.each do |klass, _|
::Guard::UI.info(format(USING_NOTIFIER, klass.title)) unless silent
klass.turn_on if klass.respond_to?(:turn_on)
end

_env.notify_active = true
def self.turn_on
@notifier.turn_on
end

# Turn notifications off.
def turn_off
_check_server!

fail "Not active!" unless active?

@detected.available.each do |klass, _|
klass.turn_off if klass.respond_to?(:turn_off)
end

_env.notify_active = false
end

# Toggle the system notifications on/off
def toggle
unless enabled?
::Guard::UI.error NOTIFICATIONS_DISABLED
def self.toggle
unless @notifier.enabled?
UI.error NOTIFICATIONS_DISABLED
return
end

if active?
::Guard::UI.info "Turn off notifications"
turn_off
if @notifier.active?
UI.info "Turn off notifications"
@notifier.turn_off
return
end

turn_on
end

# Test if the notifications can be enabled based on ENV['GUARD_NOTIFY']
def enabled?
_env.notify?
@notifier.turn_on
end

# Test if notifiers are currently turned on
def active?
_env.notify_active?
end

# Add a notification library to be used.
#
# @param [Symbol] name the name of the notifier to use
# @param [Hash] options the notifier options
# @option options [String] silent disable any error message
# @return [Boolean] if the notification could be added
#
def add(name, options = {})
_check_server!

return false unless enabled?

if name == :off && active?
turn_off
return false
end

# ok to pass new instance when called without connect (e.g. evaluator)
(@detected || Detected.new(SUPPORTED)).add(name, options)
end

# TODO: deprecate/remove
alias :add_notifier :add

# Show a system notification with all configured notifiers.
#
# @param [String] message the message to show
# @option opts [Symbol, String] image the image symbol or path to an image
# @option opts [String] title the notification title
#
def notify(message, message_opts = {})
if _client?
# TODO: reenable again?
# UI.deprecation(DEPRECTED_IMPLICIT_CONNECT)
return unless enabled?
connect(notify: true)
else
return unless active?
end

@detected.available.each do |klass, options|
_notify(klass, options, message, message_opts)
end
def self.supported
Notiffany::Notifier::SUPPORTED.inject(:merge)
end

# Used by dsl describer
def notifiers
@detected.available.map { |mod, opts| { name: mod.name, options: opts } }
end

private

def _env
@environment ||= Env.new("guard")
end

def _check_server!
_client? && fail(NotServer, ONLY_NOTIFY)
end

def _client?
(pid = _env.notify_pid) && (pid != $$)
end

def _notify(klass, options, message, message_options)
notifier = klass.new(options)
notifier.notify(message, message_options.dup)
rescue RuntimeError => e
::Guard::UI.error "Notification failed for #{notifier.name}: #{e.message}"
::Guard::UI.debug e.backtrace.join("\n")
def self.detected
@notifier.available.map do |mod|
{ name: mod.name.to_sym, options: mod.options }
end
end
end
end

0 comments on commit 7ae30b0

Please sign in to comment.