aboutsummaryrefslogtreecommitdiff
path: root/tools/extract-tr.sh
diff options
context:
space:
mode:
Diffstat (limited to 'tools/extract-tr.sh')
-rwxr-xr-xtools/extract-tr.sh327
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).