(* * Copyright (c) 2013-2020 Thomas Gazagnaire * Copyright (c) 2013-2020 Anil Madhavapeddy * Copyright (c) 2015-2020 Gabriel Radanne * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. *) open Cmdliner module Arg = struct (** {1 Arguments} *) let info = Arg.info type 'a kind = | Opt : 'a * 'a Arg.conv -> 'a kind | Opt_all : 'a Arg.conv -> 'a list kind | Required : 'a Arg.conv -> 'a option kind | Flag : bool kind let pp_conv c = Arg.conv_printer c let pp_kind : type a. a kind -> a Fmt.t = function | Opt (_, c) -> pp_conv c | Opt_all c -> pp_conv (Arg.list c) | Required c -> pp_conv (Arg.some c) | Flag -> Fmt.bool let compare_kind : type a b. a kind -> b kind -> int = fun a b -> let default cx x = Fmt.to_to_string (Arg.conv_printer cx) x in match (a, b) with | Opt (x, cx), Opt (y, cy) -> String.compare (default cx x) (default cy y) | Required _, Required _ -> 0 | Opt_all _, Opt_all _ -> 0 | Flag, Flag -> 0 | Opt _, _ -> 1 | _, Opt _ -> -1 | Required _, _ -> 1 | _, Required _ -> -1 | Opt_all _, _ -> 1 | _, Opt_all _ -> -1 type 'a t = { info : Arg.info; kind : 'a kind } let pp t = pp_kind t.kind let equal x y = compare_kind x.kind y.kind = 0 let opt conv default info = { info; kind = Opt (default, conv) } let flag info = { info; kind = Flag } let required conv info = { info; kind = Required conv } let opt_all conv info = { info; kind = Opt_all conv } let default (type a) (t : a t) = match t.kind with | Opt (d, _) -> d | Flag -> (false : bool) | Required _ -> (None : _ option) | Opt_all _ -> ([] : _ list) let make_opt_cmdliner wrap i default desc = Arg.(wrap @@ opt (some' ?none:default desc) None i) let make_opt_all_cmdliner wrap i desc = Arg.(wrap @@ opt_all desc [] i) (* Wrap terms into an ['a option] to distinguish between default and absent values. *) let to_cmdliner (type a) (t : a t) : a option Cmdliner.Term.t = let i = t.info in match t.kind with | Flag -> Arg.(value & vflag None [ (Some true, i) ]) | Opt (default, desc) -> make_opt_cmdliner Arg.value i (Some default) desc | Required desc -> make_opt_cmdliner Arg.required i None (Arg.some (Arg.some desc)) | Opt_all desc -> let list_to_option = function | [] -> None | _ :: _ as lst -> Some lst in let wrap arg = Term.(const list_to_option $ Arg.value arg) in make_opt_all_cmdliner wrap i desc end type 'a key = { name : string; arg : 'a Arg.t; key : 'a Context.key } type t = Any : 'a key -> t let equal (Any x) (Any y) = String.equal x.name y.name && Arg.equal x.arg y.arg (* Set of keys, without runtime name conflicts. This is useful to create a valid cmdliner term. *) module Names = Stdlib.Set.Make (struct type nonrec t = t let compare (Any x) (Any y) = String.compare x.name y.name end) (* Set of keys, where keys with the same name but with different defaults are distinguished. This is useful to build the graph of devices. *) module Set = struct module M = struct type nonrec t = t let compare = compare end include Set.Make (M) let add k set = if mem k set then if k != find k set then match k with Any k -> Fmt.invalid_arg "Duplicate key name: %s" k.name else set else add k set let pp_gen = Fmt.iter ~sep:(Fmt.any ",@ ") iter let pp_elt fmt (Any k) = Fmt.string fmt k.name let pp = pp_gen pp_elt end let v x = Any x let name (Any k) = k.name (* Key Map *) type context = Context.t let add_to_context t = Context.add t.key let find (type a) ctx (t : a key) : a option = Context.find t.key ctx let get ctx t = match find ctx t with Some x -> x | None -> Arg.default t.arg let mem_u ctx t = Context.mem t.key ctx (* {2 Values} *) type +'a value = { deps : Set.t; v : context -> 'a } let eval p v = v.v p let pure x = { deps = Set.empty; v = (fun _ -> x) } let app f x = { deps = Set.union f.deps x.deps; v = (fun p -> (eval p f) (eval p x)) } let map f x = app (pure f) x let pipe x f = map f x let if_ c t e = pipe c @@ fun b -> if b then t else e let match_ v f = map f v let ( $ ) = app let value k = let v c = get c k in { deps = Set.singleton (Any k); v } let of_deps deps = { (pure ()) with deps } let deps k = k.deps let mem p v = Set.for_all (function Any x -> mem_u p x) v.deps let peek p v = if mem p v then Some (eval p v) else None let default v = eval Context.empty v (* {2 Pretty printing} *) let pp = Set.pp_elt let pp_deps fmt v = Set.pp fmt v.deps let pps p ppf l = let pp' fmt k v = let default = if mem_u p k then Fmt.nop else Fmt.any " (default)" in Fmt.pf fmt "%a=%a%a" Fmt.(styled `Bold string) k.name (Arg.pp k.arg) v default () in let f fmt (Any k) = match (k.arg.Arg.kind, get p k) with | Arg.Required _, None -> Fmt.(styled `Bold string) fmt k.name | Arg.Opt _, v -> pp' fmt k v | Arg.Required _, v -> pp' fmt k v | Arg.Flag, v -> pp' fmt k v | Arg.Opt_all _, v -> pp' fmt k v (* Warning 4 and GADT don't interact well. *) in let pp = Fmt.vbox @@ fun ppf s -> Set.(pp_gen f ppf @@ s) in pp ppf l (* {2 Key creation} *) (* Unexposed smart constructor. *) let make ~arg ~name = let key = Context.new_key name in { arg; name; key } let create name arg = if name = "" then invalid_arg "Key.create: key name cannot be the empty string"; make ~arg ~name (* {2 Cmdliner interface} *) let context l = let names = Names.of_list (Set.elements l) in let gather (Any k) rest = let f v p = match v with None -> p | Some v -> Context.add k.key v p in let key = Arg.to_cmdliner k.arg in match k.arg.Arg.kind with | Arg.Opt _ -> Cmdliner.Term.(const f $ key $ rest) | Arg.Required _ -> Cmdliner.Term.(const f $ key $ rest) | Arg.Flag -> Cmdliner.Term.(const f $ key $ rest) | Arg.Opt_all _ -> Cmdliner.Term.(const f $ key $ rest) in Names.fold gather names (Cmdliner.Term.const Context.empty)