erlydtl_compiler_utils.erl 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531
  1. %%%-------------------------------------------------------------------
  2. %%% File: erlydtl_compiler_utils.erl
  3. %%% @author Roberto Saccon <rsaccon@gmail.com> [http://rsaccon.com]
  4. %%% @author Evan Miller <emmiller@gmail.com>
  5. %%% @author Andreas Stenius <kaos@astekk.se>
  6. %%% @copyright 2008 Roberto Saccon, Evan Miller
  7. %%% @copyright 2014 Andreas Stenius
  8. %%% @doc
  9. %%% ErlyDTL template compiler utils.
  10. %%% @end
  11. %%%
  12. %%% The MIT License
  13. %%%
  14. %%% Copyright (c) 2007 Roberto Saccon, Evan Miller
  15. %%% Copyright (c) 2014 Andreas Stenius
  16. %%%
  17. %%% Permission is hereby granted, free of charge, to any person obtaining a copy
  18. %%% of this software and associated documentation files (the "Software"), to deal
  19. %%% in the Software without restriction, including without limitation the rights
  20. %%% to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  21. %%% copies of the Software, and to permit persons to whom the Software is
  22. %%% furnished to do so, subject to the following conditions:
  23. %%%
  24. %%% The above copyright notice and this permission notice shall be included in
  25. %%% all copies or substantial portions of the Software.
  26. %%%
  27. %%% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  28. %%% IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  29. %%% FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  30. %%% AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  31. %%% LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  32. %%% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
  33. %%% THE SOFTWARE.
  34. %%%
  35. %%% @since 2007-12-16 by Roberto Saccon, Evan Miller
  36. %%% @since 2014 by Andreas Stenius
  37. %%%-------------------------------------------------------------------
  38. -module(erlydtl_compiler_utils).
  39. -author('rsaccon@gmail.com').
  40. -author('emmiller@gmail.com').
  41. -author('Andreas Stenius <kaos@astekk.se>').
  42. %% --------------------------------------------------------------------
  43. %% Definitions
  44. %% --------------------------------------------------------------------
  45. -define(LIB_VERSION, 1).
  46. -export([
  47. add_error/3, add_errors/2,
  48. add_filters/2,
  49. add_tags/2,
  50. add_warning/3, add_warnings/2,
  51. begin_scope/1, begin_scope/2,
  52. call_extension/3,
  53. empty_scope/0,
  54. end_scope/4,
  55. format_error/1,
  56. full_path/2,
  57. get_current_file/1,
  58. init_treewalker/1,
  59. is_stripped_token_empty/1,
  60. load_library/2, load_library/3, load_library/4,
  61. merge_info/2,
  62. print/3, print/4,
  63. push_scope/2,
  64. reset_block_dict/2,
  65. reset_parse_trail/2,
  66. resolve_variable/2, resolve_variable/3,
  67. restore_scope/2,
  68. shorten_filename/1, shorten_filename/2,
  69. to_string/2,
  70. unescape_string_literal/1,
  71. push_auto_escape/2,
  72. pop_auto_escape/1,
  73. token_pos/1
  74. ]).
  75. -include("erlydtl_ext.hrl").
  76. %% --------------------------------------------------------------------
  77. %% API
  78. %% --------------------------------------------------------------------
  79. init_treewalker(Context) ->
  80. TreeWalker = #treewalker{ context=Context },
  81. case call_extension(Context, init_treewalker, [TreeWalker]) of
  82. {ok, TW} when is_record(TW, treewalker) -> TW;
  83. undefined -> TreeWalker
  84. end.
  85. to_string(Arg, #dtl_context{ binary_strings = true }) ->
  86. to_binary_string(Arg);
  87. to_string(Arg, #dtl_context{ binary_strings = false }) ->
  88. to_list_string(Arg).
  89. unescape_string_literal(String) ->
  90. unescape_string_literal(remove_quotes(String), [], noslash).
  91. full_path(File, DocRoot) ->
  92. case filename:absname(File) of
  93. File -> File;
  94. _ -> filename:join([DocRoot, File])
  95. end.
  96. print(Fmt, Args, Context) ->
  97. print(?V_INFO, Fmt, Args, Context).
  98. print(Verbosity, Fmt, Args, #treewalker{ context=Context }) ->
  99. print(Verbosity, Fmt, Args, Context);
  100. print(Verbosity, Fmt, Args, #dtl_context{ verbose = Verbose })
  101. when Verbosity =< Verbose ->
  102. io:format(Fmt, Args);
  103. print(_Verbosity, _Fmt, _Args, _Context) ->
  104. ok.
  105. get_current_file(#treewalker{ context=Context }) ->
  106. get_current_file(Context);
  107. get_current_file(#dtl_context{ parse_trail=[File|_] }) -> File;
  108. get_current_file(#dtl_context{ is_compiling_dir=Dir }) when Dir =/= false -> Dir;
  109. get_current_file(#dtl_context{ doc_root=Root }) -> Root.
  110. add_error(Module, Error, #treewalker{ context=Context }=TreeWalker) ->
  111. TreeWalker#treewalker{ context=add_error(Module, Error, Context) };
  112. add_error(Module, Error, #dtl_context{
  113. errors=#error_info{ report=Report, list=Es }=Ei
  114. }=Context) ->
  115. Item = get_error_item(
  116. Report, "",
  117. get_current_file(Context),
  118. Error, Module),
  119. Context#dtl_context{
  120. errors=Ei#error_info{ list=[Item|Es] }
  121. }.
  122. add_errors(Errors, #treewalker{ context=Context }=TreeWalker) ->
  123. TreeWalker#treewalker{ context=add_errors(Errors, Context) };
  124. add_errors(Errors, Context) ->
  125. lists:foldl(
  126. fun (E, C) -> add_error(?MODULE, E, C) end,
  127. Context, Errors).
  128. add_warning(Module, Warning, #treewalker{ context=Context }=TreeWalker) ->
  129. TreeWalker#treewalker{ context=add_warning(Module, Warning, Context) };
  130. add_warning(Module, Warning, #dtl_context{ warnings=warnings_as_errors }=Context) ->
  131. add_error(Module, Warning, Context);
  132. add_warning(Module, Warning, #dtl_context{
  133. warnings=#error_info{ report=Report, list=Ws }=Wi
  134. }=Context) ->
  135. Item = get_error_item(
  136. Report, "Warning: ",
  137. get_current_file(Context),
  138. Warning, Module),
  139. Context#dtl_context{
  140. warnings=Wi#error_info{ list=[Item|Ws] }
  141. }.
  142. add_warnings(Warnings, #treewalker{ context=Context }=TreeWalker) ->
  143. TreeWalker#treewalker{ context=add_warnings(Warnings, Context) };
  144. add_warnings(Warnings, Context) ->
  145. lists:foldl(
  146. fun (W, C) -> add_warning(?MODULE, W, C) end,
  147. Context, Warnings).
  148. call_extension(#treewalker{ context=Context }, Fun, Args) ->
  149. call_extension(Context, Fun, Args);
  150. call_extension(#dtl_context{ extension_module=undefined }, _Fun, _Args) ->
  151. undefined;
  152. call_extension(#dtl_context{ extension_module=Mod }, Fun, Args)
  153. when is_atom(Mod), is_atom(Fun), is_list(Args) ->
  154. M = case code:is_loaded(Mod) of
  155. false ->
  156. case code:load_file(Mod) of
  157. {module, Mod} ->
  158. Mod;
  159. _ ->
  160. undefined
  161. end;
  162. _ -> Mod
  163. end,
  164. if M /= undefined ->
  165. case erlang:function_exported(M, Fun, length(Args)) of
  166. true ->
  167. apply(M, Fun, Args);
  168. false ->
  169. undefined
  170. end;
  171. true ->
  172. undefined
  173. end.
  174. merge_info(Info1, Info2) when is_record(Info1, ast_info), is_record(Info2, ast_info) ->
  175. merge_info1(record_info(size, ast_info), Info1, Info2, #ast_info{}).
  176. resolve_variable(VarName, TreeWalker) ->
  177. resolve_variable(VarName, undefined, TreeWalker).
  178. resolve_variable(VarName, Default, #treewalker{ context=Context }) ->
  179. case resolve_variable1(Context#dtl_context.local_scopes, VarName) of
  180. undefined ->
  181. case proplists:get_value(VarName, Context#dtl_context.const) of
  182. undefined ->
  183. case proplists:get_value(VarName, Context#dtl_context.vars) of
  184. undefined -> {default, Default, []};
  185. Value -> {default_vars, Value, []}
  186. end;
  187. Value -> {constant, Value, []}
  188. end;
  189. {Value, Filters} -> {scope, Value, Filters}
  190. end.
  191. push_scope(Scope, #treewalker{ context=Context }=TreeWalker) ->
  192. TreeWalker#treewalker{ context=push_scope(Scope, Context) };
  193. push_scope(Scope, #dtl_context{ local_scopes=Scopes }=Context) ->
  194. Context#dtl_context{ local_scopes=[Scope|Scopes] }.
  195. restore_scope(Target, #treewalker{ context=Context }=TreeWalker) ->
  196. TreeWalker#treewalker{ context=restore_scope(Target, Context) };
  197. restore_scope(#treewalker{ context=Target }, Context) ->
  198. restore_scope(Target, Context);
  199. restore_scope(#dtl_context{ local_scopes=Scopes }, Context) ->
  200. Context#dtl_context{ local_scopes=Scopes }.
  201. begin_scope(TreeWalker) -> begin_scope(empty_scope(), TreeWalker).
  202. begin_scope({Scope, Values}, TreeWalker) ->
  203. Id = make_ref(),
  204. {Id, push_scope({Id, Scope, Values}, TreeWalker)}.
  205. end_scope(Fun, Id, AstList, TreeWalker) ->
  206. close_scope(Fun, Id, AstList, TreeWalker).
  207. empty_scope() -> {[], []}.
  208. reset_block_dict(BlockDict, #treewalker{ context=Context }=TreeWalker) ->
  209. TreeWalker#treewalker{ context=reset_block_dict(BlockDict, Context) };
  210. reset_block_dict(BlockDict, Context) ->
  211. Context#dtl_context{ block_dict=BlockDict }.
  212. reset_parse_trail(ParseTrail, #treewalker{ context=Context }=TreeWalker) ->
  213. TreeWalker#treewalker{ context=reset_parse_trail(ParseTrail, Context) };
  214. reset_parse_trail(ParseTrail, Context) ->
  215. Context#dtl_context{ parse_trail=ParseTrail }.
  216. load_library(Lib, Context) -> load_library(none, Lib, [], Context).
  217. load_library(Pos, Lib, Context) -> load_library(Pos, Lib, [], Context).
  218. load_library(Pos, Lib, Which, #treewalker{ context=Context }=TreeWalker) ->
  219. TreeWalker#treewalker{ context=load_library(Pos, Lib, Which, Context) };
  220. load_library(Pos, Lib, Which, Context) ->
  221. case lib_module(Lib, Context) of
  222. {ok, Mod} ->
  223. ?LOG_DEBUG(
  224. "~s: Load library '~s' from ~s ~p~n",
  225. [get_current_file(Context), Lib, Mod, Which], Context),
  226. Filters = read_library(Mod, filters, Which),
  227. Tags = read_library(Mod, tags, Which),
  228. Found = Filters ++ Tags,
  229. Missing = lists:filter(
  230. fun (W) -> lists:keyfind(W, 1, Found) == false end,
  231. Which),
  232. add_tags(
  233. Tags,
  234. add_filters(
  235. Filters,
  236. warn_missing(
  237. Missing, {Pos, Lib, Mod}, Context)));
  238. Error ->
  239. ?WARN({Pos, Error}, Context)
  240. end.
  241. add_filters(Load, #dtl_context{ filters=Filters }=Context) ->
  242. ?LOG_TRACE("Load filters: ~p~n", [Load], Context),
  243. Context#dtl_context{ filters=Load ++ Filters }.
  244. add_tags(Load, #dtl_context{ tags=Tags }=Context) ->
  245. ?LOG_TRACE("Load tags: ~p~n", [Load], Context),
  246. Context#dtl_context{ tags=Load ++ Tags }.
  247. %% shorten_filename/1 copied from Erlang/OTP lib/compiler/src/compile.erl
  248. shorten_filename(Name) ->
  249. {ok, Cwd} = file:get_cwd(),
  250. shorten_filename(Name, Cwd).
  251. shorten_filename(Name, Cwd) ->
  252. case lists:prefix(Cwd, Name) of
  253. false -> Name;
  254. true ->
  255. case lists:nthtail(length(Cwd), Name) of
  256. "/"++N -> N;
  257. N -> N
  258. end
  259. end.
  260. push_auto_escape(State, #treewalker{ context=Context }=TreeWalker) ->
  261. TreeWalker#treewalker{ context=push_auto_escape(State, Context) };
  262. push_auto_escape(State, #dtl_context{ auto_escape=AutoEscape }=Context) ->
  263. Context#dtl_context{ auto_escape=[State|AutoEscape] }.
  264. pop_auto_escape(#treewalker{ context=Context }=TreeWalker) ->
  265. TreeWalker#treewalker{ context=pop_auto_escape(Context) };
  266. pop_auto_escape(#dtl_context{ auto_escape=[_|AutoEscape] }=Context)
  267. when length(AutoEscape) > 0 ->
  268. Context#dtl_context{ auto_escape=AutoEscape };
  269. pop_auto_escape(Context) -> Context.
  270. token_pos(Token) when is_tuple(Token) ->
  271. token_pos(tuple_to_list(Token));
  272. token_pos([T|Ts]) when is_tuple(T) ->
  273. case T of
  274. {R, C}=P when is_integer(R), is_integer(C) -> P;
  275. _ -> token_pos(tuple_to_list(T) ++ Ts)
  276. end;
  277. token_pos([T|Ts]) when is_list(T) -> token_pos(T ++ Ts);
  278. token_pos([_|Ts]) -> token_pos(Ts);
  279. token_pos([]) -> none.
  280. is_stripped_token_empty({string, _, S}) ->
  281. [] == [C || C <- S, C /= 32, C /= $\r, C /= $\n, C /= $\t];
  282. is_stripped_token_empty({comment, _}) -> true;
  283. is_stripped_token_empty({comment_tag, _, _}) -> true;
  284. is_stripped_token_empty(_) -> false.
  285. format_error({load_library, Name, Mod, Reason}) ->
  286. io_lib:format("Failed to load library '~p' (~p): ~p", [Name, Mod, Reason]);
  287. format_error({load_from, Name, Mod, Tag}) ->
  288. io_lib:format("'~p' not in library '~p' (~p)", [Tag, Name, Mod]);
  289. format_error({unknown_extension, Tag}) ->
  290. io_lib:format("Unhandled extension: ~p", [Tag]);
  291. format_error(Other) ->
  292. io_lib:format("## Sorry, error description for ~p not yet implemented.~n"
  293. "## Please report this so we can fix it.", [Other]).
  294. %%====================================================================
  295. %% Internal functions
  296. %%====================================================================
  297. to_binary_string(Arg) when is_binary(Arg) -> Arg;
  298. to_binary_string(Arg) when is_list(Arg) -> list_to_binary(Arg);
  299. to_binary_string(Arg) when is_integer(Arg) ->
  300. case erlang:function_exported(erlang, integer_to_binary, 1) of
  301. true -> erlang:integer_to_binary(Arg);
  302. false -> list_to_binary(integer_to_list(Arg))
  303. end;
  304. to_binary_string(Arg) when is_atom(Arg) -> atom_to_binary(Arg, latin1).
  305. to_list_string(Arg) when is_list(Arg) -> Arg;
  306. to_list_string(Arg) when is_binary(Arg) -> binary_to_list(Arg);
  307. to_list_string(Arg) when is_integer(Arg) -> integer_to_list(Arg);
  308. to_list_string(Arg) when is_atom(Arg) -> atom_to_list(Arg).
  309. unescape_string_literal([], Acc, noslash) ->
  310. lists:reverse(Acc);
  311. unescape_string_literal([$\\ | Rest], Acc, noslash) ->
  312. unescape_string_literal(Rest, Acc, slash);
  313. unescape_string_literal([C | Rest], Acc, noslash) ->
  314. unescape_string_literal(Rest, [C | Acc], noslash);
  315. unescape_string_literal("n" ++ Rest, Acc, slash) ->
  316. unescape_string_literal(Rest, [$\n | Acc], noslash);
  317. unescape_string_literal("r" ++ Rest, Acc, slash) ->
  318. unescape_string_literal(Rest, [$\r | Acc], noslash);
  319. unescape_string_literal("t" ++ Rest, Acc, slash) ->
  320. unescape_string_literal(Rest, [$\t | Acc], noslash);
  321. unescape_string_literal([C | Rest], Acc, slash) ->
  322. unescape_string_literal(Rest, [C | Acc], noslash).
  323. get_error_item(Report, Prefix, File, Error, DefaultModule) ->
  324. case compose_error_desc(Error, DefaultModule) of
  325. {Pos, Module, ErrorDesc} ->
  326. new_error_item(Report, Prefix, File, Pos, Module, ErrorDesc);
  327. ErrorItem ->
  328. ErrorItem
  329. end.
  330. compose_error_desc({{Line, Col}=Pos, ErrorDesc}, Module)
  331. when is_integer(Line), is_integer(Col), is_atom(Module) ->
  332. {Pos, Module, ErrorDesc};
  333. compose_error_desc({Line, ErrorDesc}, Module)
  334. when is_integer(Line); Line =:= none ->
  335. {Line, Module, ErrorDesc};
  336. compose_error_desc({{Line, Col}, Module, _}=ErrorDesc, _)
  337. when is_integer(Line), is_integer(Col), is_atom(Module) ->
  338. ErrorDesc;
  339. compose_error_desc({Line, Module, _}=ErrorDesc, _)
  340. when is_integer(Line), is_atom(Module) ->
  341. ErrorDesc;
  342. compose_error_desc({none, Module, _}=ErrorDesc, _)
  343. when is_atom(Module) ->
  344. ErrorDesc;
  345. compose_error_desc({_, InfoList}=ErrorDesc, _)
  346. when is_list(InfoList) -> ErrorDesc;
  347. compose_error_desc(ErrorDesc, Module) ->
  348. {none, Module, ErrorDesc}.
  349. new_error_item(Report, Prefix, File, Pos, Module, ErrorDesc) ->
  350. if Report ->
  351. io:format("~s:~s~s~s~n",
  352. [File, pos_info(Pos), Prefix,
  353. Module:format_error(ErrorDesc)]);
  354. true -> nop
  355. end,
  356. {File, [{Pos, Module, ErrorDesc}]}.
  357. pos_info(none) -> " ";
  358. pos_info(Line) when is_integer(Line) ->
  359. io_lib:format("~b: ", [Line]);
  360. pos_info({Line, Col}) when is_integer(Line), is_integer(Col) ->
  361. io_lib:format("~b:~b ", [Line, Col]).
  362. resolve_variable1([], _VarName) -> undefined;
  363. resolve_variable1([Scope|Scopes], VarName) ->
  364. case lists:keyfind(VarName, 1, get_scope(Scope)) of
  365. false ->
  366. resolve_variable1(Scopes, VarName);
  367. {_, Value} -> {Value, []};
  368. {_, Value, Filters} when is_list(Filters) -> {Value, Filters};
  369. {_, Value, Filter} when is_atom(Filter) -> {Value, [{Filter, []}]};
  370. {_, Value, Filter} -> {Value, [Filter]}
  371. end.
  372. get_scope({_Id, Scope, _Values}) -> Scope;
  373. get_scope(Scope) -> Scope.
  374. merge_info1(1, _, _, Info) -> Info;
  375. merge_info1(FieldIdx, Info1, Info2, Info) ->
  376. Value = lists:umerge(
  377. lists:usort(element(FieldIdx, Info1)),
  378. lists:usort(element(FieldIdx, Info2))),
  379. merge_info1(FieldIdx - 1, Info1, Info2, setelement(FieldIdx, Info, Value)).
  380. close_scope(Fun, Id, AstList, TreeWalker) ->
  381. case merge_scopes(Id, TreeWalker) of
  382. {[], TreeWalker1} -> {AstList, TreeWalker1};
  383. {Values, TreeWalker1} ->
  384. {lists:foldl(
  385. fun ({ScopeId, ScopeValues}, AstAcc) ->
  386. {Pre, Target, Post} = split_ast(ScopeId, AstAcc),
  387. Pre ++ Fun(ScopeValues ++ Target) ++ Post
  388. end,
  389. AstList, Values),
  390. TreeWalker1}
  391. end.
  392. merge_scopes(Id, #treewalker{ context=Context }=TreeWalker) ->
  393. {Values, Scopes} = merge_scopes(Id, Context#dtl_context.local_scopes, []),
  394. {lists:reverse(Values),
  395. TreeWalker#treewalker{
  396. context=Context#dtl_context{
  397. local_scopes = Scopes
  398. } }}.
  399. merge_scopes(Id, [{Id, _Scope, []}|Scopes], Acc) -> {Acc, Scopes};
  400. merge_scopes(Id, [{Id, _Scope, Values}|Scopes], Acc) -> {[{Id, Values}|Acc], Scopes};
  401. merge_scopes(Id, [{_ScopeId, _Scope, []}|Scopes], Acc) ->
  402. merge_scopes(Id, Scopes, Acc);
  403. merge_scopes(Id, [{ScopeId, _Scope, Values}|Scopes], Acc) ->
  404. merge_scopes(Id, Scopes, [{ScopeId, Values}|Acc]);
  405. merge_scopes(Id, [_PlainScope|Scopes], Acc) ->
  406. merge_scopes(Id, Scopes, Acc).
  407. split_ast(Id, AstList) ->
  408. split_ast(Id, AstList, []).
  409. split_ast(_Split, [], {Pre, Acc}) ->
  410. {Pre, lists:reverse(Acc), []};
  411. split_ast(_Split, [], Acc) ->
  412. {[], lists:reverse(Acc), []};
  413. split_ast(Split, [Split|Rest], {Pre, Acc}) ->
  414. {Pre, lists:reverse(Acc), Rest};
  415. split_ast(Split, [Split|Rest], Acc) ->
  416. split_ast(end_scope, Rest, {lists:reverse(Acc), []});
  417. split_ast(Split, [Ast|Rest], {Pre, Acc}) ->
  418. split_ast(Split, Rest, {Pre, [Ast|Acc]});
  419. split_ast(Split, [Ast|Rest], Acc) ->
  420. split_ast(Split, Rest, [Ast|Acc]).
  421. lib_module(Name, #dtl_context{ libraries=Libs }) ->
  422. Mod = proplists:get_value(Name, Libs, Name),
  423. case code:ensure_loaded(Mod) of
  424. {module, Mod} ->
  425. case implements_behaviour(erlydtl_library, Mod) of
  426. true ->
  427. case Mod:version() of
  428. ?LIB_VERSION -> {ok, Mod};
  429. V -> {load_library, Name, Mod, {version, V}}
  430. end;
  431. false -> {load_library, Name, Mod, behaviour}
  432. end;
  433. {error, Reason} ->
  434. {load_library, Name, Mod, Reason}
  435. end.
  436. implements_behaviour(Behaviour, Mod) ->
  437. [] =:= [Behaviour] -- [B || [B] <- proplists:get_all_values(behaviour, Mod:module_info(attributes))].
  438. read_library(Mod, Section, Which) ->
  439. [{Name, lib_function(Mod, Fun)}
  440. || {Name, Fun} <- read_inventory(Mod, Section),
  441. Which =:= [] orelse lists:member(Name, Which)].
  442. warn_missing([], _, Context) -> Context;
  443. warn_missing([X|Xs], {Pos, Lib, Mod}=Info, Context) ->
  444. warn_missing(Xs, Info, ?WARN({Pos, {load_from, Lib, Mod, X}}, Context)).
  445. lib_function(_, {Mod, Fun}) ->
  446. lib_function(Mod, Fun);
  447. lib_function(Mod, Fun) ->
  448. %% we could check for lib function availability here.. (sanity check)
  449. {Mod, Fun}.
  450. read_inventory(Mod, Section) ->
  451. [case Item of
  452. {_Name, _Fun} -> Item;
  453. Fun -> {Fun, Fun}
  454. end || Item <- Mod:inventory(Section)].
  455. remove_quotes(String) ->
  456. remove_last_quote(remove_first_quote(String)).
  457. remove_first_quote([34 | Rest]) ->
  458. Rest;
  459. remove_first_quote(String) ->
  460. String.
  461. remove_last_quote(String) ->
  462. lists:reverse(remove_first_quote(lists:reverse(String))).