M README.md => README.md +3 -1
@@ 31,7 31,9 @@ for diff in stringDiff("the word is blue", "the word is red", seps={' '}):
Other convenience wrappers may be added in the future! Feel free to request one or submit a patch.
-See the `when isMainModule` block within `simplediff.nim` for an example of using these functions to build a simple diff application.
+`simplediff` also provides `prettyDiff`, which writes a formatted version of the diff to an output stream. It can be called directly or, if you need more flexibility than it offers, used as a starting point for building your own output.
+
+See the `when isMainModule` block within `simplediff.nim` for an example of a simple diff application.
## Contributing
M src/simplediff.nim => src/simplediff.nim +39 -1
@@ 1,3 1,5 @@
+import math
+import streams
import strutils
import tables
@@ 39,7 41,7 @@ proc diff*[T](itemsOld, itemsNew: openArray[T]): seq[Diff[T]] =
var newSuffixLen = 1
if ixOld > 0 and overlap.getOrDefault(ixOld - 1, 0) > 0:
newSuffixLen = overlap.getOrDefault(ixOld - 1, 0) + 1
- overlaptemp[ixOld] = newSuffixLen
+ overlapTemp[ixOld] = newSuffixLen
if overlapTemp[ixOld] > subLength:
subLength = overlapTemp[ixOld]
subStartOld = ixOld - subLength + 1
@@ 73,6 75,42 @@ proc stringDiff*(s1, s2: string, seps: set[char] = Newlines): seq[Diff[string]]
return diff(split(s1, seps = seps), split(s2, seps = seps))
+proc prettyDiff*[T](itemsOld, itemsNew: openArray[T],
+ outStream: Stream = newFileStream(stdout), insertionPrefix = "++++",
+ deletionPrefix = "----") =
+ ## Calculate the diff of `itemsOld` and `itemsNew`
+ ## and write it to `outStream`.
+ ## `insertionPrefix` is prepended to lines that were inserted,
+ ## and `deletionPrefix` is prepended to lines that were deleted.
+ let diffed = diff(itemsOld, itemsNew)
+ var lineCounter = 1
+
+ let linePadding = int(floor(log10(float(itemsOld.len)))) + 2
+ proc formatLineCount(count: int): string =
+ align($lineCounter, linePadding) & " "
+
+ for ix, entry in diffed:
+ case entry.kind
+ of NoChange:
+ for line in entry.tokens:
+ lineCounter += 1
+ of Insertion:
+ if lineCounter != 1:
+ lineCounter -= 1
+ for line in entry.tokens:
+ outStream.writeLine(formatLineCount(lineCounter) &
+ insertionPrefix & " " & $line)
+ # Don't increment the lines when inserting at the beginning
+ # because we were adding at the zeroth line
+ if lineCounter != 1:
+ lineCounter += 1
+ of Deletion:
+ for line in entry.tokens:
+ outStream.writeLine(formatLineCount(lineCounter) &
+ deletionPrefix & " " & $line)
+ lineCounter += 1
+
+
when isMainModule:
import parseopt
import terminal
M tests/testDiff.nim => tests/testDiff.nim +33 -0
@@ 1,3 1,4 @@
+import streams
import strutils
import unittest
@@ 71,3 72,35 @@ suite "test stringDiff":
Diff[string](kind: Deletion, tokens: @["def", "123"]),
Diff[string](kind: Insertion, tokens: @["fed", "abc"])
]
+
+
+suite "test prettyDiff":
+ var output: Stream
+ setup:
+ output = newStringStream()
+
+ test "correct output on unchanged strings":
+ prettyDiff(["hello"], ["hello"], outStream = output)
+ output.setPosition(0)
+ check output.readAll() == ""
+
+ test "correct output on deletion":
+ prettyDiff(["hello", "world"], ["hello"], outStream = output)
+ output.setPosition(0)
+ check output.readAll() == " 2 ---- world\n"
+
+ test "correct output on insertion":
+ prettyDiff(["hello"], ["hello", "world"], outStream = output)
+ output.setPosition(0)
+ check output.readAll() == " 1 ++++ world\n"
+
+ test "correct output on insertion, deletion, and unchanged":
+ prettyDiff(["same", "deletion"], ["insertion", "same", ],
+ outStream = output)
+ output.setPosition(0)
+ check output.readAll() == " 1 ++++ insertion\n 2 ---- deletion\n"
+
+ test "correct output on non-string tokens":
+ prettyDiff([1, 2], [3, 1], outStream = output)
+ output.setPosition(0)
+ check output.readAll() == " 1 ++++ 3\n 2 ---- 2\n"