/modules/mod_translation/support/translation_scan.erl

https://code.google.com/p/zotonic/ · Erlang · 174 lines · 128 code · 25 blank · 21 comment · 5 complexity · 9aa2dcbcbf4ba7650f59ffc104454d09 MD5 · raw file

  1. % @author Marc Worrell <marc@worrell.nl>
  2. %% @copyright 2010,2011 Marc Worrell
  3. %% Date: 2010-05-19
  4. %% @doc Parse templates / erlang files in modules, extract all translations.
  5. %% Copyright 2010,2011 Marc Worrell
  6. %%
  7. %% Licensed under the Apache License, Version 2.0 (the "License");
  8. %% you may not use this file except in compliance with the License.
  9. %% You may obtain a copy of the License at
  10. %%
  11. %% http://www.apache.org/licenses/LICENSE-2.0
  12. %%
  13. %% Unless required by applicable law or agreed to in writing, software
  14. %% distributed under the License is distributed on an "AS IS" BASIS,
  15. %% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  16. %% See the License for the specific language governing permissions and
  17. %% limitations under the License.
  18. -module(translation_scan).
  19. -author("Marc Worrell <marc@worrell.nl>").
  20. -export([scan/1, scan_file/2]).
  21. -include("zotonic.hrl").
  22. scan(Context) ->
  23. ModTemplates = [{Path, Files} || {_M, {Path, Files}} <- z_module_indexer:all(template, Context)],
  24. ModErlang = [{Path, Files} || {_M, {Path, Files}} <- z_module_indexer:all(erlang, Context)],
  25. Combined = lists:foldl(
  26. fun({Path, Files}, Acc) ->
  27. [{Path, Files ++ proplists:get_value(Path, ModTemplates, [])}
  28. |proplists:delete(Path, Acc)]
  29. end, [], ModErlang),
  30. [ scan_module(Mod) || Mod <- Combined ].
  31. scan_module({Path, Templates}) ->
  32. {Path, dedupl(lists:flatten([ scan_file(filename:extension(File), File) || {_BaseName,File} <- Templates ]))}.
  33. dedupl(Trans) ->
  34. Dict = dict:new(),
  35. List = dict:to_list(insert(Trans, Dict)),
  36. [ Tr || {_Key,Tr} <- List ].
  37. insert([], Dict) ->
  38. Dict;
  39. insert([{Text, Args, Loc}|Trans], Dict) ->
  40. case dict:find(Text, Dict) of
  41. {ok, {_Text, Args0, Loc0}} ->
  42. Dict1 = dict:store(Text, {Text, merge_args(Args,Args0), [Loc|Loc0]}, Dict),
  43. insert(Trans, Dict1);
  44. error ->
  45. Dict1 = dict:store(Text, {Text, Args, [Loc]}, Dict),
  46. insert(Trans, Dict1)
  47. end.
  48. merge_args([], Args) -> Args;
  49. merge_args(Args, []) -> Args;
  50. merge_args([{Lang,Text}|Rest], Args) ->
  51. case proplists:get_value(Lang, Args) of
  52. undefined -> merge_args(Rest, [{Lang,Text}|Args]);
  53. _ -> merge_args(Rest, Args)
  54. end.
  55. %% @doc Parse the Erlang module. Extract all translation tags.
  56. scan_file(".erl", File) ->
  57. {ok, Path} = zotonic_app:get_path(),
  58. case epp:open(File, [filename:join(Path, "include")]) of
  59. {ok, Epp} ->
  60. parse_erl(File, Epp);
  61. {error, Reason} ->
  62. ?ERROR("POT generation, erlang error in ~p: ~p~n", [File, Reason]),
  63. []
  64. end;
  65. %% @doc Parse the template in the file. Extract all translation tags.
  66. scan_file(".tpl", File) ->
  67. case parse(File) of
  68. {ok, ParseTree} ->
  69. extract(ParseTree, [], File);
  70. {error, Reason} ->
  71. ?ERROR("POT generation, template error in ~p: ~p~n", [File, Reason]),
  72. []
  73. end.
  74. parse(File) when is_list(File) ->
  75. case catch file:read_file(File) of
  76. {ok, Data} ->
  77. case parse(Data) of
  78. {ok, Val} ->
  79. {ok, Val};
  80. Err ->
  81. Err
  82. end;
  83. Error ->
  84. {error, io_lib:format("reading ~p failed (~p)", [File, Error])}
  85. end;
  86. parse(Data) when is_binary(Data) ->
  87. case erlydtl_scanner:scan(binary_to_list(Data)) of
  88. {ok, Tokens} ->
  89. erlydtl_parser:parse(Tokens);
  90. Err ->
  91. Err
  92. end.
  93. %% @doc Extract all translation tags from the parse tree.
  94. extract(ParseTree, Acc, F) when is_list(ParseTree) ->
  95. lists:foldl(fun(Tree,A) -> extract(Tree, A, F) end, Acc, ParseTree);
  96. extract({trans, {trans_text, {_File, Line,_Col}, Text}}, Acc, F) ->
  97. [{z_string:trim(Text), [], {F,Line}}|Acc];
  98. extract({trans_literal, {_File, Line,_Col}, Text}, Acc, F) ->
  99. [{Text, [], {F,Line}}|Acc];
  100. extract({trans_ext, {string_literal, {_File, Line,_Col}, Text}, Args}, Acc, F) ->
  101. [{Text, trans_ext_args(Args,[]), {F,Line}}|Acc];
  102. extract({text, _, _}, Acc, _F) -> Acc;
  103. extract({string_literal, _, _}, Acc, _F) -> Acc;
  104. extract({number_literal, _, _}, Acc, _F) -> Acc;
  105. extract({comment, _}, Acc, _F) -> Acc;
  106. extract({auto_id, _}, Acc, _F) -> Acc;
  107. extract({variable, _}, Acc, _F) -> Acc;
  108. extract(T, Acc, F) when is_tuple(T) ->
  109. extract(tl(tuple_to_list(T)), Acc, F);
  110. extract(N, Acc, _F) when is_integer(N); is_atom(N) ->
  111. Acc.
  112. trans_ext_args([], Acc) ->
  113. Acc;
  114. trans_ext_args([{{identifier,_,Lang}, {string_literal, _, Text}}|Args], Acc) ->
  115. trans_ext_args(Args, [{list_to_atom(Lang), Text}|Acc]).
  116. %% Scan binary for erlang ?__(..., Context) syntax with either a binary or a string as first arg.
  117. parse_erl(File, Epp) ->
  118. parse_erl_form(epp:parse_erl_form(Epp), File, Epp, []).
  119. parse_erl_form({eof, _}, _File, Epp, Acc) ->
  120. epp:close(Epp),
  121. Acc;
  122. parse_erl_form({ok, Other}, File, Epp, Acc) ->
  123. parse_erl_form(epp:parse_erl_form(Epp), File, Epp,
  124. parse_erl_form_part(Other, File, Acc));
  125. parse_erl_form({error, _}, File, Epp, Acc) ->
  126. parse_erl_form(epp:parse_erl_form(Epp), File, Epp, Acc).
  127. parse_erl_form_part({function, _, _, _, Expressions}, File, Acc) ->
  128. lists:foldl(fun(Part,A) -> parse_erl_form_part(Part, File, A) end, Acc, Expressions);
  129. parse_erl_form_part({tuple, _, Expressions}, File, Acc) ->
  130. lists:foldl(fun(Part,A) -> parse_erl_form_part(Part, File, A) end, Acc, Expressions);
  131. parse_erl_form_part({clause, _, _, _, Expressions}, File, Acc) ->
  132. lists:foldl(fun(Part,A) -> parse_erl_form_part(Part, File, A) end, Acc, Expressions);
  133. parse_erl_form_part({match, _, X, Y}, File, Acc) ->
  134. parse_erl_form_part(X, File, []) ++ parse_erl_form_part(Y, File, []) ++ Acc;
  135. parse_erl_form_part({cons, _, X, Y}, File, Acc) ->
  136. parse_erl_form_part(X, File, []) ++ parse_erl_form_part(Y, File, []) ++ Acc;
  137. parse_erl_form_part({'case', _, Expr, Exprs}, File, Acc) ->
  138. parse_erl_form_part(Expr, File, []) ++
  139. lists:foldl(fun(Part,A) -> parse_erl_form_part(Part, File, A) end, Acc, Exprs);
  140. parse_erl_form_part({call, _, {remote, _, {atom, _, z_trans}, {atom, _, trans}},
  141. [{string, Line, S}, _]}, File, Acc) ->
  142. [{S, [], {File,Line}}|Acc];
  143. parse_erl_form_part({call, _, {remote, _, {atom, _, z_trans}, {atom, _, trans}},
  144. [{bin, _, [{bin_element, _, {string, Line, S}, _, _}|_]}|_]}, File, Acc) ->
  145. [{S, [], {File,Line}}|Acc];
  146. parse_erl_form_part({call, _, _, Expressions}, File, Acc) ->
  147. lists:foldl(fun(Part,A) -> parse_erl_form_part(Part, File, A) end, Acc, Expressions);
  148. parse_erl_form_part(_Part, _File, Acc) ->
  149. Acc. %% ignore