require 'logger' require 'thread' module YARD # Handles console logging for info, warnings and errors. # Uses the stdlib Logger class in Ruby for all the backend logic. class Logger < ::Logger # The list of characters displayed beside the progress bar to indicate # "movement". # @since 0.8.2 PROGRESS_INDICATORS = ["\u230C", "\u230D", "\u230E", "\u230F"] # @return [IO] the IO object being logged to # @since 0.8.2 def io; @logdev end def io=(pipe) @logdev = pipe end # @return [Boolean] whether backtraces should be shown (by default # this is on). def show_backtraces; @show_backtraces || level == DEBUG end attr_writer :show_backtraces # @return [Boolean] whether progress indicators should be shown when # logging CLIs (by default this is off). def show_progress return false if YARD.ruby18? # threading is too ineffective for progress support return false if YARD.windows? # windows has poor ANSI support return false unless io.tty? # no TTY support on IO return false if level > WARN # no progress in verbose/debug modes @show_progress end attr_writer :show_progress # The logger instance # @return [Logger] the logger instance def self.instance(pipe = STDOUT) @logger ||= new(pipe) end # Creates a new logger def initialize(pipe, *args) super(pipe, *args) self.io = pipe self.show_backtraces = true self.show_progress = false self.level = WARN self.formatter = method(:format_log) @progress_indicator = 0 @mutex = Mutex.new end # Changes the debug level to DEBUG if $DEBUG is set # and writes a debugging message. def debug(*args) self.level = DEBUG if $DEBUG super end # Captures the duration of a block of code for benchmark analysis. Also # calls {#progress} on the message to display it to the user. # # @todo Implement capture storage for reporting of benchmarks # @param [String] msg the message to display # @param [Symbol, nil] nontty_log the level to log as if the output # stream is not a TTY. Use +nil+ for no alternate logging. # @yield a block of arbitrary code to benchmark # @return [void] def capture(msg, nontty_log = :debug, &block) progress(msg, nontty_log) yield ensure clear_progress end # Displays a progress indicator for a given message. This progress report # is only displayed on TTY displays, otherwise the message is passed to # the +nontty_log+ level. # # @param [String] msg the message to log # @param [Symbol, nil] nontty_log the level to log as if the output # stream is not a TTY. Use +nil+ for no alternate logging. # @return [void] # @since 0.8.2 def progress(msg, nontty_log = :debug) send(nontty_log, msg) if nontty_log return unless show_progress icon = "" if defined?(::Encoding) icon = PROGRESS_INDICATORS[@progress_indicator] + " " end @mutex.synchronize do print("\e[2K\e[?25l\e[1m#{icon}#{msg}\e[0m\r") @progress_msg = msg @progress_indicator += 1 @progress_indicator %= PROGRESS_INDICATORS.size end Thread.new do sleep(0.05) progress(msg + ".", nil) if @progress_msg == msg end end # Clears the progress indicator in the TTY display. # @return [void] # @since 0.8.2 def clear_progress return unless show_progress print_no_newline("\e[?25h\e[2K") @progress_msg = nil end # Displays an unformatted line to the logger output stream, adding # a newline. # @param [String] msg the message to display # @return [void] # @since 0.8.2 def puts(msg = '') print("#{msg}\n") end alias_method :print_no_newline, :<< private :print_no_newline # Displays an unformatted line to the logger output stream. # @param [String] msg the message to display # @return [void] # @since 0.8.2 def print(msg = '') clear_line print_no_newline(msg) end alias_method :<<, :print # Prints the backtrace +exc+ to the logger as error data. # # @param [Array] exc the backtrace list # @param [Symbol] level_meth the level to log backtrace at # @return [void] def backtrace(exc, level_meth = :error) return unless show_backtraces send(level_meth, "#{exc.class.class_name}: #{exc.message}") send(level_meth, "Stack trace:" + exc.backtrace[0..5].map {|x| "\n\t#{x}" }.join + "\n") end # Warns that the Ruby environment does not support continuations. Applies # to JRuby, Rubinius and MacRuby. This warning will only display once # per Ruby process. # # @deprecated Continuations are no longer needed by YARD 0.8.0+. # @return [void] def warn_no_continuations end # Sets the logger level for the duration of the block # # @example # log.enter_level(Logger::ERROR) do # YARD.parse_string "def x; end" # end # @param [Fixnum] new_level the logger level for the duration of the block. # values can be found in Ruby's Logger class. # @yield the block with the logger temporarily set to +new_level+ def enter_level(new_level = level, &block) old_level, self.level = level, new_level yield self.level = old_level end private # Override this internal Logger method to clear line def add(*args) clear_line super(*args) end def clear_line return unless @progress_msg print_no_newline("\e[2K\r") end # Log format (from Logger implementation). Used by Logger internally def format_log(sev, time, prog, msg) "[#{sev.downcase}]: #{msg}\n" end end end