require 'lumberjack' require 'guard/ui/colors' module Guard # The UI class helps to format messages for the user. Everything that is logged # through this class is considered either as an error message or a diagnostic # message and is written to standard error ($stderr). # # If your Guard plugin does some output that is piped into another process for further # processing, please just write it to STDOUT with `puts`. # module UI include Colors class << self # Get the Guard::UI logger instance # def logger @logger ||= begin opts = options.marshal_dump Lumberjack::Logger.new(opts.delete(:device) { $stderr }, opts) end end # Get the logger options # # @return [Hash] the logger options # def options @options ||= ::Guard::Options.new(level: :info, template: ':time - :severity - :message', time_format: '%H:%M:%S') end # Set the logger options # # @param [Hash] options the logger options # @option options [Symbol] level the log level # @option options [String] template the logger template # @option options [String] time_format the time format # def options=(options) @options = ::Guard::Options.new(options) end # Show an info message. # # @param [String] message the message to show # @option options [Boolean] reset whether to clean the output before # @option options [String] plugin manually define the calling plugin # def info(message, options = {}) filter(options[:plugin]) do |plugin| reset_line if options[:reset] logger.info(message, plugin) end end # Show a yellow warning message that is prefixed with WARNING. # # @param [String] message the message to show # @option options [Boolean] reset whether to clean the output before # @option options [String] plugin manually define the calling plugin # def warning(message, options = {}) filter(options[:plugin]) do |plugin| reset_line if options[:reset] logger.warn(color(message, :yellow), plugin) end end # Show a red error message that is prefixed with ERROR. # # @param [String] message the message to show # @option options [Boolean] reset whether to clean the output before # @option options [String] plugin manually define the calling plugin # def error(message, options = {}) filter(options[:plugin]) do |plugin| reset_line if options[:reset] logger.error(color(message, :red), plugin) end end # Show a red deprecation message that is prefixed with DEPRECATION. # It has a log level of `warn`. # # @param [String] message the message to show # @option options [Boolean] reset whether to clean the output before # @option options [String] plugin manually define the calling plugin # def deprecation(message, options = {}) return unless ::Guard.options.show_deprecations filter(options[:plugin]) do |plugin| reset_line if options[:reset] logger.warn(color(message, :yellow), plugin) end end # Show a debug message that is prefixed with DEBUG and a timestamp. # # @param [String] message the message to show # @option options [Boolean] reset whether to clean the output before # @option options [String] plugin manually define the calling plugin # def debug(message, options = {}) filter(options[:plugin]) do |plugin| reset_line if options[:reset] logger.debug(color(message, :yellow), plugin) end end # Reset a line. # def reset_line $stderr.print(color_enabled? ? "\r\e[0m" : "\r\n") end # Clear the output if clearable. # def clear(options = {}) if ::Guard.options.clear && (@clearable || options[:force]) @clearable = false system('clear;') end end # Allow the screen to be cleared again. # def clearable @clearable = true end # Show a scoped action message. # # @param [String] action the action to show # @param [Hash] scopes hash with a guard or a group scope # def action_with_scopes(action, scopes) plugins = scopes[:plugins] || [] groups = scopes[:groups] || [] if plugins.empty? && groups.empty? plugins = ::Guard.scope[:plugins] || [] groups = ::Guard.scope[:groups] || [] end scope_message ||= plugins.map(&:title).join(', ') unless plugins.empty? scope_message ||= groups.map(&:title).join(', ') unless groups.empty? scope_message ||= 'all' info "#{ action } #{ scope_message }" end private # Filters log messages depending on either the # `:only`` or `:except` option. # # @param [String] plugin the calling plugin name # @yield When the message should be logged # @yieldparam [String] param the calling plugin name # def filter(plugin) only = options.only except = options.except plugin = plugin || calling_plugin_name if (!only && !except) || (only && only.match(plugin)) || (except && !except.match(plugin)) yield plugin end end # Tries to extract the calling Guard plugin name # from the call stack. # # @param [Integer] depth the stack depth # @return [String] the Guard plugin name # def calling_plugin_name(depth = 2) name = /(guard\/[a-z_]*)(\/[a-z_]*)?.rb:/i.match(caller[depth]) name ? name[1].split('/').map { |part| part.split(/[^a-z0-9]/i).map { |word| word.capitalize }.join }.join('::') : 'Guard' end # Checks if color output can be enabled. # # @return [Boolean] whether color is enabled or not # def color_enabled? if @color_enabled.nil? if RbConfig::CONFIG['target_os'] =~ /mswin|mingw/i if ENV['ANSICON'] @color_enabled = true else begin require 'rubygems' unless ENV['NO_RUBYGEMS'] require 'Win32/Console/ANSI' @color_enabled = true rescue LoadError @color_enabled = false info "You must 'gem install win32console' to use color on Windows" end end else @color_enabled = true end end @color_enabled end # Colorizes a text message. See the constant in the UI class for possible # color_options parameters. You can pass optionally :bright, a foreground # color and a background color. # # @example # # color('Hello World', :red, :bright) # # @param [String] text the text to colorize # @param [Array] color_options the color options # def color(text, *color_options) color_code = '' color_options.each do |color_option| color_option = color_option.to_s if color_option != '' unless color_option =~ /\d+/ color_option = const_get("ANSI_ESCAPE_#{ color_option.upcase }") end color_code += ';' + color_option end end color_enabled? ? "\e[0#{ color_code }m#{ text }\e[0m" : text end end end end