diff options
Diffstat (limited to 'tools/extract-tr.sh')
-rwxr-xr-x | tools/extract-tr.sh | 327 |
1 files changed, 77 insertions, 250 deletions
diff --git a/tools/extract-tr.sh b/tools/extract-tr.sh index ef0ae60b4..242a81fec 100755 --- a/tools/extract-tr.sh +++ b/tools/extract-tr.sh @@ -1,200 +1,85 @@ #!/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), +main(Paths) -> + Dict = fold_erls( + fun(File, Tokens, Acc) -> + File1 = filename:rootname(filename:basename(File)), + extract_tr(File1, Tokens, Acc) + end, dict:new(), Paths), 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 -> +extract_tr(File, [{'?', _}, {var, _, 'T'}, {'(', Line}|Tokens], Acc) -> + case extract_string(Tokens, "") of + {"", Tokens1} -> + err("~s:~B: Warning: invalid string", [File, Line]), + extract_tr(File, Tokens1, Acc); + {String, Tokens1} -> + extract_tr(File, Tokens1, dict:append(String, {File, Line}, Acc)) + end; +extract_tr(File, [_|Tokens], Acc) -> + extract_tr(File, Tokens, Acc); +extract_tr(_, [], Acc) -> + Acc. + +extract_string([{string, _, S}|Tokens], Acc) -> + extract_string(Tokens, [S|Acc]); +extract_string([{')', _}|Tokens], Acc) -> + {lists:flatten(lists:reverse(Acc)), Tokens}; +extract_string(Tokens, _) -> + {"", Tokens}. + +fold_erls(Fun, State, Paths) -> + Paths1 = fold_paths(Paths), + Total = length(Paths1), + {_, State1} = + lists:foldl( + fun(File, {I, Acc}) -> + io:format(standard_error, + "Progress: ~B% (~B/~B)\r", + [round(I*100/Total), I, Total]), + case tokens(File) of + {ok, Tokens} -> + {I+1, Fun(File, Tokens, Acc)}; + error -> + {I+1, Acc} + end + end, {0, State}, Paths1), + State1. + +fold_paths(Paths) -> + lists:flatmap( + fun(Path) -> + case filelib:is_dir(Path) of + true -> + lists:reverse( + filelib:fold_files( + Path, ".+\.erl\$", false, + fun(File, Acc) -> + [File|Acc] + end, [])); + false -> + [Path] + end + end, Paths). + +tokens(File) -> + case file:read_file(File) of + {ok, Data} -> + case erl_scan:string(binary_to_list(Data)) of + {ok, Tokens, _} -> + {ok, Tokens}; + {error, {_, Module, Desc}, Line} -> + err("~s:~n: Warning: scan error: ~s", + [filename:basename(File), Line, Module:format_error(Desc)]), error end; - _ -> + {error, Why} -> + err("Warning: failed to read file ~s: ~s", + [File, file:format_error(Why)]), 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( @@ -239,68 +124,10 @@ pot_header() -> "\"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\""], + "\"Content-Transfer-Encoding: 8bit\\n\"", + "\"X-Poedit-Basepath: ../../src\\n\"", + "\"X-Poedit-SearchPath-0: .\\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. +err(Format, Args) -> + io:format(standard_error, Format ++ io_lib:nl(), Args). |