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 Is = filelib:wildcard(I), 52 parse(Opts, Is ++ InclFs, Dir, Module); 53parse(["-module", Module | Opts], InclFs, Dir, _) -> 54 parse(Opts, InclFs, Dir, Module); 55parse([File], InclFs, Dir, no_module) -> 56 {ok, {file, File}, lists:reverse(InclFs), Dir}; 57parse([_], _, _, _) -> 58 {error, io_lib:format("Cannot have both -module option and file", [])}; 59parse([], _, _, no_module) -> 60 {error, io_lib:format("Missing -module option or file", [])}; 61parse([], InclFs, Dir, Module) -> 62 {ok, {module, Module}, lists:reverse(InclFs), Dir}; 63parse(Args, _, _, _) -> 64 {error, io_lib:format("Bad arguments: ~p", [Args])}. 65 66usage() -> 67 io:format("usage: ~s [-I<include_dir>]... [-o<out_dir>] " 68 "[-module <module>] [file]\n", [escript:script_name()]), 69 halt(1). 70 71call_edoc(FileSpec, InclFs, Dir) -> 72 ReadOpts = [{includes, InclFs}, {preprocess, true}], 73 ExtractOpts = [{report_missing_type, false}], 74 LayoutOpts = [{pretty_printer, erl_pp}, {layout, docgen_otp_specs}], 75 File = case FileSpec of 76 {file, File0} -> File0; 77 {module, Module0} -> Module0 78 end, 79 try 80 Fs = case FileSpec of 81 {file, _} -> 82 read_file(File, ReadOpts); 83 {module, Module} -> 84 [{attribute,erl_anno:new(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 E:R:ST -> 92 io:format("EDoc could not process file '~s'\n", [File]), 93 io:format("~p:~p ~p\n", [E,R,ST]), 94 clean_up(Dir), 95 halt(3) 96 end. 97 98read_file(File, Opts) -> 99 edoc:read_source(File, Opts). 100 101extract(File, Forms, Opts) -> 102 Env = edoc_lib:get_doc_env([], [], [{app_default,"specs:/"}]), 103 {_Module, Doc} = edoc_extract:source(Forms, File, Env, Opts), 104 Doc. 105 106write_text(Text, File, Dir) -> 107 Base = filename:basename(File, ".erl"), 108 OutFile = filename:join(Dir, Base) ++ ".specs", 109 case file:write_file(OutFile, Text) of 110 ok -> 111 ok; 112 {error, R} -> 113 R1 = file:format_error(R), 114 io:format("could not write file '~s': ~s\n", [OutFile, R1]), 115 halt(2) 116 end. 117 118rename(Dir, F) -> 119 Mod = filename:basename(F, ".erl"), 120 Old = filename:join(Dir, Mod ++ ".specs"), 121 New = filename:join(Dir, "specs_" ++ Mod ++ ".xml"), 122 case file:rename(Old, New) of 123 ok -> 124 ok; 125 {error, R} -> 126 R1 = file:format_error(R), 127 io:format("could not rename file '~s': ~s\n", [New, R1]), 128 halt(2) 129 end. 130 131clean_up(Dir) -> 132 _ = [file:delete(filename:join(Dir, F)) || 133 F <- ["packages-frame.html", 134 "overview-summary.html", 135 "modules-frame.html", 136 "index.html", "erlang.png", "edoc-info"]], 137 ok. 138