mte/unikernel/duniverse/mirage/lib/functoria/key.ml
2025-11-11 02:07:51 +01:00

218 lines
6.6 KiB
OCaml

(*
* Copyright (c) 2013-2020 Thomas Gazagnaire <thomas@gazagnaire.org>
* Copyright (c) 2013-2020 Anil Madhavapeddy <anil@recoil.org>
* Copyright (c) 2015-2020 Gabriel Radanne <drupyog@zoho.com>
*
* 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)