krz/keycask

Password manager: Swift core library, CLI for macOS/Linux/Windows, iOS/macOS app. cli password-manager swift

Commit bd8d5569eb

bd8d5569ebc36c1598199eca5d20f8f157ccffd4

parent: ba62721654

Verified · cmc

cmc <hello@cleberg.net> · 2026-09-18 01:23 UTC

Strip trailing newline from clipboard reads

Layout: unified · split

Sources/keycask/Clipboard.swift +10 −1
@@ -59,11 +59,20 @@ enum Clipboard {
59 return nil 59 return nil
60 } 60 }
61 61
62 /// Removes exactly one trailing line break, which some paste tools add.
63 static func stripTrailingNewline(_ s: String) -> String {
64 var scalars = s.unicodeScalars
65 guard scalars.last == "\n" else { return s }
66 scalars.removeLast()
67 if scalars.last == "\r" { scalars.removeLast() }
68 return String(scalars)
69 }
70
62 static func read() throws -> String { 71 static func read() throws -> String {
63 let tool = try requireTool() 72 let tool = try requireTool()
64 let (status, output) = try runTool(tool.paste, input: nil) 73 let (status, output) = try runTool(tool.paste, input: nil)
65 guard status == 0 else { return "" } 74 guard status == 0 else { return "" }
66 return output 75 return stripTrailingNewline(output)
67 } 76 }
68 77
69 static func write(_ text: String) throws { 78 static func write(_ text: String) throws {
Tests/KeycaskCLITests/ClipboardTests.swift +7
@@ -16,6 +16,13 @@ import Testing
16 #expect(try JSONDecoder().decode(Clipboard.Handoff.self, from: data) == h) 16 #expect(try JSONDecoder().decode(Clipboard.Handoff.self, from: data) == h)
17 } 17 }
18 18
19 @Test func stripsOneTrailingNewline() {
20 #expect(Clipboard.stripTrailingNewline("s\r\n") == "s")
21 #expect(Clipboard.stripTrailingNewline("s\n") == "s")
22 #expect(Clipboard.stripTrailingNewline("s") == "s")
23 #expect(Clipboard.stripTrailingNewline("s\n\n") == "s\n")
24 }
25
19 @Test func findToolScansPathInOrder() throws { 26 @Test func findToolScansPathInOrder() throws {
20 let dir = FileManager.default.temporaryDirectory 27 let dir = FileManager.default.temporaryDirectory
21 .appendingPathComponent("keycask-clip-\(UUID().uuidString)") 28 .appendingPathComponent("keycask-clip-\(UUID().uuidString)")