aboutsummaryrefslogblamecommitdiffstats
path: root/lib/erl_docgen/priv/bin/specs_gen.escript
blob: e8a8f14e3a92b37dcf86e974d17876b04f02528c (plain) (tree)
1
2
3
4
5



                      
                                                   















                                                                         
                                                                           








                                                                 
                                                           




































                                                                           

                                                        
                                                                        















                                                                


                                                                    
                          


                   



                                 
                                                 



































                                                                      












                                                                     
                





                                                            
#!/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 'docgen_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, docgen_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.