Skip to content
Micha Niskin edited this page Nov 28, 2014 · 131 revisions

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.

Example

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."
   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.
  -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.
    :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"}

Anatomy

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
   ...
  1. Short name – one-character, must be unique (h is reserved by boot)
  2. Long name – multi-character, must be unique (help is reserved by boot)
  3. Optarg – if provided, indicates that option expects argument (vs. flag)
  4. Type – the Clojure data type hint for the option value
  5. 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.

Optargs

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.

Types

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.

Examples

Rather than attempt a rigorous specification of the DSL we provide a number of examples to illustrate intuitivly how it is used.

Flags

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

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

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}

Multi Options

Options can also be sets or vectors. Multiple use of the option on the command line conjes 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}}

Complex Options

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}}
  1. 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.

  2. Conjing – Each use of the option on the command line conjes 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"]}}
Clone this wiki locally