diff --git a/.gitignore b/.gitignore index 39d5cba..2e0b6a4 100644 --- a/.gitignore +++ b/.gitignore @@ -8,3 +8,10 @@ deps *.plt erl_crash.dump tags +doc/* +!doc/style.css +!doc/overview.edoc +!doc/img +!doc/img/* +!doc/img/logo.png +exdoc \ No newline at end of file diff --git a/Makefile b/Makefile index 82eaca1..59ef73d 100644 --- a/Makefile +++ b/Makefile @@ -21,21 +21,34 @@ test: eunit eunit: $(REBAR) $(REBAR) as test eunit - + xref: $(REBAR) $(REBAR) as test xref dialyzer: $(REBAR) $(REBAR) as test dialyzer -clean: $(REBAR) +clean: $(REBAR) clean_doc $(REBAR) clean +clean_doc: + @rm -f doc/*.html + @rm -f doc/erlang.png + @rm -f doc/edoc-info + distclean: rm -rf _build rm $(REBAR) +doc: $(REBAR) + $(REBAR) edoc + +doc_private: $(REBAR) + $(REBAR) as doc_private edoc +exdoc: $(REBAR) + $(REBAR) ex_doc --output exdoc --formatter html + # dializer build-plt: diff --git a/README.md b/README.md index 1854e3d..2225d66 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,8 @@ -# Diffy - +[![License](https://img.shields.io/badge/License-Apache_2.0-blue.svg?logo=apache&logoColor=red)](https://www.apache.org/licenses/LICENSE-2.0) ![Test](https://github.com/zotonic/diffy/workflows/Test/badge.svg) +# Diffy + Diff, Match and Patch implementation for Erlang. ## Introduction @@ -30,3 +31,16 @@ Example Much good info about diff, match and patch can found at link: http://neil.fraser.name/writing/diff/ +## Documentation generation + +## EDoc + +### Generate public API +`make docs` or `rebar3 edoc` + +## Generate private API +`make doc_private` or `rebar3 as doc_private edoc` + +#ExDoc + +`make exdoc` or `rebar3 ex_doc --output exdoc --formatter html` \ No newline at end of file diff --git a/doc/img/logo.png b/doc/img/logo.png new file mode 100644 index 0000000..94ea9de Binary files /dev/null and b/doc/img/logo.png differ diff --git a/doc/overview.edoc b/doc/overview.edoc new file mode 100644 index 0000000..6778534 --- /dev/null +++ b/doc/overview.edoc @@ -0,0 +1,9 @@ +@author Maas-Maarten Zeeman +@author Marc Worrell + [https://whatwebwhat.com/] +@author Zotonic Team + [http://zotonic.com] +@title diffy +@doc Diff, match patch implementation +@copyright Apache-2.0 +@since 2014 \ No newline at end of file diff --git a/doc/style.css b/doc/style.css new file mode 100644 index 0000000..cfdb6ba --- /dev/null +++ b/doc/style.css @@ -0,0 +1,71 @@ +/* standard EDoc style sheet */ +body { + font-family: Verdana, Arial, Helvetica, sans-serif; + margin-left: .25in; + margin-right: .2in; + margin-top: 0.2in; + margin-bottom: 0.2in; + color: #000000; + background-color: #ffffff; +} +h1,h2 { + margin-left: -0.2in; +} +div.navbar { + background-color: #add8e6; + padding: 0.2em; +} +h2.indextitle { + padding: 0.4em; + background-color: #add8e6; +} +h3.function,h3.typedecl { + background-color: #add8e6; + padding-left: 1em; +} +div.spec { + margin-left: 2em; + + background-color: #eeeeee; +} +a.module { + text-decoration:none +} +a.module:hover { + background-color: #eeeeee; +} +ul.definitions { + list-style-type: none; +} +ul.index { + list-style-type: none; + background-color: #eeeeee; +} + +/* + * Minor style tweaks + */ +ul { + list-style-type: square; +} +table { + border-collapse: collapse; +} +td { + padding: 3px; + vertical-align: middle; +} + +/* +Tune styles +*/ + +table[summary="navigation bar"] { + background-image: url('http://zotonic.com/lib/images/logo.png'); + background-repeat: no-repeat; + background-position: center; +} + +code, p>tt, a>tt { + font-size: 1.2em; +} diff --git a/rebar.config b/rebar.config index adb9366..fa33d64 100644 --- a/rebar.config +++ b/rebar.config @@ -3,11 +3,13 @@ {zotonic_stdlib, "1.2.3"} ]}. - {profiles, [ {test, [ - {deps, [ - {proper, "1.2.0"} + {plugins, [ + rebar3_proper + ]}, + {deps, [ + {proper, "1.4.0"} ]}, {xref_checks, [ @@ -24,5 +26,32 @@ no_return ]} ]} - ]} + ]}, + {doc_private, [ + {edoc_opts, [ + {private, true} + ]} + ]} +]}. + +{project_plugins, [rebar3_hex, rebar3_ex_doc]}. + +{edoc_opts, [ + {preprocess, true}, {stylesheet, "style.css"} ]}. + +{hex, [ + {doc, #{provider => ex_doc}} +]}. + +{ex_doc, [ + {extras, [ + {"README.md", #{title => "Overview"}}, + {"LICENSE", #{title => "License"}} + ]}, + {main, "README.md"}, + {source_url, "https://github.com/zotonic/diffy"}, + {assets, "assets"}, + {api_reference, true} +]}. + diff --git a/src/diffy.erl b/src/diffy.erl index fc75a87..9c5f869 100644 --- a/src/diffy.erl +++ b/src/diffy.erl @@ -2,7 +2,7 @@ %% @copyright 2014-2019 Maas-Maarten Zeeman %% %% @doc Diffy, an erlang diff match and patch implementation -%% +%% @end %% Copyright 2014-2019 Maas-Maarten Zeeman %% %% Licensed under the Apache License, Version 2.0 (the "License"); @@ -79,9 +79,20 @@ length2 = 0 }). +-type patch() :: #patch{ + diffs :: diffs(), + start1 :: non_neg_integer(), + start2 :: non_neg_integer(), + length1 :: non_neg_integer(), + length2 :: non_neg_integer() + }. + % @doc Compute the difference between two binary texts % --spec diff(unicode:unicode_binary(), unicode:unicode_binary()) -> diffs(). +-spec diff(Text1, Text2) -> Result when + Text1 :: unicode:unicode_binary(), + Text2 :: unicode:unicode_binary(), + Result :: diffs(). diff(Text1, Text2) -> diff(Text1, Text2, true). @@ -278,7 +289,11 @@ compute_diff1(Text1, Text2, false) -> diff_bisect(Text1, Text2). -%% Compute diff in linemode +%% @doc Compute diff in linemode +-spec diff_linemode(Text1, Text2) -> Result when + Text1 :: unicode:unicode_binary(), + Text2 :: unicode:unicode_binary(), + Result :: diffs(). diff_linemode(Text1, Text2) -> {CharText1, CharText2, Lines} = lines_to_chars(Text1, Text2), Diffs = diff(CharText1, CharText2, false), @@ -363,10 +378,10 @@ chars_to_lines([{Op, Data}|Rest], LineArray, Acc) -> chars_to_lines(Rest, LineArray, [{Op, Data1}|Acc]). -% Find the 'middle snake' of a diff, split the problem in two +%% @doc Find the 'middle snake' of a diff, split the problem in two %% and return the recursively constructed diff. %% See Myers 1986 paper: An O(ND) Difference Algorithm and Its Variations. -%% +%%``` %% Args: %% text1: Old string to be diffed. %% text2: New string to be diffed. @@ -374,7 +389,15 @@ chars_to_lines([{Op, Data}|Rest], LineArray, Acc) -> %% %% Returns: %% Array of diff tuples. -%% """ +%%''' +%% @throws {overlap, A1, B1, X, Y} + +-spec diff_bisect(A, B) -> Result when + A :: unicode:unicode_binary(), + B :: unicode:unicode_binary(), + Result :: [DiffTuple], + DiffTuple :: {diff_op(), DiffTupleItem}, + DiffTupleItem :: unicode:unicode_binary(). diff_bisect(A, B) when is_binary(A) andalso is_binary(B) -> ArrA = array_from_binary(A), ArrB = array_from_binary(B), @@ -521,7 +544,10 @@ diff_bisect_split(A, B, X, Y) -> Diffs ++ DiffsB. % @doc Convert the diffs into a pretty html report --spec pretty_html(diffs()) -> iolist(). + +-spec pretty_html(Diffs) -> Result when + Diffs :: diffs(), + Result :: iolist(). pretty_html(Diffs) -> pretty_html(Diffs, []). @@ -540,6 +566,10 @@ pretty_html([{Op, Data}|T], Acc) -> pretty_html(T, [HTML|Acc]). % @doc Compute the source text from a list of diffs. + +-spec source_text(Diffs) -> Result when + Diffs :: diffs(), + Result :: unicode:unicode_binary(). source_text(Diffs) -> source_text(Diffs, <<>>). @@ -552,7 +582,10 @@ source_text([{_Op, Data}|T], Acc) -> % @doc Compute the destination text from a list of diffs. -destination_text(Diffs) -> +-spec destination_text(Diffs) -> Result when + Diffs :: diffs(), + Result :: unicode:unicode_binary(). +destination_text(Diffs) -> destination_text(Diffs, <<>>). destination_text([], Acc) -> @@ -562,7 +595,11 @@ destination_text([{delete, _Data}|T], Acc) -> destination_text([{_Op, Data}|T], Acc) -> destination_text(T, <>). -% @doc Compute the Levenshtein distance, the number of inserted, deleted or substituted characters. +%% @doc Compute the Levenshtein distance, the number of inserted, deleted or substituted characters. + +-spec levenshtein(Diffs) -> Result when + Diffs :: diffs(), + Result :: non_neg_integer(). levenshtein(Diffs) -> levenshtein(Diffs, 0, 0, 0). @@ -579,7 +616,9 @@ levenshtein([{equal, _Data}|T], Insertions, Deletions, Levenshtein) -> %@ @doc Cleanup diffs. % Remove empty operations, merge equal opearations, edits before equal operation and common prefix operations. % --spec cleanup_merge(diffs()) -> diffs(). +-spec cleanup_merge(Diffs) -> Result when + Diffs :: diffs(), + Result :: diffs(). cleanup_merge(Diffs) -> cleanup_merge(Diffs, []). @@ -620,9 +659,11 @@ cleanup_merge([{equal, E1}=H|T], [{Op, I}, {equal, E2}|AccTail]=Acc) when Op =:= cleanup_merge([H|T], Acc) -> cleanup_merge(T, [H|Acc]). -% @doc Do semantic cleanup of diffs -% --spec cleanup_semantic(diffs()) -> diffs(). +%% @doc Do semantic cleanup of diffs + +-spec cleanup_semantic(Diffs) -> Result when + Diffs :: diffs(), + Result :: diffs(). cleanup_semantic(Diffs) -> cleanup_semantic(Diffs, []). @@ -631,12 +672,18 @@ cleanup_semantic([], Acc) -> cleanup_semantic([H|T], Acc) -> cleanup_semantic(T, [H|Acc]). -% @doc Do efficiency cleanup of diffs. -% --spec cleanup_efficiency(diffs()) -> diffs(). +%% @doc Do efficiency cleanup of diffs. + +-spec cleanup_efficiency(Diffs) -> Result when + Diffs :: diffs(), + Result :: diffs(). cleanup_efficiency(Diffs) -> cleanup_efficiency(Diffs, 4). +-spec cleanup_efficiency(Diffs, EditCost) -> Result when + Diffs :: diffs(), + EditCost :: non_neg_integer(), + Result :: diffs(). cleanup_efficiency(Diffs, EditCost) -> cleanup_efficiency(Diffs, false, EditCost, []). @@ -693,18 +740,29 @@ text_smaller_than(<<_C, Rest/binary>>, Size) when Size > 0 -> text_smaller_than(Rest, Size-1). % @doc create a patch from a list of diffs + +-spec make_patch(Diffs) -> Result when + Diffs :: diffs(), + Result :: [patch()]. make_patch(Diffs) when is_list(Diffs) -> %% Reconstruct the source-text from the diffs. make_patch(Diffs, source_text(Diffs)). % @doc create a patch from the source and destination texts + +-spec make_patch(SourceText, DestinationText) -> Result when + SourceText :: unicode:unicode_binary(), + DestinationText :: unicode:unicode_binary(), + Result :: [patch()]. make_patch(SourceText, DestinationText) when is_binary(SourceText) andalso is_binary(DestinationText) -> Diffs = diff(SourceText, DestinationText), Diffs1 = cleanup_semantic(Diffs), Diffs2 = cleanup_efficiency(Diffs1), make_patch(Diffs2, SourceText); -% @doc Creata a patch from a list of diffs and the source text. +%% @doc Creata a patch from a list of diffs and the source text. +%% @throws throw(not_yet) + make_patch(Diffs, SourceText) when is_list(Diffs) andalso is_binary(SourceText) -> make_patch(Diffs, SourceText, SourceText, 0, 0, [#patch{}]). @@ -767,7 +825,13 @@ make_patch([{equal, Data}|T], PrePatchText, PostPatchText, Count1, Count2, [Patc make_patch(T, PrePatchText, PostPatchText, Count1+Size, Count2+Size, [P|Rest]). -% @doc Returns true iff Pattern is a unique match inside Text. +%% @doc Returns `true' if `Pattern' is a unique match inside `Text'. + +-spec unique_match(Pattern, Text) -> Result when + Pattern :: Found | nomatch, + Text :: unicode:unicode_binary(), + Result :: boolean(), + Found :: binary:part(). unique_match(Pattern, Text) -> TextSize = size(Text), case binary:match(Text, Pattern) of @@ -843,6 +907,14 @@ for(From, To, Step, Fun, State) -> S1 end. +-spec split_pre_and_suffix(Text1, Text2) -> Result when + Text1 :: unicode:unicode_binary(), + Text2 :: unicode:unicode_binary(), + Result :: {Prefix, MiddleText1, MiddleText2, Suffix}, + Prefix :: unicode:unicode_binary(), + MiddleText1 :: unicode:unicode_binary(), + MiddleText2 :: unicode:unicode_binary(), + Suffix :: unicode:unicode_binary(). split_pre_and_suffix(Text1, Text2) -> Prefix = common_prefix(Text1, Text2), PrefixLen = size(Prefix), @@ -859,7 +931,7 @@ split_pre_and_suffix(Text1, Text2) -> {Prefix, MiddleText1, MiddleText2, Suffix}. -% @doc Return the common prefix of Text1 and Text2. (utf8 aware) +%% @doc Return the common prefix of Text1 and Text2. (utf8 aware) common_prefix(Text1, Text2) -> Length = binary:longest_common_prefix([Text1, Text2]), Prefix = binary:part(Text1, 0, Length), @@ -868,7 +940,7 @@ common_prefix(Text1, Text2) -> {Prefix1, _} = repair_tail(Prefix), Prefix1. -% @doc Return the common prefix of Text1 and Text2 (utf8 aware) +%% @doc Return the common prefix of Text1 and Text2 (utf8 aware) common_suffix(Text1, Text2) -> Length = binary:longest_common_suffix([Text1, Text2]), Suffix = binary:part(Text1, size(Text1), -Length), @@ -878,7 +950,12 @@ common_suffix(Text1, Text2) -> Suffix1. -% @doc Count the number of characters in a utf8 binary. +%% @doc Count the number of characters in a utf8 binary. +%% @throws error(badarg) + +-spec text_size(Text) -> Result when + Text :: unicode:unicode_binary(), + Result :: non_neg_integer(). text_size(Text) when is_binary(Text) -> text_size(Text, 0). @@ -894,6 +971,7 @@ text_size(_, _) -> %% % @doc Create an array from a utf8 binary. + array_from_binary(Bin) when is_binary(Bin) -> array_from_binary(Bin, 0, array:new()). diff --git a/src/diffy_simple_patch.erl b/src/diffy_simple_patch.erl index 66a6ae8..b470e24 100644 --- a/src/diffy_simple_patch.erl +++ b/src/diffy_simple_patch.erl @@ -2,7 +2,7 @@ %% @copyright 2014 Maas-Maarten Zeeman %% %% @doc Diffy, an erlang diff match and patch implementation -%% +%% @end %% Copyright 2014 Maas-Maarten Zeeman %% %% Licensed under the Apache License, Version 2.0 (the "License"); @@ -23,8 +23,11 @@ -export([make_patch/1, apply_patch/2]). -%% Make a simple patch with edit operations. -%% +%% @doc Make a simple patch with edit operations. + +-spec make_patch(Diffs) -> Result when + Diffs :: diffy_term:diffs(), + Result :: diffy_term:diffs(). make_patch(Diffs) -> make_patch(Diffs, []). @@ -43,6 +46,11 @@ patch_op(delete) -> skip; patch_op(equal) -> copy. % @doc Use the SourceText to reconstruct the destination text. + +-spec apply_patch(SourceText, Diffs) -> Result when + SourceText :: unicode:unicode_binary(), + Diffs :: diffy_term:diffs(), + Result :: binary(). apply_patch(SourceText, Diffs) -> apply_patch(SourceText, 0, Diffs, []). diff --git a/src/diffy_term.erl b/src/diffy_term.erl index ed25387..9d7df6e 100644 --- a/src/diffy_term.erl +++ b/src/diffy_term.erl @@ -3,7 +3,7 @@ %% %% @doc Diffy, an erlang diff match and patch implementation %% Adapted from diffy.erl for simple diff on a list of Erlang terms -%% +%% @end %% Copyright 2014-2015 Maas-Maarten Zeeman, Marc Worrell %% %% Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/test/diffy_tests.erl b/test/diffy_tests.erl index 42065a4..6908cb8 100644 --- a/test/diffy_tests.erl +++ b/test/diffy_tests.erl @@ -335,4 +335,4 @@ cleanup_semantic(Diffs) -> diffy:cleanup_semantic(Diffs). cleanup_merge(Diffs) -> - diffy:cleanup_merge(Diffs). + diffy:cleanup_merge(Diffs). \ No newline at end of file