aboutsummaryrefslogblamecommitdiff
path: root/README.md
blob: 760be2f4017a9d2fcee7ba4738dd2dcf2671407e (plain) (tree)
1
2
3
4
5
6
7
8
9
10
11
12
                                                                                                                            
 



                                                      





                                                                       
































































































































                                                                       
[![Build Status](https://github.com/jodersky/commando/workflows/CI/badge.svg)](https://github.com/jodersky/commando/actions)

# Commando

An opinionated command line parsing utility for Scala.

```scala
libraryDependencies += "io.crashbox" %% "commando" % "<latest_version>"
```

Commando is available for Scala, Scala JS and Scala Native.

## Concepts
Commando's API is designed around two main concepts: commands and
parameters. It is recommended to read this section before diving into
the Scala API.

### Commands
Commands represent an action and have side effects when
run. Well-known examples include `ls`, `cp`, `git`, etc.

On a single command line, only one command is ever invoked. However,
commands can be nested and share *parameters*. `git` for example, fits
into this model:

```
git -p clone https://github.com/jodersky/commando
```

In the above, the top-level command has a parameter `-p`, followed by
a "subcommand" `clone`, which itself has a url as parameter. Typically,
a parent command will have several child commands. As such, a command
can also be thought of representing a on-of-many choice parameter.

### Parameters
Parameters define what arguments a command may take. There are two
kinds of parameters: positional and optional.

#### Positional parameters
Positional parameters simply get substituted by values from left to
right.

E.g. assume a command `command` that expects two positional parameters
`x` followed by `y`. Invoking the command as follows: `command 1 2`
will substitute 1 for `x` and 2 for `y`.

#### Optional parameters
Optional parameters define arguments that may be passed to a command
in any order. They typically represent "flags" or extra key-value
information.

In order to avoid ambiguity with positional arguments, optionals must
follow certain requiremenets:

- Optionals start with `--` and may be positioned anywhere within the
  scope of a command (scope of a command are all words up to the next
  subcommand).
  
  E.g. in `command --foo <arg1> --bar subcommand --baz <arg2>` the
  optionals `--foo` and `--bar` refer to `command`, and `--baz` refers
  to `subcommand`.

- May have a single-character "short" alias, in which case they an be
  specified with a single dash. E.g. `--force` and `-f`.

- Are **always** optional (use a command in case you need a
  one-of-many choice).
  
- May be repeated. `command -v -v -v -v`

- May have embedded arguments after an equals sign. `command
  --publish=80:80`

- If specified, may allow or require an argument. `command --publish
  80:80 --publish 443:443`
  
- In short form, and if they do not have parameters, arguments may be
  collapsed into a single string starting with a single
  dash. E.g. `command -i -t -f` is equivalent to `command -itf`.

- A standalone double dash is an escape sequence. Any arguments
  following are treated as positionals. E.g. `ls --
  --a-directory-starting-with---`.
  
## Scala API
The API is focused around a recursive
[`Command`](src/main/scala/definitions.scala) object. This object
represents the "grammar" of a command line application, grouping
parameter definitions and subcommands.

It is passed to a command line *parser*, along with a sequence of
*arguments* and is typically called from an application's entry point.

Commands may be constructed directly, however it is recommended to use
the domain specific language that is provided in the [commando package
object](src/main/scala/package.scala).

For example, the following defines a subset of a docker-like command
line utility:

```scala
import commando._

object Main {

  val command = cmd("docker")(
    opt("debug", 'D')
  ).sub(
    cmd("run")(
      opt("interactive", 'i'),
      opt("tty", 't'),
      pos("container")
    ).run{ arguments =>
      // run container with arguments
      println("running container " + arguments("container").head)
    },
    cmd("ps")(
      opt("all", 'a')
    ).run{ arguments =>
      if (arguments.contains("all")) {
        // list all images
      } else {
        // list running containers
      }
    }
  )

  def main(args: Array[String]): Unit = commando.parse(args, command)

}
```

Assuming the application is packaged and executable as `docker`, it
may be invoked as follows:
```
$ docker run -it my_container
running container my_container
```
```
$ docker -D ps --all
```