From 7aba335a9eac87cb136efd3ef1981a44656da9f2 Mon Sep 17 00:00:00 2001 From: Samuel Williams Date: Wed, 6 Nov 2024 14:23:43 +1300 Subject: [PATCH] Minor refactoring of error handling. --- lib/console/capture.rb | 3 ++- lib/console/event/failure.rb | 4 ++-- lib/console/logger.rb | 21 ++++++--------------- lib/console/output/default.rb | 7 +++++-- lib/console/output/failure.rb | 32 ++++++++++++++++++++++++++++++++ lib/console/output/null.rb | 4 ++++ lib/console/output/serialized.rb | 9 +++++++-- lib/console/output/terminal.rb | 5 +++++ lib/console/output/wrapper.rb | 6 ++++++ releases.md | 16 ++++++++++++++++ test/console.rb | 2 +- test/console/output.rb | 6 +++--- test/console/output/default.rb | 14 +++----------- 13 files changed, 92 insertions(+), 37 deletions(-) create mode 100644 lib/console/output/failure.rb diff --git a/lib/console/capture.rb b/lib/console/capture.rb index a8dd108..10afc43 100644 --- a/lib/console/capture.rb +++ b/lib/console/capture.rb @@ -4,6 +4,7 @@ # Copyright, 2019-2024, by Samuel Williams. require_relative "filter" +require_relative "output/failure" module Console # A general sink which captures all events into a buffer. @@ -58,7 +59,7 @@ def verbose? @verbose end - def call(subject = nil, *arguments, severity: UNKNOWN, event: nil, **options, &block) + def call(subject = nil, *arguments, severity: UNKNOWN, event: nil, **options, &block) record = { time: ::Time.now.iso8601, severity: severity, diff --git a/lib/console/event/failure.rb b/lib/console/event/failure.rb index 27d5fdf..62bce16 100644 --- a/lib/console/event/failure.rb +++ b/lib/console/event/failure.rb @@ -28,9 +28,9 @@ def self.for(exception) def self.log(subject, exception, **options) Console.error(subject, **self.for(exception).to_hash, **options) end - + attr_reader :exception - + def initialize(exception, root = Dir.getwd) @exception = exception @root = root diff --git a/lib/console/logger.rb b/lib/console/logger.rb index 2fdac03..8d0ea02 100644 --- a/lib/console/logger.rb +++ b/lib/console/logger.rb @@ -6,6 +6,8 @@ # Copyright, 2021, by Robert Schulze. require_relative "output" +require_relative "output/failure" + require_relative "filter" require_relative "event" require_relative "resolver" @@ -47,6 +49,7 @@ def self.default_logger(output = $stderr, env = ENV, **options) end output = Output.new(output, env, **options) + logger = self.new(output, **options) Resolver.default_resolver(logger) @@ -61,6 +64,9 @@ def self.local DEFAULT_LEVEL = 1 def initialize(output, **options) + # This is the expected default behaviour, but it may be nice to have a way to override it. + output = Output::Failure.new(output, **options) + super(output, **options) end @@ -69,20 +75,5 @@ def progress(subject, total, **options) Progress.new(subject, total, **options) end - - def error(subject, *arguments, **options, &block) - # This is a special case where we want to create a failure event from an exception. - # It's common to see `Console.error(self, exception)` in code. - if arguments.first.is_a?(Exception) - exception = arguments.shift - options[:event] = Event::Failure.for(exception) - end - - super - end - - def failure(subject, exception, **options) - error(subject, event: Event::Failure.for(exception), **options) - end end end diff --git a/lib/console/output/default.rb b/lib/console/output/default.rb index e6c639f..a4de264 100644 --- a/lib/console/output/default.rb +++ b/lib/console/output/default.rb @@ -5,6 +5,7 @@ require_relative "terminal" require_relative "serialized" +require_relative "failure" module Console module Output @@ -13,10 +14,12 @@ def self.new(output, **options) output ||= $stderr if output.tty? - Terminal.new(output, **options) + output = Terminal.new(output, **options) else - Serialized.new(output, **options) + output = Serialized.new(output, **options) end + + return output end end end diff --git a/lib/console/output/failure.rb b/lib/console/output/failure.rb new file mode 100644 index 0000000..48e9b3c --- /dev/null +++ b/lib/console/output/failure.rb @@ -0,0 +1,32 @@ +# frozen_string_literal: true + +# Released under the MIT License. +# Copyright, 2021-2024, by Samuel Williams. + +require_relative "wrapper" +require_relative "../event/failure" + +module Console + module Output + # A wrapper for outputting failure messages, which can include exceptions. + class Failure < Wrapper + def initialize(output, **options) + super(output, **options) + end + + # The exception must be either the last argument or passed as an option. + def call(subject = nil, *arguments, exception: nil, **options, &block) + if exception.nil? + last = arguments.last + if last.is_a?(Exception) + options[:event] = Event::Failure.for(last) + end + else + options[:event] = Event::Failure.for(exception) + end + + super(subject, *arguments, **options) + end + end + end +end diff --git a/lib/console/output/null.rb b/lib/console/output/null.rb index 9067e0c..b1839a5 100644 --- a/lib/console/output/null.rb +++ b/lib/console/output/null.rb @@ -9,6 +9,10 @@ class Null def initialize(...) end + def last_output + self + end + def call(...) # Do nothing. end diff --git a/lib/console/output/serialized.rb b/lib/console/output/serialized.rb index 1aef2ae..4de7bc2 100644 --- a/lib/console/output/serialized.rb +++ b/lib/console/output/serialized.rb @@ -10,11 +10,16 @@ module Console module Output class Serialized - def initialize(output, format: Format.default, **options) - @io = output + def initialize(io, format: Format.default, **options) + @io = io @format = format end + # This a final output that then writes to an IO object. + def last_output + self + end + attr :io attr :format diff --git a/lib/console/output/terminal.rb b/lib/console/output/terminal.rb index a4cd9a3..2e297d9 100644 --- a/lib/console/output/terminal.rb +++ b/lib/console/output/terminal.rb @@ -78,6 +78,11 @@ def initialize(output, verbose: nil, start_at: Terminal.start_at!, format: nil, self.register_formatters end + # This a final output that then writes to an IO object. + def last_output + self + end + attr :io attr_accessor :verbose diff --git a/lib/console/output/wrapper.rb b/lib/console/output/wrapper.rb index 045fec0..8082573 100644 --- a/lib/console/output/wrapper.rb +++ b/lib/console/output/wrapper.rb @@ -10,6 +10,12 @@ def initialize(delegate, **options) @delegate = delegate end + attr :delegate + + def last_output + @delegate.last_output + end + def verbose!(value = true) @delegate.verbose!(value) end diff --git a/releases.md b/releases.md index 231b086..718f225 100644 --- a/releases.md +++ b/releases.md @@ -3,6 +3,22 @@ ## Unreleased - Don't make `Kernel#warn` redirection to `Console.warn` the default behavior, you must `require 'console/warn'` to enable it. + - Remove deprecated `Console::Logger#failure`. + +### Consistent Handling of Exceptions + +`Console.call` and all wrapper methods will now consistently handle exceptions that are the last positional argument or keyword argument. This means that the following code will work as expected: + +```ruby +begin +rescue => error + # Last positional argument: + Console.warn(self, "There may be an issue", error) + + # Keyword argument (preferable): + Console.error(self, "There is an issue", exception: error) +end +``` ## v1.28.0 diff --git a/test/console.rb b/test/console.rb index 8eb1a4c..69d7f70 100644 --- a/test/console.rb +++ b/test/console.rb @@ -83,7 +83,7 @@ event: have_keys( type: be == :failure, message: be == "It failed!", - ), + ) ) end diff --git a/test/console/output.rb b/test/console/output.rb index 5cc26ac..b759e1a 100644 --- a/test/console/output.rb +++ b/test/console/output.rb @@ -17,7 +17,7 @@ let(:capture) {File.open("/tmp/console.log", "w")} it "should use a serialized format" do - expect(output).to be_a(Console::Output::Serialized) + expect(output.last_output).to be_a(Console::Output::Serialized) end end @@ -27,13 +27,13 @@ it "should use a terminal format" do expect($stderr).to receive(:tty?).twice.and_return(true) - expect(output).to be_a Console::Output::Terminal + expect(output.last_output).to be_a Console::Output::Terminal end end with env: {"CONSOLE_OUTPUT" => "JSON"} do it "can set output to Serialized and format to JSON" do - expect(output).to be_a Console::Output::Serialized + expect(output.last_output).to be_a Console::Output::Serialized expect(output.format).to be_a(Console::Format::Safe) end end diff --git a/test/console/output/default.rb b/test/console/output/default.rb index 4b970d6..a00ff7f 100644 --- a/test/console/output/default.rb +++ b/test/console/output/default.rb @@ -7,18 +7,10 @@ require "console/capture" describe Console::Output::Default do - let(:output) {nil} - let(:logger) {subject.new(output)} - - def final_output(output) - if output.respond_to?(:output) - final_output(output.output) - else - output - end - end + let(:io) {nil} + let(:output) {subject.new(io)} it "should output to $stderr by default" do - expect(final_output(logger).io).to be == $stderr + expect(output.last_output.io).to be == $stderr end end