Skip to content

Commit

Permalink
Merge pull request #22 from vkatsuba/allow-doclet-and-layout-module-c…
Browse files Browse the repository at this point in the history
…haining

Allow `doclet` and `layout` modules chaining
  • Loading branch information
dumbbell authored Jun 2, 2022
2 parents 377765a + ad14217 commit e338751
Show file tree
Hide file tree
Showing 3 changed files with 90 additions and 22 deletions.
3 changes: 2 additions & 1 deletion elvis.config
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@
{elvis_style, function_naming_convention, disable},
{elvis_style, dont_repeat_yourself, disable},
{elvis_style, no_spec_with_records, disable},
{elvis_style, no_block_expressions, disable}]},
{elvis_style, no_block_expressions, disable},
{elvis_style, invalid_dynamic_call, disable}]},
#{dirs => ["test/**"],
filter => "*.erl",
ruleset => erl_files},
Expand Down
60 changes: 54 additions & 6 deletions src/rebar3_edoc_extensions_prv.erl
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,14 @@
{rebar_state, add_provider, 2},
{rebar_state, command_parsed_args, 1}]).

-if(?OTP_RELEASE >= 24).
-dialyzer({no_underspecs, [chain_edoc_backends/1,
chain_edoc_backends/2]}).
-else.
-dialyzer({nowarn_function, [chain_edoc_backends/1,
chain_edoc_backends/2]}).
-endif.

-define(PROVIDER, edoc).
-define(DEPS, [compile]).
-define(GITHUB_URL_BASE, "https://raw.githubusercontent.com").
Expand Down Expand Up @@ -106,12 +114,7 @@ do(State) ->
State1 =
case Ret of
ok ->
%% FIXME: Clear conflicting options.
EdocOpts1 =
[{xml_export, rebar3_edoc_extensions_export},
{layout, rebar3_edoc_extensions_wrapper},
{doclet, rebar3_edoc_extensions_wrapper}
| EdocOptsWithCSS],
EdocOpts1 = chain_edoc_backends(EdocOptsWithCSS),
?DEBUG("Overriden edoc options: ~p", [EdocOpts1]),
rebar_state:set(State, edoc_opts, EdocOpts1);
{app_failed, AppName, Reason} ->
Expand Down Expand Up @@ -267,3 +270,48 @@ generate_wrapping_css(DocDir, Stylesheets) ->
Filename = filename:join(DocDir, ?GENERATED_CSS),
ok = file:write_file(Filename, Content),
ok.

-spec chain_edoc_backends(EdocOpts) -> EdocOpts when
EdocOpts :: [tuple()].
chain_edoc_backends(EdocOpts) ->
Options = [xml_export,
layout,
doclet],
chain_edoc_backends(Options, EdocOpts).

-spec chain_edoc_backends(Options, EdocOpts) -> EdocOpts when
Options :: [xml_export | doclet | layout],
EdocOpts :: [tuple()].
chain_edoc_backends([xml_export | Rest], EdocOpts) ->
%% For `xml_export', we override the module, regardless of what the user
%% provided. I don't know if we can support module chaining here as there
%% is no options passed to the module functions. Perhaps we could use a
%% `persistent_term', but anyway, I don't have a use case currently to
%% test this.
EdocOpts1 = lists:keystore(
xml_export, 1, EdocOpts,
{xml_export, rebar3_edoc_extensions_export}),
chain_edoc_backends(Rest, EdocOpts1);
chain_edoc_backends([Option | Rest], EdocOpts)
when Option =:= doclet orelse Option =:= layout ->
%% For d`doclet' and `layout', we override the user-configured module by
%% our own. However, we store the former in another option. When
%% processing the documentation, our own module will called the
%% user-configured module and patch its output.
%%
%% If there is no user-configured module, we default to `edoc_doclet' and
%% `edoc_layout'.
DefaultMod = list_to_atom(io_lib:format("edoc_~s", [Option])),
ChainedMod = proplists:get_value(Option, EdocOpts, DefaultMod),
ChainedOption = list_to_atom(
io_lib:format("chained_~s", [Option])),
EdocOpts1 = lists:keystore(
Option, 1, EdocOpts,
{ChainedOption, ChainedMod}),

EdocOpts2 = lists:keystore(
Option, 1, EdocOpts1,
{Option, rebar3_edoc_extensions_wrapper}),
chain_edoc_backends(Rest, EdocOpts2);
chain_edoc_backends([], EdocOpts) ->
EdocOpts.
49 changes: 34 additions & 15 deletions src/rebar3_edoc_extensions_wrapper.erl
Original file line number Diff line number Diff line change
Expand Up @@ -10,30 +10,36 @@

-include_lib("edoc/include/edoc_doclet.hrl").

%% doclet's Ctxt is a #context{} record in Erlang 23 and #doclet_context{} in
%% Erlang 24+.
-if(?OTP_RELEASE >= 24).
-define(RECORD, doclet_context).
-else.
-define(RECORD, context).
-endif.

-include("rebar3_edoc_extensions.hrl").

-export([module/2, overview/2, type/1]).
%% `edoc_layout` API.
-export([module/2, overview/2]).
%% `edoc_doclet` API.
-export([run/2]).

-spec module(term(), term()) -> term().
-spec module(term(), list()) -> term().
module(Element, Options) ->
edoc_layout:module(Element, Options).
LayoutMod = get_chained_mod(layout, Options),
LayoutMod:module(Element, Options).

-spec overview(term(), term()) -> [binary() | list()].
-spec overview(term(), list()) -> [binary() | list()].
overview(Element, Options) ->
Overview = edoc_layout:overview(Element, Options),
LayoutMod = get_chained_mod(layout, Options),
Overview = LayoutMod:overview(Element, Options),
patch_html(Overview).

-spec type(term()) -> term().
type(Element) ->
edoc_layout:type(Element).

-spec run(#doclet_gen{}, tuple()) -> ok | no_return().
run(#doclet_gen{app = App} = Cmd, Ctxt) ->
ok = edoc_doclet:run(Cmd, Ctxt),
%% Ctxt is a #context{} record in Erlang 23 and #doclet_context{} in Erlang
%% 24. The directory is the second field in that record in both cases.
Dir = element(2, Ctxt),
-spec run(#doclet_gen{}, #?RECORD{}) -> ok | no_return().
run(#doclet_gen{app = App} = Cmd, #?RECORD{dir = Dir} = Ctxt) ->
DocletMod = get_chained_mod(doclet, Ctxt),
ok = DocletMod:run(Cmd, Ctxt),
File = filename:join(Dir, "modules-frame.html"),
{ok, Content0} = file:read_file(File),
Content1 = add_toc(App, Content0, Dir),
Expand All @@ -43,6 +49,19 @@ run(#doclet_gen{app = App} = Cmd, Ctxt) ->
{error, Reason} -> exit({error, Reason})
end.

-spec get_chained_mod(Option, Options | Ctxt) -> Value when
Option :: doclet | layout,
Options :: [tuple()],
Ctxt :: #?RECORD{},
Value :: any().
get_chained_mod(doclet, Options) when is_list(Options) ->
proplists:get_value(chained_doclet, Options);
get_chained_mod(layout, Options) when is_list(Options) ->
proplists:get_value(chained_layout, Options);
get_chained_mod(Option, Ctxt) when is_tuple(Ctxt) ->
#?RECORD{opts = Options} = Ctxt,
get_chained_mod(Option, Options).

-spec patch_html(list()) -> list().
patch_html(Html) ->
Html2 = re:replace(
Expand Down

0 comments on commit e338751

Please sign in to comment.