Skip to content

Commit

Permalink
split readme; reorg code; start more detailed docs
Browse files Browse the repository at this point in the history
  • Loading branch information
seancorfield committed Dec 16, 2023
1 parent 2168a34 commit 3db233a
Show file tree
Hide file tree
Showing 5 changed files with 527 additions and 306 deletions.
137 changes: 7 additions & 130 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ This project follows the version scheme MAJOR.MINOR.COMMITS where MAJOR and MINO

Latest stable release: 1.0.219

* [All Released Versions](http://search.maven.org/#search%7Cgav%7C1%7Cg%3A%22org.clojure%22%20AND%20a%3A%22tools.cli%22)
* [All Released Versions](https://central.sonatype.com/artifact/org.clojure/tools.cli/versions)

* [Development Snapshot Versions](https://oss.sonatype.org/index.html#nexus-search;gav~org.clojure~tools.cli~~~)

Expand All @@ -17,7 +17,7 @@ Latest stable release: 1.0.219
org.clojure/tools.cli {:mvn/version "1.0.219"}
```

[Leiningen](https://github.com/technomancy/leiningen) dependency information:
[Leiningen](https://leiningen.org/) dependency information:
```clojure
[org.clojure/tools.cli "1.0.219"]
```
Expand Down Expand Up @@ -105,136 +105,11 @@ http://clojure.github.io/tools.cli/index.html#clojure.tools.cli/parse-opts
An interesting library built on top of `tool.cli` that provides a more compact,
higher-level API is [cli-matic](https://github.com/l3nz/cli-matic).

## Since Release 0.3.x

### Better Option Tokenization

In accordance with the [GNU Program Argument Syntax Conventions][GNU], two
features have been added to the options tokenizer:

* Short options may be grouped together.

For instance, `-abc` is equivalent to `-a -b -c`. If the `-b` option
requires an argument, the same `-abc` is interpreted as `-a -b "c"`.

* Long option arguments may be specified with an equals sign.

`--long-opt=ARG` is equivalent to `--long-opt "ARG"`.

If the argument is omitted, it is interpreted as the empty string.
e.g. `--long-opt=` is equivalent to `--long-opt ""`

[GNU]: https://www.gnu.org/software/libc/manual/html_node/Argument-Syntax.html

### In-order Processing for Subcommands

Large programs are often divided into subcommands with their own sets of
options. To aid in designing such programs, `clojure.tools.cli/parse-opts`
accepts an `:in-order` option that directs it to stop processing arguments at
the first unrecognized token.

For instance, the `git` program has a set of top-level options that are
unrecognized by subcommands and vice-versa:

git --git-dir=/other/proj/.git log --oneline --graph

By default, `clojure.tools.cli/parse-opts` interprets this command line as:

options: [[--git-dir /other/proj/.git]
[--oneline]
[--graph]]
arguments: [log]

When :in-order is true however, the arguments are interpreted as:

options: [[--git-dir /other/proj/.git]]
arguments: [log --oneline --graph]

Note that the options to `log` are not parsed, but remain in the unprocessed
arguments vector. These options could be handled by another call to
`parse-opts` from within the function that handles the `log` subcommand.

### Options Summary

`parse-opts` returns a minimal options summary string:

-p, --port NUMBER 8080 Required option with default
--host HOST localhost Short and long options may be omitted
-d, --detach Boolean option
-h, --help

This may be inserted into a larger usage summary, but it is up to the caller.

If the default formatting of the summary is unsatisfactory, a `:summary-fn`
may be supplied to `parse-opts`. This function will be passed the sequence
of compiled option specification maps and is expected to return an options
summary.

The default summary function `clojure.tools.cli/summarize` is public and may
be useful within your own `:summary-fn` for generating the default summary.

### Option Argument Validation

By default, option validation is performed immediately after parsing, which
means that "flag" arguments will have a Boolean value, even if a `:default`
is specified with a different type of value.

You can choose to perform validation after option processing instead, with
the `:post-validation true` flag. During option processing, `:default` values
are applied and `:assoc-fn` and `:update-fn` are invoked. If an option is
specified more than once, `:post-validation true` will cause validation to
be performed after each new option value is processed.

There is a new option entry `:validate`, which takes a tuple of
`[validation-fn validation-msg]`. The validation-fn receives an option's
argument *after* being parsed by `:parse-fn` if it exists. The validation-msg
can either be a string or a function of one argument that can be called on
the invalid option argument to produce a string:

["-p" "--port PORT" "A port number"
:parse-fn #(Integer/parseInt %)
:validate [#(< 0 % 0x10000) #(str % " is not a number between 0 and 65536")]]

If the validation-fn returns a falsey value, the validation-msg is added to the
errors vector.

### Error Handling and Return Values

Instead of throwing errors, `parse-opts` collects error messages into a vector
and returns them to the caller. Unknown options, missing required arguments,
validation errors, and exceptions thrown during `:parse-fn` are all added to
the errors vector.

Any option can be flagged as required by providing a `:missing` key in the
option spec with a string that should be used for the error message if the
option is omitted.

The error message when a required argument is omitted (either a short opt with
`:require` or a long opt describing an argument) is:

`Missing required argument for ...`

Correspondingly, `parse-opts` returns the following map of values:

{:options A map of default options merged with parsed values from the command line
:arguments A vector of unprocessed arguments
:summary An options summary string
:errors A vector of error messages, or nil if no errors}

During development, parse-opts asserts the uniqueness of option `:id`,
`:short-opt`, and `:long-opt` values and throws an error on failure.

### ClojureScript Support

As of 0.4.x, the namespace is `clojure.tools.cli` for both Clojure and
ClojureScript programs. The entire API, including the legacy (pre-0.3.x)
functions, is now available in both Clojure and ClojureScript.

For the 0.3.x releases, the ClojureScript namespace was `cljs.tools.cli` and
only `parse-opts` and `summarize` were available.

## Example Usage

This is an example of a program that uses most of the `tools.cli` features.
For detailed documentation, please see the docstring of `parse-opts`.

```clojure
(ns cli-example.core
(:require [cli-example.server :as server]
Expand Down Expand Up @@ -287,6 +162,8 @@ only `parse-opts` and `summarize` were available.
;; case any :default value is used as the initial option value rather than nil,
;; and :default-fn will be called to compute the final option value if none was
;; given on the command-line (thus, :default-fn can override :default)
;; Note: validation is *not* performed on the result of :default-fn (this is
;; an open issue for discussion and is not currently considered a bug).

(defn usage [options-summary]
(->> ["This is my program. There are many like it, but this one is mine."
Expand Down
4 changes: 4 additions & 0 deletions doc/cljdoc.edn
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{:cljdoc.doc/tree [["Readme" {:file "README.md"}]
["Changes" {:file "CHANGELOG.md"}]
["Command-Line Options" {:file "doc/parse-opts.md"}]
["Changes since 0.3.x" {:file "doc/new-in-0-4.md"}]]}
134 changes: 134 additions & 0 deletions doc/new-in-0-4.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
## Improvements in 0.4.x

This section highlights the changes/improvents in the 0.4.x series of
releases, compared to the earlier 0.3.x series.

As a general note, `clojure.tools.cli/cli` is deprecated and you should
use `clojure.tools.cli/parse-opts` instead. The legacy function will remain
for the foreseeable future, but will not get bug fixes or new features.

### Better Option Tokenization

In accordance with the [GNU Program Argument Syntax Conventions][GNU], two
features have been added to the options tokenizer:

* Short options may be grouped together.

For instance, `-abc` is equivalent to `-a -b -c`. If the `-b` option
requires an argument, the same `-abc` is interpreted as `-a -b "c"`.

* Long option arguments may be specified with an equals sign.

`--long-opt=ARG` is equivalent to `--long-opt "ARG"`.

If the argument is omitted, it is interpreted as the empty string.
e.g. `--long-opt=` is equivalent to `--long-opt ""`

[GNU]: https://www.gnu.org/software/libc/manual/html_node/Argument-Syntax.html

### In-order Processing for Subcommands

Large programs are often divided into subcommands with their own sets of
options. To aid in designing such programs, `clojure.tools.cli/parse-opts`
accepts an `:in-order` option that directs it to stop processing arguments at
the first unrecognized token.

For instance, the `git` program has a set of top-level options that are
unrecognized by subcommands and vice-versa:

git --git-dir=/other/proj/.git log --oneline --graph

By default, `clojure.tools.cli/parse-opts` interprets this command line as:

options: [[--git-dir /other/proj/.git]
[--oneline]
[--graph]]
arguments: [log]

When :in-order is true however, the arguments are interpreted as:

options: [[--git-dir /other/proj/.git]]
arguments: [log --oneline --graph]

Note that the options to `log` are not parsed, but remain in the unprocessed
arguments vector. These options could be handled by another call to
`parse-opts` from within the function that handles the `log` subcommand.

### Options Summary

`parse-opts` returns a minimal options summary string:

-p, --port NUMBER 8080 Required option with default
--host HOST localhost Short and long options may be omitted
-d, --detach Boolean option
-h, --help

This may be inserted into a larger usage summary, but it is up to the caller.

If the default formatting of the summary is unsatisfactory, a `:summary-fn`
may be supplied to `parse-opts`. This function will be passed the sequence
of compiled option specification maps and is expected to return an options
summary.

The default summary function `clojure.tools.cli/summarize` is public and may
be useful within your own `:summary-fn` for generating the default summary.

### Option Argument Validation

By default, option validation is performed immediately after parsing, which
means that "flag" arguments will have a Boolean value, even if a `:default`
is specified with a different type of value.

You can choose to perform validation after option processing instead, with
the `:post-validation true` flag. During option processing, `:default` values
are applied and `:assoc-fn` and `:update-fn` are invoked. If an option is
specified more than once, `:post-validation true` will cause validation to
be performed after each new option value is processed.

There is a new option entry `:validate`, which takes a tuple of
`[validation-fn validation-msg]`. The validation-fn receives an option's
argument *after* being parsed by `:parse-fn` if it exists. The validation-msg
can either be a string or a function of one argument that can be called on
the invalid option argument to produce a string:

["-p" "--port PORT" "A port number"
:parse-fn #(Integer/parseInt %)
:validate [#(< 0 % 0x10000) #(str % " is not a number between 0 and 65536")]]

If the validation-fn returns a falsey value, the validation-msg is added to the
errors vector.

### Error Handling and Return Values

Instead of throwing errors, `parse-opts` collects error messages into a vector
and returns them to the caller. Unknown options, missing required arguments,
validation errors, and exceptions thrown during `:parse-fn` are all added to
the errors vector.

Any option can be flagged as required by providing a `:missing` key in the
option spec with a string that should be used for the error message if the
option is omitted.

The error message when a required argument is omitted (either a short opt with
`:require` or a long opt describing an argument) is:

`Missing required argument for ...`

Correspondingly, `parse-opts` returns the following map of values:

{:options A map of default options merged with parsed values from the command line
:arguments A vector of unprocessed arguments
:summary An options summary string
:errors A vector of error messages, or nil if no errors}

During development, parse-opts asserts the uniqueness of option `:id`,
`:short-opt`, and `:long-opt` values and throws an error on failure.

### ClojureScript Support

As of 0.4.x, the namespace is `clojure.tools.cli` for both Clojure and
ClojureScript programs. The entire API, including the legacy (pre-0.3.x)
functions, is now available in both Clojure and ClojureScript.

For the 0.3.x releases, the ClojureScript namespace was `cljs.tools.cli` and
only `parse-opts` and `summarize` were available.
Loading

0 comments on commit 3db233a

Please sign in to comment.