Repository: cpisciotta/xcbeautify (swift)

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

```
DataModelCodegen /path/to/data/model/something.xcdatamodeld (in target 'Target' from project 'Project')
```

### Suggested Regex

_No response_

### Suggested Output

_No response_

### Additional Context

_No response_

Pull request description:
Closes https://github.com/cpisciotta/xcbeautify/issues/189.

### Changes

Add a capture group for the DataModelCodegen output.

### Testing

- Added new unit tests

Patch:
diff --git a/Sources/XcbeautifyLib/CaptureGroups.swift b/Sources/XcbeautifyLib/CaptureGroups.swift
index 056b07a7..851f93f6 100644
--- a/Sources/XcbeautifyLib/CaptureGroups.swift
+++ b/Sources/XcbeautifyLib/CaptureGroups.swift
@@ -2507,3 +2507,25 @@ struct NoteCaptureGroup: CaptureGroup {
         self.note = note
     }
 }
+
+struct DataModelCodegenCaptureGroup: CaptureGroup {
+    static let outputType: OutputType = .task
+
+    /// Regular expression captured groups:
+    /// $1 = path
+    /// $2 = target
+    /// $3 = project
+    static let regex = XCRegex(pattern: #"^DataModelCodegen (.*\.xcdatamodeld) \(in target '(.*)' from project '(.*)'\)$"#)
+
+    let path: String
+    let target: String
+    let project: String
+
+    init?(groups: [String]) {
+        assert(groups.count == 3)
+        guard let path = groups[safe: 0], let target = groups[safe: 1], let project = groups[safe: 2] else { return nil }
+        self.path = path
+        self.target = target
+        self.project = project
+    }
+}
diff --git a/Sources/XcbeautifyLib/Formatter.swift b/Sources/XcbeautifyLib/Formatter.swift
index 006b8508..4fdfb3a5 100644
--- a/Sources/XcbeautifyLib/Formatter.swift
+++ b/Sources/XcbeautifyLib/Formatter.swift
@@ -97,6 +97,8 @@ package struct Formatter {
             return renderer.formatCreateUniversalBinary(group: group)
         case let group as CursorCaptureGroup:
             return renderer.formatCursor(group: group)
+        case let group as DataModelCodegenCaptureGroup:
+            return renderer.formatDataModelCodegen(group: group)
         case let group as DetectedEncodingCaptureGroup:
             return renderer.formatDetectedEncoding(group: group)
         case let group as DuplicateLocalizedStringKeyCaptureGroup:
diff --git a/Sources/XcbeautifyLib/Parser.swift b/Sources/XcbeautifyLib/Parser.swift
index a84fdf85..852ddac8 100644
--- a/Sources/XcbeautifyLib/Parser.swift
+++ b/Sources/XcbeautifyLib/Parser.swift
@@ -140,6 +140,7 @@ package final class Parser {
         MkDirCaptureGroup.self,
         NonPCHClangCommandCaptureGroup.self,
         NoteCaptureGroup.self,
+        DataModelCodegenCaptureGroup.self,
     ]
 
     #if DEBUG
diff --git a/Sources/XcbeautifyLib/Renderers/OutputRendering.swift b/Sources/XcbeautifyLib/Renderers/OutputRendering.swift
index a6f86694..3d62db65 100644
--- a/Sources/XcbeautifyLib/Renderers/OutputRendering.swift
+++ b/Sources/XcbeautifyLib/Renderers/OutputRendering.swift
@@ -37,6 +37,7 @@ protocol OutputRendering {
     func formatCoverageReport(group: GeneratedCoverageReportCaptureGroup) -> String
     func formatCreateBuildDirectory(group: CreateBuildDirectoryCaptureGroup) -> String?
     func formatCursor(group: CursorCaptureGroup) -> String?
+    func formatDataModelCodegen(group: DataModelCodegenCaptureGroup) -> String
     func formatDetectedEncoding(group: DetectedEncodingCaptureGroup) -> String?
     func formatDuplicateLocalizedStringKey(group: DuplicateLocalizedStringKeyCaptureGroup) -> String
     func formatEmitSwiftModule(group: EmitSwiftModuleCaptureGroup) -> String?
@@ -766,4 +767,8 @@ extension OutputRendering {
     func formatNote(group: NoteCaptureGroup) -> String {
         colored ? "note: ".s.Bold.f.Cyan + group.note : "note: " + group.note
     }
+
+    func formatDataModelCodegen(group: DataModelCodegenCaptureGroup) -> String {
+        colored ? "[\(group.target.f.Cyan)] \("DataModelCodegen".s.Bold) \(group.path)" : "[\(group.target)] DataModelCodegen \(group.path)"
+    }
 }


Test patch:
diff --git a/Tests/XcbeautifyLibTests/ParserTests.swift b/Tests/XcbeautifyLibTests/ParserTests.swift
index db6ddf81..8b79b6da 100644
--- a/Tests/XcbeautifyLibTests/ParserTests.swift
+++ b/Tests/XcbeautifyLibTests/ParserTests.swift
@@ -457,4 +457,40 @@ final class ParserTests: XCTestCase {
         let captureGroup = try XCTUnwrap(parser.parse(line: input) as? XcodebuildErrorCaptureGroup)
         XCTAssertEqual(captureGroup.wholeError, input)
     }
+
+    func testMatchDataModelCodegen() throws {
+        let inputs = [
+            ("/path/to/data/model/something", "target", "Project"),
+            ("", "", ""),
+            ("", "iOS", "app"),
+            ("", "visionOS", ""),
+            ("", "", "123"),
+            ("/", "", ""),
+            ("path", "macOS", ""),
+            (".", "", "!"),
+            ("()", "[]", "(.*)"),
+        ]
+
+        for (path, target, project) in inputs {
+            let input = "DataModelCodegen \(path).xcdatamodeld (in target '\(target)' from project '\(project)')"
+            let captureGroup = try XCTUnwrap(parser.parse(line: input) as? DataModelCodegenCaptureGroup)
+            XCTAssertEqual(captureGroup.path, "\(path).xcdatamodeld")
+            XCTAssertEqual(captureGroup.target, target)
+            XCTAssertEqual(captureGroup.project, project)
+        }
+    }
+
+    func testNotDataModelCodegen() throws {
+        let inputs = [
+            "DataModelCodegen /pathxcdatamodeld (in target 'target' from project 'project')",
+            "datamodelcodegen /path.xcdatamodeld (in target 'target' from project 'project')",
+            "DataModelCodegen path.xcdatamodeld (in target from project 'project')",
+            "DataModelCodegen path.xcdatamodeld (in target 'target' from project)",
+            "DataModelCodegen path.xcdatamodeld (in target target from project project)",
+        ]
+
+        for input in inputs {
+            XCTAssertNil(parser.parse(line: input))
+        }
+    }
 }
diff --git a/Tests/XcbeautifyLibTests/RendererTests/AzureDevOpsPipelinesRendererTests.swift b/Tests/XcbeautifyLibTests/RendererTests/AzureDevOpsPipelinesRendererTests.swift
index 8b2df1d1..55e979c7 100644
--- a/Tests/XcbeautifyLibTests/RendererTests/AzureDevOpsPipelinesRendererTests.swift
+++ b/Tests/XcbeautifyLibTests/RendererTests/AzureDevOpsPipelinesRendererTests.swift
@@ -800,4 +800,9 @@ final class AzureDevOpsPipelinesRendererTests: XCTestCase {
         let formatted = logFormatted("note: Building targets in dependency order")
         XCTAssertEqual(formatted, "note: Building targets in dependency order")
     }
+
+    func testDataModelCodegen() {
+        let formatted = logFormatted("DataModelCodegen /path/to/data/model/something.xcdatamodeld (in target 'Target' from project 'Project')")
+        XCTAssertEqual(formatted, "[Target] DataModelCodegen /path/to/data/model/something.xcdatamodeld")
+    }
 }
diff --git a/Tests/XcbeautifyLibTests/RendererTests/GitHubActionsRendererTests.swift b/Tests/XcbeautifyLibTests/RendererTests/GitHubActionsRendererTests.swift
index 8cbec3a1..db8fe502 100644
--- a/Tests/XcbeautifyLibTests/RendererTests/GitHubActionsRendererTests.swift
+++ b/Tests/XcbeautifyLibTests/RendererTests/GitHubActionsRendererTests.swift
@@ -797,4 +797,9 @@ final class GitHubActionsRendererTests: XCTestCase {
         let formatted = logFormatted("note: Building targets in dependency order")
         XCTAssertEqual(formatted, "note: Building targets in dependency order")
     }
+
+    func testDataModelCodegen() {
+        let formatted = logFormatted("DataModelCodegen /path/to/data/model/something.xcdatamodeld (in target 'Target' from project 'Project')")
+        XCTAssertEqual(formatted, "[Target] DataModelCodegen /path/to/data/model/something.xcdatamodeld")
+    }
 }
diff --git a/Tests/XcbeautifyLibTests/RendererTests/TeamCityRendererTests.swift b/Tests/XcbeautifyLibTests/RendererTests/TeamCityRendererTests.swift
index d31311a2..9279fa6d 100644
--- a/Tests/XcbeautifyLibTests/RendererTests/TeamCityRendererTests.swift
+++ b/Tests/XcbeautifyLibTests/RendererTests/TeamCityRendererTests.swift
@@ -802,4 +802,9 @@ final class TeamCityRendererTests: XCTestCase {
         let formatted = noColoredFormatted("note: Building targets in dependency order")
         XCTAssertEqual(formatted, "note: Building targets in dependency order")
     }
+
+    func testDataModelCodegen() {
+        let formatted = noColoredFormatted("DataModelCodegen /path/to/data/model/something.xcdatamodeld (in target 'Target' from project 'Project')")
+        XCTAssertEqual(formatted, "[Target] DataModelCodegen /path/to/data/model/something.xcdatamodeld")
+    }
 }
diff --git a/Tests/XcbeautifyLibTests/RendererTests/TerminalRendererTests.swift b/Tests/XcbeautifyLibTests/RendererTests/TerminalRendererTests.swift
index ec8d71a6..6888fac7 100644
--- a/Tests/XcbeautifyLibTests/RendererTests/TerminalRendererTests.swift
+++ b/Tests/XcbeautifyLibTests/RendererTests/TerminalRendererTests.swift
@@ -834,4 +834,9 @@ final class TerminalRendererTests: XCTestCase {
         let formatted = noColoredFormatted("note: Building targets in dependency order")
         XCTAssertEqual(formatted, "note: Building targets in dependency order")
     }
+
+    func testDataModelCodegen() {
+        let formatted = noColoredFormatted("DataModelCodegen /path/to/data/model/something.xcdatamodeld (in target 'Target' from project 'Project')")
+        XCTAssertEqual(formatted, "[Target] DataModelCodegen /path/to/data/model/something.xcdatamodeld")
+    }
 }