suport proper semver parsing and comparison in the semver module
Signed-off-by: Jordan Wilberding <diginux@gmail.com>
This commit is contained in:
parent
9b9f070a5f
commit
bf37ad9492
5 changed files with 504 additions and 98 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -10,3 +10,4 @@ ebin/*
|
||||||
_build
|
_build
|
||||||
erl_crash.dump
|
erl_crash.dump
|
||||||
*.pyc
|
*.pyc
|
||||||
|
src/ec_semver_parser.erl
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
%% -*- mode: Erlang; fill-column: 80; comment-column: 75; -*-
|
%% -*- mode: Erlang; fill-column: 80; comment-column: 75; -*-
|
||||||
|
|
||||||
%% These are all only compile time dependencies
|
%% These are all only compile time dependencies
|
||||||
{deps, [
|
{deps, [{neotoma, "",
|
||||||
{proper, "", {git, "https://github.com/manopapad/proper.git", {branch, master}}}
|
{git, "https://github.com/seancribbs/neotoma.git", {tag, "1.5"}}},
|
||||||
]}.
|
{proper, "", {git, "https://github.com/manopapad/proper.git", {branch, master}}}]}.
|
||||||
|
|
||||||
{erl_first_files, ["ec_dictionary"]}.
|
{erl_first_files, ["ec_dictionary"]}.
|
||||||
|
|
||||||
|
|
|
@ -7,95 +7,213 @@
|
||||||
%%%-------------------------------------------------------------------
|
%%%-------------------------------------------------------------------
|
||||||
-module(ec_semver).
|
-module(ec_semver).
|
||||||
|
|
||||||
-exports([
|
-export([parse/1,
|
||||||
compare/2
|
eql/2,
|
||||||
]).
|
gt/2,
|
||||||
|
gte/2,
|
||||||
|
lt/2,
|
||||||
|
lte/2,
|
||||||
|
pes/2,
|
||||||
|
between/3]).
|
||||||
|
|
||||||
-export_type([
|
%% For internal use by the ec_semver_parser peg
|
||||||
semvar/0
|
-export([internal_parse_version/1]).
|
||||||
]).
|
|
||||||
|
-export_type([semver/0,
|
||||||
|
version_string/0,
|
||||||
|
any_version/0]).
|
||||||
|
|
||||||
%%%===================================================================
|
%%%===================================================================
|
||||||
%%% Public Types
|
%%% Public Types
|
||||||
%%%===================================================================
|
%%%===================================================================
|
||||||
|
|
||||||
-type semvar() :: string().
|
-type major_minor_patch() ::
|
||||||
-type parsed_semvar() :: {MajorVsn::integer(),
|
non_neg_integer()
|
||||||
MinorVsn::integer(),
|
| {non_neg_integer(), non_neg_integer()}
|
||||||
PatchVsn::integer(),
|
| {non_neg_integer(), non_neg_integer(), non_neg_integer()}.
|
||||||
PathString::string()}.
|
|
||||||
|
|
||||||
-type semver_tokens() :: {string(), string(), string(), string()}.
|
-type alpha_part() :: integer() | binary().
|
||||||
|
|
||||||
|
-type semver() :: {major_minor_patch(), {PreReleaseVersion::[alpha_part()],
|
||||||
|
BuildVersion::[alpha_part()]}}.
|
||||||
|
|
||||||
|
-type version_string() :: string() | binary().
|
||||||
|
|
||||||
|
-type any_version() :: version_string() | semver().
|
||||||
|
|
||||||
%%%===================================================================
|
%%%===================================================================
|
||||||
%%% API
|
%%% API
|
||||||
%%%===================================================================
|
%%%===================================================================
|
||||||
|
|
||||||
%% @doc Is semver version string A bigger than version string B?
|
%% @doc parse a string or binary into a valid semver representation
|
||||||
%% <pre>
|
-spec parse(any_version()) -> semver().
|
||||||
%% Example: compare("3.2.5alpha", "3.10.6") returns: false
|
parse(Version) when erlang:is_list(Version) ->
|
||||||
%% </pre>
|
ec_semver_parser:parse(Version);
|
||||||
-spec compare(VsnA::string(), VsnB::string()) -> boolean().
|
parse(Version) when erlang:is_binary(Version) ->
|
||||||
compare(VsnA, VsnB) ->
|
ec_semver_parser:parse(Version);
|
||||||
compare_toks(tokens(VsnA),tokens(VsnB)).
|
parse(Version) ->
|
||||||
|
Version.
|
||||||
|
|
||||||
|
%% @doc test for quality between semver versions
|
||||||
|
-spec eql(any_version(), any_version()) -> boolean().
|
||||||
|
eql(VsnA, VsnB) ->
|
||||||
|
NVsnA = normalize(parse(VsnA)),
|
||||||
|
NVsnB = normalize(parse(VsnB)),
|
||||||
|
NVsnA =:= NVsnB.
|
||||||
|
|
||||||
|
%% @doc Test that VsnA is greater than VsnB
|
||||||
|
-spec gt(any_version(), any_version()) -> boolean().
|
||||||
|
gt(VsnA, VsnB) ->
|
||||||
|
{MMPA, {AlphaA, PatchA}} = normalize(parse(VsnA)),
|
||||||
|
{MMPB, {AlphaB, PatchB}} = normalize(parse(VsnB)),
|
||||||
|
((MMPA > MMPB)
|
||||||
|
orelse
|
||||||
|
((MMPA =:= MMPB)
|
||||||
|
andalso
|
||||||
|
((AlphaA =:= [] andalso AlphaB =/= [])
|
||||||
|
orelse
|
||||||
|
((not (AlphaB =:= [] andalso AlphaA =/= []))
|
||||||
|
andalso
|
||||||
|
(AlphaA > AlphaB))))
|
||||||
|
orelse
|
||||||
|
((MMPA =:= MMPB)
|
||||||
|
andalso
|
||||||
|
(AlphaA =:= AlphaB)
|
||||||
|
andalso
|
||||||
|
((PatchB =:= [] andalso PatchA =/= [])
|
||||||
|
orelse
|
||||||
|
PatchA > PatchB))).
|
||||||
|
|
||||||
|
%% @doc Test that VsnA is greater than or equal to VsnB
|
||||||
|
-spec gte(any_version(), any_version()) -> boolean().
|
||||||
|
gte(VsnA, VsnB) ->
|
||||||
|
NVsnA = normalize(parse(VsnA)),
|
||||||
|
NVsnB = normalize(parse(VsnB)),
|
||||||
|
gt(NVsnA, NVsnB) orelse eql(NVsnA, NVsnB).
|
||||||
|
|
||||||
|
%% @doc Test that VsnA is less than VsnB
|
||||||
|
-spec lt(any_version(), any_version()) -> boolean().
|
||||||
|
lt(VsnA, VsnB) ->
|
||||||
|
{MMPA, {AlphaA, PatchA}} = normalize(parse(VsnA)),
|
||||||
|
{MMPB, {AlphaB, PatchB}} = normalize(parse(VsnB)),
|
||||||
|
((MMPA < MMPB)
|
||||||
|
orelse
|
||||||
|
((MMPA =:= MMPB)
|
||||||
|
andalso
|
||||||
|
((AlphaB =:= [] andalso AlphaA =/= [])
|
||||||
|
orelse
|
||||||
|
((not (AlphaA =:= [] andalso AlphaB =/= []))
|
||||||
|
andalso
|
||||||
|
(AlphaA < AlphaB))))
|
||||||
|
orelse
|
||||||
|
((MMPA =:= MMPB)
|
||||||
|
andalso
|
||||||
|
(AlphaA =:= AlphaB)
|
||||||
|
andalso
|
||||||
|
((PatchA =:= [] andalso PatchB =/= [])
|
||||||
|
orelse
|
||||||
|
PatchA < PatchB))).
|
||||||
|
|
||||||
|
%% @doc Test that VsnA is less than or equal to VsnB
|
||||||
|
-spec lte(any_version(), any_version()) -> boolean().
|
||||||
|
lte(VsnA, VsnB) ->
|
||||||
|
NVsnA = normalize(parse(VsnA)),
|
||||||
|
NVsnB = normalize(parse(VsnB)),
|
||||||
|
lt(NVsnA, NVsnB) orelse eql(NVsnA, NVsnB).
|
||||||
|
|
||||||
|
%% @doc Test that VsnMatch is greater than or equal to Vsn1 and
|
||||||
|
%% less than or equal to Vsn2
|
||||||
|
-spec between(any_version(), any_version(), any_version()) -> boolean().
|
||||||
|
between(Vsn1, Vsn2, VsnMatch) ->
|
||||||
|
NVsnA = normalize(parse(Vsn1)),
|
||||||
|
NVsnB = normalize(parse(Vsn2)),
|
||||||
|
NVsnMatch = normalize(parse(VsnMatch)),
|
||||||
|
gte(NVsnMatch, NVsnA) andalso
|
||||||
|
lte(NVsnMatch, NVsnB).
|
||||||
|
|
||||||
|
%% @doc check that VsnA is Approximately greater than VsnB
|
||||||
|
%%
|
||||||
|
%% Specifying ">= 2.6.5" is an optimistic version constraint. All
|
||||||
|
%% versions greater than the one specified, including major releases
|
||||||
|
%% (e.g. 3.0.0) are allowed.
|
||||||
|
%%
|
||||||
|
%% Conversely, specifying "~> 2.6" is pessimistic about future major
|
||||||
|
%% revisions and "~> 2.6.5" is pessimistic about future minor
|
||||||
|
%% revisions.
|
||||||
|
%%
|
||||||
|
%% "~> 2.6" matches cookbooks >= 2.6.0 AND < 3.0.0
|
||||||
|
%% "~> 2.6.5" matches cookbooks >= 2.6.5 AND < 2.7.0
|
||||||
|
pes(VsnA, VsnB) ->
|
||||||
|
internal_pes(parse(VsnA), parse(VsnB)).
|
||||||
|
|
||||||
|
%%%===================================================================
|
||||||
|
%%% Friend Functions
|
||||||
|
%%%===================================================================
|
||||||
|
%% @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)}}.
|
||||||
|
|
||||||
|
%% @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, [], []]) ->
|
||||||
|
MajVsn;
|
||||||
|
parse_major_minor_patch([MajVsn, [<<".">>, MinVsn], []]) ->
|
||||||
|
{MajVsn, MinVsn};
|
||||||
|
parse_major_minor_patch([MajVsn, [<<".">>, MinVsn], [<<".">>, PatchVsn]]) ->
|
||||||
|
{MajVsn, MinVsn, PatchVsn}.
|
||||||
|
|
||||||
|
%% @doc helper function for the peg grammer to parse the iolist into an alpha part
|
||||||
|
-spec parse_alpha_part(iolist()) -> [alpha_part()].
|
||||||
|
parse_alpha_part([]) ->
|
||||||
|
[];
|
||||||
|
parse_alpha_part([_, AV1, Rest]) ->
|
||||||
|
[erlang:iolist_to_binary(AV1) |
|
||||||
|
[format_alpha_part(Part) || Part <- Rest]].
|
||||||
|
|
||||||
|
%% @doc according to semver alpha parts that can be treated like
|
||||||
|
%% numbers must be. We implement that here by taking the alpha part
|
||||||
|
%% and trying to convert it to a number, if it succeeds we use
|
||||||
|
%% it. Otherwise we do not.
|
||||||
|
-spec format_alpha_part(iolist()) -> integer() | binary().
|
||||||
|
format_alpha_part([<<".">>, AlphaPart]) ->
|
||||||
|
Bin = erlang:iolist_to_binary(AlphaPart),
|
||||||
|
try
|
||||||
|
erlang:list_to_integer(erlang:binary_to_list(Bin))
|
||||||
|
catch
|
||||||
|
error:badarg ->
|
||||||
|
Bin
|
||||||
|
end.
|
||||||
|
|
||||||
%%%===================================================================
|
%%%===================================================================
|
||||||
%%% Internal Functions
|
%%% Internal Functions
|
||||||
%%%===================================================================
|
%%%===================================================================
|
||||||
|
%% @doc normalize the semver so they can be compared
|
||||||
|
-spec normalize(semver()) -> semver().
|
||||||
|
normalize({Vsn, Rest})
|
||||||
|
when erlang:is_integer(Vsn) ->
|
||||||
|
{{Vsn, 0, 0}, Rest};
|
||||||
|
normalize({{Maj, Min}, Rest}) ->
|
||||||
|
{{Maj, Min, 0}, Rest};
|
||||||
|
normalize(Other) ->
|
||||||
|
Other.
|
||||||
|
|
||||||
-spec tokens(semvar()) -> semver_tokens().
|
%% @doc to do the pessimistic compare we need a parsed semver. This is
|
||||||
tokens(Vsn) ->
|
%% the internal implementation of the of the pessimistic run. The
|
||||||
[MajorVsn, MinorVsn, RawPatch] = string:tokens(Vsn, "."),
|
%% external just ensures that versions are parsed.
|
||||||
{PatchVsn, PatchString} = split_patch(RawPatch),
|
internal_pes(VsnA, {{LM, LMI}, _}) ->
|
||||||
{MajorVsn, MinorVsn, PatchVsn, PatchString}.
|
gte(VsnA, {{LM, LMI, 0}, {[], []}}) andalso
|
||||||
|
lt(VsnA, {{LM + 1, 0, 0}, {[], []}});
|
||||||
|
internal_pes(VsnA, {{LM, LMI, LP}, _}) ->
|
||||||
|
gte(VsnA, {{LM, LMI, LP}, {[], []}})
|
||||||
|
andalso
|
||||||
|
lt(VsnA, {{LM, LMI + 1, 0}, {[], []}});
|
||||||
|
internal_pes(Vsn, LVsn) ->
|
||||||
|
gte(Vsn, LVsn).
|
||||||
|
|
||||||
-spec split_patch(string()) ->
|
|
||||||
{PatchVsn::string(), PatchStr::string()}.
|
|
||||||
split_patch(RawPatch) ->
|
|
||||||
{PatchVsn, PatchStr} = split_patch(RawPatch, {"", ""}),
|
|
||||||
{lists:reverse(PatchVsn), PatchStr}.
|
|
||||||
|
|
||||||
-spec split_patch(string(), {AccPatchVsn::string(), AccPatchStr::string()}) ->
|
|
||||||
{PatchVsn::string(), PatchStr::string()}.
|
|
||||||
split_patch([], Acc) ->
|
|
||||||
Acc;
|
|
||||||
split_patch([Dig|T], {PatchVsn, PatchStr}) when Dig >= $0 andalso Dig =< $9 ->
|
|
||||||
split_patch(T, {[Dig|PatchVsn], PatchStr});
|
|
||||||
split_patch(PatchStr, {PatchVsn, ""}) ->
|
|
||||||
{PatchVsn, PatchStr}.
|
|
||||||
|
|
||||||
-spec compare_toks(semver_tokens(), semver_tokens()) -> boolean().
|
|
||||||
compare_toks({MajA, MinA, PVA, PSA}, {MajB, MinB, PVB, PSB}) ->
|
|
||||||
compare_toks2({to_int(MajA), to_int(MinA), to_int(PVA), PSA},
|
|
||||||
{to_int(MajB), to_int(MinB), to_int(PVB), PSB}).
|
|
||||||
|
|
||||||
-spec compare_toks2(parsed_semvar(), parsed_semvar()) -> boolean().
|
|
||||||
compare_toks2({MajA, _MinA, _PVA, _PSA}, {MajB, _MinB, _PVB, _PSB})
|
|
||||||
when MajA > MajB ->
|
|
||||||
true;
|
|
||||||
compare_toks2({_Maj, MinA, _PVA, _PSA}, {_Maj, MinB, _PVB, _PSB})
|
|
||||||
when MinA > MinB ->
|
|
||||||
true;
|
|
||||||
compare_toks2({_Maj, _Min, PVA, _PSA}, {_Maj, _Min, PVB, _PSB})
|
|
||||||
when PVA > PVB ->
|
|
||||||
true;
|
|
||||||
compare_toks2({_Maj, _Min, _PV, ""}, {_Maj, _Min, _PV, PSB}) when PSB /= ""->
|
|
||||||
true;
|
|
||||||
compare_toks2({_Maj, _Min, _PV, PSA}, {_Maj, _Min, _PV, ""}) when PSA /= ""->
|
|
||||||
false;
|
|
||||||
compare_toks2({_Maj, _Min, _PV, PSA}, {_Maj, _Min, _PV, PSB}) when PSA > PSB ->
|
|
||||||
true;
|
|
||||||
compare_toks2(_ToksA, _ToksB) ->
|
|
||||||
false.
|
|
||||||
|
|
||||||
-spec to_int(string()) -> integer().
|
|
||||||
to_int(String) ->
|
|
||||||
try
|
|
||||||
list_to_integer(String)
|
|
||||||
catch
|
|
||||||
error:badarg ->
|
|
||||||
throw(invalid_semver_string)
|
|
||||||
end.
|
|
||||||
|
|
||||||
%%%===================================================================
|
%%%===================================================================
|
||||||
%%% Test Functions
|
%%% Test Functions
|
||||||
|
@ -104,18 +222,305 @@ to_int(String) ->
|
||||||
-ifndef(NOTEST).
|
-ifndef(NOTEST).
|
||||||
-include_lib("eunit/include/eunit.hrl").
|
-include_lib("eunit/include/eunit.hrl").
|
||||||
|
|
||||||
split_patch_test() ->
|
eql_test() ->
|
||||||
?assertMatch({"123", "alpha1"}, split_patch("123alpha1")).
|
?assertMatch(true, eql("1.0.0-alpha",
|
||||||
|
"1.0.0-alpha")),
|
||||||
|
?assertMatch(true, eql("1",
|
||||||
|
"1.0.0")),
|
||||||
|
?assertMatch(true, eql("1.0",
|
||||||
|
"1.0.0")),
|
||||||
|
?assertMatch(true, eql("1.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, 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")).
|
||||||
|
|
||||||
compare_test() ->
|
|
||||||
?assertMatch(true, compare("1.2.3", "1.2.3alpha")),
|
gt_test() ->
|
||||||
?assertMatch(true, compare("1.2.3beta", "1.2.3alpha")),
|
?assertMatch(true, gt("1.0.0-alpha.1",
|
||||||
?assertMatch(true, compare("1.2.4", "1.2.3")),
|
"1.0.0-alpha")),
|
||||||
?assertMatch(true, compare("1.3.3", "1.2.3")),
|
?assertMatch(true, gt("1.0.0-beta.2",
|
||||||
?assertMatch(true, compare("2.2.3", "1.2.3")),
|
"1.0.0-alpha.1")),
|
||||||
?assertMatch(true, compare("4.2.3", "3.10.3")),
|
?assertMatch(true, gt("1.0.0-beta.11",
|
||||||
?assertMatch(false, compare("1.2.3", "2.2.3")),
|
"1.0.0-beta.2")),
|
||||||
?assertThrow(invalid_semver_string, compare("1.b.2", "1.3.4")),
|
?assertMatch(true, gt("1.0.0-rc.1", "1.0.0-beta.11")),
|
||||||
?assertThrow(invalid_semver_string, compare("1.2.2", "1.3.t")).
|
?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+0.3.7", "1.0.0")),
|
||||||
|
?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.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",
|
||||||
|
"1.0.0-beta.2")),
|
||||||
|
?assertMatch(true, not gt("1.0.0-beta.2",
|
||||||
|
"1.0.0-beta.11")),
|
||||||
|
?assertMatch(true, not gt("1.0.0-beta.11",
|
||||||
|
"1.0.0-rc.1")),
|
||||||
|
?assertMatch(true, not gt("1.0.0-rc.1",
|
||||||
|
"1.0.0-rc.1+build.1")),
|
||||||
|
?assertMatch(true, not gt("1.0.0-rc.1+build.1",
|
||||||
|
"1.0.0")),
|
||||||
|
?assertMatch(true, not gt("1.0.0",
|
||||||
|
"1.0.0+0.3.7")),
|
||||||
|
?assertMatch(true, not gt("1.0.0+0.3.7",
|
||||||
|
"1.3.7+build")),
|
||||||
|
?assertMatch(true, not gt("1.3.7+build",
|
||||||
|
"1.3.7+build.2.b8f12d7")),
|
||||||
|
?assertMatch(true, not gt("1.3.7+build.2.b8f12d7",
|
||||||
|
"1.3.7+build.11.e0f985a")),
|
||||||
|
?assertMatch(true, not gt("1.0.0-alpha",
|
||||||
|
"1.0.0-alpha")),
|
||||||
|
?assertMatch(true, not gt("1",
|
||||||
|
"1.0.0")),
|
||||||
|
?assertMatch(true, not gt("1.0",
|
||||||
|
"1.0.0")),
|
||||||
|
?assertMatch(true, not gt("1.0.0",
|
||||||
|
"1")),
|
||||||
|
?assertMatch(true, not gt("1.0+alpha.1",
|
||||||
|
"1.0.0+alpha.1")),
|
||||||
|
?assertMatch(true, not gt("1.0-alpha.1+build.1",
|
||||||
|
"1.0.0-alpha.1+build.1")).
|
||||||
|
|
||||||
|
lt_test() ->
|
||||||
|
?assertMatch(true, lt("1.0.0-alpha",
|
||||||
|
"1.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-rc.1",
|
||||||
|
"1.0.0-rc.1+build.1")),
|
||||||
|
?assertMatch(true, lt("1.0.0-rc.1+build.1",
|
||||||
|
"1.0.0")),
|
||||||
|
?assertMatch(true, lt("1.0.0",
|
||||||
|
"1.0.0+0.3.7")),
|
||||||
|
?assertMatch(true, lt("1.0.0+0.3.7",
|
||||||
|
"1.3.7+build")),
|
||||||
|
?assertMatch(true, lt("1.3.7+build",
|
||||||
|
"1.3.7+build.2.b8f12d7")),
|
||||||
|
?assertMatch(true, lt("1.3.7+build.2.b8f12d7",
|
||||||
|
"1.3.7+build.11.e0f985a")),
|
||||||
|
?assertMatch(true, not lt("1.0.0-alpha",
|
||||||
|
"1.0.0-alpha")),
|
||||||
|
?assertMatch(true, not lt("1",
|
||||||
|
"1.0.0")),
|
||||||
|
?assertMatch(true, not lt("1.0",
|
||||||
|
"1.0.0")),
|
||||||
|
?assertMatch(true, not lt("1.0.0",
|
||||||
|
"1")),
|
||||||
|
?assertMatch(true, not lt("1.0+alpha.1",
|
||||||
|
"1.0.0+alpha.1")),
|
||||||
|
?assertMatch(true, not lt("1.0-alpha.1+build.1",
|
||||||
|
"1.0.0-alpha.1+build.1")),
|
||||||
|
?assertMatch(true, not lt("1.0.0-alpha.1",
|
||||||
|
"1.0.0-alpha")),
|
||||||
|
?assertMatch(true, not lt("1.0.0-beta.2",
|
||||||
|
"1.0.0-alpha.1")),
|
||||||
|
?assertMatch(true, not lt("1.0.0-beta.11",
|
||||||
|
"1.0.0-beta.2")),
|
||||||
|
?assertMatch(true, not lt("1.0.0-rc.1", "1.0.0-beta.11")),
|
||||||
|
?assertMatch(true, not lt("1.0.0-rc.1+build.1", "1.0.0-rc.1")),
|
||||||
|
?assertMatch(true, not lt("1.0.0", "1.0.0-rc.1+build.1")),
|
||||||
|
?assertMatch(true, not lt("1.0.0+0.3.7", "1.0.0")),
|
||||||
|
?assertMatch(true, not lt("1.3.7+build", "1.0.0+0.3.7")),
|
||||||
|
?assertMatch(true, not lt("1.3.7+build.2.b8f12d7",
|
||||||
|
"1.3.7+build")),
|
||||||
|
?assertMatch(true, not lt("1.3.7+build.11.e0f985a",
|
||||||
|
"1.3.7+build.2.b8f12d7")).
|
||||||
|
|
||||||
|
|
||||||
|
gte_test() ->
|
||||||
|
?assertMatch(true, gte("1.0.0-alpha",
|
||||||
|
"1.0.0-alpha")),
|
||||||
|
|
||||||
|
?assertMatch(true, gte("1",
|
||||||
|
"1.0.0")),
|
||||||
|
|
||||||
|
?assertMatch(true, gte("1.0",
|
||||||
|
"1.0.0")),
|
||||||
|
|
||||||
|
?assertMatch(true, gte("1.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",
|
||||||
|
"1.0.0-alpha")),
|
||||||
|
?assertMatch(true, gte("1.0.0-beta.2",
|
||||||
|
"1.0.0-alpha.1")),
|
||||||
|
?assertMatch(true, gte("1.0.0-beta.11",
|
||||||
|
"1.0.0-beta.2")),
|
||||||
|
?assertMatch(true, gte("1.0.0-rc.1", "1.0.0-beta.11")),
|
||||||
|
?assertMatch(true, gte("1.0.0-rc.1+build.1", "1.0.0-rc.1")),
|
||||||
|
?assertMatch(true, gte("1.0.0", "1.0.0-rc.1+build.1")),
|
||||||
|
?assertMatch(true, gte("1.0.0+0.3.7", "1.0.0")),
|
||||||
|
?assertMatch(true, gte("1.3.7+build", "1.0.0+0.3.7")),
|
||||||
|
?assertMatch(true, gte("1.3.7+build.2.b8f12d7",
|
||||||
|
"1.3.7+build")),
|
||||||
|
?assertMatch(true, gte("1.3.7+build.11.e0f985a",
|
||||||
|
"1.3.7+build.2.b8f12d7")),
|
||||||
|
?assertMatch(true, not gte("1.0.0-alpha",
|
||||||
|
"1.0.0-alpha.1")),
|
||||||
|
?assertMatch(true, not gte("1.0.0-alpha.1",
|
||||||
|
"1.0.0-beta.2")),
|
||||||
|
?assertMatch(true, not gte("1.0.0-beta.2",
|
||||||
|
"1.0.0-beta.11")),
|
||||||
|
?assertMatch(true, not gte("1.0.0-beta.11",
|
||||||
|
"1.0.0-rc.1")),
|
||||||
|
?assertMatch(true, not gte("1.0.0-rc.1",
|
||||||
|
"1.0.0-rc.1+build.1")),
|
||||||
|
?assertMatch(true, not gte("1.0.0-rc.1+build.1",
|
||||||
|
"1.0.0")),
|
||||||
|
?assertMatch(true, not gte("1.0.0",
|
||||||
|
"1.0.0+0.3.7")),
|
||||||
|
?assertMatch(true, not gte("1.0.0+0.3.7",
|
||||||
|
"1.3.7+build")),
|
||||||
|
?assertMatch(true, not gte("1.0.0",
|
||||||
|
"1.0.0+build.1")),
|
||||||
|
?assertMatch(true, not gte("1.3.7+build",
|
||||||
|
"1.3.7+build.2.b8f12d7")),
|
||||||
|
?assertMatch(true, not gte("1.3.7+build.2.b8f12d7",
|
||||||
|
"1.3.7+build.11.e0f985a")).
|
||||||
|
lte_test() ->
|
||||||
|
?assertMatch(true, lte("1.0.0-alpha",
|
||||||
|
"1.0.0-alpha.1")),
|
||||||
|
?assertMatch(true, lte("1.0.0-alpha.1",
|
||||||
|
"1.0.0-beta.2")),
|
||||||
|
?assertMatch(true, lte("1.0.0-beta.2",
|
||||||
|
"1.0.0-beta.11")),
|
||||||
|
?assertMatch(true, lte("1.0.0-beta.11",
|
||||||
|
"1.0.0-rc.1")),
|
||||||
|
?assertMatch(true, lte("1.0.0-rc.1",
|
||||||
|
"1.0.0-rc.1+build.1")),
|
||||||
|
?assertMatch(true, lte("1.0.0-rc.1+build.1",
|
||||||
|
"1.0.0")),
|
||||||
|
?assertMatch(true, lte("1.0.0",
|
||||||
|
"1.0.0+0.3.7")),
|
||||||
|
?assertMatch(true, lte("1.0.0+0.3.7",
|
||||||
|
"1.3.7+build")),
|
||||||
|
?assertMatch(true, lte("1.3.7+build",
|
||||||
|
"1.3.7+build.2.b8f12d7")),
|
||||||
|
?assertMatch(true, lte("1.3.7+build.2.b8f12d7",
|
||||||
|
"1.3.7+build.11.e0f985a")),
|
||||||
|
?assertMatch(true, lte("1.0.0-alpha",
|
||||||
|
"1.0.0-alpha")),
|
||||||
|
?assertMatch(true, lte("1",
|
||||||
|
"1.0.0")),
|
||||||
|
?assertMatch(true, lte("1.0",
|
||||||
|
"1.0.0")),
|
||||||
|
?assertMatch(true, lte("1.0.0",
|
||||||
|
"1")),
|
||||||
|
?assertMatch(true, lte("1.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",
|
||||||
|
"1.0.0-alpha")),
|
||||||
|
?assertMatch(true, not lt("1.0.0-beta.2",
|
||||||
|
"1.0.0-alpha.1")),
|
||||||
|
?assertMatch(true, not lt("1.0.0-beta.11",
|
||||||
|
"1.0.0-beta.2")),
|
||||||
|
?assertMatch(true, not lt("1.0.0-rc.1", "1.0.0-beta.11")),
|
||||||
|
?assertMatch(true, not lt("1.0.0-rc.1+build.1", "1.0.0-rc.1")),
|
||||||
|
?assertMatch(true, not lt("1.0.0", "1.0.0-rc.1+build.1")),
|
||||||
|
?assertMatch(true, not lt("1.0.0+0.3.7", "1.0.0")),
|
||||||
|
?assertMatch(true, not lt("1.3.7+build", "1.0.0+0.3.7")),
|
||||||
|
?assertMatch(true, not lt("1.3.7+build.2.b8f12d7",
|
||||||
|
"1.3.7+build")),
|
||||||
|
?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",
|
||||||
|
"1.0.0-alpha.2")),
|
||||||
|
?assertMatch(true, between("1.0.0-alpha.1",
|
||||||
|
"1.0.0-beta.2",
|
||||||
|
"1.0.0-alpha.25")),
|
||||||
|
?assertMatch(true, between("1.0.0-beta.2",
|
||||||
|
"1.0.0-beta.11",
|
||||||
|
"1.0.0-beta.7")),
|
||||||
|
?assertMatch(true, between("1.0.0-beta.11",
|
||||||
|
"1.0.0-rc.3",
|
||||||
|
"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.1")),
|
||||||
|
?assertMatch(true, between("1.0.0-rc.1+build.1",
|
||||||
|
"1.0.0",
|
||||||
|
"1.0.0-rc.33")),
|
||||||
|
?assertMatch(true, between("1.0.0",
|
||||||
|
"1.0.0+0.3.7",
|
||||||
|
"1.0.0+0.2")),
|
||||||
|
?assertMatch(true, between("1.0.0+0.3.7",
|
||||||
|
"1.3.7+build",
|
||||||
|
"1.2")),
|
||||||
|
?assertMatch(true, between("1.3.7+build",
|
||||||
|
"1.3.7+build.2.b8f12d7",
|
||||||
|
"1.3.7+build.1")),
|
||||||
|
?assertMatch(true, between("1.3.7+build.2.b8f12d7",
|
||||||
|
"1.3.7+build.11.e0f985a",
|
||||||
|
"1.3.7+build.10.a36faa")),
|
||||||
|
?assertMatch(true, between("1.0.0-alpha",
|
||||||
|
"1.0.0-alpha",
|
||||||
|
"1.0.0-alpha")),
|
||||||
|
?assertMatch(true, between("1",
|
||||||
|
"1.0.0",
|
||||||
|
"1.0.0")),
|
||||||
|
?assertMatch(true, between("1.0",
|
||||||
|
"1.0.0",
|
||||||
|
"1.0.0")),
|
||||||
|
?assertMatch(true, between("1.0.0",
|
||||||
|
"1",
|
||||||
|
"1")),
|
||||||
|
?assertMatch(true, between("1.0+alpha.1",
|
||||||
|
"1.0.0+alpha.1",
|
||||||
|
"1.0.0+alpha.1")),
|
||||||
|
?assertMatch(true, between("1.0-alpha.1+build.1",
|
||||||
|
"1.0.0-alpha.1+build.1",
|
||||||
|
"1.0.0-alpha.1+build.1")),
|
||||||
|
?assertMatch(true, not between("1.0.0-alpha.1",
|
||||||
|
"1.0.0-alpha.22",
|
||||||
|
"1.0.0")),
|
||||||
|
?assertMatch(true, not between("1.0.0",
|
||||||
|
"1.0.0-alpha.1",
|
||||||
|
"2.0")),
|
||||||
|
?assertMatch(true, not between("1.0.0-beta.1",
|
||||||
|
"1.0.0-beta.11",
|
||||||
|
"1.0.0-alpha")),
|
||||||
|
?assertMatch(true, not between("1.0.0-beta.11", "1.0.0-rc.1", "1.0.0-rc.22")).
|
||||||
|
|
||||||
|
pes_test() ->
|
||||||
|
?assertMatch(true, pes("2.6.0", "2.6")),
|
||||||
|
?assertMatch(true, pes("2.7", "2.6")),
|
||||||
|
?assertMatch(true, pes("2.8", "2.6")),
|
||||||
|
?assertMatch(true, pes("2.9", "2.6")),
|
||||||
|
?assertMatch(true, not pes("3.0.0", "2.6")),
|
||||||
|
?assertMatch(true, not pes("2.5", "2.6")),
|
||||||
|
?assertMatch(true, pes("2.6.5", "2.6.5")),
|
||||||
|
?assertMatch(true, pes("2.6.6", "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.9", "2.6.5")),
|
||||||
|
?assertMatch(true, not pes("2.7", "2.6.5")),
|
||||||
|
?assertMatch(true, not pes("2.5", "2.6.5")).
|
||||||
|
|
||||||
-endif.
|
-endif.
|
||||||
|
|
13
src/ec_semver_parser.peg
Normal file
13
src/ec_semver_parser.peg
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
semver <- major_minor_patch ("-" alpha_part ("." alpha_part)*)? ("+" alpha_part ("." alpha_part)*)? !.
|
||||||
|
` ec_semver:internal_parse_version(Node) ` ;
|
||||||
|
|
||||||
|
major_minor_patch <- version_part ("." version_part)? ("." version_part)? ;
|
||||||
|
|
||||||
|
version_part <- [0-9]+ `erlang:list_to_integer(erlang:binary_to_list(erlang:iolist_to_binary(Node)))` ;
|
||||||
|
|
||||||
|
alpha_part <- [A-Za-z0-9-]+ ;
|
||||||
|
|
||||||
|
%% This only exists to get around a bug in erlang where if
|
||||||
|
%% warnings_as_errors is specified `nowarn` directives are ignored
|
||||||
|
|
||||||
|
`-compile(export_all).`
|
|
@ -2,19 +2,6 @@
|
||||||
{application, erlware_commons,
|
{application, erlware_commons,
|
||||||
[{description, "Additional standard library for Erlang"},
|
[{description, "Additional standard library for Erlang"},
|
||||||
{vsn, "0.7.0"},
|
{vsn, "0.7.0"},
|
||||||
{modules, [
|
{modules, []},
|
||||||
ec_talk,
|
|
||||||
ec_lists,
|
|
||||||
ec_plists,
|
|
||||||
ec_file,
|
|
||||||
ec_string,
|
|
||||||
ec_semver,
|
|
||||||
ec_date,
|
|
||||||
ec_dictionary,
|
|
||||||
ec_assoc_list,
|
|
||||||
ec_dict,
|
|
||||||
ec_gb_trees,
|
|
||||||
ec_rbdict,
|
|
||||||
ec_orddict]},
|
|
||||||
{registered, []},
|
{registered, []},
|
||||||
{applications, [kernel, stdlib]}]}.
|
{applications, [kernel, stdlib]}]}.
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue