xmpp.chapril.org-ejabberd/tools/extract-tr.sh

307 lines
7.8 KiB
Bash
Executable File

#!/usr/bin/env escript
%% -*- erlang -*-
%%! -pa ebin
main(Dirs) ->
Txts =
lists:foldl(
fun(Dir, Acc) ->
EbinDir = filename:join(Dir, "ebin"),
SrcDir = filename:join(Dir, "src"),
filelib:fold_files(
EbinDir, ".+\.beam\$", false,
fun(BeamFile, Res) ->
Mod = mod(BeamFile),
ErlFile = filename:join(SrcDir, Mod ++ ".erl"),
case get_forms(BeamFile, ErlFile) of
{ok, BeamForms, ErlForms} ->
process_forms(BeamForms, Mod, application) ++
process_forms(ErlForms, Mod, macro) ++ Res;
_Err ->
Res
end
end, Acc)
end, [], Dirs),
Dict = lists:foldl(
fun({B, Meta}, Acc) ->
dict:update(
binary_to_list(B),
fun(OldMeta) ->
lists:usort([Meta|OldMeta])
end,
[Meta], Acc)
end, dict:new(), Txts),
generate_pot(Dict).
process_forms(Forms, Mod, Type) ->
Tree = erl_syntax:form_list(Forms),
erl_syntax_lib:fold_subtrees(
fun(Form, Acc) ->
case erl_syntax:type(Form) of
function ->
case map(Form, Mod, Type) of
[] ->
Acc;
Vars ->
Vars ++ Acc
end;
_ ->
Acc
end
end, [], Tree).
map(Tree, Mod, Type) ->
Vars = erl_syntax_lib:fold(
fun(Form, Acc) ->
case erl_syntax:type(Form) of
Type when Type == application ->
analyze_app(Form, Mod) ++ Acc;
Type when Type == macro ->
analyze_macro(Form, Mod) ++ Acc;
_ ->
Acc
end
end, [], Tree),
Bins = lists:flatmap(
fun({Var, Pos}) when is_atom(Var) ->
Res = erl_syntax_lib:fold(
fun(Form, Acc) ->
case process_match_expr(
Form, Var, Mod) of
{ok, Binary, NewPos} ->
[{Binary, NewPos}|Acc];
error ->
Acc
end
end, [], Tree),
case Res of
[] ->
log("~s:~p: unresolved variable: ~s~n",
[Mod, Pos, Var]);
_ ->
ok
end,
Res;
({Var, Pos}) when is_binary(Var) ->
[{Var, Pos}]
end, lists:usort(Vars)),
[{B, {Mod, Pos}} || {B, Pos} <- Bins, B /= <<"">>].
process_match_expr(Form, Var, Mod) ->
case erl_syntax:type(Form) of
match_expr ->
Pattern = erl_syntax:match_expr_pattern(Form),
Body = erl_syntax:match_expr_body(Form),
{V, Expr} =
case {erl_syntax:type(Pattern), erl_syntax:type(Body)} of
{variable, _} ->
{erl_syntax:variable_name(Pattern), Body};
{_, variable} ->
{erl_syntax:variable_name(Body), Pattern};
_ ->
{'', none}
end,
Text = maybe_extract_tuple(Expr),
if V == Var ->
Pos = erl_syntax:get_pos(Text),
try {ok, erl_syntax:concrete(Text), Pos}
catch _:_ ->
case catch erl_syntax_lib:analyze_application(Text) of
{_M, {Fn, 1}} when Fn == format_error;
Fn == io_format_error ->
error;
_ ->
log("~s:~p: not a binary: ~s~n",
[Mod, Pos, erl_prettypr:format(Text)]),
{ok, <<>>, Pos}
end
end;
true ->
error
end;
_ ->
error
end.
maybe_extract_tuple(none) ->
none;
maybe_extract_tuple(Form) ->
try
tuple = erl_syntax:type(Form),
[Text, _] = erl_syntax:tuple_elements(Form),
Text
catch _:{badmatch, _} ->
Form
end.
analyze_app(Form, Mod) ->
try
{M, {F, A}} = erl_syntax_lib:analyze_application(Form),
Args = erl_syntax:application_arguments(Form),
Txt = case {M, atom_to_list(F), A, Args} of
{xmpp, "err_" ++ _, 2, [T|_]} -> T;
{xmpp, "serr_" ++ _, 2, [T|_]} -> T;
{xmpp, "mk_text", 2, [T|_]} -> T;
{xmpp_tr, "tr", 2, [_,T|_]} -> T;
{translate, "translate", 2, [_,T|_]} -> T
end,
Pos = erl_syntax:get_pos(Txt),
case erl_syntax:type(Txt) of
binary ->
try [{erl_syntax:concrete(Txt), Pos}]
catch _:_ ->
Pos = erl_syntax:get_pos(Txt),
log("~s:~p: not a binary: ~s~n",
[Mod, Pos, erl_prettypr:format(Txt)]),
[]
end;
variable ->
[{erl_syntax:variable_name(Txt), Pos}];
application ->
Vars = sets:to_list(erl_syntax_lib:variables(Txt)),
case Vars of
[Var] ->
[{Var, Pos}];
[_|_] ->
log("Too many variables: ~p~n", [Vars]),
[];
[] ->
[]
end;
_ ->
[]
end
catch _:{badmatch, _} ->
[];
_:{case_clause, _} ->
[]
end.
analyze_macro(Form, Mod) ->
try
Name = erl_syntax:macro_name(Form),
variable = erl_syntax:type(Name),
'T' = erl_syntax:variable_name(Name),
[Txt] = erl_syntax:macro_arguments(Form),
string = erl_syntax:type(Txt),
Pos = erl_syntax:get_pos(Txt),
try [{list_to_binary(erl_syntax:string_value(Txt)), Pos}]
catch _:_ ->
log("~s:~p: not a binary: ~s~n",
[Mod, Pos, erl_prettypr:format(Txt)]),
[]
end
catch _:{badmatch, _} ->
[]
end.
generate_pot(Dict) ->
io:format("~s~n~n", [pot_header()]),
lists:foreach(
fun({Msg, Location}) ->
S1 = format_location(Location),
S2 = format_msg(Msg),
io:format("~smsgstr \"\"~n~n", [S1 ++ S2])
end, lists:keysort(1, dict:to_list(Dict))).
format_location([A, B, C|T]) ->
format_location_list([A,B,C]) ++ format_location(T);
format_location([A, B|T]) ->
format_location_list([A,B]) ++ format_location(T);
format_location([A|T]) ->
format_location_list([A]) ++ format_location(T);
format_location([]) ->
"".
format_location_list(L) ->
"#: " ++ string:join(
lists:map(
fun({File, Pos}) ->
io_lib:format("~s:~B", [File, Pos])
end, L),
" ") ++ io_lib:nl().
format_msg(Bin) ->
io_lib:format("msgid \"~s\"~n", [escape(Bin)]).
escape(Bin) ->
lists:map(
fun($") -> "\\\"";
(C) -> C
end, binary_to_list(iolist_to_binary(Bin))).
pot_header() ->
string:join(
["msgid \"\"",
"msgstr \"\"",
"\"Project-Id-Version: 15.11.127\\n\"",
"\"X-Language: Language Name\\n\"",
"\"Last-Translator: Translator name and contact method\\n\"",
"\"MIME-Version: 1.0\\n\"",
"\"Content-Type: text/plain; charset=UTF-8\\n\"",
"\"Content-Transfer-Encoding: 8bit\\n\""],
io_lib:nl()).
mod(Path) ->
filename:rootname(filename:basename(Path)).
log(Format, Args) ->
io:format(standard_error, Format, Args).
get_forms(BeamFile, ErlFile) ->
try
{ok, BeamForms} = get_beam_forms(BeamFile),
{ok, ErlForms} = get_erl_forms(ErlFile),
{ok, BeamForms, ErlForms}
catch _:{badmatch, error} ->
error
end.
get_beam_forms(File) ->
case beam_lib:chunks(File, [abstract_code]) of
{ok, {_, List}} ->
case lists:keyfind(abstract_code, 1, List) of
{abstract_code, {raw_abstract_v1, Abstr}} ->
{ok, Abstr};
_Err ->
log("failed to get abstract code from ~s~n", [File]),
error
end;
Err ->
log("failed to read chunks from ~s: ~p~n", [File, Err]),
error
end.
get_erl_forms(Path) ->
case file:open(Path, [read]) of
{ok, Fd} ->
parse(Path, Fd, 1, []);
{error, Why} ->
log("failed to read ~s: ~s~n", [Path, file:format_error(Why)]),
error
end.
parse(Path, Fd, Line, Acc) ->
{ok, Pos} = file:position(Fd, cur),
case epp_dodger:parse_form(Fd, Line) of
{ok, Form, NewLine} ->
{ok, NewPos} = file:position(Fd, cur),
{ok, RawForm} = file:pread(Fd, Pos, NewPos - Pos),
file:position(Fd, {bof, NewPos}),
AnnForm = erl_syntax:set_ann(Form, RawForm),
parse(Path, Fd, NewLine, [AnnForm|Acc]);
{eof, _} ->
{ok, NewPos} = file:position(Fd, cur),
if NewPos > Pos ->
{ok, RawForm} = file:pread(Fd, Pos, NewPos - Pos),
Form = erl_syntax:text(""),
AnnForm = erl_syntax:set_ann(Form, RawForm),
{ok, lists:reverse([AnnForm|Acc])};
true ->
{ok, lists:reverse(Acc)}
end;
Err ->
log("failed to parse ~s: ~p~n", [Path, Err]),
error
end.