@@ -5,45 +5,65 @@ import scala.Console._
55class 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>" ,
0 commit comments