A macOS utility to track home-manager JJ repo status
at main 83 lines 2.6 kB view raw
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}