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.
 | 
						|
  ---------------------------------------------------------------------------*)
 |