1%% @doc EDoc command line interface
2-module(edoc_cli).
3-export([main/1]).
4
5%% TODO: accept `private'/`hidden' and forward accordingly
6
7main([]) ->
8    print(usage());
9main(Args) ->
10    Opts = parse_args(Args),
11    print("Running with opts:\n~p\n", [Opts]),
12    ok = code:add_pathsa(maps:get(code_paths, Opts)),
13    case Opts of
14        #{run := app, app := App} ->
15            edoc:application(App, edoc_opts(Opts));
16        #{run := files, files := Files} ->
17            edoc:files(Files, edoc_opts(Opts))
18    end.
19
20parse_args(Args) ->
21    Init = #{mode => default,
22	     run => app,
23	     app => no_app,
24	     files => [],
25	     code_paths => [],
26	     out_dir => undefined,
27	     include_paths => [],
28	     continue => false},
29    check_opts(maps:without([continue], parse_args(Args, Init))).
30
31parse_args([], Opts) ->
32    Opts;
33parse_args(["-" ++ _ = Arg | Args], #{continue := Cont} = Opts) when Cont /= false ->
34    parse_args([Arg | Args], Opts#{continue := false});
35
36parse_args(["-chunks" | Args], Opts) ->
37    parse_args(Args, Opts#{mode := chunks});
38
39parse_args(["-o", OutDir | Args], Opts) ->
40    parse_args(Args, Opts#{out_dir := OutDir});
41
42parse_args(["-pa", Path | Args], Opts) ->
43    #{code_paths := Paths} = Opts,
44    parse_args(Args, Opts#{code_paths := Paths ++ [Path]});
45
46parse_args(["-I", Path | Args], Opts) ->
47    #{include_paths := Paths} = Opts,
48    parse_args(Args, Opts#{include_paths := Paths ++ [Path]});
49
50parse_args(["-app", App | Args], Opts) ->
51    parse_args(Args, Opts#{run := app, app := list_to_atom(App)});
52
53parse_args(["-files" | Args], Opts) ->
54    parse_args(Args, Opts#{run := files, continue := files});
55parse_args([File | Args], #{continue := files} = Opts) ->
56    #{files := Files} = Opts,
57    parse_args(Args, Opts#{files := Files ++ [File]});
58
59parse_args([Unknown | Args], Opts) ->
60    print("Unknown option: ~ts\n", [Unknown]),
61    parse_args(Args, Opts).
62
63check_opts(Opts) ->
64    case Opts of
65	#{run := app, app := App} when is_atom(App), App /= no_app -> ok;
66	#{run := app, app := no_app} -> quit(no_app, Opts);
67	#{run := files, files := [_|_]} -> ok;
68	#{run := files, files := []} -> quit(no_files, Opts)
69    end,
70    #{mode := Mode,
71      out_dir := OutDir,
72      code_paths := CodePaths,
73      include_paths := IncludePaths} = Opts,
74    lists:member(Mode, [default, chunks]) orelse erlang:error(mode, Opts),
75    if
76	is_list(OutDir) -> ok;
77	OutDir =:= undefined -> ok;
78	OutDir =/= undefined -> erlang:error(out_dir, Opts)
79    end,
80    is_list(CodePaths) orelse erlang:error(code_paths),
81    is_list(IncludePaths) orelse erlang:error(include_paths),
82    Opts.
83
84quit(Reason, _Opts) ->
85    case Reason of
86	no_app ->
87	    print("No app name specified\n");
88	no_files ->
89	    print("No files to process\n")
90    end,
91    print("\n"),
92    print(usage()),
93    erlang:halt(1).
94
95edoc_opts(Opts) ->
96    EdocOpts = case maps:get(mode, Opts) of
97		   default ->
98		       [{preprocess, true}];
99		   chunks ->
100		       [{doclet, edoc_doclet_chunks},
101			{layout, edoc_layout_chunks},
102			{preprocess, true}]
103	       end,
104    OutDir = maps:get(out_dir, Opts),
105    [{includes, maps:get(include_paths, Opts)} | EdocOpts] ++
106    [{dir, OutDir} || OutDir /= undefined].
107
108print(Text) ->
109    print(Text, []).
110
111print(Fmt, Args) ->
112    io:format(Fmt, Args).
113
114usage() ->
115    "Usage: edoc [options] -app App\n"
116    "       edoc [options] -files Source...\n"
117    "\n"
118    "Run EDoc from the command line:\n"
119    "  -app App       \truns edoc:application/2; App is the application name\n"
120    "  -files Sources \truns edoc:files/2; Sources are .erl files\n"
121    "\n"
122    "Options:\n"
123    "  -chunks        \twhen present, only doc chunks are generated\n"
124    "  -o Dir         \tuse Dir for doc output\n"
125    "  -I IncPath     \tadd IncPath to EDoc include file search path;\n"
126    "                 \tcan be used multiple times\n"
127    "  -pa CodePath   \tadd CodePath to Erlang code path; can be used multiple times\n".
128