1%%% vi:ts=4 sw=4 et
2%%%-------------------------------------------------------------------
3%%% @copyright (C) 2011, Erlware LLC
4%%% @doc
5%%%  Helper functions for working with semver versioning strings.
6%%%  See http://semver.org/ for the spec.
7%%% @end
8%%%-------------------------------------------------------------------
9-module(ec_semver).
10
11-export([parse/1,
12         format/1,
13         eql/2,
14         gt/2,
15         gte/2,
16         lt/2,
17         lte/2,
18         pes/2,
19         between/3]).
20
21%% For internal use by the ec_semver_parser peg
22-export([internal_parse_version/1]).
23
24-export_type([semver/0,
25              version_string/0,
26              any_version/0]).
27
28%%%===================================================================
29%%% Public Types
30%%%===================================================================
31
32-type version_element() :: non_neg_integer() | binary().
33
34-type major_minor_patch_minpatch() ::
35        version_element()
36      | {version_element(), version_element()}
37      | {version_element(), version_element(), version_element()}
38      | {version_element(), version_element(),
39         version_element(), version_element()}.
40
41-type alpha_part() :: integer() | binary() | string().
42-type alpha_info() :: {PreRelease::[alpha_part()],
43                       BuildVersion::[alpha_part()]}.
44
45-type semver() :: {major_minor_patch_minpatch(), alpha_info()}.
46
47-type version_string() :: string() | binary().
48
49-type any_version() :: version_string() | semver().
50
51%%%===================================================================
52%%% API
53%%%===================================================================
54
55%% @doc parse a string or binary into a valid semver representation
56-spec parse(any_version()) -> semver().
57parse(Version) when erlang:is_list(Version) ->
58    case ec_semver_parser:parse(Version) of
59        {fail, _} ->
60            {erlang:iolist_to_binary(Version), {[],[]}};
61        Good ->
62            Good
63    end;
64parse(Version) when erlang:is_binary(Version) ->
65    case ec_semver_parser:parse(Version) of
66        {fail, _} ->
67            {Version, {[],[]}};
68        Good ->
69            Good
70    end;
71parse(Version) ->
72    Version.
73
74-spec format(semver()) -> iolist().
75format({Maj, {AlphaPart, BuildPart}})
76  when erlang:is_integer(Maj);
77       erlang:is_binary(Maj) ->
78    [format_version_part(Maj),
79     format_vsn_rest(<<"-">>, AlphaPart),
80     format_vsn_rest(<<"+">>, BuildPart)];
81format({{Maj, Min}, {AlphaPart, BuildPart}}) ->
82    [format_version_part(Maj), ".",
83     format_version_part(Min),
84     format_vsn_rest(<<"-">>, AlphaPart),
85     format_vsn_rest(<<"+">>, BuildPart)];
86format({{Maj, Min, Patch}, {AlphaPart, BuildPart}}) ->
87    [format_version_part(Maj), ".",
88     format_version_part(Min), ".",
89     format_version_part(Patch),
90     format_vsn_rest(<<"-">>, AlphaPart),
91     format_vsn_rest(<<"+">>, BuildPart)];
92format({{Maj, Min, Patch, MinPatch}, {AlphaPart, BuildPart}}) ->
93    [format_version_part(Maj), ".",
94     format_version_part(Min), ".",
95     format_version_part(Patch), ".",
96     format_version_part(MinPatch),
97     format_vsn_rest(<<"-">>, AlphaPart),
98     format_vsn_rest(<<"+">>, BuildPart)].
99
100-spec format_version_part(integer() | binary()) -> iolist().
101format_version_part(Vsn)
102  when erlang:is_integer(Vsn) ->
103    erlang:integer_to_list(Vsn);
104format_version_part(Vsn)
105  when erlang:is_binary(Vsn) ->
106    Vsn.
107
108
109
110%% @doc test for quality between semver versions
111-spec eql(any_version(), any_version()) -> boolean().
112eql(VsnA, VsnB) ->
113    NVsnA = normalize(parse(VsnA)),
114    NVsnB = normalize(parse(VsnB)),
115    NVsnA =:= NVsnB.
116
117%% @doc Test that VsnA is greater than VsnB
118-spec gt(any_version(), any_version()) -> boolean().
119gt(VsnA, VsnB) ->
120    {MMPA, {AlphaA, PatchA}} = normalize(parse(VsnA)),
121    {MMPB, {AlphaB, PatchB}} = normalize(parse(VsnB)),
122    ((MMPA > MMPB)
123     orelse
124       ((MMPA =:= MMPB)
125        andalso
126          ((AlphaA =:= [] andalso AlphaB =/= [])
127           orelse
128             ((not (AlphaB =:= [] andalso AlphaA =/= []))
129              andalso
130                (AlphaA > AlphaB))))
131     orelse
132       ((MMPA =:= MMPB)
133        andalso
134          (AlphaA =:= AlphaB)
135        andalso
136          ((PatchB =:= [] andalso PatchA =/= [])
137           orelse
138           PatchA > PatchB))).
139
140%% @doc Test that VsnA is greater than or equal to VsnB
141-spec gte(any_version(), any_version()) -> boolean().
142gte(VsnA, VsnB) ->
143    NVsnA = normalize(parse(VsnA)),
144    NVsnB = normalize(parse(VsnB)),
145    gt(NVsnA, NVsnB) orelse eql(NVsnA, NVsnB).
146
147%% @doc Test that VsnA is less than VsnB
148-spec lt(any_version(), any_version()) -> boolean().
149lt(VsnA, VsnB) ->
150    {MMPA, {AlphaA, PatchA}} = normalize(parse(VsnA)),
151    {MMPB, {AlphaB, PatchB}} = normalize(parse(VsnB)),
152    ((MMPA < MMPB)
153     orelse
154       ((MMPA =:= MMPB)
155        andalso
156          ((AlphaB =:= [] andalso AlphaA =/= [])
157           orelse
158             ((not (AlphaA =:= [] andalso AlphaB =/= []))
159              andalso
160                (AlphaA < AlphaB))))
161     orelse
162       ((MMPA =:= MMPB)
163        andalso
164          (AlphaA =:= AlphaB)
165        andalso
166          ((PatchA =:= [] andalso PatchB =/= [])
167           orelse
168           PatchA < PatchB))).
169
170%% @doc Test that VsnA is less than or equal to VsnB
171-spec lte(any_version(), any_version()) -> boolean().
172lte(VsnA, VsnB) ->
173    NVsnA = normalize(parse(VsnA)),
174    NVsnB = normalize(parse(VsnB)),
175    lt(NVsnA, NVsnB) orelse eql(NVsnA, NVsnB).
176
177%% @doc Test that VsnMatch is greater than or equal to Vsn1 and
178%% less than or equal to Vsn2
179-spec between(any_version(), any_version(), any_version()) -> boolean().
180between(Vsn1, Vsn2, VsnMatch) ->
181    NVsnA = normalize(parse(Vsn1)),
182    NVsnB = normalize(parse(Vsn2)),
183    NVsnMatch = normalize(parse(VsnMatch)),
184    gte(NVsnMatch, NVsnA) andalso
185        lte(NVsnMatch, NVsnB).
186
187%% @doc check that VsnA is Approximately greater than VsnB
188%%
189%% Specifying ">= 2.6.5" is an optimistic version constraint. All
190%% versions greater than the one specified, including major releases
191%% (e.g. 3.0.0) are allowed.
192%%
193%% Conversely, specifying "~> 2.6" is pessimistic about future major
194%% revisions and "~> 2.6.5" is pessimistic about future minor
195%% revisions.
196%%
197%%  "~> 2.6" matches cookbooks >= 2.6.0 AND &lt; 3.0.0
198%% "~> 2.6.5" matches cookbooks >= 2.6.5 AND &lt; 2.7.0
199pes(VsnA, VsnB) ->
200    internal_pes(parse(VsnA), parse(VsnB)).
201
202%%%===================================================================
203%%% Friend Functions
204%%%===================================================================
205%% @doc helper function for the peg grammer to parse the iolist into a semver
206-spec internal_parse_version(iolist()) -> semver().
207internal_parse_version([MMP, AlphaPart, BuildPart, _]) ->
208    {parse_major_minor_patch_minpatch(MMP), {parse_alpha_part(AlphaPart),
209                                             parse_alpha_part(BuildPart)}}.
210
211%% @doc helper function for the peg grammer to parse the iolist into a major_minor_patch
212-spec parse_major_minor_patch_minpatch(iolist()) -> major_minor_patch_minpatch().
213parse_major_minor_patch_minpatch([MajVsn, [], [], []]) ->
214    strip_maj_version(MajVsn);
215parse_major_minor_patch_minpatch([MajVsn, [<<".">>, MinVsn], [], []]) ->
216    {strip_maj_version(MajVsn), MinVsn};
217parse_major_minor_patch_minpatch([MajVsn,
218                                  [<<".">>, MinVsn],
219                                  [<<".">>, PatchVsn], []]) ->
220    {strip_maj_version(MajVsn), MinVsn, PatchVsn};
221parse_major_minor_patch_minpatch([MajVsn,
222                                  [<<".">>, MinVsn],
223                                  [<<".">>, PatchVsn],
224                                  [<<".">>, MinPatch]]) ->
225    {strip_maj_version(MajVsn), MinVsn, PatchVsn, MinPatch}.
226
227%% @doc helper function for the peg grammer to parse the iolist into an alpha part
228-spec parse_alpha_part(iolist()) -> [alpha_part()].
229parse_alpha_part([]) ->
230    [];
231parse_alpha_part([_, AV1, Rest]) ->
232    [erlang:iolist_to_binary(AV1) |
233     [format_alpha_part(Part) || Part <- Rest]].
234
235%% @doc according to semver alpha parts that can be treated like
236%% numbers must be. We implement that here by taking the alpha part
237%% and trying to convert it to a number, if it succeeds we use
238%% it. Otherwise we do not.
239-spec format_alpha_part(iolist()) -> integer() | binary().
240format_alpha_part([<<".">>, AlphaPart]) ->
241    Bin = erlang:iolist_to_binary(AlphaPart),
242    try
243        erlang:list_to_integer(erlang:binary_to_list(Bin))
244    catch
245        error:badarg ->
246            Bin
247    end.
248
249%%%===================================================================
250%%% Internal Functions
251%%%===================================================================
252-spec strip_maj_version(iolist()) -> version_element().
253strip_maj_version([<<"v">>, MajVsn]) ->
254    MajVsn;
255strip_maj_version([[], MajVsn]) ->
256    MajVsn;
257strip_maj_version(MajVsn) ->
258    MajVsn.
259
260-spec to_list(integer() | binary() | string()) -> string() | binary().
261to_list(Detail) when erlang:is_integer(Detail) ->
262    erlang:integer_to_list(Detail);
263to_list(Detail) when erlang:is_list(Detail); erlang:is_binary(Detail) ->
264    Detail.
265
266-spec format_vsn_rest(binary() | string(), [integer() | binary()]) -> iolist().
267format_vsn_rest(_TypeMark, []) ->
268    [];
269format_vsn_rest(TypeMark, [Head | Rest]) ->
270    [TypeMark, Head |
271     [[".", to_list(Detail)] || Detail <- Rest]].
272
273%% @doc normalize the semver so they can be compared
274-spec normalize(semver()) -> semver().
275normalize({Vsn, Rest})
276  when erlang:is_binary(Vsn);
277       erlang:is_integer(Vsn) ->
278    {{Vsn, 0, 0, 0}, Rest};
279normalize({{Maj, Min}, Rest}) ->
280    {{Maj, Min, 0, 0}, Rest};
281normalize({{Maj, Min, Patch}, Rest}) ->
282    {{Maj, Min, Patch, 0}, Rest};
283normalize(Other = {{_, _, _, _}, {_,_}}) ->
284    Other.
285
286%% @doc to do the pessimistic compare we need a parsed semver. This is
287%% the internal implementation of the of the pessimistic run. The
288%% external just ensures that versions are parsed.
289-spec internal_pes(semver(), semver()) -> boolean().
290internal_pes(VsnA, {{LM, LMI}, Alpha})
291  when erlang:is_integer(LM),
292       erlang:is_integer(LMI) ->
293    gte(VsnA, {{LM, LMI, 0}, Alpha}) andalso
294        lt(VsnA, {{LM + 1, 0, 0, 0}, {[], []}});
295internal_pes(VsnA, {{LM, LMI, LP}, Alpha})
296    when erlang:is_integer(LM),
297         erlang:is_integer(LMI),
298         erlang:is_integer(LP) ->
299    gte(VsnA, {{LM, LMI, LP}, Alpha})
300        andalso
301        lt(VsnA, {{LM, LMI + 1, 0, 0}, {[], []}});
302internal_pes(VsnA, {{LM, LMI, LP, LMP}, Alpha})
303    when erlang:is_integer(LM),
304         erlang:is_integer(LMI),
305         erlang:is_integer(LP),
306         erlang:is_integer(LMP) ->
307    gte(VsnA, {{LM, LMI, LP, LMP}, Alpha})
308        andalso
309        lt(VsnA, {{LM, LMI, LP + 1, 0}, {[], []}});
310internal_pes(Vsn, LVsn) ->
311    gte(Vsn, LVsn).
312
313%%%===================================================================
314%%% Test Functions
315%%%===================================================================
316
317-ifdef(TEST).
318-include_lib("eunit/include/eunit.hrl").
319
320eql_test() ->
321    ?assertMatch(true, eql("1.0.0-alpha",
322                           "1.0.0-alpha")),
323    ?assertMatch(true, eql("v1.0.0-alpha",
324                           "1.0.0-alpha")),
325    ?assertMatch(true, eql("1",
326                           "1.0.0")),
327    ?assertMatch(true, eql("v1",
328                           "v1.0.0")),
329    ?assertMatch(true, eql("1.0",
330                           "1.0.0")),
331    ?assertMatch(true, eql("1.0.0",
332                           "1")),
333    ?assertMatch(true, eql("1.0.0.0",
334                           "1")),
335    ?assertMatch(true, eql("1.0+alpha.1",
336                           "1.0.0+alpha.1")),
337    ?assertMatch(true, eql("1.0-alpha.1+build.1",
338                           "1.0.0-alpha.1+build.1")),
339    ?assertMatch(true, eql("1.0-alpha.1+build.1",
340                           "1.0.0.0-alpha.1+build.1")),
341    ?assertMatch(true, eql("1.0-alpha.1+build.1",
342                           "v1.0.0.0-alpha.1+build.1")),
343    ?assertMatch(true, eql("1.0-pre-alpha.1",
344                           "1.0.0-pre-alpha.1")),
345    ?assertMatch(true, eql("aa", "aa")),
346    ?assertMatch(true, eql("AA.BB", "AA.BB")),
347    ?assertMatch(true, eql("BBB-super", "BBB-super")),
348    ?assertMatch(true, not eql("1.0.0",
349                               "1.0.1")),
350    ?assertMatch(true, not eql("1.0.0-alpha",
351                               "1.0.1+alpha")),
352    ?assertMatch(true, not eql("1.0.0+build.1",
353                               "1.0.1+build.2")),
354    ?assertMatch(true, not eql("1.0.0.0+build.1",
355                               "1.0.0.1+build.2")),
356    ?assertMatch(true, not eql("FFF", "BBB")),
357    ?assertMatch(true, not eql("1", "1BBBB")).
358
359gt_test() ->
360    ?assertMatch(true, gt("1.0.0-alpha.1",
361                          "1.0.0-alpha")),
362    ?assertMatch(true, gt("1.0.0.1-alpha.1",
363                          "1.0.0.1-alpha")),
364    ?assertMatch(true, gt("1.0.0.4-alpha.1",
365                          "1.0.0.2-alpha")),
366    ?assertMatch(true, gt("1.0.0.0-alpha.1",
367                          "1.0.0-alpha")),
368    ?assertMatch(true, gt("1.0.0-beta.2",
369                          "1.0.0-alpha.1")),
370    ?assertMatch(true, gt("1.0.0-beta.11",
371                          "1.0.0-beta.2")),
372    ?assertMatch(true, gt("1.0.0-pre-alpha.14",
373                          "1.0.0-pre-alpha.3")),
374    ?assertMatch(true, gt("1.0.0-beta.11",
375                          "1.0.0.0-beta.2")),
376    ?assertMatch(true, gt("1.0.0-rc.1", "1.0.0-beta.11")),
377    ?assertMatch(true, gt("1.0.0-rc.1+build.1", "1.0.0-rc.1")),
378    ?assertMatch(true, gt("1.0.0", "1.0.0-rc.1+build.1")),
379    ?assertMatch(true, gt("1.0.0+0.3.7", "1.0.0")),
380    ?assertMatch(true, gt("1.3.7+build", "1.0.0+0.3.7")),
381    ?assertMatch(true, gt("1.3.7+build.2.b8f12d7",
382                          "1.3.7+build")),
383    ?assertMatch(true, gt("1.3.7+build.2.b8f12d7",
384                          "1.3.7.0+build")),
385    ?assertMatch(true, gt("1.3.7+build.11.e0f985a",
386                          "1.3.7+build.2.b8f12d7")),
387    ?assertMatch(true, gt("aa.cc",
388                          "aa.bb")),
389    ?assertMatch(true, not gt("1.0.0-alpha",
390                              "1.0.0-alpha.1")),
391    ?assertMatch(true, not gt("1.0.0-alpha",
392                              "1.0.0.0-alpha.1")),
393    ?assertMatch(true, not gt("1.0.0-alpha.1",
394                              "1.0.0-beta.2")),
395    ?assertMatch(true, not gt("1.0.0-beta.2",
396                              "1.0.0-beta.11")),
397    ?assertMatch(true, not gt("1.0.0-beta.11",
398                              "1.0.0-rc.1")),
399    ?assertMatch(true, not gt("1.0.0-pre-alpha.3",
400                              "1.0.0-pre-alpha.14")),
401    ?assertMatch(true, not gt("1.0.0-rc.1",
402                              "1.0.0-rc.1+build.1")),
403    ?assertMatch(true, not gt("1.0.0-rc.1+build.1",
404                              "1.0.0")),
405    ?assertMatch(true, not gt("1.0.0",
406                              "1.0.0+0.3.7")),
407    ?assertMatch(true, not gt("1.0.0+0.3.7",
408                              "1.3.7+build")),
409    ?assertMatch(true, not gt("1.3.7+build",
410                              "1.3.7+build.2.b8f12d7")),
411    ?assertMatch(true, not gt("1.3.7+build.2.b8f12d7",
412                              "1.3.7+build.11.e0f985a")),
413    ?assertMatch(true, not gt("1.0.0-alpha",
414                              "1.0.0-alpha")),
415    ?assertMatch(true, not gt("1",
416                              "1.0.0")),
417    ?assertMatch(true, not gt("aa.bb",
418                              "aa.bb")),
419    ?assertMatch(true, not gt("aa.cc",
420                              "aa.dd")),
421    ?assertMatch(true, not gt("1.0",
422                              "1.0.0")),
423    ?assertMatch(true, not gt("1.0.0",
424                              "1")),
425    ?assertMatch(true, not gt("1.0+alpha.1",
426                              "1.0.0+alpha.1")),
427    ?assertMatch(true, not gt("1.0-alpha.1+build.1",
428                              "1.0.0-alpha.1+build.1")).
429
430lt_test() ->
431    ?assertMatch(true, lt("1.0.0-alpha",
432                          "1.0.0-alpha.1")),
433    ?assertMatch(true, lt("1.0.0-alpha",
434                          "1.0.0.0-alpha.1")),
435    ?assertMatch(true, lt("1.0.0-alpha.1",
436                          "1.0.0-beta.2")),
437    ?assertMatch(true, lt("1.0.0-beta.2",
438                          "1.0.0-beta.11")),
439    ?assertMatch(true, lt("1.0.0-pre-alpha.3",
440                          "1.0.0-pre-alpha.14")),
441    ?assertMatch(true, lt("1.0.0-beta.11",
442                          "1.0.0-rc.1")),
443    ?assertMatch(true, lt("1.0.0.1-beta.11",
444                          "1.0.0.1-rc.1")),
445    ?assertMatch(true, lt("1.0.0-rc.1",
446                          "1.0.0-rc.1+build.1")),
447    ?assertMatch(true, lt("1.0.0-rc.1+build.1",
448                          "1.0.0")),
449    ?assertMatch(true, lt("1.0.0",
450                          "1.0.0+0.3.7")),
451    ?assertMatch(true, lt("1.0.0+0.3.7",
452                          "1.3.7+build")),
453    ?assertMatch(true, lt("1.3.7+build",
454                          "1.3.7+build.2.b8f12d7")),
455    ?assertMatch(true, lt("1.3.7+build.2.b8f12d7",
456                          "1.3.7+build.11.e0f985a")),
457    ?assertMatch(true, not lt("1.0.0-alpha",
458                              "1.0.0-alpha")),
459    ?assertMatch(true, not lt("1",
460                              "1.0.0")),
461    ?assertMatch(true, lt("1",
462                          "1.0.0.1")),
463    ?assertMatch(true, lt("AA.DD",
464                          "AA.EE")),
465    ?assertMatch(true, not lt("1.0",
466                              "1.0.0")),
467    ?assertMatch(true, not lt("1.0.0.0",
468                              "1")),
469    ?assertMatch(true, not lt("1.0+alpha.1",
470                              "1.0.0+alpha.1")),
471    ?assertMatch(true, not lt("AA.DD", "AA.CC")),
472    ?assertMatch(true, not lt("1.0-alpha.1+build.1",
473                              "1.0.0-alpha.1+build.1")),
474    ?assertMatch(true, not lt("1.0.0-alpha.1",
475                              "1.0.0-alpha")),
476    ?assertMatch(true, not lt("1.0.0-beta.2",
477                              "1.0.0-alpha.1")),
478    ?assertMatch(true, not lt("1.0.0-beta.11",
479                              "1.0.0-beta.2")),
480    ?assertMatch(true, not lt("1.0.0-pre-alpha.14",
481                              "1.0.0-pre-alpha.3")),
482    ?assertMatch(true, not lt("1.0.0-rc.1", "1.0.0-beta.11")),
483    ?assertMatch(true, not lt("1.0.0-rc.1+build.1", "1.0.0-rc.1")),
484    ?assertMatch(true, not lt("1.0.0", "1.0.0-rc.1+build.1")),
485    ?assertMatch(true, not lt("1.0.0+0.3.7", "1.0.0")),
486    ?assertMatch(true, not lt("1.3.7+build", "1.0.0+0.3.7")),
487    ?assertMatch(true, not lt("1.3.7+build.2.b8f12d7",
488                              "1.3.7+build")),
489    ?assertMatch(true, not lt("1.3.7+build.11.e0f985a",
490                              "1.3.7+build.2.b8f12d7")).
491
492gte_test() ->
493    ?assertMatch(true, gte("1.0.0-alpha",
494                           "1.0.0-alpha")),
495
496    ?assertMatch(true, gte("1",
497                           "1.0.0")),
498
499    ?assertMatch(true, gte("1.0",
500                           "1.0.0")),
501
502    ?assertMatch(true, gte("1.0.0",
503                           "1")),
504
505    ?assertMatch(true, gte("1.0.0.0",
506                           "1")),
507
508    ?assertMatch(true, gte("1.0+alpha.1",
509                           "1.0.0+alpha.1")),
510
511    ?assertMatch(true, gte("1.0-alpha.1+build.1",
512                           "1.0.0-alpha.1+build.1")),
513
514    ?assertMatch(true, gte("1.0.0-alpha.1+build.1",
515                           "1.0.0.0-alpha.1+build.1")),
516    ?assertMatch(true, gte("1.0.0-alpha.1",
517                           "1.0.0-alpha")),
518    ?assertMatch(true, gte("1.0.0-pre-alpha.2",
519                           "1.0.0-pre-alpha")),
520    ?assertMatch(true, gte("1.0.0-beta.2",
521                           "1.0.0-alpha.1")),
522    ?assertMatch(true, gte("1.0.0-beta.11",
523                           "1.0.0-beta.2")),
524    ?assertMatch(true, gte("aa.bb", "aa.bb")),
525    ?assertMatch(true, gte("dd", "aa")),
526    ?assertMatch(true, gte("1.0.0-rc.1", "1.0.0-beta.11")),
527    ?assertMatch(true, gte("1.0.0-rc.1+build.1", "1.0.0-rc.1")),
528    ?assertMatch(true, gte("1.0.0", "1.0.0-rc.1+build.1")),
529    ?assertMatch(true, gte("1.0.0+0.3.7", "1.0.0")),
530    ?assertMatch(true, gte("1.3.7+build", "1.0.0+0.3.7")),
531    ?assertMatch(true, gte("1.3.7+build.2.b8f12d7",
532                           "1.3.7+build")),
533    ?assertMatch(true, gte("1.3.7+build.11.e0f985a",
534                           "1.3.7+build.2.b8f12d7")),
535    ?assertMatch(true, not gte("1.0.0-alpha",
536                               "1.0.0-alpha.1")),
537    ?assertMatch(true, not gte("1.0.0-pre-alpha",
538                               "1.0.0-pre-alpha.1")),
539    ?assertMatch(true, not gte("CC", "DD")),
540    ?assertMatch(true, not gte("1.0.0-alpha.1",
541                               "1.0.0-beta.2")),
542    ?assertMatch(true, not gte("1.0.0-beta.2",
543                               "1.0.0-beta.11")),
544    ?assertMatch(true, not gte("1.0.0-beta.11",
545                               "1.0.0-rc.1")),
546    ?assertMatch(true, not gte("1.0.0-rc.1",
547                               "1.0.0-rc.1+build.1")),
548    ?assertMatch(true, not gte("1.0.0-rc.1+build.1",
549                               "1.0.0")),
550    ?assertMatch(true, not gte("1.0.0",
551                               "1.0.0+0.3.7")),
552    ?assertMatch(true, not gte("1.0.0+0.3.7",
553                               "1.3.7+build")),
554    ?assertMatch(true, not gte("1.0.0",
555                               "1.0.0+build.1")),
556    ?assertMatch(true, not gte("1.3.7+build",
557                               "1.3.7+build.2.b8f12d7")),
558    ?assertMatch(true, not gte("1.3.7+build.2.b8f12d7",
559                               "1.3.7+build.11.e0f985a")).
560lte_test() ->
561    ?assertMatch(true, lte("1.0.0-alpha",
562                           "1.0.0-alpha.1")),
563    ?assertMatch(true, lte("1.0.0-alpha.1",
564                           "1.0.0-beta.2")),
565    ?assertMatch(true, lte("1.0.0-beta.2",
566                           "1.0.0-beta.11")),
567    ?assertMatch(true, lte("1.0.0-pre-alpha.2",
568                           "1.0.0-pre-alpha.11")),
569    ?assertMatch(true, lte("1.0.0-beta.11",
570                           "1.0.0-rc.1")),
571    ?assertMatch(true, lte("1.0.0-rc.1",
572                           "1.0.0-rc.1+build.1")),
573    ?assertMatch(true, lte("1.0.0-rc.1+build.1",
574                           "1.0.0")),
575    ?assertMatch(true, lte("1.0.0",
576                           "1.0.0+0.3.7")),
577    ?assertMatch(true, lte("1.0.0+0.3.7",
578                           "1.3.7+build")),
579    ?assertMatch(true, lte("1.3.7+build",
580                           "1.3.7+build.2.b8f12d7")),
581    ?assertMatch(true, lte("1.3.7+build.2.b8f12d7",
582                           "1.3.7+build.11.e0f985a")),
583    ?assertMatch(true, lte("1.0.0-alpha",
584                           "1.0.0-alpha")),
585    ?assertMatch(true, lte("1",
586                           "1.0.0")),
587    ?assertMatch(true, lte("1.0",
588                           "1.0.0")),
589    ?assertMatch(true, lte("1.0.0",
590                           "1")),
591    ?assertMatch(true, lte("1.0+alpha.1",
592                           "1.0.0+alpha.1")),
593    ?assertMatch(true, lte("1.0.0.0+alpha.1",
594                           "1.0.0+alpha.1")),
595    ?assertMatch(true, lte("1.0-alpha.1+build.1",
596                           "1.0.0-alpha.1+build.1")),
597    ?assertMatch(true, lte("aa","cc")),
598    ?assertMatch(true, lte("cc","cc")),
599    ?assertMatch(true, not lte("1.0.0-alpha.1",
600                              "1.0.0-alpha")),
601    ?assertMatch(true, not lte("1.0.0-pre-alpha.2",
602                               "1.0.0-pre-alpha")),
603    ?assertMatch(true, not lte("cc", "aa")),
604    ?assertMatch(true, not lte("1.0.0-beta.2",
605                              "1.0.0-alpha.1")),
606    ?assertMatch(true, not lte("1.0.0-beta.11",
607                              "1.0.0-beta.2")),
608    ?assertMatch(true, not lte("1.0.0-rc.1", "1.0.0-beta.11")),
609    ?assertMatch(true, not lte("1.0.0-rc.1+build.1", "1.0.0-rc.1")),
610    ?assertMatch(true, not lte("1.0.0", "1.0.0-rc.1+build.1")),
611    ?assertMatch(true, not lte("1.0.0+0.3.7", "1.0.0")),
612    ?assertMatch(true, not lte("1.3.7+build", "1.0.0+0.3.7")),
613    ?assertMatch(true, not lte("1.3.7+build.2.b8f12d7",
614                              "1.3.7+build")),
615    ?assertMatch(true, not lte("1.3.7+build.11.e0f985a",
616                              "1.3.7+build.2.b8f12d7")).
617
618between_test() ->
619    ?assertMatch(true, between("1.0.0-alpha",
620                               "1.0.0-alpha.3",
621                               "1.0.0-alpha.2")),
622    ?assertMatch(true, between("1.0.0-alpha.1",
623                               "1.0.0-beta.2",
624                               "1.0.0-alpha.25")),
625    ?assertMatch(true, between("1.0.0-beta.2",
626                               "1.0.0-beta.11",
627                               "1.0.0-beta.7")),
628    ?assertMatch(true, between("1.0.0-pre-alpha.2",
629                               "1.0.0-pre-alpha.11",
630                               "1.0.0-pre-alpha.7")),
631    ?assertMatch(true, between("1.0.0-beta.11",
632                               "1.0.0-rc.3",
633                               "1.0.0-rc.1")),
634    ?assertMatch(true, between("1.0.0-rc.1",
635                               "1.0.0-rc.1+build.3",
636                               "1.0.0-rc.1+build.1")),
637
638    ?assertMatch(true, between("1.0.0.0-rc.1",
639                               "1.0.0-rc.1+build.3",
640                               "1.0.0-rc.1+build.1")),
641    ?assertMatch(true, between("1.0.0-rc.1+build.1",
642                               "1.0.0",
643                               "1.0.0-rc.33")),
644    ?assertMatch(true, between("1.0.0",
645                               "1.0.0+0.3.7",
646                               "1.0.0+0.2")),
647    ?assertMatch(true, between("1.0.0+0.3.7",
648                               "1.3.7+build",
649                               "1.2")),
650    ?assertMatch(true, between("1.3.7+build",
651                               "1.3.7+build.2.b8f12d7",
652                               "1.3.7+build.1")),
653    ?assertMatch(true, between("1.3.7+build.2.b8f12d7",
654                               "1.3.7+build.11.e0f985a",
655                               "1.3.7+build.10.a36faa")),
656    ?assertMatch(true, between("1.0.0-alpha",
657                               "1.0.0-alpha",
658                               "1.0.0-alpha")),
659    ?assertMatch(true, between("1",
660                               "1.0.0",
661                               "1.0.0")),
662    ?assertMatch(true, between("1.0",
663                               "1.0.0",
664                               "1.0.0")),
665
666    ?assertMatch(true, between("1.0",
667                               "1.0.0.0",
668                               "1.0.0.0")),
669    ?assertMatch(true, between("1.0.0",
670                               "1",
671                               "1")),
672    ?assertMatch(true, between("1.0+alpha.1",
673                               "1.0.0+alpha.1",
674                               "1.0.0+alpha.1")),
675    ?assertMatch(true, between("1.0-alpha.1+build.1",
676                               "1.0.0-alpha.1+build.1",
677                               "1.0.0-alpha.1+build.1")),
678    ?assertMatch(true, between("aaa",
679                               "ddd",
680                               "cc")),
681    ?assertMatch(true, not between("1.0.0-alpha.1",
682                                   "1.0.0-alpha.22",
683                                   "1.0.0")),
684    ?assertMatch(true, not between("1.0.0-pre-alpha.1",
685                                   "1.0.0-pre-alpha.22",
686                                   "1.0.0")),
687    ?assertMatch(true, not between("1.0.0",
688                                   "1.0.0-alpha.1",
689                                   "2.0")),
690    ?assertMatch(true, not between("1.0.0-beta.1",
691                                   "1.0.0-beta.11",
692                                   "1.0.0-alpha")),
693    ?assertMatch(true, not between("1.0.0-beta.11", "1.0.0-rc.1",
694                                   "1.0.0-rc.22")),
695    ?assertMatch(true, not between("aaa", "ddd", "zzz")).
696
697pes_test() ->
698    ?assertMatch(true, pes("1.0.0-rc.0", "1.0.0-rc.0")),
699    ?assertMatch(true, pes("1.0.0-rc.1", "1.0.0-rc.0")),
700    ?assertMatch(true, pes("1.0.0", "1.0.0-rc.0")),
701    ?assertMatch(false, pes("1.0.0-rc.0", "1.0.0-rc.1")),
702    ?assertMatch(true, pes("2.6.0", "2.6")),
703    ?assertMatch(true, pes("2.7", "2.6")),
704    ?assertMatch(true, pes("2.8", "2.6")),
705    ?assertMatch(true, pes("2.9", "2.6")),
706    ?assertMatch(true, pes("A.B", "A.A")),
707    ?assertMatch(true, not pes("3.0.0", "2.6")),
708    ?assertMatch(true, not pes("2.5", "2.6")),
709    ?assertMatch(true, pes("2.6.5", "2.6.5")),
710    ?assertMatch(true, pes("2.6.6", "2.6.5")),
711    ?assertMatch(true, pes("2.6.7", "2.6.5")),
712    ?assertMatch(true, pes("2.6.8", "2.6.5")),
713    ?assertMatch(true, pes("2.6.9", "2.6.5")),
714    ?assertMatch(true, pes("2.6.0.9", "2.6.0.5")),
715    ?assertMatch(true, not pes("2.7", "2.6.5")),
716    ?assertMatch(true, not pes("2.1.7", "2.1.6.5")),
717    ?assertMatch(true, not pes("A.A", "A.B")),
718    ?assertMatch(true, not pes("2.5", "2.6.5")).
719
720parse_test() ->
721    ?assertEqual({1, {[],[]}}, parse(<<"1">>)),
722    ?assertEqual({{1,2,34},{[],[]}}, parse(<<"1.2.34">>)),
723    ?assertEqual({<<"a">>, {[],[]}}, parse(<<"a">>)),
724    ?assertEqual({{<<"a">>,<<"b">>}, {[],[]}}, parse(<<"a.b">>)),
725    ?assertEqual({1, {[],[]}}, parse(<<"1">>)),
726    ?assertEqual({{1,2}, {[],[]}}, parse(<<"1.2">>)),
727    ?assertEqual({{1,2,2}, {[],[]}}, parse(<<"1.2.2">>)),
728    ?assertEqual({{1,99,2}, {[],[]}}, parse(<<"1.99.2">>)),
729    ?assertEqual({{1,99,2}, {[<<"alpha">>],[]}}, parse(<<"1.99.2-alpha">>)),
730    ?assertEqual({{1,99,2}, {[<<"alpha">>,1], []}}, parse(<<"1.99.2-alpha.1">>)),
731    ?assertEqual({{1,99,2}, {[<<"pre-alpha">>,1], []}}, parse(<<"1.99.2-pre-alpha.1">>)),
732    ?assertEqual({{1,99,2}, {[], [<<"build">>, 1, <<"a36">>]}},
733        parse(<<"1.99.2+build.1.a36">>)),
734    ?assertEqual({{1,99,2,44}, {[], [<<"build">>, 1, <<"a36">>]}},
735        parse(<<"1.99.2.44+build.1.a36">>)),
736    ?assertEqual({{1,99,2}, {[<<"alpha">>, 1], [<<"build">>, 1, <<"a36">>]}},
737        parse("1.99.2-alpha.1+build.1.a36")),
738    ?assertEqual({{1,99,2}, {[<<"pre-alpha">>, 1], [<<"build">>, 1, <<"a36">>]}},
739        parse("1.99.2-pre-alpha.1+build.1.a36")).
740
741version_format_test() ->
742    ?assertEqual(["1", [], []], format({1, {[],[]}})),
743    ?assertEqual(["1", ".", "2", ".", "34", [], []], format({{1,2,34},{[],[]}})),
744    ?assertEqual(<<"a">>, erlang:iolist_to_binary(format({<<"a">>, {[],[]}}))),
745    ?assertEqual(<<"a.b">>, erlang:iolist_to_binary(format({{<<"a">>,<<"b">>}, {[],[]}}))),
746    ?assertEqual(<<"1">>, erlang:iolist_to_binary(format({1, {[],[]}}))),
747    ?assertEqual(<<"1.2">>, erlang:iolist_to_binary(format({{1,2}, {[],[]}}))),
748    ?assertEqual(<<"1.2.2">>, erlang:iolist_to_binary(format({{1,2,2}, {[],[]}}))),
749    ?assertEqual(<<"1.99.2">>, erlang:iolist_to_binary(format({{1,99,2}, {[],[]}}))),
750    ?assertEqual(<<"1.99.2-alpha">>, erlang:iolist_to_binary(format({{1,99,2}, {[<<"alpha">>],[]}}))),
751    ?assertEqual(<<"1.99.2-alpha.1">>, erlang:iolist_to_binary(format({{1,99,2}, {[<<"alpha">>,1], []}}))),
752    ?assertEqual(<<"1.99.2-pre-alpha.1">>, erlang:iolist_to_binary(format({{1,99,2}, {[<<"pre-alpha">>,1], []}}))),
753    ?assertEqual(<<"1.99.2+build.1.a36">>,
754                 erlang:iolist_to_binary(format({{1,99,2}, {[], [<<"build">>, 1, <<"a36">>]}}))),
755    ?assertEqual(<<"1.99.2.44+build.1.a36">>,
756                 erlang:iolist_to_binary(format({{1,99,2,44}, {[], [<<"build">>, 1, <<"a36">>]}}))),
757    ?assertEqual(<<"1.99.2-alpha.1+build.1.a36">>,
758                 erlang:iolist_to_binary(format({{1,99,2}, {[<<"alpha">>, 1], [<<"build">>, 1, <<"a36">>]}}))),
759    ?assertEqual(<<"1.99.2-pre-alpha.1+build.1.a36">>,
760        erlang:iolist_to_binary(format({{1,99,2}, {[<<"pre-alpha">>, 1], [<<"build">>, 1, <<"a36">>]}}))),
761    ?assertEqual(<<"1">>, erlang:iolist_to_binary(format({1, {[],[]}}))).
762
763-endif.
764