erlydtl_runtime.erl 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381
  1. -module(erlydtl_runtime).
  2. -compile(export_all).
  3. -type translate_fun() :: fun((string() | binary()) -> string() | binary() | undefined).
  4. -define(IFCHANGED_CONTEXT_VARIABLE, erlydtl_ifchanged_context).
  5. find_value(Key, Data, Options) when is_atom(Key), is_tuple(Data) ->
  6. Rec = element(1, Data),
  7. Info = proplists:get_value(record_info, Options),
  8. case proplists:get_value(Rec, Info) of
  9. Fields when is_list(Fields), length(Fields) == size(Data) - 1 ->
  10. case proplists:get_value(Key, Fields) of
  11. Idx when is_integer(Idx) -> element(Idx, Data);
  12. _ -> undefined
  13. end;
  14. _ -> find_value(Key, Data)
  15. end;
  16. find_value(Key, Data, Options) when is_integer(Key), is_list(Data) ->
  17. find_value(adjust_index(Key, 1, lists_0_based, Options), Data);
  18. find_value(Key, Data, _Options) ->
  19. find_value(Key, Data).
  20. adjust_index(Key, Off, Opt, Options) when is_list(Options) ->
  21. case proplists:get_value(Opt, Options) of
  22. defer ->
  23. adjust_index(
  24. Key, Off, Opt,
  25. proplists:get_value(render_options, Options));
  26. true ->
  27. Key + Off;
  28. _ ->
  29. Key
  30. end;
  31. adjust_index(Key, _Off, _Opt, _Options) -> Key.
  32. find_value(_, undefined) ->
  33. undefined;
  34. find_value(Key, Fun) when is_function(Fun, 1) ->
  35. Fun(Key);
  36. find_value(Key, L) when is_atom(Key), is_list(L) ->
  37. case lists:keyfind(Key, 1, L) of
  38. false -> find_value(atom_to_list(Key), L);
  39. {Key, Value} -> Value
  40. end;
  41. find_value(Key, L) when is_list(Key), is_list(L) ->
  42. case lists:keyfind(Key, 1, L) of
  43. false -> find_value(list_to_binary(Key), L);
  44. {Key, Value} -> Value
  45. end;
  46. find_value(Key, L) when is_binary(Key), is_list(L) ->
  47. case lists:keyfind(Key, 1, L) of
  48. false -> undefined;
  49. {Key, Value} -> Value
  50. end;
  51. find_value(Key, L) when is_integer(Key), is_list(L) ->
  52. if Key =< length(L) -> lists:nth(Key, L);
  53. true -> undefined
  54. end;
  55. find_value(Key, {GBSize, GBData}) when is_integer(GBSize) ->
  56. case gb_trees:lookup(Key, {GBSize, GBData}) of
  57. {value, Val} ->
  58. Val;
  59. _ ->
  60. undefined
  61. end;
  62. find_value(Key, Tuple) when is_tuple(Tuple) ->
  63. case element(1, Tuple) of
  64. dict ->
  65. case dict:find(Key, Tuple) of
  66. {ok, Val} ->
  67. Val;
  68. _ ->
  69. undefined
  70. end;
  71. _ when is_integer(Key) ->
  72. if Key < size(Tuple) -> element(Key, Tuple);
  73. true -> undefined
  74. end;
  75. Module ->
  76. case lists:member({Key, 1}, Module:module_info(exports)) of
  77. true ->
  78. case Tuple:Key() of
  79. Val when is_tuple(Val) ->
  80. case element(1, Val) of
  81. 'Elixir.Ecto.Associations.BelongsTo' -> Val:get();
  82. 'Elixir.Ecto.Associations.HasOne' -> Val:get();
  83. _ -> Val
  84. end;
  85. Val -> Val
  86. end;
  87. _ ->
  88. undefined
  89. end
  90. end.
  91. fetch_value(Key, Data, Options) ->
  92. case find_value(Key, Data, Options) of
  93. undefined -> [];
  94. Val -> Val
  95. end.
  96. find_deep_value([Key|Rest],Item) ->
  97. case find_value(Key,Item) of
  98. undefined -> undefined;
  99. NewItem -> find_deep_value(Rest,NewItem)
  100. end;
  101. find_deep_value([],Item) -> Item.
  102. regroup(List, Attribute) ->
  103. regroup(List, Attribute, []).
  104. regroup([], _, []) ->
  105. [];
  106. regroup([], _, [[{grouper, LastGrouper}, {list, LastList}]|Acc]) ->
  107. lists:reverse([[{grouper, LastGrouper}, {list, lists:reverse(LastList)}]|Acc]);
  108. regroup([Item|Rest], Attribute, []) ->
  109. regroup(Rest, Attribute, [[{grouper, find_deep_value(Attribute, Item)}, {list, [Item]}]]);
  110. regroup([Item|Rest], Attribute, [[{grouper, PrevGrouper}, {list, PrevList}]|Acc]) ->
  111. case find_deep_value(Attribute, Item) of
  112. Value when Value =:= PrevGrouper ->
  113. regroup(Rest, Attribute, [[{grouper, PrevGrouper}, {list, [Item|PrevList]}]|Acc]);
  114. Value ->
  115. regroup(Rest, Attribute, [[{grouper, Value}, {list, [Item]}], [{grouper, PrevGrouper}, {list, lists:reverse(PrevList)}]|Acc])
  116. end.
  117. -spec translate(Str, none | translate_fun()) -> Str when
  118. Str :: string() | binary().
  119. translate(String, none) -> String;
  120. translate(String, TranslationFun)
  121. when is_function(TranslationFun) ->
  122. case TranslationFun(String) of
  123. undefined -> String;
  124. <<"">> -> String;
  125. "" -> String;
  126. Str -> Str
  127. end.
  128. %% @doc Translate and interpolate 'blocktrans' content.
  129. %% Pre-requisites:
  130. %% * `Variables' should be sorted
  131. %% * Each interpolation variable should exist
  132. %% (String="{{a}}", Variables=[{"b", "b-val"}] will fall)
  133. %% * Orddict keys should be string(), not binary()
  134. -spec translate_block(string() | binary(), translate_fun(), orddict:orddict()) -> iodata().
  135. translate_block(String, TranslationFun, Variables) ->
  136. TransString = case TranslationFun(String) of
  137. No when (undefined == No)
  138. orelse (<<"">> == No)
  139. orelse ("" == No) -> String;
  140. Str -> Str
  141. end,
  142. try interpolate_variables(TransString, Variables)
  143. catch _:_ ->
  144. %% Fallback to default language in case of errors (like Djando does)
  145. interpolate_variables(String, Variables)
  146. end.
  147. interpolate_variables(Tpl, []) ->
  148. Tpl;
  149. interpolate_variables(Tpl, Variables) ->
  150. BTpl = iolist_to_binary(Tpl),
  151. interpolate_variables1(BTpl, Variables).
  152. interpolate_variables1(Tpl, Vars) ->
  153. %% pre-compile binary patterns?
  154. case binary:split(Tpl, <<"{{">>) of
  155. [NotFound] ->
  156. [NotFound];
  157. [Pre, Post] ->
  158. case binary:split(Post, <<"}}">>) of
  159. [_] -> throw({no_close_var, Post});
  160. [Var, Post1] ->
  161. Var1 = string:strip(binary_to_list(Var)),
  162. Value = orddict:fetch(Var1, Vars),
  163. [Pre, Value | interpolate_variables1(Post1, Vars)]
  164. end
  165. end.
  166. are_equal(Arg1, Arg2) when Arg1 =:= Arg2 ->
  167. true;
  168. are_equal(Arg1, Arg2) when is_binary(Arg1) ->
  169. are_equal(binary_to_list(Arg1), Arg2);
  170. are_equal(Arg1, Arg2) when is_binary(Arg2) ->
  171. are_equal(Arg1, binary_to_list(Arg2));
  172. are_equal(Arg1, Arg2) when is_integer(Arg1) ->
  173. are_equal(integer_to_list(Arg1), Arg2);
  174. are_equal(Arg1, Arg2) when is_integer(Arg2) ->
  175. are_equal(Arg1, integer_to_list(Arg2));
  176. are_equal(Arg1, Arg2) when is_atom(Arg1), is_list(Arg2) ->
  177. are_equal(atom_to_list(Arg1), Arg2);
  178. are_equal(Arg1, Arg2) when is_list(Arg1), is_atom(Arg2) ->
  179. are_equal(Arg1, atom_to_list(Arg2));
  180. are_equal(_, _) ->
  181. false.
  182. is_false("") -> true;
  183. is_false(false) -> true;
  184. is_false(undefined) -> true;
  185. is_false(0) -> true;
  186. is_false("0") -> true;
  187. is_false(<<"0">>) -> true;
  188. is_false(<<>>) -> true;
  189. is_false(_) -> false.
  190. is_true(V) -> not is_false(V).
  191. 'in'(Sublist, [Sublist|_]) ->
  192. true;
  193. 'in'(Sublist, List) when is_atom(List) ->
  194. 'in'(Sublist, atom_to_list(List));
  195. 'in'(Sublist, List) when is_binary(Sublist) ->
  196. 'in'(binary_to_list(Sublist), List);
  197. 'in'(Sublist, List) when is_binary(List) ->
  198. 'in'(Sublist, binary_to_list(List));
  199. 'in'(Sublist, [C|Rest]) when is_list(Sublist) andalso is_binary(C) ->
  200. 'in'(Sublist, [binary_to_list(C)|Rest]);
  201. 'in'(Sublist, [C|Rest]) when is_list(Sublist) andalso is_list(C) ->
  202. 'in'(Sublist, Rest);
  203. 'in'(Sublist, List) when is_list(Sublist) andalso is_list(List) ->
  204. string:str(List, Sublist) > 0;
  205. 'in'(Element, List) when is_list(List) ->
  206. lists:member(Element, List);
  207. 'in'(_, _) ->
  208. false.
  209. 'not'(Value) ->
  210. not is_true(Value).
  211. 'or'(Value1, Value2) ->
  212. is_true(Value1) or is_true(Value2).
  213. 'and'(Value1, Value2) ->
  214. is_true(Value1) and is_true(Value2).
  215. 'eq'(Value1, Value2) ->
  216. are_equal(Value1, Value2).
  217. 'ne'(Value1, Value2) ->
  218. not are_equal(Value1, Value2).
  219. 'le'(Value1, Value2) ->
  220. not 'gt'(Value1, Value2).
  221. 'ge'(Value1, Value2) ->
  222. not 'lt'(Value1, Value2).
  223. 'gt'(Value1, Value2) when is_list(Value1) ->
  224. 'gt'(list_to_integer(Value1), Value2);
  225. 'gt'(Value1, Value2) when is_list(Value2) ->
  226. 'gt'(Value1, list_to_integer(Value2));
  227. 'gt'(Value1, Value2) when Value1 > Value2 ->
  228. true;
  229. 'gt'(_, _) ->
  230. false.
  231. 'lt'(Value1, Value2) when is_list(Value1) ->
  232. 'lt'(list_to_integer(Value1), Value2);
  233. 'lt'(Value1, Value2) when is_list(Value2) ->
  234. 'lt'(Value1, list_to_integer(Value2));
  235. 'lt'(Value1, Value2) when Value1 < Value2 ->
  236. true;
  237. 'lt'(_, _) ->
  238. false.
  239. stringify_final(In, BinaryStrings) ->
  240. stringify_final(In, [], BinaryStrings).
  241. stringify_final([], Out, _) ->
  242. lists:reverse(Out);
  243. stringify_final([El | Rest], Out, false = BinaryStrings) when is_atom(El) ->
  244. stringify_final(Rest, [atom_to_list(El) | Out], BinaryStrings);
  245. stringify_final([El | Rest], Out, true = BinaryStrings) when is_atom(El) ->
  246. stringify_final(Rest, [atom_to_binary(El, latin1) | Out], BinaryStrings);
  247. stringify_final([El | Rest], Out, BinaryStrings) when is_list(El) ->
  248. stringify_final(Rest, [stringify_final(El, BinaryStrings) | Out], BinaryStrings);
  249. stringify_final([El | Rest], Out, false = BinaryStrings) when is_tuple(El) ->
  250. stringify_final(Rest, [io_lib:print(El) | Out], BinaryStrings);
  251. stringify_final([El | Rest], Out, true = BinaryStrings) when is_tuple(El) ->
  252. stringify_final(Rest, [list_to_binary(io_lib:print(El)) | Out], BinaryStrings);
  253. stringify_final([El | Rest], Out, BinaryStrings) ->
  254. stringify_final(Rest, [El | Out], BinaryStrings).
  255. to_list(Value, true) ->
  256. lists:reverse(to_list(Value, false));
  257. to_list(Value, false) when is_list(Value) ->
  258. Value;
  259. to_list(Value, false) when is_tuple(Value) ->
  260. case element(1, Value) of
  261. 'Elixir.Ecto.Associations.HasMany' ->
  262. Value:to_list();
  263. _ ->
  264. tuple_to_list(Value)
  265. end.
  266. init_counter_stats(List) ->
  267. init_counter_stats(List, undefined).
  268. init_counter_stats(List, Parent) when is_list(List) ->
  269. ListLen = length(List),
  270. [{counter, 1},
  271. {counter0, 0},
  272. {revcounter, ListLen},
  273. {revcounter0, ListLen - 1},
  274. {first, true},
  275. {last, ListLen =:= 1},
  276. {parentloop, Parent}].
  277. increment_counter_stats([{counter, Counter}, {counter0, Counter0}, {revcounter, RevCounter},
  278. {revcounter0, RevCounter0}, {first, _}, {last, _}, {parentloop, Parent}]) ->
  279. [{counter, Counter + 1},
  280. {counter0, Counter0 + 1},
  281. {revcounter, RevCounter - 1},
  282. {revcounter0, RevCounter0 - 1},
  283. {first, false}, {last, RevCounter0 =:= 1},
  284. {parentloop, Parent}].
  285. forloop(_Fun, [], _Parent) -> empty;
  286. forloop(Fun, Values, Parent) ->
  287. push_ifchanged_context(),
  288. Result = lists:mapfoldl(Fun, init_counter_stats(Values, Parent), Values),
  289. pop_ifchanged_context(),
  290. Result.
  291. push_ifchanged_context() ->
  292. IfChangedContextStack = case get(?IFCHANGED_CONTEXT_VARIABLE) of
  293. undefined -> [];
  294. Stack -> Stack
  295. end,
  296. put(?IFCHANGED_CONTEXT_VARIABLE, [[]|IfChangedContextStack]).
  297. pop_ifchanged_context() ->
  298. [_|Rest] = get(?IFCHANGED_CONTEXT_VARIABLE),
  299. put(?IFCHANGED_CONTEXT_VARIABLE, Rest).
  300. ifchanged(Expressions) ->
  301. [IfChangedContext|Rest] = get(?IFCHANGED_CONTEXT_VARIABLE),
  302. {Result, NewContext} = lists:foldl(fun (Expr, {ProvResult, Context}) when ProvResult == true ->
  303. {_, NContext} = ifchanged2(Expr, Context),
  304. {true, NContext};
  305. (Expr, {_ProvResult, Context}) ->
  306. ifchanged2(Expr, Context)
  307. end, {false, IfChangedContext}, Expressions),
  308. put(?IFCHANGED_CONTEXT_VARIABLE, [NewContext|Rest]),
  309. Result.
  310. ifchanged2({Key, Value}, IfChangedContext) ->
  311. PreviousValue = proplists:get_value(Key, IfChangedContext),
  312. if
  313. PreviousValue =:= Value ->
  314. {false, IfChangedContext};
  315. true ->
  316. NewContext = [{Key, Value}|proplists:delete(Key, IfChangedContext)],
  317. {true, NewContext}
  318. end.
  319. cycle(NamesTuple, Counters) when is_tuple(NamesTuple) ->
  320. element(find_value(counter0, Counters) rem size(NamesTuple) + 1, NamesTuple).
  321. widthratio(Numerator, Denominator, Scale) ->
  322. round(Numerator / Denominator * Scale).
  323. spaceless(Contents) ->
  324. Contents1 = lists:flatten(Contents),
  325. Contents2 = re:replace(Contents1, "^\\s+<", "<", [{return,list}]),
  326. Contents3 = re:replace(Contents2, ">\\s+$", ">", [{return,list}]),
  327. Contents4 = re:replace(Contents3, ">\\s+<", "><", [global, {return,list}]),
  328. Contents4.
  329. read_file(Module, Function, DocRoot, FileName) ->
  330. AbsName = case filename:absname(FileName) of
  331. FileName -> FileName;
  332. _ -> filename:join([DocRoot, FileName])
  333. end,
  334. case Module:Function(AbsName) of
  335. {ok, Data} -> Data;
  336. {error, Reason} ->
  337. throw({read_file, AbsName, Reason})
  338. end.