Current section
Files
Jump to
Current section
Files
src/erlup_utils.erl
%% @copyright 2016 Hinagiku Soranoba All Rights Reserved.
%%
%% @doc Utility functions
-module(erlup_utils).
-include("erlup.hrl").
%%----------------------------------------------------------------------------------------------------------------------
%% Exported API
%%----------------------------------------------------------------------------------------------------------------------
-export([
list_to_integer_if_possible/1,
to_string/1,
to_binary/1,
split/2,
absname/1,
base_dir/1,
find_rels/1,
lookup_current_vsn/1,
lookup_include_libs/1,
sort_vsns/1
]).
%%----------------------------------------------------------------------------------------------------------------------
%% Exported Functions
%%----------------------------------------------------------------------------------------------------------------------
%% @doc Convert to integer from string, if possible.
-spec list_to_integer_if_possible(string()) -> integer() | string().
list_to_integer_if_possible(X) ->
case catch list_to_integer(X) of
Int when is_integer(Int) -> Int;
_ -> X
end.
%% @doc Convert to string, if necessary.
-spec to_string(binary() | string()) -> string().
to_string(Bin) when is_binary(Bin) ->
binary_to_list(Bin);
to_string(Str) when is_list(Str) ->
Str.
%% @doc Convert to binary, if necessary.
-spec to_binary(binary() | string()) -> binary().
to_binary(Str) when is_list(Str) ->
list_to_binary(Str);
to_binary(Bin) when is_binary(Bin) ->
Bin.
%% @doc Split the list by the position of the Term.
%%
%% The Term is erased.
-spec split([term()], term()) -> {[term()], [term()]}.
split(List, Term) ->
case lists:splitwith(fun(X) -> X =/= Term end, List) of
{X, []} -> {X, []};
{X, [_|Y]} -> {X, Y}
end.
%% @doc Given a file path, return a absolute file path.
%%
%% It differs to `filename:absname/1', `..' and `.' are not included.
-spec absname(file:filename_all()) -> file:filename_all().
absname(Path) ->
Ret = lists:foldl(fun(X, Acc) when X =:= <<".">>; X =:= "." ->
Acc;
(X, Acc) when X =:= <<"..">>; X =:= ".." ->
?IIF(Acc =:= [], [], tl(Acc));
(X, Acc) ->
[X | Acc]
end, [], filename:split(filename:absname(Path))),
filename:join(lists:reverse(Ret)).
%% @doc Given a file path, return the release base file path.
-spec base_dir(file:filename_all()) -> file:filename().
base_dir(Path) ->
AbsPath = absname(Path),
Ext = filename:extension(AbsPath),
Base = filename:basename(AbsPath),
Mapping = [
{".rel", "myapp/releases/vsn/myapp.rel", "myapp"},
{"relup", "myapp/releases/vsn/relup", "myapp"},
{".appup", "myapp/lib/mymod-vsn/ebin/myapp.appup", "myapp"},
{".app", "myapp/lib/mymod-vsn/ebin/myapp.app", "myapp"},
{".beam", "myapp/lib/mymod-vsn/ebin/mymod.beam", "myapp"},
{".data", "myapp/releases/start_erl.data", "myapp"},
{"releases", "myapp/releases", "myapp"},
{"lib", "myapp/lib", "myapp"},
{"ebin", "myapp/lib/mymod-vsn/ebin", "myapp"},
{"bin", "myapp/bin", "myapp"}
],
IsDir = ?IIF(filelib:is_file(Path), filelib:is_dir(Path), Ext =:= ""),
case ?IIF(IsDir, lists:keyfind(Base, 1, Mapping), lists:keyfind(Ext, 1, Mapping)) of
false when IsDir -> Path;
false -> error({not_support_extension, Ext}, [Path]);
{_, F, B} ->
lists:foldl(fun(_, Acc) -> filename:dirname(Acc) end,
AbsPath, lists:seq(1, length(filename:split(F)) - length(filename:split(B))))
end.
%% @doc Find .rel files in the directories.
-spec find_rels([Dir :: file:filename_all()]) -> [{RelName :: string(), Vsn :: string(), file:filename_all()}].
find_rels(Dirs) ->
Ret = lists:foldr(fun(Dir, Acc) ->
Wildcard = filename:join([base_dir(Dir), "releases", "*", "*.rel"]),
lists:filtermap(fun(Path) ->
case file:consult(Path) of
{ok, [{release, {RelName, RelVsn}, _, _}]} ->
{true, {RelName, RelVsn, Path}};
_ ->
false
end
end, filelib:wildcard(Wildcard)) ++ Acc
end, [], Dirs),
lists:ukeysort(2, Ret).
%% @doc Lookup the current version of a release that given directory is belongs.
-spec lookup_current_vsn(file:filename_all()) -> {ok, Vsn :: string()} | {error, string()}.
lookup_current_vsn(Path0) ->
Path = filename:join([base_dir(Path0), "releases", "start_erl.data"]),
case file:read_file(Path) of
{ok, Bin} ->
case binary:split(Bin, [<<"\n">>, <<" ">>], [global, trim]) of
[_, Vsn] -> {ok, to_string(Vsn)};
_ -> {error, "Invalid format. " ++ to_string(Path)}
end;
{error, Reason} ->
{error, file:format_error(Reason) ++ " " ++ to_string(Path)}
end.
%% @doc Lookup the application name and vsn of the library.
-spec lookup_include_libs(file:filename()) -> {ok, [{Application :: atom(), Vsn :: string()}]} | {error, string()}.
lookup_include_libs(RelFile) ->
case file:consult(RelFile) of
{ok, [{release, _, _, AppVsns}]} ->
{ok, [{element(1, X), element(2, X)} || X <- AppVsns, is_tuple(X)]};
{ok, _} ->
{error, "Invalid format. " ++ RelFile};
{error, Reason} ->
{error, file:format_error(Reason) ++ " " ++ RelFile}
end.
%% @doc Sort the vsns to the old version order.
%%
%% MUST be the form of <a href="http://semver.org/">Semantic Versioning</a>
%%
%% ```
%% v1.2.3-alpha.1+build.1.g12345
%%
%% Prefix : v : ignore non-numeric
%% Majour : 1 : only integer
%% Minor : 2 : only integer
%% Patch : 3 : only integer
%% PreRelease : alpha.1 : It compare for each that split by dot. PreRelease is old than Non-PreRelease.
%% BuildMetaData : build.1.g12345 : ignore all
%% '''
-spec sort_vsns([Vsn :: string()]) -> [string()].
sort_vsns(Vsns) ->
VersionFun
= fun(Vsn0) ->
%% remove a prefix (ver1.2.3 -> 1.2.3)
{_Prefix, Vsn1} = lists:splitwith(fun(X) ->
not ($0 =< X andalso $9 >= X)
end, Vsn0),
%% remove a build metadata (1.2.3+buildg1234 -> 1.2.3)
{Vsn2, _Suffix} = split(Vsn1, $+),
%% separates the pre-release version and the release version (Sem1-Sem2)
{Sem1, Sem2} = split(Vsn2, $-),
SemVer1 = lists:map(fun list_to_integer_if_possible/1, string:tokens(Sem1, ".")),
SemVer2 = lists:map(fun list_to_integer_if_possible/1, string:tokens(Sem2, ".")),
%% binary is grater than list.
{SemVer1, ?IIF(SemVer2 =:= [], <<>>, SemVer2)}
end,
lists:sort(fun(X, Y) -> VersionFun(X) < VersionFun(Y) end, Vsns).