@@ -40,11 +40,13 @@ defmodule ExUnit.Diff do
4040
4141 # Char lists and lists
4242 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 , [ ] )
4850 end
4951 end
5052
@@ -117,58 +119,203 @@ defmodule ExUnit.Diff do
117119 end )
118120 end
119121
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 )
123158 end
124159
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 ] )
128162 end
129163
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 )
133167 end
134168
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 ) }
138174 end
139175
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
150227 end
151228
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
153300 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 ] )
155302 end
156303
157- defp script_list ( last , [ elem | rest ] , keyword? , acc ) do
304+ defp script_list ( last , [ elem | rest ] , acc ) do
158305 joiner_diff = [ del: " |" , ins: "," , eq: " " ]
159306 elem_diff = script_inner ( last , elem )
160307 new_acc = [ joiner_diff ++ elem_diff | acc ]
161- script_list ( [ ] , rest , keyword? , new_acc )
308+ script_list ( [ ] , rest , new_acc )
162309 end
163310
164- defp script_list ( [ elem | rest ] , last , keyword? , acc ) do
311+ defp script_list ( [ elem | rest ] , last , acc ) do
165312 joiner_diff = [ del: "," , ins: " |" , eq: " " ]
166313 elem_diff = script_inner ( elem , last )
167314 new_acc = [ joiner_diff ++ elem_diff | acc ]
168- script_list ( rest , [ ] , keyword? , new_acc )
315+ script_list ( rest , [ ] , new_acc )
169316 end
170317
171- defp script_list ( last1 , last2 , keyword? , acc ) do
318+ defp script_list ( last1 , last2 , acc ) do
172319 elem_diff =
173320 cond do
174321 last1 == [ ] ->
@@ -178,14 +325,9 @@ defmodule ExUnit.Diff do
178325 true ->
179326 [ eq: " | " ] ++ script_inner ( last1 , last2 )
180327 end
181- script_list ( [ ] , [ ] , keyword? , [ elem_diff | acc ] )
328+ script_list ( [ ] , [ ] , [ elem_diff | acc ] )
182329 end
183330
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-
189331 defp script_tuple ( { _tuple1 , - 1 } , { _tuple2 , - 1 } , acc ) do
190332 [ [ _ | elem_diff ] | rest ] = acc
191333 [ { :eq , "{" } , [ elem_diff | rest ] , { :eq , "}" } ]
0 commit comments