Welcome! Please see the About page for a little more info on how this works.

0 votes
in Contrib libs by

Here's a pattern that pops up pretty often in my work:

(def cli
  [["-f" "--foo FOO"
    :default-fn (fn [_] (System/getenv "FOO"))
    :default-desc "$FOO"
    :validate [seq "You must either use --foo or set $FOO"]])

Unfortunately this doesn't work as expected. Even with :post-validation true, the default is not validated by the validator. Instead I have to write a separate manual validation function. I suspect this is because validators don't run unless the option was invoked.

Would it make sense to have :post-validation cause validators to be run on options with defaults? Or maybe an additional option makes sense, something like :validate-default?

1 Answer

0 votes

Since you control the default value, there's an inherent assumption that you should be checking it yourself -- since it is a programming error if the default is invalid, not a usage error.

It also acts as an escape hatch so you can provide a custom default that a user could not provide via the option which is needed sometimes.

Sure, I think that's a reasonable default, but why not give devs the option of having defaults validated if they want it? Options falling through to external values of some kind (environment variables, config files, etc.) is an extremely common pattern that tools.cli could probably support better, and this would be an easy way to do it.
I'll track it as https://clojure.atlassian.net/browse/TCLI-99 but it needs analysis before any decision is made. I'll argue this isn't as common as you claim based on no one else having requested this before.
I think it makes sense that this hasn't been requested before, in the past Clojure wasn't really  a viable tool for building CLI-based programs due to JVM startup times. Now that babashka is on the scene, I would guess that there'll be more requests to improve Clojure's CLI-building tools as the set of people building CLIs with Clojure grows. If you look at other ecosystems more commonly used to build CLIs (e.g. Python, Ruby, etc.) they have much more sophisticated CLI-building tools. For example, Python's argparse (in the standard library) handles positional arguments in addition to options and handles building the --help text for you. Python's click adds on to that tools for easily building "nested" CLIs in the style of the AWS CLI or leiningen. By comparison, Clojure's tools are rather bare-bones, which makes sense for the prior state of the ecosystem, but might not going forward.
I'm also stumped with this behaviour because I'm trying to use tools.cli for enforce mandatory options. As suggested on in the #clojure slack channel

(def cli-options
  [["-U" "--gitlab-uri URI" "Gitlab host URI" :parse-fn #(URI. %)]
   ["-t" "--gitlab-token TOKEN" "Gitlab token"
    :default ::absent
    :validate [(fn [x] (println "XXXXX:" x) (not= ::absent)) "Missing required option: --gitlab-token"]
   ["-h" "--help"]])

doesn't work since the :validate fn is simply never invoked. I would actually prefer some kind of :mandatory (:required is already taken) property to enforce that the user needs to set this option.
"required" in tools.cli means that an option requires a value after it, not that the option itself is required.

I am seeing several requests for enhancements that all fall into a post-processing category such as dealing with non-option arguments and dealing with mandatory option checking and so on. This is something that tools.cli does not have any "hooks" for right now but it seems like a nice area for enhancements, so I'll give it some thought.

Thanks for the input on this.