From 54be980b26004f01040e5537880d23338d3ffacf Mon Sep 17 00:00:00 2001 From: basuke Date: Sat, 16 Aug 2025 16:41:38 -0700 Subject: [PATCH] Add --quieter-after-error flag MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Implements a new flag that switches to quieter mode after the first error is encountered. Initially displays all output normally, but after an error occurs, only shows errors, results, and test outcomes (warnings and regular tasks are suppressed). - Add --quieter-after-error flag with -Q short option - Modify OutputHandler to track error state and dynamically switch modes - Add comprehensive tests covering the new behavior - Maintains backward compatibility with existing quiet/quieter modes 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- Sources/XcbeautifyLib/OutputHandler.swift | 23 +++++++- Sources/xcbeautify/Xcbeautify.swift | 5 +- .../OutputHandlerTests.swift | 58 +++++++++++++++++++ 3 files changed, 82 insertions(+), 4 deletions(-) diff --git a/Sources/XcbeautifyLib/OutputHandler.swift b/Sources/XcbeautifyLib/OutputHandler.swift index 4fde71d6..9776ae5f 100644 --- a/Sources/XcbeautifyLib/OutputHandler.swift +++ b/Sources/XcbeautifyLib/OutputHandler.swift @@ -13,6 +13,7 @@ import Foundation package class OutputHandler { let quiet: Bool let quieter: Bool + let quieterAfterError: Bool let isCI: Bool let writer: (String) -> Void @@ -26,9 +27,13 @@ package class OutputHandler { /// Ref: https://github.com/cpisciotta/xcbeautify/pull/15 private var lastFormatted: String? - package init(quiet: Bool, quieter: Bool, isCI: Bool = false, _ writer: @escaping (String) -> Void) { + /// Tracks if an error has been encountered when using quieterAfterError mode + private var errorEncountered = false + + package init(quiet: Bool, quieter: Bool, quieterAfterError: Bool = false, isCI: Bool = false, _ writer: @escaping (String) -> Void) { self.quiet = quiet self.quieter = quieter + self.quieterAfterError = quieterAfterError self.isCI = isCI self.writer = writer } @@ -36,14 +41,22 @@ package class OutputHandler { package func write(_ type: OutputType, _ content: String?) { guard let content else { return } - if !quiet, !quieter { + // Determine effective quiet/quieter mode + let effectiveQuiet = quiet || (quieterAfterError && errorEncountered) + let effectiveQuieter = quieter || (quieterAfterError && errorEncountered) + + if !effectiveQuiet, !effectiveQuieter { writer(content) + // Check after writing to activate quieter mode for next output + if quieterAfterError, type == .error { + errorEncountered = true + } return } switch type { case OutputType.warning: - if quieter { return } + if effectiveQuieter { return } fallthrough case OutputType.error: if let last = lastFormatted { @@ -51,6 +64,10 @@ package class OutputHandler { lastFormatted = nil } writer(content) + // Check after writing to activate quieter mode for next output + if quieterAfterError, type == .error { + errorEncountered = true + } case OutputType.issue: writer(content) case OutputType.result: diff --git a/Sources/xcbeautify/Xcbeautify.swift b/Sources/xcbeautify/Xcbeautify.swift index d3e7e0f0..38ca771e 100644 --- a/Sources/xcbeautify/Xcbeautify.swift +++ b/Sources/xcbeautify/Xcbeautify.swift @@ -29,6 +29,9 @@ struct Xcbeautify: ParsableCommand { @Flag(name: [.long, .customLong("qq", withSingleDash: true)], help: "Only print tasks that have errors.") var quieter = false + @Flag(name: [.customShort("Q"), .long], help: "Switch to quieter mode after the first error is encountered.") + var quieterAfterError = false + @Flag(name: [.long], help: "Preserves unbeautified output lines.") var preserveUnbeautified = false @@ -89,7 +92,7 @@ struct Xcbeautify: ParsableCommand { ) } - let output = OutputHandler(quiet: quiet, quieter: quieter, isCI: isCI) { print($0) } + let output = OutputHandler(quiet: quiet, quieter: quieter, quieterAfterError: quieterAfterError, isCI: isCI) { print($0) } let junitReporter = JUnitReporter() let parser = Parser() diff --git a/Tests/XcbeautifyLibTests/OutputHandlerTests.swift b/Tests/XcbeautifyLibTests/OutputHandlerTests.swift index f1097ee3..28f1b918 100644 --- a/Tests/XcbeautifyLibTests/OutputHandlerTests.swift +++ b/Tests/XcbeautifyLibTests/OutputHandlerTests.swift @@ -127,4 +127,62 @@ class OutputHandlerTests: XCTestCase { XCTAssertEqual(collector, ["test started", "error", "test completed", "result"]) } + + func testQuieterAfterErrorSwitchesToQuieterModeAfterFirstError() throws { + var collector: [String] = [] + let sut = OutputHandler(quiet: false, quieter: false, quieterAfterError: true, isCI: false) { content in + collector.append(content) + } + + // Before error, everything should be printed + sut.write(.task, "task 1") + sut.write(.warning, "warning 1") + sut.write(.result, "result 1") + + XCTAssertEqual(collector, ["task 1", "warning 1", "result 1"]) + + // Encounter an error + sut.write(.error, "error 1") + + XCTAssertEqual(collector, ["task 1", "warning 1", "result 1", "error 1"]) + + collector.removeAll() + + // After error, should behave like quieter mode + sut.write(.task, "task 2") + sut.write(.warning, "warning 2") // Should be suppressed + sut.write(.error, "error 2") // Errors should still show (with task 2 banner) + sut.write(.result, "result 2") // Results should still show + sut.write(.undefined, "undefined") // Should be suppressed + + // Note: task 2 appears because it's the banner for error 2 + XCTAssertEqual(collector, ["task 2", "error 2", "result 2"]) + } + + func testQuieterAfterErrorDoesNotAffectNormalQuietMode() throws { + var collector: [String] = [] + let sut = OutputHandler(quiet: true, quieter: false, quieterAfterError: true, isCI: false) { content in + collector.append(content) + } + + // Should behave like quiet mode even before error + sut.write(.task, "task 1") + sut.write(.warning, "warning 1") + sut.write(.result, "result 1") + + XCTAssertEqual(collector, ["task 1", "warning 1", "result 1"]) + + // After error, should still behave like quieter mode + sut.write(.error, "error 1") + + collector.removeAll() + + sut.write(.task, "task 2") + sut.write(.warning, "warning 2") // Should be suppressed after error + sut.write(.error, "error 2") + sut.write(.result, "result 2") + + // Note: task 2 appears because it's the banner for error 2 + XCTAssertEqual(collector, ["task 2", "error 2", "result 2"]) + } }