Skip to content

Commit b39f4e4

Browse files
committed
minor refactors
1 parent 315c911 commit b39f4e4

6 files changed

Lines changed: 139 additions & 55 deletions

File tree

lib/dmp/diff.ex

Lines changed: 34 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,16 @@ defmodule Dmp.Diff do
77

88
alias Dmp.{Cursor, Options}
99

10-
@typedoc "A diff's operation type."
11-
@type op() :: :delete | :insert | :equal
10+
@typedoc """
11+
A diff's operation type. The operation `:nil` is used internally
12+
to indicate a nil value for the diff.
13+
"""
14+
@type op() :: :delete | :insert | :equal | nil
1215

13-
@typedoc "The diff tuple, consisting of two elements: the operation and the associated text."
16+
@typedoc """
17+
The diff tuple, consisting of two elements: the operation
18+
and the associated text.
19+
"""
1420
@type t() :: {op(), String.t()}
1521

1622
@typedoc """
@@ -251,6 +257,9 @@ defmodule Dmp.Diff do
251257
end
252258

253259
{diffs2, 0, 0, "", ""}
260+
261+
_ ->
262+
raise RuntimeError, "Invalid operation #{inspect(op)}"
254263
end
255264

256265
line_mode_loop(
@@ -1668,10 +1677,6 @@ defmodule Dmp.Diff do
16681677
|> remove_dummy()
16691678
end
16701679

1671-
# Extract text for prefix and suffix from prev_diff and next_diff
1672-
defp undiff({op, text}), do: {op, text}
1673-
defp undiff(_), do: {:equal, ""}
1674-
16751680
@type first_pass_acc() ::
16761681
{non_neg_integer(), non_neg_integer(), String.t(), String.t()}
16771682

@@ -1714,6 +1719,9 @@ defmodule Dmp.Diff do
17141719
end
17151720

17161721
{diffs, {0, 0, "", ""}}
1722+
1723+
_ ->
1724+
raise RuntimeError, "Invalid operation #{inspect(op)}"
17171725
end
17181726

17191727
diffs
@@ -1766,7 +1774,7 @@ defmodule Dmp.Diff do
17661774

17671775
diffs =
17681776
if is_tuple(prev_diff) do
1769-
{prev_op, prev_text} = undiff(prev_diff)
1777+
{prev_op, prev_text} = prev_diff
17701778

17711779
if prev_op != :equal do
17721780
raise RuntimeError, "Previous diff should have been an equality."
@@ -1951,6 +1959,9 @@ defmodule Dmp.Diff do
19511959

19521960
:delete ->
19531961
{chars1 + text_length, chars2}
1962+
1963+
_ ->
1964+
raise RuntimeError, "Invalid operation #{inspect(op)}"
19541965
end
19551966

19561967
if chars1 > loc do
@@ -1992,6 +2003,7 @@ defmodule Dmp.Diff do
19922003
:insert -> acc <> "<ins style=\"background:#e6ffe6;\">" <> text <> "</ins>"
19932004
:delete -> acc <> "<del style=\"background:#ffe6e6;\">" <> text <> "</del>"
19942005
:equal -> acc <> "<span>" <> text <> "</span>"
2006+
_ -> raise RuntimeError, "Invalid operation #{inspect(op)}"
19952007
end
19962008
end)
19972009
end
@@ -2044,6 +2056,9 @@ defmodule Dmp.Diff do
20442056
:equal ->
20452057
# A deletion and an insertion is one substitution.
20462058
{levenshtein + max(insertions, deletions), 0, 0}
2059+
2060+
_ ->
2061+
raise RuntimeError, "Invalid operation #{inspect(op)}"
20472062
end
20482063
end)
20492064

@@ -2077,6 +2092,9 @@ defmodule Dmp.Diff do
20772092

20782093
:equal ->
20792094
acc <> "=#{String.length(text)}\t"
2095+
2096+
_ ->
2097+
raise RuntimeError, "Invalid operation #{inspect(op)}"
20802098
end
20812099
end)
20822100

@@ -2165,4 +2183,12 @@ defmodule Dmp.Diff do
21652183
raise ArgumentError, "Invalid number in from_delta: #{param}"
21662184
end
21672185
end
2186+
2187+
@doc """
2188+
Returns the diff tuple, or a "nil" pseudo-diff (with op `:nil`
2189+
and empty text).
2190+
"""
2191+
@spec undiff(nil | t()) :: t()
2192+
def undiff({op, text}), do: {op, text}
2193+
def undiff(nil), do: {nil, ""}
21682194
end

lib/dmp/match.ex

Lines changed: 37 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -66,11 +66,27 @@ defmodule Dmp.Match do
6666
(0.0 = perfection, 1.0 = very loose, default = 0.5).
6767
* `match_distance` - How far to search for a match (0 = exact location,
6868
1000+ = broad match, default = 1000).
69+
* `return_score` - Control what is returned. See the following.
6970
70-
Returns best match index or -1.
71+
If `return_score` is false (the default), returns the index of the best
72+
match closest to `loc` in `text`, or -1 if no suitable match was found.
73+
74+
If `return_score` is true, returns a tuple with two elements:
75+
* The best match index, or -1.
76+
* The adjusted match score at the returned index (a value less than
77+
or equal to the `match_threshold`). If no match was found,
78+
the score is 1.
7179
"""
72-
@spec bitap(String.t(), String.t(), non_neg_integer(), float(), non_neg_integer()) :: integer()
73-
def bitap(text, pattern, loc, match_threshold \\ 0.5, match_distance \\ 1000) do
80+
@spec bitap(String.t(), String.t(), non_neg_integer(), float(), non_neg_integer(), boolean()) ::
81+
integer()
82+
def bitap(
83+
text,
84+
pattern,
85+
loc,
86+
match_threshold \\ 0.5,
87+
match_distance \\ 1000,
88+
return_score \\ false
89+
) do
7490
pattern_length = String.length(pattern)
7591

7692
# `best_loc` - Is there a nearby exact match? (speedup), -1 if not found.
@@ -91,12 +107,20 @@ defmodule Dmp.Match do
91107
acc = {best_loc, score_threshold, text_length + pattern_length, %{}}
92108

93109
# Iterate over possible error levels
94-
{best_loc, _score_threshold, _max_distance, _rd} =
110+
{best_loc, score, _max_distance, _rd} =
95111
Enum.reduce_while(0..(pattern_length - 1), acc, fn d, acc ->
96112
best_match_at_error_level(d, acc, constants)
97113
end)
98114

99-
best_loc
115+
if return_score do
116+
if best_loc == -1 do
117+
{-1, 1.0}
118+
else
119+
{best_loc, score}
120+
end
121+
else
122+
best_loc
123+
end
100124
end
101125

102126
# Set a starting point if an exact match can be found,
@@ -119,12 +143,12 @@ defmodule Dmp.Match do
119143
-1 ->
120144
{best_loc, score}
121145

122-
best_loc_2 ->
146+
best_loc2 ->
123147
# Second match of pattern in text
124-
score_2 = bitap_score(0, best_loc_2, loc, pattern_length, match_distance)
148+
score2 = bitap_score(0, best_loc2, loc, pattern_length, match_distance)
125149

126-
if score_2 < score do
127-
{best_loc_2, score_2}
150+
if score2 < score do
151+
{best_loc2, score2}
128152
else
129153
{best_loc, score}
130154
end
@@ -163,13 +187,12 @@ defmodule Dmp.Match do
163187

164188
# `rd` is a sparse array of integers of capacity `finish + 2`
165189
rd = %{(finish + 1) => (1 <<< d) - 1}
166-
acc_2 = {best_loc, score_threshold, start, rd}
167-
168-
constants_2 = {d, text, loc, last_rd, s, matchmask, pattern_length, match_distance}
190+
acc2 = {best_loc, score_threshold, start, rd}
191+
constants2 = {d, text, loc, last_rd, s, matchmask, pattern_length, match_distance}
169192

170193
{best_loc, score_threshold, _start, rd} =
171-
Enum.reduce_while(finish..0//-1, acc_2, fn j, acc ->
172-
bitap_update(j, acc, constants_2)
194+
Enum.reduce_while(finish..0//-1, acc2, fn j, acc ->
195+
bitap_update(j, acc, constants2)
173196
end)
174197

175198
# One last time

lib/dmp/options.ex

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ defmodule Dmp.Options do
6464
6565
## Examples
6666
67-
iex> Options.valid_options!(nil)
67+
iex> Options.valid_options!([])
6868
[
6969
diff_edit_cost: 4,
7070
diff_timeout: 1.0,
@@ -83,7 +83,7 @@ defmodule Dmp.Options do
8383
def valid_options!([]), do: default()
8484

8585
# credo:disable-for-lines:25 Credo.Check.Refactor.CyclomaticComplexity
86-
def valid_options!(opts) do
86+
def valid_options!(opts) when is_list(opts) do
8787
opts = Keyword.merge(default(), opts) |> Enum.sort()
8888
diff_timeout = Keyword.fetch!(opts, :diff_timeout)
8989
diff_edit_cost = Keyword.fetch!(opts, :diff_edit_cost)

lib/dmp/patch.ex

Lines changed: 46 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,9 @@ defmodule Dmp.Patch do
7575

7676
:equal ->
7777
" "
78+
79+
_ ->
80+
raise RuntimeError, "Invalid operation #{inspect(op)}"
7881
end
7982

8083
text = uri_encode(text)
@@ -534,7 +537,9 @@ defmodule Dmp.Patch do
534537
opts
535538
)
536539

537-
if start_loc != -1 do
540+
if start_loc == -1 do
541+
{-1, -1}
542+
else
538543
end_loc =
539544
Match.main(
540545
text,
@@ -549,8 +554,6 @@ defmodule Dmp.Patch do
549554
else
550555
{start_loc, end_loc}
551556
end
552-
else
553-
{start_loc, -1}
554557
end
555558
else
556559
{Match.main(text, text1, expected_loc, opts), -1}
@@ -586,11 +589,14 @@ defmodule Dmp.Patch do
586589

587590
# Returns true if the end points match, but the content is unacceptably bad.
588591
def poor_match(diffs, text1, opts) do
589-
lev = Diff.levenshtein(diffs)
590592
text1_length = String.length(text1)
591593

592-
text1_length > Keyword.fetch!(opts, :match_max_bits) &&
593-
lev / text1_length > Keyword.fetch!(opts, :patch_delete_threshold)
594+
if text1_length > Keyword.fetch!(opts, :match_max_bits) do
595+
normalized_lev = Diff.levenshtein(diffs) / text1_length
596+
normalized_lev > Keyword.fetch!(opts, :patch_delete_threshold)
597+
else
598+
false
599+
end
594600
end
595601

596602
def apply_match_diff({op, first_text}, acc_text, index1, diffs, start_loc) do
@@ -621,6 +627,9 @@ defmodule Dmp.Patch do
621627
substring(acc_text, start_loc + index3)
622628

623629
{acc_text, index1}
630+
631+
_ ->
632+
raise RuntimeError, "Invalid operation #{inspect(op)}"
624633
end
625634
end
626635

@@ -680,19 +689,9 @@ defmodule Dmp.Patch do
680689
padding_length = String.length(null_padding)
681690
first_diff = List.first(first_patch.diffs)
682691

683-
if is_nil(first_diff) || elem(first_diff, 0) != :equal do
684-
# Add null_padding equality.
685-
# start1 and start2 should be 0.
686-
%Patch{
687-
first_patch
688-
| diffs: [{:equal, null_padding} | first_patch.diffs],
689-
start1: first_patch.start1 - padding_length,
690-
start2: first_patch.start2 - padding_length,
691-
length1: first_patch.length1 + padding_length,
692-
length2: first_patch.length2 + padding_length
693-
}
694-
else
695-
{op, text} = first_diff
692+
{op, text} = Diff.undiff(first_diff)
693+
694+
if op == :equal do
696695
text_length = String.length(text)
697696

698697
if padding_length > text_length do
@@ -712,6 +711,18 @@ defmodule Dmp.Patch do
712711
else
713712
first_patch
714713
end
714+
else
715+
# :nil, :insert, or :delete
716+
# Add null_padding equality.
717+
# start1 and start2 should be 0.
718+
%Patch{
719+
first_patch
720+
| diffs: [{:equal, null_padding} | first_patch.diffs],
721+
start1: first_patch.start1 - padding_length,
722+
start2: first_patch.start2 - padding_length,
723+
length1: first_patch.length1 + padding_length,
724+
length2: first_patch.length2 + padding_length
725+
}
715726
end
716727
end
717728

@@ -720,16 +731,9 @@ defmodule Dmp.Patch do
720731
padding_length = String.length(null_padding)
721732
last_diff = List.last(last_patch.diffs)
722733

723-
if is_nil(last_diff) || elem(last_diff, 0) != :equal do
724-
# Add null_padding equality.
725-
%Patch{
726-
last_patch
727-
| diffs: last_patch.diffs ++ [{:equal, null_padding}],
728-
length1: last_patch.length1 + padding_length,
729-
length2: last_patch.length2 + padding_length
730-
}
731-
else
732-
{op, text} = last_diff
734+
{op, text} = Diff.undiff(last_diff)
735+
736+
if op == :equal do
733737
text_length = String.length(text)
734738

735739
if padding_length > text_length do
@@ -746,6 +750,15 @@ defmodule Dmp.Patch do
746750
else
747751
last_patch
748752
end
753+
else
754+
# :nil, :insert, or :delete
755+
# Add null_padding equality.
756+
%Patch{
757+
last_patch
758+
| diffs: last_patch.diffs ++ [{:equal, null_padding}],
759+
length1: last_patch.length1 + padding_length,
760+
length2: last_patch.length2 + padding_length
761+
}
749762
end
750763
end
751764

@@ -837,11 +850,13 @@ defmodule Dmp.Patch do
837850
if postcontext != "" do
838851
postcontext_length = String.length(postcontext)
839852
last_diff = List.last(patch.diffs)
853+
{op, text} = Diff.undiff(last_diff)
840854

841855
patch_diffs =
842-
if is_tuple(last_diff) && elem(last_diff, 0) == :equal do
843-
Enum.drop(patch.diffs, -1) ++ [{:equal, elem(last_diff, 1) <> postcontext}]
856+
if op == :equal do
857+
Enum.drop(patch.diffs, -1) ++ [{:equal, text <> postcontext}]
844858
else
859+
# :nil, :insert, or :delete
845860
patch.diffs ++ [{:equal, postcontext}]
846861
end
847862

test/diff_test.exs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,9 @@ defmodule DiffTest do
2323

2424
:equal ->
2525
{text1 <> text, text2 <> text}
26+
27+
_ ->
28+
raise RuntimeError, "Invalid operation #{inspect(op)}"
2629
end
2730
end)
2831
end

0 commit comments

Comments
 (0)