-
-
Notifications
You must be signed in to change notification settings - Fork 180
Task Options DSL
Boot tasks are intended to be used from the command line, from a REPL, in the
project's build.boot
file, or from regular Clojure namespaces. This requires
support for two conceptually different calling conventions: with command line
options and optargs, and as s-expressions in Clojure with argument values.
Furthermore, since tasks are boot's user interface it's important that they provide good usage and help documentation in both environments.
To get an idea of how this correspondence works consider the following simple example task (you can start a boot REPL and type it in):
(deftask options
"Demonstrate the task options DSL."
[a a-option VAL kw "The option."
c counter int "The counter."
e entry VAL sym "An entrypoint symbol."
f flag bool "Enable flag."
o o-option VAL str "The other option."]
*opts*)
boot.user=> (options "-h")
Demonstrate the task options DSL.
Options:
-h, --help Print this help info.
-a, --a-option VAL Set the option to VAL.
-c, --counter Increase the counter.
-e, --entry VAL Set an entrypoint symbol to VAL.
-f, --flag Enable flag.
-o, --o-option VAL Set the other option to VAL.
nil
boot.user=> (doc options)
-------------------------
boot.user/options
([& {:keys [help a-option counter flag o-option], :as *opts*}])
Demonstrate the task options DSL.
Keyword Args:
:help bool Print this help info.
:a-option kw The option.
:counter int The counter.
:entry sym An entrypoint symbol.
:flag bool Enable flag.
:o-option str The other option.
nil
boot.user=> (options "-a" "foo")
{:a-option :foo}
boot.user=> (options "--a-option" "foo")
{:a-option :foo}
boot.user=> (options :a-option :foo)
{:a-option :foo}
boot.user=> (options "-ccfa" "foo" "-o" "bar")
{:counter 2, :flag true, :a-option :foo, :o-option "bar"}
boot.user=> (options :counter 2 :flag true :a-option :foo :o-option "bar")
{:counter 2, :flag true, :a-option :foo, :o-option "bar"}
Boot tasks are defined with the deftask
macro. This macro defines functions
that can be called with either keyword arguments or command-line arguments as
strings. Boot exploits this property of tasks when pipelines are created on
the command line.
Note: Prior to version 2.7.0 tasks only took keyword arguments / command line options. The support for positional parameters is rudimentary and has a few gotchas (see below). In general, prefer using keyword arguments.
As with defn
, arguments in deftask
are declared in an arglist vector. The
arglist for deftask
is not, however, a simple vector of binding forms; it is
more similar to the specification passed to tools.cli. Each argument is
represented by a number of symbols and a string:
(deftask foo
"Does foo."
[f foo FOO str "The foo option."
↑ ↑ ↑ ↑ ↑
1 2 3 4 5
...
-
Short name – one-character, must be unique (
h
is reserved by boot)
- Use
_
for options with no short name
-
Long name – multi-character, must be unique (
help
is reserved by boot) - Optarg – if provided, indicates that option expects argument (vs. flag)
- Type – the Clojure data type hint for the option value (see below)
- Description – incorporated into command line help output and docstring
Options values are bound in the deftask
body to:
-
The long option name – (eg.
foo
in the example above) - The *opts* catchall – a map of options to values.
Additionally, a function that will print the task usage help info is bound to
*usage*
in the task body. This can be used by the task like this:
(deftask foo
[b bar VALUE int "The bar option."]
(if-not bar
(do (boot.util/fail "The -b/--bar option is required!") (*usage*))
...
Tasks can also take positional parameters. These cannot be declared in
the argument vector and they will all be collected into a sequence
bound to *args*
.
(deftask print-args []
(prn *args*)
identity)
(boot (print-args "1" "2" "3"))
;; ("1" "2" "3")
Passing non-string parameters is possible, but currently (2017-02-08)
requires a bit of a hack. The string --
is used to signal the end of
the keyword arguments to the parser.
(boot (print-args "--" 1 2 3))
;; (1 2 3)
You can also pass positional arguments from the command line. Note
that all values will be strings and there's some extra syntax needed.
You must wrap the entire task in []
so boot doesn't interpret the
positional args as a list of task names.
$ boot [ print-args 1 2 3 ]
;; ("1" "2" "3")
One common usage of positional arguments is passing arguments through
to something else, like the -main
function of a namespace. If these
arguments contain things that look like boot flags (i.e. they start
with a -
), you need to use the --
separator so boot doesn't throw
an error when it encounters an unrecognized flag.
$ boot [ print-args -- --pass-through=args 2 3 -f ]
;; ("--pass-through=args" "2" "3" "-f")
The optarg is an optional placeholder that indicates that the associated option takes a required argument. This is in contrast with flag or counter type options that take no arguments.
The foo
above is a simple example of an option with a required argument.
For more complex applications the DSL provides a mechanism by which additional
structure can be encoded in the optarg.
Note that the optarg is a pattern only. The value of an option, as mentioned in the preceding section, is bound to the long option name. The more elaborate forms of the pattern are discussed in the section on Complex Options.
Option type declarations provide a concise language specifying how command line arguments are parsed. They also serve as templates for automatic documentation and validation of task options.
type hint | parse fn | validate fn |
---|---|---|
bool | identity | #{true false} |
char | first | char? |
code | (comp eval read-string) | (constantly true) |
edn | read-string | (constantly true) |
file | clojure.java.io/file | #(instance? File %) |
float | read-string | float? |
int | read-string | integer? |
kw | keyword | keyword? |
regex | re-pattern | #(instance? Pattern %) |
str | identity | string? |
sym | symbol | symbol? |
Options can also be declared as collections of these primitive types. Sets, maps, and vectors are supported. There is an obvious limit to the complexity of option types on the command line; only the most useful and straightforward patterns are supported.
In some cases you may want apply validation and parsing only from command line. To achieve this simply place ^:!
as metadata right before the type expression. This is similar in power to using edn
or code
, but can give a better CLI experience.
Rather than attempt a rigorous specification of the DSL we provide a number of
examples to illustrate intuitively how it is used. The example deftask
s should
be understood to return *opts*
.
Flags are boolean options. They take no arguments–their presence or absence is enough to determine their value.
Note: no optarg is specified because this option takes no argument.
(deftask foo
"Does foo."
[f foo bool "Enable foo behavior."
...
boot.user=> (foo "-f")
{:foo true}
Counters are options which, like flags, take no arguments. Their value is the number of times the option was given on the command line.
Note: no optarg is specified because this option takes no argument.
(deftask foo
"Does foo."
[f foo int "The number of foos."
...
boot.user=> (foo "-fff")
{:foo 3}
Simple options have a single, required argument.
(deftask foo
"Does foo."
[f foo LEVEL int "The initial foo level."
...
boot.user=> (foo "-f" "100")
{:foo 100}
Options can also be sets or vectors. Multiple use of the option on the command
line conj
es items onto the set or vector.
(deftask foo
"Does foo."
[f foo LEVELS #{int} "The set of initial foo levels."
...
boot.user=> (foo "-f" "100" "-f" "200" "-f" "300")
{:foo #{100, 200, 300}}
Option values can also be collections of collections. They can be maps, sets of vectors, or vectors of vectors.
(deftask foo
"Does foo."
[f foo FOO=BAR {kw sym} "The foo option."
↑ ↑
1 2
...
boot.user=> (foo "-f" "bar=baz")
{:foo {:bar baz}}
boot.user=> (foo "-f" "bar=baz" "-f" "baf=quux")
{:foo {:bar baz :baf quux}}
-
Splitting – This shows "splitting" of the optarg on the
=
character. In fact, any non alpha-numeric character in the optarg placeholder is interpreted as a character to split on. The splitting produces a vector. -
Conjing – Each use of the option on the command line
conj
es the split value vector onto the collection map.
A set of vector triples:
(deftask foo
"Does foo."
[f foo FOO=BAR:BAZ #{[kw sym str]} "The foo option."
...
boot.user=> (foo "-f" "bar=baz:baf")
{:foo #{[:bar baz "baf"]}}
boot.user=> (foo "-f" "bar=baz:baf" "-f" "baf=quux:xyzzy")
{:foo #{[:bar baz "baf"] [:baf quux "xyzzy"]}}
Sequential, not nested optarg types are supported as well.
(deftask foo
"Does foo."
[f foo FOO=BAR:BAZ [sym str str] "The foo option." ;; not nested in a map anymore
...
boot.user=> (foo "-f" "bar=baz:baf")
{:foo [:bar baz "baf"]}
Note that in boot
< 2.8.0
sequential types fail with:
java.lang.ClassCastException: clojure.lang.PersistentVector cannot be cast to java.lang.String
You can find other developers and users in the #hoplon
channel on freenode IRC or the boot slack channel.
If you have questions or need help, please visit the Discourse site.
- Environments
- Boot environment
- Java environment
- Tasks
- Built-ins
- Third-party
- Tasks Options
- Filesets
- Target Directory
- Pods
- Boot Exceptions
- Configuring Boot
- Updating Boot
- Setting Clojure version
- JVM Options
- S3 Repositories
- Scripts
- Task Writer's Guide
- Require inside Tasks
- Boot for Leiningen Users
- Boot in Leiningen Projects
- Repl reloading
- Repository Credentials and Deploying
- Snippets
- Troubleshooting
- FAQ
- API docs
- Core
- Pod
- Util