require "rbconfig" require "guard/ui" module Guard module Notifier # Base class for all notifiers. # class Base HOSTS = { darwin: "Mac OS X", linux: "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 Guard with \"bundle exec\"." attr_reader :options def initialize(opts = {}) @options = opts end # This method should be overriden by subclasses and return an array of # OSes the notifier supports. By default, it returns :all which mean # there's no check against the current OS. # # @see HOSTS for the list of possible OSes # def self.supported_hosts :all end # Test if the notifier can be used. # # @param [Hash] opts notifier options # @option opts [Boolean] silent true if no error messages should be shown # @return [Boolean] the availability status # def self.available?(opts = {}) if _supported_host? true else hosts = supported_hosts.map { |host| HOSTS[host.to_sym] }.join(", ") unless opts.fetch(:silent) { false } ::Guard::UI.error "The :#{name} notifier runs only on #{hosts}." end false end end # This method must be overriden. # def notify(_message, opts = {}) options.delete(:silent) opts.replace(options.merge(opts)) normalize_standard_options!(opts) end # Returns the title of the notifier. # # @example Un-modulize the class name # Guard::Notifier::FileNotifier.title # #=> 'FileNotifier' # # @return [String] the title of the notifier # def self.title to_s.sub(/.+::(\w+)$/, '\1') end # Returns the name of the notifier. # # @example Un-modulize, underscorize and downcase the class name # Guard::Notifier::FileNotifier.name # #=> 'file_notifier' # # @return [String] the name of the notifier # def self.name title.gsub(/([a-z])([A-Z])/, '\1_\2').downcase end # Returns the name of the notifier's gem. By default it returns the # notifier name. This method can be overriden by subclasses. # # @example Un-modulize, underscorize and downcase the class name # Guard::Notifier::FileNotifier.gem_name # #=> 'file_notifier' # # @return [String] the name of the notifier's gem # def self.gem_name name end # This method tries to require the gem whose name is returned by # `.gem_name`. If a LoadError or NameError occurs, it displays an error # message (unless opts[:silent] is true) and returns false. # # @param [Hash] opts some options # @option opts [Boolean] silent true if no error messages should be shown # # @return [Boolean] whether or not the gem is loaded # def self.require_gem_safely(opts = {}) require gem_name true rescue LoadError, NameError unless opts[:silent] UI.error ERROR_ADD_GEM_AND_RUN_BUNDLE % [gem_name] end false end # Returns the title of the notifier. # # @example Un-modulize the class name # Guard::Notifier::FileNotifier.new.title # #=> 'FileNotifier' # # @return [String] the title of the notifier # def title self.class.title end # Returns the name of the notifier. # # @example Un-modulize, underscorize and downcase the class name # Guard::Notifier::FileNotifier.new.name # #=> 'file_notifier' # # @return [String] the name of the notifier # def name self.class.name end # Paths where all Guard images are located # # @return [Pathname] the path to the images directory # def images_path @images_path ||= Pathname.new(__FILE__).dirname + "../../../images" end # @private # # Checks if the current OS is supported by the notifier. # # @see .supported_hosts # def self._supported_host? supported_hosts == :all || RbConfig::CONFIG["host_os"] =~ /#{supported_hosts.join('|')}/ end # Set or modify the `:title`, `:type` and `:image` options for a # notification. Should be used in `#notify`. # # @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 # def normalize_standard_options!(opts) opts[:title] ||= "Guard" opts[:type] ||= _notification_type(opts.fetch(:image, :success)) opts[:image] = _image_path(opts.delete(:image) { :success }) end private # Get the image path for an image symbol for the following # known image types: # # - failed # - pending # - success # # If the image is not a known symbol, it will be returned unmodified. # # @param [Symbol, String] image the image symbol or path to an image # # @return [String] the image path # def _image_path(image) case image when :failed, :pending, :success images_path.join("#{image}.png").to_s else image end end # Get the notification type depending on the # image that has been selected for the notification. # # @param [Symbol, String] image the image symbol or path to an image # # @return [String] the notification type # def _notification_type(image) case image when :failed, :pending, :success image else :notify end end end end end