10
1
mirror of https://gitlab.com/scemama/QCaml.git synced 2024-11-18 12:03:40 +01:00
QCaml/common/command_line.org

355 lines
9.9 KiB
Org Mode
Raw Normal View History

2020-12-27 16:36:25 +01:00
#+begin_src elisp tangle: no :results none :exports none
2020-12-27 15:46:11 +01:00
(setq pwd (file-name-directory buffer-file-name))
(setq name (file-name-nondirectory (substring buffer-file-name 0 -4)))
(setq lib (concat pwd "lib/"))
(setq testdir (concat pwd "test/"))
(setq mli (concat lib name ".mli"))
(setq ml (concat lib name ".ml"))
(setq test-ml (concat testdir name ".ml"))
(org-babel-tangle)
2020-12-27 16:36:25 +01:00
#+end_src
2020-12-27 15:46:11 +01:00
* Command line
:PROPERTIES:
:header-args: :noweb yes :comments both
:END:
This module is a wrapper around the ~Getopt~ library and helps to
define command-line arguments.
Here is an example of how to use this module.
First, define the specification:
2020-12-27 17:38:04 +01:00
#+begin_src ocaml :tangle no
2020-12-27 15:46:11 +01:00
let open Command_line in
begin
set_header_doc (Sys.argv.(0) ^ " - One-line description");
set_description_doc "Long description of the command.";
set_specs
[ { short='c'; long="check"; opt=Optional;
doc="Checks the input data";
arg=Without_arg; };
{ short='b' ; long="basis" ; opt=Mandatory;
2020-12-27 17:38:04 +01:00
arg=With_arg "<string>";
doc="Name of the file containing the basis set"; } ;
2020-12-27 16:36:25 +01:00
2020-12-27 15:46:11 +01:00
{ short='m' ; long="multiplicity" ; opt=Optional;
arg=With_arg "<int>";
doc="Spin multiplicity (2S+1). Default is singlet"; } ;
]
end;
2020-12-27 17:38:04 +01:00
#+end_src
2020-12-27 16:36:25 +01:00
2020-12-27 17:38:04 +01:00
Then, define what to do with the arguments:
#+begin_src ocaml :tangle no
2020-12-27 15:46:11 +01:00
let c =
Command_line.get_bool "check"
in
let basis =
match Command_line.get "basis" with
| Some x -> x
| None -> assert false
in
let multiplicity =
match Command_line.get "multiplicity" with
| None -> 1
| Some n -> int_of_string n
in
2020-12-27 17:38:04 +01:00
#+end_src
2020-12-27 16:36:25 +01:00
2021-01-28 00:34:26 +01:00
** Types
2020-12-27 15:46:11 +01:00
#+NAME:type
#+begin_src ocaml :tangle (eval mli)
type short_opt = char
2020-12-27 16:36:25 +01:00
type long_opt = string
type optional = Mandatory | Optional
2020-12-27 15:46:11 +01:00
type documentation = string
2021-01-28 00:34:26 +01:00
type argument = | With_arg of string
| Without_arg
| With_opt_arg of string
2020-12-27 15:46:11 +01:00
type description = {
short: short_opt ;
long : long_opt ;
opt : optional ;
doc : documentation ;
arg : argument ;
}
#+end_src
2021-01-28 00:34:26 +01:00
- <<<Short option>>>: in the command line, a dash with a single character
2020-12-27 17:38:04 +01:00
(ex: =ls -l=)
2021-01-28 00:34:26 +01:00
- <<<Long option>>>: in the command line, two dashes with a word
2020-12-27 17:38:04 +01:00
(ex: =ls --directory=)
- Command-line options can be ~Mandatory~ or ~Optional~
- Documentation of the option is used in the help function
- Some options require an argument (~ls --ignore="*.ml"~ ), some
don't (~ls -l~) and for some arguments the argument is optional
(~git --log[=<n>]~)
2020-12-27 16:36:25 +01:00
#+begin_src ocaml :tangle (eval ml) :exports none
2020-12-27 15:46:11 +01:00
<<type>>
#+end_src
** Mutable attributes
All the options are stored in the hash table ~dict~ where the key
is the long option and the value is a value of type ~description~.
2020-12-27 16:36:25 +01:00
#+begin_src ocaml :tangle (eval ml) :exports none
2020-12-27 15:46:11 +01:00
let header_doc = ref ""
let description_doc = ref ""
let footer_doc = ref ""
let anon_args_ref = ref []
let specs = ref []
2020-12-27 16:36:25 +01:00
let dict = Hashtbl.create 67
2020-12-27 15:46:11 +01:00
#+end_src
#+begin_src ocaml :tangle (eval mli)
val set_header_doc : string -> unit
val set_description_doc : string -> unit
val set_footer_doc : string -> unit
#+end_src
2020-12-27 16:36:25 +01:00
2020-12-27 17:38:04 +01:00
Functions to set the header, footer and main description of the
documentation provided by the ~help~ function:
2020-12-27 16:36:25 +01:00
#+begin_src ocaml :tangle (eval ml) :exports none
2020-12-27 15:46:11 +01:00
let set_header_doc s = header_doc := s
let set_description_doc s = description_doc := s
let set_footer_doc s = footer_doc := s
#+end_src
2020-12-27 16:36:25 +01:00
2020-12-27 15:46:11 +01:00
#+begin_src ocaml :tangle (eval mli)
val anonymous : long_opt -> optional -> documentation -> description
#+end_src
2020-12-27 17:38:04 +01:00
Function to create an anonymous argument.
2020-12-27 16:36:25 +01:00
#+begin_src ocaml :tangle (eval ml) :exports none
2020-12-27 15:46:11 +01:00
let anonymous name opt doc =
{ short=' ' ; long=name; opt; doc; arg=Without_arg; }
#+end_src
2020-12-27 16:36:25 +01:00
** Text formatting functions :noexport:
2020-12-27 15:46:11 +01:00
Function to print some text such that it fits on the screen
2020-12-27 17:38:04 +01:00
2020-12-27 16:36:25 +01:00
#+begin_src ocaml :tangle (eval ml) :exports none
2020-12-27 15:46:11 +01:00
let output_text t =
Format.printf "@[<v 0>";
begin
match Str.split (Str.regexp "\n") t with
| x :: [] ->
Format.printf "@[<hov 0>";
Str.split (Str.regexp " ") x
|> List.iter (fun y -> Format.printf "@[%s@]@ " y) ;
Format.printf "@]"
| t ->
2020-12-27 16:36:25 +01:00
List.iter (fun x ->
2020-12-27 15:46:11 +01:00
Format.printf "@[<hov 0>";
Str.split (Str.regexp " ") x
|> List.iter (fun y -> Format.printf "@[%s@]@ " y) ;
Format.printf "@]@;"
) t
end;
Format.printf "@]"
#+end_src
2020-12-27 16:36:25 +01:00
2020-12-27 15:46:11 +01:00
Function to build the short description of the command-line
arguments, such as
2020-12-27 17:38:04 +01:00
2021-01-28 00:34:26 +01:00
Example:
2020-12-27 15:46:11 +01:00
#+begin_example
my_program -b <string> [-h] [-u <float>] -x <string> [--]
#+end_example
2020-12-27 16:36:25 +01:00
#+begin_src ocaml :tangle (eval ml) :exports none
2020-12-27 15:46:11 +01:00
let output_short x =
match x.short, x.opt, x.arg with
| ' ', Mandatory, _ -> Format.printf "@[%s@]" x.long
| ' ', Optional , _ -> Format.printf "@[[%s]@]" x.long
| _ , Mandatory, Without_arg -> Format.printf "@[-%c@]" x.short
| _ , Optional , Without_arg -> Format.printf "@[[-%c]@]" x.short
| _ , Mandatory, With_arg arg -> Format.printf "@[-%c %s@]" x.short arg
| _ , Optional , With_arg arg -> Format.printf "@[[-%c %s]@]" x.short arg
| _ , Mandatory, With_opt_arg arg -> Format.printf "@[-%c [%s]@]" x.short arg
| _ , Optional , With_opt_arg arg -> Format.printf "@[[-%c [%s]]@]" x.short arg
#+end_src
2020-12-27 16:36:25 +01:00
2020-12-27 15:46:11 +01:00
Function to build the long description of the command-line
arguments, such as
2020-12-27 17:38:04 +01:00
2021-01-28 00:34:26 +01:00
Example:
2020-12-27 15:46:11 +01:00
#+begin_example
-x --xyz=<string> Name of the file containing the nuclear
2020-12-27 16:36:25 +01:00
coordinates in xyz format
2020-12-27 15:46:11 +01:00
#+end_example
2020-12-27 16:36:25 +01:00
#+begin_src ocaml :tangle (eval ml) :exports none
2020-12-27 15:46:11 +01:00
let output_long max_width x =
let arg =
match x.short, x.arg with
| ' ' , _ -> x.long
| _ , Without_arg -> x.long
| _ , With_arg arg -> Printf.sprintf "%s=%s" x.long arg
| _ , With_opt_arg arg -> Printf.sprintf "%s[=%s]" x.long arg
in
let long =
2020-12-27 17:38:04 +01:00
let l = String.length arg in
arg^(String.make (max_width-l) ' ')
2020-12-27 15:46:11 +01:00
in
Format.printf "@[<v 0>";
begin
match x.short with
| ' ' -> Format.printf "@[%s @]" long
| short -> Format.printf "@[-%c --%s @]" short long
end;
Format.printf "@]";
output_text x.doc
#+end_src
** Query functions
2020-12-27 16:36:25 +01:00
2020-12-27 23:08:12 +01:00
#+begin_src ocaml :tangle (eval mli)
val get : long_opt -> string option
val get_bool : long_opt -> bool
2020-12-27 15:46:11 +01:00
val anon_args : unit -> string list
2020-12-27 23:08:12 +01:00
#+end_src
2020-12-27 17:38:04 +01:00
2020-12-27 23:08:12 +01:00
| ~get~ | Returns the argument associated with a long option |
| ~get_bool~ | True if the ~Optional~ argument is present in the command-line |
| ~anon_args~ | Returns the list of anonymous arguments |
2020-12-27 15:46:11 +01:00
2020-12-27 23:08:12 +01:00
#+begin_src ocaml :tangle (eval ml) :exports none
let anon_args () = !anon_args_ref
2020-12-27 15:46:11 +01:00
let help () =
(* Print the header *)
output_text !header_doc;
Format.printf "@.@.";
(* Find the anonymous arguments *)
let anon =
List.filter (fun x -> x.short = ' ') !specs
in
(* Find the options *)
let options =
List.filter (fun x -> x.short <> ' ') !specs
|> List.sort (fun x y -> Char.compare x.short y.short)
in
(* Find column lengths *)
let max_width =
2020-12-27 16:36:25 +01:00
List.map (fun x ->
2020-12-27 15:46:11 +01:00
( match x.arg with
| Without_arg -> String.length x.long
| With_arg arg -> String.length x.long + String.length arg
| With_opt_arg arg -> String.length x.long + String.length arg + 2
)
+ ( if x.opt = Optional then 2 else 0)
) !specs
|> List.fold_left max 0
in
(* Print usage *)
Format.printf "@[<v>@[<v 2>Usage:@,@,@[<hov 4>@[%s@]" Sys.argv.(0);
List.iter (fun x -> Format.printf "@ "; output_short x) options;
Format.printf "@ @[[--]@]";
List.iter (fun x -> Format.printf "@ "; output_short x;) anon;
Format.printf "@]@,@]@,";
(* Print arguments and doc *)
Format.printf "@[<v 2>Arguments:@,";
Format.printf "@[<v 0>" ;
List.iter (fun x -> Format.printf "@ "; output_long max_width x) anon;
Format.printf "@]@,@]@,";
(* Print options and doc *)
Format.printf "@[<v 2>Options:@,";
Format.printf "@[<v 0>" ;
List.iter (fun x -> Format.printf "@ "; output_long max_width x) options;
Format.printf "@]@,@]@,";
(* Print footer *)
if !description_doc <> "" then
begin
Format.printf "@[<v 2>Description:@,@,";
output_text !description_doc;
Format.printf "@,"
end;
(* Print footer *)
output_text !footer_doc;
Format.printf "@."
2020-12-27 17:38:04 +01:00
2020-12-27 15:46:11 +01:00
let get x =
try Some (Hashtbl.find dict x)
with Not_found -> None
let get_bool x = Hashtbl.mem dict x
2020-12-27 23:08:12 +01:00
#+end_src
2020-12-27 16:36:25 +01:00
2020-12-27 15:46:11 +01:00
** Specification
2020-12-27 17:38:04 +01:00
#+begin_src ocaml :tangle (eval mli)
2020-12-27 15:46:11 +01:00
val set_specs : description list -> unit
2020-12-27 17:38:04 +01:00
#+end_src
2020-12-27 15:46:11 +01:00
2020-12-27 17:38:04 +01:00
Sets the specifications of the current program from a list of
~descrption~ variables.
#+begin_src ocaml :tangle (eval ml) :exports none
2020-12-27 15:46:11 +01:00
let set_specs specs_in =
specs := { short = 'h' ;
long = "help" ;
doc = "Prints the help message." ;
arg = Without_arg ;
opt = Optional ;
} :: specs_in;
let cmd_specs =
List.filter (fun x -> x.short != ' ') !specs
|> List.map (fun { short ; long ; arg ; _ } ->
match arg with
| With_arg _ ->
(short, long, None, Some (fun x -> Hashtbl.replace dict long x) )
| Without_arg ->
(short, long, Some (fun () -> Hashtbl.replace dict long ""), None)
| With_opt_arg _ ->
(short, long, Some (fun () -> Hashtbl.replace dict long ""),
Some (fun x -> Hashtbl.replace dict long x) )
)
in
Getopt.parse_cmdline cmd_specs (fun x -> anon_args_ref := !anon_args_ref @ [x]);
if (get_bool "help") then
(help () ; exit 0);
(* Check that all mandatory arguments are set *)
List.filter (fun x -> x.short <> ' ' && x.opt = Mandatory) !specs
2020-12-27 16:36:25 +01:00
|> List.iter (fun x ->
2020-12-27 15:46:11 +01:00
match get x.long with
| Some _ -> ()
| None -> failwith ("Error: --"^x.long^" option is missing.")
)
#+end_src