From b4ab414419d544d9f8989e055bed8b8f6c6bc747 Mon Sep 17 00:00:00 2001 From: Eric Merritt Date: Mon, 17 Sep 2012 09:38:32 -0500 Subject: [PATCH] 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 --- src/ec_semver.erl | 99 ++++++++++++++++++++++++++++++++-------- src/ec_semver_parser.peg | 4 +- 2 files changed, 83 insertions(+), 20 deletions(-) diff --git a/src/ec_semver.erl b/src/ec_semver.erl index 0d8b40b..0566322 100644 --- a/src/ec_semver.erl +++ b/src/ec_semver.erl @@ -1,3 +1,4 @@ + %%%------------------------------------------------------------------- %%% @copyright (C) 2011, Erlware LLC %%% @doc @@ -28,16 +29,18 @@ %%% 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()}. -type alpha_part() :: integer() | binary() | string(). -type alpha_info() :: {PreRelease::[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(). @@ -72,6 +75,13 @@ format({{Maj, Min, Patch}, {AlphaPart, BuildPart}}) -> erlang:integer_to_list(Min), ".", erlang:integer_to_list(Patch), 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)]. %% @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 -spec internal_parse_version(iolist()) -> semver(). internal_parse_version([MMP, AlphaPart, BuildPart, _]) -> - {parse_major_minor_patch(MMP), {parse_alpha_part(AlphaPart), - parse_alpha_part(BuildPart)}}. + {parse_major_minor_patch_minpatch(MMP), {parse_alpha_part(AlphaPart), + parse_alpha_part(BuildPart)}}. %% @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(). -parse_major_minor_patch([MajVsn, [], []]) -> +-spec parse_major_minor_patch_minpatch(iolist()) -> major_minor_patch_minpatch(). +parse_major_minor_patch_minpatch([MajVsn, [], [], []]) -> MajVsn; -parse_major_minor_patch([MajVsn, [<<".">>, MinVsn], []]) -> +parse_major_minor_patch_minpatch([MajVsn, [<<".">>, MinVsn], [], []]) -> {MajVsn, MinVsn}; -parse_major_minor_patch([MajVsn, [<<".">>, MinVsn], [<<".">>, PatchVsn]]) -> - {MajVsn, MinVsn, PatchVsn}. +parse_major_minor_patch_minpatch([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 -spec parse_alpha_part(iolist()) -> [alpha_part()]. @@ -226,22 +239,29 @@ format_vsn_rest(TypeMark, [Head | Rest]) -> -spec normalize(semver()) -> semver(). normalize({Vsn, Rest}) when erlang:is_integer(Vsn) -> - {{Vsn, 0, 0}, Rest}; + {{Vsn, 0, 0, 0}, Rest}; normalize({{Maj, Min}, Rest}) -> - {{Maj, Min, 0}, Rest}; -normalize(Other) -> + {{Maj, Min, 0, 0}, Rest}; +normalize({{Maj, Min, Patch}, Rest}) -> + {{Maj, Min, Patch, 0}, Rest}; +normalize(Other = {{_, _, _, _}, {_,_}}) -> Other. %% @doc to do the pessimistic compare we need a parsed semver. This is %% the internal implementation of the of the pessimistic run. The %% external just ensures that versions are parsed. +-spec internal_pes(semver(), semver()) -> boolean(). internal_pes(VsnA, {{LM, LMI}, _}) -> 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}, _}) -> gte(VsnA, {{LM, LMI, LP}, {[], []}}) 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) -> gte(Vsn, LVsn). @@ -261,24 +281,38 @@ eql_test() -> "1.0.0")), ?assertMatch(true, eql("1.0.0", "1")), + ?assertMatch(true, eql("1.0.0.0", + "1")), ?assertMatch(true, eql("1.0+alpha.1", "1.0.0+alpha.1")), ?assertMatch(true, eql("1.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", "1.0.1")), ?assertMatch(true, not eql("1.0.0-alpha", "1.0.1+alpha")), ?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() -> ?assertMatch(true, gt("1.0.0-alpha.1", "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", "1.0.0-alpha.1")), ?assertMatch(true, gt("1.0.0-beta.11", "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+build.1", "1.0.0-rc.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.2.b8f12d7", "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", "1.3.7+build.2.b8f12d7")), ?assertMatch(true, not gt("1.0.0-alpha", "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", "1.0.0-beta.2")), ?assertMatch(true, not gt("1.0.0-beta.2", @@ -324,12 +362,16 @@ gt_test() -> lt_test() -> ?assertMatch(true, lt("1.0.0-alpha", "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", "1.0.0-beta.2")), ?assertMatch(true, lt("1.0.0-beta.2", "1.0.0-beta.11")), ?assertMatch(true, lt("1.0.0-beta.11", "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", "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")), ?assertMatch(true, not lt("1", "1.0.0")), + ?assertMatch(true, lt("1", + "1.0.0.1")), ?assertMatch(true, not lt("1.0", "1.0.0")), - ?assertMatch(true, not lt("1.0.0", + ?assertMatch(true, not lt("1.0.0.0", "1")), ?assertMatch(true, not lt("1.0+alpha.1", "1.0.0+alpha.1")), @@ -384,12 +428,18 @@ gte_test() -> ?assertMatch(true, gte("1.0.0", "1")), + ?assertMatch(true, gte("1.0.0.0", + "1")), + ?assertMatch(true, gte("1.0+alpha.1", "1.0.0+alpha.1")), ?assertMatch(true, gte("1.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", "1.0.0-alpha")), ?assertMatch(true, gte("1.0.0-beta.2", @@ -458,6 +508,8 @@ lte_test() -> "1")), ?assertMatch(true, lte("1.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", "1.0.0-alpha.1+build.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", "1.3.7+build.2.b8f12d7")). - between_test() -> ?assertMatch(true, between("1.0.0-alpha", "1.0.0-alpha.3", @@ -493,6 +544,10 @@ between_test() -> ?assertMatch(true, between("1.0.0-rc.1", "1.0.0-rc.1+build.3", "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", "1.0.0", "1.0.0-rc.33")), @@ -517,6 +572,10 @@ between_test() -> ?assertMatch(true, between("1.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", "1", "1")), @@ -549,7 +608,9 @@ pes_test() -> ?assertMatch(true, pes("2.6.7", "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.0.9", "2.6.0.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")). 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+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">>, erlang:iolist_to_binary(format({{1,99,2}, {[<<"alpha">>, 1], [<<"build">>, 1, <<"a36">>]}}))), ?assertEqual(<<"1">>, erlang:iolist_to_binary(format({1, {[],[]}}))). diff --git a/src/ec_semver_parser.peg b/src/ec_semver_parser.peg index 9636d95..f505693 100644 --- a/src/ec_semver_parser.peg +++ b/src/ec_semver_parser.peg @@ -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) ` ; -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)))` ;