Skip to content

Commit ac6cb23

Browse files
rgrinbergjeremiedimino
authored andcommitted
First cut cmdliner support (#5)
Import the sources of cmdliner and rewrite the CLI of jbuilder
1 parent 1b84d5d commit ac6cb23

25 files changed

+4944
-54
lines changed

LICENSE.cmdliner

+13
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
Copyright (c) 2011 Daniel C. Bünzli
2+
3+
Permission to use, copy, modify, and/or distribute this software for any
4+
purpose with or without fee is hereby granted, provided that the above
5+
copyright notice and this permission notice appear in all copies.
6+
7+
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
8+
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
9+
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
10+
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
11+
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
12+
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
13+
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.

src/cmdliner.ml

+301
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,301 @@
1+
(*---------------------------------------------------------------------------
2+
Copyright (c) 2011 Daniel C. Bünzli. All rights reserved.
3+
Distributed under the ISC license, see terms at the end of the file.
4+
%%NAME%% %%VERSION%%
5+
---------------------------------------------------------------------------*)
6+
7+
open Result
8+
9+
module Manpage = Cmdliner_manpage
10+
module Arg = Cmdliner_arg
11+
module Term = struct
12+
13+
include Cmdliner_term
14+
15+
(* Deprecated *)
16+
17+
let man_format = Cmdliner_arg.man_format
18+
let pure = const
19+
20+
(* Terms *)
21+
22+
let ( $ ) = app
23+
24+
type 'a ret = [ `Ok of 'a | term_escape ]
25+
26+
let ret (al, v) =
27+
al, fun ei cl -> match v ei cl with
28+
| Ok (`Ok v) -> Ok v
29+
| Ok (`Error _ as err) -> Error err
30+
| Ok (`Help _ as help) -> Error help
31+
| Error _ as e -> e
32+
33+
let ret_of_result ?(usage = false) = function
34+
| Ok v -> `Ok v
35+
| Error (`Msg e) -> `Error (usage, e)
36+
37+
let ret_result ?usage t = app (const @@ ret_of_result ?usage) t
38+
39+
let term_result ?(usage = false) (al, v) =
40+
al, fun ei cl -> match v ei cl with
41+
| Ok (Ok _ as ok) -> ok
42+
| Ok (Error (`Msg e)) -> Error (`Error (usage, e))
43+
| Error _ as e -> e
44+
45+
let cli_parse_result (al, v) =
46+
al, fun ei cl -> match v ei cl with
47+
| Ok (Ok _ as ok) -> ok
48+
| Ok (Error (`Msg e)) -> Error (`Parse e)
49+
| Error _ as e -> e
50+
51+
let main_name =
52+
Cmdliner_info.Args.empty,
53+
(fun ei _ -> Ok (Cmdliner_info.(term_name @@ eval_main ei)))
54+
55+
let choice_names =
56+
let choice_name t = Cmdliner_info.term_name t in
57+
Cmdliner_info.Args.empty,
58+
(fun ei _ -> Ok (List.rev_map choice_name (Cmdliner_info.eval_choices ei)))
59+
60+
(* Term information *)
61+
62+
type exit_info = Cmdliner_info.exit
63+
let exit_info = Cmdliner_info.exit
64+
65+
let exit_status_success = 0
66+
let exit_status_internal_error = 124
67+
let exit_status_cli_error = 125
68+
let default_error_exits =
69+
[ exit_info exit_status_internal_error
70+
~doc:"on unexpected internal errors (bugs).";
71+
exit_info exit_status_cli_error
72+
~doc:"on command line parsing errors."; ]
73+
74+
let default_exits =
75+
(exit_info exit_status_success ~doc:"on success.") :: default_error_exits
76+
77+
type env_info = Cmdliner_info.env
78+
let env_info = Cmdliner_info.env
79+
80+
type info = Cmdliner_info.term
81+
let info = Cmdliner_info.term ~args:Cmdliner_info.Args.empty
82+
let name ti = Cmdliner_info.term_name ti
83+
84+
(* Evaluation *)
85+
86+
let err_help s = "Term error, help requested for unknown command " ^ s
87+
let err_argv = "argv array must have at least one element"
88+
let err_multi_cmd_def name (a, _) (a', _) =
89+
Cmdliner_base.err_multi_def ~kind:"command" name Cmdliner_info.term_doc a a'
90+
91+
type 'a result =
92+
[ `Ok of 'a | `Error of [`Parse | `Term | `Exn ] | `Version | `Help ]
93+
94+
let add_stdopts ei =
95+
let docs = Cmdliner_info.(term_stdopts_docs @@ eval_term ei) in
96+
let vargs, vers = match Cmdliner_info.(term_version @@ eval_main ei) with
97+
| None -> Cmdliner_info.Args.empty, None
98+
| Some _ ->
99+
let args, _ as vers = Cmdliner_arg.stdopt_version ~docs in
100+
args, Some vers
101+
in
102+
let help = Cmdliner_arg.stdopt_help ~docs in
103+
let args = Cmdliner_info.Args.union vargs (fst help) in
104+
let term = Cmdliner_info.(term_add_args (eval_term ei) args) in
105+
help, vers, Cmdliner_info.eval_with_term ei term
106+
107+
type 'a eval_result =
108+
('a, [ term_escape
109+
| `Exn of exn * Printexc.raw_backtrace
110+
| `Parse of string
111+
| `Std_help of Manpage.format | `Std_version ]) Result.result
112+
113+
let run ~catch ei cl f = try (f ei cl :> 'a eval_result) with
114+
| exn when catch ->
115+
let bt = Printexc.get_raw_backtrace () in
116+
Error (`Exn (exn, bt))
117+
118+
let try_eval_stdopts ~catch ei cl help version =
119+
match run ~catch ei cl (snd help) with
120+
| Ok (Some fmt) -> Some (Error (`Std_help fmt))
121+
| Error _ as err -> Some err
122+
| Ok None ->
123+
match version with
124+
| None -> None
125+
| Some version ->
126+
match run ~catch ei cl (snd version) with
127+
| Ok false -> None
128+
| Ok true -> Some (Error (`Std_version))
129+
| Error _ as err -> Some err
130+
131+
let term_eval ~catch ei f args =
132+
let help, version, ei = add_stdopts ei in
133+
let term_args = Cmdliner_info.(term_args @@ eval_term ei) in
134+
let res = match Cmdliner_cline.create term_args args with
135+
| Error (e, cl) ->
136+
begin match try_eval_stdopts ~catch ei cl help version with
137+
| Some e -> e
138+
| None -> Error (`Error (true, e))
139+
end
140+
| Ok cl ->
141+
match try_eval_stdopts ~catch ei cl help version with
142+
| Some e -> e
143+
| None -> run ~catch ei cl f
144+
in
145+
ei, res
146+
147+
let term_eval_peek_opts ei f args =
148+
let help, version, ei = add_stdopts ei in
149+
let term_args = Cmdliner_info.(term_args @@ eval_term ei) in
150+
let v, ret = match Cmdliner_cline.create ~peek_opts:true term_args args with
151+
| Error (e, cl) ->
152+
begin match try_eval_stdopts ~catch:true ei cl help version with
153+
| Some e -> None, e
154+
| None -> None, Error (`Error (true, e))
155+
end
156+
| Ok cl ->
157+
let ret = run ~catch:true ei cl f in
158+
let v = match ret with Ok v -> Some v | Error _ -> None in
159+
match try_eval_stdopts ~catch:true ei cl help version with
160+
| Some e -> v, e
161+
| None -> v, ret
162+
in
163+
let ret = match ret with
164+
| Ok v -> `Ok v
165+
| Error `Std_help _ -> `Help
166+
| Error `Std_version -> `Version
167+
| Error `Parse _ -> `Error `Parse
168+
| Error `Help _ -> `Help
169+
| Error `Exn _ -> `Error `Exn
170+
| Error `Error _ -> `Error `Term
171+
in
172+
v, ret
173+
174+
let do_help help_ppf err_ppf ei fmt cmd =
175+
let ei = match cmd with
176+
| None -> Cmdliner_info.(eval_with_term ei @@ eval_main ei)
177+
| Some cmd ->
178+
try
179+
let is_cmd t = Cmdliner_info.term_name t = cmd in
180+
let cmd = List.find is_cmd (Cmdliner_info.eval_choices ei) in
181+
Cmdliner_info.eval_with_term ei cmd
182+
with Not_found -> invalid_arg (err_help cmd)
183+
in
184+
let _, _, ei = add_stdopts ei (* may not be the originally eval'd term *) in
185+
Cmdliner_docgen.pp_man ~errs:err_ppf fmt help_ppf ei
186+
187+
let do_result help_ppf err_ppf ei = function
188+
| Ok v -> `Ok v
189+
| Error res ->
190+
match res with
191+
| `Std_help fmt -> Cmdliner_docgen.pp_man err_ppf fmt help_ppf ei; `Help
192+
| `Std_version -> Cmdliner_msg.pp_version help_ppf ei; `Version
193+
| `Parse err -> Cmdliner_msg.pp_err_usage err_ppf ei ~err; `Error `Parse
194+
| `Help (fmt, cmd) -> do_help help_ppf err_ppf ei fmt cmd; `Help
195+
| `Exn (e, bt) -> Cmdliner_msg.pp_backtrace err_ppf ei e bt; `Error `Exn
196+
| `Error (usage, err) ->
197+
(if usage
198+
then Cmdliner_msg.pp_err_usage err_ppf ei ~err
199+
else Cmdliner_msg.pp_err err_ppf ei ~err);
200+
`Error `Term
201+
202+
(* API *)
203+
204+
let env_default v = try Some (Sys.getenv v) with Not_found -> None
205+
let remove_exec argv =
206+
try List.tl (Array.to_list argv) with Failure _ -> invalid_arg err_argv
207+
208+
let eval
209+
?help:(help_ppf = Format.std_formatter)
210+
?err:(err_ppf = Format.err_formatter)
211+
?(catch = true) ?(env = env_default) ?(argv = Sys.argv) ((al, f), ti) =
212+
let term = Cmdliner_info.term_add_args ti al in
213+
let ei = Cmdliner_info.eval ~term ~main:term ~choices:[] ~env in
214+
let args = remove_exec argv in
215+
let ei, res = term_eval ~catch ei f args in
216+
do_result help_ppf err_ppf ei res
217+
218+
let choose_term main choices = function
219+
| [] -> Ok (main, [])
220+
| maybe :: args' as args ->
221+
if String.length maybe > 1 && maybe.[0] = '-' then Ok (main, args) else
222+
let index =
223+
let add acc (choice, _ as c) =
224+
let name = Cmdliner_info.term_name choice in
225+
match Cmdliner_trie.add acc name c with
226+
| `New t -> t
227+
| `Replaced (c', _) -> invalid_arg (err_multi_cmd_def name c c')
228+
in
229+
List.fold_left add Cmdliner_trie.empty choices
230+
in
231+
match Cmdliner_trie.find index maybe with
232+
| `Ok choice -> Ok (choice, args')
233+
| `Not_found ->
234+
let all = Cmdliner_trie.ambiguities index "" in
235+
let hints = Cmdliner_suggest.value maybe all in
236+
Error (Cmdliner_base.err_unknown ~kind:"command" maybe ~hints)
237+
| `Ambiguous ->
238+
let ambs = Cmdliner_trie.ambiguities index maybe in
239+
let ambs = List.sort compare ambs in
240+
Error (Cmdliner_base.err_ambiguous ~kind:"command" maybe ~ambs)
241+
242+
let eval_choice
243+
?help:(help_ppf = Format.std_formatter)
244+
?err:(err_ppf = Format.err_formatter)
245+
?(catch = true) ?(env = env_default) ?(argv = Sys.argv)
246+
main choices =
247+
let to_term_f ((al, f), ti) = Cmdliner_info.term_add_args ti al, f in
248+
let choices_f = List.rev_map to_term_f choices in
249+
let main_f = to_term_f main in
250+
let choices = List.rev_map fst choices_f in
251+
let main = fst main_f in
252+
match choose_term main_f choices_f (remove_exec argv) with
253+
| Error err ->
254+
let ei = Cmdliner_info.eval ~term:main ~main ~choices ~env in
255+
Cmdliner_msg.pp_err_usage err_ppf ei ~err; `Error `Parse
256+
| Ok ((chosen, f), args) ->
257+
let ei = Cmdliner_info.eval ~term:chosen ~main ~choices ~env in
258+
let ei, res = term_eval ~catch ei f args in
259+
do_result help_ppf err_ppf ei res
260+
261+
let eval_peek_opts
262+
?(version_opt = false) ?(env = env_default) ?(argv = Sys.argv)
263+
((args, f) : 'a t) =
264+
let version = if version_opt then Some "dummy" else None in
265+
let term = Cmdliner_info.term ~args ?version "dummy" in
266+
let ei = Cmdliner_info.eval ~term ~main:term ~choices:[] ~env in
267+
(term_eval_peek_opts ei f (remove_exec argv) :> 'a option * 'a result)
268+
269+
(* Exits *)
270+
271+
let exit_status_of_result ?(term_err = 1) = function
272+
| `Ok _ | `Help | `Version -> exit_status_success
273+
| `Error `Term -> term_err
274+
| `Error `Exn -> exit_status_internal_error
275+
| `Error `Parse -> exit_status_cli_error
276+
277+
let exit_status_of_status_result ?term_err = function
278+
| `Ok n -> n
279+
| r -> exit_status_of_result ?term_err r
280+
281+
let exit ?term_err r = Pervasives.exit (exit_status_of_result ?term_err r)
282+
let exit_status ?term_err r =
283+
Pervasives.exit (exit_status_of_status_result ?term_err r)
284+
285+
end
286+
287+
(*---------------------------------------------------------------------------
288+
Copyright (c) 2011 Daniel C. Bünzli
289+
290+
Permission to use, copy, modify, and/or distribute this software for any
291+
purpose with or without fee is hereby granted, provided that the above
292+
copyright notice and this permission notice appear in all copies.
293+
294+
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
295+
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
296+
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
297+
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
298+
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
299+
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
300+
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
301+
---------------------------------------------------------------------------*)

0 commit comments

Comments
 (0)