[%%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.

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

open! Core

open Expect_test_helpers_core

open Expect_test_helpers_async

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
  {|
~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.shape main) : Command.Shape.Fully_forced.t)]
;;

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

[%%org
  {|
To be more precise, a type is "enumerable" if it exposes the following interface:
|}]

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.
|}]

let () =
  Enum.doc
    (module T)
    ~represent_choice_with:"T"
    ~doc:"doc with custom [represent_choice_with]"
  |> print_endline
;;

[%%expect {| T doc with custom [represent_choice_with], T can be (a|a-also|b) |}]

[%%org
  {|
  [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 {| |}]
