class Nanoc::CLI::ErrorHandler
Catches errors and prints nice diagnostic messages, then exits.
@api private
Constants
- GEM_NAMES
A hash that contains the name of the gem for a given required file. If a `#require` fails, the gem name is looked up in this hash.
Public Class Methods
Disables error handling. This is used by the test cases to prevent error from being handled by the CLI
while tests are running.
# File lib/nanoc/cli/error_handler.rb, line 21 def self.disable @disabled = true end
Re-enables error handling after it was disabled. This is used by the test cases to prevent error from being handled by the CLI
while tests are running.
# File lib/nanoc/cli/error_handler.rb, line 28 def self.enable @disabled = false end
Enables error handling in the given block.
@return [void]
# File lib/nanoc/cli/error_handler.rb, line 11 def self.handle_while(exit_on_error: true) if @disabled yield else new.handle_while(exit_on_error: exit_on_error) { yield } end end
Prints the given error to stderr. Includes message, possible resolution (see {#resolution_for}), compilation stack, backtrace, etc.
@param [Error] error The error that should be described
@return [void]
# File lib/nanoc/cli/error_handler.rb, line 85 def self.print_error(error) new.print_error(error) end
Public Instance Methods
# File lib/nanoc/cli/error_handler.rb, line 64 def handle_error(error, exit_on_error:) if trivial?(error) $stderr.puts $stderr.puts "Error: #{error.message}" resolution = resolution_for(error) if resolution $stderr.puts $stderr.puts resolution end else print_error(error) end exit(1) if exit_on_error end
Enables error handling in the given block. This method should not be called directly; use {Nanoc::CLI::ErrorHandler.handle_while} instead.
@return [void]
# File lib/nanoc/cli/error_handler.rb, line 36 def handle_while(exit_on_error:) # Set exit handler %w[INT TERM].each do |signal| Signal.trap(signal) do puts exit!(0) end end # Set stack trace dump handler if !defined?(RUBY_ENGINE) || RUBY_ENGINE != 'jruby' begin Signal.trap('USR1') do puts 'Caught USR1; dumping a stack trace' puts caller.map { |i| " #{i}" }.join("\n") end rescue ArgumentError end end # Run yield rescue Interrupt exit(1) rescue StandardError, ScriptError => e handle_error(e, exit_on_error: exit_on_error) end
Prints the given error to stderr. Includes message, possible resolution (see {#resolution_for}), compilation stack, backtrace, etc.
@param [Error] error The error that should be described
@return [void]
# File lib/nanoc/cli/error_handler.rb, line 95 def print_error(error) write_compact_error(error, $stderr) File.open('crash.log', 'w') do |io| cio = Nanoc::CLI.wrap_in_cleaning_stream(io) cio.add_stream_cleaner(::Nanoc::CLI::StreamCleaners::ANSIColors) write_verbose_error(error, cio) end end
@api private
# File lib/nanoc/cli/error_handler.rb, line 146 def trivial?(error) case error when Nanoc::Core::TrivialError, Errno::EADDRINUSE true when LoadError GEM_NAMES.key?(gem_name_from_load_error(error)) else false end end
Writes a compact representation of the error, suitable for a terminal, on the given stream (probably stderr).
@param [Error] error The error that should be described
@param [IO] stream The stream to write the description too
@return [void]
# File lib/nanoc/cli/error_handler.rb, line 113 def write_compact_error(error, stream) stream.puts stream.puts 'Captain! We’ve been hit!' write_error_message(stream, error) write_item_rep(stream, error) write_stack_trace(stream, error) stream.puts stream.puts 'A detailed crash log has been written to ./crash.log.' end
Writes a verbose representation of the error on the given stream.
@param [Error] error The error that should be described
@param [IO] stream The stream to write the description too
@return [void]
# File lib/nanoc/cli/error_handler.rb, line 132 def write_verbose_error(error, stream) stream.puts "Crashlog created at #{Time.now}" write_error_message(stream, error, verbose: true) write_item_rep(stream, error, verbose: true) write_stack_trace(stream, error, verbose: true) write_version_information(stream, verbose: true) write_system_information(stream, verbose: true) write_installed_gems(stream, verbose: true) write_gemfile_lock(stream, verbose: true) write_load_paths(stream, verbose: true) end
Protected Instance Methods
# File lib/nanoc/cli/error_handler.rb, line 249 def gem_name_from_load_error(error) matches = error.message.match(/(no such file to load|cannot load such file) -- ([^\s]+)/) return nil if matches.nil? GEM_NAMES[matches[2]] end
@return [Hash<String, Array>] A hash containing the gem names as keys and gem versions as value
# File lib/nanoc/cli/error_handler.rb, line 160 def gems_and_versions gems = {} Gem::Specification.find_all.sort_by { |s| [s.name, s.version] }.each do |spec| gems[spec.name] ||= [] gems[spec.name] << spec.version.to_s end gems end
# File lib/nanoc/cli/error_handler.rb, line 287 def message_for_error(error) case error when JsonSchema::AggregateError "\n" + error.errors.map { |e| " * #{e.pointer}: #{e.message}" }.join("\n") else error.message end end
Attempts to find a resolution for the given error, or nil if no resolution can be automatically obtained.
@param [Error] error The error to find a resolution for
@return [String] The resolution for the given error
# File lib/nanoc/cli/error_handler.rb, line 219 def resolution_for(error) error = unwrap_error(error) case error when LoadError gem_name = gem_name_from_load_error(error) if gem_name if using_bundler? <<~RES 1. Add `gem '#{gem_name}'` to your Gemfile 2. Run `bundle install` 3. Re-run this command RES else "Install the '#{gem_name}' gem using `gem install #{gem_name}`." end end when RuntimeError if /^can't modify frozen/.match?(error.message) 'You attempted to modify immutable data. Some data cannot ' \ 'be modified once compilation has started. Such data includes ' \ 'content and attributes of items and layouts, and filter arguments.' end when Errno::EADDRINUSE 'There already is a server running. Either shut down that one, or ' \ 'specify a different port to run this server on.' end end
# File lib/nanoc/cli/error_handler.rb, line 260 def ruby_version RUBY_VERSION end
# File lib/nanoc/cli/error_handler.rb, line 345 def unwrap_error(e) case e when Nanoc::Core::Errors::CompilationError e.unwrap else e end end
# File lib/nanoc/cli/error_handler.rb, line 256 def using_bundler? defined?(Bundler) && Bundler::SharedHelpers.in_bundle? end
# File lib/nanoc/cli/error_handler.rb, line 273 def write_error_message(stream, error, verbose: false) write_section_header(stream, 'Message', verbose: verbose) error = unwrap_error(error) message = "#{error.class}: #{message_for_error(error)}" unless verbose message = "\e[1m\e[31m" + message + "\e[0m" end stream.puts message resolution = resolution_for(error) stream.puts resolution.to_s if resolution end
# File lib/nanoc/cli/error_handler.rb, line 331 def write_gemfile_lock(stream, verbose: false) if File.exist?('Gemfile.lock') write_section_header(stream, 'Gemfile.lock', verbose: verbose) stream.puts File.read('Gemfile.lock') end end
# File lib/nanoc/cli/error_handler.rb, line 324 def write_installed_gems(stream, verbose: false) write_section_header(stream, 'Installed gems', verbose: verbose) gems_and_versions.each do |g| stream.puts " #{g.first} #{g.last.join(', ')}" end end
# File lib/nanoc/cli/error_handler.rb, line 296 def write_item_rep(stream, error, verbose: false) return unless error.is_a?(Nanoc::Core::Errors::CompilationError) write_section_header(stream, 'Item being compiled', verbose: verbose) item_rep = error.item_rep stream.puts "Current item: #{item_rep.item.identifier} (#{item_rep.name.inspect} representation)" end
# File lib/nanoc/cli/error_handler.rb, line 338 def write_load_paths(stream, verbose: false) write_section_header(stream, 'Load paths', verbose: verbose) $LOAD_PATH.each_with_index do |i, index| stream.puts " #{index}. #{i}" end end
# File lib/nanoc/cli/error_handler.rb, line 264 def write_section_header(stream, title, verbose: false) stream.puts if verbose stream.puts '===== ' + title.upcase + ':' stream.puts end end
# File lib/nanoc/cli/error_handler.rb, line 305 def write_stack_trace(stream, error, verbose: false) write_section_header(stream, 'Stack trace', verbose: verbose) writer = Nanoc::CLI::StackTraceWriter.new(stream) writer.write(unwrap_error(error), verbose: verbose) end
# File lib/nanoc/cli/error_handler.rb, line 317 def write_system_information(stream, verbose: false) uname = `uname -a` write_section_header(stream, 'System information', verbose: verbose) stream.puts uname rescue Errno::ENOENT end
# File lib/nanoc/cli/error_handler.rb, line 312 def write_version_information(stream, verbose: false) write_section_header(stream, 'Version information', verbose: verbose) stream.puts Nanoc::Core.version_information end