-
-
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 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 An entrypoint symbol.
-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: Tasks take only keyword arguments / command line options. They do not take positional parameters.
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) -
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
- 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.
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.
Option type declarations provide a concise language specifying how command line arguments are parsed. They also serve as templates for automatic docu-mentation and validation of task options.
type hint | parse fn | validate fn |
---|---|---|
int | read-string | integer? |
float | read-string | float? |
str | identity | string? |
kw | keyword | keyword? |
sym | symbol | symbol? |
char | first | char? |
bool | identity | #{true false} |
edn | read-string | (constantly true) |
code | (comp eval read-string) | (constantly true) |
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.
Rather than attempt a rigorous specification of the DSL we provide a number of
examples to illustrate intuitivly 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"]}}
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