check-i18n-style.escript 3.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116
  1. #!/usr/bin/env escript
  2. -mode(compile).
  3. -define(YELLOW, "\e[33m").
  4. -define(RED, "\e[31m").
  5. -define(RESET, "\e[39m").
  6. main([Files0]) ->
  7. _ = put(errors, 0),
  8. Files = string:tokens(Files0, "\n"),
  9. ok = load_hocon(),
  10. ok = lists:foreach(fun check/1, Files),
  11. case get(errors) of
  12. 1 ->
  13. logerr("1 error found~n", []);
  14. N when is_integer(N) andalso N > 1 ->
  15. logerr("~p errors found~n", [N]);
  16. _ ->
  17. io:format(user, "OK~n", [])
  18. end.
  19. load_hocon() ->
  20. Dir = "_build/default/lib/hocon/ebin",
  21. File = filename:join([Dir, "hocon.beam"]),
  22. case filelib:is_regular(File) of
  23. true ->
  24. code:add_path(Dir),
  25. ok;
  26. false ->
  27. die("HOCON is not compiled in " ++ Dir ++ "~n")
  28. end.
  29. die(Msg) ->
  30. die(Msg, []).
  31. die(Msg, Args) ->
  32. ok = logerr(Msg, Args),
  33. halt(1).
  34. logerr(Fmt, Args) ->
  35. io:format(standard_error, ?RED ++ "ERROR: " ++ Fmt ++ ?RESET, Args),
  36. N = get(errors),
  37. _ = put(errors, N + 1),
  38. ok.
  39. check(File) ->
  40. io:format(user, "checking: ~s~n", [File]),
  41. {ok, C} = hocon:load(File),
  42. maps:foreach(fun check_one_field/2, C),
  43. ok.
  44. check_one_field(Name, Field) ->
  45. maps:foreach(fun(SubName, DescAndLabel) ->
  46. check_desc_and_label([Name, ".", SubName], DescAndLabel)
  47. end, Field).
  48. check_desc_and_label(Name, D) ->
  49. case maps:keys(D) -- [<<"desc">>, <<"label">>] of
  50. [] ->
  51. ok;
  52. Unknown ->
  53. die("~s: unknown tags ~p~n", [Name, Unknown])
  54. end,
  55. ok = check_desc(Name, D),
  56. ok = check_label(Name, D).
  57. check_label(_Name, #{<<"label">> := _Label}) ->
  58. ok;
  59. check_label(_Name, _) ->
  60. %% some may not have label
  61. ok.
  62. check_desc(Name, #{<<"desc">> := Desc}) ->
  63. do_check_desc(Name, Desc);
  64. check_desc(Name, _) ->
  65. die("~s: no 'desc'~n", [Name]).
  66. do_check_desc(Name, #{<<"zh">> := Zh, <<"en">> := En}) ->
  67. ok = check_desc_string(Name, "zh", Zh),
  68. ok = check_desc_string(Name, "en", En);
  69. do_check_desc(Name, _) ->
  70. die("~s: missing 'zh' or 'en'~n", [Name]).
  71. check_desc_string(Name, Tr, <<>>) ->
  72. io:format(standard_error, ?YELLOW ++ "WARNING: ~s.~s: empty string~n" ++ ?RESET, [Name, Tr]);
  73. check_desc_string(Name, Tr, BinStr) ->
  74. Str = unicode:characters_to_list(BinStr, utf8),
  75. Err = fun(Reason) ->
  76. logerr("~s.~s: ~s~n", [Name, Tr, Reason])
  77. end,
  78. case Str of
  79. [$\s | _] ->
  80. Err("remove leading whitespace");
  81. [$\n | _] ->
  82. Err("remove leading line-break");
  83. "<br/>" ->
  84. Err("remove leading <br/>");
  85. "<br />" ->
  86. Err("remove leading <br />");
  87. _ ->
  88. ok
  89. end,
  90. case lists:reverse(Str) of
  91. [$\s | _] ->
  92. Err("remove trailing whitespace");
  93. [$\n | _] ->
  94. Err("remove trailing line-break");
  95. ">/rb<" ++ _ ->
  96. Err("remove trailing <br/>");
  97. ">/ rb<" ++ _ ->
  98. Err("remove trailing <br />");
  99. _ ->
  100. ok
  101. end.