2017-09-23 23:08:01 +02:00
|
|
|
#!/usr/bin/env escript
|
|
|
|
%% -*- erlang -*-
|
|
|
|
|
2019-06-22 16:08:45 +02:00
|
|
|
main(Paths) ->
|
|
|
|
Dict = fold_erls(
|
|
|
|
fun(File, Tokens, Acc) ->
|
|
|
|
File1 = filename:rootname(filename:basename(File)),
|
|
|
|
extract_tr(File1, Tokens, Acc)
|
|
|
|
end, dict:new(), Paths),
|
2017-09-23 23:08:01 +02:00
|
|
|
generate_pot(Dict).
|
|
|
|
|
2019-06-22 16:08:45 +02:00
|
|
|
extract_tr(File, [{'?', _}, {var, _, 'T'}, {'(', Line}|Tokens], Acc) ->
|
2019-06-24 20:09:29 +02:00
|
|
|
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;
|
2019-06-22 16:08:45 +02:00
|
|
|
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]);
|
2019-06-24 20:09:29 +02:00
|
|
|
extract_string([{')', _}|Tokens], Acc) ->
|
|
|
|
{lists:flatten(lists:reverse(Acc)), Tokens};
|
|
|
|
extract_string(Tokens, _) ->
|
|
|
|
{"", Tokens}.
|
2019-06-22 16:08:45 +02:00
|
|
|
|
|
|
|
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)]),
|
2017-09-23 23:08:01 +02:00
|
|
|
error
|
|
|
|
end;
|
2019-06-22 16:08:45 +02:00
|
|
|
{error, Why} ->
|
|
|
|
err("Warning: failed to read file ~s: ~s",
|
|
|
|
[File, file:format_error(Why)]),
|
2017-09-23 23:08:01 +02:00
|
|
|
error
|
|
|
|
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}) ->
|
2019-02-22 11:57:47 +01:00
|
|
|
io_lib:format("~s.erl:~B", [File, Pos])
|
2017-09-23 23:08:01 +02:00
|
|
|
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\"",
|
2019-06-22 17:07:36 +02:00
|
|
|
"\"Content-Transfer-Encoding: 8bit\\n\"",
|
|
|
|
"\"X-Poedit-Basepath: ../../src\\n\"",
|
|
|
|
"\"X-Poedit-SearchPath-0: .\\n\""],
|
2017-09-23 23:08:01 +02:00
|
|
|
io_lib:nl()).
|
|
|
|
|
2019-06-22 16:08:45 +02:00
|
|
|
err(Format, Args) ->
|
|
|
|
io:format(standard_error, Format ++ io_lib:nl(), Args).
|