[%%org
  {|
#+TITLE: Enum

~Enum~ is a single-module library intended to make it easier to work with
variant types that are enumerable, i.e., that are sexpable and that implement an ~all~
function. Currently the main use case is to auto-generate ~Command.Param~ command-line
parameters from these types.

* Quick start example

Suppose for example we had this simple variant type:
|}]

open! Core
open Expect_test_helpers_core

module Mode = struct
  type t =
    | Prod
    | Dev
    | Unit_test
    | Something_else
  [@@deriving enumerate, sexp_of]
end

[%%org
  {|
We can use ~Enum.to_string_hum~ to turn the variant into a string that is
~Command.Param~-friendly:
|}]

let () =
  List.iter Mode.all ~f:(fun mode ->
    print_endline (Enum.to_string_hum (module Mode) mode))
;;

[%%expect {|
prod
dev
unit-test
something-else
|}]

[%%org
  {|
Note that single quote (') characters are stripped from the string to prevent them from
needing to be escaped in the command line.

~Enum.make_param~ can then be used to build a command line parameter from our variant. It
even generates documentation that shows all the options to the user:
|}]

let main =
  Command.basic
    ~summary:"Run my server"
    (let%map_open.Command mode =
       Enum.make_param
         ~f:(fun flag -> Command.Param.optional_with_default Mode.Prod flag)
         "-mode"
         (module Mode)
         ~doc:"Running mode"
     in
     fun () -> print_s [%message "Running in mode" (mode : Mode.t)])
;;

let () =
  print_s
    [%sexp
      (Command.Shape.fully_forced (Command_unix.shape main)
       : Command.Shape.Fully_forced.t)]
;;

[%%expect
  {|
(Basic (
  (summary "Run my server")
  (anons (Grammar Zero))
  (flags (
    ((name "[-mode _]")
     (doc "Running mode (can be: dev, prod, something-else, unit-test)")
     (aliases ()))
    ((name [-help])
     (doc  "print this help text and exit")
     (aliases (-?)))))))
|}]

[%%org
  {|

* More detail

To be more precise, a type is "enumerable" if it exposes the following interface and
 sexp_of is an sexp atom:
|}]

module type S = sig
  type t [@@deriving sexp_of]

  val all : t list
end

[%%org {| This interface is known and exposed as ~Enum.S~: |}]

let f (module S : S) = (module S : Enum.S)

[%%org
  {|Our ~Mode~ module is enumerable because the ~enumerate~ ppx extension generates
an ~all~ function for us, and the ~sexp_of~ makes it sexpable. We can verify that indeed
it satisfies the interface:|}]

let assert_mode_is_enumerable () = (module Mode : Enum.S)

[%%org
  {|
If the order of the constructors does not matter in the definition of an enum type,
one may enforce the order definition to follow some canonical expectation.
|}]

module T = struct
  type t =
    | A
    | A_also
    | B
  [@@deriving compare, enumerate, sexp_of]
end

let () = Enum.assert_alphabetic_order_exn [%here] (module T)

[%%org
  {|
[Enum.doc] can also be used to create a more nicely-formatted documentation text.

If [represent\_choice\_with] is not passed, the documentation will be:

    {v
       -flag (choice1|choice2|...) [doc]
    v}

If there are many choices, this can cause this and other flags to have the
documentation aligned very far to the right. To avoid that, the
[represent\_choice\_with] flag can be passed as a shorter reference to the possible
choices. Example:

    {v
       -flag CHOICE [doc], CHOICE can be (choice1|choice2|...)
    v}

[Command] does a much better job of aligning this.

[Enum.Make\_stringable] converts [Enum.S] modules to [Stringable] modules.
|}]

module M = struct
  include T
  include Enum.Make_stringable (T)
end

let () =
  List.iter [%all: M.t] ~f:(fun m ->
    require_compare_equal [%here] (module M) m (M.of_string (M.to_string m));
    print_endline (M.to_string m))
;;

[%%expect {|
a
a-also
b
|}]

let () = show_raise (fun () -> M.of_string "foo")

[%%expect {|
(raised ("Unknown value." foo (known_values (a a-also b))))
|}]

[%%org
  {|
If ~all~ value includes duplicates, which might happen even using
~[@@deriving enumerate]~ in some cases with polymorphic variants,
the ~of_string~ function generated by ~Enum.Make_stringable~ will pick
one of the values mapping to the given string arbitrarily.

In theory, this could be bad if there are two non-equal values with
the string string representation (in which case the best behavior of
~Enum.Make_stringable~ would probably be to raise), but that's not
anticipated to be a problem in practice.
|}]

module T1 = struct
  type t =
    [ `A
    | `B
    | `C
    ]
  [@@deriving enumerate, sexp_of]
end

module T2 = struct
  type t =
    [ `C
    | `D
    ]
  [@@deriving enumerate, sexp_of]
end

module T3 = struct
  module T = struct
    type t =
      [ T1.t
      | T2.t
      ]
    [@@deriving enumerate, sexp_of]
  end

  include T
  include Enum.Make_stringable (T)
end

let () =
  Expect_test_helpers_core.require_does_not_raise ~cr:CR [%here] (fun () ->
    ignore (T3.of_string "a" : T3.t))
;;

[%%expect {| |}]

[%%org
  {|
~Enum.make_param_one_of_flags~ can be used to build a Command.Param.t very similar to
~Enum.make_flags~, except each enum becomes its own standalone flag, one of which must be passed.
|}]

let main =
  Command.basic
    ~summary:"Run my server"
    (let%map_open.Command mode =
       Enum.make_param_one_of_flags
         (module Mode)
         ~aliases:(function
           | Mode.Unit_test -> [ "test" ]
           | _ -> [])
         ~doc:(function
           | Mode.Prod -> "run in prod"
           | Dev -> "run for development, not in prod"
           | Unit_test -> "run in testing"
           | Something_else -> "something else")
     in
     fun () -> print_s [%message "Running in mode" (mode : Mode.t)])
;;

let () =
  print_s
    [%sexp
      (Command.Shape.fully_forced (Command_unix.shape main)
       : Command.Shape.Fully_forced.t)]
;;

[%%expect
  {|
(Basic (
  (summary "Run my server")
  (anons (Grammar Zero))
  (flags (
    ((name [-dev])
     (doc  "run for development, not in prod")
     (aliases ()))
    ((name [-prod])           (doc "run in prod")    (aliases ()))
    ((name [-something-else]) (doc "something else") (aliases ()))
    ((name [-unit-test])
     (doc  "run in testing")
     (aliases (-test)))
    ((name [-help])
     (doc  "print this help text and exit")
     (aliases (-?)))))))
|}]

[%%org
  {|

* =Enum.Single=

Some =Enum= operations do not need to enumerate the type, because they are given
a single value to use. =Enum.Single= exports these operations as functions on
modules deriving only =sexp_of=, without deriving =enumerate=.

For example, suppose our app takes key-value pairs on the command line to set
fields of a record:

#+begin_example
foo -set-field name=value
#+end_example

|}]

type t =
  { here's_an_int : int
  ; and_a_string : string
  ; plus_a_bool : bool
  }
[@@deriving fields]

let complete _ ~part =
  (* We might as well suggest the = that goes between a field and its value. *)
  let f field = Enum.Single.to_string_hum (module String) (Field.name field) ^ "=" in
  Fields.to_list ~here's_an_int:f ~and_a_string:f ~plus_a_bool:f
  |> List.filter ~f:(String.is_prefix ~prefix:part)
;;

let () = complete () ~part:"" |> List.iter ~f:print_endline

[%%expect {|
heres-an-int=
and-a-string=
plus-a-bool=
|}]

let () = complete () ~part:"h" |> List.iter ~f:print_endline

[%%expect {|
heres-an-int=
|}]
