Core.Command v. 0.10?

I learned to use Core's command-line processing module, Core.Command, from the very nice chapter on it in RWO1. The interface seems to have changed in Core v. 0.10.0, and now Core.Command.basic no longer accepts four arguments as before. However, I’m not yet sure what it expects. It looks like the command-line processing chapter in RWO2 hasn’t yet been updated (understandable, things take time) so that’s not helpful yet.

I’m puzzled by the change, because (what I think is the) documentation for v. 0.10.0 seems to put the new interface into a new function, basic', while basic seems to implement the same interface as before. Yet I’m getting an error from code using Command.basic that compiled previously:

Error: This function has type
         summary:string ->
         ?readme:(unit -> string) ->
         (unit -> unit) Command.Param.t -> Command.t
       It is applied to too many arguments; maybe you forgot a `;'.

(It’s not yet obvious to me how to map my existing code into the new interface. I can probably figure it out from the bare example at the top of the Core.Command ocamldoc, but I may downgrade to v. 0.09 for the moment.)

1 Like

Apologies. This is more confusing than it should be. The docs you’re pointing at are for the old (0.9) version. The 0.10 release isn’t fully out the door yet, and I’m not sure the documentation is as of yet up to date. I’ll look into that.

In the new (0.10) version, what were basic and basic’ have been switched to basic_spec and basic. So you can make your old code work as it was by changing Command.basic into Command.basic_spec.

The newer “param” API is the result of rewriting the command specification as an Applicative. The end result is quite a bit easier to think about, and works well with our ppx_let syntax. Here’s an example of what the new-style specifications look like, when using ppx_let:

module Sing = struct
  module Note = struct
    type t = A | B | C | D | E | F | G [@@deriving sexp]
    let of_string x = t_of_sexp (Sexp.Atom x)
    let arg_type = Command.Arg_type.create of_string
  end
  let command =
    Command.basic ~summary:"sing a song"
      (let open Command.Let_syntax in
       [%map_open
          (* flags *)
         let slow     = flag "slow" ~aliases:["AA";"-BB"] no_arg ~doc:" sing slow"
         and loudness = flag "-loudness" (optional int)
                          ~doc:"N how loud to sing (number of decibels)"
         and date     = flag "-date" (optional date) ~doc:"DATE the date"
         and notes    = flag "-note" (listed Note.arg_type) ~doc:"NOTE a note"
         (* anonymous arguments *)
         and song = anon ("NAME" %: string)
         and _foo = anon ("FOO" %: string)
         and _bar = anon (sequence ("BAR" %: string))
         in
         fun () ->
           (* command body *)
           print_endline (if slow then "slow" else "fast");
           printf "loudness = %s\n"
             (Option.value ~default:"none"
                (Option.map ~f:Int.to_string loudness));
           printf "date = %s\n"
             (Option.value ~default:"no date"
                (Option.map date ~f:Date.to_string));
           printf "song name = %s\n" song;
           List.iter notes ~f:(fun note ->
             print_endline
               (Sexp.to_string_hum (
                  Sexp.List [Sexp.Atom "note"; Note.sexp_of_t note])))
       ])
end
5 Likes

Thanks @Yaron_Minsky. That’s very helpful. Great–I can use basic_spec.

v0.10 is what opam defaults to now.

I’m not sure I understand every bit of the example, but it’s probably enough to get started. (I never fully understood the old basic in all respects either, actually, but I was able to follow the examples, so I didn’t care. :slight_smile: )