src/rebar3_erlquery_prv.erl

-module(rebar3_erlquery_prv).

-export([init/1, do/1, format_error/1]).

-define(PROVIDER, compile).
-define(DEPS, [{default, compile}]).

%% ===================================================================
%% Public API
%% ===================================================================
-spec init(rebar_state:t()) -> {ok, rebar_state:t()}.
init(State) ->
    Provider =
        providers:create([{name, ?PROVIDER},            % The 'user friendly' name of the task
                          {module, ?MODULE},            % The module implementation of the task
                          {namespace, erlquery},
                          {bare,
                           false},                 % The task can be run by the user, always true
                          {deps, ?DEPS},                % The list of dependencies
                          {example, "Config in rebar.config"}, % How to use the plugin
                          {opts, []},                   % list of options understood by the plugin
                          {short_desc, "A rebar plugin for compiling erlquery files"},
                          {desc, "A rebar plugin for compiling erlquery files"}]),
    {ok, rebar_state:add_provider(State, Provider)}.

-spec do(rebar_state:t()) -> {ok, rebar_state:t()} | {error, string()}.
do(State) ->
    rebar_api:info("Running erlquery...", []),
    Apps =
        case rebar_state:current_app(State) of
            undefined ->
                rebar_state:project_apps(State);
            AppInfo ->
                [AppInfo]
        end,

    [begin
         Opts = rebar_app_info:opts(AppInfo),
         Dir = rebar_app_info:dir(AppInfo),
         OutDir = rebar_app_info:out_dir(AppInfo),
         EbinDir = rebar_app_info:ebin_dir(AppInfo),

         filelib:ensure_dir(
             filename:join(EbinDir, "dummy.beam")),
         filelib:ensure_dir(
             filename:join(OutDir, "dummy.erl")),

         TargetExt = ".erl",
         SourceExt = ".erlq",
         rebar_base_compiler:run(Opts,
                                 [],
                                 Dir,
                                 SourceExt,
                                 OutDir,
                                 TargetExt,
                                 fun(S, T, _C) -> do_compile(S, T, Dir, EbinDir) end)
     end
     || AppInfo <- Apps],
    {ok, State}.

-spec format_error(any()) -> iolist().
format_error(Reason) ->
    io_lib:format("~p", [Reason]).

do_compile(Source, Target, SourceDir, BinDir) ->
    InputFile = filename:join(SourceDir, Source),
    case erlq_codegen(InputFile) of
        {ok, EBin} ->
            NewTarget = bin_filename(Target),
            NewPath = filename:join(BinDir, NewTarget),
            file:write_file(NewPath, EBin);
        {error, _Err} = E ->
            {error, ferror(E), []}
    end.

-spec erlq_codegen(binary()) -> {ok, binary()} | {error, any()}.
erlq_codegen(InputFile) ->
    case file:read_file(InputFile) of
        {ok, SourceBin} ->
            case erlquery:parse(SourceBin) of
                {ok, Config} ->
                    case erlquery:codegen(Config) of
                        {ok, Forms} ->
                            case compile:forms(Forms, [debug_info]) of
                                {ok, _ModuleName, EBin} ->
                                    {ok, EBin};
                                E ->
                                    {error, E}
                            end;
                        {error, _Err} = E ->
                            E
                    end;
                {error, _Err} = E ->
                    E
            end;
        {error, _Err} = E ->
            E
    end.

-spec bin_filename(binary()) -> string().
bin_filename(Target) ->
    string:concat(
        filename:rootname(
            filename:basename(Target), ".erl"),
        ".beam").

-spec ferror({error, {atom(), binary()}} |
             {error, {atom(), {integer(), binary()}}} |
             {error, {invalid_query_arity, {integer(), binary()}}} |
             {error, any()} |
             {error, {atom(), any()}}) ->
                [string()].
ferror({error, {invalid_query_arity, {Line, <<Data/binary>>}}}) ->
    [io_lib:format("~nerlquery compiler error - invalid query arity, expected 2 - line ~p:~n~n\t~s~n",
                   [Line, Data])];
ferror({error, {Reason, {Line, <<Data/binary>>}}}) ->
    [io_lib:format("~nerlquery compiler error - ~p - line ~p:~n~n\t~s~n",
                   [Reason, Line, Data])];
ferror({error, {Reason, <<Data/binary>>}}) ->
    [io_lib:format("erlquery compiler error - ~p:~n~n\t~s~n", [Reason, Data])];
ferror({error, {Reason, Data}}) ->
    [io_lib:format("erlquery compiler error - ~p:~n~n\t~p~n", [Reason, Data])];
ferror({error, Err}) ->
    [io_lib:format("erlquery compiler error:~n~p", [Err])].