edoc_data.erl
来自「OTP是开放电信平台的简称」· ERL 代码 · 共 533 行
ERL
533 行
%% =====================================================================%% This library is free software; you can redistribute it and/or modify%% it under the terms of the GNU Lesser General Public License as%% published by the Free Software Foundation; either version 2 of the%% License, or (at your option) any later version.%%%% This library is distributed in the hope that it will be useful, but%% WITHOUT ANY WARRANTY; without even the implied warranty of%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU%% Lesser General Public License for more details.%%%% You should have received a copy of the GNU Lesser General Public%% License along with this library; if not, write to the Free Software%% Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307%% USA%%%% $Id$%%%% @private%% @copyright 2003 Richard Carlsson%% @author Richard Carlsson <richardc@csd.uu.se>%% @see edoc%% @end %% =====================================================================%% @doc Building the EDoc external data structure. See the file%% <a href="../priv/edoc.dtd">`edoc.dtd'</a> for details.-module(edoc_data).-export([module/4, package/4, overview/4, type/2]).-include("edoc.hrl").%% TODO: report multiple definitions of the same type in the same module.%% TODO: check that variables in @equiv are found in the signature%% TODO: copy types from target (if missing) when using @equiv%% <!ELEMENT module (description?, author*, copyright?, version?,%% since?, deprecated?, see*, reference*, todo?,%% behaviour*, callbacks?, typedecls?, functions)>%% <!ATTLIST module%% name CDATA #REQUIRED%% private NMTOKEN(yes | no) #IMPLIED%% hidden NMTOKEN(yes | no) #IMPLIED%% root CDATA #IMPLIED>%% <!ELEMENT description (briefDescription, fullDescription?)>%% <!ELEMENT briefDescription (#PCDATA)>%% <!ELEMENT fullDescription (#PCDATA)>%% <!ELEMENT author EMPTY>%% <!ATTLIST author%% name CDATA #REQUIRED%% email CDATA #IMPLIED%% website CDATA #IMPLIED>%% <!ELEMENT version (#PCDATA)>%% <!ELEMENT since (#PCDATA)>%% <!ELEMENT copyright (#PCDATA)>%% <!ELEMENT deprecated (description)>%% <!ELEMENT see (#PCDATA)>%% <!ATTLIST see%% name CDATA #REQUIRED%% href CDATA #IMPLIED>%% <!ELEMENT reference (#PCDATA)>%% <!ELEMENT todo (#PCDATA)>%% <!ELEMENT behaviour (#PCDATA)>%% <!ATTLIST behaviour%% href CDATA #IMPLIED>%% <!ELEMENT callbacks (callback+)>%% <!ELEMENT typedecls (typedecl+)>%% <!ELEMENT typedecl (typedef, description?)>%% <!ELEMENT functions (function+)>%% NEW-OPTIONS: private, hidden, todo%% DEFER-OPTIONS: edoc_extract:source/4module(Module, Entries, Env, Opts) -> Name = atom_to_list(Module#module.name), HeaderEntry = get_entry(module, Entries), HeaderTags = HeaderEntry#entry.data, AllTags = get_all_tags(Entries), Functions = function_filter(Entries, Opts), Out = {module, ([{name, Name}, {root, Env#env.root}] ++ case is_private(HeaderTags) of true -> [{private, "yes"}]; false -> [] end ++ case is_hidden(HeaderTags) of true -> [{hidden, "yes"}]; false -> [] end), (behaviours(Module#module.attributes, Env) ++ get_doc(HeaderTags) ++ authors(HeaderTags) ++ get_version(HeaderTags) ++ get_since(HeaderTags) ++ get_copyright(HeaderTags) ++ get_deprecated(HeaderTags) ++ sees(HeaderTags, Env) ++ references(HeaderTags) ++ todos(HeaderTags, Opts) ++ [{typedecls, types(AllTags, Env)}, {functions, functions(Functions, Env, Opts)} | callbacks(Functions, Module, Env, Opts)]) }, xmerl_lib:expand_element(Out).get_all_tags(Es) -> lists:flatmap(fun (#entry{data = Ts}) -> Ts end, Es).is_private(Ts) -> get_tags(private, Ts) =/= [].description([]) -> [];description(Desc) -> ShortDesc = edoc_lib:get_first_sentence(Desc), [{description, [{briefDescription, ShortDesc}, {fullDescription, Desc}]}].types(Tags, Env) -> [{typedecl, [{label, edoc_types:to_label(Def)}], [edoc_types:to_xml(Def, Env)] ++ description(Doc)} || #tag{name = type, data = {Def, Doc}} <- Tags].functions(Es, Env, Opts) -> [function(N, As, Export, Ts, Env, Opts) || #entry{name = {_,_}=N, args = As, export = Export, data = Ts} <- Es].function_filter(Es, Opts) -> Private = proplists:get_bool(private, Opts), Hidden = proplists:get_bool(hidden, Opts), [E || E <- Es, function_filter(E, Private, Hidden)].%% Note that only entries whose names have the form {_,_} are functions.function_filter(#entry{name = {_,_}, export = Export, data = Ts}, Private, Hidden) -> ((Export andalso not is_private(Ts)) orelse Private) andalso ((not is_hidden(Ts)) orelse Hidden);function_filter(_, _, _) -> false.is_hidden(Ts) -> get_tags(hidden, Ts) =/= [].callbacks(Es, Module, Env, Opts) -> case lists:any(fun (#entry{name = {behaviour_info, 1}}) -> true; (_) -> false end, Es) of true -> case catch (Module#module.name):behaviour_info(callbacks) of {'EXIT', _} -> []; Fs -> Fs1 = [{F,A} || {F,A} <- Fs, is_atom(F), is_integer(A)], if Fs1 == [] -> []; true -> [{callbacks, [callback(F, Env, Opts) || F <- Fs1]}] end end; false -> [] end.%% <!ELEMENT callback EMPTY>%% <!ATTLIST callback%% name CDATA #REQUIRED%% arity CDATA #REQUIRED>callback({N, A}, _Env, _Opts) -> {callback, [{name, atom_to_list(N)}, {arity, integer_to_list(A)}], []}.%% <!ELEMENT function (args, typespec?, returns?, throws?, equiv?,%% description?, since?, deprecated?, see*, todo?)>%% <!ATTLIST function%% name CDATA #REQUIRED%% arity CDATA #REQUIRED%% exported NMTOKEN(yes | no) #REQUIRED%% label CDATA #IMPLIED>%% <!ELEMENT args (arg*)>%% <!ELEMENT arg (argName, description?)>%% <!ELEMENT argName (#PCDATA)>%% <!ELEMENT returns (description)>%% <!ELEMENT throws (type, localdef*)>%% <!ELEMENT equiv (expr, see?)>%% <!ELEMENT expr (#PCDATA)>function({N, A}, As, Export, Ts, Env, Opts) -> {Args, Ret, Spec} = signature(Ts, As, Env), {function, [{name, atom_to_list(N)}, {arity, integer_to_list(A)}, {exported, case Export of true -> "yes"; false -> "no" end}, {label, edoc_refs:to_label(edoc_refs:function(N, A))}], [{args, [{arg, [{argName, [atom_to_list(A)]}] ++ description(D)} || {A, D} <- Args]}] ++ Spec ++ case Ret of [] -> []; _ -> [{returns, description(Ret)}] end ++ get_throws(Ts, Env) ++ get_equiv(Ts, Env) ++ get_doc(Ts) ++ get_since(Ts) ++ get_deprecated(Ts, N, A, Env) ++ sees(Ts, Env) ++ todos(Ts, Opts) }.get_throws(Ts, Env) -> case get_tags(throws, Ts) of [Throws] -> Type = Throws#tag.data, [edoc_types:to_xml(Type, Env)]; [] -> [] end.get_equiv(Ts, Env) -> case get_tags(equiv, Ts) of [Equiv] -> Expr = Equiv#tag.data, See = case get_expr_ref(Equiv#tag.data) of none -> []; Ref -> [see(Ref, [edoc_refs:to_string(Ref)], Env)] end, [{equiv, [{expr, [erl_prettypr:format(Expr)]} | See]}]; [] -> [] end.get_doc(Ts) -> case get_tags(doc, Ts) of [T] -> description(T#tag.data); [] -> [] end.get_copyright(Ts) -> get_pcdata_tag(copyright, Ts).get_version(Ts) -> get_pcdata_tag(version, Ts).get_since(Ts) -> get_pcdata_tag(since, Ts).get_pcdata_tag(Tag, Ts) -> case get_tags(Tag, Ts) of [T] -> [{Tag, [T#tag.data]}]; [] -> [] end.%% Deprecation declarations for xref:%%%% -deprecated(Info).%% Info = Spec | [Spec]%% Spec = module | {F,A} | {F,A,Details}}%% Details = next_version | next_major_release | eventually%% (EXTENSION: | string() | {M1,F1,A1}}%% TODO: use info from '-deprecated(...)' (xref-)declarations.get_deprecated(Ts) -> case get_tags(deprecated, Ts) of [T] -> [{deprecated, description(T#tag.data)}]; [] -> [] end.get_deprecated(Ts, F, A, Env) -> case get_deprecated(Ts) of [] -> M = Env#env.module, case erl_internal:obsolete(M, F, A) of {true, {M1, F1, A1}} -> Text = if M =:= M1 -> io_lib:fwrite("~w/~w", [F1, A1]); true -> io_lib:fwrite("~w:~w/~w", [M1, F1, A1]) end, Ref = if M =:= M1 -> edoc_refs:function(F1, A1); true -> edoc_refs:function(M1, F1, A1) end, Desc = ["Use ", {a, href(Ref, Env), [{code, [Text]}]}, " instead."], [{deprecated, description(Desc)}]; {true, Text} -> [{deprecated, description([Text])}]; _ -> [] end; Es -> Es end.get_expr_ref(Expr) -> case catch {ok, erl_syntax_lib:analyze_application(Expr)} of {ok, {F, A}} when is_atom(F), is_integer(A) -> edoc_refs:function(F, A); {ok, {M, {F, A}}} when is_atom(M), is_atom(F), is_integer(A) -> edoc_refs:function(M, F, A); _ -> none end.authors(Ts) -> [author(Info) || #tag{data = Info} <- get_tags(author, Ts)].%% <!ATTLIST author%% name CDATA #REQUIRED%% email CDATA #IMPLIED%% website CDATA #IMPLIED>author({Name, Mail, URI}) -> %% At least one of Name and Mail must be nonempty in the tag. {author, ([{name, if Name =:= "" -> Mail; true -> Name end}] ++ if Mail =:= "" -> case lists:member($@, Name) of true -> [{email, Name}]; false -> [] end; true -> [{email, Mail}] end ++ if URI =:= "" -> []; true -> [{website, URI}] end), []}.behaviours(As, Env) -> [{behaviour, href(edoc_refs:module(B), Env), [atom_to_list(B)]} || {behaviour, B} <- As, is_atom(B)].sees(Tags, Env) -> Ts = get_tags(see, Tags), Rs = lists:keysort(1, [Data || #tag{data = Data} <- Ts]), [see(Ref, XML, Env) || {Ref, XML} <- Rs].see(Ref, [], Env) -> see(Ref, [edoc_refs:to_string(Ref)], Env);see(Ref, XML, Env) -> {see, [{name, edoc_refs:to_string(Ref)}] ++ href(Ref, Env), XML}.href(Ref, Env) -> [{href, edoc_refs:get_uri(Ref, Env)}] ++ case edoc_refs:is_top(Ref, Env) of true -> [{target, "_top"}]; false -> [] end.references(Tags) -> [{reference, XML} || #tag{data = XML} <- get_tags(reference, Tags)].todos(Tags, Opts) -> case proplists:get_bool(todo, Opts) of true -> [{todo, XML} || #tag{data = XML} <- get_tags('todo', Tags)]; false -> [] end.signature(Ts, As, Env) -> case get_tags(spec, Ts) of [T] -> Spec = T#tag.data, R = merge_returns(Spec, Ts), As0 = edoc_types:arg_names(Spec), Ds0 = edoc_types:arg_descs(Spec), %% choose names in spec before names in code P = dict:from_list(params(Ts)), As1 = merge_args(As0, As, Ds0, P), %% check_params(As1, P), Spec1 = edoc_types:set_arg_names(Spec, [A || {A,_} <- As1]), {As1, R, [edoc_types:to_xml(Spec1, Env)]}; [] -> S = sets:new(), {fix_argnames(As, S, 1), [], []} end.params(Ts) -> [T#tag.data || T <- get_tags(param, Ts)].%% check_params(As, P) ->%% case dict:keys(P) -- [N || {N,_} <- As] of%% [] -> ok;%% Ps -> error %% TODO: report @param declarations with no match%% end.merge_returns(Spec, Ts) -> case get_tags(return, Ts) of [] -> case edoc_types:range_desc(Spec) of "" -> []; Txt -> [Txt] end; [T] -> T#tag.data end.%% Names are chosen from the first list (the specification) if possible.%% Descriptions specified with @param (in P dict) override descriptions%% from the spec (in Ds).merge_args(As, As1, Ds, P) -> merge_args(As, As1, Ds, [], P, sets:new(), 1).merge_args(['_' | As], ['_' | As1], [D | Ds], Rs, P, S, N) -> merge_args(As, As1, Ds, Rs, P, S, N, make_name(N, S), D);merge_args(['_' | As], [A | As1], [D | Ds], Rs, P, S, N) -> merge_args(As, As1, Ds, Rs, P, S, N, A, D);merge_args([A | As], [_ | As1], [D | Ds], Rs, P, S, N) -> merge_args(As, As1, Ds, Rs, P, S, N, A, D);merge_args([], [], [], Rs, _P, _S, _N) -> lists:reverse(Rs).merge_args(As, As1, Ds, Rs, P, S, N, A, D0) -> D = case dict:find(A, P) of {ok, D1} -> D1; error when D0 =:= [] -> []; % no description error -> [D0] % a simple-xml text element end, merge_args(As, As1, Ds, [{A, D} | Rs], P, sets:add_element(A, S), N + 1).fix_argnames(['_' | As], S, N) -> A = make_name(N, S), [A | fix_argnames(As, sets:add_element(A, S), N + 1)];fix_argnames([A | As], S, N) -> [A | fix_argnames(As, sets:add_element(A, S), N + 1)];fix_argnames([], _S, _N) -> [].make_name(N, S) -> make_name(N, S, "X").make_name(N, S, Base) -> A = list_to_atom(Base ++ integer_to_list(N)), case sets:is_element(A, S) of true -> make_name(N, S, Base ++ "x"); false -> A end.get_entry(Name, [#entry{name = Name} = E | _Es]) -> E;get_entry(Name, [_ | Es]) -> get_entry(Name, Es).get_tags(Tag, [#tag{name = Tag} = T | Ts]) -> [T | get_tags(Tag, Ts)];get_tags(Tag, [_ | Ts]) -> get_tags(Tag, Ts);get_tags(_, []) -> [].%% ---------------------------------------------------------------------type(T, Env) -> xmerl_lib:expand_element({type, [edoc_types:to_xml(T, Env)]}).%% <!ELEMENT package (description?, author*, copyright?, version?,%% since?, deprecated?, see*, reference*, todo?,%% modules)>%% <!ATTLIST package%% name CDATA #REQUIRED%% root CDATA #IMPLIED>%% <!ELEMENT modules (module+)>package(Package, Tags, Env, Opts) -> Env1 = Env#env{package = Package, root = edoc_refs:relative_package_path('', Package)}, xmerl_lib:expand_element(package_1(Package, Tags, Env1, Opts)).package_1(Package, Tags, Env, Opts) -> {package, [{root, Env#env.root}], ([{packageName, [atom_to_list(Package)]}] ++ get_doc(Tags) ++ authors(Tags) ++ get_copyright(Tags) ++ get_version(Tags) ++ get_since(Tags) ++ get_deprecated(Tags) ++ sees(Tags, Env) ++ references(Tags) ++ todos(Tags, Opts)) }.%% <!ELEMENT overview (title, description?, author*, copyright?, version?,%% since?, see*, reference*, todo?, packages, modules)>%% <!ATTLIST overview%% root CDATA #IMPLIED>%% <!ELEMENT title (#PCDATA)>overview(Title, Tags, Env, Opts) -> Env1 = Env#env{package = '', root = ""}, xmerl_lib:expand_element(overview_1(Title, Tags, Env1, Opts)).overview_1(Title, Tags, Env, Opts) -> {overview, [{root, Env#env.root}], ([{title, [get_title(Tags, Title)]}] ++ get_doc(Tags) ++ authors(Tags) ++ get_copyright(Tags) ++ get_version(Tags) ++ get_since(Tags) ++ sees(Tags, Env) ++ references(Tags) ++ todos(Tags, Opts)) }.get_title(Ts, Default) -> case get_tags(title, Ts) of [T] -> T#tag.data; [] -> Default end.
⌨️ 快捷键说明
复制代码Ctrl + C
搜索代码Ctrl + F
全屏模式F11
增大字号Ctrl + =
减小字号Ctrl + -
显示快捷键?