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 < 3.0.0 198%% "~> 2.6.5" matches cookbooks >= 2.6.5 AND < 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