Packages

A Gleam library for comparing strings/lists and producing a textual (styled) representation of the differences.

Current section

Files

Jump to
gap src gap.erl
Raw

src/gap.erl

-module(gap).
-compile([no_auto_import, nowarn_unused_vars]).
-export([to_styled/1, compare_lists/2, compare_strings/2]).
-export_type([score/1]).
-type score(FMG) :: {score, integer(), gleam@option:option(FMG)}.
-spec to_styled(gap@comparison:comparison(any())) -> gap@styled_comparison:styled_comparison().
to_styled(Comparison) ->
_pipe = Comparison,
_pipe@1 = gap@styling:from_comparison(_pipe),
_pipe@2 = gap@styling:highlight(
_pipe@1,
fun gap@styling:first_highlight_default/1,
fun gap@styling:second_highlight_default/1,
fun gap@styling:no_highlight/1
),
gap@styling:to_styled_comparison(_pipe@2).
-spec collect_matches(
gleam@map:map_(FNM, any()),
list(FMU),
fun((FNM) -> integer())
) -> list(gap@comparison:match(list(FMU))).
collect_matches(Tracking, Str, Extract_fun) ->
Matching_indexes = begin
_pipe = gleam@map:keys(Tracking),
_pipe@1 = gleam@list:map(_pipe, Extract_fun),
gleam@set:from_list(_pipe@1)
end,
Matches = begin
_pipe@2 = Str,
gleam@list:index_map(
_pipe@2,
fun(Index, Item) ->
case gleam@set:contains(Matching_indexes, Index) of
true ->
{match, Item};
false ->
{no_match, Item}
end
end
)
end,
_pipe@3 = Matches,
_pipe@4 = gleam@list:chunk(_pipe@3, fun(Match) -> case Match of
{match, _} ->
true;
{no_match, _} ->
false
end end),
gleam@list:map(_pipe@4, fun(Match_list) -> case Match_list of
[{match, _} | _] ->
{match,
gleam@list:filter_map(
Match_list,
fun(Match@1) -> case Match@1 of
{match, Item@1} ->
{ok, Item@1};
{no_match, _} ->
{error, nil}
end end
)};
[{no_match, _} | _] ->
{no_match,
gleam@list:filter_map(
Match_list,
fun(Match@2) -> case Match@2 of
{no_match, Item@2} ->
{ok, Item@2};
{match, _} ->
{error, nil}
end end
)}
end end).
-spec build_diff_map(
FNE,
integer(),
FNE,
integer(),
gleam@map:map_({integer(), integer()}, score(FNE))
) -> gleam@map:map_({integer(), integer()}, score(FNE)).
build_diff_map(First_item, First_index, Second_item, Second_index, Diff_map) ->
Prev_score = begin
_pipe = gleam@map:get(Diff_map, {First_index - 1, Second_index - 1}),
gleam@result:unwrap(_pipe, {score, 0, none})
end,
Derived_score_up = begin
_pipe@1 = Diff_map,
_pipe@2 = gleam@map:get(_pipe@1, {First_index, Second_index - 1}),
gleam@result:unwrap(_pipe@2, {score, 0, none})
end,
Derived_score_back = begin
_pipe@3 = Diff_map,
_pipe@4 = gleam@map:get(_pipe@3, {First_index - 1, Second_index}),
gleam@result:unwrap(_pipe@4, {score, 0, none})
end,
Derived_score = gleam@int:max(
erlang:element(2, Derived_score_up),
erlang:element(2, Derived_score_back)
),
This_score = case First_item =:= Second_item of
true ->
{score, erlang:element(2, Prev_score) + 1, {some, First_item}};
false ->
{score, Derived_score, none}
end,
_pipe@5 = Diff_map,
gleam@map:insert(_pipe@5, {First_index, Second_index}, This_score).
-spec back_track(
gleam@map:map_({integer(), integer()}, score(FMY)),
integer(),
integer(),
list({{integer(), integer()}, FMY})
) -> list({{integer(), integer()}, FMY}).
back_track(Diff_map, First_index, Second_index, Stack) ->
case (First_index =:= 0) orelse (Second_index =:= 0) of
true ->
This_score = begin
_pipe = gleam@map:get(Diff_map, {First_index, Second_index}),
gleam@result:unwrap(_pipe, {score, 0, none})
end,
case This_score of
{score, _, {some, Item}} ->
[{{First_index, Second_index}, Item} | Stack];
_ ->
case {First_index, Second_index} of
{0, A} when A > 0 ->
back_track(
Diff_map,
First_index,
Second_index - 1,
Stack
);
{A@1, 0} when A@1 > 0 ->
back_track(
Diff_map,
First_index - 1,
Second_index,
Stack
);
{0, 0} ->
Stack;
{_, _} ->
back_track(
Diff_map,
First_index - 1,
Second_index,
Stack
)
end
end;
false ->
This_score@1 = begin
_pipe@1 = gleam@map:get(Diff_map, {First_index, Second_index}),
gleam@result:unwrap(_pipe@1, {score, 0, none})
end,
case This_score@1 of
{score, _, {some, Item@1}} ->
back_track(
Diff_map,
First_index - 1,
Second_index - 1,
[{{First_index, Second_index}, Item@1} | Stack]
);
{score, _, none} ->
Up = begin
_pipe@2 = gleam@map:get(
Diff_map,
{First_index, Second_index - 1}
),
gleam@result:unwrap(_pipe@2, {score, 0, none})
end,
Back = begin
_pipe@3 = gleam@map:get(
Diff_map,
{First_index - 1, Second_index}
),
gleam@result:unwrap(_pipe@3, {score, 0, none})
end,
case gleam@int:compare(
erlang:element(2, Up),
erlang:element(2, Back)
) of
gt ->
back_track(
Diff_map,
First_index,
Second_index - 1,
Stack
);
lt ->
back_track(
Diff_map,
First_index - 1,
Second_index,
Stack
);
eq ->
case {First_index, Second_index} of
{0, A@2} when A@2 > 0 ->
back_track(
Diff_map,
First_index,
Second_index - 1,
Stack
);
{A@3, 0} when A@3 > 0 ->
back_track(
Diff_map,
First_index - 1,
Second_index,
Stack
);
{0, 0} ->
Stack;
{_, _} ->
back_track(
Diff_map,
First_index - 1,
Second_index,
Stack
)
end
end
end
end.
-spec compare_lists(list(FMP), list(FMP)) -> gap@comparison:comparison(FMP).
compare_lists(First_sequence, Second_sequence) ->
Diff_map@2 = begin
_pipe = Second_sequence,
gleam@list:index_fold(
_pipe,
gleam@map:new(),
fun(Diff_map, Item_second, Index_second) ->
_pipe@1 = First_sequence,
gleam@list:index_fold(
_pipe@1,
Diff_map,
fun(Diff_map@1, Item_first, Index_first) ->
build_diff_map(
Item_first,
Index_first,
Item_second,
Index_second,
Diff_map@1
)
end
)
end
)
end,
Tracking = begin
_pipe@2 = back_track(
Diff_map@2,
gleam@list:length(First_sequence) - 1,
gleam@list:length(Second_sequence) - 1,
[]
),
gleam@map:from_list(_pipe@2)
end,
First_segments = collect_matches(
Tracking,
First_sequence,
fun(Key) ->
{First, _} = Key,
First
end
),
Second_segments = collect_matches(
Tracking,
Second_sequence,
fun(Key@1) ->
{_, Second} = Key@1,
Second
end
),
{list_comparison, First_segments, Second_segments}.
-spec compare_strings(binary(), binary()) -> gap@comparison:comparison(binary()).
compare_strings(First, Second) ->
Comparison = compare_lists(
gleam@string:to_graphemes(First),
gleam@string:to_graphemes(Second)
),
case Comparison of
{list_comparison, First@1, Second@1} ->
{string_comparison, First@1, Second@1};
{string_comparison, First@2, Second@2} ->
{string_comparison, First@2, Second@2}
end.