diff --git a/diff.carp b/diff.carp index 6a39049..bc14b82 100644 --- a/diff.carp +++ b/diff.carp @@ -265,6 +265,13 @@ Expects the output of `line-diff` or a diff of string arrays. Uses 3 lines of context around changes.") (defn unified [d] (unified-with-context d 3)) + ; core's starts-with?/suffix guard on byte length but index characters; patch input is bytes + (defn- byte-starts-with? [s sub] + (let [ls (String.length sub)] + (and (>= (String.length s) ls) (= sub &(String.byte-slice s 0 ls))))) + + (defn- byte-suffix [s b] (String.byte-slice s b (String.length s))) + ; parses the 1-based old-file start line from a unified hunk header such as ; `@@ -3,4 +3,5 @@`; the count is optional (`@@ -3 +3 @@` means one line). (defn- parse-hunk-start [header] @@ -273,9 +280,9 @@ Uses 3 lines of context around changes.") (for [i 0 (Array.length &tokens)] (let [tok (Array.unsafe-nth &tokens i)] (when (and (Maybe.nothing? &res) - (String.starts-with? tok "-") + (byte-starts-with? tok "-") (> (String.length tok) 1)) - (let [range (String.suffix tok 1)] + (let [range (byte-suffix tok 1)] (set! res (Int.from-string (Array.unsafe-nth &(String.split-by &range &[\,]) 0))))))) @@ -300,7 +307,7 @@ or out of order. Both documents are split and rejoined on newlines, so (unless failed (let [line (Array.unsafe-nth &lines i)] (cond - (String.starts-with? line "@@") + (byte-starts-with? line "@@") (match (parse-hunk-start line) (Maybe.Nothing) (set! failed true) (Maybe.Just start) @@ -311,22 +318,22 @@ or out of order. Both documents are split and rejoined on newlines, so (for [k cursor target] (Array.push-back! &out @(Array.unsafe-nth &orig k))) (set! cursor target))))) - (String.starts-with? line " ") - (let [content (String.suffix line 1)] + (byte-starts-with? line " ") + (let [content (byte-suffix line 1)] (if (or (>= cursor olen) (/= (Array.unsafe-nth &orig cursor) &content)) (set! failed true) (do (Array.push-back! &out content) (set! cursor (Int.inc cursor))))) - (String.starts-with? line "-") - (let [content (String.suffix line 1)] + (byte-starts-with? line "-") + (let [content (byte-suffix line 1)] (if (or (>= cursor olen) (/= (Array.unsafe-nth &orig cursor) &content)) (set! failed true) (set! cursor (Int.inc cursor)))) - (String.starts-with? line "+") - (Array.push-back! &out (String.suffix line 1)) + (byte-starts-with? line "+") + (Array.push-back! &out (byte-suffix line 1)) ())))) (unless failed (for [k cursor olen] (Array.push-back! &out @(Array.unsafe-nth &orig k)))) diff --git a/tests/diff.carp b/tests/diff.carp index 8f42bc2..94e5725 100644 --- a/tests/diff.carp +++ b/tests/diff.carp @@ -4,6 +4,9 @@ (use-all Diff Test) +; 0xBB is `»` in latin-1: one byte, zero utf-8 characters +(defn latin1-raquo [] (String.from-bytes &[187b])) + (defn main [] (with-test test (assert-equal test @@ -498,4 +501,30 @@ c"))) "patch returns Nothing when a context line does not match the original") (assert-equal test &(the (Maybe String) (Maybe.Nothing)) &(patch "a b" "@@ garbage @@ a -") "patch returns Nothing on a malformed hunk header"))) +") "patch returns Nothing on a malformed hunk header") + (assert-equal test &(Maybe.Just @"a +b") &(patch "a +b" "@@ -1,2 +1,2 @@ + a +é + b") "patch does not abort on a line that is a single multi-byte character") + (assert-equal test &(Maybe.Just @"a +b") &(patch "a +b" &(String.concat &[@"@@ -1,2 +1,2 @@ + a +" (latin1-raquo) @" + b"])) "patch does not abort on a body line that is a single continuation byte") + (assert-equal test &(the (Maybe String) (Maybe.Nothing)) &(patch "a +b" &(String.concat &[@"@@ " (latin1-raquo) @" @@ + a +"])) "patch does not abort on a continuation byte in the hunk header") + (assert-equal test &(Maybe.Just (String.concat &[(latin1-raquo) @" +b"])) &(patch &(String.concat &[(latin1-raquo) @" +b"]) &(String.concat &[@"@@ -1,2 +1,2 @@ + " (latin1-raquo) @" + b"])) "patch applies a no-op patch to a latin-1 document") + (assert-equal test &(Maybe.Just (String.concat &[(latin1-raquo) @" +B"])) &(patch &(String.concat &[(latin1-raquo) @" +b"]) &(unified &(line-diff &(String.concat &[(latin1-raquo) @" +b"]) &(String.concat &[(latin1-raquo) @" +B"])))) "patch round-trips a diff of a latin-1 document")))