support four primary version numbers of in parsing

The OTP Versions distributed with erlang tend to have four version
numbers not three. This is a fairly minor deviation from semver that
we can support. Basically, the semver parser treats the fourth version
in exactly the same way as the other three.

Signed-off-by: Jordan Wilberding <diginux@gmail.com>
This commit is contained in:
Eric Merritt 2012-09-17 09:38:32 -05:00 committed by Jordan Wilberding
parent 5105df48f9
commit b4ab414419
2 changed files with 83 additions and 20 deletions

View file

@ -1,3 +1,4 @@
%%%------------------------------------------------------------------- %%%-------------------------------------------------------------------
%%% @copyright (C) 2011, Erlware LLC %%% @copyright (C) 2011, Erlware LLC
%%% @doc %%% @doc
@ -28,16 +29,18 @@
%%% Public Types %%% Public Types
%%%=================================================================== %%%===================================================================
-type major_minor_patch() :: -type major_minor_patch_minpatch() ::
non_neg_integer() non_neg_integer()
| {non_neg_integer(), non_neg_integer()} | {non_neg_integer(), non_neg_integer()}
| {non_neg_integer(), non_neg_integer(), non_neg_integer()}. | {non_neg_integer(), non_neg_integer(), non_neg_integer()}
| {non_neg_integer(), non_neg_integer(),
non_neg_integer(), non_neg_integer()}.
-type alpha_part() :: integer() | binary() | string(). -type alpha_part() :: integer() | binary() | string().
-type alpha_info() :: {PreRelease::[alpha_part()], -type alpha_info() :: {PreRelease::[alpha_part()],
BuildVersion::[alpha_part()]}. BuildVersion::[alpha_part()]}.
-type semver() :: {major_minor_patch(), alpha_info()}. -type semver() :: {major_minor_patch_minpatch(), alpha_info()}.
-type version_string() :: string() | binary(). -type version_string() :: string() | binary().
@ -72,6 +75,13 @@ format({{Maj, Min, Patch}, {AlphaPart, BuildPart}}) ->
erlang:integer_to_list(Min), ".", erlang:integer_to_list(Min), ".",
erlang:integer_to_list(Patch), erlang:integer_to_list(Patch),
format_vsn_rest(<<"-">>, AlphaPart), format_vsn_rest(<<"-">>, AlphaPart),
format_vsn_rest(<<"+">>, BuildPart)];
format({{Maj, Min, Patch, MinPatch}, {AlphaPart, BuildPart}}) ->
[erlang:integer_to_list(Maj), ".",
erlang:integer_to_list(Min), ".",
erlang:integer_to_list(Patch), ".",
erlang:integer_to_list(MinPatch),
format_vsn_rest(<<"-">>, AlphaPart),
format_vsn_rest(<<"+">>, BuildPart)]. format_vsn_rest(<<"+">>, BuildPart)].
%% @doc test for quality between semver versions %% @doc test for quality between semver versions
@ -172,17 +182,20 @@ pes(VsnA, VsnB) ->
%% @doc helper function for the peg grammer to parse the iolist into a semver %% @doc helper function for the peg grammer to parse the iolist into a semver
-spec internal_parse_version(iolist()) -> semver(). -spec internal_parse_version(iolist()) -> semver().
internal_parse_version([MMP, AlphaPart, BuildPart, _]) -> internal_parse_version([MMP, AlphaPart, BuildPart, _]) ->
{parse_major_minor_patch(MMP), {parse_alpha_part(AlphaPart), {parse_major_minor_patch_minpatch(MMP), {parse_alpha_part(AlphaPart),
parse_alpha_part(BuildPart)}}. parse_alpha_part(BuildPart)}}.
%% @doc helper function for the peg grammer to parse the iolist into a major_minor_patch %% @doc helper function for the peg grammer to parse the iolist into a major_minor_patch
-spec parse_major_minor_patch(iolist()) -> major_minor_patch(). -spec parse_major_minor_patch_minpatch(iolist()) -> major_minor_patch_minpatch().
parse_major_minor_patch([MajVsn, [], []]) -> parse_major_minor_patch_minpatch([MajVsn, [], [], []]) ->
MajVsn; MajVsn;
parse_major_minor_patch([MajVsn, [<<".">>, MinVsn], []]) -> parse_major_minor_patch_minpatch([MajVsn, [<<".">>, MinVsn], [], []]) ->
{MajVsn, MinVsn}; {MajVsn, MinVsn};
parse_major_minor_patch([MajVsn, [<<".">>, MinVsn], [<<".">>, PatchVsn]]) -> parse_major_minor_patch_minpatch([MajVsn, [<<".">>, MinVsn], [<<".">>, PatchVsn], []]) ->
{MajVsn, MinVsn, PatchVsn}. {MajVsn, MinVsn, PatchVsn};
parse_major_minor_patch_minpatch([MajVsn, [<<".">>, MinVsn],
[<<".">>, PatchVsn], [<<".">>, MinPatch]]) ->
{MajVsn, MinVsn, PatchVsn, MinPatch}.
%% @doc helper function for the peg grammer to parse the iolist into an alpha part %% @doc helper function for the peg grammer to parse the iolist into an alpha part
-spec parse_alpha_part(iolist()) -> [alpha_part()]. -spec parse_alpha_part(iolist()) -> [alpha_part()].
@ -226,22 +239,29 @@ format_vsn_rest(TypeMark, [Head | Rest]) ->
-spec normalize(semver()) -> semver(). -spec normalize(semver()) -> semver().
normalize({Vsn, Rest}) normalize({Vsn, Rest})
when erlang:is_integer(Vsn) -> when erlang:is_integer(Vsn) ->
{{Vsn, 0, 0}, Rest}; {{Vsn, 0, 0, 0}, Rest};
normalize({{Maj, Min}, Rest}) -> normalize({{Maj, Min}, Rest}) ->
{{Maj, Min, 0}, Rest}; {{Maj, Min, 0, 0}, Rest};
normalize(Other) -> normalize({{Maj, Min, Patch}, Rest}) ->
{{Maj, Min, Patch, 0}, Rest};
normalize(Other = {{_, _, _, _}, {_,_}}) ->
Other. Other.
%% @doc to do the pessimistic compare we need a parsed semver. This is %% @doc to do the pessimistic compare we need a parsed semver. This is
%% the internal implementation of the of the pessimistic run. The %% the internal implementation of the of the pessimistic run. The
%% external just ensures that versions are parsed. %% external just ensures that versions are parsed.
-spec internal_pes(semver(), semver()) -> boolean().
internal_pes(VsnA, {{LM, LMI}, _}) -> internal_pes(VsnA, {{LM, LMI}, _}) ->
gte(VsnA, {{LM, LMI, 0}, {[], []}}) andalso gte(VsnA, {{LM, LMI, 0}, {[], []}}) andalso
lt(VsnA, {{LM + 1, 0, 0}, {[], []}}); lt(VsnA, {{LM + 1, 0, 0, 0}, {[], []}});
internal_pes(VsnA, {{LM, LMI, LP}, _}) -> internal_pes(VsnA, {{LM, LMI, LP}, _}) ->
gte(VsnA, {{LM, LMI, LP}, {[], []}}) gte(VsnA, {{LM, LMI, LP}, {[], []}})
andalso andalso
lt(VsnA, {{LM, LMI + 1, 0}, {[], []}}); lt(VsnA, {{LM, LMI + 1, 0, 0}, {[], []}});
internal_pes(VsnA, {{LM, LMI, LP, LMP}, _}) ->
gte(VsnA, {{LM, LMI, LP, LMP}, {[], []}})
andalso
lt(VsnA, {{LM, LMI, LP + 1, 0}, {[], []}});
internal_pes(Vsn, LVsn) -> internal_pes(Vsn, LVsn) ->
gte(Vsn, LVsn). gte(Vsn, LVsn).
@ -261,24 +281,38 @@ eql_test() ->
"1.0.0")), "1.0.0")),
?assertMatch(true, eql("1.0.0", ?assertMatch(true, eql("1.0.0",
"1")), "1")),
?assertMatch(true, eql("1.0.0.0",
"1")),
?assertMatch(true, eql("1.0+alpha.1", ?assertMatch(true, eql("1.0+alpha.1",
"1.0.0+alpha.1")), "1.0.0+alpha.1")),
?assertMatch(true, eql("1.0-alpha.1+build.1", ?assertMatch(true, eql("1.0-alpha.1+build.1",
"1.0.0-alpha.1+build.1")), "1.0.0-alpha.1+build.1")),
?assertMatch(true, eql("1.0-alpha.1+build.1",
"1.0.0.0-alpha.1+build.1")),
?assertMatch(true, not eql("1.0.0", ?assertMatch(true, not eql("1.0.0",
"1.0.1")), "1.0.1")),
?assertMatch(true, not eql("1.0.0-alpha", ?assertMatch(true, not eql("1.0.0-alpha",
"1.0.1+alpha")), "1.0.1+alpha")),
?assertMatch(true, not eql("1.0.0+build.1", ?assertMatch(true, not eql("1.0.0+build.1",
"1.0.1+build.2")). "1.0.1+build.2")),
?assertMatch(true, not eql("1.0.0.0+build.1",
"1.0.0.1+build.2")).
gt_test() -> gt_test() ->
?assertMatch(true, gt("1.0.0-alpha.1", ?assertMatch(true, gt("1.0.0-alpha.1",
"1.0.0-alpha")), "1.0.0-alpha")),
?assertMatch(true, gt("1.0.0.1-alpha.1",
"1.0.0.1-alpha")),
?assertMatch(true, gt("1.0.0.4-alpha.1",
"1.0.0.2-alpha")),
?assertMatch(true, gt("1.0.0.0-alpha.1",
"1.0.0-alpha")),
?assertMatch(true, gt("1.0.0-beta.2", ?assertMatch(true, gt("1.0.0-beta.2",
"1.0.0-alpha.1")), "1.0.0-alpha.1")),
?assertMatch(true, gt("1.0.0-beta.11", ?assertMatch(true, gt("1.0.0-beta.11",
"1.0.0-beta.2")), "1.0.0-beta.2")),
?assertMatch(true, gt("1.0.0-beta.11",
"1.0.0.0-beta.2")),
?assertMatch(true, gt("1.0.0-rc.1", "1.0.0-beta.11")), ?assertMatch(true, gt("1.0.0-rc.1", "1.0.0-beta.11")),
?assertMatch(true, gt("1.0.0-rc.1+build.1", "1.0.0-rc.1")), ?assertMatch(true, gt("1.0.0-rc.1+build.1", "1.0.0-rc.1")),
?assertMatch(true, gt("1.0.0", "1.0.0-rc.1+build.1")), ?assertMatch(true, gt("1.0.0", "1.0.0-rc.1+build.1")),
@ -286,10 +320,14 @@ gt_test() ->
?assertMatch(true, gt("1.3.7+build", "1.0.0+0.3.7")), ?assertMatch(true, gt("1.3.7+build", "1.0.0+0.3.7")),
?assertMatch(true, gt("1.3.7+build.2.b8f12d7", ?assertMatch(true, gt("1.3.7+build.2.b8f12d7",
"1.3.7+build")), "1.3.7+build")),
?assertMatch(true, gt("1.3.7+build.2.b8f12d7",
"1.3.7.0+build")),
?assertMatch(true, gt("1.3.7+build.11.e0f985a", ?assertMatch(true, gt("1.3.7+build.11.e0f985a",
"1.3.7+build.2.b8f12d7")), "1.3.7+build.2.b8f12d7")),
?assertMatch(true, not gt("1.0.0-alpha", ?assertMatch(true, not gt("1.0.0-alpha",
"1.0.0-alpha.1")), "1.0.0-alpha.1")),
?assertMatch(true, not gt("1.0.0-alpha",
"1.0.0.0-alpha.1")),
?assertMatch(true, not gt("1.0.0-alpha.1", ?assertMatch(true, not gt("1.0.0-alpha.1",
"1.0.0-beta.2")), "1.0.0-beta.2")),
?assertMatch(true, not gt("1.0.0-beta.2", ?assertMatch(true, not gt("1.0.0-beta.2",
@ -324,12 +362,16 @@ gt_test() ->
lt_test() -> lt_test() ->
?assertMatch(true, lt("1.0.0-alpha", ?assertMatch(true, lt("1.0.0-alpha",
"1.0.0-alpha.1")), "1.0.0-alpha.1")),
?assertMatch(true, lt("1.0.0-alpha",
"1.0.0.0-alpha.1")),
?assertMatch(true, lt("1.0.0-alpha.1", ?assertMatch(true, lt("1.0.0-alpha.1",
"1.0.0-beta.2")), "1.0.0-beta.2")),
?assertMatch(true, lt("1.0.0-beta.2", ?assertMatch(true, lt("1.0.0-beta.2",
"1.0.0-beta.11")), "1.0.0-beta.11")),
?assertMatch(true, lt("1.0.0-beta.11", ?assertMatch(true, lt("1.0.0-beta.11",
"1.0.0-rc.1")), "1.0.0-rc.1")),
?assertMatch(true, lt("1.0.0.1-beta.11",
"1.0.0.1-rc.1")),
?assertMatch(true, lt("1.0.0-rc.1", ?assertMatch(true, lt("1.0.0-rc.1",
"1.0.0-rc.1+build.1")), "1.0.0-rc.1+build.1")),
?assertMatch(true, lt("1.0.0-rc.1+build.1", ?assertMatch(true, lt("1.0.0-rc.1+build.1",
@ -346,9 +388,11 @@ lt_test() ->
"1.0.0-alpha")), "1.0.0-alpha")),
?assertMatch(true, not lt("1", ?assertMatch(true, not lt("1",
"1.0.0")), "1.0.0")),
?assertMatch(true, lt("1",
"1.0.0.1")),
?assertMatch(true, not lt("1.0", ?assertMatch(true, not lt("1.0",
"1.0.0")), "1.0.0")),
?assertMatch(true, not lt("1.0.0", ?assertMatch(true, not lt("1.0.0.0",
"1")), "1")),
?assertMatch(true, not lt("1.0+alpha.1", ?assertMatch(true, not lt("1.0+alpha.1",
"1.0.0+alpha.1")), "1.0.0+alpha.1")),
@ -384,12 +428,18 @@ gte_test() ->
?assertMatch(true, gte("1.0.0", ?assertMatch(true, gte("1.0.0",
"1")), "1")),
?assertMatch(true, gte("1.0.0.0",
"1")),
?assertMatch(true, gte("1.0+alpha.1", ?assertMatch(true, gte("1.0+alpha.1",
"1.0.0+alpha.1")), "1.0.0+alpha.1")),
?assertMatch(true, gte("1.0-alpha.1+build.1", ?assertMatch(true, gte("1.0-alpha.1+build.1",
"1.0.0-alpha.1+build.1")), "1.0.0-alpha.1+build.1")),
?assertMatch(true, gte("1.0.0-alpha.1+build.1",
"1.0.0.0-alpha.1+build.1")),
?assertMatch(true, gte("1.0.0-alpha.1", ?assertMatch(true, gte("1.0.0-alpha.1",
"1.0.0-alpha")), "1.0.0-alpha")),
?assertMatch(true, gte("1.0.0-beta.2", ?assertMatch(true, gte("1.0.0-beta.2",
@ -458,6 +508,8 @@ lte_test() ->
"1")), "1")),
?assertMatch(true, lte("1.0+alpha.1", ?assertMatch(true, lte("1.0+alpha.1",
"1.0.0+alpha.1")), "1.0.0+alpha.1")),
?assertMatch(true, lte("1.0.0.0+alpha.1",
"1.0.0+alpha.1")),
?assertMatch(true, lte("1.0-alpha.1+build.1", ?assertMatch(true, lte("1.0-alpha.1+build.1",
"1.0.0-alpha.1+build.1")), "1.0.0-alpha.1+build.1")),
?assertMatch(true, not lt("1.0.0-alpha.1", ?assertMatch(true, not lt("1.0.0-alpha.1",
@ -476,7 +528,6 @@ lte_test() ->
?assertMatch(true, not lt("1.3.7+build.11.e0f985a", ?assertMatch(true, not lt("1.3.7+build.11.e0f985a",
"1.3.7+build.2.b8f12d7")). "1.3.7+build.2.b8f12d7")).
between_test() -> between_test() ->
?assertMatch(true, between("1.0.0-alpha", ?assertMatch(true, between("1.0.0-alpha",
"1.0.0-alpha.3", "1.0.0-alpha.3",
@ -493,6 +544,10 @@ between_test() ->
?assertMatch(true, between("1.0.0-rc.1", ?assertMatch(true, between("1.0.0-rc.1",
"1.0.0-rc.1+build.3", "1.0.0-rc.1+build.3",
"1.0.0-rc.1+build.1")), "1.0.0-rc.1+build.1")),
?assertMatch(true, between("1.0.0.0-rc.1",
"1.0.0-rc.1+build.3",
"1.0.0-rc.1+build.1")),
?assertMatch(true, between("1.0.0-rc.1+build.1", ?assertMatch(true, between("1.0.0-rc.1+build.1",
"1.0.0", "1.0.0",
"1.0.0-rc.33")), "1.0.0-rc.33")),
@ -517,6 +572,10 @@ between_test() ->
?assertMatch(true, between("1.0", ?assertMatch(true, between("1.0",
"1.0.0", "1.0.0",
"1.0.0")), "1.0.0")),
?assertMatch(true, between("1.0",
"1.0.0.0",
"1.0.0.0")),
?assertMatch(true, between("1.0.0", ?assertMatch(true, between("1.0.0",
"1", "1",
"1")), "1")),
@ -549,7 +608,9 @@ pes_test() ->
?assertMatch(true, pes("2.6.7", "2.6.5")), ?assertMatch(true, pes("2.6.7", "2.6.5")),
?assertMatch(true, pes("2.6.8", "2.6.5")), ?assertMatch(true, pes("2.6.8", "2.6.5")),
?assertMatch(true, pes("2.6.9", "2.6.5")), ?assertMatch(true, pes("2.6.9", "2.6.5")),
?assertMatch(true, pes("2.6.0.9", "2.6.0.5")),
?assertMatch(true, not pes("2.7", "2.6.5")), ?assertMatch(true, not pes("2.7", "2.6.5")),
?assertMatch(true, not pes("2.1.7", "2.1.6.5")),
?assertMatch(true, not pes("2.5", "2.6.5")). ?assertMatch(true, not pes("2.5", "2.6.5")).
version_format_test() -> version_format_test() ->
@ -563,6 +624,8 @@ version_format_test() ->
?assertEqual(<<"1.99.2-alpha.1">>, erlang:iolist_to_binary(format({{1,99,2}, {[<<"alpha">>,1], []}}))), ?assertEqual(<<"1.99.2-alpha.1">>, erlang:iolist_to_binary(format({{1,99,2}, {[<<"alpha">>,1], []}}))),
?assertEqual(<<"1.99.2+build.1.a36">>, ?assertEqual(<<"1.99.2+build.1.a36">>,
erlang:iolist_to_binary(format({{1,99,2}, {[], [<<"build">>, 1, <<"a36">>]}}))), erlang:iolist_to_binary(format({{1,99,2}, {[], [<<"build">>, 1, <<"a36">>]}}))),
?assertEqual(<<"1.99.2.44+build.1.a36">>,
erlang:iolist_to_binary(format({{1,99,2,44}, {[], [<<"build">>, 1, <<"a36">>]}}))),
?assertEqual(<<"1.99.2-alpha.1+build.1.a36">>, ?assertEqual(<<"1.99.2-alpha.1+build.1.a36">>,
erlang:iolist_to_binary(format({{1,99,2}, {[<<"alpha">>, 1], [<<"build">>, 1, <<"a36">>]}}))), erlang:iolist_to_binary(format({{1,99,2}, {[<<"alpha">>, 1], [<<"build">>, 1, <<"a36">>]}}))),
?assertEqual(<<"1">>, erlang:iolist_to_binary(format({1, {[],[]}}))). ?assertEqual(<<"1">>, erlang:iolist_to_binary(format({1, {[],[]}}))).

View file

@ -1,7 +1,7 @@
semver <- major_minor_patch ("-" alpha_part ("." alpha_part)*)? ("+" alpha_part ("." alpha_part)*)? !. semver <- major_minor_patch_min_patch ("-" alpha_part ("." alpha_part)*)? ("+" alpha_part ("." alpha_part)*)? !.
` ec_semver:internal_parse_version(Node) ` ; ` ec_semver:internal_parse_version(Node) ` ;
major_minor_patch <- version_part ("." version_part)? ("." version_part)? ; major_minor_patch_min_patch <- version_part ("." version_part)? ("." version_part)? ("." version_part)? ;
version_part <- [0-9]+ `erlang:list_to_integer(erlang:binary_to_list(erlang:iolist_to_binary(Node)))` ; version_part <- [0-9]+ `erlang:list_to_integer(erlang:binary_to_list(erlang:iolist_to_binary(Node)))` ;