|
| 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