From 9ed3bd0c35674096fc43fe46cfffd1c7aec1a34e Mon Sep 17 00:00:00 2001 From: Jakob Odersky Date: Sun, 29 Apr 2018 19:48:10 -0700 Subject: Add readme --- README.md | 133 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 133 insertions(+) create mode 100644 README.md diff --git a/README.md b/README.md new file mode 100644 index 0000000..1684909 --- /dev/null +++ b/README.md @@ -0,0 +1,133 @@ +# Commando + +An opinionated command line parsing utility for Scala. + +## 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 --bar subcommand --baz ` 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 +``` -- cgit v1.2.3