notiffany-0.1.1/0000755000175000017500000000000013203053217011556 5ustar daidainotiffany-0.1.1/notiffany.gemspec0000644000175000017500000000517413203053217015127 0ustar daidai######################################################### # This file has been automatically generated by gem2tgz # ######################################################### # -*- encoding: utf-8 -*- # stub: notiffany 0.1.1 ruby lib Gem::Specification.new do |s| s.name = "notiffany".freeze s.version = "0.1.1" s.required_rubygems_version = Gem::Requirement.new(">= 0".freeze) if s.respond_to? :required_rubygems_version= s.require_paths = ["lib".freeze] s.authors = ["Cezary Baginski".freeze, "R\u{e9}my Coutable".freeze, "Thibaud Guillaume-Gentil".freeze] s.date = "2016-07-29" s.description = " Wrapper libray for most popular notification\n libraries such as Growl, Libnotify, Notifu\n".freeze s.email = ["cezary@chronomantic.net".freeze] s.files = ["LICENSE.txt".freeze, "README.md".freeze, "images/failed.png".freeze, "images/pending.png".freeze, "images/success.png".freeze, "lib/notiffany.rb".freeze, "lib/notiffany/notifier.rb".freeze, "lib/notiffany/notifier/base.rb".freeze, "lib/notiffany/notifier/config.rb".freeze, "lib/notiffany/notifier/detected.rb".freeze, "lib/notiffany/notifier/emacs.rb".freeze, "lib/notiffany/notifier/emacs/client.rb".freeze, "lib/notiffany/notifier/file.rb".freeze, "lib/notiffany/notifier/gntp.rb".freeze, "lib/notiffany/notifier/growl.rb".freeze, "lib/notiffany/notifier/libnotify.rb".freeze, "lib/notiffany/notifier/notifysend.rb".freeze, "lib/notiffany/notifier/rb_notifu.rb".freeze, "lib/notiffany/notifier/terminal_notifier.rb".freeze, "lib/notiffany/notifier/terminal_title.rb".freeze, "lib/notiffany/notifier/tmux.rb".freeze, "lib/notiffany/notifier/tmux/client.rb".freeze, "lib/notiffany/notifier/tmux/notification.rb".freeze, "lib/notiffany/notifier/tmux/session.rb".freeze, "lib/notiffany/version.rb".freeze] s.homepage = "https://github.com/guard/notiffany".freeze s.licenses = ["MIT".freeze] s.rubygems_version = "2.5.2.1".freeze s.summary = "Notifier library (extracted from Guard project)".freeze if s.respond_to? :specification_version then s.specification_version = 4 if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then s.add_development_dependency(%q.freeze, ["~> 1.7"]) s.add_runtime_dependency(%q.freeze, ["~> 0.1"]) s.add_runtime_dependency(%q.freeze, ["~> 0.0"]) else s.add_dependency(%q.freeze, ["~> 1.7"]) s.add_dependency(%q.freeze, ["~> 0.1"]) s.add_dependency(%q.freeze, ["~> 0.0"]) end else s.add_dependency(%q.freeze, ["~> 1.7"]) s.add_dependency(%q.freeze, ["~> 0.1"]) s.add_dependency(%q.freeze, ["~> 0.0"]) end end notiffany-0.1.1/lib/0000755000175000017500000000000013203053217012324 5ustar daidainotiffany-0.1.1/lib/notiffany/0000755000175000017500000000000013203053217014321 5ustar daidainotiffany-0.1.1/lib/notiffany/version.rb0000644000175000017500000000005113203053217016327 0ustar daidaimodule Notiffany VERSION = "0.1.1" end notiffany-0.1.1/lib/notiffany/notifier/0000755000175000017500000000000013203053217016140 5ustar daidainotiffany-0.1.1/lib/notiffany/notifier/tmux/0000755000175000017500000000000013203053217017135 5ustar daidainotiffany-0.1.1/lib/notiffany/notifier/tmux/session.rb0000644000175000017500000000241013203053217021142 0ustar daidaimodule Notiffany class Notifier class Tmux < Base # Preserves TMux settings for all tmux sessions class Session def initialize @options_store = {} # NOTE: we are reading the settings of all clients # - regardless of the :display_on_all_clients option # Ideally, this should be done incrementally (e.g. if we start with # "current" client and then override the :display_on_all_clients to # true, only then the option store should be updated to contain # settings of all clients Client.new(:all).clients.each do |client| @options_store[client] = { "status-left-bg" => nil, "status-right-bg" => nil, "status-left-fg" => nil, "status-right-fg" => nil, "message-bg" => nil, "message-fg" => nil, "display-time" => nil }.merge(Client.new(client).parse_options) end end def close @options_store.each do |client, options| options.each do |key, value| Client.new(client).unset(key, value) end end @options_store = nil end end end end end notiffany-0.1.1/lib/notiffany/notifier/tmux/notification.rb0000644000175000017500000000341513203053217022153 0ustar daidaimodule Notiffany class Notifier class Tmux < Base # Wraps a notification with it's options class Notification def initialize(type, options) @type = type @options = options @color = options[type.to_sym] || options[:default] @separator = options[:line_separator] @message_color = _value_for(:message_color) @client = Client.new(options[:display_on_all_clients] ? :all : nil) end def display_title(title, message) title_format = _value_for(:title_format) teaser_message = message.split("\n").first display_title = format(title_format, title, teaser_message) client.title = display_title end def display_message(title, message) message = _message_for(title, message) client.display_time = options[:timeout] * 1000 client.message_fg = message_color client.message_bg = color client.display_message(message) end def colorize(locations) locations.each do |location| client.set(location, color) end end private attr_reader :type attr_reader :options attr_reader :color attr_reader :message_color attr_reader :client attr_reader :separator def _value_for(field) format = "#{type}_#{field}".to_sym default = options["default_#{field}".to_sym] options.fetch(format, default) end def _message_for(title, message) message_format = _value_for(:message_format) formatted_message = message.split("\n").join(separator) format(message_format, title, formatted_message) end end end end end notiffany-0.1.1/lib/notiffany/notifier/tmux/client.rb0000644000175000017500000000472613203053217020751 0ustar daidairequire "shellany/sheller" module Notiffany class Notifier class Tmux < Base # Class for actually calling TMux to run commands class Client CLIENT = "tmux".freeze class << self def version Float(_capture("-V")[/\d+\.\d+/]) end def _capture(*args) Shellany::Sheller.stdout(([CLIENT] + args).join(" ")) end def _run(*args) Shellany::Sheller.run(([CLIENT] + args).join(" ")) end end def initialize(client) @client = client end def clients return [@client] unless @client == :all ttys = _capture("list-clients", "-F", "'\#{client_tty}'") ttys = ttys.split(/\n/) # if user is running 'tmux -C' remove this client from list ttys.delete("(null)") ttys end def set(key, value) clients.each do |client| args = client ? ["-t", client.strip] : nil _run("set", "-q", *args, key, value) end end def display_message(message) clients.each do |client| args = ["-c", client.strip] if client # TODO: should properly escape message here _run("display", *args, "'#{message}'") end end def unset(key, value) clients.each do |client| _run(*_all_args_for(key, value, client)) end end def parse_options output = _capture("show", "-t", @client) Hash[output.lines.map { |line| _parse_option(line) }] end def message_fg=(color) set("message-fg", color) end def message_bg=(color) set("message-bg", color) end def display_time=(time) set("display-time", time) end def title=(string) # TODO: properly escape? set("set-titles-string", "'#{string}'") end private def _run(*args) self.class._run(*args) end def _capture(*args) self.class._capture(*args) end def _parse_option(line) line.partition(" ").map(&:strip).reject(&:empty?) end def _all_args_for(key, value, client) unset = value ? [] : %w(-u) args = client ? ["-t", client.strip] : [] ["set", "-q", *unset, *args, key, *[value].compact] end end end end end notiffany-0.1.1/lib/notiffany/notifier/tmux.rb0000644000175000017500000001034013203053217017460 0ustar daidairequire "notiffany/notifier/base" require "notiffany/notifier/tmux/client" require "notiffany/notifier/tmux/session" require "notiffany/notifier/tmux/notification" # TODO: this probably deserves a gem of it's own module Notiffany class Notifier # Changes the color of the Tmux status bar and optionally # shows messages in the status bar. class Tmux < Base @session = nil DEFAULTS = { tmux_environment: "TMUX", success: "green", failed: "red", pending: "yellow", default: "green", timeout: 5, display_message: false, default_message_format: "%s - %s", default_message_color: "white", display_on_all_clients: false, display_title: false, default_title_format: "%s - %s", line_separator: " - ", change_color: true, color_location: "status-left-bg" } class Error < RuntimeError end ERROR_NOT_INSIDE_TMUX = ":tmux notifier is only available inside a "\ "TMux session." ERROR_ANCIENT_TMUX = "Your tmux version is way too old (%s)!" # Notification starting, save the current Tmux settings # and quiet the Tmux output. # def turn_on self.class._start_session end # Notification stopping. Restore the previous Tmux state # if available (existing options are restored, new options # are unset) and unquiet the Tmux output. # def turn_off self.class._end_session end private def _gem_name nil end def _check_available(opts = {}) @session ||= nil # to avoid unitialized error fail "PREVIOUS TMUX SESSION NOT CLEARED!" if @session var_name = opts[:tmux_environment] fail Error, ERROR_NOT_INSIDE_TMUX unless ENV.key?(var_name) version = Client.version fail Error, format(ERROR_ANCIENT_TMUX, version) if version < 1.7 true rescue Error => e fail UnavailableError, e.message end # Shows a system notification. # # By default, the Tmux notifier only makes # use of a color based notification, changing the background color of the # `color_location` to the color defined in either the `success`, # `failed`, `pending` or `default`, depending on the notification type. # # You may enable an extra explicit message by setting `display_message` # to true, and may further disable the colorization by setting # `change_color` to false. # # @param [String] message the notification message # @param [Hash] options additional notification library options # @option options [String] type the notification type. Either 'success', # 'pending', 'failed' or 'notify' # @option options [String] message the notification message body # @option options [String] image the path to the notification image # @option options [Boolean] change_color whether to show a color # notification # @option options [String,Array] color_location the location where to draw # the color notification # @option options [Boolean] display_message whether to display a message # or not # @option options [Boolean] display_on_all_clients whether to display a # message on all tmux clients or not # def _perform_notify(message, options = {}) locations = Array(options[:color_location]) type = options[:type].to_s title = options[:title] tmux = Notification.new(type, options) tmux.colorize(locations) if options[:change_color] tmux.display_title(title, message) if options[:display_title] tmux.display_message(title, message) if options[:display_message] end class << self def _start_session fail "Already turned on!" if @session @session = Session.new end def _end_session fail "Already turned off!" unless @session @session.close @session = nil end def _session @session end end end end end notiffany-0.1.1/lib/notiffany/notifier/terminal_title.rb0000644000175000017500000000166513203053217021511 0ustar daidairequire "notiffany/notifier/base" module Notiffany class Notifier # Shows system notifications in the terminal title bar. # class TerminalTitle < Base DEFAULTS = {} # Clears the terminal title def turn_off STDOUT.puts "\e]2;\a" end private def _gem_name nil end def _check_available(_options) end # Shows a system notification. # # @param [Hash] opts additional notification library options # @option opts [String] message the notification message body # @option opts [String] type the notification type. Either 'success', # 'pending', 'failed' or 'notify' # @option opts [String] title the notification title # def _perform_notify(message, opts = {}) first_line = message.sub(/^\n/, "").sub(/\n.*/m, "") STDOUT.puts "\e]2;[#{opts[:title]}] #{first_line}\a" end end end end notiffany-0.1.1/lib/notiffany/notifier/terminal_notifier.rb0000644000175000017500000000344513203053217022205 0ustar daidairequire "notiffany/notifier/base" module Notiffany class Notifier # System notifications using the # # [terminal-notifier](https://github.com/Springest/terminal-notifier-guard) # # gem. # # This gem is available for OS X 10.8 Mountain Lion and sends notifications # to the OS X notification center. class TerminalNotifier < Base DEFAULTS = { app_name: "Notiffany" } ERROR_ONLY_OSX10 = "The :terminal_notifier only runs"\ " on Mac OS X 10.8 and later." def _supported_hosts %w(darwin) end def _gem_name "terminal-notifier-guard" end def _check_available(_opts = {}) return if ::TerminalNotifier::Guard.available? fail UnavailableError, ERROR_ONLY_OSX10 end # Shows a system notification. # # @param [String] message the notification message body # @param [Hash] opts additional notification library options # @option opts [String] type the notification type. Either 'success', # 'pending', 'failed' or 'notify' # @option opts [String] title the notification title # @option opts [String] image the path to the notification image (ignored) # @option opts [String] app_name name of your app # @option opts [String] execute a command # @option opts [String] activate an app bundle # @option opts [String] open some url or file # def _perform_notify(message, opts = {}) title = [opts[:app_name], opts[:type].downcase.capitalize].join(" ") opts = { title: title }.merge(opts) opts[:message] = message opts[:title] ||= title opts.delete(:image) opts.delete(:app_name) ::TerminalNotifier::Guard.execute(false, opts) end end end end notiffany-0.1.1/lib/notiffany/notifier/rb_notifu.rb0000644000175000017500000000525513203053217020463 0ustar daidairequire "notiffany/notifier/base" module Notiffany class Notifier # System notifications using the # [rb-notifu](https://github.com/stereobooster/rb-notifu) gem. # # This gem is available for Windows and sends system notifications to # [Notifu](http://www.paralint.com/projects/notifu/index.html): # # @example Add the `rb-notifu` gem to your `Gemfile` # group :development # gem 'rb-notifu' # end # class Notifu < Base # Default options for the rb-notifu notifications. DEFAULTS = { time: 3, icon: false, baloon: false, nosound: false, noquiet: false, xp: false } private def _supported_hosts %w(mswin mingw) end def _gem_name "rb-notifu" end def _check_available(_opts = {}) end # Shows a system notification. # # @param [String] message the notification message body # @param [Hash] opts additional notification library options # @option opts [String] type the notification type. Either 'success', # 'pending', 'failed' or 'notify' # @option opts [String] title the notification title # @option opts [String] image the path to the notification image # @option opts [Number] time the number of seconds to display (0 for # infinit) # @option opts [Boolean] icon specify an icon to use ("parent" uses the # icon of the parent process) # @option opts [Boolean] baloon enable ballon tips in the registry (for # this user only) # @option opts [Boolean] nosound do not play a sound when the tooltip is # displayed # @option opts [Boolean] noquiet show the tooltip even if the user is in # the quiet period that follows his very first login (Windows 7 and up) # @option opts [Boolean] xp use IUserNotification interface event when # IUserNotification2 is available # def _perform_notify(message, opts = {}) options = opts.dup options[:type] = _notifu_type(opts[:type]) options[:message] = message # The empty block is needed until # https://github.com/stereobooster/rb-notifu/pull/1 is merged ::Notifu.show(options) {} end # Converts generic notification type to the best matching # Notifu type. # # @param [String] type the generic notification type # @return [Symbol] the Notify notification type # def _notifu_type(type) case type.to_sym when :failed :error when :pending :warn else :info end end end end end notiffany-0.1.1/lib/notiffany/notifier/notifysend.rb0000644000175000017500000000560613203053217020656 0ustar daidairequire "notiffany/notifier/base" require "shellany/sheller" module Notiffany class Notifier # System notifications using notify-send, a binary that ships with # the libnotify-bin package on many Debian-based distributions. # # @example Add the `:notifysend` notifier to your `Guardfile` # notification :notifysend # class NotifySend < Base # Default options for the notify-send notifications. DEFAULTS = { t: 3000, # Default timeout is 3000ms h: "int:transient:1" # Automatically close the notification } # Full list of options supported by notify-send. SUPPORTED = [:u, :t, :i, :c, :h] private # notify-send has no gem, just a binary to shell out def _gem_name nil end def _supported_hosts %w(linux linux-gnu freebsd openbsd sunos solaris) end def _check_available(_opts = {}) which = Shellany::Sheller.stdout("which notify-send") return true unless which.nil? || which.empty? fail UnavailableError, "libnotify-bin package is not installed" end # Shows a system notification. # # @param [String] message the notification message body # @param [Hash] opts additional notification library options # @option opts [String] type the notification type. Either 'success', # 'pending', 'failed' or 'notify' # @option opts [String] title the notification title # @option opts [String] image the path to the notification image # @option opts [String] c the notification category # @option opts [Number] t the number of milliseconds to display (1000, # 3000) # def _perform_notify(message, opts = {}) command = [opts[:title], message] opts = opts.merge( i: opts[:i] || opts[:image], u: opts[:u] || _notifysend_urgency(opts[:type]) ) Shellany::Sheller. run("notify-send", *_to_arguments(command, SUPPORTED, opts)) end # Converts Guards notification type to the best matching # notify-send urgency. # # @param [String] type the Guard notification type # @return [String] the notify-send urgency # def _notifysend_urgency(type) { failed: "normal", pending: "low" }.fetch(type, "low") end # Builds a shell command out of a command string and option hash. # # @param [String] command the command execute # @param [Array] supported list of supported option flags # @param [Hash] opts additional command options # # @return [Array] the command and its options converted to a # shell command. # def _to_arguments(command, supported, opts = {}) opts.inject(command) do |cmd, (flag, value)| supported.include?(flag) ? (cmd << "-#{flag}" << value.to_s) : cmd end end end end end notiffany-0.1.1/lib/notiffany/notifier/libnotify.rb0000644000175000017500000000320713203053217020466 0ustar daidairequire "notiffany/notifier/base" module Notiffany class Notifier # System notifications using the # [libnotify](https://github.com/splattael/libnotify) gem. # # This gem is available for Linux, FreeBSD, OpenBSD and Solaris and sends # system notifications to # Gnome [libnotify](http://developer.gnome.org/libnotify): # class Libnotify < Base DEFAULTS = { transient: false, append: true, timeout: 3 } private def _supported_hosts %w(linux linux-gnu freebsd openbsd sunos solaris) end def _check_available(_opts = {}) end # Shows a system notification. # # @param [String] message the notification message body # @param [Hash] opts additional notification library options # @option opts [String] type the notification type. Either 'success', # 'pending', 'failed' or 'notify' # @option opts [String] title the notification title # @option opts [String] image the path to the notification image # @option opts [Boolean] transient keep the notifications around after # display # @option opts [Boolean] append append onto existing notification # @option opts [Number, Boolean] timeout the number of seconds to display # (1.5 (s), 1000 (ms), false) # def _perform_notify(message, opts = {}) opts = opts.merge( summary: opts[:title], icon_path: opts[:image], body: message, urgency: opts[:urgency] || (opts[:type] == "failed" ? :normal : :low) ) ::Libnotify.show(opts) end end end end notiffany-0.1.1/lib/notiffany/notifier/growl.rb0000644000175000017500000000567613203053217017635 0ustar daidairequire "notiffany/notifier/base" module Notiffany class Notifier # System notifications using the # [growl](https://github.com/visionmedia/growl) gem. # # This gem is available for OS X and sends system notifications to # [Growl](http://growl.info) through the # [GrowlNotify](http://growl.info/downloads) executable. # # The `growlnotify` executable must be installed manually or by using # [Homebrew](http://mxcl.github.com/homebrew/). # # Sending notifications with this notifier will not show the different # notifications in the Growl preferences. Use the :gntp notifier if you # want to customize each notification type in Growl. # # @example Install `growlnotify` with Homebrew # brew install growlnotify # # @example Add the `growl` gem to your `Gemfile` # group :development # gem 'growl' # end # # @example Add the `:growl` notifier to your `Guardfile` # notification :growl # # @example Add the `:growl_notify` notifier with configuration options to # your `Guardfile` notification :growl, sticky: true, host: '192.168.1.5', # password: 'secret' # class Growl < Base INSTALL_GROWLNOTIFY = "Please install the 'growlnotify' executable'\ ' (available by installing the 'growl' gem)." # Default options for the growl notifications. DEFAULTS = { sticky: false, priority: 0 } def _supported_hosts %w(darwin) end def _check_available(_opts = {}) fail UnavailableError, INSTALL_GROWLNOTIFY unless ::Growl.installed? end # Shows a system notification. # # The documented options are for GrowlNotify 1.3, but the older options # are also supported. Please see `growlnotify --help`. # # Priority can be one of the following named keys: `Very Low`, # `Moderate`, `Normal`, `High`, `Emergency`. It can also be an integer # between -2 and 2. # # @param [String] message the notification message body # @param [Hash] opts additional notification library options # @option opts [String] type the notification type. Either 'success', # 'pending', 'failed' or 'notify' # @option opts [String] title the notification title # @option opts [String] image the path to the notification image # @option opts [Boolean] sticky make the notification sticky # @option opts [String, Integer] priority specify an int or named key # (default is 0) # @option opts [String] host the hostname or IP address to which to # send a remote notification # @option opts [String] password the password used for remote # notifications # def _perform_notify(message, opts = {}) opts = { name: "Notiffany" }.merge(opts) opts.select! { |k, _| ::Growl::Base.switches.include?(k) } ::Growl.notify(message, opts) end end end end notiffany-0.1.1/lib/notiffany/notifier/gntp.rb0000644000175000017500000000540013203053217017434 0ustar daidairequire "notiffany/notifier/base" module Notiffany class Notifier # System notifications using the # [ruby_gntp](https://github.com/snaka/ruby_gntp) gem. # # This gem is available for OS X, Linux and Windows and sends system # notifications to the following system notification frameworks through the # # [Growl Network Transport # Protocol](http://www.growlforwindows.com/gfw/help/gntp.aspx): # # * [Growl](http://growl.info) # * [Growl for Windows](http://www.growlforwindows.com) # * [Growl for Linux](http://mattn.github.com/growl-for-linux) # * [Snarl](https://sites.google.com/site/snarlapp) class GNTP < Base DEFAULTS = { sticky: false } # Default options for the ruby gtnp client. CLIENT_DEFAULTS = { host: "127.0.0.1", password: "", port: 23_053 } def _supported_hosts %w(darwin linux linux-gnu freebsd openbsd sunos solaris mswin mingw cygwin) end def _gem_name "ruby_gntp" end def _check_available(_opts) end # Shows a system notification. # # @param [String] message the notification message body # @param [Hash] opts additional notification library options # @option opts [String] type the notification type. Either 'success', # 'pending', 'failed' or 'notify' # @option opts [String] title the notification title # @option opts [String] image the path to the notification image # @option opts [String] host the hostname or IP address to which to send # a remote notification # @option opts [String] password the password used for remote # notifications # @option opts [Integer] port the port to send a remote notification # @option opts [Boolean] sticky make the notification sticky # def _perform_notify(message, opts = {}) opts = { name: opts[:type].to_s, text: message, icon: opts[:image] }.merge(opts) _gntp_client(opts).notify(opts) end private def _gntp_client(opts = {}) @_client ||= begin gntp = ::GNTP.new( "Notiffany", opts.fetch(:host) { CLIENT_DEFAULTS[:host] }, opts.fetch(:password) { CLIENT_DEFAULTS[:password] }, opts.fetch(:port) { CLIENT_DEFAULTS[:port] } ) gntp.register( app_icon: _image_path(:guard), notifications: [ { name: "notify", enabled: true }, { name: "failed", enabled: true }, { name: "pending", enabled: true }, { name: "success", enabled: true } ] ) gntp end end end end end notiffany-0.1.1/lib/notiffany/notifier/file.rb0000644000175000017500000000263413203053217017411 0ustar daidairequire "notiffany/notifier/base" module Notiffany class Notifier # Writes notifications to a file. # class File < Base DEFAULTS = { format: "%s\n%s\n%s\n" } private # @param [Hash] opts some options # @option opts [Boolean] path the path to a file where notification # message will be written # def _check_available(opts = {}) fail UnavailableError, "No :path option given" unless opts[:path] end # Writes the notification to a file. By default it writes type, title, # and message separated by newlines. # # @param [String] message the notification message body # @param [Hash] opts additional notification library options # @option opts [String] type the notification type. Either 'success', # 'pending', 'failed' or 'notify' # @option opts [String] title the notification title # @option opts [String] image the path to the notification image # @option opts [String] format printf style format for file contents # @option opts [String] path the path of where to write the file # def _perform_notify(message, opts = {}) fail UnavailableError, "No :path option given" unless opts[:path] str = format(opts[:format], opts[:type], opts[:title], message) ::File.write(opts[:path], str) end def _gem_name nil end end end end notiffany-0.1.1/lib/notiffany/notifier/emacs/0000755000175000017500000000000013203053217017230 5ustar daidainotiffany-0.1.1/lib/notiffany/notifier/emacs/client.rb0000644000175000017500000000252113203053217021033 0ustar daidairequire 'erb' module Notiffany class Notifier class Emacs < Base # Handles evaluating ELISP code in Emacs via Erb class Client attr_reader :elisp_erb # Creates a safe binding with local variables for ERB class Elisp < ERB attr_reader :color attr_reader :bgcolor attr_reader :message def initialize(code, color, bgcolor, message) @color = color @bgcolor = bgcolor @message = message @code = code super(@code) end def result super(binding) end end def initialize(options) @client = options[:client] @elisp_erb = options[:elisp_erb] raise ArgumentError, 'No :elisp_erb option given!' unless elisp_erb end def available? script = Elisp.new(@elisp_erb, nil, nil, nil).result _emacs_eval({ 'ALTERNATE_EDITOR' => 'false' }, script) end def notify(color, bgcolor, message = nil) elisp = Elisp.new(elisp_erb, color, bgcolor, message).result _emacs_eval({ 'ALTERNATE_EDITOR' => 'false' }, elisp) end private def _emacs_eval(env, code) Shellany::Sheller.run(env, @client, '--eval', code) end end end end end notiffany-0.1.1/lib/notiffany/notifier/emacs.rb0000644000175000017500000000624613203053217017565 0ustar daidairequire 'notiffany/notifier/base' require 'shellany/sheller' require 'notiffany/notifier/emacs/client' module Notiffany class Notifier # Send a notification to Emacs with emacsclient # (http://www.emacswiki.org/emacs/EmacsClient). # class Emacs < Base DEFAULTS = { client: 'emacsclient', success: 'ForestGreen', failed: 'Firebrick', default: 'Black', fontcolor: 'White' }.freeze DEFAULT_ELISP_ERB = <" :foreground "<%= color %>") EOF private def _gem_name nil end def _check_available(options) return if Client.new(options.merge(elisp_erb: "'1'")).available? raise UnavailableError, 'Emacs client failed' end # Shows a system notification. # # @param [String] type the notification type. Either 'success', # 'pending', 'failed' or 'notify' # @param [String] title the notification title # @param [String] message the notification message body # @param [String] image the path to the notification image # @param [Hash] opts additional notification library options # @option opts [String] success the color to use for success # notifications (default is 'ForestGreen') # @option opts [String] failed the color to use for failure # notifications (default is 'Firebrick') # @option opts [String] pending the color to use for pending # notifications # @option opts [String] default the default color to use (default is # 'Black') # @option opts [String] client the client to use for notification # (default is 'emacsclient') # @option opts [String, Integer] priority specify an int or named key # (default is 0) # def _perform_notify(message, opts = {}) color = _emacs_color(opts[:type], opts) fontcolor = _emacs_color(:fontcolor, opts) opts = opts.merge(elisp_erb: _erb_for(opts[:elisp_file])) Client.new(opts).notify(fontcolor, color, message) end # Get the Emacs color for the notification type. # You can configure your own color by overwrite the defaults. # # @param [String] type the notification type # @param [Hash] options aditional notification options # # @option options [String] success the color to use for success # notifications (default is 'ForestGreen') # # @option options [String] failed the color to use for failure # notifications (default is 'Firebrick') # # @option options [String] pending the color to use for pending # notifications # # @option options [String] default the default color to use (default is # 'Black') # # @return [String] the name of the emacs color # def _emacs_color(type, options = {}) default = options.fetch(:default, DEFAULTS[:default]) options.fetch(type.to_sym, default) end def _erb_for(filename) return DEFAULT_ELISP_ERB unless filename IO.read(::File.expand_path(filename)) end end end end notiffany-0.1.1/lib/notiffany/notifier/detected.rb0000644000175000017500000000642413203053217020254 0ustar daidairequire "nenv" require "yaml" require_relative "emacs" require_relative "file" require_relative "gntp" require_relative "growl" require_relative "libnotify" require_relative "notifysend" require_relative "rb_notifu" require_relative "terminal_notifier" require_relative "terminal_title" require_relative "tmux" module Notiffany class Notifier # @private api # TODO: use a socket instead of passing env variables to child processes # (currently probably only used by guard-cucumber anyway) YamlEnvStorage = Nenv::Builder.build do create_method(:notifiers=) { |data| YAML.dump(data || []) } create_method(:notifiers) { |data| data ? YAML.load(data) : [] } end # @private api class Detected NO_SUPPORTED_NOTIFIERS = "Notiffany could not detect any of the"\ " supported notification libraries." class NoneAvailableError < RuntimeError end class UnknownNotifier < RuntimeError def initialize(name) super @name = name end attr_reader :name def message "Unknown notifier: #{@name.inspect}" end end def initialize(supported, env_namespace, logger) @supported = supported @environment = YamlEnvStorage.new(env_namespace) @logger = logger end def reset @environment.notifiers = [] end def detect return unless _notifiers.empty? @supported.each do |group| group.detect do |name, _| begin _add(name, {}) true rescue Notifier::Base::UnavailableError => e @logger.debug "Notiffany: #{name} not available (#{e.message})." false end end end fail NoneAvailableError, NO_SUPPORTED_NOTIFIERS if _notifiers.empty? end def available @available ||= _notifiers.map do |entry| _to_module(entry[:name]).new(entry[:options]) end end # Called when user has notifier-specific config. # Honor the config by warning if something is wrong def add(name, opts) _add(name, opts) rescue Notifier::Base::UnavailableError => e @logger.warning("Notiffany: #{name} not available (#{e.message}).") end private def _add(name, opts) @available = nil all = _notifiers # Silently skip if it's already available, because otherwise # we'd have to do :turn_off, then configure, then :turn_on names = all.map(&:first).map(&:last) unless names.include?(name) fail UnknownNotifier, name unless (klass = _to_module(name)) klass.new(opts) # raises if unavailable @environment.notifiers = all << { name: name, options: opts } end # Just overwrite the options (without turning the notifier off or on), # so those options will be passed in next calls to notify() all.each { |item| item[:options] = opts if item[:name] == name } end def _to_module(name) @supported.each do |group| next unless (notifier = group.detect { |n, _| n == name }) return notifier.last end nil end def _notifiers @environment.notifiers end end end end notiffany-0.1.1/lib/notiffany/notifier/config.rb0000644000175000017500000000132713203053217017735 0ustar daidairequire "logger" module Notiffany class Notifier # Configuration class for Notifier class Config DEFAULTS = { notify: true }.freeze attr_reader :env_namespace attr_reader :logger attr_reader :notifiers def initialize(opts) options = DEFAULTS.merge(opts) @env_namespace = opts.fetch(:namespace, "notiffany") @logger = _setup_logger(options) @notify = options[:notify] @notifiers = opts.fetch(:notifiers, {}) end def notify? @notify end private def _setup_logger(opts) opts.fetch(:logger) do Logger.new($stderr).tap { |l| l.level = Logger::WARN } end end end end end notiffany-0.1.1/lib/notiffany/notifier/base.rb0000644000175000017500000000575413203053217017412 0ustar daidairequire "rbconfig" module Notiffany class Notifier class Base HOSTS = { darwin: "Mac OS X", linux: "Linux", 'linux-gnu' => "Linux", freebsd: "FreeBSD", openbsd: "OpenBSD", sunos: "SunOS", solaris: "Solaris", mswin: "Windows", mingw: "Windows", cygwin: "Windows" } ERROR_ADD_GEM_AND_RUN_BUNDLE = "Please add \"gem '%s'\" to your Gemfile "\ "and run your app with \"bundle exec\"." class UnavailableError < RuntimeError def initialize(reason) super @reason = reason end def message @reason end end class RequireFailed < UnavailableError def initialize(gem_name) super ERROR_ADD_GEM_AND_RUN_BUNDLE % gem_name end end class UnsupportedPlatform < UnavailableError def initialize super "Unsupported platform #{RbConfig::CONFIG['host_os'].inspect}" end end attr_reader :options def initialize(opts = {}) options = opts.dup options.delete(:silent) @options = { title: "Notiffany" }. merge(self.class.const_get(:DEFAULTS)). merge(options).freeze @images_path = Pathname.new(__FILE__).dirname + "../../../images" _check_host_supported _require_gem _check_available(@options) end def title self.class.to_s[/.+::(\w+)$/, 1] end def name title.gsub(/([a-z])([A-Z])/, '\1_\2').downcase end def notify(message, opts = {}) new_opts = _notify_options(opts).freeze _perform_notify(message, new_opts) end def _image_path(image) images = [:failed, :pending, :success, :guard] images.include?(image) ? @images_path.join("#{image}.png").to_s : image end private # Override if necessary def _gem_name name end # Override if necessary def _supported_hosts :all end # Override def _check_available(_options) fail NotImplementedError end # Override def _perform_notify(_message, _opts) fail NotImplementedError end def _notification_type(image) [:failed, :pending, :success].include?(image) ? image : :notify end def _notify_options(overrides = {}) opts = @options.merge(overrides) img_type = opts.fetch(:image, :success) opts[:type] ||= _notification_type(img_type) opts[:image] = _image_path(img_type) opts end def _check_host_supported return if _supported_hosts == :all expr = /#{_supported_hosts * '|'}/ fail UnsupportedPlatform unless expr.match(RbConfig::CONFIG["host_os"]) end def _require_gem Kernel.require _gem_name unless _gem_name.nil? rescue LoadError, NameError fail RequireFailed, _gem_name end end end end notiffany-0.1.1/lib/notiffany/notifier.rb0000644000175000017500000001162113203053217016466 0ustar daidairequire "yaml" require "rbconfig" require "pathname" require "nenv" require "notiffany/notifier/detected" require "notiffany/notifier/config" module Notiffany # 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. # # Notiffany 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. # # Notiffany can be configured to make use of more than one notifier at once. # def self.connect(options = {}) Notifier.new(options) end class Notifier NOTIFICATIONS_DISABLED = "Notifications disabled by GUARD_NOTIFY" \ " environment variable" USING_NOTIFIER = "Notiffany is using %s to send notifications." ONLY_NOTIFY = "Only notify() is available from a child process" # 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: File } ] 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=) end class NotServer < RuntimeError end attr_reader :config def initialize(opts) @config = Config.new(opts) @detected = Detected.new(SUPPORTED, config.env_namespace, config.logger) return if _client? _activate rescue Detected::NoneAvailableError => e config.logger.info e.to_s end def disconnect if _client? @detected = nil return end turn_off if active? @detected.reset unless @detected.nil? _env.notify_pid = nil @detected = nil 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? _turn_on_notifiers(options) _env.notify_active = true end # Turn notifications off. def turn_off _check_server! fail "Not active!" unless active? @detected.available.each do |obj| obj.turn_off if obj.respond_to?(:turn_off) end _env.notify_active = false end # Test if the notifications can be enabled based on ENV['GUARD_NOTIFY'] def enabled? _env.notify? end # Test if notifiers are currently turned on def active? _env.notify_active? end # 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? return unless enabled? else return unless active? end @detected.available.each do |notifier| notifier.notify(message, message_opts.dup) end end def available @detected.available end private def _env @environment ||= Env.new(config.env_namespace) end def _check_server! _client? && fail(NotServer, ONLY_NOTIFY) end def _client? (pid = _env.notify_pid) && (pid != $$) end def _detect_or_add_notifiers notifiers = config.notifiers return @detected.detect if notifiers.empty? notifiers.each do |name, notifier_options| @detected.add(name, notifier_options) end end def _notification_wanted? enabled? && config.notify? end def _activate _env.notify_pid = $$ fail "Already connected" if active? return unless _notification_wanted? _detect_or_add_notifiers turn_on end def _turn_on_notifiers(options) silent = options[:silent] @detected.available.each do |obj| config.logger.debug(format(USING_NOTIFIER, obj.title)) unless silent obj.turn_on if obj.respond_to?(:turn_on) end end end end notiffany-0.1.1/lib/notiffany.rb0000644000175000017500000000015213203053217014644 0ustar daidairequire "notiffany/version" require "notiffany/notifier" module Notiffany # Your code goes here... end notiffany-0.1.1/images/0000755000175000017500000000000013203053217013023 5ustar daidainotiffany-0.1.1/images/success.png0000755000175000017500000000212513203053217015204 0ustar daidaiPNG  IHDRkXT`PLTELiq¦uCXe)]#_$j0ԿwA^#b&sB_"x]#j-u=ȢĪѰδ2tRNS@fIDATxYb0a 0dcK d#K-gA4M4M4M4M4Mw_ݾo78-g-,g,p+pg+p*0g*0)0g)0(g(/pΏ^@@@@?bu~kh q Q 1  (G/P]?n+v, G+`,wo_~_~_~_~_~_~_~_~_~_?{+jv]q 7-oX ߬@XQ~ gggg.w7uk ` S S `@+_._*/+.*?-?.?,/.*ǭg>Л|m]0|#/.! T}hu&_hmfheh]U._MnE{:ppk/:pr[7O:ptKWo:pvywpxipzYp|Ip~z+pj/+rZ0O+tJ@*B+7/o\ ߴ@Ha~aFM /._X_T_Pğ]ƟYȟUʟQ̿e@@`I g  //  OO   /@WX? n}4M4M4M4M4M4 1M3IENDB`notiffany-0.1.1/images/pending.png0000755000175000017500000000314213203053217015160 0ustar daidaiPNG  IHDRkXT`PLTELiq\RONKCgݷ|iݎrګyyXTcgtRNS@fIDAT4+I7w 2p8p8m5/_%] !F|;N,gL?2+LJ_%I+9!}U=QE5"[l8\o bKԈ@&|d_W}U3B*~D\c lg䌐遟4~dWsi`Ֆ5B+BT$,xA#@̄)oNmy;[#4W=OBWW# LpS-X6E cJ dHtUu@a(ST %|Fg 4>dYYՖϲ5+>OV%$,\S?pC/M0|4\J:CRl\ˑ+SrI%|s.I2W e>iZx\mpyr/t5 `2Kju>עrx zoGfN\ӠpMfƔJG8\\qm*/N +np Fm-Xn6pUA%Sj8'nG2c -niZ8ܖmpkr;+Wj8#3ܞ.-`k!W܇ᄄ{IY?=p?& {S)rOƾr$Ǯ-N½ +걛p^ZE;i,]`"t\]2+$:l0t)V%%B[]3hl(3tɰ.{-ckl%WtʱnJY&=U&CW S+]ڮB++: &'躪NJ CיiݧZtmr:tI?Ȅ~ k*갂ZEKK>2h,*3ɰ1o -}ck,'Wʱ>JGr2~z,b21 h,}e<*\>+L3𜾢ߪO) f %>U YaLaS->X6Y:9}d^5ޕdxO2iwԖ15ޖ+AxSX$x,A7a<̄_)cEm[gWa\$OJƦďN +Ƨ0>wbT51 qI.FUL+9%5h"\0^v^1{JƬ^W1{xraLo8DoYjf^KLe>?bqp8p8DOqtA|<4IENDB`notiffany-0.1.1/images/failed.png0000755000175000017500000000313713203053217014764 0ustar daidaiPNG  IHDRkXT`PLTELiqnI餌[.pHU+W,ʛY-V,ɞӒ|U+pIrNrPoL٠b:]3sMwQpNc:pKÿ)@1tRNS@fIDATxWR#A[,B,7~#3/3؟%XT?m×!5uMn|VZ uXk,ŗ_`/_vE|VR eXK,_`Ͻ>w>|^\ yxs,ŧ_OvG>.O`>۩T 泵*OOÇ[:8}>v>nA>fA >|A>tA>lA$>dA,~hm "[/ʯ]_ 2fAl 2/Z߿ oA.l 2/._? _]<`.X4 VRJR2xO[?i OW?YTzOS`?IF_`?x [?hhW?X @a @(PyH w[iBH=z_,=KI\O./}P~nm\@n)mX@^ikmT@N-+mP@׿/ʿ4v_6 X*`_% X.`_E6 X*`_e X.`ߟ> x*ǟ' x.ϟG> x*ןgc )^ mn)˿@4 Do)ӿ@Tq D)@t d+@y d+@6 d,@VyNm <.XV-W0ֳ+٩hr~C _kK| .ȗ\`/*_j]| .\Ϸ k>O|.mO^GӤ/) &,IW8YS׉ ?ORq} ϸ'P_c @ /@u\9`g@Vt!h C Pj.@^Rcb ๿P,U`-y]{k p,?]Df'G 9R@"* y= H+ _@Ma _й}Q@*P/y~?/e<*X_p??fCLL^IENDB`notiffany-0.1.1/README.md0000644000175000017500000000257213203053217013043 0ustar daidai# Notiffany Notification library supporting popular notifiers, such as: - Growl - libnotify - TMux - Emacs (see: https://github.com/guard/notiffany/wiki/Emacs-support) - rb-notifu - notifysend - gntp - TerminalNotifier ## Features - most popular notification libraries supported - easy to override options at any level (new(), notify()) - using multiple notifiers simultaneously - child processes reuse same configuration ## Installation Add this line to your application's Gemfile: ```ruby gem 'notiffany' ``` And then execute: $ bundle Or install it yourself as: $ gem install notiffany ## Usage Basic notification ```ruby notifier = Notiffany.connect(title: "A message") notifier.notify("Hello there!", image: :success) notifier.disconnect # some plugins like TMux and TerminalTitle rely on this ``` Enabling/disabling and on/off ### disable with option ```ruby notifier = Notiffany.connect(notify: false) notifier.notify('hello') # does nothing ``` ### switch on/off using methods ```ruby notifier = Notiffany.connect notifier.turn_off notifier.turn_on notifier.toggle ``` ## Contributing 1. Fork it ( https://github.com/[my-github-username]/notiffany/fork ) 2. Create your feature branch (`git checkout -b my-new-feature`) 3. Commit your changes (`git commit -am 'Add some feature'`) 4. Push to the branch (`git push origin my-new-feature`) 5. Create a new Pull Request notiffany-0.1.1/LICENSE.txt0000644000175000017500000000206013203053217013377 0ustar daidaiCopyright (c) 2014 Cezary Baginski MIT License Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.