Skip to content

Commit

Permalink
Merge pull request #84 from nevinera/nev-42--extract-logging-facility
Browse files Browse the repository at this point in the history
Extract a Presenter class from the Entrypoint
  • Loading branch information
nevinera authored May 30, 2023
2 parents e8b7495 + 8227390 commit 5164fb4
Show file tree
Hide file tree
Showing 12 changed files with 243 additions and 147 deletions.
1 change: 1 addition & 0 deletions .quiet_quality.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,4 @@ executor: concurrent
comparison_branch: main
changed_files: false
filter_messages: false
logging: light
6 changes: 3 additions & 3 deletions lib/quiet_quality/cli/arg_parser.rb
Original file line number Diff line number Diff line change
Expand Up @@ -138,15 +138,15 @@ def setup_filter_messages_options(parser)

def setup_logging_options(parser)
parser.on("-l", "--light", "Print aggregated results only") do
set_global_option(:logging, Logging::LIGHT)
set_global_option(:logging, Config::Logging::LIGHT)
end

parser.on("-q", "--quiet", "Don't print results, only return a status code") do
set_global_option(:logging, Logging::QUIET)
set_global_option(:logging, Config::Logging::QUIET)
end

parser.on("-L", "--logging LEVEL", "Specify logging mode that results will be returned in. Valid options: light, quiet") do |level|
validate_value_from("logging level", level, Logging::LEVELS)
validate_value_from("logging level", level, Config::Logging::LEVELS)
set_global_option(:logging, level.to_sym)
end
end
Expand Down
69 changes: 17 additions & 52 deletions lib/quiet_quality/cli/entrypoint.rb
Original file line number Diff line number Diff line change
Expand Up @@ -33,12 +33,21 @@ def successful?

attr_reader :argv, :output_stream, :error_stream

def log_results
return if quiet_logging?
return log_light_outcomes if light_logging?
def logger
@_logger ||= QuietQuality::Logger.new(stream: error_stream, logging: options.logging)
end

def presenter
@_presenter ||= Presenter.new(
logger: logger,
logging: options.logging,
outcomes: executor.outcomes,
messages: executor.messages
)
end

log_outcomes
log_messages
def log_results
presenter.log_results
end

def arg_parser
Expand Down Expand Up @@ -70,15 +79,15 @@ def no_tools?
end

def log_help_text
error_stream.puts(arg_parser.help_text)
logger.puts(arg_parser.help_text)
end

def log_version_text
error_stream.puts(QuietQuality::VERSION)
logger.puts(QuietQuality::VERSION)
end

def log_no_tools_text
error_stream.puts(<<~TEXT)
logger.puts(<<~TEXT)
You must specify one or more tools to run, either on the command-line or in the
default_tools key in a configuration file.
TEXT
Expand Down Expand Up @@ -110,55 +119,11 @@ def executed
@_executed = executor
end

def log_outcomes
executed.outcomes.each do |outcome|
result = outcome.success? ? "Passed" : "Failed"
error_stream.puts "--- #{result}: #{outcome.tool}"
end
end

def log_message(msg)
line_range =
if msg.start_line == msg.stop_line
msg.start_line.to_s
else
"#{msg.start_line}-#{msg.stop_line}"
end
rule_string = msg.rule ? " [#{msg.rule}]" : ""
truncated_body = msg.body.gsub(/ *\n */, "\\n").slice(0, 120)
error_stream.puts " #{msg.path}:#{line_range}#{rule_string} #{truncated_body}"
end

def log_messages
return unless executed.messages.any?
error_stream.puts "\n\n#{executed.messages.count} messages:"
executed.messages.each { |msg| log_message(msg) }
end

def annotate_messages
return unless options.annotator
annotator = options.annotator.new(output_stream: output_stream)
annotator.annotate!(executed.messages)
end

def log_light_outcomes
msg = "#{total_outcomes} tools executed: #{successful_outcomes.count} passed, #{failed_outcomes.count} failed"
msg += " (#{failed_outcomes.map(&:tool).uniq.join(", ")})" unless failed_outcomes.empty?

output_stream.puts msg
end

def total_outcomes
@_total_outcomes ||= executed.outcomes.count
end

def successful_outcomes
@_successful_outcomes ||= executed.successful_outcomes
end

def failed_outcomes
@_failed_outcomes ||= executed.failed_outcomes
end
end
end
end
77 changes: 77 additions & 0 deletions lib/quiet_quality/cli/presenter.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
module QuietQuality
module Cli
class Presenter
def initialize(logger:, logging:, outcomes:, messages:)
@logger = logger
@logging = logging
@outcomes = outcomes
@messages = messages
end

def log_results
return if logging.quiet?

if logging.light?
log_light_outcomes
else
log_outcomes
log_messages
end
end

private

attr_reader :logger, :logging, :outcomes, :messages

def failed_outcomes
@_failed_outcomes ||= outcomes.select(&:failure?)
end

def successful_outcomes
@_successful_outcomes ||= outcomes.select(&:success?)
end

def log_light_outcomes
line = "%d tools executed: %d passed, %d failed" % [
outcomes.count,
successful_outcomes.count,
failed_outcomes.count
]
line += " (#{failed_outcomes.map(&:tool).join(", ")})" if failed_outcomes.any?
logger.puts line
end

def log_outcomes
outcomes.each do |outcome|
result = outcome.success? ? "Passed" : "Failed"
logger.puts "--- #{result}: #{outcome.tool}"
end
end

def log_messages
return unless messages.any?
logger.puts "\n\n#{messages.count} messages:"
messages.each { |msg| log_message(msg) }
end

def line_range_for(msg)
if msg.start_line == msg.stop_line
msg.start_line.to_s
else
"#{msg.start_line}-#{msg.stop_line}"
end
end

def reduce_text(s, length)
s.gsub(/ *\n */, "\\n").slice(0, length)
end

def log_message(msg)
line_range = line_range_for(msg)
rule_string = msg.rule ? " [#{msg.rule}]" : ""
truncated_body = reduce_text(msg.body, 120)
logger.puts " #{msg.path}:#{line_range}#{rule_string} #{truncated_body}"
end
end
end
end
23 changes: 23 additions & 0 deletions lib/quiet_quality/config/logging.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
module QuietQuality
module Config
class Logging
LIGHT = :light
QUIET = :quiet
LEVELS = [LIGHT, QUIET].freeze

attr_accessor :level

def initialize(level: nil)
@level = level
end

def light?
@level == LIGHT
end

def quiet?
@level == QUIET
end
end
end
end
17 changes: 17 additions & 0 deletions lib/quiet_quality/logger.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
module QuietQuality
class Logger
def initialize(stream:, logging:)
@stream = stream
@logging = logging
end

def puts(s)
return if logging.quiet?
stream.puts(s)
end

private

attr_reader :stream, :logging
end
end
21 changes: 0 additions & 21 deletions lib/quiet_quality/logging.rb

This file was deleted.

95 changes: 26 additions & 69 deletions spec/quiet_quality/cli/entrypoint_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,10 @@
let(:config_builder) { instance_double(QuietQuality::Config::Builder, options: options) }
before { allow(QuietQuality::Config::Builder).to receive(:new).and_return(config_builder) }

let(:presenter_class) { QuietQuality::Cli::Presenter }
let(:presenter) { instance_double(presenter_class, log_results: nil) }
before { allow(presenter_class).to receive(:new).and_return(presenter) }

describe "#execute" do
subject(:execute) { entrypoint.execute }

Expand All @@ -39,10 +43,15 @@
it { is_expected.to eq(entrypoint) }
it { is_expected.to be_successful }

it "logs the outcomes properly" do
it "presents the outcomes properly" do
execute
expect(error_stream).to have_received(:puts).with("--- Passed: rspec")
expect(error_stream).to have_received(:puts).with("--- Passed: rubocop")
expect(presenter_class).to have_received(:new).with(
logger: an_instance_of(QuietQuality::Logger),
logging: options.logging,
outcomes: outcomes,
messages: messages
)
expect(presenter).to have_received(:log_results)
end

context "when annotation is requested" do
Expand Down Expand Up @@ -75,33 +84,27 @@
it { is_expected.to eq(entrypoint) }
it { is_expected.not_to be_successful }

shared_examples "annotations are requested" do
it "writes the proper annotations to stdout" do
execute
expect(output_stream).to have_received(:puts).with("::warning file=foo.rb,line=1::Msg1")
expect(output_stream).to have_received(:puts).with("::warning file=bar.rb,line=2,title=Title::Msg2")
expect(output_stream).to have_received(:puts).with("::warning file=baz.rb,line=5::Msg3")
end
end

it "logs the outcomes properly" do
it "presents the outcomes properly" do
execute
expect(error_stream).to have_received(:puts).with("--- Failed: rspec")
expect(error_stream).to have_received(:puts).with("--- Passed: rubocop")
end

it "logs the messages properly" do
execute
expect(error_stream).to have_received(:puts).with(" foo.rb:1 Msg1")
expect(error_stream).to have_received(:puts).with(" bar.rb:2 [Title] Msg2")
expect(error_stream).to have_received(:puts).with(" baz.rb:3-7 Msg3")
expect(presenter_class).to have_received(:new).with(
logger: an_instance_of(QuietQuality::Logger),
logging: options.logging,
outcomes: outcomes,
messages: messages
)
expect(presenter).to have_received(:log_results)
end

context "when annotation is requested" do
let(:argv) { ["--annotate", "github_stdout"] }
let(:options) { build_options(annotator: :github_stdout, rubocop: {}, rspec: {}) }

include_examples "annotations are requested"
it "writes the proper annotations to stdout" do
execute
expect(output_stream).to have_received(:puts).with("::warning file=foo.rb,line=1::Msg1")
expect(output_stream).to have_received(:puts).with("::warning file=bar.rb,line=2,title=Title::Msg2")
expect(output_stream).to have_received(:puts).with("::warning file=baz.rb,line=5::Msg3")
end
end

context "when annotation is not requested" do
Expand All @@ -112,52 +115,6 @@
expect(output_stream).not_to have_received(:puts)
end
end

context "when logging is quiet" do
let(:options) { build_options(logging: :quiet, rubocop: {}) }

it "does not print the messages" do
execute
expect(output_stream).not_to have_received(:puts)
expect(error_stream).not_to have_received(:puts)
end

context "with annotations requested" do
let(:options) { build_options(logging: :quiet, annotator: :github_stdout, rubocop: {}) }

include_examples "annotations are requested"
end
end

context "when logging is light" do
let(:options) { build_options(logging: :light, rubocop: {}, rspec: {}) }

it "does not print the messages" do
execute
expect(error_stream).not_to have_received(:puts).with(" foo.rb:1 Msg1")
expect(error_stream).not_to have_received(:puts).with(" bar.rb:2 [Title] Msg2")
expect(error_stream).not_to have_received(:puts).with(" baz.rb:3-7 Msg3")
end

it "does not print the standard outcomes" do
execute
expect(error_stream).not_to have_received(:puts).with("--- Failed: rspec")
expect(error_stream).not_to have_received(:puts).with("--- Passed: rubocop")
end

it "prints the aggregated outcomes" do
execute
expect(output_stream).to have_received(:puts).with(
"2 tools executed: 1 passed, 1 failed (rspec)"
)
end

context "when annotations are requested" do
let(:options) { build_options(logging: :light, annotator: :github_stdout, rubocop: {}) }

include_examples "annotations are requested"
end
end
end

context "when asked for --help" do
Expand Down
Loading

0 comments on commit 5164fb4

Please sign in to comment.