First cut cmdliner support (#5)

Import the sources of cmdliner and rewrite the CLI of jbuilder
This commit is contained in:
Rudi Grinberg 2017-02-20 10:51:03 -05:00 committed by Jérémie Dimino
parent 1b84d5df1f
commit ac6cb2360a
25 changed files with 4944 additions and 54 deletions

13
LICENSE.cmdliner Normal file
View File

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

301
src/cmdliner.ml Normal file
View File

@ -0,0 +1,301 @@
(*---------------------------------------------------------------------------
Copyright (c) 2011 Daniel C. Bünzli. All rights reserved.
Distributed under the ISC license, see terms at the end of the file.
%%NAME%% %%VERSION%%
---------------------------------------------------------------------------*)
open Result
module Manpage = Cmdliner_manpage
module Arg = Cmdliner_arg
module Term = struct
include Cmdliner_term
(* Deprecated *)
let man_format = Cmdliner_arg.man_format
let pure = const
(* Terms *)
let ( $ ) = app
type 'a ret = [ `Ok of 'a | term_escape ]
let ret (al, v) =
al, fun ei cl -> match v ei cl with
| Ok (`Ok v) -> Ok v
| Ok (`Error _ as err) -> Error err
| Ok (`Help _ as help) -> Error help
| Error _ as e -> e
let ret_of_result ?(usage = false) = function
| Ok v -> `Ok v
| Error (`Msg e) -> `Error (usage, e)
let ret_result ?usage t = app (const @@ ret_of_result ?usage) t
let term_result ?(usage = false) (al, v) =
al, fun ei cl -> match v ei cl with
| Ok (Ok _ as ok) -> ok
| Ok (Error (`Msg e)) -> Error (`Error (usage, e))
| Error _ as e -> e
let cli_parse_result (al, v) =
al, fun ei cl -> match v ei cl with
| Ok (Ok _ as ok) -> ok
| Ok (Error (`Msg e)) -> Error (`Parse e)
| Error _ as e -> e
let main_name =
Cmdliner_info.Args.empty,
(fun ei _ -> Ok (Cmdliner_info.(term_name @@ eval_main ei)))
let choice_names =
let choice_name t = Cmdliner_info.term_name t in
Cmdliner_info.Args.empty,
(fun ei _ -> Ok (List.rev_map choice_name (Cmdliner_info.eval_choices ei)))
(* Term information *)
type exit_info = Cmdliner_info.exit
let exit_info = Cmdliner_info.exit
let exit_status_success = 0
let exit_status_internal_error = 124
let exit_status_cli_error = 125
let default_error_exits =
[ exit_info exit_status_internal_error
~doc:"on unexpected internal errors (bugs).";
exit_info exit_status_cli_error
~doc:"on command line parsing errors."; ]
let default_exits =
(exit_info exit_status_success ~doc:"on success.") :: default_error_exits
type env_info = Cmdliner_info.env
let env_info = Cmdliner_info.env
type info = Cmdliner_info.term
let info = Cmdliner_info.term ~args:Cmdliner_info.Args.empty
let name ti = Cmdliner_info.term_name ti
(* Evaluation *)
let err_help s = "Term error, help requested for unknown command " ^ s
let err_argv = "argv array must have at least one element"
let err_multi_cmd_def name (a, _) (a', _) =
Cmdliner_base.err_multi_def ~kind:"command" name Cmdliner_info.term_doc a a'
type 'a result =
[ `Ok of 'a | `Error of [`Parse | `Term | `Exn ] | `Version | `Help ]
let add_stdopts ei =
let docs = Cmdliner_info.(term_stdopts_docs @@ eval_term ei) in
let vargs, vers = match Cmdliner_info.(term_version @@ eval_main ei) with
| None -> Cmdliner_info.Args.empty, None
| Some _ ->
let args, _ as vers = Cmdliner_arg.stdopt_version ~docs in
args, Some vers
in
let help = Cmdliner_arg.stdopt_help ~docs in
let args = Cmdliner_info.Args.union vargs (fst help) in
let term = Cmdliner_info.(term_add_args (eval_term ei) args) in
help, vers, Cmdliner_info.eval_with_term ei term
type 'a eval_result =
('a, [ term_escape
| `Exn of exn * Printexc.raw_backtrace
| `Parse of string
| `Std_help of Manpage.format | `Std_version ]) Result.result
let run ~catch ei cl f = try (f ei cl :> 'a eval_result) with
| exn when catch ->
let bt = Printexc.get_raw_backtrace () in
Error (`Exn (exn, bt))
let try_eval_stdopts ~catch ei cl help version =
match run ~catch ei cl (snd help) with
| Ok (Some fmt) -> Some (Error (`Std_help fmt))
| Error _ as err -> Some err
| Ok None ->
match version with
| None -> None
| Some version ->
match run ~catch ei cl (snd version) with
| Ok false -> None
| Ok true -> Some (Error (`Std_version))
| Error _ as err -> Some err
let term_eval ~catch ei f args =
let help, version, ei = add_stdopts ei in
let term_args = Cmdliner_info.(term_args @@ eval_term ei) in
let res = match Cmdliner_cline.create term_args args with
| Error (e, cl) ->
begin match try_eval_stdopts ~catch ei cl help version with
| Some e -> e
| None -> Error (`Error (true, e))
end
| Ok cl ->
match try_eval_stdopts ~catch ei cl help version with
| Some e -> e
| None -> run ~catch ei cl f
in
ei, res
let term_eval_peek_opts ei f args =
let help, version, ei = add_stdopts ei in
let term_args = Cmdliner_info.(term_args @@ eval_term ei) in
let v, ret = match Cmdliner_cline.create ~peek_opts:true term_args args with
| Error (e, cl) ->
begin match try_eval_stdopts ~catch:true ei cl help version with
| Some e -> None, e
| None -> None, Error (`Error (true, e))
end
| Ok cl ->
let ret = run ~catch:true ei cl f in
let v = match ret with Ok v -> Some v | Error _ -> None in
match try_eval_stdopts ~catch:true ei cl help version with
| Some e -> v, e
| None -> v, ret
in
let ret = match ret with
| Ok v -> `Ok v
| Error `Std_help _ -> `Help
| Error `Std_version -> `Version
| Error `Parse _ -> `Error `Parse
| Error `Help _ -> `Help
| Error `Exn _ -> `Error `Exn
| Error `Error _ -> `Error `Term
in
v, ret
let do_help help_ppf err_ppf ei fmt cmd =
let ei = match cmd with
| None -> Cmdliner_info.(eval_with_term ei @@ eval_main ei)
| Some cmd ->
try
let is_cmd t = Cmdliner_info.term_name t = cmd in
let cmd = List.find is_cmd (Cmdliner_info.eval_choices ei) in
Cmdliner_info.eval_with_term ei cmd
with Not_found -> invalid_arg (err_help cmd)
in
let _, _, ei = add_stdopts ei (* may not be the originally eval'd term *) in
Cmdliner_docgen.pp_man ~errs:err_ppf fmt help_ppf ei
let do_result help_ppf err_ppf ei = function
| Ok v -> `Ok v
| Error res ->
match res with
| `Std_help fmt -> Cmdliner_docgen.pp_man err_ppf fmt help_ppf ei; `Help
| `Std_version -> Cmdliner_msg.pp_version help_ppf ei; `Version
| `Parse err -> Cmdliner_msg.pp_err_usage err_ppf ei ~err; `Error `Parse
| `Help (fmt, cmd) -> do_help help_ppf err_ppf ei fmt cmd; `Help
| `Exn (e, bt) -> Cmdliner_msg.pp_backtrace err_ppf ei e bt; `Error `Exn
| `Error (usage, err) ->
(if usage
then Cmdliner_msg.pp_err_usage err_ppf ei ~err
else Cmdliner_msg.pp_err err_ppf ei ~err);
`Error `Term
(* API *)
let env_default v = try Some (Sys.getenv v) with Not_found -> None
let remove_exec argv =
try List.tl (Array.to_list argv) with Failure _ -> invalid_arg err_argv
let eval
?help:(help_ppf = Format.std_formatter)
?err:(err_ppf = Format.err_formatter)
?(catch = true) ?(env = env_default) ?(argv = Sys.argv) ((al, f), ti) =
let term = Cmdliner_info.term_add_args ti al in
let ei = Cmdliner_info.eval ~term ~main:term ~choices:[] ~env in
let args = remove_exec argv in
let ei, res = term_eval ~catch ei f args in
do_result help_ppf err_ppf ei res
let choose_term main choices = function
| [] -> Ok (main, [])
| maybe :: args' as args ->
if String.length maybe > 1 && maybe.[0] = '-' then Ok (main, args) else
let index =
let add acc (choice, _ as c) =
let name = Cmdliner_info.term_name choice in
match Cmdliner_trie.add acc name c with
| `New t -> t
| `Replaced (c', _) -> invalid_arg (err_multi_cmd_def name c c')
in
List.fold_left add Cmdliner_trie.empty choices
in
match Cmdliner_trie.find index maybe with
| `Ok choice -> Ok (choice, args')
| `Not_found ->
let all = Cmdliner_trie.ambiguities index "" in
let hints = Cmdliner_suggest.value maybe all in
Error (Cmdliner_base.err_unknown ~kind:"command" maybe ~hints)
| `Ambiguous ->
let ambs = Cmdliner_trie.ambiguities index maybe in
let ambs = List.sort compare ambs in
Error (Cmdliner_base.err_ambiguous ~kind:"command" maybe ~ambs)
let eval_choice
?help:(help_ppf = Format.std_formatter)
?err:(err_ppf = Format.err_formatter)
?(catch = true) ?(env = env_default) ?(argv = Sys.argv)
main choices =
let to_term_f ((al, f), ti) = Cmdliner_info.term_add_args ti al, f in
let choices_f = List.rev_map to_term_f choices in
let main_f = to_term_f main in
let choices = List.rev_map fst choices_f in
let main = fst main_f in
match choose_term main_f choices_f (remove_exec argv) with
| Error err ->
let ei = Cmdliner_info.eval ~term:main ~main ~choices ~env in
Cmdliner_msg.pp_err_usage err_ppf ei ~err; `Error `Parse
| Ok ((chosen, f), args) ->
let ei = Cmdliner_info.eval ~term:chosen ~main ~choices ~env in
let ei, res = term_eval ~catch ei f args in
do_result help_ppf err_ppf ei res
let eval_peek_opts
?(version_opt = false) ?(env = env_default) ?(argv = Sys.argv)
((args, f) : 'a t) =
let version = if version_opt then Some "dummy" else None in
let term = Cmdliner_info.term ~args ?version "dummy" in
let ei = Cmdliner_info.eval ~term ~main:term ~choices:[] ~env in
(term_eval_peek_opts ei f (remove_exec argv) :> 'a option * 'a result)
(* Exits *)
let exit_status_of_result ?(term_err = 1) = function
| `Ok _ | `Help | `Version -> exit_status_success
| `Error `Term -> term_err
| `Error `Exn -> exit_status_internal_error
| `Error `Parse -> exit_status_cli_error
let exit_status_of_status_result ?term_err = function
| `Ok n -> n
| r -> exit_status_of_result ?term_err r
let exit ?term_err r = Pervasives.exit (exit_status_of_result ?term_err r)
let exit_status ?term_err r =
Pervasives.exit (exit_status_of_status_result ?term_err r)
end
(*---------------------------------------------------------------------------
Copyright (c) 2011 Daniel C. Bünzli
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
---------------------------------------------------------------------------*)

1635
src/cmdliner.mli Normal file

File diff suppressed because it is too large Load Diff

358
src/cmdliner_arg.ml Normal file
View File

@ -0,0 +1,358 @@
(*---------------------------------------------------------------------------
Copyright (c) 2011 Daniel C. Bünzli. All rights reserved.
Distributed under the ISC license, see terms at the end of the file.
%%NAME%% %%VERSION%%
---------------------------------------------------------------------------*)
open Result
let rev_compare n0 n1 = compare n1 n0
(* Invalid_argument strings **)
let err_not_opt = "Option argument without name"
let err_not_pos = "Positional argument with a name"
(* Documentation formatting helpers *)
let strf = Printf.sprintf
let doc_quote = Cmdliner_base.quote
let doc_alts = Cmdliner_base.alts_str
let doc_alts_enum ?quoted enum = doc_alts ?quoted (List.map fst enum)
let str_of_pp pp v = pp Format.str_formatter v; Format.flush_str_formatter ()
(* Argument converters *)
type 'a parser = string -> [ `Ok of 'a | `Error of string ]
type 'a printer = Format.formatter -> 'a -> unit
type 'a conv = 'a parser * 'a printer
type 'a converter = 'a conv
let default_docv = "VALUE"
let conv ?docv (parse, print) =
let parse s = match parse s with Ok v -> `Ok v | Error (`Msg e) -> `Error e in
parse, print
let pconv ?docv conv = conv
let conv_parser (parse, _) =
fun s -> match parse s with `Ok v -> Ok v | `Error e -> Error (`Msg e)
let conv_printer (_, print) = print
let conv_docv _ = default_docv
let err_invalid s kind = `Msg (strf "invalid value '%s', expected %s" s kind)
let parser_of_kind_of_string ~kind k_of_string =
fun s -> match k_of_string s with
| None -> Error (err_invalid s kind)
| Some v -> Ok v
let some = Cmdliner_base.some
(* Argument information *)
type env = Cmdliner_info.env
let env_var = Cmdliner_info.env
type 'a t = 'a Cmdliner_term.t
type info = Cmdliner_info.arg
let info = Cmdliner_info.arg
(* Arguments *)
let ( & ) f x = f x
let err e = Error (`Parse e)
let parse_to_list parser s = match parser s with
| `Ok v -> `Ok [v]
| `Error _ as e -> e
let try_env ei a parse ~absent = match Cmdliner_info.arg_env a with
| None -> Ok absent
| Some env ->
let var = Cmdliner_info.env_var env in
match Cmdliner_info.(eval_env_var ei var) with
| None -> Ok absent
| Some v ->
match parse v with
| `Ok v -> Ok v
| `Error e -> err (Cmdliner_msg.err_env_parse env ~err:e)
let arg_to_args = Cmdliner_info.Args.singleton
let list_to_args f l =
let add acc v = Cmdliner_info.Args.add (f v) acc in
List.fold_left add Cmdliner_info.Args.empty l
let flag a =
if Cmdliner_info.arg_is_pos a then invalid_arg err_not_opt else
let convert ei cl = match Cmdliner_cline.opt_arg cl a with
| [] -> try_env ei a Cmdliner_base.env_bool_parse ~absent:false
| [_, _, None] -> Ok true
| [_, f, Some v] -> err (Cmdliner_msg.err_flag_value f v)
| (_, f, _) :: (_ ,g, _) :: _ -> err (Cmdliner_msg.err_opt_repeated f g)
in
arg_to_args a, convert
let flag_all a =
if Cmdliner_info.arg_is_pos a then invalid_arg err_not_opt else
let a = Cmdliner_info.arg_make_all_opts a in
let convert ei cl = match Cmdliner_cline.opt_arg cl a with
| [] ->
try_env ei a (parse_to_list Cmdliner_base.env_bool_parse) ~absent:[]
| l ->
try
let truth (_, f, v) = match v with
| None -> true
| Some v -> failwith (Cmdliner_msg.err_flag_value f v)
in
Ok (List.rev_map truth l)
with Failure e -> err e
in
arg_to_args a, convert
let vflag v l =
let convert _ cl =
let rec aux fv = function
| (v, a) :: rest ->
begin match Cmdliner_cline.opt_arg cl a with
| [] -> aux fv rest
| [_, f, None] ->
begin match fv with
| None -> aux (Some (f, v)) rest
| Some (g, _) -> failwith (Cmdliner_msg.err_opt_repeated g f)
end
| [_, f, Some v] -> failwith (Cmdliner_msg.err_flag_value f v)
| (_, f, _) :: (_, g, _) :: _ ->
failwith (Cmdliner_msg.err_opt_repeated g f)
end
| [] -> match fv with None -> v | Some (_, v) -> v
in
try Ok (aux None l) with Failure e -> err e
in
let flag (_, a) =
if Cmdliner_info.arg_is_pos a then invalid_arg err_not_opt else a
in
list_to_args flag l, convert
let vflag_all v l =
let convert _ cl =
let rec aux acc = function
| (fv, a) :: rest ->
begin match Cmdliner_cline.opt_arg cl a with
| [] -> aux acc rest
| l ->
let fval (k, f, v) = match v with
| None -> (k, fv)
| Some v -> failwith (Cmdliner_msg.err_flag_value f v)
in
aux (List.rev_append (List.rev_map fval l) acc) rest
end
| [] ->
if acc = [] then v else List.rev_map snd (List.sort rev_compare acc)
in
try Ok (aux [] l) with Failure e -> err e
in
let flag (_, a) =
if Cmdliner_info.arg_is_pos a then invalid_arg err_not_opt else
Cmdliner_info.arg_make_all_opts a
in
list_to_args flag l, convert
let parse_opt_value parse f v = match parse v with
| `Ok v -> v
| `Error e -> failwith (Cmdliner_msg.err_opt_parse f e)
let opt ?vopt (parse, print) v a =
if Cmdliner_info.arg_is_pos a then invalid_arg err_not_opt else
let absent = Cmdliner_info.Val (lazy (str_of_pp print v)) in
let kind = match vopt with
| None -> Cmdliner_info.Opt
| Some dv -> Cmdliner_info.Opt_vopt (str_of_pp print dv)
in
let a = Cmdliner_info.arg_make_opt ~absent ~kind a in
let convert ei cl = match Cmdliner_cline.opt_arg cl a with
| [] -> try_env ei a parse ~absent:v
| [_, f, Some v] ->
(try Ok (parse_opt_value parse f v) with Failure e -> err e)
| [_, f, None] ->
begin match vopt with
| None -> err (Cmdliner_msg.err_opt_value_missing f)
| Some optv -> Ok optv
end
| (_, f, _) :: (_, g, _) :: _ -> err (Cmdliner_msg.err_opt_repeated g f)
in
arg_to_args a, convert
let opt_all ?vopt (parse, print) v a =
if Cmdliner_info.arg_is_pos a then invalid_arg err_not_opt else
let absent = Cmdliner_info.Val (lazy "") in
let kind = match vopt with
| None -> Cmdliner_info.Opt
| Some dv -> Cmdliner_info.Opt_vopt (str_of_pp print dv)
in
let a = Cmdliner_info.arg_make_opt_all ~absent ~kind a in
let convert ei cl = match Cmdliner_cline.opt_arg cl a with
| [] -> try_env ei a (parse_to_list parse) ~absent:v
| l ->
let parse (k, f, v) = match v with
| Some v -> (k, parse_opt_value parse f v)
| None -> match vopt with
| None -> failwith (Cmdliner_msg.err_opt_value_missing f)
| Some dv -> (k, dv)
in
try Ok (List.rev_map snd
(List.sort rev_compare (List.rev_map parse l))) with
| Failure e -> err e
in
arg_to_args a, convert
(* Positional arguments *)
let parse_pos_value parse a v = match parse v with
| `Ok v -> v
| `Error e -> failwith (Cmdliner_msg.err_pos_parse a e)
let pos ?(rev = false) k (parse, print) v a =
if Cmdliner_info.arg_is_opt a then invalid_arg err_not_pos else
let absent = Cmdliner_info.Val (lazy (str_of_pp print v)) in
let pos = Cmdliner_info.pos ~rev ~start:k ~len:(Some 1) in
let a = Cmdliner_info.arg_make_pos_abs ~absent ~pos a in
let convert ei cl = match Cmdliner_cline.pos_arg cl a with
| [] -> try_env ei a parse ~absent:v
| [v] ->
(try Ok (parse_pos_value parse a v) with Failure e -> err e)
| _ -> assert false
in
arg_to_args a, convert
let pos_list pos (parse, _) v a =
if Cmdliner_info.arg_is_opt a then invalid_arg err_not_pos else
let a = Cmdliner_info.arg_make_pos pos a in
let convert ei cl = match Cmdliner_cline.pos_arg cl a with
| [] -> try_env ei a (parse_to_list parse) ~absent:v
| l ->
try Ok (List.rev (List.rev_map (parse_pos_value parse a) l)) with
| Failure e -> err e
in
arg_to_args a, convert
let all = Cmdliner_info.pos ~rev:false ~start:0 ~len:None
let pos_all c v a = pos_list all c v a
let pos_left ?(rev = false) k =
let start = if rev then k + 1 else 0 in
let len = if rev then None else Some k in
pos_list (Cmdliner_info.pos ~rev ~start ~len)
let pos_right ?(rev = false) k =
let start = if rev then 0 else k + 1 in
let len = if rev then Some k else None in
pos_list (Cmdliner_info.pos ~rev ~start ~len)
(* Arguments as terms *)
let absent_error args =
let make_req a acc =
let req_a = Cmdliner_info.arg_make_req a in
Cmdliner_info.Args.add req_a acc
in
Cmdliner_info.Args.fold make_req args Cmdliner_info.Args.empty
let value a = a
let err_arg_missing args =
err @@ Cmdliner_msg.err_arg_missing (Cmdliner_info.Args.choose args)
let required (args, convert) =
let args = absent_error args in
let convert ei cl = match convert ei cl with
| Ok (Some v) -> Ok v
| Ok None -> err_arg_missing args
| Error _ as e -> e
in
args, convert
let non_empty (al, convert) =
let args = absent_error al in
let convert ei cl = match convert ei cl with
| Ok [] -> err_arg_missing args
| Ok l -> Ok l
| Error _ as e -> e
in
args, convert
let last (args, convert) =
let convert ei cl = match convert ei cl with
| Ok [] -> err_arg_missing args
| Ok l -> Ok (List.hd (List.rev l))
| Error _ as e -> e
in
args, convert
(* Predefined arguments *)
let man_fmts =
["auto", `Auto; "pager", `Pager; "groff", `Groff; "plain", `Plain]
let man_fmt_docv = "FMT"
let man_fmts_enum = Cmdliner_base.enum man_fmts
let man_fmts_alts = doc_alts_enum man_fmts
let man_fmts_doc kind =
strf "Show %s in format $(docv). The value $(docv) must be %s. With `auto',
the format is `pager` or `plain' whenever the $(b,TERM) env var is
`dumb' or undefined."
kind man_fmts_alts
let man_format =
let doc = man_fmts_doc "output" in
let docv = man_fmt_docv in
value & opt man_fmts_enum `Pager & info ["man-format"] ~docv ~doc
let stdopt_version ~docs =
value & flag & info ["version"] ~docs ~doc:"Show version information."
let stdopt_help ~docs =
let doc = man_fmts_doc "this help" in
let docv = man_fmt_docv in
value & opt ~vopt:(Some `Auto) (some man_fmts_enum) None &
info ["help"] ~docv ~docs ~doc
(* Predefined converters. *)
let bool = Cmdliner_base.bool
let char = Cmdliner_base.char
let int = Cmdliner_base.int
let nativeint = Cmdliner_base.nativeint
let int32 = Cmdliner_base.int32
let int64 = Cmdliner_base.int64
let float = Cmdliner_base.float
let string = Cmdliner_base.string
let enum = Cmdliner_base.enum
let file = Cmdliner_base.file
let dir = Cmdliner_base.dir
let non_dir_file = Cmdliner_base.non_dir_file
let list = Cmdliner_base.list
let array = Cmdliner_base.array
let pair = Cmdliner_base.pair
let t2 = Cmdliner_base.t2
let t3 = Cmdliner_base.t3
let t4 = Cmdliner_base.t4
(*---------------------------------------------------------------------------
Copyright (c) 2011 Daniel C. Bünzli
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
---------------------------------------------------------------------------*)

113
src/cmdliner_arg.mli Normal file
View File

@ -0,0 +1,113 @@
(*---------------------------------------------------------------------------
Copyright (c) 2011 Daniel C. Bünzli. All rights reserved.
Distributed under the ISC license, see terms at the end of the file.
%%NAME%% %%VERSION%%
---------------------------------------------------------------------------*)
open Result
(** Command line arguments as terms. *)
type 'a parser = string -> [ `Ok of 'a | `Error of string ]
type 'a printer = Format.formatter -> 'a -> unit
type 'a conv = 'a parser * 'a printer
type 'a converter = 'a conv
val conv :
?docv:string -> (string -> ('a, [`Msg of string]) result) * 'a printer ->
'a conv
val pconv : ?docv:string -> 'a parser * 'a printer -> 'a conv
val conv_parser : 'a conv -> (string -> ('a, [`Msg of string]) result)
val conv_printer : 'a conv -> 'a printer
val conv_docv : 'a conv -> string
val parser_of_kind_of_string :
kind:string -> (string -> 'a option) ->
(string -> ('a, [`Msg of string]) result)
val some : ?none:string -> 'a converter -> 'a option converter
type env = Cmdliner_info.env
val env_var : ?docs:string -> ?doc:string -> string -> env
type 'a t = 'a Cmdliner_term.t
type info
val info :
?docs:string -> ?docv:string -> ?doc:string -> ?env:env -> string list -> info
val ( & ) : ('a -> 'b) -> 'a -> 'b
val flag : info -> bool t
val flag_all : info -> bool list t
val vflag : 'a -> ('a * info) list -> 'a t
val vflag_all : 'a list -> ('a * info) list -> 'a list t
val opt : ?vopt:'a -> 'a converter -> 'a -> info -> 'a t
val opt_all : ?vopt:'a -> 'a converter -> 'a list -> info -> 'a list t
val pos : ?rev:bool -> int -> 'a converter -> 'a -> info -> 'a t
val pos_all : 'a converter -> 'a list -> info -> 'a list t
val pos_left : ?rev:bool -> int -> 'a converter -> 'a list -> info -> 'a list t
val pos_right : ?rev:bool -> int -> 'a converter -> 'a list -> info -> 'a list t
(** {1 As terms} *)
val value : 'a t -> 'a Cmdliner_term.t
val required : 'a option t -> 'a Cmdliner_term.t
val non_empty : 'a list t -> 'a list Cmdliner_term.t
val last : 'a list t -> 'a Cmdliner_term.t
(** {1 Predefined arguments} *)
val man_format : Cmdliner_manpage.format Cmdliner_term.t
val stdopt_version : docs:string -> bool Cmdliner_term.t
val stdopt_help : docs:string -> Cmdliner_manpage.format option Cmdliner_term.t
(** {1 Converters} *)
val bool : bool converter
val char : char converter
val int : int converter
val nativeint : nativeint converter
val int32 : int32 converter
val int64 : int64 converter
val float : float converter
val string : string converter
val enum : (string * 'a) list -> 'a converter
val file : string converter
val dir : string converter
val non_dir_file : string converter
val list : ?sep:char -> 'a converter -> 'a list converter
val array : ?sep:char -> 'a converter -> 'a array converter
val pair : ?sep:char -> 'a converter -> 'b converter -> ('a * 'b) converter
val t2 : ?sep:char -> 'a converter -> 'b converter -> ('a * 'b) converter
val t3 :
?sep:char -> 'a converter ->'b converter -> 'c converter ->
('a * 'b * 'c) converter
val t4 :
?sep:char -> 'a converter ->'b converter -> 'c converter -> 'd converter ->
('a * 'b * 'c * 'd) converter
val doc_quote : string -> string
val doc_alts : ?quoted:bool -> string list -> string
val doc_alts_enum : ?quoted:bool -> (string * 'a) list -> string
(*---------------------------------------------------------------------------
Copyright (c) 2011 Daniel C. Bünzli
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
---------------------------------------------------------------------------*)

302
src/cmdliner_base.ml Normal file
View File

@ -0,0 +1,302 @@
(*---------------------------------------------------------------------------
Copyright (c) 2011 Daniel C. Bünzli. All rights reserved.
Distributed under the ISC license, see terms at the end of the file.
%%NAME%% %%VERSION%%
---------------------------------------------------------------------------*)
(* Invalid argument strings *)
let err_empty_list = "empty list"
let err_incomplete_enum = "Incomplete enumeration for the type"
(* String helpers, should be migrated to ascii_ versions once >= 4.03 *)
let lowercase = String.lowercase
let uppercase = String.lowercase
let capitalize = String.capitalize
(* Formatting tools *)
let strf = Printf.sprintf
let pp = Format.fprintf
let pp_sp = Format.pp_print_space
let pp_str = Format.pp_print_string
let pp_char = Format.pp_print_char
let pp_white_str ~spaces ppf s = (* hint spaces (maybe) and new lines. *)
let left = ref 0 and right = ref 0 and len = String.length s in
let flush () =
Format.pp_print_string ppf (String.sub s !left (!right - !left));
incr right; left := !right;
in
while (!right <> len) do
if s.[!right] = '\n' then (flush (); Format.pp_force_newline ppf ()) else
if spaces && s.[!right] = ' ' then (flush (); Format.pp_print_space ppf ())
else incr right;
done;
if !left <> len then flush ()
let pp_text = pp_white_str ~spaces:true
let pp_lines = pp_white_str ~spaces:false
let pp_tokens ~spaces ppf s = (* collapse white and hint spaces (maybe) *)
let is_space = function ' ' | '\n' | '\r' | '\t' -> true | _ -> false in
let i_max = String.length s - 1 in
let flush start stop = pp_str ppf (String.sub s start (stop - start + 1)) in
let rec skip_white i =
if i > i_max then i else
if is_space s.[i] then skip_white (i + 1) else i
in
let rec loop start i =
if i > i_max then flush start i_max else
if not (is_space s.[i]) then loop start (i + 1) else
let next_start = skip_white i in
(flush start (i - 1); if spaces then pp_sp ppf () else pp_char ppf ' ';
if next_start > i_max then () else loop next_start next_start)
in
loop 0 0
(* Converter (end-user) error messages *)
let quote s = strf "`%s'" s
let alts_str ?(quoted = true) alts =
let quote = if quoted then quote else (fun s -> s) in
match alts with
| [] -> invalid_arg err_empty_list
| [a] -> (quote a)
| [a; b] -> strf "either %s or %s" (quote a) (quote b)
| alts ->
let rev_alts = List.rev alts in
strf "one of %s or %s"
(String.concat ", " (List.rev_map quote (List.tl rev_alts)))
(quote (List.hd rev_alts))
let err_multi_def ~kind name doc v v' =
strf "%s %s defined twice (doc strings are '%s' and '%s')"
kind name (doc v) (doc v')
let err_ambiguous ~kind s ~ambs =
strf "%s %s ambiguous and could be %s" kind (quote s) (alts_str ambs)
let err_unknown ?(hints = []) ~kind v =
let did_you_mean s = strf ", did you mean %s ?" s in
let hints = match hints with [] -> "." | hs -> did_you_mean (alts_str hs) in
strf "unknown %s %s%s" kind (quote v) hints
let err_no kind s = strf "no %s %s" (quote s) kind
let err_not_dir s = strf "%s is not a directory" (quote s)
let err_is_dir s = strf "%s is a directory" (quote s)
let err_element kind s exp =
strf "invalid element in %s (`%s'): %s" kind s exp
let err_invalid kind s exp = strf "invalid %s %s, %s" kind (quote s) exp
let err_invalid_val = err_invalid "value"
let err_sep_miss sep s =
err_invalid_val s (strf "missing a `%c' separator" sep)
(* Converters *)
type 'a parser = string -> [ `Ok of 'a | `Error of string ]
type 'a printer = Format.formatter -> 'a -> unit
type 'a conv = 'a parser * 'a printer
let some ?(none = "") (parse, print) =
let parse s = match parse s with
| `Ok v -> `Ok (Some v)
| `Error _ as e -> e
in
let print ppf v = match v with
| None -> Format.pp_print_string ppf none
| Some v -> print ppf v
in
parse, print
let bool =
let parse s = try `Ok (bool_of_string s) with
| Invalid_argument _ ->
`Error (err_invalid_val s (alts_str ["true"; "false"]))
in
parse, Format.pp_print_bool
let char =
let parse s = match String.length s = 1 with
| true -> `Ok s.[0]
| false -> `Error (err_invalid_val s "expected a character")
in
parse, pp_char
let parse_with t_of_str exp s =
try `Ok (t_of_str s) with Failure _ -> `Error (err_invalid_val s exp)
let int =
parse_with int_of_string "expected an integer", Format.pp_print_int
let int32 =
parse_with Int32.of_string "expected a 32-bit integer",
(fun ppf -> pp ppf "%ld")
let int64 =
parse_with Int64.of_string "expected a 64-bit integer",
(fun ppf -> pp ppf "%Ld")
let nativeint =
parse_with Nativeint.of_string "expected a processor-native integer",
(fun ppf -> pp ppf "%nd")
let float =
parse_with float_of_string "expected a floating point number",
Format.pp_print_float
let string = (fun s -> `Ok s), pp_str
let enum sl =
if sl = [] then invalid_arg err_empty_list else
let t = Cmdliner_trie.of_list sl in
let parse s = match Cmdliner_trie.find t s with
| `Ok _ as r -> r
| `Ambiguous ->
let ambs = List.sort compare (Cmdliner_trie.ambiguities t s) in
`Error (err_ambiguous "enum value" s ambs)
| `Not_found ->
let alts = List.rev (List.rev_map (fun (s, _) -> s) sl) in
`Error (err_invalid_val s ("expected " ^ (alts_str alts)))
in
let print ppf v =
let sl_inv = List.rev_map (fun (s,v) -> (v,s)) sl in
try pp_str ppf (List.assoc v sl_inv)
with Not_found -> invalid_arg err_incomplete_enum
in
parse, print
let file =
let parse s = match Sys.file_exists s with
| true -> `Ok s
| false -> `Error (err_no "file or directory" s)
in
parse, pp_str
let dir =
let parse s = match Sys.file_exists s with
| true -> if Sys.is_directory s then `Ok s else `Error (err_not_dir s)
| false -> `Error (err_no "directory" s)
in
parse, pp_str
let non_dir_file =
let parse s = match Sys.file_exists s with
| true -> if not (Sys.is_directory s) then `Ok s else `Error (err_is_dir s)
| false -> `Error (err_no "file" s)
in
parse, pp_str
let split_and_parse sep parse s = (* raises [Failure] *)
let parse sub = match parse sub with
| `Error e -> failwith e | `Ok v -> v
in
let rec split accum j =
let i = try String.rindex_from s j sep with Not_found -> -1 in
if (i = -1) then
let p = String.sub s 0 (j + 1) in
if p <> "" then parse p :: accum else accum
else
let p = String.sub s (i + 1) (j - i) in
let accum' = if p <> "" then parse p :: accum else accum in
split accum' (i - 1)
in
split [] (String.length s - 1)
let list ?(sep = ',') (parse, pp_e) =
let parse s = try `Ok (split_and_parse sep parse s) with
| Failure e -> `Error (err_element "list" s e)
in
let rec print ppf = function
| v :: l -> pp_e ppf v; if (l <> []) then (pp_char ppf sep; print ppf l)
| [] -> ()
in
parse, print
let array ?(sep = ',') (parse, pp_e) =
let parse s = try `Ok (Array.of_list (split_and_parse sep parse s)) with
| Failure e -> `Error (err_element "array" s e)
in
let print ppf v =
let max = Array.length v - 1 in
for i = 0 to max do pp_e ppf v.(i); if i <> max then pp_char ppf sep done
in
parse, print
let split_left sep s =
try
let i = String.index s sep in
let len = String.length s in
Some ((String.sub s 0 i), (String.sub s (i + 1) (len - i - 1)))
with Not_found -> None
let pair ?(sep = ',') (pa0, pr0) (pa1, pr1) =
let parser s = match split_left sep s with
| None -> `Error (err_sep_miss sep s)
| Some (v0, v1) ->
match pa0 v0, pa1 v1 with
| `Ok v0, `Ok v1 -> `Ok (v0, v1)
| `Error e, _ | _, `Error e -> `Error (err_element "pair" s e)
in
let printer ppf (v0, v1) = pp ppf "%a%c%a" pr0 v0 sep pr1 v1 in
parser, printer
let t2 = pair
let t3 ?(sep = ',') (pa0, pr0) (pa1, pr1) (pa2, pr2) =
let parse s = match split_left sep s with
| None -> `Error (err_sep_miss sep s)
| Some (v0, s) ->
match split_left sep s with
| None -> `Error (err_sep_miss sep s)
| Some (v1, v2) ->
match pa0 v0, pa1 v1, pa2 v2 with
| `Ok v0, `Ok v1, `Ok v2 -> `Ok (v0, v1, v2)
| `Error e, _, _ | _, `Error e, _ | _, _, `Error e ->
`Error (err_element "triple" s e)
in
let print ppf (v0, v1, v2) =
pp ppf "%a%c%a%c%a" pr0 v0 sep pr1 v1 sep pr2 v2
in
parse, print
let t4 ?(sep = ',') (pa0, pr0) (pa1, pr1) (pa2, pr2) (pa3, pr3) =
let parse s = match split_left sep s with
| None -> `Error (err_sep_miss sep s)
| Some(v0, s) ->
match split_left sep s with
| None -> `Error (err_sep_miss sep s)
| Some (v1, s) ->
match split_left sep s with
| None -> `Error (err_sep_miss sep s)
| Some (v2, v3) ->
match pa0 v0, pa1 v1, pa2 v2, pa3 v3 with
| `Ok v1, `Ok v2, `Ok v3, `Ok v4 -> `Ok (v1, v2, v3, v4)
| `Error e, _, _, _ | _, `Error e, _, _ | _, _, `Error e, _
| _, _, _, `Error e -> `Error (err_element "quadruple" s e)
in
let print ppf (v0, v1, v2, v3) =
pp ppf "%a%c%a%c%a%c%a" pr0 v0 sep pr1 v1 sep pr2 v2 sep pr3 v3
in
parse, print
let env_bool_parse s = match lowercase s with
| "" | "false" | "no" | "n" | "0" -> `Ok false
| "true" | "yes" | "y" | "1" -> `Ok true
| s -> `Error (err_invalid_val s (alts_str ["true"; "yes"; "false"; "no" ]))
(*---------------------------------------------------------------------------
Copyright (c) 2011 Daniel C. Bünzli
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
---------------------------------------------------------------------------*)

74
src/cmdliner_base.mli Normal file
View File

@ -0,0 +1,74 @@
(*---------------------------------------------------------------------------
Copyright (c) 2011 Daniel C. Bünzli. All rights reserved.
Distributed under the ISC license, see terms at the end of the file.
%%NAME%% %%VERSION%%
---------------------------------------------------------------------------*)
(** A few helpful base definitions. *)
(** {1:str String helpers} *)
val lowercase : string -> string
val uppercase : string -> string
val capitalize : string -> string
(** {1:fmt Formatting helpers} *)
val pp_text : Format.formatter -> string -> unit
val pp_lines : Format.formatter -> string -> unit
val pp_tokens : spaces:bool -> Format.formatter -> string -> unit
(** {1:err Error message helpers} *)
val quote : string -> string
val alts_str : ?quoted:bool -> string list -> string
val err_ambiguous : kind:string -> string -> ambs:string list -> string
val err_unknown : ?hints:string list -> kind:string -> string -> string
val err_multi_def :
kind:string -> string -> ('b -> string) -> 'b -> 'b -> string
(** {1:conv Textual OCaml value converters} *)
type 'a parser = string -> [ `Ok of 'a | `Error of string ]
type 'a printer = Format.formatter -> 'a -> unit
type 'a conv = 'a parser * 'a printer
val some : ?none:string -> 'a conv -> 'a option conv
val bool : bool conv
val char : char conv
val int : int conv
val nativeint : nativeint conv
val int32 : int32 conv
val int64 : int64 conv
val float : float conv
val string : string conv
val enum : (string * 'a) list -> 'a conv
val file : string conv
val dir : string conv
val non_dir_file : string conv
val list : ?sep:char -> 'a conv -> 'a list conv
val array : ?sep:char -> 'a conv -> 'a array conv
val pair : ?sep:char -> 'a conv -> 'b conv -> ('a * 'b) conv
val t2 : ?sep:char -> 'a conv -> 'b conv -> ('a * 'b) conv
val t3 : ?sep:char -> 'a conv ->'b conv -> 'c conv -> ('a * 'b * 'c) conv
val t4 :
?sep:char -> 'a conv -> 'b conv -> 'c conv -> 'd conv ->
('a * 'b * 'c * 'd) conv
val env_bool_parse : bool parser
(*---------------------------------------------------------------------------
Copyright (c) 2011 Daniel C. Bünzli
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
---------------------------------------------------------------------------*)

194
src/cmdliner_cline.ml Normal file
View File

@ -0,0 +1,194 @@
(*---------------------------------------------------------------------------
Copyright (c) 2011 Daniel C. Bünzli. All rights reserved.
Distributed under the ISC license, see terms at the end of the file.
%%NAME%% %%VERSION%%
---------------------------------------------------------------------------*)
open Result
(* A command line stores pre-parsed information about the command
line's arguments in a more structured way. Given the
Cmdliner_info.arg values mentioned in a term and Sys.argv
(without exec name) we parse the command line into a map of
Cmdliner_info.arg values to [arg] values (see below). This map is used by
the term's closures to retrieve and convert command line arguments
(see the Cmdliner_arg module). *)
let err_multi_opt_name_def name a a' =
Cmdliner_base.err_multi_def
~kind:"option name" name Cmdliner_info.arg_doc a a'
module Amap = Map.Make (Cmdliner_info.Arg)
type arg = (* unconverted argument data as found on the command line. *)
| O of (int * string * (string option)) list (* (pos, name, value) of opt. *)
| P of string list
type t = arg Amap.t (* command line, maps arg_infos to arg value. *)
let get_arg cl a = try Amap.find a cl with Not_found -> assert false
let opt_arg cl a = match get_arg cl a with O l -> l | _ -> assert false
let pos_arg cl a = match get_arg cl a with P l -> l | _ -> assert false
let arg_info_indexes args =
(* from [args] returns a trie mapping the names of optional arguments to
their arg_info, a list with all arg_info for positional arguments and
a cmdline mapping each arg_info to an empty [arg]. *)
let rec loop optidx posidx cl = function
| [] -> optidx, posidx, cl
| a :: l ->
match Cmdliner_info.arg_is_pos a with
| true -> loop optidx (a :: posidx) (Amap.add a (P []) cl) l
| false ->
let add t name = match Cmdliner_trie.add t name a with
| `New t -> t
| `Replaced (a', _) -> invalid_arg (err_multi_opt_name_def name a a')
in
let names = Cmdliner_info.arg_opt_names a in
let optidx = List.fold_left add optidx names in
loop optidx posidx (Amap.add a (O []) cl) l
in
loop Cmdliner_trie.empty [] Amap.empty (Cmdliner_info.Args.elements args)
(* Optional argument parsing *)
let is_opt s = String.length s > 1 && s.[0] = '-'
let is_short_opt s = String.length s = 2 && s.[0] = '-'
let parse_opt_arg s = (* (name, value) of opt arg, assert len > 1. *)
let l = String.length s in
if s.[1] <> '-' then (* short opt *)
if l = 2 then s, None else
String.sub s 0 2, Some (String.sub s 2 (l - 2)) (* with glued opt arg *)
else try (* long opt *)
let i = String.index s '=' in
String.sub s 0 i, Some (String.sub s (i + 1) (l - i - 1))
with Not_found -> s, None
let hint_matching_opt optidx s =
(* hint options that could match [s] in [optidx]. FIXME explain this is
a bit obscure. *)
if String.length s <= 2 then [] else
let short_opt, long_opt =
if s.[1] <> '-'
then s, Printf.sprintf "-%s" s
else String.sub s 1 (String.length s - 1), s
in
let short_opt, _ = parse_opt_arg short_opt in
let long_opt, _ = parse_opt_arg long_opt in
let all = Cmdliner_trie.ambiguities optidx "-" in
match List.mem short_opt all, Cmdliner_suggest.value long_opt all with
| false, [] -> []
| false, l -> l
| true, [] -> [short_opt]
| true, l -> if List.mem short_opt l then l else short_opt :: l
let parse_opt_args ~peek_opts optidx cl args =
(* returns an updated [cl] cmdline according to the options found in [args]
with the trie index [optidx]. Positional arguments are returned in order
in a list. *)
let rec loop errs k cl pargs = function
| [] -> List.rev errs, cl, List.rev pargs
| "--" :: args -> List.rev errs, cl, (List.rev_append pargs args)
| s :: args ->
if not (is_opt s) then loop errs (k + 1) cl (s :: pargs) args else
let name, value = parse_opt_arg s in
match Cmdliner_trie.find optidx name with
| `Ok a ->
let value, args = match value, Cmdliner_info.arg_opt_kind a with
| Some v, Cmdliner_info.Flag when is_short_opt name ->
None, ("-" ^ v) :: args
| Some _, _ -> value, args
| None, Cmdliner_info.Flag -> value, args
| None, _ ->
match args with
| [] -> None, args
| v :: rest -> if is_opt v then None, args else Some v, rest
in
let arg = O ((k, name, value) :: opt_arg cl a) in
loop errs (k + 1) (Amap.add a arg cl) pargs args
| `Not_found when peek_opts -> loop errs (k + 1) cl pargs args
| `Not_found ->
let hints = hint_matching_opt optidx s in
let err = Cmdliner_base.err_unknown ~kind:"option" ~hints name in
loop (err :: errs) (k + 1) cl pargs args
| `Ambiguous ->
let ambs = Cmdliner_trie.ambiguities optidx name in
let ambs = List.sort compare ambs in
let err = Cmdliner_base.err_ambiguous "option" name ambs in
loop (err :: errs) (k + 1) cl pargs args
in
let errs, cl, pargs = loop [] 0 cl [] args in
if errs = [] then Ok (cl, pargs) else
let err = String.concat "\n" errs in
Error (err, cl, pargs)
let take_range start stop l =
let rec loop i acc = function
| [] -> List.rev acc
| v :: vs ->
if i < start then loop (i + 1) acc vs else
if i <= stop then loop (i + 1) (v :: acc) vs else
List.rev acc
in
loop 0 [] l
let process_pos_args posidx cl pargs =
(* returns an updated [cl] cmdline in which each positional arg mentioned
in the list index posidx, is given a value according the list
of positional arguments values [pargs]. *)
if pargs = [] then
let misses = List.filter Cmdliner_info.arg_is_req posidx in
if misses = [] then Ok cl else
Error (Cmdliner_msg.err_pos_misses misses, cl)
else
let last = List.length pargs - 1 in
let pos rev k = if rev then last - k else k in
let rec loop misses cl max_spec = function
| [] -> misses, cl, max_spec
| a :: al ->
let apos = Cmdliner_info.arg_pos a in
let rev = Cmdliner_info.pos_rev apos in
let start = pos rev (Cmdliner_info.pos_start apos) in
let stop = match Cmdliner_info.pos_len apos with
| None -> pos rev last
| Some n -> pos rev (Cmdliner_info.pos_start apos + n - 1)
in
let start, stop = if rev then stop, start else start, stop in
let args = take_range start stop pargs in
let max_spec = max stop max_spec in
let cl = Amap.add a (P args) cl in
let misses = match Cmdliner_info.arg_is_req a && args = [] with
| true -> a :: misses
| false -> misses
in
loop misses cl max_spec al
in
let misses, cl, max_spec = loop [] cl (-1) posidx in
if misses <> [] then Error (Cmdliner_msg.err_pos_misses misses, cl) else
if last <= max_spec then Ok cl else
let excess = take_range (max_spec + 1) last pargs in
Error (Cmdliner_msg.err_pos_excess excess, cl)
let create ?(peek_opts = false) al args =
let optidx, posidx, cl = arg_info_indexes al in
match parse_opt_args ~peek_opts optidx cl args with
| Ok (cl, _) when peek_opts -> Ok cl
| Ok (cl, pargs) -> process_pos_args posidx cl pargs
| Error (errs, cl, _) -> Error (errs, cl)
(*---------------------------------------------------------------------------
Copyright (c) 2011 Daniel C. Bünzli
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
---------------------------------------------------------------------------*)

34
src/cmdliner_cline.mli Normal file
View File

@ -0,0 +1,34 @@
(*---------------------------------------------------------------------------
Copyright (c) 2011 Daniel C. Bünzli. All rights reserved.
Distributed under the ISC license, see terms at the end of the file.
%%NAME%% %%VERSION%%
---------------------------------------------------------------------------*)
open Result
(** Command lines. *)
type t
val create :
?peek_opts:bool -> Cmdliner_info.args -> string list ->
(t, string * t) result
val opt_arg : t -> Cmdliner_info.arg -> (int * string * (string option)) list
val pos_arg : t -> Cmdliner_info.arg -> string list
(*---------------------------------------------------------------------------
Copyright (c) 2011 Daniel C. Bünzli
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
---------------------------------------------------------------------------*)

352
src/cmdliner_docgen.ml Normal file
View File

@ -0,0 +1,352 @@
(*---------------------------------------------------------------------------
Copyright (c) 2011 Daniel C. Bünzli. All rights reserved.
Distributed under the ISC license, see terms at the end of the file.
%%NAME%% %%VERSION%%
---------------------------------------------------------------------------*)
let rev_compare n0 n1 = compare n1 n0
let strf = Printf.sprintf
let esc = Cmdliner_manpage.escape
let term_name t = esc @@ Cmdliner_info.term_name t
let sorted_items_to_blocks ~boilerplate:b items =
(* Items are sorted by section and then rev. sorted by appearance.
We gather them by section in correct order in a `Block and prefix
them with optional boilerplate *)
let boilerplate = match b with None -> (fun _ -> None) | Some b -> b in
let mk_block sec acc = match boilerplate sec with
| None -> (sec, `Blocks acc)
| Some b -> (sec, `Blocks (b :: acc))
in
let rec loop secs sec acc = function
| (sec', it) :: its when sec' = sec -> loop secs sec (it :: acc) its
| (sec', it) :: its -> loop (mk_block sec acc :: secs) sec' [it] its
| [] -> (mk_block sec acc) :: secs
in
match items with
| [] -> []
| (sec, it) :: its -> loop [] sec [it] its
(* Doc string variables substitutions. *)
let env_info_subst ~subst e = function
| "env" -> Some (strf "$(b,%s)" @@ esc (Cmdliner_info.env_var e))
| id -> subst id
let exit_info_subst ~subst e = function
| "status" -> Some (strf "%d" (fst @@ Cmdliner_info.exit_statuses e))
| "status_max" -> Some (strf "%d" (snd @@ Cmdliner_info.exit_statuses e))
| id -> subst id
let arg_info_subst ~subst a = function
| "docv" ->
Some (strf "$(i,%s)" @@ esc (Cmdliner_info.arg_docv a))
| "opt" when Cmdliner_info.arg_is_opt a ->
Some (strf "$(b,%s)" @@ esc (Cmdliner_info.arg_opt_name_sample a))
| "env" as id ->
begin match Cmdliner_info.arg_env a with
| Some e -> env_info_subst ~subst e id
| None -> subst id
end
| id -> subst id
let term_info_subst ei = function
| "tname" -> Some (strf "$(b,%s)" @@ term_name (Cmdliner_info.eval_term ei))
| "mname" -> Some (strf "$(b,%s)" @@ term_name (Cmdliner_info.eval_main ei))
| _ -> None
(* Command docs *)
let invocation ?(sep = ' ') ei = match Cmdliner_info.eval_kind ei with
| `Simple | `Multiple_main -> term_name (Cmdliner_info.eval_main ei)
| `Multiple_sub ->
strf "%s%c%s"
Cmdliner_info.(term_name @@ eval_main ei) sep
Cmdliner_info.(term_name @@ eval_term ei)
let plain_invocation ei = invocation ei
let invocation ?sep ei = esc @@ invocation ?sep ei
let synopsis_pos_arg a =
let v = match Cmdliner_info.arg_docv a with "" -> "ARG" | v -> v in
let v = strf "$(i,%s)" (esc v) in
let v = (if Cmdliner_info.arg_is_req a then strf "%s" else strf "[%s]") v in
match Cmdliner_info.(pos_len @@ arg_pos a) with
| None -> v ^ "..."
| Some 1 -> v
| Some n ->
let rec loop n acc = if n <= 0 then acc else loop (n - 1) (v :: acc) in
String.concat " " (loop n [])
let synopsis ei = match Cmdliner_info.eval_kind ei with
| `Multiple_main -> strf "$(b,%s) $(i,COMMAND) ..." @@ invocation ei
| `Simple | `Multiple_sub ->
let rev_cli_order (a0, _) (a1, _) =
Cmdliner_info.rev_arg_pos_cli_order a0 a1
in
let add_pos a acc = match Cmdliner_info.arg_is_opt a with
| true -> acc
| false -> (a, synopsis_pos_arg a) :: acc
in
let args = Cmdliner_info.(term_args @@ eval_term ei) in
let pargs = Cmdliner_info.Args.fold add_pos args [] in
let pargs = List.sort rev_cli_order pargs in
let pargs = String.concat " " (List.rev_map snd pargs) in
strf "$(b,%s) [$(i,OPTION)]... %s" (invocation ei) pargs
let cmd_docs ei = match Cmdliner_info.eval_kind ei with
| `Simple | `Multiple_sub -> []
| `Multiple_main ->
let add_cmd acc t =
let cmd = strf "$(b,%s)" @@ term_name t in
(Cmdliner_info.term_docs t, `I (cmd, Cmdliner_info.term_doc t)) :: acc
in
let by_sec_by_rev_name (s0, `I (c0, _)) (s1, `I (c1, _)) =
let c = compare s0 s1 in
if c <> 0 then c else compare c1 c0 (* N.B. reverse *)
in
let cmds = List.fold_left add_cmd [] (Cmdliner_info.eval_choices ei) in
let cmds = List.sort by_sec_by_rev_name cmds in
let cmds = (cmds :> (string * Cmdliner_manpage.block) list) in
sorted_items_to_blocks ~boilerplate:None cmds
(* Argument docs *)
let arg_man_item_label a =
if Cmdliner_info.arg_is_pos a
then strf "$(i,%s)" (esc @@ Cmdliner_info.arg_docv a) else
let fmt_name var = match Cmdliner_info.arg_opt_kind a with
| Cmdliner_info.Flag -> fun n -> strf "$(b,%s)" (esc n)
| Cmdliner_info.Opt ->
fun n ->
if String.length n > 2
then strf "$(b,%s)=$(i,%s)" (esc n) (esc var)
else strf "$(b,%s) $(i,%s)" (esc n) (esc var)
| Cmdliner_info.Opt_vopt _ ->
fun n ->
if String.length n > 2
then strf "$(b,%s)[=$(i,%s)]" (esc n) (esc var)
else strf "$(b,%s) [$(i,%s)]" (esc n) (esc var)
in
let var = match Cmdliner_info.arg_docv a with "" -> "VAL" | v -> v in
let names = List.sort compare (Cmdliner_info.arg_opt_names a) in
let s = String.concat ", " (List.rev_map (fmt_name var) names) in
s
let arg_to_man_item ~errs ~subst ~buf a =
let or_env ~value a = match Cmdliner_info.arg_env a with
| None -> ""
| Some e ->
let value = if value then " or" else "absent " in
strf "%s $(b,%s) env" value (esc @@ Cmdliner_info.env_var e)
in
let absent = match Cmdliner_info.arg_absent a with
| Cmdliner_info.Err -> ""
| Cmdliner_info.Val v ->
match Lazy.force v with
| "" -> strf "%s" (or_env ~value:false a)
| v -> strf "absent=%s%s" v (or_env ~value:true a)
in
let optvopt = match Cmdliner_info.arg_opt_kind a with
| Cmdliner_info.Opt_vopt v -> strf "default=%s" v
| _ -> ""
in
let argvdoc = match optvopt, absent with
| "", "" -> ""
| s, "" | "", s -> strf " (%s)" s
| s, s' -> strf " (%s) (%s)" s s'
in
let subst = arg_info_subst ~subst a in
let doc = Cmdliner_info.arg_doc a in
let doc = Cmdliner_manpage.subst_vars ~errs ~subst buf doc in
(Cmdliner_info.arg_docs a, `I (arg_man_item_label a ^ argvdoc, doc))
let arg_docs ~errs ~subst ~buf ei =
let by_sec_by_arg a0 a1 =
let c = compare (Cmdliner_info.arg_docs a0) (Cmdliner_info.arg_docs a1) in
if c <> 0 then c else
match Cmdliner_info.arg_is_opt a0, Cmdliner_info.arg_is_opt a1 with
| true, true -> (* optional by name *)
let key names =
let k = List.hd (List.sort rev_compare names) in
let k = Cmdliner_base.lowercase k in
if k.[1] = '-' then String.sub k 1 (String.length k - 1) else k
in
compare
(key @@ Cmdliner_info.arg_opt_names a0)
(key @@ Cmdliner_info.arg_opt_names a1)
| false, false -> (* positional by variable *)
compare
(Cmdliner_base.lowercase @@ Cmdliner_info.arg_docv a0)
(Cmdliner_base.lowercase @@ Cmdliner_info.arg_docv a1)
| true, false -> -1 (* positional first *)
| false, true -> 1 (* optional after *)
in
let keep_arg a acc =
if not Cmdliner_info.(arg_is_pos a && (arg_docv a = "" || arg_doc a = ""))
then (a :: acc) else acc
in
let args = Cmdliner_info.(term_args @@ eval_term ei) in
let args = Cmdliner_info.Args.fold keep_arg args [] in
let args = List.sort by_sec_by_arg args in
let args = List.rev_map (arg_to_man_item ~errs ~subst ~buf) args in
sorted_items_to_blocks ~boilerplate:None args
(* Exit statuses doc *)
let exit_boilerplate sec = match sec = Cmdliner_manpage.s_exit_status with
| false -> None
| true -> Some (Cmdliner_manpage.s_exit_status_intro)
let exit_docs ~errs ~subst ~buf ~has_sexit ei =
let by_sec (s0, _) (s1, _) = compare s0 s1 in
let add_exit_item acc e =
let subst = exit_info_subst ~subst e in
let min, max = Cmdliner_info.exit_statuses e in
let doc = Cmdliner_info.exit_doc e in
let label = if min = max then strf "%d" min else strf "%d-%d" min max in
let item = `I (label, Cmdliner_manpage.subst_vars ~errs ~subst buf doc) in
Cmdliner_info.(exit_docs e, item) :: acc
in
let exits = Cmdliner_info.(term_exits @@ eval_term ei) in
let exits = List.sort Cmdliner_info.exit_order exits in
let exits = List.fold_left add_exit_item [] exits in
let exits = List.stable_sort by_sec (* sort by section *) exits in
let boilerplate = if has_sexit then None else Some exit_boilerplate in
sorted_items_to_blocks ~boilerplate exits
(* Environment doc *)
let env_boilerplate sec = match sec = Cmdliner_manpage.s_environment with
| false -> None
| true -> Some (Cmdliner_manpage.s_environment_intro)
let env_docs ~errs ~subst ~buf ~has_senv ei =
let add_env_item ~subst (seen, envs as acc) e =
if Cmdliner_info.Envs.mem e seen then acc else
let seen = Cmdliner_info.Envs.add e seen in
let var = strf "$(b,%s)" @@ esc (Cmdliner_info.env_var e) in
let doc = Cmdliner_info.env_doc e in
let doc = Cmdliner_manpage.subst_vars ~errs ~subst buf doc in
let envs = (Cmdliner_info.env_docs e, `I (var, doc)) :: envs in
seen, envs
in
let add_arg_env a acc = match Cmdliner_info.arg_env a with
| None -> acc
| Some e -> add_env_item ~subst:(arg_info_subst ~subst a) acc e
in
let add_env acc e = add_env_item ~subst:(env_info_subst ~subst e) acc e in
let by_sec_by_rev_name (s0, `I (v0, _)) (s1, `I (v1, _)) =
let c = compare s0 s1 in
if c <> 0 then c else compare v1 v0 (* N.B. reverse *)
in
(* Arg envs before term envs is important here: if the same is mentioned
both in an arg and in a term the substs of the arg are allowed. *)
let args = Cmdliner_info.(term_args @@ eval_term ei) in
let tenvs = Cmdliner_info.(term_envs @@ eval_term ei) in
let init = Cmdliner_info.Envs.empty, [] in
let acc = Cmdliner_info.Args.fold add_arg_env args init in
let _, envs = List.fold_left add_env acc tenvs in
let envs = List.sort by_sec_by_rev_name envs in
let envs = (envs :> (string * Cmdliner_manpage.block) list) in
let boilerplate = if has_senv then None else Some env_boilerplate in
sorted_items_to_blocks ~boilerplate envs
(* xref doc *)
let xref_docs ~errs ei =
let main = Cmdliner_info.(term_name @@ eval_main ei) in
let to_xref = function
| `Main -> 1, main
| `Tool tool -> 1, tool
| `Page (sec, name) -> sec, name
| `Cmd c ->
if Cmdliner_info.eval_has_choice ei c then 1, strf "%s-%s" main c else
(Format.fprintf errs "xref %s: no such term name@." c; 0, "doc-err")
in
let xref_str (sec, name) = strf "%s(%d)" (esc name) sec in
let xrefs = Cmdliner_info.(term_man_xrefs @@ eval_term ei) in
let xrefs = List.fold_left (fun acc x -> to_xref x :: acc) [] xrefs in
let xrefs = List.(rev_map xref_str (sort rev_compare xrefs)) in
if xrefs = [] then [] else
[Cmdliner_manpage.s_see_also, `P (String.concat ", " xrefs)]
(* Man page construction *)
let ensure_s_name ei sm =
if Cmdliner_manpage.(smap_has_section sm s_name) then sm else
let tname = invocation ~sep:'-' ei in
let tdoc = Cmdliner_info.(term_doc @@ eval_term ei) in
let tagline = if tdoc = "" then "" else strf " - %s" tdoc in
let tagline = `P (strf "%s%s" tname tagline) in
Cmdliner_manpage.(smap_append_block sm ~sec:s_name tagline)
let ensure_s_synopsis ei sm =
if Cmdliner_manpage.(smap_has_section sm ~sec:s_synopsis) then sm else
let synopsis = `P (synopsis ei) in
Cmdliner_manpage.(smap_append_block sm ~sec:s_synopsis synopsis)
let insert_term_man_docs ~errs ei sm =
let buf = Buffer.create 200 in
let subst = term_info_subst ei in
let ins sm (s, b) = Cmdliner_manpage.smap_append_block sm s b in
let has_senv = Cmdliner_manpage.(smap_has_section sm s_environment) in
let has_sexit = Cmdliner_manpage.(smap_has_section sm s_exit_status) in
let sm = List.fold_left ins sm (cmd_docs ei) in
let sm = List.fold_left ins sm (arg_docs ~errs ~subst ~buf ei) in
let sm = List.fold_left ins sm (exit_docs ~errs ~subst ~buf ~has_sexit ei)in
let sm = List.fold_left ins sm (env_docs ~errs ~subst ~buf ~has_senv ei) in
let sm = List.fold_left ins sm (xref_docs ~errs ei) in
sm
let text ~errs ei =
let man = Cmdliner_info.(term_man @@ eval_term ei) in
let sm = Cmdliner_manpage.smap_of_blocks man in
let sm = ensure_s_name ei sm in
let sm = ensure_s_synopsis ei sm in
let sm = insert_term_man_docs ei ~errs sm in
Cmdliner_manpage.smap_to_blocks sm
let title ei =
let main = Cmdliner_info.eval_main ei in
let exec = Cmdliner_base.capitalize (Cmdliner_info.term_name main) in
let name = Cmdliner_base.uppercase (invocation ~sep:'-' ei) in
let center_header = esc @@ strf "%s Manual" exec in
let left_footer =
let version = match Cmdliner_info.term_version main with
| None -> "" | Some v -> " " ^ v
in
esc @@ strf "%s%s" exec version
in
name, 1, "", left_footer, center_header
let man ~errs ei = title ei, text ~errs ei
let pp_man ~errs fmt ppf ei =
Cmdliner_manpage.print
~errs ~subst:(term_info_subst ei) fmt ppf (man ~errs ei)
(* Plain synopsis for usage *)
let pp_plain_synopsis ~errs ppf ei =
let buf = Buffer.create 100 in
let subst = term_info_subst ei in
let syn = Cmdliner_manpage.doc_to_plain ~errs ~subst buf (synopsis ei) in
Format.fprintf ppf "@[%s@]" syn
(*---------------------------------------------------------------------------
Copyright (c) 2011 Daniel C. Bünzli
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
---------------------------------------------------------------------------*)

30
src/cmdliner_docgen.mli Normal file
View File

@ -0,0 +1,30 @@
(*---------------------------------------------------------------------------
Copyright (c) 2011 Daniel C. Bünzli. All rights reserved.
Distributed under the ISC license, see terms at the end of the file.
%%NAME%% %%VERSION%%
---------------------------------------------------------------------------*)
val plain_invocation : Cmdliner_info.eval -> string
val pp_man :
errs:Format.formatter -> Cmdliner_manpage.format -> Format.formatter ->
Cmdliner_info.eval -> unit
val pp_plain_synopsis :
errs:Format.formatter -> Format.formatter -> Cmdliner_info.eval -> unit
(*---------------------------------------------------------------------------
Copyright (c) 2011 Daniel C. Bünzli
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
---------------------------------------------------------------------------*)

233
src/cmdliner_info.ml Normal file
View File

@ -0,0 +1,233 @@
(*---------------------------------------------------------------------------
Copyright (c) 2011 Daniel C. Bünzli. All rights reserved.
Distributed under the ISC license, see terms at the end of the file.
%%NAME%% %%VERSION%%
---------------------------------------------------------------------------*)
let new_id = (* thread-safe UIDs, Oo.id (object end) was used before. *)
let c = ref 0 in
fun () ->
let id = !c in
incr c; if id > !c then assert false (* too many ids *) else id
(* Environments *)
type env = (* information about an environment variable. *)
{ env_id : int; (* unique id for the env var. *)
env_var : string; (* the variable. *)
env_doc : string; (* help. *)
env_docs : string; } (* title of help section where listed. *)
let env
?docs:(env_docs = Cmdliner_manpage.s_environment)
?doc:(env_doc = "See option $(opt).") env_var =
{ env_id = new_id (); env_var; env_doc; env_docs }
let env_var e = e.env_var
let env_doc e = e.env_doc
let env_docs e = e.env_docs
module Env = struct
type t = env
let compare a0 a1 = (compare : int -> int -> int) a0.env_id a1.env_id
end
module Envs = Set.Make (Env)
type envs = Envs.t
(* Arguments *)
type arg_absence = Err | Val of string Lazy.t
type opt_kind = Flag | Opt | Opt_vopt of string
type pos_kind = (* information about a positional argument. *)
{ pos_rev : bool; (* if [true] positions are counted from the end. *)
pos_start : int; (* start positional argument. *)
pos_len : int option } (* number of arguments or [None] if unbounded. *)
let pos ~rev:pos_rev ~start:pos_start ~len:pos_len =
{ pos_rev; pos_start; pos_len}
let pos_rev p = p.pos_rev
let pos_start p = p.pos_start
let pos_len p = p.pos_len
type arg = (* information about a command line argument. *)
{ id : int; (* unique id for the argument. *)
absent : arg_absence; (* behaviour if absent. *)
env : env option; (* environment variable. *)
doc : string; (* help. *)
docv : string; (* variable name for the argument in help. *)
docs : string; (* title of help section where listed. *)
pos : pos_kind; (* positional arg kind. *)
opt_kind : opt_kind; (* optional arg kind. *)
opt_names : string list; (* names (for opt args). *)
opt_all : bool; } (* repeatable (for opt args). *)
let dumb_pos = pos ~rev:false ~start:(-1) ~len:None
let arg ?docs ?(docv = "") ?(doc = "") ?env names =
let dash n = if String.length n = 1 then "-" ^ n else "--" ^ n in
let opt_names = List.map dash names in
let docs = match docs with
| Some s -> s
| None ->
match names with
| [] -> Cmdliner_manpage.s_arguments
| _ -> Cmdliner_manpage.s_options
in
{ id = new_id (); absent = Val (lazy ""); env; doc; docv; docs;
pos = dumb_pos; opt_kind = Flag; opt_names; opt_all = false; }
let arg_id a = a.id
let arg_absent a = a.absent
let arg_env a = a.env
let arg_doc a = a.doc
let arg_docv a = a.docv
let arg_docs a = a.docs
let arg_pos a = a.pos
let arg_opt_kind a = a.opt_kind
let arg_opt_names a = a.opt_names
let arg_opt_all a = a.opt_all
let arg_opt_name_sample a =
(* First long or short name (in that order) in the list; this
allows the client to control which name is shown *)
let rec find = function
| [] -> List.hd a.opt_names
| n :: ns -> if (String.length n) > 2 then n else find ns
in
find a.opt_names
let arg_make_req a = { a with absent = Err }
let arg_make_all_opts a = { a with opt_all = true }
let arg_make_opt ~absent ~kind:opt_kind a = { a with absent; opt_kind }
let arg_make_opt_all ~absent ~kind:opt_kind a =
{ a with absent; opt_kind; opt_all = true }
let arg_make_pos ~pos a = { a with pos }
let arg_make_pos_abs ~absent ~pos a = { a with absent; pos }
let arg_is_opt a = a.opt_names <> []
let arg_is_pos a = a.opt_names = []
let arg_is_req a = a.absent = Err
let arg_pos_cli_order a0 a1 = (* best-effort order on the cli. *)
let c = compare (a0.pos.pos_rev) (a1.pos.pos_rev) in
if c <> 0 then c else
if a0.pos.pos_rev
then compare a1.pos.pos_start a0.pos.pos_start
else compare a0.pos.pos_start a1.pos.pos_start
let rev_arg_pos_cli_order a0 a1 = arg_pos_cli_order a1 a0
module Arg = struct
type t = arg
let compare a0 a1 = (compare : int -> int -> int) a0.id a1.id
end
module Args = Set.Make (Arg)
type args = Args.t
(* Exit info *)
type exit =
{ exit_statuses : int * int;
exit_doc : string;
exit_docs : string; }
let exit
?docs:(exit_docs = Cmdliner_manpage.s_exit_status)
?doc:(exit_doc = "undocumented") ?max min =
let max = match max with None -> min | Some max -> max in
{ exit_statuses = (min, max); exit_doc; exit_docs }
let exit_statuses e = e.exit_statuses
let exit_doc e = e.exit_doc
let exit_docs e = e.exit_docs
let exit_order e0 e1 = compare e0.exit_statuses e1.exit_statuses
(* Term info *)
type term_info =
{ term_name : string; (* name of the term. *)
term_version : string option; (* version (for --version). *)
term_doc : string; (* one line description of term. *)
term_docs : string; (* title of man section where listed (commands). *)
term_sdocs : string; (* standard options, title of section where listed. *)
term_exits : exit list; (* exit codes for the term. *)
term_envs : env list; (* env vars that influence the term. *)
term_man : Cmdliner_manpage.block list; (* man page text. *)
term_man_xrefs : Cmdliner_manpage.xref list; } (* man cross-refs. *)
type term =
{ term_info : term_info;
term_args : args; }
let term
?args:(term_args = Args.empty) ?man_xrefs:(term_man_xrefs = [])
?man:(term_man = []) ?envs:(term_envs = []) ?exits:(term_exits = [])
?sdocs:(term_sdocs = Cmdliner_manpage.s_options)
?docs:(term_docs = "COMMANDS") ?doc:(term_doc = "") ?version:term_version
term_name =
let term_info =
{ term_name; term_version; term_doc; term_docs; term_sdocs; term_exits;
term_envs; term_man; term_man_xrefs }
in
{ term_info; term_args }
let term_name t = t.term_info.term_name
let term_version t = t.term_info.term_version
let term_doc t = t.term_info.term_doc
let term_docs t = t.term_info.term_docs
let term_stdopts_docs t = t.term_info.term_sdocs
let term_exits t = t.term_info.term_exits
let term_envs t = t.term_info.term_envs
let term_man t = t.term_info.term_man
let term_man_xrefs t = t.term_info.term_man_xrefs
let term_args t = t.term_args
let term_add_args t args =
{ t with term_args = Args.union args t.term_args }
(* Eval info *)
type eval = (* information about the evaluation context. *)
{ term : term; (* term being evaluated. *)
main : term; (* main term. *)
choices : term list; (* all term choices. *)
env : string -> string option } (* environment variable lookup. *)
let eval ~term ~main ~choices ~env = { term; main; choices; env }
let eval_term e = e.term
let eval_main e = e.main
let eval_choices e = e.choices
let eval_env_var e v = e.env v
let eval_kind ei =
if ei.choices = [] then `Simple else
if (ei.term.term_info.term_name == ei.main.term_info.term_name)
then `Multiple_main else `Multiple_sub
let eval_with_term ei term = { ei with term }
let eval_has_choice e cmd =
let is_cmd t = t.term_info.term_name = cmd in
List.exists is_cmd e.choices
(*---------------------------------------------------------------------------
Copyright (c) 2011 Daniel C. Bünzli
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
---------------------------------------------------------------------------*)

140
src/cmdliner_info.mli Normal file
View File

@ -0,0 +1,140 @@
(*---------------------------------------------------------------------------
Copyright (c) 2011 Daniel C. Bünzli. All rights reserved.
Distributed under the ISC license, see terms at the end of the file.
%%NAME%% %%VERSION%%
---------------------------------------------------------------------------*)
(** Terms, argument, env vars information.
The following types keep untyped information about arguments and
terms. This data is used to parse the command line, report errors
and format man pages. *)
(** {1:env Environment variables} *)
type env
val env : ?docs:string -> ?doc:string -> string -> env
val env_var : env -> string
val env_doc : env -> string
val env_docs : env -> string
module Env : Set.OrderedType with type t = env
module Envs : Set.S with type elt = env
type envs = Envs.t
(** {1:arg Arguments} *)
type arg_absence =
| Err (** an error is reported. *)
| Val of string Lazy.t (** if <> "", takes the given default value. *)
(** The type for what happens if the argument is absent from the cli. *)
type opt_kind =
| Flag (** without value, just a flag. *)
| Opt (** with required value. *)
| Opt_vopt of string (** with optional value, takes given default. *)
(** The type for optional argument kinds. *)
type pos_kind
val pos : rev:bool -> start:int -> len:int option -> pos_kind
val pos_rev : pos_kind -> bool
val pos_start : pos_kind -> int
val pos_len : pos_kind -> int option
type arg
val arg :
?docs:string -> ?docv:string -> ?doc:string -> ?env:env ->
string list -> arg
val arg_id : arg -> int
val arg_absent : arg -> arg_absence
val arg_env : arg -> env option
val arg_doc : arg -> string
val arg_docv : arg -> string
val arg_docs : arg -> string
val arg_opt_names : arg -> string list (* has dashes *)
val arg_opt_name_sample : arg -> string (* warning must be an opt arg *)
val arg_opt_kind : arg -> opt_kind
val arg_pos : arg -> pos_kind
val arg_make_req : arg -> arg
val arg_make_all_opts : arg -> arg
val arg_make_opt : absent:arg_absence -> kind:opt_kind -> arg -> arg
val arg_make_opt_all : absent:arg_absence -> kind:opt_kind -> arg -> arg
val arg_make_pos : pos:pos_kind -> arg -> arg
val arg_make_pos_abs : absent:arg_absence -> pos:pos_kind -> arg -> arg
val arg_is_opt : arg -> bool
val arg_is_pos : arg -> bool
val arg_is_req : arg -> bool
val arg_pos_cli_order : arg -> arg -> int
val rev_arg_pos_cli_order : arg -> arg -> int
module Arg : Set.OrderedType with type t = arg
module Args : Set.S with type elt = arg
type args = Args.t
(** {1:exit Exit status} *)
type exit
val exit : ?docs:string -> ?doc:string -> ?max:int -> int -> exit
val exit_statuses : exit -> int * int
val exit_doc : exit -> string
val exit_docs : exit -> string
val exit_order : exit -> exit -> int
(** {1:term Term information} *)
type term
val term :
?args:args -> ?man_xrefs:Cmdliner_manpage.xref list ->
?man:Cmdliner_manpage.block list -> ?envs:env list -> ?exits:exit list ->
?sdocs:string -> ?docs:string -> ?doc:string -> ?version:string ->
string -> term
val term_name : term -> string
val term_version : term -> string option
val term_doc : term -> string
val term_docs : term -> string
val term_stdopts_docs : term -> string
val term_exits : term -> exit list
val term_envs : term -> env list
val term_man : term -> Cmdliner_manpage.block list
val term_man_xrefs : term -> Cmdliner_manpage.xref list
val term_args : term -> args
val term_add_args : term -> args -> term
(** {1:eval Evaluation information} *)
type eval
val eval :
term:term -> main:term -> choices:term list ->
env:(string -> string option) -> eval
val eval_term : eval -> term
val eval_main : eval -> term
val eval_choices : eval -> term list
val eval_env_var : eval -> string -> string option
val eval_kind : eval -> [> `Multiple_main | `Multiple_sub | `Simple ]
val eval_with_term : eval -> term -> eval
val eval_has_choice : eval -> string -> bool
(*---------------------------------------------------------------------------
Copyright (c) 2011 Daniel C. Bünzli
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
---------------------------------------------------------------------------*)

504
src/cmdliner_manpage.ml Normal file
View File

@ -0,0 +1,504 @@
(*---------------------------------------------------------------------------
Copyright (c) 2011 Daniel C. Bünzli. All rights reserved.
Distributed under the ISC license, see terms at the end of the file.
%%NAME%% %%VERSION%%
---------------------------------------------------------------------------*)
open Result
(* Manpages *)
type block =
[ `S of string | `P of string | `Pre of string | `I of string * string
| `Noblank | `Blocks of block list ]
type title = string * int * string * string * string
type t = title * block list
type xref =
[ `Main | `Cmd of string | `Tool of string | `Page of int * string ]
(* Standard sections *)
let s_name = "NAME"
let s_synopsis = "SYNOPSIS"
let s_description = "DESCRIPTION"
let s_commands = "COMMANDS"
let s_arguments = "ARGUMENTS"
let s_options = "OPTIONS"
let s_common_options = "COMMON OPTIONS"
let s_exit_status = "EXIT STATUS"
let s_exit_status_intro =
`P "$(tname) exits with the following status:"
let s_environment = "ENVIRONMENT"
let s_environment_intro =
`P "These environment variables affect the execution of $(tname):"
let s_files = "FILES"
let s_examples = "EXAMPLES"
let s_bugs = "BUGS"
let s_authors = "AUTHORS"
let s_see_also = "SEE ALSO"
(* Section order *)
let s_created = ""
let order =
[| s_name; s_synopsis; s_description; s_created; s_commands;
s_arguments; s_options; s_common_options; s_exit_status;
s_environment; s_files; s_examples; s_bugs; s_authors; s_see_also; |]
let order_synopsis = 1
let order_created = 3
let section_of_order i = order.(i)
let section_to_order ~on_unknown s =
let max = Array.length order - 1 in
let rec loop i = match i > max with
| true -> on_unknown
| false -> if order.(i) = s then i else loop (i + 1)
in
loop 0
(* Section maps
Section maps, maps section names to their section order and reversed
content blocks (content is not reversed in `Block blocks). The sections
are listed in reversed order. Unknown sections get the order of the last
known section. *)
type smap = (string * (int * block list)) list
let smap_of_blocks bs = (* N.B. this flattens `Blocks, not t.r. *)
let rec loop s s_o rbs smap = function
| [] -> s, s_o, rbs, smap
| `S new_sec :: bs ->
let new_o = section_to_order ~on_unknown:s_o new_sec in
loop new_sec new_o [] ((s, (s_o, rbs)):: smap) bs
| `Blocks blist :: bs ->
let s, s_o, rbs, rmap = loop s s_o rbs smap blist (* not t.r. *) in
loop s s_o rbs rmap bs
| (`P _ | `Pre _ | `I _ | `Noblank as c) :: bs ->
loop s s_o (c :: rbs) smap bs
in
let first, (bs : block list) = match bs with
| `S s :: bs -> s, bs
| `Blocks (`S s :: blist) :: bs -> s, (`Blocks blist) :: bs
| _ -> "", bs
in
let first_o = section_to_order ~on_unknown:order_synopsis first in
let s, s_o, rc, smap = loop first first_o [] [] bs in
(s, (s_o, rc)) :: smap
let smap_to_blocks smap = (* N.B. this leaves `Blocks content untouched. *)
let rec loop acc smap s = function
| b :: rbs -> loop (b :: acc) smap s rbs
| [] ->
let acc = if s = "" then acc else `S s :: acc in
match smap with
| (s, (_, rbs)) :: smap -> loop acc smap s rbs
| [] -> acc
in
match smap with
| [] -> []
| (s, (_, rbs)) :: smap -> loop [] smap s rbs
let smap_has_section smap ~sec = List.exists (fun (s, _) -> sec = s) smap
let smap_append_block smap ~sec b =
let o = section_to_order ~on_unknown:order_created sec in
let try_insert =
let rec loop max_lt_o left = function
| (s', (o, rbs)) :: right when s' = sec ->
Ok (List.rev_append ((sec, (o, b :: rbs)) :: left) right)
| (_, (o', _) as s) :: right ->
let max_lt_o = if o' < o then max o' max_lt_o else max_lt_o in
loop max_lt_o (s :: left) right
| [] ->
if max_lt_o <> -1 then Error max_lt_o else
Ok (List.rev ((sec, (o, [b])) :: left))
in
loop (-1) [] smap
in
match try_insert with
| Ok smap -> smap
| Error insert_before ->
let rec loop left = function
| (s', (o', _)) :: _ as right when o' = insert_before ->
List.rev_append ((sec, (o, [b])) :: left) right
| s :: ss -> loop (s :: left) ss
| [] -> assert false
in
loop [] smap
(* Formatting tools *)
let strf = Printf.sprintf
let pf = Format.fprintf
let pp_str = Format.pp_print_string
let pp_char = Format.pp_print_char
let pp_indent ppf c = for i = 1 to c do pp_char ppf ' ' done
let pp_lines = Cmdliner_base.pp_lines
let pp_tokens = Cmdliner_base.pp_tokens
(* Cmdliner markup handling *)
let err e fmt = pf e ("cmdliner error: " ^^ fmt ^^ "@.")
let err_unescaped ~errs c s = err errs "unescaped %C in %S" c s
let err_malformed ~errs s = err errs "Malformed $(...) in %S" s
let err_unclosed ~errs s = err errs "Unclosed $(...) in %S" s
let err_undef ~errs id s = err errs "Undefined variable $(%s) in %S" id s
let err_illegal_esc ~errs c s = err errs "Illegal escape char %C in %S" c s
let err_markup ~errs dir s =
err errs "Unknown cmdliner markup $(%c,...) in %S" dir s
let is_markup_dir = function 'i' | 'b' -> true | _ -> false
let is_markup_esc = function '$' | '\\' | '(' | ')' -> true | _ -> false
let markup_need_esc = function '\\' | '$' -> true | _ -> false
let markup_text_need_esc = function '\\' | '$' | ')' -> true | _ -> false
let escape s = (* escapes [s] from doc language. *)
let max_i = String.length s - 1 in
let rec escaped_len i l =
if i > max_i then l else
if markup_text_need_esc s.[i] then escaped_len (i + 1) (l + 2) else
escaped_len (i + 1) (l + 1)
in
let escaped_len = escaped_len 0 0 in
if escaped_len = String.length s then s else
let b = Bytes.create escaped_len in
let rec loop i k =
if i > max_i then Bytes.unsafe_to_string b else
let c = String.unsafe_get s i in
if not (markup_text_need_esc c)
then (Bytes.unsafe_set b k c; loop (i + 1) (k + 1))
else (Bytes.unsafe_set b k '\\'; Bytes.unsafe_set b (k + 1) c;
loop (i + 1) (k + 2))
in
loop 0 0
let subst_vars ~errs ~subst b s =
let max_i = String.length s - 1 in
let flush start stop = match start > max_i with
| true -> ()
| false -> Buffer.add_substring b s start (stop - start + 1)
in
let skip_escape k start i =
if i > max_i then err_unescaped ~errs '\\' s else k start (i + 1)
in
let rec skip_markup k start i =
if i > max_i then (err_unclosed ~errs s; k start i) else
match s.[i] with
| '\\' -> skip_escape (skip_markup k) start (i + 1)
| ')' -> k start (i + 1)
| c -> skip_markup k start (i + 1)
in
let rec add_subst start i =
if i > max_i then (err_unclosed ~errs s; loop start i) else
if s.[i] <> ')' then add_subst start (i + 1) else
let id = String.sub s start (i - start) in
let next = i + 1 in
begin match subst id with
| None -> err_undef ~errs id s; Buffer.add_string b "undefined";
| Some v -> Buffer.add_string b v
end;
loop next next
and loop start i =
if i > max_i then flush start max_i else
let next = i + 1 in
match s.[i] with
| '\\' -> skip_escape loop start next
| '$' ->
if next > max_i then err_unescaped ~errs '$' s else
begin match s.[next] with
| '(' ->
let min = next + 2 in
if min > max_i then (err_unclosed ~errs s; loop start next) else
begin match s.[min] with
| ',' -> skip_markup loop start (min + 1)
| _ ->
let start_id = next + 1 in
flush start (i - 1); add_subst start_id start_id
end
| _ -> err_unescaped ~errs '$' s; loop start next
end;
| c -> loop start next
in
(Buffer.clear b; loop 0 0; Buffer.contents b)
let add_markup_esc ~errs k b s start next target_need_escape target_escape =
let max_i = String.length s - 1 in
if next > max_i then err_unescaped ~errs '\\' s else
match s.[next] with
| c when not (is_markup_esc s.[next]) ->
err_illegal_esc ~errs c s;
k (next + 1) (next + 1)
| c ->
(if target_need_escape c then target_escape b c else Buffer.add_char b c);
k (next + 1) (next + 1)
let add_markup_text ~errs k b s start target_need_escape target_escape =
let max_i = String.length s - 1 in
let flush start stop = match start > max_i with
| true -> ()
| false -> Buffer.add_substring b s start (stop - start + 1)
in
let rec loop start i =
if i > max_i then (err_unclosed ~errs s; flush start max_i) else
let next = i + 1 in
match s.[i] with
| '\\' -> (* unescape *)
flush start (i - 1);
add_markup_esc ~errs loop b s start next
target_need_escape target_escape
| ')' -> flush start (i - 1); k next next
| c when markup_text_need_esc c ->
err_unescaped ~errs c s; flush start (i - 1); loop next next
| c when target_need_escape c ->
flush start (i - 1); target_escape b c; loop next next
| c -> loop start next
in
loop start start
(* Plain text output *)
let markup_to_plain ~errs b s =
let max_i = String.length s - 1 in
let flush start stop = match start > max_i with
| true -> ()
| false -> Buffer.add_substring b s start (stop - start + 1)
in
let need_escape _ = false in
let escape _ _ = assert false in
let rec loop start i =
if i > max_i then flush start max_i else
let next = i + 1 in
match s.[i] with
| '\\' ->
flush start (i - 1);
add_markup_esc ~errs loop b s start next need_escape escape
| '$' ->
if next > max_i then err_unescaped ~errs '$' s else
begin match s.[next] with
| '(' ->
let min = next + 2 in
if min > max_i then (err_unclosed ~errs s; loop start next) else
begin match s.[min] with
| ',' ->
let markup = s.[min - 1] in
if not (is_markup_dir markup)
then (err_markup ~errs markup s; loop start next) else
let start_data = min + 1 in
(flush start (i - 1);
add_markup_text ~errs loop b s start_data need_escape escape)
| _ ->
err_malformed ~errs s; loop start next
end
| _ -> err_unescaped ~errs '$' s; loop start next
end
| c when markup_need_esc c ->
err_unescaped ~errs c s; flush start (i - 1); loop next next
| c -> loop start next
in
(Buffer.clear b; loop 0 0; Buffer.contents b)
let doc_to_plain ~errs ~subst b s =
markup_to_plain ~errs b (subst_vars ~errs ~subst b s)
let p_indent = 7 (* paragraph indentation. *)
let l_indent = 4 (* label indentation. *)
let pp_plain_blocks ~errs subst ppf ts =
let b = Buffer.create 1024 in
let markup t = doc_to_plain ~errs b ~subst t in
let pp_tokens ppf t = pp_tokens ~spaces:true ppf t in
let rec loop = function
| [] -> ()
| t :: ts ->
begin match t with
| `Noblank -> ()
| `Blocks bs -> loop bs (* not T.R. *)
| `P s -> pf ppf "%a@[%a@]@," pp_indent p_indent pp_tokens (markup s)
| `S s -> pf ppf "@[%a@]" pp_tokens (markup s)
| `Pre s -> pf ppf "%a@[%a@]@," pp_indent p_indent pp_lines (markup s)
| `I (label, s) ->
let label = markup label in
let s = markup s in
pf ppf "@[%a@[%a@]" pp_indent p_indent pp_tokens label;
if s = "" then pf ppf "@]@," else
let ll = String.length label in
begin match ll < l_indent with
| true ->
pf ppf "%a@[%a@]@]" pp_indent (l_indent - ll) pp_tokens s
| false ->
pf ppf "@\n%a@[%a@]@]"
pp_indent (p_indent + l_indent) pp_tokens s
end;
match ts with `I _ :: _ -> pf ppf "@," | _ -> ()
end;
begin match ts with
| `Noblank :: ts -> loop ts
| ts -> Format.pp_print_cut ppf (); loop ts
end
in
loop ts
let pp_plain_page ~errs subst ppf (_, text) =
pf ppf "@[<v>%a@]" (pp_plain_blocks ~errs subst) text
(* Groff output *)
let markup_to_groff ~errs b s =
let max_i = String.length s - 1 in
let flush start stop = match start > max_i with
| true -> ()
| false -> Buffer.add_substring b s start (stop - start + 1)
in
let need_escape = function '.' | '\'' | '-' | '\\' -> true | _ -> false in
let escape b c = Printf.bprintf b "\\N'%d'" (Char.code c) in
let rec end_text start i = Buffer.add_string b "\\fR"; loop start i
and loop start i =
if i > max_i then flush start max_i else
let next = i + 1 in
match s.[i] with
| '\\' ->
flush start (i - 1);
add_markup_esc ~errs loop b s start next need_escape escape
| '$' ->
if next > max_i then err_unescaped ~errs '$' s else
begin match s.[next] with
| '(' ->
let min = next + 2 in
if min > max_i then (err_unclosed ~errs s; loop start next) else
begin match s.[min] with
| ',' ->
let start_data = min + 1 in
flush start (i - 1);
begin match s.[min - 1] with
| 'i' -> Buffer.add_string b "\\fI"
| 'b' -> Buffer.add_string b "\\fB"
| markup -> err_markup ~errs markup s
end;
add_markup_text ~errs end_text b s start_data need_escape escape
| _ -> err_malformed ~errs s; loop start next
end
| _ -> err_unescaped ~errs '$' s; flush start (i - 1); loop next next
end
| c when markup_need_esc c ->
err_unescaped ~errs c s; flush start (i - 1); loop next next
| c when need_escape c ->
flush start (i - 1); escape b c; loop next next
| c -> loop start next
in
(Buffer.clear b; loop 0 0; Buffer.contents b)
let doc_to_groff ~errs ~subst b s =
markup_to_groff ~errs b (subst_vars ~errs ~subst b s)
let pp_groff_blocks ~errs subst ppf text =
let buf = Buffer.create 1024 in
let markup t = doc_to_groff ~errs ~subst buf t in
let pp_tokens ppf t = pp_tokens ~spaces:false ppf t in
let rec pp_block = function
| `Blocks bs -> List.iter pp_block bs (* not T.R. *)
| `P s -> pf ppf "@\n.P@\n%a" pp_tokens (markup s)
| `Pre s -> pf ppf "@\n.P@\n.nf@\n%a@\n.fi" pp_lines (markup s)
| `S s -> pf ppf "@\n.SH %a" pp_tokens (markup s)
| `Noblank -> pf ppf "@\n.sp -1"
| `I (l, s) ->
pf ppf "@\n.TP 4@\n%a@\n%a" pp_tokens (markup l) pp_tokens (markup s)
in
List.iter pp_block text
let pp_groff_page ~errs subst ppf ((n, s, a1, a2, a3), t) =
pf ppf ".\\\" Pipe this output to groff -Tutf8 | less@\n\
.\\\"@\n\
.mso an.tmac@\n\
.TH \"%s\" %d \"%s\" \"%s\" \"%s\"@\n\
.\\\" Disable hyphenation and ragged-right@\n\
.nh@\n\
.ad l\
%a@?"
n s a1 a2 a3 (pp_groff_blocks ~errs subst) t
(* Printing to a pager *)
let pp_to_temp_file pp_v v =
try
let exec = Filename.basename Sys.argv.(0) in
let file, oc = Filename.open_temp_file exec "out" in
let ppf = Format.formatter_of_out_channel oc in
pp_v ppf v; Format.pp_print_flush ppf (); close_out oc;
at_exit (fun () -> try Sys.remove file with Sys_error e -> ());
Some file
with Sys_error _ -> None
let find_cmd cmds =
let test, null = match Sys.os_type with
| "Win32" -> "where", " NUL"
| _ -> "type", "/dev/null"
in
let cmd c = Sys.command (strf "%s %s 1>%s 2>%s" test c null null) = 0 in
try Some (List.find cmd cmds) with Not_found -> None
let pp_to_pager print ppf v =
let pager =
let cmds = ["less"; "more"] in
let cmds = try (Sys.getenv "PAGER") :: cmds with Not_found -> cmds in
let cmds = try (Sys.getenv "MANPAGER") :: cmds with Not_found -> cmds in
find_cmd cmds
in
match pager with
| None -> print `Plain ppf v
| Some pager ->
let cmd = match (find_cmd ["groff"; "nroff"]) with
| None ->
begin match pp_to_temp_file (print `Plain) v with
| None -> None
| Some f -> Some (strf "%s < %s" pager f)
end
| Some c ->
begin match pp_to_temp_file (print `Groff) v with
| None -> None
| Some f ->
(* TODO use -Tutf8, but annoyingly maps U+002D to U+2212. *)
let xroff = if c = "groff" then c ^ " -Tascii -P-c" else c in
Some (strf "%s < %s | %s" xroff f pager)
end
in
match cmd with
| None -> print `Plain ppf v
| Some cmd -> if (Sys.command cmd) <> 0 then print `Plain ppf v
(* Output *)
type format = [ `Auto | `Pager | `Plain | `Groff ]
let rec print
?(errs = Format.err_formatter)
?(subst = fun x -> None) fmt ppf page =
match fmt with
| `Pager -> pp_to_pager (print ~errs ~subst) ppf page
| `Plain -> pp_plain_page ~errs subst ppf page
| `Groff -> pp_groff_page ~errs subst ppf page
| `Auto ->
match try (Some (Sys.getenv "TERM")) with Not_found -> None with
| None | Some "dumb" -> print ~errs ~subst `Plain ppf page
| Some _ -> print ~errs ~subst `Pager ppf page
(*---------------------------------------------------------------------------
Copyright (c) 2011 Daniel C. Bünzli
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
---------------------------------------------------------------------------*)

100
src/cmdliner_manpage.mli Normal file
View File

@ -0,0 +1,100 @@
(*---------------------------------------------------------------------------
Copyright (c) 2011 Daniel C. Bünzli. All rights reserved.
Distributed under the ISC license, see terms at the end of the file.
%%NAME%% %%VERSION%%
---------------------------------------------------------------------------*)
(** Manpages.
See {!Cmdliner.Manpage}. *)
type block =
[ `S of string | `P of string | `Pre of string | `I of string * string
| `Noblank | `Blocks of block list ]
val escape : string -> string
(** [escape s] escapes [s] from the doc language. *)
type title = string * int * string * string * string
type t = title * block list
type xref =
[ `Main | `Cmd of string | `Tool of string | `Page of int * string ]
(** {1 Standard section names} *)
val s_name : string
val s_synopsis : string
val s_description : string
val s_commands : string
val s_arguments : string
val s_options : string
val s_common_options : string
val s_exit_status : string
val s_environment : string
val s_files : string
val s_bugs : string
val s_examples : string
val s_authors : string
val s_see_also : string
(** {1 Section maps}
Used for handling the merging of metadata doc strings. *)
type smap
val smap_of_blocks : block list -> smap
val smap_to_blocks : smap -> block list
val smap_has_section : smap -> sec:string -> bool
val smap_append_block : smap -> sec:string -> block -> smap
(** [smap_append_block smap sec b] appends [b] at the end of section
[sec] creating it at the right place if needed. *)
(** {1 Content boilerplate} *)
val s_exit_status_intro : block
val s_environment_intro : block
(** {1 Output} *)
type format = [ `Auto | `Pager | `Plain | `Groff ]
val print :
?errs:Format.formatter -> ?subst:(string -> string option) -> format ->
Format.formatter -> t -> unit
(** {1 Printers and escapes used by Cmdliner module} *)
val subst_vars :
errs:Format.formatter -> subst:(string -> string option) -> Buffer.t ->
string -> string
(** [subst b ~subst s], using [b], substitutes in [s] variables of the form
"$(doc)" by their [subst] definition. This leaves escapes and markup
directives $(markup,...) intact.
@raise Invalid_argument in case of illegal syntax. *)
val doc_to_plain :
errs:Format.formatter -> subst:(string -> string option) -> Buffer.t ->
string -> string
(** [doc_to_plain b ~subst s] using [b], subsitutes in [s] variables by
their [subst] definition and renders cmdliner directives to plain
text.
@raise Invalid_argument in case of illegal syntax. *)
(*---------------------------------------------------------------------------
Copyright (c) 2011 Daniel C. Bünzli
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
---------------------------------------------------------------------------*)

115
src/cmdliner_msg.ml Normal file
View File

@ -0,0 +1,115 @@
(*---------------------------------------------------------------------------
Copyright (c) 2011 Daniel C. Bünzli. All rights reserved.
Distributed under the ISC license, see terms at the end of the file.
%%NAME%% %%VERSION%%
---------------------------------------------------------------------------*)
let strf = Printf.sprintf
let quote = Cmdliner_base.quote
let pp = Format.fprintf
let pp_text = Cmdliner_base.pp_text
let pp_lines = Cmdliner_base.pp_lines
(* Environment variable errors *)
let err_env_parse env ~err =
let var = Cmdliner_info.env_var env in
strf "environment variable %s: %s" (quote var) err
(* Positional argument errors *)
let err_pos_excess excess =
strf "too many arguments, don't know what to do with %s"
(String.concat ", " (List.map quote excess))
let err_pos_miss a = match Cmdliner_info.arg_docv a with
| "" -> "a required argument is missing"
| v -> strf "required argument %s is missing" v
let err_pos_misses = function
| [] -> assert false
| [a] -> err_pos_miss a
| args ->
let add_arg acc a = match Cmdliner_info.arg_docv a with
| "" -> "ARG" :: acc
| argv -> argv :: acc
in
let rev_args = List.sort Cmdliner_info.rev_arg_pos_cli_order args in
let args = List.fold_left add_arg [] rev_args in
let args = String.concat ", " args in
strf "required arguments %s are missing" args
let err_pos_parse a ~err = match Cmdliner_info.arg_docv a with
| "" -> err
| argv ->
match Cmdliner_info.(pos_len @@ arg_pos a) with
| Some 1 -> strf "%s argument: %s" argv err
| None | Some _ -> strf "%s... arguments: %s" argv err
(* Optional argument errors *)
let err_flag_value flag v =
strf "option %s is a flag, it cannot take the argument %s"
(quote flag) (quote v)
let err_opt_value_missing f = strf "option %s needs an argument" (quote f)
let err_opt_parse f ~err = strf "option %s: %s" (quote f) err
let err_opt_repeated f f' =
if f = f' then strf "option %s cannot be repeated" (quote f) else
strf "options %s and %s cannot be present at the same time"
(quote f) (quote f')
(* Argument errors *)
let err_arg_missing a =
if Cmdliner_info.arg_is_pos a then err_pos_miss a else
strf "required option %s is missing" (Cmdliner_info.arg_opt_name_sample a)
(* Other messages *)
let exec_name ei = Cmdliner_info.(term_name @@ eval_main ei)
let pp_version ppf ei = match Cmdliner_info.(term_version @@ eval_main ei) with
| None -> assert false
| Some v -> pp ppf "@[%a@]@." Cmdliner_base.pp_text v
let pp_try_help ppf ei = match Cmdliner_info.eval_kind ei with
| `Simple | `Multiple_main ->
pp ppf "@[<2>Try `%s --help' for more information.@]" (exec_name ei)
| `Multiple_sub ->
let exec_cmd = Cmdliner_docgen.plain_invocation ei in
pp ppf "@[<2>Try `%s --help' or `%s --help' for more information.@]"
exec_cmd (exec_name ei)
let pp_err ppf ei ~err = pp ppf "%s: @[%a@]@." (exec_name ei) pp_text err
let pp_err_usage ppf ei ~err =
pp ppf "@[<v>%s: @[%a@]@,@[Usage: @[%a@]@]@,%a@]@."
(exec_name ei) pp_text err (Cmdliner_docgen.pp_plain_synopsis ~errs:ppf) ei
pp_try_help ei
let pp_backtrace ppf ei e bt =
let bt = Printexc.raw_backtrace_to_string bt in
let bt =
let len = String.length bt in
if len > 0 then String.sub bt 0 (len - 1) (* remove final '\n' *) else bt
in
pp ppf "%s: @[internal error, uncaught exception:@\n%a@]@."
(exec_name ei) pp_lines (strf "%s\n%s" (Printexc.to_string e) bt)
(*---------------------------------------------------------------------------
Copyright (c) 2011 Daniel C. Bünzli
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
---------------------------------------------------------------------------*)

54
src/cmdliner_msg.mli Normal file
View File

@ -0,0 +1,54 @@
(*---------------------------------------------------------------------------
Copyright (c) 2011 Daniel C. Bünzli. All rights reserved.
Distributed under the ISC license, see terms at the end of the file.
%%NAME%% %%VERSION%%
---------------------------------------------------------------------------*)
(** Messages for the end-user. *)
(** {1:env_err Environment variable errors} *)
val err_env_parse : Cmdliner_info.env -> err:string -> string
(** {1:pos_err Positional argument errors} *)
val err_pos_excess : string list -> string
val err_pos_misses : Cmdliner_info.arg list -> string
val err_pos_parse : Cmdliner_info.arg -> err:string -> string
(** {1:opt_err Optional argument errors} *)
val err_flag_value : string -> string -> string
val err_opt_value_missing : string -> string
val err_opt_parse : string -> err:string -> string
val err_opt_repeated : string -> string -> string
(** {1:arg_err Argument errors} *)
val err_arg_missing : Cmdliner_info.arg -> string
(** {1:msgs Other messages} *)
val pp_version : Format.formatter -> Cmdliner_info.eval -> unit
val pp_try_help : Format.formatter -> Cmdliner_info.eval -> unit
val pp_err : Format.formatter -> Cmdliner_info.eval -> err:string -> unit
val pp_err_usage : Format.formatter -> Cmdliner_info.eval -> err:string -> unit
val pp_backtrace :
Format.formatter ->
Cmdliner_info.eval -> exn -> Printexc.raw_backtrace -> unit
(*---------------------------------------------------------------------------
Copyright (c) 2011 Daniel C. Bünzli
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
---------------------------------------------------------------------------*)

54
src/cmdliner_suggest.ml Normal file
View File

@ -0,0 +1,54 @@
(*---------------------------------------------------------------------------
Copyright (c) 2011 Daniel C. Bünzli. All rights reserved.
Distributed under the ISC license, see terms at the end of the file.
%%NAME%% %%VERSION%%
---------------------------------------------------------------------------*)
let levenshtein_distance s t =
(* As found here http://rosettacode.org/wiki/Levenshtein_distance#OCaml *)
let minimum a b c = min a (min b c) in
let m = String.length s in
let n = String.length t in
(* for all i and j, d.(i).(j) will hold the Levenshtein distance between
the first i characters of s and the first j characters of t *)
let d = Array.make_matrix (m+1) (n+1) 0 in
for i = 0 to m do d.(i).(0) <- i done;
for j = 0 to n do d.(0).(j) <- j done;
for j = 1 to n do
for i = 1 to m do
if s.[i-1] = t.[j-1] then
d.(i).(j) <- d.(i-1).(j-1) (* no operation required *)
else
d.(i).(j) <- minimum
(d.(i-1).(j) + 1) (* a deletion *)
(d.(i).(j-1) + 1) (* an insertion *)
(d.(i-1).(j-1) + 1) (* a substitution *)
done;
done;
d.(m).(n)
let value s candidates =
let add (min, acc) name =
let d = levenshtein_distance s name in
if d = min then min, (name :: acc) else
if d < min then d, [name] else
min, acc
in
let dist, suggs = List.fold_left add (max_int, []) candidates in
if dist < 3 (* suggest only if not too far *) then suggs else []
(*---------------------------------------------------------------------------
Copyright (c) 2011 Daniel C. Bünzli
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
---------------------------------------------------------------------------*)

25
src/cmdliner_suggest.mli Normal file
View File

@ -0,0 +1,25 @@
(*---------------------------------------------------------------------------
Copyright (c) 2011 Daniel C. Bünzli. All rights reserved.
Distributed under the ISC license, see terms at the end of the file.
%%NAME%% %%VERSION%%
---------------------------------------------------------------------------*)
val value : string -> string list -> string list
(** [value near candidates] suggests values from [candidates]
not to far from near. *)
(*---------------------------------------------------------------------------
Copyright (c) 2011 Daniel C. Bünzli
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
---------------------------------------------------------------------------*)

43
src/cmdliner_term.ml Normal file
View File

@ -0,0 +1,43 @@
(*---------------------------------------------------------------------------
Copyright (c) 2011 Daniel C. Bünzli. All rights reserved.
Distributed under the ISC license, see terms at the end of the file.
%%NAME%% %%VERSION%%
---------------------------------------------------------------------------*)
open Result
type term_escape =
[ `Error of bool * string
| `Help of Cmdliner_manpage.format * string option ]
type 'a parser =
Cmdliner_info.eval -> Cmdliner_cline.t ->
('a, [ `Parse of string | term_escape ]) result
type 'a t = Cmdliner_info.args * 'a parser
let const v = Cmdliner_info.Args.empty, (fun _ _ -> Ok v)
let app (args_f, f) (args_v, v) =
Cmdliner_info.Args.union args_f args_v,
fun ei cl -> match (f ei cl) with
| Error _ as e -> e
| Ok f ->
match v ei cl with
| Error _ as e -> e
| Ok v -> Ok (f v)
(*---------------------------------------------------------------------------
Copyright (c) 2011 Daniel C. Bünzli
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
---------------------------------------------------------------------------*)

42
src/cmdliner_term.mli Normal file
View File

@ -0,0 +1,42 @@
(*---------------------------------------------------------------------------
Copyright (c) 2011 Daniel C. Bünzli. All rights reserved.
Distributed under the ISC license, see terms at the end of the file.
%%NAME%% %%VERSION%%
---------------------------------------------------------------------------*)
open Result
(** Terms *)
type term_escape =
[ `Error of bool * string
| `Help of Cmdliner_manpage.format * string option ]
type 'a parser =
Cmdliner_info.eval -> Cmdliner_cline.t ->
('a, [ `Parse of string | term_escape ]) result
(** Type type for command line parser. given static information about
the command line and a command line to parse returns an OCaml value. *)
type 'a t = Cmdliner_info.args * 'a parser
(** The type for terms. The list of arguments it can parse and the parsing
function that does so. *)
val const : 'a -> 'a t
val app : ('a -> 'b) t -> 'a t -> 'b t
(*---------------------------------------------------------------------------
Copyright (c) 2011 Daniel C. Bünzli
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
---------------------------------------------------------------------------*)

97
src/cmdliner_trie.ml Normal file
View File

@ -0,0 +1,97 @@
(*---------------------------------------------------------------------------
Copyright (c) 2011 Daniel C. Bünzli. All rights reserved.
Distributed under the ISC license, see terms at the end of the file.
%%NAME%% %%VERSION%%
---------------------------------------------------------------------------*)
module Cmap = Map.Make (Char) (* character maps. *)
type 'a value = (* type for holding a bound value. *)
| Pre of 'a (* value is bound by the prefix of a key. *)
| Key of 'a (* value is bound by an entire key. *)
| Amb (* no value bound because of ambiguous prefix. *)
| Nil (* not bound (only for the empty trie). *)
type 'a t = { v : 'a value; succs : 'a t Cmap.t }
let empty = { v = Nil; succs = Cmap.empty }
let is_empty t = t = empty
(* N.B. If we replace a non-ambiguous key, it becomes ambiguous but it's
not important for our use. Also the following is not tail recursive but
the stack is bounded by key length. *)
let add t k d =
let rec loop t k len i d pre_d = match i = len with
| true ->
let t' = { v = Key d; succs = t.succs } in
begin match t.v with
| Key old -> `Replaced (old, t')
| _ -> `New t'
end
| false ->
let v = match t.v with
| Amb | Pre _ -> Amb | Key _ as v -> v | Nil -> pre_d
in
let t' = try Cmap.find k.[i] t.succs with Not_found -> empty in
match loop t' k len (i + 1) d pre_d with
| `New n -> `New { v; succs = Cmap.add k.[i] n t.succs }
| `Replaced (o, n) ->
`Replaced (o, { v; succs = Cmap.add k.[i] n t.succs })
in
loop t k (String.length k) 0 d (Pre d (* allocate less *))
let find_node t k =
let rec aux t k len i =
if i = len then t else
aux (Cmap.find k.[i] t.succs) k len (i + 1)
in
aux t k (String.length k) 0
let find t k =
try match (find_node t k).v with
| Key v | Pre v -> `Ok v | Amb -> `Ambiguous | Nil -> `Not_found
with Not_found -> `Not_found
let ambiguities t p = (* ambiguities of [p] in [t]. *)
try
let t = find_node t p in
match t.v with
| Key _ | Pre _ | Nil -> []
| Amb ->
let add_char s c = s ^ (String.make 1 c) in
let rem_char s = String.sub s 0 ((String.length s) - 1) in
let to_list m = Cmap.fold (fun k t acc -> (k,t) :: acc) m [] in
let rec aux acc p = function
| ((c, t) :: succs) :: rest ->
let p' = add_char p c in
let acc' = match t.v with
| Pre _ | Amb -> acc
| Key _ -> (p' :: acc)
| Nil -> assert false
in
aux acc' p' ((to_list t.succs) :: succs :: rest)
| [] :: [] -> acc
| [] :: rest -> aux acc (rem_char p) rest
| [] -> assert false
in
aux [] p (to_list t.succs :: [])
with Not_found -> []
let of_list l =
let add t (s, v) = match add t s v with `New t -> t | `Replaced (_, t) -> t in
List.fold_left add empty l
(*---------------------------------------------------------------------------
Copyright (c) 2011 Daniel C. Bünzli
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
---------------------------------------------------------------------------*)

35
src/cmdliner_trie.mli Normal file
View File

@ -0,0 +1,35 @@
(*---------------------------------------------------------------------------
Copyright (c) 2011 Daniel C. Bünzli. All rights reserved.
Distributed under the ISC license, see terms at the end of the file.
%%NAME%% %%VERSION%%
---------------------------------------------------------------------------*)
(** Tries.
This implementation also maps any non ambiguous prefix of a
key to its value. *)
type 'a t
val empty : 'a t
val is_empty : 'a t -> bool
val add : 'a t -> string -> 'a -> [ `New of 'a t | `Replaced of 'a * 'a t ]
val find : 'a t -> string -> [ `Ok of 'a | `Ambiguous | `Not_found ]
val ambiguities : 'a t -> string -> string list
val of_list : (string * 'a) list -> 'a t
(*---------------------------------------------------------------------------
Copyright (c) 2011 Daniel C. Bünzli
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
---------------------------------------------------------------------------*)

View File

@ -1,31 +1,7 @@
open Import
open Future
let common_args =
[ "-j", Arg.Set_int Clflags.concurrency, "JOBS concurrency"
; "-drules", Arg.Set Clflags.debug_rules, " show rules"
; "-ddep-path", Arg.Set Clflags.debug_dep_path, " show depency path of errors"
; "-dfindlib", Arg.Set Clflags.debug_findlib, " debug findlib stuff"
]
let parse_args argv msg l =
let anons = ref [] in
try
Arg.parse_argv argv (Arg.align l) (fun x -> anons := x :: !anons) msg;
List.rev !anons
with
| Arg.Bad msg -> Printf.eprintf "%s" msg; exit 2
| Arg.Help msg -> Printf.printf "%s" msg; exit 0
let parse_args1 argv msg l =
match parse_args argv msg l with
| [x] -> x
| _ ->
Printf.eprintf "no enough arguments\nUsage: %s\n" msg;
exit 2
let internal argv =
match Array.to_list argv with
let internal = function
| [_; "findlib-packages"] ->
Future.Scheduler.go
(Lazy.force Context.default >>= fun ctx ->
@ -68,11 +44,7 @@ let external_lib_deps ~packages =
let internals = Jbuild_types.Stanza.lib_names stanzas in
String_map.filter deps ~f:(fun name _ -> not (String_set.mem name internals))))
let external_lib_deps_cmd argv =
let packages =
parse_args argv "jbuild external-lib-deps PACKAGES"
common_args
in
let external_lib_deps_cmd packages =
let deps =
Path.Map.fold (external_lib_deps ~packages) ~init:String_map.empty
~f:(fun ~key:_ ~data:deps acc -> Build.merge_lib_deps deps acc)
@ -82,33 +54,101 @@ let external_lib_deps_cmd argv =
| Required -> Printf.printf "%s\n" n
| Optional -> Printf.printf "%s (optional)\n" n)
let main () =
let argv = Sys.argv in
let argc = Array.length argv in
let compact () =
Array.append
[|sprintf "%s %s" argv.(0) argv.(1)|]
(Array.sub argv ~pos:2 ~len:(argc - 2))
in
if argc >= 2 then
match argv.(1) with
| "internal" -> internal (compact ())
| "build-package" ->
let pkg =
parse_args1 (compact ()) "jbuild build-package PACKAGE"
common_args
in
Future.Scheduler.go
(setup () >>= fun (bs, _, _) ->
Build_system.do_build_exn bs [Path.(relative root) (pkg ^ ".install")])
| "external-lib-deps" ->
external_lib_deps_cmd (compact ())
| _ ->
let targets = parse_args argv "jbuild TARGETS" common_args in
let build_package pkg =
Future.Scheduler.go
(setup () >>= fun (bs, _, _) ->
Build_system.do_build_exn bs [Path.(relative root) (pkg ^ ".install")])
module Cli = struct
open Cmdliner
let internal =
let doc = "internal" in
let name_ = Arg.info [] in
( Term.(const internal $ Arg.(non_empty & pos_all string [] name_))
, Term.info "internal" ~doc)
type common =
{ concurrency: int
; debug_rules: bool
; debug_dep_path: bool
; debug_findlib: bool
}
let set_common c =
Clflags.concurrency := c.concurrency;
Clflags.debug_rules := c.debug_rules;
Clflags.debug_dep_path := c.debug_dep_path;
Clflags.debug_findlib := c.debug_findlib
let copts_sect = "COMMON OPTIONS"
let help_secs =
[ `S copts_sect
; `P "These options are common to all commands."
; `S "MORE HELP"
; `P "Use `$(mname) $(i,COMMAND) --help' for help on a single command."
;`Noblank
; `S "BUGS"
; `P "Check bug reports at https://github.com/janestreet/jbuilder/issues"
]
let common =
let make concurrency debug_rules debug_dep_path debug_findlib =
{ concurrency ; debug_rules ; debug_dep_path ; debug_findlib } in
let docs = copts_sect in
let concurrency =
Arg.(value & opt int !Clflags.concurrency & info ["j"] ~docs) in
let drules = Arg.(value & flag & info ["drules"] ~docs) in
let ddep_path = Arg.(value & flag & info ["ddep-path"] ~docs) in
let dfindlib = Arg.(value & flag & info ["dfindlib"] ~docs) in
Term.(const make $ concurrency $ drules $ ddep_path $ dfindlib)
let build_package =
let doc = "build-package" in
let name_ = Arg.info [] in
let go common pkg =
set_common common;
build_package pkg in
( Term.(const go
$ common
$ Arg.(required & pos 0 (some string) None name_))
, Term.info "build-package" ~doc ~man:help_secs)
let external_lib_deps =
let doc = "external-lib-deps" in
let name_ = Arg.info [] in
let go common packages =
set_common common;
external_lib_deps_cmd packages in
( Term.(const go
$ common
$ Arg.(non_empty & pos_all string [] name_))
, Term.info "external-lib-deps" ~doc ~man:help_secs)
let build_targets =
let doc = "build" in
let name_ = Arg.info [] in
let go common targets =
set_common common;
Future.Scheduler.go
(setup () >>= fun (bs, _, ctx) ->
let targets = List.map targets ~f:(Path.relative ctx.build_dir) in
Build_system.do_build_exn bs targets)
Build_system.do_build_exn bs targets) in
( Term.(const go
$ common
$ Arg.(non_empty & pos_all string [] name_))
, Term.info "build" ~doc ~man:help_secs)
let all =
[ internal ; build_package ; external_lib_deps ; build_targets ]
let main () =
match Term.eval_choice build_targets all with
| `Error _ -> exit 1
| _ -> exit 0
end
let main = Cli.main
let report_error ?(map_fname=fun x->x) ppf exn ~backtrace =
match exn with

2
src/result.ml Normal file
View File

@ -0,0 +1,2 @@
type nonrec ('ok, 'err) result = ('ok, 'err) result