mirror of
				https://github.com/KevinMidboe/linguist.git
				synced 2025-10-29 17:50:22 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			1345 lines
		
	
	
		
			50 KiB
		
	
	
	
		
			OCaml
		
	
	
	
	
	
			
		
		
	
	
			1345 lines
		
	
	
		
			50 KiB
		
	
	
	
		
			OCaml
		
	
	
	
	
	
| (*---------------------------------------------------------------------------
 | |
|    Copyright (c) 2011 Daniel C. Bünzli. All rights reserved.
 | |
|    Distributed under a BSD3 license, see license at the end of the file.
 | |
|    %%NAME%% release %%VERSION%%
 | |
|   ---------------------------------------------------------------------------*)
 | |
| 
 | |
| (* Invalid_arg strings *)
 | |
| 
 | |
| let err_argv = "argv array must have at least one element"
 | |
| let err_not_opt = "Option argument without name"
 | |
| let err_not_pos = "Positional argument with a name"
 | |
| let err_help s = "term error, help requested for unknown command " ^ s
 | |
| let err_empty_list = "empty list"
 | |
| 
 | |
| (* A few useful definitions. *)
 | |
| 
 | |
| let rev_compare n n' = compare n' n
 | |
| let str = Printf.sprintf
 | |
| let pr = Format.fprintf
 | |
| let pr_str = Format.pp_print_string
 | |
| let pr_char = Format.pp_print_char
 | |
| let str_of_pp pp v = pp Format.str_formatter v; Format.flush_str_formatter ()
 | |
| let quote s = str "`%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] -> str "either %s or %s" (quote a) (quote b)
 | |
|   | alts ->
 | |
|       let rev_alts = List.rev alts in
 | |
|       str "one of %s or %s"
 | |
|         (String.concat ", " (List.rev_map quote (List.tl rev_alts)))
 | |
|         (quote (List.hd rev_alts))
 | |
| 
 | |
| let pr_white_str spaces ppf s =  (* spaces and new lines with Format's funs *)
 | |
|   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 pr_text = pr_white_str true
 | |
| let pr_lines = pr_white_str false
 | |
| let pr_to_temp_file pr 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
 | |
|   pr 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
 | |
| 
 | |
| (* Levenshtein distance, for making spelling suggestions in case of error. *)
 | |
| 
 | |
| 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 suggest 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 []
 | |
| 
 | |
| (* Tries. This implementation also maps any non ambiguous prefix of a
 | |
|    key to its value. *)
 | |
| 
 | |
| module Trie : sig
 | |
|   type 'a t
 | |
|   val empty : 'a t
 | |
|   val is_empty : 'a t -> bool
 | |
|   val add : 'a t -> string -> '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
 | |
| end = struct
 | |
|   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 aux t k len i d pre_d =
 | |
|       if i = len then { v = Key d; succs = t.succs } else
 | |
|       let v = match t.v with
 | |
|       | Amb | Pre _ -> Amb | Key _ as v -> v | Nil -> pre_d
 | |
|       in
 | |
|       let succs =
 | |
|         let t' = try Cmap.find k.[i] t.succs with Not_found -> empty in
 | |
|         Cmap.add k.[i] (aux t' k len (i + 1) d pre_d) t.succs
 | |
|       in
 | |
|       { v; succs }
 | |
|     in
 | |
|     aux 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 = List.fold_left (fun t (s, v) -> add t s v) empty l
 | |
| end
 | |
| 
 | |
| (* The following types keep untyped information about arguments and
 | |
|    terms. This data is used to parse the command line, report errors
 | |
|    and format man page information. *)
 | |
| 
 | |
| type absence =        (* what happens if the argument is absent from the cl. *)
 | |
|   | Error                                           (* an error is reported. *)
 | |
|   | Val of string Lazy.t         (* if <> "", takes the given default value. *)
 | |
| 
 | |
| type opt_kind =                              (* kinds of optional arguments. *)
 | |
|   | Flag                                      (* just a flag, without value. *)
 | |
|   | Opt                                                (* value is required. *)
 | |
|   | Opt_vopt of string     (* option value is optional, takes given default. *)
 | |
| 
 | |
| type pos_kind =                            (* kinds of positional arguments. *)
 | |
|   | All                                         (* all positional arguments. *)
 | |
|   | Nth of bool * int                                  (* specific position. *)
 | |
|   | Left of bool * int                (* all args on the left of a position. *)
 | |
|   | Right of bool * int              (* all args on the right of a position. *)
 | |
| 
 | |
| type arg_info =                (* information about a command line argument. *)
 | |
|     { id : int;                               (* unique id for the argument. *)
 | |
|       absent : absence;                              (* behaviour if absent. *)
 | |
|       doc : string;                                                 (* help. *)
 | |
|       docv : string;              (* variable name for the argument in help. *)
 | |
|       docs : string;                  (* title of help section where listed. *)
 | |
|       p_kind : pos_kind;                             (* positional arg kind. *)
 | |
|       o_kind : opt_kind;                               (* optional arg kind. *)
 | |
|       o_names : string list;                        (* names (for opt args). *)
 | |
|       o_all : bool; }                          (* repeatable (for opt args). *)
 | |
| 
 | |
| let arg_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
 | |
| 
 | |
| let is_opt a = a.o_names <> []
 | |
| let is_pos a = a.o_names = []
 | |
| 
 | |
| module Amap = Map.Make                                     (* arg info maps. *)
 | |
|     (struct type t = arg_info let compare a a' = compare a.id a'.id end)
 | |
| 
 | |
| 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 cmdline = arg Amap.t      (* command line, maps arg_infos to arg value. *)
 | |
| 
 | |
| type man_block = [                                 (* block of manpage text. *)
 | |
|   | `S of string | `P of string | `I of string * string | `Noblank ]
 | |
| 
 | |
| type term_info =
 | |
|   { name : string;                                    (* name of the term. *)
 | |
|     version : string option;                   (* version (for --version). *)
 | |
|     tdoc : string;                        (* one line description of term. *)
 | |
|     tdocs : string;       (* title of man section where listed (commands). *)
 | |
|     sdocs : string;    (* standard options, title of section where listed. *)
 | |
|     man : man_block list; }                              (* man page text. *)
 | |
| 
 | |
| type eval_info =                (* information about the evaluation context. *)
 | |
|   { term : term_info * arg_info list;             (* term being evaluated. *)
 | |
|     main : term_info * arg_info list;                        (* main term. *)
 | |
|     choices : (term_info * arg_info list) list}       (* all term choices. *)
 | |
| 
 | |
| let eval_kind ei =                       (* evaluation with multiple terms ? *)
 | |
|   if ei.choices = [] then `Simple else
 | |
|   if (fst ei.term) == (fst ei.main) then `M_main else `M_choice
 | |
| 
 | |
| module Manpage = struct
 | |
|   type title = string * int * string * string * string
 | |
|   type block = man_block
 | |
|   type t = title * block list
 | |
| 
 | |
|   let p_indent = 7                                  (* paragraph indentation. *)
 | |
|   let l_indent = 4                                      (* label indentation. *)
 | |
|   let escape subst esc buf s =
 | |
|     let subst s =
 | |
|       let len = String.length s in
 | |
|       if not (len > 1 && s.[1] = ',') then (subst s) else
 | |
|       if len = 2 then "" else
 | |
|       esc s.[0] (String.sub s 2 (len - 2))
 | |
|     in
 | |
|     Buffer.clear buf; Buffer.add_substitute buf subst s;
 | |
|     let s = Buffer.contents buf in (* twice for $(i,$(mname)). *)
 | |
|     Buffer.clear buf; Buffer.add_substitute buf subst s;
 | |
|     Buffer.contents buf
 | |
| 
 | |
|   let pr_tokens ?(groff = false) ppf s =
 | |
|     let is_space = function ' ' | '\n' | '\r' | '\t' -> true | _ -> false in
 | |
|     let len = String.length s in
 | |
|     let i = ref 0 in
 | |
|     try while (true) do
 | |
|         while (!i < len && is_space s.[!i]) do incr i done;
 | |
|         let start = !i in
 | |
|         if start = len then raise Exit;
 | |
|         while (!i < len && not (is_space s.[!i]) && not (s.[!i] = '-')) do
 | |
|           incr i
 | |
|         done;
 | |
|         pr_str ppf (String.sub s start (!i - start));
 | |
|         if !i = len then raise Exit;
 | |
|         if s.[!i] = '-' then
 | |
|           (incr i; if groff then pr_str ppf "\\-" else pr_char ppf '-');
 | |
|         if (!i < len && is_space s.[!i]) then
 | |
|           (if groff then pr_char ppf ' ' else Format.pp_print_space ppf ())
 | |
|       done with Exit -> ()
 | |
| 
 | |
|   (* Plain text output *)
 | |
| 
 | |
|   let plain_esc c s = match c with 'g' -> "" (* groff specific *) | _ ->  s
 | |
|   let pr_indent ppf c = for i = 1 to c do pr_char ppf ' ' done
 | |
|   let pr_plain_blocks subst ppf ts =
 | |
|     let buf = Buffer.create 1024 in
 | |
|     let escape t = escape subst plain_esc buf t in
 | |
|     let pr_tokens ppf t = pr_tokens ppf (escape t) in
 | |
|     let rec aux = function
 | |
|     | [] -> ()
 | |
|     | t :: ts ->
 | |
|         begin match t with
 | |
|         | `Noblank -> ()
 | |
|         | `P s -> pr ppf "%a@[%a@]@," pr_indent p_indent pr_tokens s
 | |
|         | `S s -> pr ppf "@[%a@]" pr_tokens s
 | |
|         | `I (label, s) ->
 | |
|             let label = escape label in
 | |
|             let ll = String.length label in
 | |
|             pr ppf "@[%a@[%a@]" pr_indent p_indent pr_tokens label;
 | |
|             if s = "" then () else
 | |
|             if ll < l_indent then
 | |
|               pr ppf "%a@[%a@]@]@," pr_indent (l_indent - ll) pr_tokens s
 | |
|             else
 | |
|             pr ppf "@\n%a@[%a@]@]@,"
 | |
|               pr_indent (p_indent + l_indent) pr_tokens s
 | |
|         end;
 | |
|         begin match ts with
 | |
|         | `Noblank :: ts -> aux ts
 | |
|         | ts -> Format.pp_print_cut ppf (); aux ts
 | |
|         end
 | |
|     in
 | |
|     aux ts
 | |
| 
 | |
|   let pr_plain_page subst ppf (_, text) =
 | |
|     pr ppf "@[<v>%a@]" (pr_plain_blocks subst) text
 | |
| 
 | |
|   (* Groff output *)
 | |
| 
 | |
|   let groff_esc c s = match c with
 | |
|   | 'i' -> (str "\\fI%s\\fR" s)
 | |
|   | 'b' -> (str "\\fB%s\\fR" s)
 | |
|   | 'p' -> "" (* plain text specific *)
 | |
|   | _ -> s
 | |
| 
 | |
|   let pr_groff_blocks subst ppf text =
 | |
|     let buf = Buffer.create 1024 in
 | |
|     let escape t = escape subst groff_esc buf t in
 | |
|     let pr_tokens ppf t = pr_tokens ~groff:true ppf (escape t) in
 | |
|     let pr_block = function
 | |
|     | `P s -> pr ppf "@\n.P@\n%a" pr_tokens s
 | |
|     | `S s -> pr ppf "@\n.SH %a" pr_tokens s
 | |
|     | `Noblank -> pr ppf "@\n.sp -1"
 | |
|     | `I (l, s) -> pr ppf "@\n.TP 4@\n%a@\n%a" pr_tokens l pr_tokens s
 | |
|     in
 | |
|     List.iter pr_block text
 | |
| 
 | |
|   let pr_groff_page subst ppf ((n, s, a1, a2, a3), t) =
 | |
|     pr ppf ".\\\" Pipe this output to groff -man -Tutf8 | less@\n\
 | |
|             .\\\"@\n\
 | |
|             .TH \"%s\" %d \"%s\" \"%s\" \"%s\"@\n\
 | |
|             .\\\" Disable hyphenantion and ragged-right@\n\
 | |
|             .nh@\n\
 | |
|       .ad l\
 | |
|       %a@?"
 | |
|       n s a1 a2 a3 (pr_groff_blocks subst) t
 | |
| 
 | |
|   (* Printing to a pager *)
 | |
| 
 | |
|   let find_cmd cmds =
 | |
|     let test, null = match Sys.os_type with
 | |
|     | "Win32" -> "where", " NUL"
 | |
|     | _ -> "type", "/dev/null"
 | |
|     in
 | |
|     let cmd c = Sys.command (str "%s %s 1>%s 2>%s" test c null null) = 0 in
 | |
|     try Some (List.find cmd cmds) with Not_found -> None
 | |
| 
 | |
|   let pr_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 pr_to_temp_file (print `Plain) v with
 | |
|             | None -> None
 | |
|             | Some f -> Some (str "%s < %s" pager f)
 | |
|             end
 | |
|         | Some c ->
 | |
|             begin match pr_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 (str "%s -man < %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
 | |
| 
 | |
|   let rec print ?(subst = fun x -> x) fmt ppf page = match fmt with
 | |
|   | `Pager -> pr_to_pager (print ~subst) ppf page
 | |
|   | `Plain -> pr_plain_page subst ppf page
 | |
|   | `Groff -> pr_groff_page subst ppf page
 | |
| end
 | |
| 
 | |
| module Help = struct
 | |
|   let invocation ?(sep = ' ') ei = match eval_kind ei with
 | |
|   | `Simple | `M_main -> (fst ei.main).name
 | |
|   | `M_choice -> str "%s%c%s" (fst ei.main).name sep (fst ei.term).name
 | |
| 
 | |
|   let title ei =
 | |
|     let prog = String.capitalize (fst ei.main).name in
 | |
|     let name = String.uppercase (invocation ~sep:'-' ei) in
 | |
|     let left_footer = prog ^ match (fst ei.main).version with
 | |
|       | None -> "" | Some v -> str " %s" v
 | |
|     in
 | |
|     let center_header = str "%s Manual" prog in
 | |
|     name, 1, "", left_footer, center_header
 | |
| 
 | |
|   let name_section ei =
 | |
|     let tdoc d = if d = "" then "" else (str " - %s" d) in
 | |
|     [`S "NAME"; `P (str "%s%s" (invocation ~sep:'-' ei)
 | |
|                       (tdoc (fst ei.term).tdoc)); ]
 | |
| 
 | |
|   let synopsis ei = match eval_kind ei with
 | |
|   | `M_main -> str "$(b,%s) $(i,COMMAND) ..." (invocation ei)
 | |
|   | `Simple | `M_choice ->
 | |
|       let rev_cmp (p, _) (p', _) = match p', p with        (* best effort. *)
 | |
|       | p, All -> -1 | All, p -> 1
 | |
|       | Left _, Right _ -> -1 | Right _, Left _ -> 1
 | |
|       | Left (false, k), Nth (false, k')
 | |
|       | Nth (false, k), Nth (false, k')
 | |
|       | Nth (false, k), Right (false, k') -> if k <= k' then -1 else 1
 | |
|       | Nth (false, k), Left (false, k')
 | |
|       | Right (false, k), Nth (false, k') -> if k >= k' then 1 else -1
 | |
|       | Left (true, k), Nth (true, k')
 | |
|       | Nth (true, k), Nth (true, k')
 | |
|       | Nth (true, k), Right (true, k') -> if k >= k' then -1 else 1
 | |
|       | Nth (true, k), Left (true, k')
 | |
|       | Right (true, k), Nth (true, k') -> if k <= k' then 1 else -1
 | |
|       | p, p' -> compare p p'
 | |
|       in
 | |
|       let rec format_pos acc = function
 | |
|       | a :: al ->
 | |
|           if is_opt a then format_pos acc al else
 | |
|           let v = if a.docv = "" then "$(i,ARG)" else str "$(i,%s)" a.docv in
 | |
|           let v = if a.absent = Error then str "%s" v else str "[%s]" v in
 | |
|           let v = v ^ match a.p_kind with Nth _ -> "" | _ -> "..." in
 | |
|           format_pos ((a.p_kind, v) :: acc) al
 | |
|       | [] -> acc
 | |
|       in
 | |
|       let args = List.sort rev_cmp (format_pos [] (snd ei.term)) in
 | |
|       let args = String.concat " " (List.rev_map snd args) in
 | |
|       str "$(b,%s) [$(i,OPTION)]... %s" (invocation ei) args
 | |
| 
 | |
|   let get_synopsis_section ei =
 | |
|     let rec extract_synopsis syn = function
 | |
|     | `S _ :: _ as man -> List.rev syn, man
 | |
|     |  block :: rest -> extract_synopsis (block :: syn) rest
 | |
|     | [] -> List.rev syn, []
 | |
|     in
 | |
|     match (fst ei.term).man with
 | |
|     | `S "SYNOPSIS" as s :: rest -> extract_synopsis [s] rest (* user-defined *)
 | |
|     | man -> [ `S "SYNOPSIS"; `P (synopsis ei); ], man           (* automatic *)
 | |
| 
 | |
|   let make_arg_label a =
 | |
|     if is_pos a then str "$(i,%s)" a.docv else
 | |
|     let fmt_name var = match a.o_kind with
 | |
|     | Flag -> fun n -> str "$(b,%s)" n
 | |
|     | Opt ->
 | |
|         fun n ->
 | |
|           if String.length n > 2 then str "$(b,%s)=$(i,%s)" n var else
 | |
|           str "$(b,%s) $(i,%s)" n var
 | |
|     | Opt_vopt _ ->
 | |
|         fun n ->
 | |
|           if String.length n > 2 then str "$(b,%s)[=$(i,%s)]" n var else
 | |
|           str "$(b,%s) [$(i,%s)]" n var
 | |
|     in
 | |
|     let var = if a.docv = "" then "VAL" else a.docv in
 | |
|     let names = List.sort compare a.o_names in
 | |
|     let s = String.concat ", " (List.rev_map (fmt_name var) names) in
 | |
|     s
 | |
| 
 | |
|   let make_arg_items ei =
 | |
|     let buf = Buffer.create 200 in
 | |
|     let subst_docv docv d =
 | |
|       let subst = function "docv" -> str "$(i,%s)" docv | s -> str "$(%s)" s in
 | |
|       Buffer.clear buf; Buffer.add_substitute buf subst d; Buffer.contents buf
 | |
|     in
 | |
|     let rev_cmp a' a =
 | |
|       let c = compare a.docs a'.docs in
 | |
|       if c <> 0 then c else
 | |
|       match is_opt a, is_opt a' with
 | |
|       | true, true ->
 | |
|           let key names =
 | |
|             let k = String.lowercase (List.hd (List.sort rev_compare names)) in
 | |
|             if k.[1] = '-' then String.sub k 1 (String.length k - 1) else k
 | |
|           in
 | |
|           compare (key a.o_names) (key a'.o_names)
 | |
|       | false, false ->
 | |
|           compare (String.lowercase a.docv) (String.lowercase a'.docv)
 | |
|       | true, false -> -1
 | |
|       | false, true -> 1
 | |
|     in
 | |
|     let format a =
 | |
|       let absent = match a.absent with
 | |
|       | Error -> ""
 | |
|       | Val v -> match Lazy.force v with "" -> "" | v -> str "absent=%s" v
 | |
|       in
 | |
|       let optvopt = match a.o_kind with
 | |
|       | Opt_vopt v -> str "default=%s" v
 | |
|       | _ -> ""
 | |
|       in
 | |
|       let argvdoc = match absent, optvopt with
 | |
|       | "", "" -> ""
 | |
|       | s, "" | "", s -> str " (%s)" s
 | |
|       | s, s' -> str " (%s, %s)" s s'
 | |
|       in
 | |
|       (a.docs, `I (make_arg_label a ^ argvdoc, (subst_docv a.docv a.doc)))
 | |
|     in
 | |
|     let is_arg_item a = not (is_pos a && (a.docv = "" || a.doc = "")) in
 | |
|     let l = List.sort rev_cmp (List.filter is_arg_item (snd ei.term)) in
 | |
|     List.rev_map format l
 | |
| 
 | |
|   let make_cmd_items ei = match eval_kind ei with
 | |
|   | `Simple | `M_choice -> []
 | |
|   | `M_main ->
 | |
|       let add_cmd acc (ti, _) =
 | |
|         (ti.tdocs, `I ((str "$(b,%s)" ti.name), ti.tdoc)) :: acc
 | |
|       in
 | |
|       List.sort rev_compare (List.fold_left add_cmd [] ei.choices)
 | |
| 
 | |
|   let text ei =                  (* man that code is particulary unreadable. *)
 | |
|     let rec merge_items acc to_insert mark il = function
 | |
|     | `S s as sec :: ts ->
 | |
|         let acc = List.rev_append to_insert acc in
 | |
|         let acc = if mark then sec :: `Orphan_mark :: acc else sec :: acc in
 | |
|         let to_insert, il = List.partition (fun (n, _) -> n = s) il in
 | |
|         let to_insert = List.rev_map (fun (_, i) -> i) to_insert in
 | |
|         let to_insert = (to_insert :> [ `Orphan_mark | Manpage.block] list) in
 | |
|         merge_items acc to_insert (s = "DESCRIPTION") il ts
 | |
|     | t :: ts ->
 | |
|         let t = (t :> [`Orphan_mark | Manpage.block]) in
 | |
|         merge_items (t :: acc) to_insert mark il ts
 | |
|     | [] ->
 | |
|         let acc = List.rev_append to_insert acc in
 | |
|         (if mark then `Orphan_mark :: acc else acc), il
 | |
|     in
 | |
|     let rec merge_orphans acc orphans = function
 | |
|     | `Orphan_mark :: ts ->
 | |
|         let rec merge acc s = function
 | |
|         | [] -> (`S s) :: acc
 | |
|         | (s', i) :: ss ->
 | |
|             let i = (i :> Manpage.block) in
 | |
|             if s = s' then merge (i :: acc) s ss else
 | |
|             merge (i :: (`S s) :: acc) s' ss
 | |
|         in
 | |
|         let acc = match orphans with
 | |
|         | [] -> acc | (s, _) :: _ -> merge acc s orphans
 | |
|         in
 | |
|         merge_orphans acc [] ts
 | |
|     | (#Manpage.block as e) :: ts -> merge_orphans (e :: acc) orphans ts
 | |
|     | [] -> acc
 | |
|     in
 | |
|     let cmds = make_cmd_items ei in
 | |
|     let args = make_arg_items ei in
 | |
|     let cmp (s, _) (s', _) = compare s s' in
 | |
|     let items = List.rev (List.stable_sort cmp (List.rev_append cmds args)) in
 | |
|     let synopsis, man = get_synopsis_section ei in
 | |
|     let rev_text, orphans =
 | |
|       merge_items [`Orphan_mark] [] false items man
 | |
|     in
 | |
|     synopsis @ merge_orphans [] orphans rev_text
 | |
| 
 | |
|   let ei_subst ei = function
 | |
|   | "tname" -> (fst ei.term).name
 | |
|   | "mname" -> (fst ei.main).name
 | |
|   | s -> s
 | |
| 
 | |
|   let man ei =
 | |
|     title ei, (name_section ei) @ (text ei)
 | |
| 
 | |
|   let print fmt ppf ei = Manpage.print ~subst:(ei_subst ei) fmt ppf (man ei)
 | |
|   let pr_synopsis ppf ei =
 | |
|     pr ppf "@[%s@]"
 | |
|       (Manpage.escape (ei_subst ei)
 | |
|          Manpage.plain_esc (Buffer.create 100) (synopsis ei))
 | |
| 
 | |
|   let pr_version ppf ei = match (fst ei.main).version with
 | |
|   | None -> assert false
 | |
|   | Some v -> pr ppf "@[%a@]@." pr_text v
 | |
| end
 | |
| 
 | |
| (* Errors for the command line user *)
 | |
| 
 | |
| module Err = struct
 | |
|   let invalid kind s exp = str "invalid %s %s, %s" kind (quote s) exp
 | |
|   let invalid_val = invalid "value"
 | |
|   let no kind s = str "no %s %s" (quote s) kind
 | |
|   let not_dir s = str "%s is not a directory" (quote s)
 | |
|   let is_dir s = str "%s is a directory" (quote s)
 | |
|   let element kind s exp = str "invalid element in %s (`%s'): %s" kind s exp
 | |
|   let sep_miss sep s = invalid_val s (str "missing a `%c' separator" sep)
 | |
|   let unknown kind ?(hints = []) v =
 | |
|     let did_you_mean s = str ", did you mean %s ?" s in
 | |
|     let hints = match hints with [] -> "." | hs -> did_you_mean (alts_str hs) in
 | |
|     str "unknown %s %s%s" kind (quote v) hints
 | |
| 
 | |
|   let ambiguous kind s ambs =
 | |
|     str "%s %s ambiguous and could be %s" kind (quote s) (alts_str ambs)
 | |
| 
 | |
|   let pos_excess excess =
 | |
|     str "too many arguments, don't know what to do with %s"
 | |
|       (String.concat ", " (List.map quote excess))
 | |
| 
 | |
|   let flag_value f v =
 | |
|     str "option %s is a flag, it cannot take the argument %s"
 | |
|       (quote f) (quote v)
 | |
| 
 | |
|   let opt_value_missing f = str "option %s needs an argument" (quote f)
 | |
|   let opt_parse_value f e = str "option %s: %s" (quote f) e
 | |
|   let opt_repeated f f' =
 | |
|     if f = f' then str "option %s cannot be repeated" (quote f) else
 | |
|     str "options %s and %s cannot be present at the same time" (quote f)
 | |
|       (quote f')
 | |
| 
 | |
|   let pos_parse_value a e =
 | |
|     if a.docv = "" then e else match a.p_kind with
 | |
|     | Nth _ -> str "%s argument: %s" a.docv e
 | |
|     | _ -> str "%s... arguments: %s" a.docv e
 | |
| 
 | |
|   let arg_missing a =
 | |
|     if is_opt a then
 | |
|       let rec long_name = function
 | |
|       | n :: l -> if (String.length n) > 2 || l = [] then n else long_name l
 | |
|       | [] -> assert false
 | |
|       in
 | |
|       str "required option %s is missing" (long_name a.o_names)
 | |
|     else
 | |
|     if a.docv = "" then str "a required argument is missing" else
 | |
|     str "required argument %s is missing" a.docv
 | |
| 
 | |
|   (* Error printers *)
 | |
| 
 | |
|   let print ppf ei e = pr ppf "%s: @[%a@]@." (fst ei.main).name pr_text e
 | |
|   let pr_backtrace err ei e bt =
 | |
|     let bt =
 | |
|       let len = String.length bt in
 | |
|       if len > 0 then String.sub bt 0 (len - 1) (* remove final '\n' *) else bt
 | |
|     in
 | |
|     pr err
 | |
|       "%s: @[internal error, uncaught exception:@\n%a@]@."
 | |
|       (fst ei.main).name pr_lines (str "%s\n%s" (Printexc.to_string e) bt)
 | |
| 
 | |
|   let pr_try_help ppf ei =
 | |
|     let exec = Help.invocation ei in
 | |
|     let main = (fst ei.main).name in
 | |
|     if exec = main then
 | |
|       pr ppf "@[<2>Try `%s --help' for more information.@]" exec
 | |
|     else
 | |
|     pr ppf "@[<2>Try `%s --help' or `%s --help' for more information.@]"
 | |
|       exec main
 | |
| 
 | |
|   let pr_usage ppf ei e =
 | |
|     pr ppf "@[<v>%s: @[%a@]@,@[Usage: @[%a@]@]@,%a@]@."
 | |
|       (fst ei.main).name pr_text e Help.pr_synopsis ei pr_try_help ei
 | |
| end
 | |
| 
 | |
| (* Command lines. A command line stores pre-parsed information about
 | |
|    the command line's arguments in a more structured way. Given the
 | |
|    [arg_info] values mentionned in a term and Sys.argv (whithout exec
 | |
|    name) we parse the command line into a map of [arg_info] values to
 | |
|    [arg] values. This map is used by the term's closures to retrieve
 | |
|    and convert command line arguments (see the Arg module). *)
 | |
| 
 | |
| module Cmdline :sig
 | |
|   exception Error of string
 | |
|   val choose_term : term_info -> (term_info * 'a) list -> string list ->
 | |
|     term_info * string list
 | |
|   val create : ?peek_opts:bool -> arg_info list -> string list -> cmdline
 | |
|   val opt_arg : cmdline -> arg_info -> (int * string * (string option)) list
 | |
|   val pos_arg : cmdline -> arg_info -> string list
 | |
| end = struct
 | |
|   exception Error of string
 | |
| 
 | |
|   let opt_arg cl a = match try Amap.find a cl with Not_found -> assert false
 | |
|   with O l -> l | _ -> assert false
 | |
| 
 | |
|   let pos_arg cl a = match try Amap.find a cl with Not_found -> assert false
 | |
|   with P l -> l | _ -> assert false
 | |
| 
 | |
|   let choose_term ti choices = function
 | |
|   | [] -> ti, []
 | |
|   | maybe :: args' as args ->
 | |
|       if String.length maybe > 1 && maybe.[0] = '-' then ti, args else
 | |
|       let index =
 | |
|         let add acc (choice, _) = Trie.add acc choice.name choice in
 | |
|         List.fold_left add Trie.empty choices
 | |
|       in
 | |
|       match Trie.find index maybe with
 | |
|       | `Ok choice -> choice, args'
 | |
|       | `Not_found ->
 | |
|         let all = Trie.ambiguities index "" in
 | |
|         let hints = suggest maybe all in
 | |
|         raise (Error (Err.unknown "command" ~hints maybe))
 | |
|       | `Ambiguous ->
 | |
|           let ambs = List.sort compare (Trie.ambiguities index maybe) in
 | |
|           raise (Error (Err.ambiguous "command" maybe ambs))
 | |
| 
 | |
|   let arg_info_indexes al =
 | |
|     (* from [al] 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 aux opti posi cl = function
 | |
|     | a :: l ->
 | |
|         if is_pos a then aux opti (a :: posi) (Amap.add a (P []) cl) l else
 | |
|         let add t name = Trie.add t name a in
 | |
|         aux (List.fold_left add opti a.o_names) posi (Amap.add a (O []) cl) l
 | |
|     | [] -> opti, posi, cl
 | |
|     in
 | |
|     aux Trie.empty [] Amap.empty al
 | |
| 
 | |
|   let parse_opt_arg s =          (* (name,value) of opt arg, assert len > 1. *)
 | |
|     let l = String.length s in
 | |
|     if s.[1] <> '-' then
 | |
|       if l = 2 then s, None else
 | |
|       String.sub s 0 2, Some (String.sub s 2 (l - 2))
 | |
|     else try
 | |
|       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 parse_args ~peek_opts opti cl args =
 | |
|     (* returns an updated [cl] cmdline according to the options found in [args]
 | |
|        with the trie index [opti]. Positional arguments are returned in order
 | |
|        in a list. *)
 | |
|     let rec aux k opti cl pargs = function
 | |
|     | [] -> cl, (List.rev pargs)
 | |
|     | "--" :: args -> cl, (List.rev_append pargs args)
 | |
|     | s :: args ->
 | |
|         let is_opt s = String.length s > 1 && s.[0] = '-' in
 | |
|         let is_short_opt s = String.length s = 2 && s.[0] = '-' in
 | |
|         if not (is_opt s) then aux (k+1) opti cl (s :: pargs) args else
 | |
|         let name, value = parse_opt_arg s in
 | |
|         match Trie.find opti name with
 | |
|         | `Ok a ->
 | |
|             let value, args = match value, a.o_kind with
 | |
|             | Some v, Flag when is_short_opt name -> None, ("-" ^ v) :: args
 | |
|             | Some v, _ -> value, args
 | |
|             | None, Flag -> value, args
 | |
|             | None, _ ->
 | |
|                 match args with
 | |
|                 | v :: rest -> if is_opt v then None, args else Some v, rest
 | |
|                 | [] -> None, args
 | |
|             in
 | |
|             let arg = O ((k, name, value) :: opt_arg cl a) in
 | |
|             aux (k+1) opti (Amap.add a arg cl) pargs args
 | |
|         | `Not_found when peek_opts -> aux (k+1) opti cl pargs args (* skip *)
 | |
|         | `Not_found ->
 | |
|             let hints =
 | |
|               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 = Trie.ambiguities opti "-" in
 | |
|               match List.mem short_opt all, suggest 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
 | |
|             in
 | |
|             raise (Error (Err.unknown "option" ~hints name))
 | |
|         | `Ambiguous ->
 | |
|             let ambs = List.sort compare (Trie.ambiguities opti name) in
 | |
|             raise (Error (Err.ambiguous "option" name ambs))
 | |
|     in
 | |
|     aux 0 opti cl [] args
 | |
| 
 | |
|   let process_pos_args posi cl pargs =
 | |
|     (* returns an updated [cl] cmdline in which each positional arg mentionned
 | |
|        in the list index posi, is given a value according the list
 | |
|        of positional arguments values [pargs]. *)
 | |
|     if pargs = [] then cl else
 | |
|     let rec take n acc l =
 | |
|       if n = 0 then List.rev acc else
 | |
|       take (n - 1) (List.hd l :: acc) (List.tl l)
 | |
|     in
 | |
|     let rec aux pargs last cl max_spec = function
 | |
|     | a :: al ->
 | |
|         let arg, max_spec = match a.p_kind with
 | |
|         | All -> P pargs, last
 | |
|         | Nth (rev, k) ->
 | |
|             let k = if rev then last - k else k in
 | |
|             let max_spec = max k max_spec in
 | |
|             if k < 0 || k > last then P [], max_spec else
 | |
|             P ([List.nth pargs k]), max_spec
 | |
|         | Left (rev, k) ->
 | |
|             let k = if rev then last - k else k in
 | |
|             let max_spec = max k max_spec in
 | |
|             if k <= 0 || k > last then P [], max_spec else
 | |
|             P (take k [] pargs), max_spec
 | |
|         | Right (rev, k) ->
 | |
|             let k = if rev then last - k else k in
 | |
|             if k < 0 || k >= last then P [], last else
 | |
|             P (List.rev (take (last - k) [] (List.rev pargs))), last
 | |
|         in
 | |
|         aux pargs last (Amap.add a arg cl) max_spec al
 | |
|     | [] -> cl, max_spec
 | |
|     in
 | |
|     let last = List.length pargs - 1 in
 | |
|     let cl, max_spec = aux pargs last cl (-1) posi in
 | |
|     if last <= max_spec then cl else
 | |
|     let excess = List.rev (take (last - max_spec) [] (List.rev pargs)) in
 | |
|     raise (Error (Err.pos_excess excess))
 | |
| 
 | |
|   let create ?(peek_opts = false) al args =
 | |
|     let opti, posi, cl = arg_info_indexes al in
 | |
|     let cl, pargs = parse_args ~peek_opts opti cl args in
 | |
|     if peek_opts then cl (* skip positional arguments *) else
 | |
|     process_pos_args posi cl pargs
 | |
| end
 | |
| 
 | |
| module Arg = struct
 | |
|   type 'a parser = string -> [ `Ok of 'a | `Error of string ]
 | |
|   type 'a printer = Format.formatter -> 'a -> unit
 | |
|   type 'a converter = 'a parser * 'a printer
 | |
|   type 'a arg_converter = (eval_info -> cmdline -> 'a)
 | |
|   type 'a t = arg_info list * 'a arg_converter
 | |
|   type info = arg_info
 | |
| 
 | |
|   let ( & ) f x = f x
 | |
|   let parse_error e = raise (Cmdline.Error e)
 | |
|   let some ?(none = "") (parse, print) =
 | |
|     (fun s -> match parse s with `Ok v -> `Ok (Some v) | `Error _ as e -> e),
 | |
|     (fun ppf v -> match v with None -> pr_str ppf none| Some v -> print ppf v)
 | |
| 
 | |
|   let info ?docs ?(docv = "") ?(doc = "") names =
 | |
|     let dash n = if String.length n = 1 then "-" ^ n else "--" ^ n in
 | |
|     let docs = match docs with
 | |
|     | None -> if names = [] then "ARGUMENTS" else "OPTIONS"
 | |
|     | Some s -> s
 | |
|     in
 | |
|     { id = arg_id (); absent = Val (Lazy.from_val "");
 | |
|       doc = doc; docv = docv; docs = docs;
 | |
|       p_kind = All; o_kind = Flag; o_names = List.rev_map dash names;
 | |
|       o_all = false; }
 | |
| 
 | |
|   let flag a =
 | |
|     if is_pos a then invalid_arg err_not_opt else
 | |
|     let convert _ cl = match Cmdline.opt_arg cl a with
 | |
|     | [] -> false
 | |
|     | [_, _, None] -> true
 | |
|     | [_, f, Some v] -> parse_error (Err.flag_value f v)
 | |
|     | (_, f, _) :: (_ ,g, _) :: _  -> parse_error (Err.opt_repeated f g)
 | |
|     in
 | |
|     [a], convert
 | |
| 
 | |
|   let flag_all a =
 | |
|     if is_pos a then invalid_arg err_not_opt else
 | |
|     let a = { a with o_all = true } in
 | |
|     let convert _ cl = match Cmdline.opt_arg cl a with
 | |
|     | [] -> []
 | |
|     | l ->
 | |
|         let truth (_, f, v) = match v with
 | |
|         | None -> true | Some v -> parse_error (Err.flag_value f v)
 | |
|   in
 | |
|   List.rev_map truth l
 | |
|     in
 | |
|     [a], convert
 | |
| 
 | |
|   let vflag v l =
 | |
|     let convert _ cl =
 | |
|       let rec aux fv = function
 | |
|       | (v, a) :: rest ->
 | |
|           begin match Cmdline.opt_arg cl a with
 | |
|           | [] -> aux fv rest
 | |
|           | [_, f, None] ->
 | |
|               begin match fv with
 | |
|               | None -> aux (Some (f, v)) rest
 | |
|               | Some (g, _) -> parse_error (Err.opt_repeated g f)
 | |
|               end
 | |
|           | [_, f, Some v] -> parse_error (Err.flag_value f v)
 | |
|           | (_, f, _) :: (_, g, _) :: _ -> parse_error (Err.opt_repeated g f)
 | |
|           end
 | |
|       | [] -> match fv with None -> v | Some (_, v) -> v
 | |
|       in
 | |
|       aux None l
 | |
|     in
 | |
|     let flag (_, a) = if is_pos a then invalid_arg err_not_opt else a in
 | |
|     List.rev_map flag l, convert
 | |
| 
 | |
|   let vflag_all v l =
 | |
|     let convert _ cl =
 | |
|       let rec aux acc = function
 | |
|       | (fv, a) :: rest ->
 | |
|           begin match Cmdline.opt_arg cl a with
 | |
|           | [] -> aux acc rest
 | |
|           | l ->
 | |
|               let fval (k, f, v) = match v with
 | |
|               | None -> (k, fv) | Some v -> parse_error (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
 | |
|       aux [] l
 | |
|     in
 | |
|     let flag (_, a) =
 | |
|       if is_pos a then invalid_arg err_not_opt else { a with o_all = true }
 | |
|     in
 | |
|     List.rev_map flag l, convert
 | |
| 
 | |
|   let parse_opt_value parse f v = match parse v with
 | |
|   | `Ok v -> v | `Error e -> parse_error (Err.opt_parse_value f e)
 | |
| 
 | |
|   let opt ?vopt (parse, print) v a =
 | |
|     if is_pos a then invalid_arg err_not_opt else
 | |
|     let a = { a with absent = Val (lazy (str_of_pp print v));
 | |
|                      o_kind = match vopt with
 | |
|                      | None -> Opt | Some dv -> Opt_vopt (str_of_pp print dv) }
 | |
|     in
 | |
|     let convert _ cl = match Cmdline.opt_arg cl a with
 | |
|     | [] -> v
 | |
|     | [_, f, Some v] -> parse_opt_value parse f v
 | |
|     | [_, f, None] ->
 | |
|         begin match vopt with
 | |
|         | None -> parse_error (Err.opt_value_missing f)
 | |
|         | Some optv -> optv
 | |
|         end
 | |
|     | (_, f, _) :: (_, g, _) :: _ -> parse_error (Err.opt_repeated g f)
 | |
|     in
 | |
|     [a], convert
 | |
| 
 | |
|   let opt_all ?vopt (parse, print) v a =
 | |
|     if is_pos a then invalid_arg err_not_opt else
 | |
|     let a = { a with absent = Val (Lazy.from_val ""); o_all = true;
 | |
|                      o_kind = match vopt with
 | |
|                      | None -> Opt | Some dv -> Opt_vopt (str_of_pp print dv) }
 | |
|     in
 | |
|     let convert _ cl = match Cmdline.opt_arg cl a with
 | |
|     | [] -> v
 | |
|     | l ->
 | |
|         let parse (k, f, v) = match v with
 | |
|         | Some v -> (k, parse_opt_value parse f v)
 | |
|         | None -> match vopt with
 | |
|         | None -> parse_error (Err.opt_value_missing f)
 | |
|         | Some dv -> (k, dv)
 | |
|         in
 | |
|         List.rev_map snd (List.sort rev_compare (List.rev_map parse l))
 | |
|     in
 | |
|     [a], convert
 | |
| 
 | |
|   (* Positional arguments *)
 | |
| 
 | |
|   let parse_pos_value parse a v = match parse v with
 | |
|   | `Ok v -> v | `Error e -> parse_error (Err.pos_parse_value a e)
 | |
| 
 | |
|   let pos ?(rev = false) k (parse, print) v a =
 | |
|     if is_opt a then invalid_arg err_not_pos else
 | |
|     let a = { a with p_kind = Nth (rev, k);
 | |
|                      absent = Val (Lazy.from_val (str_of_pp print v)) }
 | |
|     in
 | |
|     let convert _ cl = match Cmdline.pos_arg cl a with
 | |
|     | [] -> v
 | |
|     | [v] -> parse_pos_value parse a v
 | |
|     | _ -> assert false
 | |
|     in
 | |
|     [a], convert
 | |
| 
 | |
|   let pos_list kind (parse, _) v a =
 | |
|     if is_opt a then invalid_arg err_not_pos else
 | |
|     let a = { a with p_kind = kind } in
 | |
|     let convert _ cl = match Cmdline.pos_arg cl a with
 | |
|     | [] -> v
 | |
|     | l -> List.rev (List.rev_map (parse_pos_value parse a) l)
 | |
|     in
 | |
|     [a], convert
 | |
| 
 | |
|   let pos_all c v a = pos_list All c v a
 | |
|   let pos_left ?(rev = false) k = pos_list (Left (rev, k))
 | |
|   let pos_right ?(rev = false) k = pos_list (Right (rev, k))
 | |
| 
 | |
|   (* Arguments as terms *)
 | |
| 
 | |
|   let absent_error al = List.rev_map (fun a -> { a with absent = Error }) al
 | |
|   let value a = a
 | |
|   let required (al, convert) =
 | |
|     let al = absent_error al in
 | |
|     let convert ei cl = match convert ei cl with
 | |
|     | Some v -> v
 | |
|     | None -> parse_error (Err.arg_missing (List.hd al))
 | |
|     in
 | |
|     al, convert
 | |
| 
 | |
|   let non_empty (al, convert) =
 | |
|     let al = absent_error al in
 | |
|     let convert ei cl = match convert ei cl with
 | |
|     | [] -> parse_error (Err.arg_missing (List.hd al))
 | |
|     | l -> l
 | |
|     in
 | |
|     al, convert
 | |
| 
 | |
|   let last (al, convert) =
 | |
|     let convert ei cl = match convert ei cl with
 | |
|     | [] -> parse_error (Err.arg_missing (List.hd al))
 | |
|     | l -> List.hd (List.rev l)
 | |
|     in
 | |
|     al, convert
 | |
| 
 | |
|   (* Predefined converters. *)
 | |
| 
 | |
|   let bool =
 | |
|     (fun s -> try `Ok (bool_of_string s) with Invalid_argument _ ->
 | |
|         `Error (Err.invalid_val s (alts_str ["true"; "false"]))),
 | |
|     Format.pp_print_bool
 | |
| 
 | |
|   let char =
 | |
|     (fun s -> if String.length s = 1 then `Ok s.[0] else
 | |
|       `Error (Err.invalid_val s "expected a character")),
 | |
|     pr_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 -> pr ppf "%ld")
 | |
| 
 | |
|   let int64 =
 | |
|     parse_with Int64.of_string "expected a 64-bit integer",
 | |
|     (fun ppf -> pr ppf "%Ld")
 | |
| 
 | |
|   let nativeint =
 | |
|     parse_with Nativeint.of_string "expected a processor-native integer",
 | |
|     (fun ppf -> pr ppf "%nd")
 | |
| 
 | |
|   let float =
 | |
|     parse_with float_of_string "expected a floating point number",
 | |
|     Format.pp_print_float
 | |
| 
 | |
|   let string = (fun s -> `Ok s), pr_str
 | |
|   let enum sl =
 | |
|     if sl = [] then invalid_arg err_empty_list else
 | |
|     let sl_inv = List.rev_map (fun (s,v) -> (v,s)) sl in
 | |
|     let print ppf v = pr_str ppf (List.assoc v sl_inv) in
 | |
|     let t = Trie.of_list sl in
 | |
|     let parse s = match Trie.find t s with
 | |
|     | `Ok _ as r -> r
 | |
|     | `Ambiguous ->
 | |
|         let ambs = List.sort compare (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
 | |
|     parse, print
 | |
| 
 | |
|   let file =
 | |
|     (fun s -> if Sys.file_exists s then `Ok s else
 | |
|       `Error (Err.no "file or directory" s)),
 | |
|     pr_str
 | |
| 
 | |
|   let dir =
 | |
|     (fun s ->
 | |
|        if Sys.file_exists s then
 | |
|          if Sys.is_directory s then `Ok s else `Error (Err.not_dir s)
 | |
|        else
 | |
|        `Error (Err.no "directory" s)),
 | |
|     pr_str
 | |
| 
 | |
|   let non_dir_file =
 | |
|     (fun s ->
 | |
|        if Sys.file_exists s then
 | |
|          if not (Sys.is_directory s) then `Ok s else `Error (Err.is_dir s)
 | |
|        else
 | |
|        `Error (Err.no "file" s)),
 | |
|     pr_str
 | |
| 
 | |
|   let split_and_parse sep parse s =
 | |
|     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, pr_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 -> pr_e ppf v; if (l <> []) then (pr_char ppf sep; print ppf l)
 | |
|     | [] -> ()
 | |
|     in
 | |
|     parse, print
 | |
| 
 | |
|   let array ?(sep = ',') (parse, pr_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 pr_e ppf v.(i); if i <> max then pr_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) = pr 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) =
 | |
|       pr 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) =
 | |
|       pr ppf "%a%c%a%c%a%c%a" pr0 v0 sep pr1 v1 sep pr2 v2 sep pr3 v3
 | |
|     in
 | |
|     parse, print
 | |
| 
 | |
|   (* Documentation formatting helpers *)
 | |
| 
 | |
|   let doc_quote = quote
 | |
|   let doc_alts = alts_str
 | |
|   let doc_alts_enum ?quoted enum = alts_str ?quoted (List.map fst enum)
 | |
| end
 | |
| 
 | |
| module Term = struct
 | |
|   type info = term_info
 | |
|   type +'a t = arg_info list * (eval_info -> cmdline -> 'a)
 | |
|   type 'a result = [
 | |
|     | `Ok of 'a | `Error of [`Parse | `Term | `Exn ] | `Version | `Help ]
 | |
| 
 | |
|   exception Term of
 | |
|       [ `Help of [`Pager | `Plain | `Groff] * string option
 | |
|       | `Error of bool * string ]
 | |
| 
 | |
|   let info  ?(sdocs = "OPTIONS") ?(man = []) ?(docs = "COMMANDS") ?(doc = "")
 | |
|       ?version name =
 | |
|     { name = name; version = version; tdoc = doc; tdocs = docs; sdocs = sdocs;
 | |
|       man = man }
 | |
| 
 | |
|   let name ti = ti.name
 | |
|   let pure v = [], (fun _ _ -> v)
 | |
|   let app (al, f) (al', v) =
 | |
|     List.rev_append al al',
 | |
|     fun ei cl -> (f ei cl) (v ei cl)
 | |
| 
 | |
|   let ( $ ) = app
 | |
| 
 | |
|   type 'a ret =
 | |
|     [ `Help of [`Pager | `Plain | `Groff] * string option
 | |
|     | `Error of (bool * string)
 | |
|     | `Ok of 'a ]
 | |
| 
 | |
|   let ret (al, v) =
 | |
|     al, fun ei cl -> match v ei cl with
 | |
|     | `Ok v -> v
 | |
|     | `Error (u,e) -> raise (Term (`Error (u,e)))
 | |
|     | `Help h -> raise (Term (`Help h))
 | |
| 
 | |
|   let main_name = [], (fun ei _ -> (fst ei.main).name)
 | |
|   let choice_names =
 | |
|     [], fun ei _ -> List.rev_map (fun e -> (fst e).name) ei.choices
 | |
| 
 | |
|   let man_format =
 | |
|     let fmts = ["pager", `Pager; "groff", `Groff; "plain", `Plain] in
 | |
|     let doc = "Show output in format $(docv) (pager, plain or groff)."in
 | |
|     Arg.(value & opt (enum fmts) `Pager & info ["man-format"] ~docv:"FMT" ~doc)
 | |
| 
 | |
|   (* Evaluation *)
 | |
| 
 | |
|   let remove_exec argv =
 | |
|     try List.tl (Array.to_list argv) with Failure _ -> invalid_arg err_argv
 | |
| 
 | |
|   let add_std_opts ei =
 | |
|     let docs = (fst ei.term).sdocs in
 | |
|     let args, v_lookup =
 | |
|       if (fst ei.main).version = None then [], None else
 | |
|       let (a, lookup) =
 | |
|         Arg.flag (Arg.info ["version"] ~docs ~doc:"Show version information.")
 | |
|       in
 | |
|       a, Some lookup
 | |
|     in
 | |
|     let args, h_lookup =
 | |
|       let (a, lookup) =
 | |
|         let fmt = Arg.enum ["pager",`Pager; "groff",`Groff; "plain",`Plain] in
 | |
|         let doc = "Show this help in format $(docv) (pager, plain or groff)."in
 | |
|         let a = Arg.info ["help"] ~docv:"FMT" ~docs ~doc in
 | |
|         Arg.opt ~vopt:(Some `Pager) (Arg.some fmt) None a
 | |
|       in
 | |
|       List.rev_append a args, lookup
 | |
|     in
 | |
|     h_lookup, v_lookup,
 | |
|     { ei with term = (fst ei.term), List.rev_append args (snd ei.term) }
 | |
| 
 | |
|   let eval_term help err ei f args =
 | |
|     let help_arg, vers_arg, ei = add_std_opts ei in
 | |
|     try
 | |
|       let cl = Cmdline.create (snd ei.term) args in
 | |
|       match help_arg ei cl, vers_arg with
 | |
|       | Some fmt, _ -> Help.print fmt help ei; `Help
 | |
|       | None, Some v_arg when v_arg ei cl -> Help.pr_version help ei; `Version
 | |
|       | _ -> `Ok (f ei cl)
 | |
|     with
 | |
|     | Cmdline.Error e -> Err.pr_usage err ei e; `Error `Parse
 | |
|     | Term (`Error (usage, e)) ->
 | |
|         if usage then Err.pr_usage err ei e else Err.print err ei e;
 | |
|         `Error `Term
 | |
|     | Term (`Help (fmt, cmd)) ->
 | |
|         let ei = match cmd with
 | |
|         | Some cmd ->
 | |
|             let cmd =
 | |
|               try List.find (fun (i, _) -> i.name = cmd) ei.choices
 | |
|               with Not_found -> invalid_arg (err_help cmd)
 | |
|             in
 | |
|             {ei with term = cmd }
 | |
|         | None -> { ei with term = ei.main }
 | |
|         in
 | |
|         let _, _, ei = add_std_opts ei in
 | |
|         Help.print fmt help ei; `Help
 | |
| 
 | |
|   let eval ?(help = Format.std_formatter) ?(err = Format.err_formatter)
 | |
|       ?(catch = true) ?(argv = Sys.argv) ((al, f), ti)  =
 | |
|     let term = ti, al in
 | |
|     let ei = { term = term; main = term; choices = [] } in
 | |
|     try eval_term help err ei f (remove_exec argv) with
 | |
|     | e when catch ->
 | |
|         Err.pr_backtrace err ei e (Printexc.get_backtrace ()); `Error `Exn
 | |
| 
 | |
|   let eval_choice ?(help = Format.std_formatter) ?(err = Format.err_formatter)
 | |
|       ?(catch = true) ?(argv = Sys.argv) (((al, f) as t), ti) choices =
 | |
|     let ei_choices = List.rev_map (fun ((al, _), ti) -> ti, al) choices in
 | |
|     let main = (ti, al) in
 | |
|     let ei = { term = main; main = main; choices = ei_choices } in
 | |
|     try
 | |
|       let chosen, args = Cmdline.choose_term ti ei_choices (remove_exec argv) in
 | |
|       let find_chosen (_, ti) = ti = chosen in
 | |
|       let (al, f), _ = List.find find_chosen ((t, ti) :: choices) in
 | |
|       let ei = { ei with term = (chosen, al) } in
 | |
|       eval_term help err ei f args
 | |
|     with
 | |
|     | Cmdline.Error e ->                    (* may be raised by choose_term. *)
 | |
|         Err.pr_usage err ei e; `Error `Parse
 | |
|     | e when catch ->
 | |
|         Err.pr_backtrace err ei e (Printexc.get_backtrace ()); `Error `Exn
 | |
| 
 | |
|   let eval_peek_opts ?(version_opt = false) ?(argv = Sys.argv) (al, f) =
 | |
|     let args = remove_exec argv in
 | |
|     let version = if version_opt then Some "dummy" else None in
 | |
|     let term = info ?version "dummy", al in
 | |
|     let ei = { term = term; main = term; choices = [] } in
 | |
|     let help_arg, vers_arg, ei = add_std_opts ei in
 | |
|     try
 | |
|       let cl = Cmdline.create ~peek_opts:true (snd ei.term) args in
 | |
|       match help_arg ei cl, vers_arg with
 | |
|       | Some fmt, _ ->
 | |
|           (try (Some (f ei cl), `Help) with e -> None, `Help)
 | |
|       | None, Some v_arg when v_arg ei cl ->
 | |
|           (try (Some (f ei cl), `Version) with e -> None, `Version)
 | |
|       | _ ->
 | |
|           let v = f ei cl in
 | |
|           Some v, `Ok v
 | |
|     with
 | |
|     | Cmdline.Error _ -> None, (`Error `Parse)
 | |
|     | Term _ -> None, (`Error `Term)
 | |
|     | e -> None, (`Error `Exn)
 | |
| end
 | |
| 
 | |
| (*---------------------------------------------------------------------------
 | |
|    Copyright (c) 2011 Daniel C. Bünzli
 | |
|    All rights reserved.
 | |
| 
 | |
|    Redistribution and use in source and binary forms, with or without
 | |
|    modification, are permitted provided that the following conditions
 | |
|    are met:
 | |
| 
 | |
|    1. Redistributions of source code must retain the above copyright
 | |
|       notice, this list of conditions and the following disclaimer.
 | |
| 
 | |
|    2. Redistributions in binary form must reproduce the above
 | |
|       copyright notice, this list of conditions and the following
 | |
|       disclaimer in the documentation and/or other materials provided
 | |
|       with the distribution.
 | |
| 
 | |
|    3. Neither the name of Daniel C. Bünzli nor the names of
 | |
|       contributors may be used to endorse or promote products derived
 | |
|       from this software without specific prior written permission.
 | |
| 
 | |
|    THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 | |
|    "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 | |
|    LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 | |
|    A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 | |
|    OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 | |
|    SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 | |
|    LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 | |
|    DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 | |
|    THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 | |
|    (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 | |
|    OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 | |
|   ---------------------------------------------------------------------------*)
 |