233 lines
5.6 KiB
Bash
233 lines
5.6 KiB
Bash
|
#!/usr/bin/env escript
|
||
|
%% -*- erlang -*-
|
||
|
%%! -pa ebin
|
||
|
|
||
|
main([Dir]) ->
|
||
|
Txts =
|
||
|
filelib:fold_files(
|
||
|
Dir, ".+\.beam\$", false,
|
||
|
fun(FileIn, Res) ->
|
||
|
case get_forms(FileIn) of
|
||
|
{ok, Forms} ->
|
||
|
Tree = erl_syntax:form_list(Forms),
|
||
|
Mod = mod(FileIn),
|
||
|
erl_syntax_lib:fold_subtrees(
|
||
|
fun(Form, Acc) ->
|
||
|
case erl_syntax:type(Form) of
|
||
|
function ->
|
||
|
case map(Form, Mod) of
|
||
|
[] ->
|
||
|
Acc;
|
||
|
Vars ->
|
||
|
Vars ++ Acc
|
||
|
end;
|
||
|
_ ->
|
||
|
Acc
|
||
|
end
|
||
|
end, [], Tree) ++ Res;
|
||
|
_Err ->
|
||
|
Res
|
||
|
end
|
||
|
end, []),
|
||
|
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).
|
||
|
|
||
|
map(Tree, Mod) ->
|
||
|
Vars = erl_syntax_lib:fold(
|
||
|
fun(Form, Acc) ->
|
||
|
case erl_syntax:type(Form) of
|
||
|
application ->
|
||
|
analyze_app(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;
|
||
|
{translate, "translate", 2, [_,T|_]} -> T;
|
||
|
{translate, "mark", 1, [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.
|
||
|
|
||
|
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)) ++ ".erl".
|
||
|
|
||
|
log(Format, Args) ->
|
||
|
io:format(standard_error, Format, Args).
|
||
|
|
||
|
get_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};
|
||
|
_ ->
|
||
|
error
|
||
|
end;
|
||
|
_ ->
|
||
|
error
|
||
|
end.
|