Current section
Files
Jump to
Current section
Files
src/tdata.erl
-module(tdata).
%% API exports
-export([
start/0,
start/2,
stop/0,
set_key/2,
get_key/1,
transform_all/2,
transform_files/3,
transform_file/3
]).
-export_type([
transform_define/0,
transform_defines/0,
global_config/0,
transform_fun_config/0,
output_result/0,
output_results/0
]).
-type input_file() :: file:filename().
-type output_file() :: file:filename().
-type input_file_opts() :: any().
-type input_file_define() ::
{input_file(), input_file_opts()} |
#{file := input_file(), opts := input_file_opts()}.
-type transform_define() :: #{
input_file_defines := input_file_define() | [input_file_define()] | map(),
output_file := output_file(),
transform_fun := function(),
tpl_type => tdata_render:tpl_type(),
tpl_file => file:filename()
}.
-type transform_defines() :: [transform_define()].
-type global_config() :: #{
input_dir := file:filename(),
output_dir := file:filename(),
template_dir => file:filename()
}.
-type transform_fun_config() :: any().
-type output_error_status() :: any().
-type output_status() :: ok | skipped | output_error_status().
-type output_result() :: {output_file(), output_status()}.
-type output_results() :: [output_result()].
-callback transform_defines() -> transform_defines().
%%====================================================================
%% API functions
%%====================================================================
start() ->
application:ensure_all_started(?MODULE).
start(_, _) ->
tdata_loader:start(),
Pid = spawn_link(fun() -> receive shutdown -> ok end end),
{ok, Pid}.
-spec stop() -> ok.
stop() ->
tdata_loader:stop(),
ok.
set_key(Key, Value) ->
application:set_env(?MODULE, Key, Value).
get_key(Key) ->
application:get_env(?MODULE, Key, undefined).
-spec transform_all(global_config(), transform_fun_config()) -> [output_results()].
transform_all(Config, TransformConfig) ->
[transform_files(HandleModule, Config, TransformConfig) ||
HandleModule <- tdata_loader:all_attr_modules(behavior, [?MODULE])].
-spec transform_files(HandleModule :: module() | transform_define() | transform_defines(),
global_config(), transform_fun_config()) -> output_results().
transform_files(HandleModule, Config, TransformConfig) when is_atom(HandleModule) ->
TransformDefines = HandleModule:transform_defines(),
transform_files(TransformDefines, Config, TransformConfig);
transform_files(TransformDefines, Config, TransformConfig) when is_list(TransformDefines) ->
[transform_file(TransformDefine, Config, TransformConfig)
|| TransformDefine <- TransformDefines];
transform_files(TransformDefine, Config, TransformConfig) when is_map(TransformDefine) ->
transform_file(TransformDefine, Config, TransformConfig).
-spec transform_file(transform_define(), global_config(), transform_fun_config()) -> output_result().
transform_file(TransformDefine, Config, TransformConfig) ->
case check_transform_define(TransformDefine) of
ok ->
#{input_file_defines := InputFileDefines, output_file := OutputFile0} = TransformDefine,
#{input_dir := InputDir, output_dir := OutputDir} = Config,
InputFiles = get_input_files_from_defines(InputFileDefines, InputDir),
OutputFile = filename:join(OutputDir, OutputFile0),
ok = filelib:ensure_dir(OutputFile),
{TplType, TplFile} = get_tpl_info(TransformDefine, Config),
case is_all_file(InputFiles) andalso is_need_transform(InputFiles, OutputFile, TplFile) of
true ->
transform_file_do(InputFileDefines, OutputFile0, OutputFile, TransformDefine,
InputDir, TplType, TplFile, TransformConfig);
false ->
{OutputFile0, skipped}
end;
Result -> Result
end.
%%====================================================================
%% Internal functions
%%====================================================================
transform_file_do(InputFileDefines, OutputFile0, OutputFile, TransformDefine,
InputDir, TplType, TplFile, TransformConfig) ->
InputDataList = tdata_loader:load_input_files(InputFileDefines, InputDir, []),
#{transform_fun := TransformFun} = TransformDefine,
Data = TransformFun(OutputFile0, InputDataList, TransformConfig),
SourceFiles = unicode:characters_to_binary(string:join([InputFile || {InputFile, _} <- InputDataList], ",")),
HeaderComments = <<"%% Automatically generated, do not edit\n%% Source Files: ", SourceFiles/binary, "\n">>,
case tdata_render:render(Data, TplType, TplFile, OutputFile, HeaderComments) of
ok -> {OutputFile0, ok};
Err -> {OutputFile0, Err}
end.
check_transform_define(#{output_file := OutputFile0} = TransformDefine) ->
case maps:get(input_file_defines, TransformDefine, miss) of
miss ->
{OutputFile0, miss_input_file_defines};
_ ->
case maps:get(transform_fun, TransformDefine, miss) of
miss ->
{OutputFile0, miss_transform_fun};
_ ->
ok
end
end;
check_transform_define(_TransformDefine) ->
error(miss_output_file).
get_input_files_from_defines(#{file := InputFile}, InputDir) ->
[filename:join(InputDir, InputFile)];
get_input_files_from_defines({InputFile, _}, InputDir) ->
[filename:join(InputDir, InputFile)];
get_input_files_from_defines(InputFileDefines, InputDir) when is_list(InputFileDefines) ->
[get_input_files_from_defines(InputFileDefine, InputDir) || InputFileDefine <- InputFileDefines];
get_input_files_from_defines(InputFileDefines, InputDir) when is_map(InputFileDefines) ->
maps:fold(
fun(InputFile, _Opts, Acc) ->
[filename:join(InputDir, InputFile) | Acc]
end, [], InputFileDefines);
get_input_files_from_defines(InputFileDefines, _InputDir) ->
error({error_input_file_defines, InputFileDefines}).
get_tpl_info(TransformDefine, Config) ->
case maps:get(tpl_file, TransformDefine, miss) of
miss ->
{undefined, undefined};
TplFile0 ->
TplType0 = get_tpl_type(TplFile0, TransformDefine),
TplDir = get_tpl_dir(TransformDefine, Config),
{TplType0, filename:join(TplDir, TplFile0)}
end.
get_tpl_type(TplFile, TransformDefine) ->
case maps:get(tpl_type, TransformDefine, auto) of
auto ->
case filename:extension(TplFile) of
".tpl" -> mustache;
".dtl" -> dtl;
_ -> unknow
end;
TplType -> TplType
end.
get_tpl_dir(TransformDefine, Config) ->
case maps:get(template_dir, TransformDefine, miss) of
miss ->
maps:get(template_dir, Config);
TplDir -> TplDir
end.
is_all_file([]) -> true;
is_all_file([InputFile | InputFiles]) ->
filelib:is_file(InputFile) andalso not filelib:is_dir(InputFile) andalso is_all_file(InputFiles).
is_need_transform(InputFiles, OutputFile, TplFile0) ->
Input = lists:max([filelib:last_modified(InputFile) || InputFile <- InputFiles]),
Output = filelib:last_modified(OutputFile),
case TplFile0 of
undefined ->
Input > Output;
TplFile ->
Tpl = filelib:last_modified(TplFile),
Input > Output orelse Tpl > Output
end.