tools.cli and required arguments

931 views
Skip to first unread message

Alf Kristian Støyle

unread,
Jan 22, 2014, 7:48:22 AM1/22/14
to clo...@googlegroups.com
Hi all!

Trying to use clojure.tools.cli to help us parse command line arguments. However we are having trouble understanding how to specify required arguments.

The following example taken from the documentation (https://github.com/clojure/tools.cli#quick-start) should give a required argument (but we have commented out default value). However errors is nil:

(def cli-options
  ;; An option with a required argument
  [["-p" "--port PORT" "Port number"
    ; :default 80
    :parse-fn #(Integer/parseInt %)
    :validate [#(< 0 % 0x10000) "Must be a number between 0 and 65536"]]
   ;; A non-idempotent option
   ["-v" nil "Verbosity level"
    :id :verbosity
    :default 0
    :assoc-fn (fn [m k _] (update-in m [k] inc))]
   ;; A boolean option defaulting to nil
   ["-h" "--help"]])
;=> (var user/cli-options)

(:errors (parse-opts [] cli-options))
;=> nil

Actually giving it a value will give an error though:
(:errors (parse-opts ["--port" ""] cli-options))
;=> ["Error while parsing option \"--port \": java.lang.NumberFormatException: For input string: \"\""]

From the documentation it seems that specifying the second part of the "long option" should be enough to make an argument required. So this minimal example should also produce errors:

(def cli-options-simplified
  [["-p" "--port PORT" "Port number"]])
;=> (var user/cli-options-simplified)

(:errors (parse-opts [] cli-options-simplified))
;=> nil

So what are we misunderstanding? Any pointers would be greatly appreciated.

Using Clojure 1.5.1 with tools.cli 0.3.1.

Cheers,
Alf


guns

unread,
Jan 22, 2014, 8:04:08 AM1/22/14
to clo...@googlegroups.com
On Wed 22 Jan 2014 at 01:48:22PM +0100, Alf Kristian Støyle wrote:

> From the documentation it seems that specifying the second part of the
> "long option" should be enough to make an argument required. So this
> minimal example should also produce errors:
>
> (def cli-options-simplified
> [["-p" "--port PORT" "Port number"]])
> ;=> (var user/cli-options-simplified)
>
> (:errors (parse-opts [] cli-options-simplified))
> ;=> nil
>
> So what are we misunderstanding? Any pointers would be greatly appreciated.
>
> Using Clojure 1.5.1 with tools.cli 0.3.1.

Hi Alf,

An option with a required argument (via "--port PORT" or the :required
entry) only specifies that the option must be followed by an argument
_if_ it is specified on the command line. For example,

$ cmd --port

is an error, but simply

$ cmd

is not.

If you would like require that --port must be specified in every
invocation, you can assert its existence/value in the options map:

(let [{:keys [options errors …]} (parse-opts argv cli-options)]

(assert (:port options) "`--port` must be specified")
…)

You can, of course, set a :default value for a required option.

HTH
guns

Alf Kristian Støyle

unread,
Jan 22, 2014, 8:21:51 AM1/22/14
to guns, clo...@googlegroups.com
Thanks! That does explain it :)

Would be nice to be able to specify that an option "must be specified in every invocation" though. I think it would lead to better error messages, e.g. if several "mandatory" options are forgotten, they will be shown at once. That is a bit of a hassle when doing it yourself.

Haven't looked too hard at the source though, but any chance of contributing a patch to something like that?

E.g.

[["-p" "--port PORT" "Port number"
  :mandatory true]]

Cheers,
Alf


Ray Miller

unread,
Jan 22, 2014, 8:52:16 AM1/22/14
to clo...@googlegroups.com
On 22 January 2014 13:21, Alf Kristian Støyle <alf.kr...@gmail.com> wrote:
> Thanks! That does explain it :)
>
> Would be nice to be able to specify that an option "must be specified in
> every invocation" though. I think it would lead to better error messages,
> e.g. if several "mandatory" options are forgotten, they will be shown at
> once. That is a bit of a hassle when doing it yourself.

Take a look at Cliopatra; they have added a :required flag to the
option specification which does just what you want. I've had great
success using Cliopatra to build Clojure command-line tools.

https://github.com/runa-dev/cliopatra

Ray.

guns

unread,
Jan 22, 2014, 10:04:53 AM1/22/14
to clo...@googlegroups.com
On Wed 22 Jan 2014 at 02:21:51PM +0100, Alf Kristian Støyle wrote:

> Would be nice to be able to specify that an option "must be specified
> in every invocation" though. I think it would lead to better error
> messages, e.g. if several "mandatory" options are forgotten, they will
> be shown at once. That is a bit of a hassle when doing it yourself.
>
> Haven't looked too hard at the source though, but any chance of
> contributing a patch to something like that?
>
> E.g.
> [["-p" "--port PORT" "Port number"
> :mandatory true]]

I think the nicest thing about the original design of tools.cli is that
it is conceptually a transformation of a default map of values into
another map of values. Kind of like:

(reduce option-rules default-map ARGV) ; => new-map

From this POV, it makes more sense to me to assert the shape and value
of `new-map` than to validate the membership of `ARGV`. I hope this
makes sense.

That said, there is definitely plenty of room for a more convenient
framework on top of tools.cli. The recent reboot of the library was
primarily aimed at increasing the flexibility of the argument tokenizer
(e.g. supporting GNU option conventions) and that of summary generation.

I purposefully made no attempts at adding anything fancier than that
since the API change was already jarring.

Also, in my experience with CLI libraries in other languages,
programmers simply cannot agree on a single way to structure command
line programs. Therefore, it my hope that `parse-opts` can at least
serve as a common base between such libraries and for Clojurists
interested in rolling their own.¹

If a clear favorite does emerge in the future, then that will be a great
candidate for inclusion in tools.cli before an eventual 1.0 release.

Cheers,
guns

¹ What is unfortunate is that many libraries ship with their own
ad-hoc parsers that use regular expressions or simple word matching.
`parse-opts` tokenizes input before parsing so things like clumped
options (`-abc`) are properly handled.

Alf Kristian Støyle

unread,
Jan 22, 2014, 10:11:54 AM1/22/14
to guns, clo...@googlegroups.com
Ok, thanks for the reply guys :)

Cheers,
Alf
Reply all
Reply to author
Forward
0 new messages