[%%org
  {|
#+TITLE: Enum

#+BEGIN_comment
|}]

(* Some setup and helper functions used later. These are hidden from the published doc. *)

open! Core
open Expect_test_helpers_core

let print_flag_help_display the_flag =
  (* Prints out what [the_flag]'s help text would look like.

     We construct a basic command, only to immediately break it apart again, because
     [Command.Shape] doesn't currently expose a way to obtain [Command.Shape.Flag_info.t]
     directly from a [Command.Param.t]. *)
  let basic_command =
    Command.basic
      ~summary:""
      (let%map_open.Command _ = the_flag in
       fun () -> assert false)
  in
  let basic_command_shape =
    match Command.Shape.fully_forced (Command_unix.shape basic_command) with
    | Basic basic_command_shape -> basic_command_shape
    | _ -> assert false
  in
  print_endline
    (Command.Shape.Flag_help_display.to_string
       (Command.Shape.Base_info.flags basic_command_shape))
;;

[%%org
  {|
#+END_comment

~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:
|}]

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 (param : Mode.t Command.Param.t) =
  Enum.make_param
    ~f:(fun flag -> Command.Param.optional_with_default Mode.Prod flag)
    "-mode"
    (module Mode)
    ~doc:"Running mode"
;;

let () = print_flag_help_display param

[%%expect
  {|
[-mode _]                  . Running mode (can be: dev, prod, something-else,
                             unit-test)
[-help], -?                . print this help text and exit
|}]

[%%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.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 same 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 (param : Mode.t Command.Param.t) =
  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")
;;

let () = print_flag_help_display param

[%%expect
  {|
[-dev]                     . run for development, not in prod
[-prod]                    . run in prod
[-something-else]          . something else
[-unit-test], -test        . run in testing
[-help], -?                . print this help text and exit
|}]

[%%org
  {|
~Enum.make_param_optional_comma_separated~ and
~Enum.make_param_optional_comma_separated_with_default_doc~ allow you to create
flags that take a comma-separated list:
|}]

let (param : Mode.t list option Command.Param.t) =
  Enum.make_param_optional_comma_separated "-mode" (module Mode) ~doc:"list of modes"
;;

let () = print_flag_help_display param

[%%expect
  {|
[-mode _]                  . list of modes (can be comma-separated values:
                             dev, prod, something-else, unit-test)
[-help], -?                . print this help text and exit
|}]

let (param : Mode.t list Command.Param.t) =
  Enum.make_param_optional_comma_separated_with_default_doc
    "-mode"
    (module Mode)
    ~doc:"list of modes"
    ~default:[ Mode.Prod; Mode.Dev ]
;;

let () = print_flag_help_display param

[%%expect
  {|
[-mode _]                  . list of modes (can be comma-separated values:
                             dev, prod, something-else, unit-test) (default:
                             prod,dev)
[-help], -?                . print this help text and exit
|}]

[%%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 ~iterators:to_list]

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

[%%org
  {|

* =Enum.command_friendly_name=

Most =Enum= operations rely on transforming the generated representation to a
string that can be used everywhere. This transformation is called the
=command_friendly_name=.

See the enum_intf.ml interface file for documentation about the semantics of
this function.
|}]

let () = Enum.command_friendly_name "Capitals_And_Underscores" |> print_endline

[%%expect
  {|
capitals-and-underscores
|}]
