Repository: cpisciotta/xcbeautify (swift)

Issue:
[New Output] Support Explicit Dependency Output
### What's the uncaptured output?

```
        ➜ Explicit dependency on target 'Target' in project 'Project'
```

### Suggested Regex

_No response_

### Additional Context

Note: There is leading whitespace in the xcodebuild output.

Pull request description:

Patch:
diff --git a/Sources/XcbeautifyLib/CaptureGroups.swift b/Sources/XcbeautifyLib/CaptureGroups.swift
index 07f93706..272d1951 100644
--- a/Sources/XcbeautifyLib/CaptureGroups.swift
+++ b/Sources/XcbeautifyLib/CaptureGroups.swift
@@ -524,6 +524,22 @@ struct ExecutedWithSkippedCaptureGroup: ExecutedCaptureGroup {
     }
 }
 
+struct ExplicitDependencyCaptureGroup: CaptureGroup {
+    static let outputType: OutputType = .task
+
+    static let regex = Regex(pattern: #"^[ \t]*➜ Explicit dependency on target '([^']+)' in project '([^']+)'$"#)
+
+    let target: String
+    let project: String
+
+    init?(groups: [String]) {
+        assert(groups.count == 2)
+        guard let target = groups[safe: 0], let project = groups[safe: 1] else { return nil }
+        self.target = target
+        self.project = project
+    }
+}
+
 struct FailingTestCaptureGroup: CaptureGroup {
     static let outputType: OutputType = .error
 
diff --git a/Sources/XcbeautifyLib/Formatter.swift b/Sources/XcbeautifyLib/Formatter.swift
index 54d82f81..3938e41e 100644
--- a/Sources/XcbeautifyLib/Formatter.swift
+++ b/Sources/XcbeautifyLib/Formatter.swift
@@ -82,6 +82,8 @@ package struct Formatter {
             return renderer.formatExecutedWithoutSkipped(group: group)
         case let group as ExecutedWithSkippedCaptureGroup:
             return renderer.formatExecutedWithSkipped(group: group)
+        case let group as ExplicitDependencyCaptureGroup:
+            return renderer.formatExplicitDependencyCaptureGroup(group: group)
         case let group as FailingTestCaptureGroup:
             return renderer.formatFailingTest(group: group)
         case let group as FatalErrorCaptureGroup:
diff --git a/Sources/XcbeautifyLib/Parser.swift b/Sources/XcbeautifyLib/Parser.swift
index daad0c7d..a1bb1f07 100644
--- a/Sources/XcbeautifyLib/Parser.swift
+++ b/Sources/XcbeautifyLib/Parser.swift
@@ -24,6 +24,7 @@ package final class Parser {
         CopyPlistCaptureGroup.self,
         CopyStringsCaptureGroup.self,
         CpresourceCaptureGroup.self,
+        ExplicitDependencyCaptureGroup.self,
         FailingTestCaptureGroup.self,
         UIFailingTestCaptureGroup.self,
         RestartingTestCaptureGroup.self,
diff --git a/Sources/XcbeautifyLib/Renderers/OutputRendering.swift b/Sources/XcbeautifyLib/Renderers/OutputRendering.swift
index 3bfa47ef..3611594d 100644
--- a/Sources/XcbeautifyLib/Renderers/OutputRendering.swift
+++ b/Sources/XcbeautifyLib/Renderers/OutputRendering.swift
@@ -28,6 +28,7 @@ protocol OutputRendering {
     func formatError(group: any ErrorCaptureGroup) -> String
     func formatExecutedWithoutSkipped(group: ExecutedWithoutSkippedCaptureGroup) -> String
     func formatExecutedWithSkipped(group: ExecutedWithSkippedCaptureGroup) -> String
+    func formatExplicitDependencyCaptureGroup(group: ExplicitDependencyCaptureGroup) -> String?
     func formatFailingTest(group: FailingTestCaptureGroup) -> String
     func formatFileMissingError(group: FileMissingErrorCaptureGroup) -> String
     func formatGenerateCoverageData(group: GenerateCoverageDataCaptureGroup) -> String
@@ -155,6 +156,10 @@ extension OutputRendering {
         group.wholeResult
     }
 
+    func formatExplicitDependencyCaptureGroup(group: ExplicitDependencyCaptureGroup) -> String? {
+        nil
+    }
+
     func formatGenerateCoverageData(group: GenerateCoverageDataCaptureGroup) -> String {
         colored ? "\("Generating".s.Bold) code coverage data..." : "Generating code coverage data..."
     }


Test patch:
diff --git a/Tests/XcbeautifyLibTests/ParserTests.swift b/Tests/XcbeautifyLibTests/ParserTests.swift
index a943ac64..de59aa96 100644
--- a/Tests/XcbeautifyLibTests/ParserTests.swift
+++ b/Tests/XcbeautifyLibTests/ParserTests.swift
@@ -68,4 +68,11 @@ final class ParserTests: XCTestCase {
         XCTAssertEqual(captureGroup.filename, #"BirdFood+DataGeneration.swift"#)
         XCTAssertEqual(captureGroup.target, "BackyardBirdsData")
     }
+
+    func testMatchExplicitDependency() throws {
+        let input = #"        ➜ Explicit dependency on target 'BackyardBirdsData_BackyardBirdsData' in project 'Backyard Birds Data'"#
+        let captureGroup = try XCTUnwrap(parser.parse(line: input) as? ExplicitDependencyCaptureGroup)
+        XCTAssertEqual(captureGroup.target, "BackyardBirdsData_BackyardBirdsData")
+        XCTAssertEqual(captureGroup.project, "Backyard Birds Data")
+    }
 }
diff --git a/Tests/XcbeautifyLibTests/ParsingTests/ParsingTests.swift b/Tests/XcbeautifyLibTests/ParsingTests/ParsingTests.swift
index c1ed7501..a51aea1d 100644
--- a/Tests/XcbeautifyLibTests/ParsingTests/ParsingTests.swift
+++ b/Tests/XcbeautifyLibTests/ParsingTests/ParsingTests.swift
@@ -26,9 +26,9 @@ final class ParsingTests: XCTestCase {
         // Update this magic number whenever `uncapturedOutput` is less than the current magic number.
         // There's a regression whenever `uncapturedOutput` is greater than the current magic number.
         #if os(macOS)
-        XCTAssertEqual(uncapturedOutput, 277)
+        XCTAssertEqual(uncapturedOutput, 255)
         #else
-        XCTAssertEqual(uncapturedOutput, 293)
+        XCTAssertEqual(uncapturedOutput, 271)
         #endif
     }
 
@@ -56,9 +56,9 @@ final class ParsingTests: XCTestCase {
         // Update this magic number whenever `uncapturedOutput` is less than the current magic number.
         // There's a regression whenever `uncapturedOutput` is greater than the current magic number.
         #if os(macOS)
-        XCTAssertEqual(uncapturedOutput, 9223)
+        XCTAssertEqual(uncapturedOutput, 8519)
         #else
-        XCTAssertEqual(uncapturedOutput, 9791)
+        XCTAssertEqual(uncapturedOutput, 9087)
         #endif
     }
 }