diff --git a/src/ec_string.erl b/src/ec_string.erl new file mode 100644 index 0000000..1dcba81 --- /dev/null +++ b/src/ec_string.erl @@ -0,0 +1,117 @@ +%%%------------------------------------------------------------------- +%%% @copyright (C) 2011, Erlware LLC +%%% @doc +%%% Helper functions for working with strings. +%%% @end +%%%------------------------------------------------------------------- +-module(ec_string). + +-export([ + compare_versions/2 + ]). +%%%=================================================================== +%%% API +%%%=================================================================== + +%% @doc Is arbitrary version string A bigger than version string B? +%% Valid version string elements are either separated by . or - or both. +%% Final version string elements may have a numeric followed directly by an +%% alpha numeric and will be compared separately as in 12alpha. +%% +%%
+%% Example: compare_versions("3-2-5-alpha", "3.10.6") will return false +%% compare_versions("3-2-alpha", "3.2.1-alpha") will return false +%% compare_versions("3-2alpha", "3.2.1-alpha") will return false +%% compare_versions("3.2.2", "3.2.2") will return false +%% compare_versions("3.2.1", "3.2.1-rc2") will return true +%% compare_versions("3.2.2", "3.2.1") will return true +%%+-spec compare_versions(VsnA::string(), VsnB::string()) -> boolean(). +compare_versions(VsnA, VsnB) -> + compare(string:tokens(VsnA, ".-"),string:tokens(VsnB, ".-")). + +%%%=================================================================== +%%% Internal Functions +%%%=================================================================== + +compare([Str|TA], [Str|TB]) -> + compare(TA, TB); +compare([StrA|TA], [StrB|TB]) -> + fine_compare(split_numeric_alpha(StrA), TA, + split_numeric_alpha(StrB), TB); +compare([], [Str]) -> + not compare_against_nothing(Str); +compare([Str], []) -> + compare_against_nothing(Str); +compare([], [_,_|_]) -> + false; +compare([_,_|_], []) -> + true; +compare([], []) -> + false. + +compare_against_nothing(Str) -> + case split_numeric_alpha(Str) of + {_StrDig, ""} -> true; + {"", _StrAlpha} -> false; + {_StrDig, _StrAlpha} -> true + end. + +fine_compare({_StrDigA, StrA}, TA, {_StrDigB, _StrB}, _TB) when StrA /= "", TA /= [] -> + throw(invalid_version_string); +fine_compare({_StrDigA, _StrA}, _TA, {_StrDigB, StrB}, TB) when StrB /= "", TB /= [] -> + throw(invalid_version_string); +fine_compare({"", _StrA}, _TA, {StrDigB, _StrB}, _TB) when StrDigB /= "" -> + false; +fine_compare({StrDigA, _StrA}, _TA, {"", _StrB}, _TB) when StrDigA /= "" -> + true; +fine_compare({StrDig, ""}, _TA, {StrDig, StrB}, _TB) when StrB /= "" -> + true; +fine_compare({StrDig, StrA}, _TA, {StrDig, ""}, _TB) when StrA /= "" -> + false; +fine_compare({StrDig, StrA}, _TA, {StrDig, StrB}, _TB) -> + StrA > StrB; +fine_compare({StrDigA, _StrA}, _TA, {StrDigB, _StrB}, _TB) -> + list_to_integer(StrDigA) > list_to_integer(StrDigB). + +%% In the case of a version sub part with a numeric then an alpha, +%% split out the numeric and alpha "24alpha" becomes {"24", "alpha"} +split_numeric_alpha(RawVsn) -> + {Num, Str} = split_numeric_alpha(RawVsn, {"", ""}), + {lists:reverse(Num), Str}. + +split_numeric_alpha([], Acc) -> + Acc; +split_numeric_alpha([Dig|T], {PatchVsn, PatchStr}) + when Dig >= $0 andalso Dig =< $9 -> + split_numeric_alpha(T, {[Dig|PatchVsn], PatchStr}); +split_numeric_alpha(PatchStr, {PatchVsn, ""}) -> + {PatchVsn, PatchStr}. + + +%%%=================================================================== +%%% Test Functions +%%%=================================================================== + +-ifndef(NOTEST). +-include_lib("eunit/include/eunit.hrl"). + +split_numeric_alpha_test() -> + ?assertMatch({"123", "alpha1"}, split_numeric_alpha("123alpha1")). + +compare_versions_test() -> + ?assertMatch(true, compare_versions("1.2.3", "1.2.3alpha")), + ?assertMatch(true, compare_versions("1.2.3-beta", "1.2.3-alpha")), + ?assertMatch(true, compare_versions("1-2-3", "1-2-3alpha")), + ?assertMatch(true, compare_versions("1-2-3", "1-2-3-rc3")), + ?assertMatch(true, compare_versions("1.2.3beta", "1.2.3alpha")), + ?assertMatch(true, compare_versions("1.2.4", "1.2.3")), + ?assertMatch(true, compare_versions("1.3.3", "1.2.3")), + ?assertMatch(true, compare_versions("2.2.3", "1.2.3")), + ?assertMatch(true, compare_versions("4.2.3", "3.10.3")), + ?assertMatch(false, compare_versions("1.2.3", "2.2.3")), + ?assertMatch(false, compare_versions("1.2.2", "1.3.t")), + ?assertMatch(false, compare_versions("1.2t", "1.3.t")), + ?assertThrow(invalid_version_string, compare_versions("1.b.2", "1.3.4")). + +-endif.