@@ -40,11 +40,13 @@ defmodule ExUnit.Diff do
40
40
41
41
# Char lists and lists
42
42
def script ( left , right ) when is_list ( left ) and is_list ( right ) do
43
- if Inspect.List . printable? ( left ) and Inspect.List . printable? ( right ) do
44
- script_string ( List . to_string ( left ) , List . to_string ( right ) , ?' )
45
- else
46
- keyword? = Inspect.List . keyword? ( left ) and Inspect.List . keyword? ( right )
47
- script_list ( left , right , keyword? , [ ] )
43
+ cond do
44
+ Inspect.List . printable? ( left ) and Inspect.List . printable? ( right ) ->
45
+ script_string ( List . to_string ( left ) , List . to_string ( right ) , ?' )
46
+ Inspect.List . keyword? ( left ) and Inspect.List . keyword? ( right ) ->
47
+ script_keyword ( left , right )
48
+ true ->
49
+ script_list ( left , right , [ ] )
48
50
end
49
51
end
50
52
@@ -117,58 +119,203 @@ defmodule ExUnit.Diff do
117
119
end )
118
120
end
119
121
120
- defp script_list ( [ ] , [ ] , _keyword? , acc ) do
121
- [ [ _ | elem_diff ] | rest ] = Enum . reverse ( acc )
122
- [ { :eq , "[" } , [ elem_diff | rest ] , { :eq , "]" } ]
122
+ defp script_keyword ( list1 , list2 ) do
123
+ path = { 0 , 0 , list1 , list2 , [ ] }
124
+ result =
125
+ find_script ( 0 , length ( list1 ) + length ( list2 ) , [ path ] )
126
+ |> format_each_fragment ( [ ] )
127
+ [ { :eq , "[" } , result , { :eq , "]" } ]
128
+ end
129
+
130
+ defp format_each_fragment ( [ { :diff , script } ] , [ ] ) ,
131
+ do: script
132
+
133
+ defp format_each_fragment ( [ { kind , elems } ] , [ ] ) ,
134
+ do: [ format_fragment ( kind , elems ) ]
135
+
136
+ defp format_each_fragment ( [ _ , _ ] = fragments , acc ) do
137
+ result =
138
+ case fragments do
139
+ [ diff: script1 , diff: script2 ] ->
140
+ [ script1 , { :eq , ", " } , script2 ]
141
+
142
+ [ { :diff , script } , { kind , elems } ] ->
143
+ [ script , { kind , ", " } , format_fragment ( kind , elems ) ]
144
+
145
+ [ { kind , elems } , { :diff , script } ] ->
146
+ [ format_fragment ( kind , elems ) , { kind , ", " } , script ]
147
+
148
+ [ del: elems1 , ins: elems2 ] ->
149
+ [ format_fragment ( :del , elems1 ) , format_fragment ( :ins , elems2 ) ]
150
+
151
+ [ { :eq , elems1 } , { kind , elems2 } ] ->
152
+ [ format_fragment ( :eq , elems1 ) , { kind , ", " } , format_fragment ( kind , elems2 ) ]
153
+
154
+ [ { kind , elems1 } , { :eq , elems2 } ] ->
155
+ [ format_fragment ( kind , elems1 ) , { kind , ", " } , format_fragment ( :eq , elems2 ) ]
156
+ end
157
+ Enum . reverse ( acc , result )
123
158
end
124
159
125
- defp script_list ( [ ] , [ elem | rest ] , keyword? , acc ) do
126
- elem_diff = [ ins: format_list_elem ( elem , keyword? ) ]
127
- script_list ( [ ] , rest , keyword? , [ [ ins: ", " ] ++ elem_diff | acc ] )
160
+ defp format_each_fragment ( [ { :diff , script } | rest ] , acc ) do
161
+ format_each_fragment ( rest , [ { :eq , ", " } , script | acc ] )
128
162
end
129
163
130
- defp script_list ( [ elem | rest ] , [ ] , keyword? , acc ) do
131
- elem_diff = [ del: format_list_elem ( elem , keyword? ) ]
132
- script_list ( rest , [ ] , keyword? , [ [ del: ", " ] ++ elem_diff | acc ] )
164
+ defp format_each_fragment ( [ { kind , elems } | rest ] , acc ) do
165
+ new_acc = [ { kind , ", " } , format_fragment ( kind , elems ) | acc ]
166
+ format_each_fragment ( rest , new_acc )
133
167
end
134
168
135
- defp script_list ( [ elem | rest1 ] , [ elem | rest2 ] , keyword? , acc ) do
136
- elem_diff = [ eq: format_list_elem ( elem , keyword? ) ]
137
- script_list ( rest1 , rest2 , keyword? , [ [ eq: ", " ] ++ elem_diff | acc ] )
169
+ defp format_fragment ( kind , elems ) do
170
+ formatter = fn { key , val } ->
171
+ format_key_value ( key , val , true )
172
+ end
173
+ { kind , Enum . map_join ( elems , ", " , formatter ) }
138
174
end
139
175
140
- defp script_list ( [ { key1 , val1 } | rest1 ] , [ { key2 , val2 } | rest2 ] , true , acc ) do
141
- key_diff =
142
- if key1 != key2 do
143
- script_string ( Atom . to_string ( key1 ) , Atom . to_string ( key2 ) )
144
- else
145
- [ eq: Atom . to_string ( key1 ) ]
146
- end
147
- value_diff = script_inner ( val1 , val2 )
148
- elem_diff = [ [ key_diff , { :eq , ": " } ] , value_diff ]
149
- script_list ( rest1 , rest2 , true , [ [ eq: ", " ] ++ elem_diff | acc ] )
176
+ defp find_script ( envelope , max , _paths ) when envelope > max do
177
+ nil
178
+ end
179
+
180
+ defp find_script ( envelope , max , paths ) do
181
+ case each_diagonal ( - envelope , envelope , paths , [ ] ) do
182
+ { :done , edits } ->
183
+ compact_reverse ( edits , [ ] )
184
+ { :next , paths } -> find_script ( envelope + 1 , max , paths )
185
+ end
186
+ end
187
+
188
+ defp compact_reverse ( [ ] , acc ) ,
189
+ do: acc
190
+
191
+ defp compact_reverse ( [ { :diff , _ } = fragment | rest ] , acc ) ,
192
+ do: compact_reverse ( rest , [ fragment | acc ] )
193
+
194
+ defp compact_reverse ( [ { kind , char } | rest ] , [ { kind , chars } | acc ] ) ,
195
+ do: compact_reverse ( rest , [ { kind , [ char | chars ] } | acc ] )
196
+
197
+ defp compact_reverse ( [ { kind , char } | rest ] , acc ) ,
198
+ do: compact_reverse ( rest , [ { kind , [ char ] } | acc ] )
199
+
200
+ defp each_diagonal ( diag , limit , _paths , next_paths ) when diag > limit do
201
+ { :next , Enum . reverse ( next_paths ) }
202
+ end
203
+
204
+ defp each_diagonal ( diag , limit , paths , next_paths ) do
205
+ { path , rest } = proceed_path ( diag , limit , paths )
206
+ with { :cont , path } <- follow_snake ( path ) do
207
+ each_diagonal ( diag + 2 , limit , rest , [ path | next_paths ] )
208
+ end
209
+ end
210
+
211
+ defp proceed_path ( 0 , 0 , [ path ] ) , do: { path , [ ] }
212
+
213
+ defp proceed_path ( diag , limit , [ path | _ ] = paths ) when diag == - limit do
214
+ { move_down ( path ) , paths }
215
+ end
216
+
217
+ defp proceed_path ( diag , limit , [ path ] ) when diag == limit do
218
+ { move_right ( path ) , [ ] }
219
+ end
220
+
221
+ defp proceed_path ( _diag , _limit , [ path1 , path2 | rest ] ) do
222
+ if elem ( path1 , 1 ) > elem ( path2 , 1 ) do
223
+ { move_right ( path1 ) , [ path2 | rest ] }
224
+ else
225
+ { move_down ( path2 ) , [ path2 | rest ] }
226
+ end
150
227
end
151
228
152
- defp script_list ( [ elem1 | rest1 ] , [ elem2 | rest2 ] , false , acc ) do
229
+ defp script_keyword_inner ( { key , val1 } , { key , val2 } ) ,
230
+ do: [ { :eq , format_key ( key , true ) } , script_inner ( val1 , val2 ) ]
231
+
232
+ defp script_keyword_inner ( _pair1 , _pair2 ) ,
233
+ do: nil
234
+
235
+ defp move_right ( { x , x , [ elem1 | rest1 ] = list1 , [ elem2 | rest2 ] , edits } ) do
236
+ if result = script_keyword_inner ( elem1 , elem2 ) do
237
+ { x + 1 , x + 1 , rest1 , rest2 , [ { :diff , result } | edits ] }
238
+ else
239
+ { x + 1 , x , list1 , rest2 , [ { :ins , elem2 } | edits ] }
240
+ end
241
+ end
242
+
243
+ defp move_right ( { x , y , list1 , [ elem | rest ] , edits } ) do
244
+ { x + 1 , y , list1 , rest , [ { :ins , elem } | edits ] }
245
+ end
246
+
247
+ defp move_right ( { x , y , list1 , [ ] , edits } ) do
248
+ { x + 1 , y , list1 , [ ] , edits }
249
+ end
250
+
251
+ defp move_down ( { x , x , [ elem1 | rest1 ] , [ elem2 | rest2 ] = list2 , edits } ) do
252
+ if result = script_keyword_inner ( elem1 , elem2 ) do
253
+ { x + 1 , x + 1 , rest1 , rest2 , [ { :diff , result } | edits ] }
254
+ else
255
+ { x , x + 1 , rest1 , list2 , [ { :del , elem1 } | edits ] }
256
+ end
257
+ end
258
+
259
+ defp move_down ( { x , y , [ elem | rest ] , list2 , edits } ) do
260
+ { x , y + 1 , rest , list2 , [ { :del , elem } | edits ] }
261
+ end
262
+
263
+ defp move_down ( { x , y , [ ] , list2 , edits } ) do
264
+ { x , y + 1 , [ ] , list2 , edits }
265
+ end
266
+
267
+ defp follow_snake ( { x , y , [ elem | rest1 ] , [ elem | rest2 ] , edits } ) do
268
+ follow_snake ( { x + 1 , y + 1 , rest1 , rest2 , [ { :eq , elem } | edits ] } )
269
+ end
270
+
271
+ defp follow_snake ( { _x , _y , [ ] , [ ] , edits } ) do
272
+ { :done , edits }
273
+ end
274
+
275
+ defp follow_snake ( path ) do
276
+ { :cont , path }
277
+ end
278
+
279
+ defp script_list ( [ ] , [ ] , acc ) do
280
+ [ [ _ | elem_diff ] | rest ] = Enum . reverse ( acc )
281
+ [ { :eq , "[" } , [ elem_diff | rest ] , { :eq , "]" } ]
282
+ end
283
+
284
+ defp script_list ( [ ] , [ elem | rest ] , acc ) do
285
+ elem_diff = [ ins: inspect ( elem ) ]
286
+ script_list ( [ ] , rest , [ [ ins: ", " ] ++ elem_diff | acc ] )
287
+ end
288
+
289
+ defp script_list ( [ elem | rest ] , [ ] , acc ) do
290
+ elem_diff = [ del: inspect ( elem ) ]
291
+ script_list ( rest , [ ] , [ [ del: ", " ] ++ elem_diff | acc ] )
292
+ end
293
+
294
+ defp script_list ( [ elem | rest1 ] , [ elem | rest2 ] , acc ) do
295
+ elem_diff = [ eq: inspect ( elem ) ]
296
+ script_list ( rest1 , rest2 , [ [ eq: ", " ] ++ elem_diff | acc ] )
297
+ end
298
+
299
+ defp script_list ( [ elem1 | rest1 ] , [ elem2 | rest2 ] , acc ) do
153
300
elem_diff = script_inner ( elem1 , elem2 )
154
- script_list ( rest1 , rest2 , false , [ [ eq: ", " ] ++ elem_diff | acc ] )
301
+ script_list ( rest1 , rest2 , [ [ eq: ", " ] ++ elem_diff | acc ] )
155
302
end
156
303
157
- defp script_list ( last , [ elem | rest ] , keyword? , acc ) do
304
+ defp script_list ( last , [ elem | rest ] , acc ) do
158
305
joiner_diff = [ del: " |" , ins: "," , eq: " " ]
159
306
elem_diff = script_inner ( last , elem )
160
307
new_acc = [ joiner_diff ++ elem_diff | acc ]
161
- script_list ( [ ] , rest , keyword? , new_acc )
308
+ script_list ( [ ] , rest , new_acc )
162
309
end
163
310
164
- defp script_list ( [ elem | rest ] , last , keyword? , acc ) do
311
+ defp script_list ( [ elem | rest ] , last , acc ) do
165
312
joiner_diff = [ del: "," , ins: " |" , eq: " " ]
166
313
elem_diff = script_inner ( elem , last )
167
314
new_acc = [ joiner_diff ++ elem_diff | acc ]
168
- script_list ( rest , [ ] , keyword? , new_acc )
315
+ script_list ( rest , [ ] , new_acc )
169
316
end
170
317
171
- defp script_list ( last1 , last2 , keyword? , acc ) do
318
+ defp script_list ( last1 , last2 , acc ) do
172
319
elem_diff =
173
320
cond do
174
321
last1 == [ ] ->
@@ -178,14 +325,9 @@ defmodule ExUnit.Diff do
178
325
true ->
179
326
[ eq: " | " ] ++ script_inner ( last1 , last2 )
180
327
end
181
- script_list ( [ ] , [ ] , keyword? , [ elem_diff | acc ] )
328
+ script_list ( [ ] , [ ] , [ elem_diff | acc ] )
182
329
end
183
330
184
- defp format_list_elem ( elem , false ) ,
185
- do: inspect ( elem )
186
- defp format_list_elem ( { key , val } , true ) ,
187
- do: format_key_value ( key , val , true )
188
-
189
331
defp script_tuple ( { _tuple1 , - 1 } , { _tuple2 , - 1 } , acc ) do
190
332
[ [ _ | elem_diff ] | rest ] = acc
191
333
[ { :eq , "{" } , [ elem_diff | rest ] , { :eq , "}" } ]
0 commit comments