On Thu 15 May 2014 at 10:45:52AM -0700, Bob Larrick wrote:
>
> A single element in the cli-options can be as brief as
>
> ["-p" "--port" "A port number"]
>
> What is non-obvious is that specifying
>
> "--port PORT"
>
> has entirely different semantics than specifying
>
> "--port"

Like I mentioned on the issue page, this is just a shortcut around
passing a `:required` entry with the same information.

The motivation behind this is that the resulting option summary mirrors
the option vectors:

  [["-p" "--port PORT" "A port number" :parse-fn #(Integer/parseInt %)]
   ["-q" "--quiet" "Shhh"]]

  -p, --port PORT  A port number
  -q, --quiet      Shhh

It is easy to tell at a glance that --port requires an option argument,
while --quiet does not.

If you dislike this syntax (which I borrowed from Ruby), you may replace
it with:

  ["-p" "--port" "A port number"
   :required "PORT"
   :parse-fn #(Integer/parseInt %)]

> In the first case the command "lein run -p 3000" will result in an options
> map of {:options {:port 3000}}.

> Can you guess what the same command would produce given the second case?

I would expect that an option flag that has not been marked as requiring
an argument to be a boolean toggle. This is the convention in C's
getopt(1), bash's getopts builtin, Perl's GetOpt::Long, and Ruby's
OptionParser.

> If you're like me, you wouldn't guess {:options {:port true}}. :-/
> It is worth noting that in earlier versions of this library "--port" did
> result in {:options {:port 3000}}, so this is a change in behavior from
> previous versions.

Yes, this is a change. The old tools.cli/cli still exists for people who
do not want to bother with learning a new API (I can sympathize). It has
also been upgraded to use the new option tokenizer.

> The current behavior is neither simple nor easy to understand, and
> complects the behavior of the flag with the definition of the long option.

Again, this is optional sugar. The option vectors are actually compiled
to maps:

    (#'clojure.tools.cli/compile-option-specs
      [["-p" "--port PORT" "A port number"]
       [nil  "--host" :required "HOST"]])

    ->

    ({:id :port
      :short-opt "-p"
      :long-opt "--port"
      :required "PORT"
      :desc "A port number"}
     {:id :host
      :short-opt nil
      :long-opt "--host"
      :required "HOST"
      :desc nil})

You may, if you like, supply the underlying maps instead of the option
vectors to parse-opts. The option specification is documented here:

https://github.com/clojure/tools.cli/blob/master/src/main/clojure/clojure/tools/cli.clj#L265-L309

> I suggest that this implicit behavior be removed and that all options be
> treated as required unless ":required false" is explicitly declared.
>
> Or perhaps there should be :boolean flag similar to :parse-fn or :default,
> since :required feels a little overloaded.
> That way given "lein run -p 3000", ["-p" "--port" "A port number"] would
> result in {:options {:port 3000}} and
> ["-p" "--port" "A port number" :boolean true] would result in {:options
> {:port true}}.
>
> That would make when a flag will be treated as a boolean explicit
> and obvious, and make this library a little less frustrating and
> foot-gunish.

You are suggesting that "options require arguments by default" is less
implicit than "options are boolean toggles by default". This is unclear
to me.

As to which default is preferable and less surprising, I prefer the
latter since it matches the conventions in C, bash, Perl, and Ruby. My
understanding is that the former is the default on Python. Perhaps this
is the source of our disagreement?

I do agree that the documentation is not clear enough on this matter; I
will ameliorate this later today.

    guns

Attachment: pgp60HSRLzVmo.pgp
Description: PGP signature

Reply via email to