diff --git a/.rubocop.yml b/.rubocop.yml new file mode 100644 index 0000000..cc32da4 --- /dev/null +++ b/.rubocop.yml @@ -0,0 +1 @@ +inherit_from: .rubocop_todo.yml diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml new file mode 100644 index 0000000..f4c4c89 --- /dev/null +++ b/.rubocop_todo.yml @@ -0,0 +1,101 @@ +# This configuration was generated by +# `rubocop --auto-gen-config` +# on 2016-05-18 02:46:59 +0200 using RuboCop version 0.40.0. +# The point is for the user to remove these configuration records +# one by one as the offenses are removed from the code base. +# Note that changes in the inspected code, or installation of new +# versions of RuboCop, may require this file to be generated again. + +# Offense count: 6 +# Configuration parameters: CountComments. +Metrics/ClassLength: + Max: 485 + +# Offense count: 3 +# Configuration parameters: CountComments. +Metrics/MethodLength: + Max: 18 + +# Offense count: 5 +# Configuration parameters: CountComments. +Metrics/ModuleLength: + Max: 334 + +# Offense count: 10 +Style/Documentation: + Exclude: + - 'spec/**/*' + - 'test/**/*' + - 'lib/notiffany.rb' + - 'lib/notiffany/notifier.rb' + - 'lib/notiffany/notifier/base.rb' + - 'lib/notiffany/notifier/detected.rb' + - 'lib/notiffany/notifier/tmux.rb' + +# Offense count: 52 +# Cop supports --auto-correct. +# Configuration parameters: EnforcedStyle, SupportedStyles. +# SupportedStyles: leading, trailing +Style/DotPosition: + Enabled: false + +# Offense count: 11 +# Cop supports --auto-correct. +# Configuration parameters: EnforcedStyle, SupportedStyles. +# SupportedStyles: symmetrical, new_line, same_line +Style/MultilineMethodCallBraceLayout: + Exclude: + - 'spec/lib/notiffany/notifier/growl_spec.rb' + - 'spec/lib/notiffany/notifier/libnotify_spec.rb' + - 'spec/lib/notiffany/notifier/rb_notifu_spec.rb' + - 'spec/lib/notiffany/notifier/terminal_notifier_spec.rb' + - 'spec/lib/notiffany/notifier/tmux_spec.rb' + +# Offense count: 25 +# Cop supports --auto-correct. +Style/MutableConstant: + Exclude: + - 'lib/notiffany/notifier.rb' + - 'lib/notiffany/notifier/base.rb' + - 'lib/notiffany/notifier/detected.rb' + - 'lib/notiffany/notifier/file.rb' + - 'lib/notiffany/notifier/gntp.rb' + - 'lib/notiffany/notifier/growl.rb' + - 'lib/notiffany/notifier/libnotify.rb' + - 'lib/notiffany/notifier/notifysend.rb' + - 'lib/notiffany/notifier/rb_notifu.rb' + - 'lib/notiffany/notifier/terminal_notifier.rb' + - 'lib/notiffany/notifier/terminal_title.rb' + - 'lib/notiffany/notifier/tmux.rb' + - 'lib/notiffany/version.rb' + - 'spec/lib/notiffany/notifier/base_spec.rb' + +# Offense count: 23 +# Cop supports --auto-correct. +# Configuration parameters: EnforcedStyle, SupportedStyles. +# SupportedStyles: only_raise, only_fail, semantic +Style/SignalException: + Exclude: + - 'lib/notiffany/notifier.rb' + - 'lib/notiffany/notifier/base.rb' + - 'lib/notiffany/notifier/detected.rb' + - 'lib/notiffany/notifier/file.rb' + - 'lib/notiffany/notifier/growl.rb' + - 'lib/notiffany/notifier/notifysend.rb' + - 'lib/notiffany/notifier/terminal_notifier.rb' + - 'lib/notiffany/notifier/tmux.rb' + - 'spec/lib/notiffany/notifier/detected_spec.rb' + +# Offense count: 13 +# Cop supports --auto-correct. +# Configuration parameters: EnforcedStyle, SupportedStyles. +# SupportedStyles: use_perl_names, use_english_names +Style/SpecialGlobalVars: + Enabled: false + +# Offense count: 967 +# Cop supports --auto-correct. +# Configuration parameters: EnforcedStyle, SupportedStyles, ConsistentQuotesInMultiline. +# SupportedStyles: single_quotes, double_quotes +Style/StringLiterals: + Enabled: false diff --git a/.travis.yml b/.travis.yml index 49a3603..649829e 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,14 +1,9 @@ language: ruby bundler_args: --without development rvm: - - 1.9.3 - - 2.0.0 - - 2.1.5 - ruby-head - - jruby - - rbx-2 -matrix: - allow_failures: - - rvm: rbx-2 - - rvm: jruby + - 2.2.5 + - 2.3.1 + - jruby-9.0.5.0 sudo: false +cache: bundler diff --git a/Gemfile b/Gemfile index ba59da4..976d23a 100644 --- a/Gemfile +++ b/Gemfile @@ -3,13 +3,16 @@ source 'https://rubygems.org' # Specify your gem's dependencies in notiffany.gemspec gemspec development_group: :gem_build_tools -gem "rake", "~> 10.0" -gem 'nenv', "~> 0.1" +gem "rake", "~> 11.1" +gem 'nenv', "~> 0.3" group :test do - gem "rspec", "~> 3.1" + gem "rspec", "~> 3.4" end group :development do - gem 'guard-rspec', "~> 4.5", require: false + gem 'guard-rspec', "~> 4.6", require: false + gem 'listen', "~> 3.1" + gem 'guard-rubocop', "~> 1.2", require: false + gem 'rubocop', '~> 0.40', require: false end diff --git a/Guardfile b/Guardfile index 2ed9a34..b2eda88 100644 --- a/Guardfile +++ b/Guardfile @@ -24,7 +24,7 @@ # # and, you'll have to watch "config/Guardfile" instead of "Guardfile" # -watch ("Guardfile") do +watch("Guardfile") do UI.info "Exiting because Guard must be restarted for changes to take effect" exit 0 end @@ -38,19 +38,26 @@ end # * zeus: 'zeus rspec' (requires the server to be started separately) # * 'just' rspec: 'rspec' -guard :rspec, cmd: "bundle exec rspec" do - require "guard/rspec/dsl" - dsl = Guard::RSpec::Dsl.new(self) +group :specs, halt_on_fail: true do + guard :rspec, cmd: "bundle exec rspec" do + require "guard/rspec/dsl" + dsl = Guard::RSpec::Dsl.new(self) - # Feel free to open issues for suggestions and improvements + # Feel free to open issues for suggestions and improvements - # RSpec files - rspec = dsl.rspec - watch(rspec.spec_helper) { rspec.spec_dir } - watch(rspec.spec_support) { rspec.spec_dir } - watch(rspec.spec_files) + # RSpec files + rspec = dsl.rspec + watch(rspec.spec_helper) { rspec.spec_dir } + watch(rspec.spec_support) { rspec.spec_dir } + watch(rspec.spec_files) - # Ruby files - ruby = dsl.ruby - dsl.watch_spec_files_for(ruby.lib_files) + # Ruby files + ruby = dsl.ruby + dsl.watch_spec_files_for(ruby.lib_files) + end + + guard :rubocop do + watch(/.+\.rb$/) + watch(%r{(?:.+/)?\.rubocop(?:_todo)\.yml$}) { |m| File.dirname(m[0]) } + end end diff --git a/Rakefile b/Rakefile index a259cc7..74ae0ef 100644 --- a/Rakefile +++ b/Rakefile @@ -5,9 +5,8 @@ require 'nenv' default_tasks = [] require 'rspec/core/rake_task' -default_tasks << RSpec::Core::RakeTask.new(:spec) do |t| +default_tasks << RSpec::Core::RakeTask.new(:spec) do |t| t.verbose = Nenv.ci? end task default: default_tasks.map(&:name) - diff --git a/lib/notiffany/notifier.rb b/lib/notiffany/notifier.rb index 173b7fd..47fcf20 100644 --- a/lib/notiffany/notifier.rb +++ b/lib/notiffany/notifier.rb @@ -2,9 +2,9 @@ require "rbconfig" require "pathname" require "nenv" -require "logger" require "notiffany/notifier/detected" +require "notiffany/notifier/config" module Notiffany # The notifier handles sending messages to different notifiers. Currently the @@ -43,9 +43,7 @@ def self.connect(options = {}) end class Notifier - DEFAULTS = { notify: true } - - NOTIFICATIONS_DISABLED = "Notifications disabled by GUARD_NOTIFY" + + NOTIFICATIONS_DISABLED = "Notifications disabled by GUARD_NOTIFY" \ " environment variable" USING_NOTIFIER = "Notiffany is using %s to send notifications." @@ -79,35 +77,16 @@ class Notifier class NotServer < RuntimeError end - def initialize(opts) - @env_namespace = opts.fetch(:namespace, "notiffany") - @logger = opts.fetch(:logger) do - Logger.new($stderr).tap { |l| l.level = Logger::WARN } - end + attr_reader :config - - @detected = Detected.new(SUPPORTED, @env_namespace, @logger) + def initialize(opts) + @config = Config.new(opts) + @detected = Detected.new(SUPPORTED, config.env_namespace, config.logger) return if _client? - _env.notify_pid = $$ - - fail "Already connected" if active? - - options = DEFAULTS.merge(opts) - return unless enabled? && options[:notify] - - notifiers = opts.fetch(:notifiers, {}) - if notifiers.any? - notifiers.each do |name, notifier_options| - @detected.add(name, notifier_options) - end - else - @detected.detect - end - - turn_on + _activate rescue Detected::NoneAvailableError => e - @logger.info e.to_s + config.logger.info e.to_s end def disconnect @@ -133,13 +112,7 @@ def turn_on(options = {}) fail "Already active!" if active? - silent = options[:silent] - - @detected.available.each do |obj| - @logger.debug(format(USING_NOTIFIER, obj.title)) unless silent - obj.turn_on if obj.respond_to?(:turn_on) - end - + _turn_on_notifiers(options) _env.notify_active = true end @@ -191,7 +164,7 @@ def available private def _env - @environment ||= Env.new(@env_namespace) + @environment ||= Env.new(config.env_namespace) end def _check_server! @@ -201,5 +174,37 @@ def _check_server! 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 diff --git a/lib/notiffany/notifier/base.rb b/lib/notiffany/notifier/base.rb index 5068ecd..4313126 100644 --- a/lib/notiffany/notifier/base.rb +++ b/lib/notiffany/notifier/base.rb @@ -38,7 +38,7 @@ def initialize(gem_name) class UnsupportedPlatform < UnavailableError def initialize - super "Unsupported platform #{RbConfig::CONFIG["host_os"].inspect}" + super "Unsupported platform #{RbConfig::CONFIG['host_os'].inspect}" end end diff --git a/lib/notiffany/notifier/config.rb b/lib/notiffany/notifier/config.rb new file mode 100644 index 0000000..4a7757a --- /dev/null +++ b/lib/notiffany/notifier/config.rb @@ -0,0 +1,34 @@ +require "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 diff --git a/lib/notiffany/notifier/detected.rb b/lib/notiffany/notifier/detected.rb index fa909f5..63dec16 100644 --- a/lib/notiffany/notifier/detected.rb +++ b/lib/notiffany/notifier/detected.rb @@ -19,8 +19,8 @@ class Notifier # 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) : [] } + create_method(:notifiers=) { |data| YAML.dump(data || []) } + create_method(:notifiers) { |data| data ? YAML.load(data) : [] } end # @private api @@ -37,9 +37,7 @@ def initialize(name) @name = name end - def name - @name - end + attr_reader :name def message "Unknown notifier: #{@name.inspect}" @@ -61,7 +59,7 @@ def detect @supported.each do |group| group.detect do |name, _| begin - add(name, {}) + _add(name, {}) true rescue Notifier::Base::UnavailableError => e @logger.debug "Notiffany: #{name} not available (#{e.message})." @@ -79,7 +77,17 @@ def available 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 @@ -98,8 +106,6 @@ def add(name, opts) all.each { |item| item[:options] = opts if item[:name] == name } end - private - def _to_module(name) @supported.each do |group| next unless (notifier = group.detect { |n, _| n == name }) diff --git a/lib/notiffany/notifier/file.rb b/lib/notiffany/notifier/file.rb index fa4ab9c..e83bff7 100644 --- a/lib/notiffany/notifier/file.rb +++ b/lib/notiffany/notifier/file.rb @@ -32,8 +32,8 @@ def _check_available(opts = {}) def _perform_notify(message, opts = {}) fail UnavailableError, "No :path option given" unless opts[:path] - format = opts[:format] - ::File.write(opts[:path], format % [opts[:type], opts[:title], message]) + str = format(opts[:format], opts[:type], opts[:title], message) + ::File.write(opts[:path], str) end def _gem_name diff --git a/lib/notiffany/notifier/gntp.rb b/lib/notiffany/notifier/gntp.rb index cc3cf0b..6695cf7 100644 --- a/lib/notiffany/notifier/gntp.rb +++ b/lib/notiffany/notifier/gntp.rb @@ -24,11 +24,12 @@ class GNTP < Base CLIENT_DEFAULTS = { host: "127.0.0.1", password: "", - port: 23053 + port: 23_053 } def _supported_hosts - %w(darwin linux linux-gnu freebsd openbsd sunos solaris mswin mingw cygwin) + %w(darwin linux linux-gnu freebsd openbsd sunos solaris mswin mingw + cygwin) end def _gem_name diff --git a/lib/notiffany/notifier/notifysend.rb b/lib/notiffany/notifier/notifysend.rb index f375dc4..5c27015 100644 --- a/lib/notiffany/notifier/notifysend.rb +++ b/lib/notiffany/notifier/notifysend.rb @@ -26,7 +26,7 @@ class NotifySend < Base def _gem_name nil end - + def _supported_hosts %w(linux linux-gnu freebsd openbsd sunos solaris) end @@ -81,7 +81,7 @@ def _notifysend_urgency(type) # def _to_arguments(command, supported, opts = {}) opts.inject(command) do |cmd, (flag, value)| - supported.include?(flag) ? (cmd << "-#{ flag }" << value.to_s) : cmd + supported.include?(flag) ? (cmd << "-#{flag}" << value.to_s) : cmd end end end diff --git a/lib/notiffany/notifier/terminal_title.rb b/lib/notiffany/notifier/terminal_title.rb index 2a96b96..7643f94 100644 --- a/lib/notiffany/notifier/terminal_title.rb +++ b/lib/notiffany/notifier/terminal_title.rb @@ -32,7 +32,7 @@ def _check_available(_options) def _perform_notify(message, opts = {}) first_line = message.sub(/^\n/, "").sub(/\n.*/m, "") - STDOUT.puts "\e]2;[#{ opts[:title] }] #{ first_line }\a" + STDOUT.puts "\e]2;[#{opts[:title]}] #{first_line}\a" end end end diff --git a/lib/notiffany/notifier/tmux.rb b/lib/notiffany/notifier/tmux.rb index 9a25fe5..2023200 100644 --- a/lib/notiffany/notifier/tmux.rb +++ b/lib/notiffany/notifier/tmux.rb @@ -1,5 +1,8 @@ require "notiffany/notifier/base" -require "shellany/sheller" + +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 @@ -27,134 +30,6 @@ class Tmux < Base color_location: "status-left-bg" } - class Client - CLIENT = "tmux" - - 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| - args = client ? ["-t", client.strip] : [] - if value - _run("set", "-q", *args, key, value) - else - _run("set", "-q", "-u", *args, key) - end - 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 - end - - 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 - class Error < RuntimeError end @@ -226,129 +101,31 @@ def _check_available(opts = {}) # message on all tmux clients or not # def _perform_notify(message, options = {}) - change_color = options[:change_color] locations = Array(options[:color_location]) - display_the_title = options[:display_title] - display_message = options[:display_message] type = options[:type].to_s title = options[:title] - if change_color - color = _tmux_color(type, options) - locations.each do |location| - Client.new(client(options)).set(location, color) - end - end - - _display_title(type, title, message, options) if display_the_title - - return unless display_message - _display_message(type, title, message, options) - end - - # Displays a message in the title bar of the terminal. - # - # @param [String] title the notification title - # @param [String] message the notification message body - # @param [Hash] options additional notification library options - # @option options [String] success_message_format a string to use as - # formatter for the success message. - # @option options [String] failed_message_format a string to use as - # formatter for the failed message. - # @option options [String] pending_message_format a string to use as - # formatter for the pending message. - # @option options [String] default_message_format a string to use as - # formatter when no format per type is defined. - # - def _display_title(type, title, message, options = {}) - format = "#{type}_title_format".to_sym - default_title_format = options[:default_title_format] - title_format = options.fetch(format, default_title_format) - teaser_message = message.split("\n").first - display_title = title_format % [title, teaser_message] - - Client.new(client(options)).title = display_title - end - - # Displays a message in the status bar of tmux. - # - # @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 [Hash] options additional notification library options - # @option options [Integer] timeout the amount of seconds to show the - # message in the status bar - # @option options [String] success_message_format a string to use as - # formatter for the success message. - # @option options [String] failed_message_format a string to use as - # formatter for the failed message. - # @option options [String] pending_message_format a string to use as - # formatter for the pending message. - # @option options [String] default_message_format a string to use as - # formatter when no format per type is defined. - # @option options [String] success_message_color the success notification - # foreground color name. - # @option options [String] failed_message_color the failed notification - # foreground color name. - # @option options [String] pending_message_color the pending notification - # foreground color name. - # @option options [String] default_message_color a notification - # foreground color to use when no color per type is defined. - # @option options [String] line_separator a string to use instead of a - # line-break. - # - def _display_message(type, title, message, opts = {}) - default_format = opts[:default_message_format] - default_color = opts[:default_message_color] - display_time = opts[:timeout] - separator = opts[:line_separator] - - format = "#{type}_message_format".to_sym - message_format = opts.fetch(format, default_format) - - color = "#{type}_message_color".to_sym - message_color = opts.fetch(color, default_color) - - color = _tmux_color(type, opts) - formatted_message = message.split("\n").join(separator) - msg = message_format % [title, formatted_message] - - cl = Client.new(client(opts)) - cl.display_time = display_time * 1000 - cl.message_fg = message_color - cl.message_bg = color - cl.display_message(msg) - end - - # Get the Tmux color for the notification type. - # You can configure your own color by overwriting the defaults. - # - # @param [String] type the notification type - # @return [String] the name of the emacs color - # - def _tmux_color(type, opts = {}) - type = type.to_sym - opts[type] || opts[:default] + 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 - def self._start_session - fail "Already turned on!" if @session - @session = Session.new - end - - def self._end_session - fail "Already turned off!" unless @session || nil - @session.close - @session = nil - end + class << self + def _start_session + fail "Already turned on!" if @session + @session = Session.new + end - def self._session - @session - end + def _end_session + fail "Already turned off!" unless @session + @session.close + @session = nil + end - def client(options) - options[:display_on_all_clients] ? :all : nil + def _session + @session + end end end end diff --git a/lib/notiffany/notifier/tmux/client.rb b/lib/notiffany/notifier/tmux/client.rb new file mode 100644 index 0000000..e740783 --- /dev/null +++ b/lib/notiffany/notifier/tmux/client.rb @@ -0,0 +1,103 @@ +require "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 diff --git a/lib/notiffany/notifier/tmux/notification.rb b/lib/notiffany/notifier/tmux/notification.rb new file mode 100644 index 0000000..711de80 --- /dev/null +++ b/lib/notiffany/notifier/tmux/notification.rb @@ -0,0 +1,62 @@ +module 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 diff --git a/lib/notiffany/notifier/tmux/session.rb b/lib/notiffany/notifier/tmux/session.rb new file mode 100644 index 0000000..bf7eb2b --- /dev/null +++ b/lib/notiffany/notifier/tmux/session.rb @@ -0,0 +1,40 @@ +module 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 diff --git a/notiffany.gemspec b/notiffany.gemspec index 1d7d1cf..e805681 100644 --- a/notiffany.gemspec +++ b/notiffany.gemspec @@ -6,10 +6,14 @@ require 'notiffany/version' Gem::Specification.new do |spec| spec.name = "notiffany" spec.version = Notiffany::VERSION - spec.authors = ["Cezary Baginski", "Rémy Coutable", "Thibaud Guillaume-Gentil"] + spec.authors = [ + "Cezary Baginski", + "Rémy Coutable", + "Thibaud Guillaume-Gentil" + ] spec.email = ["cezary@chronomantic.net"] - spec.summary = %q{Notifier library (extracted from Guard project)} + spec.summary = 'Notifier library (extracted from Guard project)' spec.description = <<-EOF Wrapper libray for most popular notification libraries such as Growl, Libnotify, Notifu @@ -19,7 +23,7 @@ Gem::Specification.new do |spec| spec.license = "MIT" git_files = `git ls-files -z`.split("\x0") - files = git_files.select { |f| /^lib\/.*$/ =~ f } + files = git_files.select { |f| %r{^lib/.*$} =~ f } files += %w(README.md LICENSE.txt) # skip the large images/guard.png diff --git a/spec/lib/notiffany/notifier/base_spec.rb b/spec/lib/notiffany/notifier/base_spec.rb index 18e067d..e9546b7 100644 --- a/spec/lib/notiffany/notifier/base_spec.rb +++ b/spec/lib/notiffany/notifier/base_spec.rb @@ -3,7 +3,7 @@ # TODO: no point in testing the base class, really module Notiffany RSpec.describe Notifier::Base do - let(:fake) { double ("fake_lib") } + let(:fake) { double "fake_lib" } let(:options) { {} } let(:os) { "solaris" } diff --git a/spec/lib/notiffany/notifier/detected_spec.rb b/spec/lib/notiffany/notifier/detected_spec.rb index 2ee4b7e..dd3c3da 100644 --- a/spec/lib/notiffany/notifier/detected_spec.rb +++ b/spec/lib/notiffany/notifier/detected_spec.rb @@ -5,7 +5,6 @@ class Notifier RSpec.describe YamlEnvStorage do let(:subject) { YamlEnvStorage.new("notiffany_tests_foo") } describe "#notifiers" do - context "when set to empty array" do before { subject.notifiers = [] } specify { expect(subject.notifiers).to be_empty } @@ -51,7 +50,7 @@ class Notifier end end - describe ".available" do + describe "#available" do context "with detected notifiers" do let(:available) do [ @@ -74,11 +73,13 @@ class Notifier end end - describe ".add" do + describe "#add" do before do - allow(env).to receive(:notifiers).and_return([]) + allow(env).to receive(:notifiers).and_return(notifiers) end + context "with no detected notifiers" do + let(:notifiers) { [] } context "when unknown" do it "does not add the library" do expect(env).to_not receive(:notifiers=) @@ -87,9 +88,37 @@ class Notifier end end end + + context "with manually configured notifiers" do + let(:notifiers) { [] } + + context "when not available" do + before do + allow(foo_mod).to receive(:new). + with(foo: :bar). + and_raise(Notifier::Base::UnavailableError, "something failed") + allow(logger).to receive(:warning) + end + + it "does not add the library" do + expect(env).to_not receive(:notifiers=) + subject.add(:foo, foo: :bar) + end + + it "does not raise an error" do + expect { subject.add(:foo, foo: :bar) }.to_not raise_error + end + + it "shows a warning" do + expect(logger).to receive(:warning). + with("Notiffany: foo not available (something failed).") + subject.add(:foo, foo: :bar) + end + end + end end - describe ".detect" do + describe "#detect" do context "with some detected notifiers" do before do allow(env).to receive(:notifiers).and_return([]) @@ -127,7 +156,7 @@ class Notifier end end - describe ".reset" do + describe "#reset" do before do allow(env).to receive(:notifiers=) end diff --git a/spec/lib/notiffany/notifier/growl_spec.rb b/spec/lib/notiffany/notifier/growl_spec.rb index e787bb6..bb11ec8 100644 --- a/spec/lib/notiffany/notifier/growl_spec.rb +++ b/spec/lib/notiffany/notifier/growl_spec.rb @@ -3,7 +3,7 @@ module Notiffany RSpec.describe Notifier::Growl do module FakeGrowl - def self.notify(message, opts) + def self.notify(_message, _opts) end def self.installed? @@ -67,11 +67,11 @@ def self.switches expect(growl).to receive(:notify).with( "Welcome!", hash_including( - sticky: false, - priority: 0, - name: "Notiffany", - title: "Hello", - image: "/tmp/welcome.png" + sticky: false, + priority: 0, + name: "Notiffany", + title: "Hello", + image: "/tmp/welcome.png" ) ) @@ -82,12 +82,12 @@ def self.switches expect(growl).to receive(:notify).with( "Welcome!", hash_including( - sticky: false, - priority: 0, - name: "Notiffany", - title: "Welcome", - image: "/tmp/welcome.png" - ) + sticky: false, + priority: 0, + name: "Notiffany", + title: "Welcome", + image: "/tmp/welcome.png" + ) ) subject.notify("Welcome!", @@ -101,11 +101,11 @@ def self.switches expect(growl).to receive(:notify).with( "Welcome!", hash_including( - sticky: false, - priority: 0, - name: "Notiffany", - title: "Welcome", - image: "/tmp/welcome.png" + sticky: false, + priority: 0, + name: "Notiffany", + title: "Welcome", + image: "/tmp/welcome.png" ) ) @@ -121,12 +121,12 @@ def self.switches expect(growl).to receive(:notify).with( "Waiting for something", hash_including( - sticky: true, - priority: 2, - name: "Notiffany", - title: "Waiting", - image: "/tmp/wait.png" - ) + sticky: true, + priority: 2, + name: "Notiffany", + title: "Waiting", + image: "/tmp/wait.png" + ) ) subject.notify( diff --git a/spec/lib/notiffany/notifier/notifysend_spec.rb b/spec/lib/notiffany/notifier/notifysend_spec.rb index e3a813f..55b97d7 100644 --- a/spec/lib/notiffany/notifier/notifysend_spec.rb +++ b/spec/lib/notiffany/notifier/notifysend_spec.rb @@ -34,7 +34,7 @@ class Notifier it "checks if the binary is available" do expect(sheller).to receive(:stdout).with("which notify-send"). - and_return("foo\n") + and_return("foo\n") subject end end diff --git a/spec/lib/notiffany/notifier/rb_notifu_spec.rb b/spec/lib/notiffany/notifier/rb_notifu_spec.rb index 739570f..e4197e4 100644 --- a/spec/lib/notiffany/notifier/rb_notifu_spec.rb +++ b/spec/lib/notiffany/notifier/rb_notifu_spec.rb @@ -126,7 +126,6 @@ class Notifier end end end - end end end diff --git a/spec/lib/notiffany/notifier/terminal_notifier_spec.rb b/spec/lib/notiffany/notifier/terminal_notifier_spec.rb index 56f7819..330b84f 100644 --- a/spec/lib/notiffany/notifier/terminal_notifier_spec.rb +++ b/spec/lib/notiffany/notifier/terminal_notifier_spec.rb @@ -88,6 +88,5 @@ module Notiffany end end end - end end diff --git a/spec/lib/notiffany/notifier/tmux_spec.rb b/spec/lib/notiffany/notifier/tmux_spec.rb index f23a188..dfe63c2 100644 --- a/spec/lib/notiffany/notifier/tmux_spec.rb +++ b/spec/lib/notiffany/notifier/tmux_spec.rb @@ -45,7 +45,8 @@ class Notifier "/dev/ttys001\n" end - expect(sheller).to receive(:run).with("tmux display -c /dev/ttys001 'foo'") + expect(sheller).to receive(:run) + .with("tmux display -c /dev/ttys001 'foo'") subject.display_message("foo") end end @@ -153,9 +154,7 @@ class Notifier end after do - if described_class.send(:_session) - described_class.send(:_end_session) - end + described_class.send(:_end_session) if described_class.send(:_session) end describe "#initialize" do @@ -176,7 +175,7 @@ class Notifier to raise_error( Base::UnavailableError, /way too old \(1.6\)/ - ) + ) end end end @@ -188,7 +187,7 @@ class Notifier to raise_error( Base::UnavailableError, /only available inside a TMux session/ - ) + ) end end end @@ -470,7 +469,7 @@ class Notifier { type: "pending", title: "any title", - display_message: true, + display_message: true } end diff --git a/spec/lib/notiffany/notifier_spec.rb b/spec/lib/notiffany/notifier_spec.rb index e94071b..b563049 100644 --- a/spec/lib/notiffany/notifier_spec.rb +++ b/spec/lib/notiffany/notifier_spec.rb @@ -13,7 +13,7 @@ module Notiffany let(name.to_sym) do class_double( "Notiffany::Notifier::Tmux", - name: name, + name: name ) end end @@ -109,8 +109,8 @@ module Notiffany context "with custom notifier config" do let(:env_enabled) { true } - let(:notifiers) { {foo: { bar: :baz} } } - let(:options) { { notifiers: notifiers} } + let(:notifiers) { { foo: { bar: :baz } } } + let(:options) { { notifiers: notifiers } } before do allow(detected).to receive(:available).and_return([])