Skip to content
This repository
tag: v1379
Fetching contributors…

Cannot retrieve contributors at this time

file 320 lines (287 sloc) 11.103 kb
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320
(*
Copyright © 2011 MLstate

This file is part of OPA.

OPA is free software: you can redistribute it and/or modify it under the
terms of the GNU Affero General Public License, version 3, as published by
the Free Software Foundation.

OPA is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for
more details.

You should have received a copy of the GNU Affero General Public License
along with OPA. If not, see <http://www.gnu.org/licenses/>.
*)
(* depends *)
module String = BaseString

type simple_completion =
  | Nothing
  | File of string
  | Dir
  | Oneof of string list
type completion = {params : simple_completion list; stop : bool}

type spec =
  | Unit of (unit -> unit)
  | Bool of (bool -> unit)
  | Set of bool ref
  | Clear of bool ref
  | String of (string -> unit)
  | Set_string of string ref
  | Int of (int -> unit)
  | Set_int of int ref
  | Float of (float -> unit)
  | Set_float of float ref
  | Tuple of spec list
  | Symbol of string list * (string -> unit)
  | Rest of (string -> unit)
  | Complete of spec * completion

let spec_fun_of_assoc f assoc =
  Symbol (List.map fst assoc, (fun s -> f (List.assoc s assoc)))
let spec_of_assoc ref_ assoc =
  Symbol (List.map fst assoc, (fun s -> ref_ := List.assoc s assoc))
let spec_opt_of_assoc ref_ assoc =
  Symbol (List.map fst assoc, (fun s -> ref_ := Some (List.assoc s assoc)))
let spec_of_opt_assoc ref_ default assoc =
  Unit (fun () ->
          if !Arg.current+1 < Array.length Sys.argv then
            let s = Sys.argv.(!Arg.current+1) in
            if s <> "" && s.[0] = '-' then
              ref_ := default
            else (
              try
                ref_ := List.assoc s assoc;
                incr Arg.current;
              with Not_found ->
                ref_ := default
            )
          else
            ref_ := default
       )

let rec convert_spec_to_old_arg = function
  | Unit x -> Arg.Unit x
  | Bool x -> Arg.Bool x
  | Set x -> Arg.Set x
  | Clear x -> Arg.Clear x
  | String x -> Arg.String x
  | Set_string x -> Arg.Set_string x
  | Int x -> Arg.Int x
  | Set_int x -> Arg.Set_int x
  | Float x -> Arg.Float x
  | Set_float x -> Arg.Set_float x
  | Tuple x -> Arg.Tuple (List.map convert_spec_to_old_arg x)
  | Symbol (x,y) -> Arg.Symbol (x,y)
  | Rest x -> Arg.Rest x
  | Complete (x,_) -> convert_spec_to_old_arg x
let rec convert_spec_from_old_arg = function
  | Arg.Unit x -> Unit x
  | Arg.Bool x -> Bool x
  | Arg.Set x -> Set x
  | Arg.Clear x -> Clear x
  | Arg.String x -> String x
  | Arg.Set_string x -> Set_string x
  | Arg.Int x -> Int x
  | Arg.Set_int x -> Set_int x
  | Arg.Float x -> Float x
  | Arg.Set_float x -> Set_float x
  | Arg.Tuple x -> Tuple (List.map convert_spec_from_old_arg x)
  | Arg.Symbol (x,y) -> Symbol (x,y)
  | Arg.Rest x -> Rest x
let convert_from_old_arg_one (x,spec,y) = (x,convert_spec_from_old_arg spec,y)
let convert_to_old_arg_one (x,spec,y) = (x,convert_spec_to_old_arg spec,y)
let convert_to_old_arg l = List.map convert_to_old_arg_one l
let convert_from_old_arg l = List.map convert_from_old_arg_one l

(* stdlib's functions *)
let parse p = Arg.parse (convert_to_old_arg p)
let parse_argv ?current x p = Arg.parse_argv ?current x (convert_to_old_arg p)
let usage p = Arg.usage (convert_to_old_arg p)
let align p = convert_from_old_arg (Arg.align (convert_to_old_arg p))
let current = Arg.current
type key = Arg.key
type doc = Arg.doc
type usage_msg = Arg.usage_msg
type anon_fun = Arg.anon_fun
exception Help = Arg.Help
exception Bad = Arg.Bad

(* -- generate a simple manpage -- *)

let date_manpage () =
  let dt = Unix.gmtime (Unix.time ()) in
  (Date.fullmonth.(dt.Unix.tm_mon))
  ^ " " ^ (string_of_int (dt.Unix.tm_mday))
  ^ ", " ^ (string_of_int (dt.Unix.tm_year+1900))


(* todo: move to baseList *)
let pretty_list_to_string empty left separator right = function
  | [] -> empty
  | x::q -> (List.fold_left (fun s y -> s ^ separator ^ y) (left ^ x) q) ^ right

let split_option_args str =
  let reg = Str.regexp "[>)\"}] " in
  try
    let pos = (Str.search_forward reg str 0) + 1
    in
    (String.ltrim (Str.string_before str pos)), (String.ltrim (Str.string_after str pos))
  with
    Not_found -> "", (String.ltrim str)

let print_spec file (key, spec, doc) =
  let key = String.replace key "-" "\\-" in
  let options, doc = split_option_args doc in
  match spec with
  | Symbol (l, _) -> Printf.fprintf file ".TP\n%s %s %s\n%s\n" key (pretty_list_to_string "<none>" "{" "|" "}" l) options doc
  | _ -> Printf.fprintf file ".TP\n%s %s\n%s\n" key options doc; ()

let add_help speclist =
  let add help =
    if List.exists (fun (x, _, _) -> x = help) speclist then []
    else [help, Unit (fun x->x), " Display this list of options"]
   in
  speclist @ (add "-help") @ (add "--help")

let write_simple_manpage
    ~cmdname ~section
    ?(centerfooter=(date_manpage ()))
    ?(leftfooter="") ?(centerheader="")
    ?(summary="") ?(synopsis="") ?(description="") ?options ?(other=[])
    file =
  Printf.fprintf file ".TH \"%s\" \"%s\" \"%s\" \"%s\" \"%s\"\n" cmdname (string_of_int section) centerfooter leftfooter centerheader;
  if summary <> "" then
    Printf.fprintf file ".SH NAME\n%s \\- %s\n" cmdname summary
  else
    Printf.fprintf file ".SH NAME\n%s\n" cmdname;
  if synopsis <> "" then Printf.fprintf file ".SH SYNOPSIS\n%s\n" synopsis;
  if description <> "" then Printf.fprintf file ".SH DESCRIPTION\n%s\n" description;
  begin match options with None -> () | Some(speclist) -> begin
    Printf.fprintf file ".SH OPTIONS\n";
    List.iter (print_spec file) (add_help speclist);
  end end;
  List.iter (fun (title, content) -> Printf.fprintf file ".SH %s\n%s\n" title content) other;
  ()
  
(* --- *)


let sort_by_name l = List.stable_sort (fun (x,_,_) (y,_,_) -> compare (x:string) y) l
let sort l = (* also makes names unique *)
  let rec aux acc = function
    | [] -> List.rev acc
    | [x] -> List.rev (x :: acc)
    | ((s1,_,_) as v1) :: ((s2,_,_) :: tl2 as tl) ->
        if s1 = s2 then (
          if (try (Sys.getenv "MLSTATE_TESTING") = "0" with Not_found -> true) then
            Printf.eprintf "Internal warning: The option %s is matched several times in the command line parser.\n%!" s1;
          (* cannot call omanager from here, nor debugVariables *)
          aux acc (v1 :: tl2) (* keeping the first one in the list *)
        ) else
          aux (v1 :: acc) tl in
  aux [] (sort_by_name l)

let merge ({params = l1; stop = b1} as left) {params = l2; stop = b2} =
  if b1 then left else {params = l1 @ l2; stop = b1 || b2}

let rec convert_spec = function
  | Bool _ -> {params = [Oneof ["true";"false"]]; stop = false} (* possibly case insensitive ? *)
  | Unit _
  | Set _
  | Clear _ -> {params = []; stop = false} (* nothing follows *)
  | String _
  | Set_string _
  | Int _
  | Set_int _
  | Float _
  | Set_float _ -> {params = [Nothing]; stop = false} (* no completion possible *)
  | Tuple specs ->
      let l = List.map convert_spec specs in
      List.fold_left merge {params = []; stop = false} l
  | Symbol (sl,_) -> {params = [Oneof sl]; stop = false}
  | Rest _ -> {params = []; stop = true}
  | Complete (_,comp) -> comp
let convert_one (key,spec,_) =
  (key, convert_spec spec)
let convert parse =
  let completion = List.map convert_one parse in
  List.fold_left (fun completion builtin_option ->
                    if List.mem_assoc builtin_option completion then
                      completion
                    else
                      (builtin_option,{params = []; stop = false}) :: completion) completion ["-help";"--help"]

let stringify_simple_completion = function
  | Nothing -> "COMPREPLY=()"
  | File pattern -> Printf.sprintf "_filedir %s" pattern
  | Dir -> "_filedir -d"
  | Oneof sl -> Printf.sprintf "COMPREPLY=($(compgen -W '%s' -- ${cur}))" (String.concat " " sl)
let stringify_completion (k,l) =
  if l = [] then
    ""
  else
    let main,_ =
      List.fold_left (fun (acc,i) sc ->
                        let acc =
                          if i = 0 then acc ^ Printf.sprintf "
if [ \"$n\" -eq %d ]; then
%s" (i+1) (stringify_simple_completion sc)
                          else acc ^ Printf.sprintf "
elif [ \"$n\" -eq %d ]; then
%s" (i+1) (stringify_simple_completion sc) in
                        acc,(i+1)
                       ) ("",0) l in
    Printf.sprintf "
%s)%s
else
did_something=0
fi;;" k main

(* making sure we have only letters in the name, to avoid function containing wierd chars
* (like '/') *)
let remove_illegal_chars s =
  "prefix" ^ Str.global_replace (Str.regexp "[^a-zA-Z]") "" s

let stringify ?name ?(names=[]) ?(default=File "*") l =
  let one_name,names =
    match name,names with
    | None, [] -> Sys.argv.(0), [Filename.basename Sys.argv.(0)]
    | None, (h :: _ as l) -> h, l
    | Some v, l -> v, v :: l in
  let one_name = remove_illegal_chars one_name in
  let stops = List.map fst (List.filter (fun (_,{params=_; stop = b}) -> b) l) in
  let prologue = Printf.sprintf "\
# this file was generated by Base.Arg.add_bash_completion
# do not modify by hand

shopt -s extglob

_%s() {
COMPREPLY=()
local cur=`_get_cword`
" one_name in
  let check_stop = Printf.sprintf "
local latest_minus=$COMP_CWORD
local stop=(%s)

for (( i = 0; i < $COMP_CWORD; i ++ )); do
for j in \"${stop[@]}\"; do
[ \"${COMP_WORDS[i]}\" = \"$j\" ] && return 0
done
[[ \"${COMP_WORDS[i]}\" =~ -.* ]] && latest_minus=$i
done

local n=$(( COMP_CWORD - latest_minus ))
" (String.concat " " stops) in
  let complete = Printf.sprintf "
local did_something=0
if [ \"$n\" -gt 0 ]; then
did_something=1
case ${COMP_WORDS[latest_minus]} in%s
*) did_something=0;;
esac
fi
" (String.concat "" (List.map (fun (k,{params=l;stop=_}) -> stringify_completion (k,l)) l)) in
  let default_completion = Printf.sprintf "
if [ $did_something -eq 0 ]; then
case ${cur} in
-*) COMPREPLY=($(compgen -W '%s' -- ${cur}));;
*) %s;;
esac
fi
" (String.concat " " (List.map fst l))
    (stringify_simple_completion default) in
  let epilogue = Printf.sprintf "\
}

complete -F _%s -o filenames %s
" one_name (String.concat " " names) in
  prologue ^ check_stop ^ complete ^ default_completion ^ epilogue

let generate ?name ?names ?default args =
  let completion = convert args in
  let s = stringify ?name ?names ?default completion in
  let c = open_out "bash_completion" in
  output_string c s;
  close_out c

let rec add_bash_completion ?name ?names ?default args =
  let rec new_args =
    ("--bash-completion",
     Unit (fun () -> generate ?name ?names ?default new_args; exit 0),
     " Dumps a bash completion in ./bash_completion") :: args in
  new_args

let split s = String.slice_chars " ,;" s
Something went wrong with that request. Please try again.