Packages
rebar3_hex
6.11.6
7.1.0
7.0.11
7.0.10
7.0.9
7.0.8
7.0.7
7.0.6
7.0.5
7.0.4
7.0.3
7.0.2
7.0.1
7.0.0
6.11.9
6.11.8
6.11.7
6.11.6
6.11.5
6.11.4
6.11.3
6.11.2
6.11.1
6.11.0
6.10.3
6.10.2
6.10.1
6.10.0
6.9.6
6.9.5
6.9.4
6.9.3
6.9.2
6.9.1
6.9.0
6.8.0
6.7.0
6.6.0
6.5.0
6.4.0
6.3.0
6.2.0
6.1.0
6.0.0
4.1.0
4.0.0
3.1.0
3.0.0
2.5.1
2.5.0
2.4.0
2.3.0
2.2.0
2.1.0
2.0.0
1.20.0
1.19.0
1.18.0
1.17.0
1.16.0
1.15.0
1.14.0
1.13.0
1.12.0
1.11.0
1.10.0
1.9.1
1.9.0
1.8.1
1.8.0
1.7.2
1.7.1
1.7.0
1.6.3
1.6.1
1.6.0
1.5.0
1.4.0
1.3.0
1.2.0
1.1.0
0.9.0
0.8.0
0.7.0
0.6.0
0.5.1
0.5.0
0.4.0
0.3.0
0.2.0
0.1.0
Hex.pm plugin for rebar3
Current section
Files
Jump to
Current section
Files
src/rebar3_hex_docs.erl
-module(rebar3_hex_docs).
-export([init/1,
do/1,
publish/3,
format_error/1]).
-include("rebar3_hex.hrl").
-define(PROVIDER, docs).
-define(DEPS, [{default, lock}]).
-define(DEFAULT_DOC_DIR, "doc").
%% ===================================================================
%% Public API
%% ===================================================================
-spec init(rebar_state:t()) -> {ok, rebar_state:t()}.
init(State) ->
Provider = providers:create([
{name, ?PROVIDER},
{module, ?MODULE},
{namespace, hex},
{bare, true},
{deps, ?DEPS},
{example, "rebar3 hex docs"},
{short_desc, "Publish documentation for the current project and version"},
{desc, ""},
{opts, [{revert, undefined, "revert", string, "Revert given version."},
{dry_run, undefined, "dry-run", {boolean, false}, help(dry_run)},
rebar3_hex:repo_opt()]},
{profiles, [docs]}]),
State1 = rebar_state:add_provider(State, Provider),
{ok, State1}.
-spec do(rebar_state:t()) -> {ok, rebar_state:t()} | {error, string()}.
do(State) ->
Apps = rebar3_hex_io:select_apps(rebar_state:project_apps(State)),
try publish_apps(Apps, State) of
{ok, State} ->
{ok, State}
catch
throw:{error,{rebar3_hex_docs, _}} = Err ->
Err;
error:{badmatch, {error, Reason}} ->
?PRV_ERROR(Reason)
end.
%% @doc Publish documentation directory to repository
%%
%% This following function is exported for publishing docs via the
%% main the publish command.
-spec publish(rebar_app_info:t(), rebar_state:t(), map()) ->
{ok, rebar_state:t()}.
publish(App, State, Repo) ->
handle_command(App, State, Repo).
-spec format_error(any()) -> iolist().
format_error(bad_command) ->
"Invalid command and/or options provided";
format_error({publish, {unauthorized, _Res}}) ->
"Error publishing : Not authorized";
format_error({publish, {not_found, _Res}}) ->
"Error publishing : Package or Package Version not found";
format_error({revert, {unauthorized, _Res}}) ->
"Error reverting docs : Not authorized";
format_error({revert, {not_found, _Res}}) ->
"Error reverting docs : Package or Package Version not found";
format_error(Reason) ->
rebar3_hex_error:format_error(Reason).
%% ===================================================================
%% Internal Functions
%% ===================================================================
help(dry_run) ->
"Generates docs (if configured) but does not publish the docs. Useful for inspecting docs before publishing.".
publish_apps(Apps, State) ->
lists:foldl(fun(App, {ok, StateAcc}) ->
case handle_command(App, StateAcc) of
{ok, _StateAcc} ->
{ok, StateAcc};
Err ->
throw(Err)
end
end, {ok, State}, Apps).
handle_command(App, State) ->
{ok, Repo} = rebar3_hex_config:repo(State),
handle_command(App, State, Repo).
handle_command(App, State, Repo) ->
{Args, _} = rebar_state:command_parsed_args(State),
case proplists:get_value(revert, Args, undefined) of
undefined ->
do_publish(App, State, Repo);
Vsn ->
do_revert(App,State,Repo, Vsn)
end.
do_publish(App, State, Repo) ->
maybe_gen_docs(State, Repo),
AppDir = rebar_app_info:dir(App),
DocDir = resolve_doc_dir(App),
assert_doc_dir(filename:join(AppDir, DocDir)),
Files = rebar3_hex_file:expand_paths([DocDir], AppDir),
AppDetails = rebar_app_info:app_details(App),
Name = binary_to_list(rebar_app_info:name(App)),
PkgName = rebar_utils:to_list(proplists:get_value(pkg_name, AppDetails, Name)),
OriginalVsn = rebar_app_info:original_vsn(App),
Vsn = rebar_utils:vcs_vsn(App, OriginalVsn, State),
Tarball = PkgName ++ "-" ++ vsn_string(Vsn) ++ "-docs.tar.gz",
ok = erl_tar:create(Tarball, file_list(Files, DocDir), [compressed]),
{ok, Tar} = file:read_file(Tarball),
file:delete(Tarball),
{ok, Config} = rebar3_hex_config:hex_config_write(Repo),
{Args, _} = rebar_state:command_parsed_args(State),
case proplists:get_bool(dry_run, Args) of
true ->
rebar_api:info("--dry-run enabled : will not publish docs.", []),
{ok, State};
false ->
case rebar3_hex_client:publish_docs(Config, rebar_utils:to_binary(PkgName), rebar_utils:to_binary(Vsn), Tar) of
{ok, _} ->
rebar_api:info("Published docs for ~ts ~ts", [PkgName, Vsn]),
{ok, State};
Reason ->
?PRV_ERROR({publish, Reason})
end
end.
vsn_string(<<Vsn/binary>>) ->
binary_to_list(Vsn);
vsn_string(Vsn) ->
Vsn.
do_revert(App, State, Repo, Vsn) ->
{ok, Config} = rebar3_hex_config:hex_config_write(Repo),
AppDetails = rebar_app_info:app_details(App),
Name = rebar_utils:to_list(rebar_app_info:name(App)),
PkgName = rebar_utils:to_list(proplists:get_value(pkg_name, AppDetails, Name)),
case rebar3_hex_client:delete_docs(Config, rebar_utils:to_binary(PkgName), rebar_utils:to_binary(Vsn)) of
{ok, _} ->
rebar_api:info("Successfully deleted docs for ~ts ~ts", [Name, Vsn]),
{ok, State};
Reason ->
?PRV_ERROR({revert, Reason})
end.
%% @doc Returns the directory were docs are to be found
%%
%% The priority for resolution is the following:
%% 1. `doc' entry in the application's `*.app.src'.
%% 2. `dir' entry specified in `edoc_opts'.
%% 3. `"doc"' fallback default value.
-spec resolve_doc_dir(rebar_app_info:t()) -> string().
resolve_doc_dir(AppInfo) ->
AppOpts = rebar_app_info:opts(AppInfo),
EdocOpts = rebar_opts:get(AppOpts, edoc_opts, []),
AppDetails = rebar_app_info:app_details(AppInfo),
Dir = proplists:get_value(dir, EdocOpts, ?DEFAULT_DOC_DIR),
proplists:get_value(doc, AppDetails, Dir).
%% @doc Generates docs based on configuration
%%
%% This function will generate docs according to the following configuration:
%%
%% - `{doc, Options}' as part of your global hex config, where `Options' is a map.
%% - `#{doc => Options}' as part of a specific repo configuration, where `Options' is a map
%%
%% Repo specific config will always override global hex config if the repo in question is
%% the context in which rebar3_hex is operating in.
%%
%% Supported options:
%%
%% - `provider' - This value of this option should be the name of a valid doc
%% provider, such as `edoc'. Note that only `edoc' is supported out of
%% the box with rebar3. Refer to `src/rebar_prv_edoc.erl' as an example
%% of a docs provider in `rebar3', as well as
%% https://rebar3.org/docs/tutorials/building_plugins/ for documentation on
%% creating plugins.
%%
%% Example global config within rebar.config :
%%
%% `{hex, {doc, #{provider => edoc}}}.'
%%
%% Example repo specific config:
%% ```
%% {hex, [
%% {repos, [
%% #{name => <<"my_private_hex">>,
%% repo_url => <<"https://my_private_hex.foo">>,
%% doc => #{provider => edoc}
%% }
%% ]
%% }
%% ]
%% }.
%% '''
maybe_gen_docs(State, Repo) ->
case doc_opts(State, Repo) of
{ok, #{provider := PrvName}} ->
case providers:get_provider(PrvName, rebar_state:providers(State)) of
not_found ->
rebar_api:error("No provider found for ~ts", [PrvName]);
Prv ->
gen_docs(State, Prv)
end;
_ ->
Msg = "No valid hex docs configuration found. Docs will will not be generated",
rebar_api:error(Msg, [])
end.
doc_opts(State, Repo) ->
case Repo of
#{doc := DocOpts} when is_map(DocOpts) ->
{ok, DocOpts};
_ ->
Opts = rebar_state:opts(State),
case proplists:get_value(doc, rebar_opts:get(Opts, hex, []), undefined) of
DocOpts when is_map(DocOpts) -> {ok, DocOpts};
_ -> undefined
end
end.
gen_docs(State, Prv) ->
case providers:do(Prv, State) of
{ok, State} ->
{ok, State};
Err ->
?PRV_ERROR({publish, Err})
end.
-spec assert_doc_dir(string()) -> true.
assert_doc_dir(DocDir) ->
filelib:is_file(DocDir ++ "/index.html") orelse missing_doc_abort(DocDir).
missing_doc_abort(DocDir) ->
rebar_api:abort( "Docs were not published since they "
"couldn't be found in '~s'. "
"Please build the docs and then run "
"`rebar3 hex docs` to publish them."
, [DocDir]
).
file_list(Files, DocDir) ->
[{drop_path(ShortName, [DocDir]), FullName} || {ShortName, FullName} <- Files].
drop_path(File, Path) ->
filename:join(filename:split(File) -- Path).