Current section

Files

Jump to
rebar3_lfe src r3lfe_prv_confabulate.erl
Raw

src/r3lfe_prv_confabulate.erl

-module(r3lfe_prv_confabulate).
-behaviour(provider).
-export([
init/1,
do/1,
format_error/1
]).
%% For testing
-export([
info/1
]).
-include("r3lfe.hrl").
-define(PROVIDER, confabulate).
-define(DEPS, []).
%%====================================================================
%% Provider API
%%====================================================================
-spec init(rebar_state:t()) -> {ok, rebar_state:t()}.
init(State) ->
Description = "Convert Erlang data files to LFE data files",
Opts = [
{input, $i, "input", string,
"Input Erlang file to convert"},
{output, $o, "output", string,
"Output LFE file (defaults to <input>.lfe)"},
{force, $f, "force", boolean,
"Overwrite output file if it exists"}
],
Provider = providers:create([
{namespace, ?NAMESPACE},
{name, ?PROVIDER},
{module, ?MODULE},
{bare, true},
{deps, ?DEPS},
{example, "rebar3 lfe confabulate --input data.erl"},
{opts, Opts},
{short_desc, Description},
{desc, info(Description)}
]),
{ok, rebar_state:add_provider(State, Provider)}.
-spec do(rebar_state:t()) -> {ok, rebar_state:t()} | {error, string()}.
do(State) ->
?DEBUG("LFE confabulate provider starting", []),
rebar_paths:set_paths([deps, plugins], State),
try
%% Get options
{Opts, _} = rebar_state:command_parsed_args(State),
InputFile = proplists:get_value(input, Opts),
OutputFile = determine_output_file(InputFile, Opts),
Force = proplists:get_value(force, Opts, false),
case InputFile of
undefined ->
{error, format_error(no_input_file)};
_ ->
%% Convert file
?INFO("Converting ~s to ~s", [InputFile, OutputFile]),
case convert_file(InputFile, OutputFile, Force) of
ok ->
?INFO("Conversion successful!", []),
{ok, State};
{error, Reason} ->
{error, format_error(Reason)}
end
end
catch
throw:{error, ErrorReason} ->
{error, format_error(ErrorReason)};
error:ErrorReason:Stack ->
?ERROR("Confabulate failed: ~p", [ErrorReason]),
?DEBUG("Stack trace: ~p", [Stack]),
{error, format_error({confabulate_error, ErrorReason})}
end.
-spec format_error(term()) -> iolist().
format_error(no_input_file) ->
"No input file specified. Use --input option:\n"
" rebar3 lfe confabulate --input data.erl";
format_error({input_not_found, File}) ->
io_lib:format("Input file not found: ~s", [File]);
format_error({output_exists, File}) ->
io_lib:format(
"Output file already exists: ~s~n"
"Use --force to overwrite",
[File]
);
format_error({read_error, File, Reason}) ->
io_lib:format("Failed to read ~s: ~p", [File, Reason]);
format_error({write_error, File, Reason}) ->
io_lib:format("Failed to write ~s: ~p", [File, Reason]);
format_error({confabulate_error, Reason}) ->
io_lib:format("Conversion failed: ~p", [Reason]);
format_error(Reason) ->
io_lib:format("~p", [Reason]).
%%====================================================================
%% Internal functions
%%====================================================================
%% @doc Determine output file path
-spec determine_output_file(file:filename() | undefined, proplists:proplist()) ->
file:filename() | undefined.
determine_output_file(undefined, _Opts) ->
undefined;
determine_output_file(InputFile, Opts) ->
case proplists:get_value(output, Opts) of
undefined ->
%% Default: replace .erl with .lfe
filename:rootname(InputFile, ".erl") ++ ".lfe";
OutputFile ->
OutputFile
end.
%% @doc Convert a single Erlang file to LFE format
-spec convert_file(file:filename(), file:filename(), boolean()) ->
ok | {error, term()}.
convert_file(InputFile, OutputFile, Force) ->
%% Validate input exists
case filelib:is_file(InputFile) of
false ->
{error, {input_not_found, InputFile}};
true ->
%% Check output doesn't exist (unless force)
case filelib:is_file(OutputFile) andalso not Force of
true ->
{error, {output_exists, OutputFile}};
false ->
do_conversion(InputFile, OutputFile)
end
end.
%% @doc Perform the actual conversion
-spec do_conversion(file:filename(), file:filename()) -> ok | {error, term()}.
do_conversion(InputFile, OutputFile) ->
?DEBUG("Reading Erlang file: ~s", [InputFile]),
%% Read Erlang terms via file:consult/1
case file:consult(InputFile) of
{ok, Terms} ->
?DEBUG("Read ~p terms", [length(Terms)]),
write_lfe_file(OutputFile, Terms);
{error, Reason} ->
{error, {read_error, InputFile, Reason}}
end.
%% @doc Write terms as LFE data, one per line
-spec write_lfe_file(file:filename(), [term()]) -> ok | {error, term()}.
write_lfe_file(OutputFile, Terms) ->
try
%% Delete existing file if present
_ = file:delete(OutputFile),
lists:foreach(
fun(Term) ->
ok = append_lfe_term(OutputFile, Term)
end,
Terms
),
ok
catch
error:Reason ->
{error, {write_error, OutputFile, Reason}}
end.
%% @doc Append a single term to the output file in LFE syntax
-spec append_lfe_term(file:filename(), term()) -> ok.
append_lfe_term(OutputFile, Term) ->
LfeTerm = [lfe_io:print1(Term), "\n"],
case file:write_file(OutputFile, LfeTerm, [append]) of
ok ->
ok;
{error, Reason} ->
throw({error, {write_error, OutputFile, Reason}})
end.
-spec info(string()) -> iolist().
info(Description) ->
io_lib:format(
"~n~s~n"
"~n"
"Converts Erlang data files to LFE data files. This is useful for:~n"
" - Converting Erlang configuration to LFE format~n"
" - Generating LFE test data from Erlang~n"
" - Data exchange between Erlang and LFE codebases~n"
" - Migration between languages~n"
"~n"
"The conversion reads Erlang terms and writes them as LFE~n"
"data structures, one per line.~n"
"~n"
"Input Format (Erlang):~n"
" %% data.erl~n"
" {person,\"Alice\",30}.~n"
" {person,\"Bob\",25}.~n"
"~n"
"Output Format (LFE):~n"
" ;; data.lfe~n"
" #(person \"Alice\" 30)~n"
" #(person \"Bob\" 25)~n"
"~n"
"Options:~n"
" --input FILE Input Erlang file (required)~n"
" --output FILE Output LFE file (default: <input>.lfe)~n"
" --force Overwrite existing output file~n"
"~n"
"Examples:~n"
" rebar3 lfe confabulate --input data.erl~n"
" rebar3 lfe confabulate --input data.erl --output config.lfe~n"
" rebar3 lfe confabulate -i data.erl -o config.lfe --force~n"
"~n"
"Note: Only data files are supported, not code modules.~n"
"The input should contain Erlang terms terminated with '.',~n"
"not function definitions or module declarations.~n"
"~n"
"Pair with 'defabulate' for roundtrip conversions:~n"
" rebar3 lfe confabulate --input data.erl~n"
" rebar3 lfe defabulate --input data.lfe~n",
[Description]
).