1#!/usr/bin/env escript 2%% -*- erlang -*- 3%% %CopyrightBegin% 4%% 5%% Copyright Ericsson AB 2011-2016. All Rights Reserved. 6%% 7%% Licensed under the Apache License, Version 2.0 (the "License"); 8%% you may not use this file except in compliance with the License. 9%% You may obtain a copy of the License at 10%% 11%% http://www.apache.org/licenses/LICENSE-2.0 12%% 13%% Unless required by applicable law or agreed to in writing, software 14%% distributed under the License is distributed on an "AS IS" BASIS, 15%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16%% See the License for the specific language governing permissions and 17%% limitations under the License. 18%% 19%% %CopyrightEnd% 20 21%%% <script> [-I<dir>]... [-o<dir>] [-module Module] [File] 22%%% 23%%% Use EDoc and the layout module 'docgen_otp_specs' to create an XML file 24%%% containing Dialyzer types and specifications (-type, -spec). 25%%% 26%%% Options: 27%%% 28%%% "-o<dir>" The output directory for the created file. 29%%% Default is ".". 30%%% "-I<dir>" Directory to be searched when including a file. 31%%% "-module Module" 32%%% Module name to use when there is no File argument. 33%%% A empty specifications file will be created. 34%%% Exactly one of -module Module and File must be given. 35%%% 36%%% The name of the generated file is "specs_<module>.xml". Its exact 37%%% format is not further described here. 38 39main(Args) -> 40 case catch parse(Args, [], ".", no_module) of 41 {ok, FileSpec, InclFs, Dir} -> 42 call_edoc(FileSpec, InclFs, Dir); 43 {error, Msg} -> 44 io:format("~s\n", [Msg]), 45 usage() 46 end. 47 48parse(["-o"++Dir | Opts], InclFs, _, Module) -> 49 parse(Opts, InclFs, Dir, Module); 50parse(["-I"++I | Opts], InclFs, Dir, Module) -> 51 parse(Opts, [I | InclFs], Dir, Module); 52parse(["-module", Module | Opts], InclFs, Dir, _) -> 53 parse(Opts, InclFs, Dir, Module); 54parse([File], InclFs, Dir, no_module) -> 55 {ok, {file, File}, lists:reverse(InclFs), Dir}; 56parse([_], _, _, _) -> 57 {error, io_lib:format("Cannot have both -module option and file", [])}; 58parse([], _, _, no_module) -> 59 {error, io_lib:format("Missing -module option or file", [])}; 60parse([], InclFs, Dir, Module) -> 61 {ok, {module, Module}, lists:reverse(InclFs), Dir}; 62parse(Args, _, _, _) -> 63 {error, io_lib:format("Bad arguments: ~p", [Args])}. 64 65usage() -> 66 io:format("usage: ~s [-I<include_dir>]... [-o<out_dir>] " 67 "[-module <module>] [file]\n", [escript:script_name()]), 68 halt(1). 69 70call_edoc(FileSpec, InclFs, Dir) -> 71 ReadOpts = [{includes, InclFs}, {preprocess, true}], 72 ExtractOpts = [{report_missing_type, false}], 73 LayoutOpts = [{pretty_printer, erl_pp}, {layout, docgen_otp_specs}], 74 File = case FileSpec of 75 {file, File0} -> File0; 76 {module, Module0} -> Module0 77 end, 78 try 79 Fs = case FileSpec of 80 {file, _} -> 81 Fs0 = read_file(File, ReadOpts), 82 clauses(Fs0); 83 {module, Module} -> 84 [{attribute,0,module,list_to_atom(Module)}] 85 end, 86 Doc = extract(File, Fs, ExtractOpts), 87 Text = edoc:layout(Doc, LayoutOpts), 88 ok = write_text(Text, File, Dir), 89 rename(Dir, File) 90 catch 91 _:_ -> 92 io:format("EDoc could not process file '~s'\n", [File]), 93 clean_up(Dir), 94 halt(3) 95 end. 96 97read_file(File, Opts) -> 98 edoc:read_source(File, Opts). 99 100extract(File, Forms, Opts) -> 101 Env = edoc_lib:get_doc_env([], [], _Opts=[]), 102 {_Module, Doc} = edoc_extract:source(Forms, File, Env, Opts), 103 Doc. 104 105clauses(Fs) -> 106 clauses(Fs, no). 107 108clauses([], no) -> 109 []; 110clauses([F | Fs], Spec) -> 111 case F of 112 {attribute,_,spec,_} -> 113 clauses(Fs, F); 114 {function,_,_N,_A,_Cls} when Spec =/= no-> 115 {attribute,_,spec,{Name,FunTypes}} = Spec, 116 %% [throw({no,Name,{_N,_A}}) || Name =/= {_N,_A}], 117 %% EDoc doesn't care if a function appears more than once; 118 %% this is how overloaded specs are handled: 119 (lists:append([[setelement(4, Spec, {Name,[T]}),F] || 120 T <- FunTypes]) 121 ++ clauses(Fs, no)); 122 _ -> 123 [F | clauses(Fs, Spec)] 124 end. 125 126write_text(Text, File, Dir) -> 127 Base = filename:basename(File, ".erl"), 128 OutFile = filename:join(Dir, Base) ++ ".specs", 129 case file:write_file(OutFile, Text) of 130 ok -> 131 ok; 132 {error, R} -> 133 R1 = file:format_error(R), 134 io:format("could not write file '~s': ~s\n", [File, R1]), 135 halt(2) 136 end. 137 138rename(Dir, F) -> 139 Mod = filename:basename(F, ".erl"), 140 Old = filename:join(Dir, Mod ++ ".specs"), 141 New = filename:join(Dir, "specs_" ++ Mod ++ ".xml"), 142 case file:rename(Old, New) of 143 ok -> 144 ok; 145 {error, R} -> 146 R1 = file:format_error(R), 147 io:format("could not rename file '~s': ~s\n", [New, R1]), 148 halt(2) 149 end. 150 151clean_up(Dir) -> 152 _ = [file:delete(filename:join(Dir, F)) || 153 F <- ["packages-frame.html", 154 "overview-summary.html", 155 "modules-frame.html", 156 "index.html", "erlang.png", "edoc-info"]], 157 ok. 158