diff --git a/lib/asciidoctor/cli/invoker.rb b/lib/asciidoctor/cli/invoker.rb index 74458f07b2..d531c2eebc 100644 --- a/lib/asciidoctor/cli/invoker.rb +++ b/lib/asciidoctor/cli/invoker.rb @@ -33,6 +33,7 @@ def invoke! return unless @options old_verbose = $VERBOSE + logger = LoggerManager.logger old_logger = old_logger_level = nil opts = {} infiles = [] @@ -67,13 +68,12 @@ def invoke! when 0 $VERBOSE = nil old_logger = LoggerManager.logger - LoggerManager.logger = NullLogger.new + LoggerManager.logger = (logger = NullLogger.new) when 1 $VERBOSE = false when 2 $VERBOSE = true - old_logger_level = LoggerManager.logger.level - LoggerManager.logger.level = ::Logger::Severity::DEBUG + old_logger_level, logger.level = logger.level, ::Logger::Severity::DEBUG end else opts[key] = val unless val.nil? @@ -131,6 +131,7 @@ def invoke! end end end + @code = 1 if (logger.respond_to? :max_severity) && (max_severity = logger.max_severity) && max_severity >= opts[:failure_level] rescue ::Exception => e if ::SignalException === e @code = e.signo @@ -155,7 +156,7 @@ def invoke! if old_logger LoggerManager.logger = old_logger elsif old_logger_level - LoggerManager.logger.level = old_logger_level + logger.level = old_logger_level end end diff --git a/lib/asciidoctor/cli/options.rb b/lib/asciidoctor/cli/options.rb index dd4828b794..50ce165a42 100644 --- a/lib/asciidoctor/cli/options.rb +++ b/lib/asciidoctor/cli/options.rb @@ -28,6 +28,7 @@ def initialize(options = {}) self[:base_dir] = options[:base_dir] self[:source_dir] = options[:source_dir] || nil self[:destination_dir] = options[:destination_dir] || nil + self[:failure_level] = ::Logger::Severity::FATAL self[:trace] = false self[:timings] = false end @@ -119,6 +120,10 @@ def parse!(args) 'may be specified more than once') do |path| (self[:requires] ||= []).concat(path.split ',') end + opts.on('--failure-level LEVEL', %w(warning WARNING error ERROR), 'set severity level that triggers a non-zero exit code: [WARN, ERROR] (default: FATAL)') do |level| + level = 'WARN' if (level = level.upcase) == 'WARNING' + self[:failure_level] = ::Logger::Severity.const_get level + end opts.on('-q', '--quiet', 'suppress warnings (default: false)') do |verbose| self[:verbose] = 0 end diff --git a/lib/asciidoctor/logging.rb b/lib/asciidoctor/logging.rb index 9b4aa06310..3d9efadc14 100644 --- a/lib/asciidoctor/logging.rb +++ b/lib/asciidoctor/logging.rb @@ -2,6 +2,8 @@ module Asciidoctor class Logger < ::Logger + attr_reader :max_severity + def initialize *args super self.progname = 'asciidoctor' @@ -9,6 +11,13 @@ def initialize *args self.level = WARN end + def add severity, message = nil, progname = nil + if (severity ||= UNKNOWN) > (@max_severity ||= severity) + @max_severity = severity + end + super + end + class BasicFormatter < Formatter SEVERITY_LABELS = { 'WARN' => 'WARNING', 'FATAL' => 'FAILED' } @@ -37,23 +46,32 @@ def initialize def add severity, message = nil, progname = nil message = block_given? ? yield : progname unless message - @messages << { :severity => SEVERITY_LABELS[severity], :message => message } + @messages << { :severity => SEVERITY_LABELS[severity || UNKNOWN], :message => message } true end + def clear + @messages.clear + end + def empty? @messages.empty? end - def clear - @messages.clear + def max_severity + empty? ? nil : @messages.map {|m| Severity.const_get m[:severity] }.max end end class NullLogger < ::Logger + attr_reader :max_severity + def initialize; end - def add *args + def add severity, message = nil, progname = nil + if (severity ||= UNKNOWN) > (@max_severity ||= severity) + @max_severity = severity + end true end end diff --git a/test/invoker_test.rb b/test/invoker_test.rb index 4d66a6e854..d44bb1dd45 100644 --- a/test/invoker_test.rb +++ b/test/invoker_test.rb @@ -179,6 +179,18 @@ def readlines assert_equal '', warnings end + test 'should return non-zero exit code if failure level is reached' do + input = <<-EOS +2. second +3. third + EOS + exit_code, messages = redirect_streams do |_, err| + [invoke_cli(%w(-q --failure-level=WARN -o /dev/null), '-') { input }.code, err.string] + end + assert_equal 1, exit_code + assert messages.empty? + end + test 'should report usage if no input file given' do redirect_streams do |out, err| invoke_cli [], nil diff --git a/test/options_test.rb b/test/options_test.rb index a8d097aaeb..a262998342 100644 --- a/test/options_test.rb +++ b/test/options_test.rb @@ -202,6 +202,33 @@ end end + test 'should set failure level to FATAL by default' do + options = Asciidoctor::Cli::Options.parse! %W(test/fixtures/sample.asciidoc) + assert_equal ::Logger::Severity::FATAL, options[:failure_level] + end + + test 'should allow failure level to be set to WARN' do + %w(w warn WARN warning WARNING).each do |val| + options = Asciidoctor::Cli::Options.parse!(%W(--failure-level=#{val} test/fixtures/sample.asciidoc)) + assert_equal ::Logger::Severity::WARN, options[:failure_level] + end + end + + test 'should allow failure level to be set to ERROR' do + %w(e err ERR error ERROR).each do |val| + options = Asciidoctor::Cli::Options.parse!(%W(--failure-level=#{val} test/fixtures/sample.asciidoc)) + assert_equal ::Logger::Severity::ERROR, options[:failure_level] + end + end + + test 'should not allow failure level to be set to unknown value' do + exit_code, messages = redirect_streams do |_, err| + [(Asciidoctor::Cli::Options.parse! %W(--failure-level=foobar test/fixtures/sample.asciidoc)), err.string] + end + assert_equal 1, exit_code + assert_includes messages, 'invalid argument: --failure-level=foobar' + end + test 'should set verbose to 2 when -v flag is specified' do options = Asciidoctor::Cli::Options.parse!(%w(-v test/fixtures/sample.asciidoc)) assert_equal 2, options[:verbose]