Reputation: 4441
Let's say I have the following code
let main a b c d e = Format.eprintf "%B %B %B %B %B@." a b c d e
let cmd =
let open Cmdliner in
let a = Arg.(value & flag & info ["a"] ~doc:"a") in
let b = Arg.(value & flag & info ["b"] ~doc:"b") in
let c = Arg.(value & flag & info ["c"] ~doc:"c") in
let d = Arg.(value & flag & info ["d"] ~doc:"d") in
let e = Arg.(value & flag & info ["e"] ~doc:"e") in
Term.(const main $ a $ b $ c $ d $ e), Term.(info "test" ~version:"1" ~doc:"abcde" ~exits:default_exits ~man:[])
let () = Cmdliner.Term.(exit @@ eval cmd)
If I execute my program with no option I will obtain false false false false false
and if I use it with -ade
I will obtain true false false true true
which is exactly what I wanted.
Now, suppose I made a typo in my main
function and wrote instead
(* Notice the d before c *)
let main a b d c e = Format.eprintf "%B %B %B %B %B@." a b c d e
If I execute my main program with -ade
like previously I will obtain true false true false true
which can be considered wrong.
So, what I wanted to know is if it was possible to gather options in a record to use them with their proper names, something like the following example (which doesn't work) :
open Cmdliner
type o = {a : bool Term.t;
b : bool Term.t;
c : bool Term.t;
d : bool Term.t;
e : bool Term.t;}
(* a - e are not booleans but bool Term.t which gives an obvious error *)
let main {a; b; c; d; e} = Format.eprintf "%B %B %B %B %B@." a b c d e
let cmd =
let a = Arg.(value & flag & info ["a"] ~doc:"a") in
let b = Arg.(value & flag & info ["b"] ~doc:"b") in
let c = Arg.(value & flag & info ["c"] ~doc:"c") in
let d = Arg.(value & flag & info ["d"] ~doc:"d") in
let e = Arg.(value & flag & info ["e"] ~doc:"e") in
let o = Term.const {a; b; c; d; e} in
Term.(const main $ o), Term.(info "test" ~version:"1" ~doc:"abcde" ~exits:default_exits ~man:[])
let () = Cmdliner.Term.(exit @@ eval cmd)
This could be useful on big projects and would lighten the number of arguments given to the functions. Maybe there's a way to do it but all the examples I found used the first way of doing. I didn't want to open an issue on the github page so I asked it here.
Upvotes: 1
Views: 143
Reputation: 56
This can be achieved with relatively few boilerplate by using labels to emulate a record with Term.t
fields, for instance:
type arg = {a : bool; b : bool; c : bool; d : bool; e : bool}
let main {a; b; c; d; e} = Format.printf "%B %B %B %B %B@." a b c d e
let cmd =
let open Cmdliner in
let arg ~a ~b ~c ~d ~e =
Term.(const (fun a b c d e -> {a; b; c; d; e}) $ a $ b $ c $ d $ e)
in
let a = Arg.(value & flag & info ["a"] ~doc:"a") in
let b = Arg.(value & flag & info ["b"] ~doc:"b") in
let c = Arg.(value & flag & info ["c"] ~doc:"c") in
let d = Arg.(value & flag & info ["d"] ~doc:"d") in
let e = Arg.(value & flag & info ["e"] ~doc:"e") in
Term.
( const main $ arg ~a ~b ~c ~d ~e
, info "test" ~version:"1" ~doc:"abcde" ~exits:default_exits ~man:[] )
let () = Cmdliner.Term.(exit @@ eval cmd)
By using the same name for the keyword arguments and the record fields, the risk of typos is limited to the conversion function (arg
here), which is presumably much simpler than your real main
function. In a large project, the conversion function could easily be generated automatically using a ppx.
Upvotes: 2
Reputation: 18892
This can be done quite directly if you write the field update functions for the record type. For instance, if we have
type arg = { a:bool; b:bool; c:bool; d:bool; e: bool }
let main {a;b;c;d;e} = Format.eprintf "%B %B %B %B %B@." a b c d e
module Update = struct
let a a r = { r with a }
let b b r = { r with b }
let c c r = { r with c }
let d d r = { r with d }
let e e r = { r with e }
end
The only missing step is to transform Cmdliner.Term.t
that directly provides the argument into terms that update a record of type arg. An implementation would be:
let cmd =
let open Cmdliner in
(* first the starting record *)
let start = Term.const { a = false; b=false; c=false; d=false; e=false } in
let transform r (update,arg) =
Term.( const update $ arg $ r ) in
let arg =
List.fold_left transform
start
Update.[
a, Arg.(value & flag & info ["a"] ~doc:"a");
b, Arg.(value & flag & info ["b"] ~doc:"b");
c, Arg.(value & flag & info ["c"] ~doc:"c");
d, Arg.(value & flag & info ["d"] ~doc:"d");
e, Arg.(value & flag & info ["e"] ~doc:"e");
] in
Term.(const main $ arg),
Term.info "test"
~version:"1"
~doc:"abcde"
~exits:Term.default_exits
~man:[]
let () = Cmdliner.Term.(exit @@ eval cmd)
Upvotes: 2