Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

Lookup type specs in seperate type specs xml files (fixes #12)

  • Loading branch information...
commit b380108136872e261638b35a728d2adb03652968 1 parent 8b97987
@daleharvey authored
View
BIN  erldocs 100644 → 100755
Binary file not shown
View
156 priv/bin/specs_gen.escript
@@ -0,0 +1,156 @@
+#!/usr/bin/env escript
+%% -*- erlang -*-
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2011. All Rights Reserved.
+%%
+%% The contents of this file are subject to the Erlang Public License,
+%% Version 1.1, (the "License"); you may not use this file except in
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+
+%%% <script> [-I<dir>]... [-o<dir>] [-module Module] [File]
+%%%
+%%% Use EDoc and the layout module 'otp_specs' to create an XML file
+%%% containing Dialyzer types and specifications (-type, -spec).
+%%%
+%%% Options:
+%%%
+%%% "-o<dir>" The output directory for the created file.
+%%% Default is ".".
+%%% "-I<dir>" Directory to be searched when including a file.
+%%% "-module Module"
+%%% Module name to use when there is no File argument.
+%%% A empty specifications file will be created.
+%%% Exactly one of -module Module and File must be given.
+%%%
+%%% The name of the generated file is "specs_<module>.xml". Its exact
+%%% format is not further described here.
+
+main(Args) ->
+ case catch parse(Args, [], ".", no_module) of
+ {ok, FileSpec, InclFs, Dir} ->
+ call_edoc(FileSpec, InclFs, Dir);
+ {error, Msg} ->
+ io:format("~s\n", [Msg]),
+ usage()
+ end.
+
+parse(["-o"++Dir | Opts], InclFs, _, Module) ->
+ parse(Opts, InclFs, Dir, Module);
+parse(["-I"++I | Opts], InclFs, Dir, Module) ->
+ parse(Opts, [I | InclFs], Dir, Module);
+parse(["-module", Module | Opts], InclFs, Dir, _) ->
+ parse(Opts, InclFs, Dir, Module);
+parse([File], InclFs, Dir, no_module) ->
+ {ok, {file, File}, lists:reverse(InclFs), Dir};
+parse([_], _, _, _) ->
+ {error, io_lib:format("Cannot have both -module option and file", [])};
+parse([], _, _, no_module) ->
+ {error, io_lib:format("Missing -module option or file", [])};
+parse([], InclFs, Dir, Module) ->
+ {ok, {module, Module}, lists:reverse(InclFs), Dir};
+parse(Args, _, _, _) ->
+ {error, io_lib:format("Bad arguments: ~p", [Args])}.
+
+usage() ->
+ io:format("usage: ~s [-I<include_dir>]... [-o<out_dir>] "
+ "[-module <module>] [file]\n", [escript:script_name()]),
+ halt(1).
+
+call_edoc(FileSpec, InclFs, Dir) ->
+ ReadOpts = [{includes, InclFs}, {preprocess, true}],
+ ExtractOpts = [{report_missing_type, false}],
+ LayoutOpts = [{pretty_printer, erl_pp}, {layout, otp_specs}],
+ File = case FileSpec of
+ {file, File0} -> File0;
+ {module, Module0} -> Module0
+ end,
+ try
+ Fs = case FileSpec of
+ {file, _} ->
+ Fs0 = read_file(File, ReadOpts),
+ clauses(Fs0);
+ {module, Module} ->
+ [{attribute,0,module,list_to_atom(Module)}]
+ end,
+ Doc = extract(File, Fs, ExtractOpts),
+ Text = edoc:layout(Doc, LayoutOpts),
+ ok = write_text(Text, File, Dir),
+ rename(Dir, File)
+ catch
+ _:_ ->
+ io:format("EDoc could not process file '~s'\n", [File]),
+ clean_up(Dir),
+ halt(3)
+ end.
+
+read_file(File, Opts) ->
+ edoc:read_source(File, Opts).
+
+extract(File, Forms, Opts) ->
+ Env = edoc_lib:get_doc_env([], [], [], _Opts=[]),
+ {_Module, Doc} = edoc_extract:source(Forms, File, Env, Opts),
+ Doc.
+
+clauses(Fs) ->
+ clauses(Fs, no).
+
+clauses([], no) ->
+ [];
+clauses([F | Fs], Spec) ->
+ case F of
+ {attribute,_,spec,_} ->
+ clauses(Fs, F);
+ {function,_,_N,_A,_Cls} when Spec =/= no->
+ {attribute,_,spec,{Name,FunTypes}} = Spec,
+ %% [throw({no,Name,{_N,_A}}) || Name =/= {_N,_A}],
+ %% EDoc doesn't care if a function appears more than once;
+ %% this is how overloaded specs are handled:
+ (lists:append([[setelement(4, Spec, {Name,[T]}),F] ||
+ T <- FunTypes])
+ ++ clauses(Fs, no));
+ _ ->
+ [F | clauses(Fs, Spec)]
+ end.
+
+write_text(Text, File, Dir) ->
+ Base = filename:basename(File, ".erl"),
+ OutFile = filename:join(Dir, Base) ++ ".specs",
+ case file:write_file(OutFile, Text) of
+ ok ->
+ ok;
+ {error, R} ->
+ R1 = file:format_error(R),
+ io:format("could not write file '~s': ~s\n", [File, R1]),
+ halt(2)
+ end.
+
+rename(Dir, F) ->
+ Mod = filename:basename(F, ".erl"),
+ Old = filename:join(Dir, Mod ++ ".specs"),
+ New = filename:join(Dir, "specs_" ++ Mod ++ ".xml"),
+ case file:rename(Old, New) of
+ ok ->
+ ok;
+ {error, R} ->
+ R1 = file:format_error(R),
+ io:format("could not rename file '~s': ~s\n", [New, R1]),
+ halt(2)
+ end.
+
+clean_up(Dir) ->
+ _ = [file:delete(filename:join(Dir, F)) ||
+ F <- ["packages-frame.html",
+ "overview-summary.html",
+ "modules-frame.html",
+ "index.html", "erlang.png", "edoc-info"]],
+ ok.
View
61 src/erldocs_core.erl
@@ -53,6 +53,12 @@ build_file_map(Conf, AppName, File) ->
log("Generating HTML - ~s~n", [bname(File, ".xml")]),
{Type, _Attr, Content} = read_xml(Conf, File),
+ TypeSpecsFile = filename:join([dest(Conf), ".xml", "specs_" ++ bname(File)]),
+ TypeSpecs = case read_xml(Conf, TypeSpecsFile) of
+ {error, _, _} -> [];
+ {module, _, Specs} -> strip_whitespace(Specs)
+ end,
+
case lists:member(Type, buildable()) of
false -> [];
true ->
@@ -76,7 +82,7 @@ build_file_map(Conf, AppName, File) ->
% strip silly shy characters
Funs = get_funs(AppName, Module, lists:keyfind(funcs, 1, Xml)),
- ok = render(Type, AppName, Module, Content, Conf),
+ ok = render(Type, AppName, Module, Content, TypeSpecs, Conf),
case lists:member({AppName, Module}, ignore()) of
true -> [];
@@ -109,9 +115,11 @@ ensure_docsrc(AppDir, Conf) ->
XMLDir = filename:join([dest(Conf), ".xml", bname(AppDir)]),
filelib:ensure_dir(XMLDir ++ "/"),
+ SpecsDest = filename:join([dest(Conf), ".xml"]),
+
[ begin
- log("Generating Type Specs for: ~s~n", [File]),
- os:cmd("./priv/bin/specs_gen.escript -o.xml/ " ++ File)
+ log("Generating Type Specs - ~s~n", [File]),
+ os:cmd("./priv/bin/specs_gen.escript -o" ++ SpecsDest ++ " " ++ File)
end || File <- ErlFiles],
%% Return the complete list of XML files
@@ -220,17 +228,17 @@ javascript_index(Conf, FIndex) ->
ok = file:write_file([dest(Conf), "/erldocs_index.js"], Js).
-render(cref, App, Mod, Xml, Conf) ->
- render(erlref, App, Mod, Xml, Conf);
+render(cref, App, Mod, Xml, Types, Conf) ->
+ render(erlref, App, Mod, Xml, Types, Conf);
-render(erlref, App, Mod, Xml, Conf) ->
+render(erlref, App, Mod, Xml, Types, Conf) ->
File = filename:join([dest(Conf), App, Mod ++ ".html"]),
ok = filelib:ensure_dir(filename:dirname(File) ++ "/"),
- Acc = [{ids,[]}, {list, ul}, {functions, []}],
+ Acc = [{ids,[]}, {list, ul}, {functions, []}, {types, Types}],
- {[_Id, _List, {functions, Funs}], NXml}
+ {[_Id, _List, {functions, Funs}, {types, _}], NXml}
= render(fun tr_erlref/2, Xml, Acc),
XmlFuns = [{li, [], [{a, [{href, "#"++X}], [X]}]}
@@ -393,23 +401,50 @@ tr_erlref({warning, [], Child}, _Acc) ->
{'div', [{class, "warning"}], [{h2, [], ["Warning!"]} | Child]};
tr_erlref({name, [], [{ret,[],[Ret]}, {nametext,[],[Desc]}]}, _Acc) ->
{pre, [], [Ret ++ " " ++ Desc]};
-tr_erlref({name, [{name, Name}, {arity, N}], []}, [{ids, Ids}, List, {functions, Funs}]) ->
+tr_erlref({name, [{name, Name}, {arity, N}], []}, Acc) ->
+ [{ids, Ids}, List, {functions, Funs}, {types, Types}] = Acc,
+ PName = case find_spec(Name, N, Types) of
+ {ok, Tmp} -> Tmp;
+ _ -> Name ++ "/" ++ N
+ end,
NName = inc_name(Name, Ids, 0),
- { {h3, [{id, Name ++ "/" ++ N}], [Name ++ "/" ++ N]},
- [{ids, [NName | Ids]}, List, {functions, [NName|Funs]}]};
-tr_erlref({name, [], Child}, [{ids, Ids}, List, {functions, Funs}]) ->
+ { {h3, [{id, Name ++ "/" ++ N}], [PName]},
+ [{ids, [NName | Ids]}, List, {functions, [NName|Funs]}, {types, Types}]};
+tr_erlref({name, [], Child}, [{ids, Ids}, List, {functions, Funs}, {types, Types}]) ->
case make_name(Child) of
ignore -> ignore;
Name ->
NName = inc_name(Name, Ids, 0),
{ {h3, [{id, NName}], [Child]},
- [{ids, [NName | Ids]}, List, {functions, [NName|Funs]}]}
+ [{ids, [NName | Ids]}, List, {functions, [NName|Funs]}, {types, Types}]}
end;
tr_erlref({fsummary, [], _Child}, _Acc) ->
ignore;
tr_erlref(Else, _Acc) ->
Else.
+find_spec(_Name, _Arity, []) ->
+ spec_not_found;
+
+find_spec(Name, Arity, [{spec, [], Specs} | Rest]) ->
+ {name, _, [SpecName]} = lists:keyfind(name, 1, Specs),
+ {arity, _, [ArityName]} = lists:keyfind(arity, 1, Specs),
+ case SpecName =:= Name andalso ArityName =:= Arity of
+ true ->
+ {contract, _, Contracts} = lists:keyfind(contract, 1, Specs),
+ {clause, _, Clause} = lists:keyfind(clause, 1, Contracts),
+ {head, _, Head} = lists:keyfind(head, 1, Clause),
+ case io_lib:deep_char_list(Head) of
+ true -> {ok, lists:flatten(Head)};
+ false -> invalid_name
+ end;
+ false ->
+ find_spec(Name, Arity, Rest)
+ end;
+find_spec(Name, Arity, [_ | Rest]) ->
+ find_spec(Name, Arity, Rest).
+
+
nname(Name, 0) -> Name;
nname(Name, Acc) -> Name ++ "-" ++ integer_to_list(Acc).
Please sign in to comment.
Something went wrong with that request. Please try again.