Files
libguestfs/common/mltools/getopt.ml
Richard W.M. Jones 80fa8a91e3 Rename mllib -> common/mltools.
This directory which previously contained random modules and functions
now has an official purpose: to be the place for any OCaml utility
needed by the OCaml virt tools.

This is just code movement, I didn't (yet) rename or move any of the
modules.
2017-09-28 14:39:23 +01:00

242 lines
6.7 KiB
OCaml

(* Command line handling for OCaml tools in libguestfs.
* Copyright (C) 2016 Red Hat Inc.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*)
open Common_gettext.Gettext
open Printf
type spec =
| Unit of (unit -> unit)
| Set of bool ref
| Clear of bool ref
| String of string * (string -> unit)
| Set_string of string * string ref
| Int of string * (int -> unit)
| Set_int of string * int ref
| Symbol of string * string list * (string -> unit)
module OptionName = struct
type option_name = S of char | L of string | M of string
end
open OptionName
type keys = option_name list
type doc = string
type usage_msg = string
type anon_fun = (string -> unit)
type c_keys = string array
type speclist = (keys * spec * doc) list
type t = {
mutable specs : speclist;
anon_fun : anon_fun option;
usage_msg : usage_msg;
}
let hidden_option_description = ""
external getopt_parse : string array -> (c_keys * spec * doc) array -> ?anon_fun:anon_fun -> usage_msg -> unit = "guestfs_int_mllib_getopt_parse"
let column_wrap = 38
(* This should only be used for --help and man pages. *)
let string_of_option_name = function
| S c -> sprintf "-%c" c
| L s -> "--" ^ s
| M s -> "-" ^ s
let show_help h () =
let b = Buffer.create 1024 in
let spaces n =
String.make n ' ' in
let prologue = sprintf (f_"%s\nOptions:\n") h.usage_msg in
Buffer.add_string b prologue;
let specs =
List.filter (
fun (_, _, doc) ->
doc <> hidden_option_description
) h.specs in
List.iter (
fun (keys, spec, doc) ->
let columns = ref 0 in
let add s =
Buffer.add_string b s;
columns := !columns + (String.length s)
in
add " ";
add (String.concat ", " (List.map string_of_option_name keys));
let arg =
match spec with
| Unit _
| Set _
| Clear _ -> None
| String (arg, _)
| Set_string (arg, _)
| Int (arg, _)
| Set_int (arg, _)
| Symbol (arg, _, _) -> Some arg in
(match arg with
| None -> ()
| Some arg ->
add (sprintf " <%s>" arg)
);
if !columns >= column_wrap then (
Buffer.add_char b '\n';
Buffer.add_string b (spaces column_wrap);
) else (
Buffer.add_string b (spaces (column_wrap - !columns));
);
Buffer.add_string b doc;
Buffer.add_char b '\n';
) specs;
Buffer.output_buffer stdout b;
exit 0
let is_prefix str prefix =
let n = String.length prefix in
String.length str >= n && String.sub str 0 n = prefix
(* Implement `--short-options' and `--long-options'. *)
let display_short_options h () =
List.iter (
fun (args, _, _) ->
List.iter (
function
| S c -> printf "-%c\n" c
| M s -> printf "-%s\n" s
| L _ -> ()
) args
) h.specs;
exit 0
let display_long_options h () =
List.iter (
fun (args, _, _) ->
List.iter (
function
| L "short-options" | L "long-options"
| S _ -> ()
| L s | M s -> printf "--%s\n" s
) args
) h.specs;
exit 0
let compare_command_line_args a b =
let string_of_option_name_no_dashes = function
| S c -> String.make 1 c
| L s | M s -> s
in
let a = String.lowercase (string_of_option_name_no_dashes a) in
let b = String.lowercase (string_of_option_name_no_dashes b) in
compare a b
let create specs ?anon_fun usage_msg =
(* Sanity check the input *)
let validate_key = function
| M s when String.length s <> 2 || (s.[0] <> 'i' && s.[0] <> 'o') ->
invalid_arg "Getopt spec: invalid use of M\"...\" option - only use this for virt-v2v -iX and -oX options"
| L"" -> invalid_arg "Getopt spec: invalid empty long option"
| L"help" -> invalid_arg "Getopt spec: should not have L\"help\""
| L"short-options" ->
invalid_arg "Getopt spec: should not have L\"short-options\""
| L"long-options" ->
invalid_arg "Getopt spec: should not have L\"long-options\""
| L s when s.[0] = '-' ->
invalid_arg (sprintf "Getopt spec: L%S should not begin with a dash"
s)
| L s when String.contains s '_' ->
invalid_arg (sprintf "Getopt spec: L%S should not contain '_'"
s)
| _ -> ()
in
let validate_spec = function
| Unit _ -> ()
| Set _ -> ()
| Clear _ -> ()
| String _ -> ()
| Set_string _ -> ()
| Int _ -> ()
| Set_int _ -> ()
| Symbol (_, elements, _) ->
List.iter (
fun e ->
if String.length e == 0 || is_prefix e "-" then
invalid_arg (sprintf "invalid element in Symbol: '%s'" e);
) elements;
in
List.iter (
fun (keys, spec, doc) ->
if keys == [] then
invalid_arg "empty keys for Getopt spec";
List.iter validate_key keys;
validate_spec spec;
) specs;
let t = {
specs = []; (* Set it later, with own options, and sorted. *)
anon_fun = anon_fun;
usage_msg = usage_msg;
} in
let added_options = [
[ L"short-options" ], Unit (display_short_options t),
hidden_option_description;
[ L"long-options" ], Unit (display_long_options t),
hidden_option_description;
[ L"help" ], Unit (show_help t), s_"Display brief help";
] in
let specs = added_options @ specs in
(* Sort the specs. *)
let specs = List.map (
fun (keys, action, doc) ->
List.hd (List.sort compare_command_line_args keys), (keys, action, doc)
) specs in
let specs =
let cmp (arg1, _) (arg2, _) = compare_command_line_args arg1 arg2 in
List.sort cmp specs in
let specs = List.map snd specs in
t.specs <- specs;
t
let parse_argv t argv =
let specs = List.map (
fun (keys, spec, doc) ->
let keys = List.map (
function
| S c -> sprintf "-%c" c
| L s | M s -> sprintf "--%s" s
) keys in
let keys = Array.of_list keys in
keys, spec, doc
) t.specs in
let specs = Array.of_list specs in
getopt_parse argv specs ?anon_fun:t.anon_fun t.usage_msg
let parse t =
parse_argv t Sys.argv