diff --git a/rebar.config b/rebar.config index 6bc7d6f..057e498 100644 --- a/rebar.config +++ b/rebar.config @@ -5,7 +5,7 @@ {git, "https://github.com/erlware/rebar_vsn_plugin.git", {branch, "master"}}}]}. -{erl_first_files, ["ec_dictionary"]}. +{erl_first_files, ["ec_dictionary", "ec_vsn"]}. %% Compiler Options ============================================================ {erl_opts, diff --git a/src/ec_git_vsn.erl b/src/ec_git_vsn.erl new file mode 100644 index 0000000..21d2639 --- /dev/null +++ b/src/ec_git_vsn.erl @@ -0,0 +1,79 @@ +%%% vi:ts=4 sw=4 et +%%%------------------------------------------------------------------- +%%% @author Eric Merritt +%%% @copyright 2011 Erlware, LLC. +%%% @doc +%%% This provides an implementation of the ec_vsn for git. That is +%%% it is capable of returning a semver for a git repository +%%% see ec_vsn +%%% see ec_semver +%%% @end +%%%------------------------------------------------------------------- +-module(ec_git_vsn). + +-behaviour(ec_vsn). + +%% API +-export([new/0, + vsn/1]). + +-export_type([t/0]). + +%%%=================================================================== +%%% Types +%%%=================================================================== +%% This should be opaque, but that kills dialyzer so for now we export it +%% however you should not rely on the internal representation here +-type t() :: {}. + +%%%=================================================================== +%%% API +%%%=================================================================== + +-spec new() -> t(). +new() -> + {}. + +-spec vsn(t()) -> {ok, string()} | {error, Reason::any()}. +vsn(_Data) -> + Result = do_cmd("git describe --tags --always"), + case re:split(Result, "-") of + [Vsn, Count, RefTag] -> + erlang:iolist_to_binary([strip_leading_v(Vsn), + <<"+build.">>, + Count, + <<".ref.">>, + RefTag]); + [VsnOrRefTag] -> + case re:run(VsnOrRefTag, "^[0-9a-fA-F]+$") of + {match, _} -> + find_vsn_from_start_of_branch(VsnOrRefTag); + nomatch -> + strip_leading_v(VsnOrRefTag) + end; + _ -> + {error, {invalid_result, Result}} + end. + +%%%=================================================================== +%%% Internal Functions +%%%=================================================================== +-spec strip_leading_v(string()) -> string(). +strip_leading_v(Vsn) -> + case re:run(Vsn, "v?(.+)", [{capture, [1], binary}]) of + {match, [NVsn]} -> + NVsn; + _ -> + Vsn + end. + +-spec find_vsn_from_start_of_branch(string()) -> string(). +find_vsn_from_start_of_branch(RefTag) -> + Count = do_cmd("git rev-list HEAD --count"), + erlang:iolist_to_binary("0.0.0+build.", Count, ".ref.", RefTag). + +do_cmd(Cmd) -> + trim_whitespace(os:cmd(Cmd)). + +trim_whitespace(Input) -> + re:replace(Input, "\\s+", "", [global]). diff --git a/src/ec_vsn.erl b/src/ec_vsn.erl new file mode 100644 index 0000000..2f38090 --- /dev/null +++ b/src/ec_vsn.erl @@ -0,0 +1,66 @@ +%%% vi:ts=4 sw=4 et +%%%------------------------------------------------------------------- +%%% @author Eric Merritt +%%% @copyright 2014 Erlware, LLC. +%%% @doc +%%% Provides a signature to manage returning semver formatted versions +%%% from various version control repositories. +%%% +%%% This interface is a member of the Erlware Commons Library. +%%% @end +%%%------------------------------------------------------------------- +-module(ec_vsn). + +%% API +-export([new/1, + vsn/1]). + +-export_type([t/0]). + +%%%=================================================================== +%%% Types +%%%=================================================================== + +-record(t, {callback, data}). + +%% This should be opaque, but that kills dialyzer so for now we export it +%% however you should not rely on the internal representation here +-type t() :: #t{}. + +-ifdef(have_callback_support). + +-callback new() -> any(). +-callback vsn(any()) -> {ok, string()} | {error, Reason::any()}. + +-else. + +%% In the case where R14 or lower is being used to compile the system +%% we need to export a behaviour info +-export([behaviour_info/1]). +-spec behaviour_info(atom()) -> [{atom(), arity()}] | undefined. +behaviour_info(callbacks) -> + [{new, 0}, + {vsn, 1}]; +behaviour_info(_Other) -> + undefined. +-endif. + +%%%=================================================================== +%%% API +%%%=================================================================== + +%% @doc create a new dictionary object from the specified module. The +%% module should implement the dictionary behaviour. +%% +%% @param ModuleName The module name. +-spec new(module()) -> t(). +new(ModuleName) when erlang:is_atom(ModuleName) -> + #t{callback = ModuleName, data = ModuleName:new()}. + +%% @doc Return the semver or an error depending on what is possible +%% with this implementation in this directory. +%% +%% @param The dictionary object +-spec vsn(t()) -> {ok, string()} | {error, Reason::any()}. +vsn(#t{callback = Mod, data = Data}) -> + Mod:vsn(Data).