Created
February 18, 2026 16:20
-
-
Save thedeemon/4c0396b64c3c7f7d6aabd18d3cb4651a to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| // Formats only the changed lines mentioned in "git diff". | |
| // Removes trailing whitespace and in leading whitespace converts tabs to spaces. | |
| // Made with Codex. | |
| import Foundation | |
| struct ParsedDiff { | |
| let files: [FileChange] | |
| } | |
| struct FileChange { | |
| let filePath: String | |
| var lineNumbers: [Int] | |
| } | |
| enum DiffParseError: Error, CustomStringConvertible { | |
| case invalidHunkHeader(String) | |
| var description: String { | |
| switch self { | |
| case .invalidHunkHeader(let header): | |
| return "Invalid hunk header: \(header)" | |
| } | |
| } | |
| } | |
| func parseFilePath(fromDiffGitLine line: String) -> String? { | |
| // Example: diff --git a/path/to/file b/path/to/file | |
| let parts = line.split(separator: " ") | |
| guard parts.count >= 4 else { return nil } | |
| let bPath = String(parts[3]) | |
| if bPath == "b/dev/null" { | |
| // Deleted file: fall back to original path. | |
| let aPath = String(parts[2]) | |
| return aPath.hasPrefix("a/") ? String(aPath.dropFirst(2)) : aPath | |
| } | |
| return bPath.hasPrefix("b/") ? String(bPath.dropFirst(2)) : bPath | |
| } | |
| func parseNewStartLine(fromHunkHeader line: String) throws -> Int { | |
| // Example: @@ -13,7 +13,7 @@ optional trailing text | |
| guard let plusRange = line.range(of: " +") else { | |
| throw DiffParseError.invalidHunkHeader(line) | |
| } | |
| let afterPlus = line[plusRange.upperBound...] | |
| guard let spaceAfterNewRange = afterPlus.firstIndex(of: " ") else { | |
| throw DiffParseError.invalidHunkHeader(line) | |
| } | |
| let newRangeChunk = afterPlus[..<spaceAfterNewRange] // e.g. 13,7 or 42 | |
| let startString = newRangeChunk.split(separator: ",").first.map(String.init) ?? "" | |
| guard let start = Int(startString) else { | |
| throw DiffParseError.invalidHunkHeader(line) | |
| } | |
| return start | |
| } | |
| func parseGitDiff(_ text: String) throws -> ParsedDiff { | |
| let lines = text.split(whereSeparator: \ .isNewline).map(String.init) | |
| var files: [FileChange] = [] | |
| var currentFileIndex: Int? | |
| var currentNewLineNumber: Int? | |
| for line in lines { | |
| if line.hasPrefix("diff --git ") { | |
| let currentFile = parseFilePath(fromDiffGitLine: line) | |
| currentNewLineNumber = nil | |
| if let file = currentFile { | |
| if let existingIndex = files.firstIndex(where: { $0.filePath == file }) { | |
| currentFileIndex = existingIndex | |
| } else { | |
| files.append(FileChange(filePath: file, lineNumbers: [])) | |
| currentFileIndex = files.count - 1 | |
| } | |
| } else { | |
| currentFileIndex = nil | |
| } | |
| continue | |
| } | |
| if line.hasPrefix("@@ ") { | |
| currentNewLineNumber = try parseNewStartLine(fromHunkHeader: line) | |
| continue | |
| } | |
| guard let fileIndex = currentFileIndex, let newLine = currentNewLineNumber else { | |
| continue | |
| } | |
| if line.hasPrefix("+") && !line.hasPrefix("+++") { | |
| files[fileIndex].lineNumbers.append(newLine) | |
| currentNewLineNumber = newLine + 1 | |
| } else if line.hasPrefix("-") && !line.hasPrefix("---") { | |
| // Removed line from old file; does not advance new-file line number. | |
| continue | |
| } else if line.hasPrefix(" ") { | |
| currentNewLineNumber = newLine + 1 | |
| } else if line.hasPrefix("\\ No newline at end of file") { | |
| continue | |
| } else { | |
| // Unknown line in hunk context; safest behavior is to ignore it. | |
| continue | |
| } | |
| } | |
| return ParsedDiff(files: files) | |
| } | |
| func printUsage() { | |
| let program = CommandLine.arguments.first ?? "diff-parser" | |
| print("Usage: \(program) [path-to-diff.txt]") | |
| print("If no path is provided, the program runs `git diff` and parses its output.") | |
| print("Then it rewrites only added lines in changed files:") | |
| print("- leading indentation tabs are replaced with 4 spaces") | |
| print("- trailing whitespace is removed") | |
| } | |
| func readDiffText(fromFilePath filePath: String?) throws -> String { | |
| if let filePath { | |
| return try String(contentsOfFile: filePath, encoding: .utf8) | |
| } | |
| let process = Process() | |
| process.executableURL = URL(fileURLWithPath: "/usr/bin/env") | |
| process.arguments = ["git", "diff"] | |
| let stdoutPipe = Pipe() | |
| let stderrPipe = Pipe() | |
| process.standardOutput = stdoutPipe | |
| process.standardError = stderrPipe | |
| try process.run() | |
| process.waitUntilExit() | |
| let stdoutData = stdoutPipe.fileHandleForReading.readDataToEndOfFile() | |
| let stderrData = stderrPipe.fileHandleForReading.readDataToEndOfFile() | |
| guard process.terminationStatus == 0 else { | |
| let stderrText = String(data: stderrData, encoding: .utf8) ?? "unknown error" | |
| let conciseError = stderrText | |
| .split(whereSeparator: \.isNewline) | |
| .map(String.init) | |
| .first(where: { !$0.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty }) ?? "unknown error" | |
| throw NSError( | |
| domain: "DiffParser", | |
| code: Int(process.terminationStatus), | |
| userInfo: [NSLocalizedDescriptionKey: "git diff failed: \(conciseError)"] | |
| ) | |
| } | |
| return String(data: stdoutData, encoding: .utf8) ?? "" | |
| } | |
| func transformedTargetedLine(_ line: String) -> String { | |
| var current = line.startIndex | |
| while current < line.endIndex { | |
| let ch = line[current] | |
| if ch == " " || ch == "\t" { | |
| current = line.index(after: current) | |
| } else { | |
| break | |
| } | |
| } | |
| let leading = String(line[..<current]).replacingOccurrences(of: "\t", with: " ") | |
| let remainder = String(line[current...]) | |
| let combined = leading + remainder | |
| var end = combined.endIndex | |
| while end > combined.startIndex { | |
| let prev = combined.index(before: end) | |
| let ch = combined[prev] | |
| if ch == " " || ch == "\t" { | |
| end = prev | |
| } else { | |
| break | |
| } | |
| } | |
| return String(combined[..<end]) | |
| } | |
| func applyLineTransformations(filePath: String, lineNumbers: [Int]) throws -> Bool { | |
| if lineNumbers.isEmpty { | |
| return false | |
| } | |
| let original = try String(contentsOfFile: filePath, encoding: .utf8) | |
| let hadTrailingNewline = original.hasSuffix("\n") | |
| var lines = original.components(separatedBy: "\n") | |
| if hadTrailingNewline && !lines.isEmpty { | |
| lines.removeLast() | |
| } | |
| var changed = false | |
| for lineNumber in lineNumbers { | |
| guard lineNumber > 0 && lineNumber <= lines.count else { | |
| continue | |
| } | |
| let index = lineNumber - 1 | |
| let updated = transformedTargetedLine(lines[index]) | |
| if updated != lines[index] { | |
| lines[index] = updated | |
| changed = true | |
| } | |
| } | |
| if !changed { | |
| return false | |
| } | |
| var rewritten = lines.joined(separator: "\n") | |
| if hadTrailingNewline { | |
| rewritten += "\n" | |
| } | |
| try rewritten.write(toFile: filePath, atomically: true, encoding: .utf8) | |
| return true | |
| } | |
| func main() { | |
| if CommandLine.arguments.contains("--help") || CommandLine.arguments.contains("-h") { | |
| printUsage() | |
| return | |
| } | |
| let diffPath = CommandLine.arguments.dropFirst().first | |
| do { | |
| let text = try readDiffText(fromFilePath: diffPath) | |
| let parsed = try parseGitDiff(text) | |
| var updatedFiles = 0 | |
| for fileChange in parsed.files { | |
| if try applyLineTransformations(filePath: fileChange.filePath, lineNumbers: fileChange.lineNumbers) { | |
| updatedFiles += 1 | |
| print("Updated \(fileChange.filePath)") | |
| } | |
| } | |
| print("Done. Updated \(updatedFiles) file(s).") | |
| } catch { | |
| fputs("Error: \(error)\n", stderr) | |
| exit(1) | |
| } | |
| } | |
| main() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment