Skip to content

Commit f106870

Browse files
committed
fixes and logging
1 parent 2685470 commit f106870

File tree

3 files changed

+150
-123
lines changed

3 files changed

+150
-123
lines changed

stringdiff/src/main/scala/app/tulz/diff/DiffBlock.scala

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,5 +9,11 @@ object DiffBlock {
99
actual: List[String],
1010
expected: List[String]
1111
) extends DiffBlock
12+
final case class Missing(
13+
expected: List[String]
14+
) extends DiffBlock
15+
final case class Extra(
16+
actual: List[String]
17+
) extends DiffBlock
1218

1319
}

stringdiff/src/main/scala/app/tulz/diff/StringDiff.scala

Lines changed: 119 additions & 91 deletions
Original file line numberDiff line numberDiff line change
@@ -5,45 +5,65 @@ import scala.Console._
55
class StringDiff(
66
beforeAll: String = RESET,
77
beforeNoMatch: String = "[",
8-
beforeExpected: String = YELLOW,
8+
beforeExpected: String = YELLOW + UNDERLINED,
99
afterExpected: String = RESET,
1010
between: String = "|",
11-
beforeActual: String = RED,
11+
beforeActual: String = RED + UNDERLINED,
1212
afterActual: String = RESET,
1313
afterNoMatch: String = "]",
14-
empty: String = "",
15-
beforeMatch: String = "",
16-
afterMatch: String = "",
14+
beforeExtra: String = "[" + RED + UNDERLINED,
15+
afterExtra: String = RESET + "|∅]",
16+
beforeMissing: String = "[∅|" + YELLOW + UNDERLINED,
17+
afterMissing: String = RESET + "]",
18+
beforeMatch: String = UNDERLINED,
19+
afterMatch: String = RESET,
1720
afterAll: String = ""
1821
) {
1922

2023
import Tokens._
2124

25+
private var prefix = ""
26+
27+
private def enter[T](label: String, f: => T): T = {
28+
println(s"$prefix<$label>")
29+
prefix = prefix + " "
30+
val result = f
31+
prefix = prefix.substring(0, prefix.length - 4)
32+
println(s"$prefix</$label>")
33+
result
34+
}
35+
36+
private def log(s: => String): Unit = {
37+
println(s"$prefix<m>")
38+
println(s"$prefix $s")
39+
println(s"$prefix</m>")
40+
}
41+
2242
def apply(
2343
actual: String,
2444
expected: String
2545
): String = {
26-
// println(s"actual: ${tokenize(actual)}")
27-
// println(s"expected: ${tokenize(expected)}")
28-
val options = diff(tokenize(actual), tokenize(expected), List.empty)
46+
val actualTokens = tokenize(actual)
47+
val expectedTokens = tokenize(expected)
48+
val options = diff(actualTokens, expectedTokens, List.empty)
2949
if (options.isEmpty) {
3050
"no diff result (probably a bug in app.tulz.stringdiff)"
3151
} else {
32-
// println("options:")
33-
// options.foreach { s =>
34-
// println(diffToString(s))
35-
// }
36-
val withMatchLength = options.map { path =>
52+
println("diff for:")
53+
println(s"'${actual}'")
54+
println(s"'${expected}'")
55+
println("options:")
56+
options.foreach { s =>
57+
println(diffToString(s))
58+
}
59+
val collapsed = options.map(collapse).distinct
60+
val withMatchLength = collapsed.map { path =>
3761
val matchLengths = path.collect { case DiffBlock.Match(s) => s.length }
3862
path -> matchLengths.sum
3963
}
4064
val largestMatch = withMatchLength.map(_._2).max
4165
val withLargestMatch = withMatchLength.filter(_._2 == largestMatch).map(_._1)
42-
val best = withLargestMatch.map(diffToString).minBy(_.length)
43-
// println("best:")
44-
// println(best)
45-
46-
best
66+
withLargestMatch.map(diffToString).minBy(_.replaceAll("\\s+", "").length)
4767
}
4868
}
4969

@@ -52,99 +72,102 @@ class StringDiff(
5272
expected: List[String],
5373
path: List[DiffBlock]
5474
): List[List[DiffBlock]] = {
55-
tryEnd(actual, expected, path) ++
56-
tryCommonPrefix(actual, expected, path) ++
57-
tryExtraPrefixInActual(actual, expected, path) ++
58-
tryExtraPrefixInExpected(actual, expected, path) ++
59-
tryNonMatchingPrefix(actual, expected, path)
75+
enter(
76+
"diff", {
77+
log(s"'${actual.mkString}'")
78+
log(s"'${expected.mkString}'")
79+
enter("tryEnd", tryEnd(actual, expected, path)) ++
80+
enter("tryMatchingPrefix", tryMatchingPrefix(actual, expected, path)) ++
81+
enter("tryExtraPrefix", tryExtraPrefix(actual, expected, path)) ++
82+
enter("tryMissingPrefix", tryMissingPrefix(actual, expected, path))
83+
}
84+
)
6085
}
6186

6287
private def tryEnd(
6388
actual: List[String],
6489
expected: List[String],
6590
path: List[DiffBlock]
6691
): List[List[DiffBlock]] = {
67-
if (actual.isEmpty || actual.isEmpty != expected.isEmpty) {
68-
List(
69-
DiffBlock.NoMatch(actual, expected) :: path
70-
)
71-
} else if (actual.isEmpty || expected.isEmpty) {
72-
throw new RuntimeException("looks like you have hit a bug in app.tulz.stringdiff")
92+
log(s"'${actual.mkString}'")
93+
log(s"'${expected.mkString}'")
94+
95+
if (actual.isEmpty && expected.isEmpty) {
96+
log("--yes--")
97+
List(path)
7398
} else {
99+
log("--no--")
74100
List.empty
75101
}
76102
}
77103

78-
private def tryExtraPrefixInActual(
104+
private def tryExtraPrefix(
79105
actual: List[String],
80106
expected: List[String],
81107
path: List[DiffBlock]
82108
): List[List[DiffBlock]] = {
83-
val (actualTail, actualPrefix) = extraPrefix(actual, expected)
84-
if (actualPrefix.isEmpty) {
85-
List.empty
86-
} else {
87-
diff(
88-
actualTail,
89-
expected,
90-
DiffBlock.NoMatch(actualPrefix, List.empty) :: path
91-
)
109+
log(s"'${actual.mkString}'")
110+
log(s"'${expected.mkString}'")
111+
extraPrefixes(actual, expected).flatMap { case (actualTail, actualPrefix) =>
112+
log("--yes--")
113+
log(s"'${actualPrefix.mkString}'")
114+
val newPath = DiffBlock.Extra(actualPrefix) :: path
115+
enter("tryEnd", tryEnd(actualTail, expected, path)) ++
116+
enter("tryMatchingPrefix", tryMatchingPrefix(actualTail, expected, newPath)) ++
117+
enter("tryMissingPrefix", tryMissingPrefix(actualTail, expected, newPath))
92118
}
93119
}
94120

95-
private def tryExtraPrefixInExpected(
121+
private def tryMissingPrefix(
96122
actual: List[String],
97123
expected: List[String],
98124
path: List[DiffBlock]
99125
): List[List[DiffBlock]] = {
100-
val (expectedTail, expectedPrefix) = extraPrefix(expected, actual)
101-
if (expectedPrefix.isEmpty) {
102-
List.empty
103-
} else {
104-
diff(
105-
actual,
106-
expectedTail,
107-
DiffBlock.NoMatch(List.empty, expectedPrefix) :: path
108-
)
126+
log(s"'${actual.mkString}'")
127+
log(s"'${expected.mkString}'")
128+
129+
extraPrefixes(expected, actual).flatMap { case (expectedTail, expectedPrefix) =>
130+
log("--yes--")
131+
log(s"'${expectedPrefix.mkString}'")
132+
val newPath = DiffBlock.Missing(expectedPrefix) :: path
133+
enter("tryEnd", tryEnd(actual, expectedTail, path)) ++
134+
enter("tryMatchingPrefix", tryMatchingPrefix(actual, expectedTail, newPath)) ++
135+
enter("tryExtraPrefix", tryExtraPrefix(actual, expectedTail, newPath))
109136
}
110137
}
111138

112-
private def tryCommonPrefix(
139+
private def tryMatchingPrefix(
113140
actual: List[String],
114141
expected: List[String],
115142
path: List[DiffBlock]
116143
): List[List[DiffBlock]] = {
144+
log(s"'${actual.mkString}'")
145+
log(s"'${expected.mkString}'")
117146
if (actual.isEmpty || expected.isEmpty) {
147+
log("--no--")
118148
List.empty
119149
} else {
120-
val (actualTail, expectedTail, prefix) = matchingPrefix(actual, expected)
150+
val (actualTail, expectedTail, prefix) = matchingPrefixes(actual, expected)
121151
if (prefix.isEmpty) {
152+
log("--no--")
122153
List.empty
123154
} else {
124-
diff(
125-
actualTail,
126-
expectedTail,
127-
DiffBlock.Match(prefix) :: path
128-
)
155+
log("--yes--")
156+
log(s"'${prefix.mkString}'")
157+
val newPath = DiffBlock.Match(prefix) :: path
158+
enter("tryEnd", tryEnd(actualTail, expectedTail, newPath)) ++
159+
enter("tryExtraPrefix", tryExtraPrefix(actualTail, expectedTail, newPath)) ++
160+
enter("tryMissingPrefix", tryMissingPrefix(actualTail, expectedTail, newPath))
161+
129162
}
130163
}
131164
}
132165

133-
private def tryNonMatchingPrefix(
134-
actual: List[String],
135-
expected: List[String],
136-
path: List[DiffBlock]
137-
): List[List[DiffBlock]] = {
138-
val (actualTail, expectedTail, actualPrefix, expectedPrefix) = nonMatchingPrefix(actual, expected)
139-
if (actualPrefix.isEmpty) {
140-
List.empty
141-
} else {
142-
diff(
143-
actualTail,
144-
expectedTail,
145-
DiffBlock.NoMatch(actualPrefix, expectedPrefix) :: path
146-
)
147-
}
166+
private def collapse(diff: List[DiffBlock]): List[DiffBlock] = diff match {
167+
case Nil => Nil
168+
case DiffBlock.Missing(expected) :: DiffBlock.Extra(actual) :: tail => DiffBlock.NoMatch(actual, expected) :: collapse(tail)
169+
case DiffBlock.Extra(actual) :: DiffBlock.Missing(expected) :: tail => DiffBlock.NoMatch(actual, expected) :: collapse(tail)
170+
case head :: tail => head :: collapse(tail)
148171
}
149172

150173
private def diffToString(diff: List[DiffBlock]): String = {
@@ -159,25 +182,27 @@ class StringDiff(
159182
buffer.toString
160183
case DiffBlock.NoMatch(actual, expected) =>
161184
val buffer = new StringBuffer
162-
if (actual.nonEmpty || expected.nonEmpty) {
163-
buffer.append(beforeNoMatch)
164-
buffer.append(beforeExpected)
165-
if (expected.nonEmpty) {
166-
expected.foreach(buffer.append)
167-
} else {
168-
buffer.append(empty)
169-
}
170-
buffer.append(afterExpected)
171-
buffer.append(between)
172-
buffer.append(beforeActual)
173-
if (actual.nonEmpty) {
174-
actual.foreach(buffer.append)
175-
} else {
176-
buffer.append(empty)
177-
}
178-
buffer.append(afterActual)
179-
buffer.append(afterNoMatch)
180-
}
185+
buffer.append(beforeNoMatch)
186+
buffer.append(beforeExpected)
187+
expected.foreach(buffer.append)
188+
buffer.append(afterExpected)
189+
buffer.append(between)
190+
buffer.append(beforeActual)
191+
actual.foreach(buffer.append)
192+
buffer.append(afterActual)
193+
buffer.append(afterNoMatch)
194+
buffer.toString
195+
case DiffBlock.Extra(actual) =>
196+
val buffer = new StringBuffer
197+
buffer.append(beforeExtra)
198+
actual.foreach(buffer.append)
199+
buffer.append(afterExtra)
200+
buffer.toString
201+
case DiffBlock.Missing(expected) =>
202+
val buffer = new StringBuffer
203+
buffer.append(beforeMissing)
204+
expected.foreach(buffer.append)
205+
buffer.append(afterMissing)
181206
buffer.toString
182207
}.mkString,
183208
afterAll
@@ -196,7 +221,10 @@ object StringDiff {
196221
between = "",
197222
beforeActual = "<actual>",
198223
afterActual = "</actual>",
199-
empty = "<empty/>",
224+
beforeExtra = "<extra>",
225+
afterExtra = "</extra>",
226+
beforeMissing = "<missing>",
227+
afterMissing = "</missing>",
200228
afterNoMatch = "</no-match>",
201229
beforeMatch = "<match>",
202230
afterMatch = "</match>",

stringdiff/src/main/scala/app/tulz/diff/Tokens.scala

Lines changed: 25 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ object Tokens {
2727
result.reverse
2828
}
2929

30-
def matchingPrefix(
30+
def matchingPrefixes(
3131
actual: List[String],
3232
expected: List[String]
3333
): (List[String], List[String], List[String]) = { // actualRem rem, expectedRem rem, prefix
@@ -38,39 +38,32 @@ object Tokens {
3838
(actual.drop(commonPrefixLength), expected.drop(commonPrefixLength), actual.take(commonPrefixLength))
3939
}
4040

41-
def nonMatchingPrefix(
42-
actual: List[String],
43-
expected: List[String]
44-
): (List[String], List[String], List[String], List[String]) = { // actualRem rem, expectedRem rem, prefix
45-
val maxLen = Math.min(actual.length, expected.length)
46-
val distinctPrefixLength =
47-
(1 to maxLen)
48-
.takeWhile { len =>
49-
val lastIsWhitespace = isWhitespace(actual(len - 1)) && isWhitespace(expected(len - 1))
50-
val nextIsDifferent = len < maxLen && actual(len) != expected(len)
51-
actual(len - 1) != expected(len - 1) || (lastIsWhitespace && nextIsDifferent)
52-
}
53-
.lastOption.getOrElse(0)
54-
(
55-
actual.drop(distinctPrefixLength),
56-
expected.drop(distinctPrefixLength),
57-
actual.take(distinctPrefixLength),
58-
expected.take(distinctPrefixLength)
59-
)
60-
}
61-
62-
def extraPrefix(
41+
def extraPrefixes(
6342
tokens: List[String],
6443
reference: List[String]
65-
): (List[String], List[String]) = { // tokens tail, tokens prefix
66-
val maxLen = Math.min(tokens.length, reference.length)
67-
val prefixLength =
68-
(1 to maxLen)
69-
.takeWhile { len =>
70-
tokens(len - 1) != reference.head
71-
}
72-
.lastOption.getOrElse(0)
73-
(tokens.drop(prefixLength), tokens.take(prefixLength))
44+
): List[(List[String], List[String])] = { // tokens tail, tokens prefix
45+
(1 to tokens.length)
46+
.map { len =>
47+
(
48+
len,
49+
tokens.take(len) != reference.take(len),
50+
// reference.isEmpty || tokens(len - 1) != reference.head,
51+
len == tokens.length || !isWhitespace(tokens(len))
52+
)
53+
}
54+
.toList
55+
.takeWhile { case (_, keepGoing, _) =>
56+
keepGoing
57+
}
58+
.collect {
59+
case (prefixLength, _, keep) if keep => prefixLength
60+
}
61+
.map { prefixLength =>
62+
(
63+
tokens.drop(prefixLength),
64+
tokens.take(prefixLength)
65+
)
66+
}
7467
}
7568

7669
}

0 commit comments

Comments
 (0)