A macOS utility to track home-manager JJ repo status
1import Foundation
2
3/// Pure functions for parsing jj command output into RepoStatus.
4public enum StatusParser {
5 /// Parse the output of `jj log` that lists one commit description per line.
6 ///
7 /// - Parameter output: Raw stdout from jj log command
8 /// - Returns: Number of commits and their first-line descriptions
9 public static func parseCommitLines(from output: String) -> (count: Int, descriptions: [String]) {
10 let trimmed = output.trimmingCharacters(in: .whitespacesAndNewlines)
11 if trimmed.isEmpty {
12 return (0, [])
13 }
14 let lines = trimmed.components(separatedBy: "\n")
15 return (lines.count, lines)
16 }
17
18 /// Parse the output of the dirty-check command.
19 ///
20 /// - Parameter output: Raw stdout, expected to be "clean" or "dirty"
21 /// - Returns: `true` if the working copy has changes, `false` if clean
22 /// - Throws: If the output is unexpected
23 public static func parseDirtyStatus(from output: String) throws -> Bool {
24 let trimmed = output.trimmingCharacters(in: .whitespacesAndNewlines)
25 switch trimmed {
26 case "clean":
27 return false
28 case "dirty":
29 return true
30 default:
31 throw ParserError.unexpectedDirtyOutput(trimmed)
32 }
33 }
34
35 /// Build a RepoStatus from the three jj command outputs.
36 ///
37 /// - Parameters:
38 /// - aheadOutput: Output from the "commits ahead of trunk" command
39 /// - behindOutput: Output from the "commits behind trunk" command
40 /// - dirtyOutput: Output from the "dirty check" command
41 /// - date: Timestamp for the check (defaults to now)
42 /// - Returns: A fully populated RepoStatus
43 public static func parse(
44 aheadOutput: String,
45 behindOutput: String,
46 dirtyOutput: String,
47 at date: Date = Date()
48 ) -> RepoStatus {
49 let (ahead, aheadCommits) = parseCommitLines(from: aheadOutput)
50 let (behind, behindCommits) = parseCommitLines(from: behindOutput)
51
52 let dirty: Bool
53 do {
54 dirty = try parseDirtyStatus(from: dirtyOutput)
55 } catch {
56 return RepoStatus.error(
57 "Failed to parse working copy status: \(error.localizedDescription)",
58 at: date
59 )
60 }
61
62 return RepoStatus(
63 ahead: ahead,
64 behind: behind,
65 dirty: dirty,
66 error: nil,
67 aheadCommits: aheadCommits,
68 behindCommits: behindCommits,
69 checkedAt: date
70 )
71 }
72
73 public enum ParserError: LocalizedError {
74 case unexpectedDirtyOutput(String)
75
76 public var errorDescription: String? {
77 switch self {
78 case .unexpectedDirtyOutput(let output):
79 return "Unexpected dirty status output: '\(output)'"
80 }
81 }
82 }
83}