@thi.ng/args
Version:
Declarative, functional CLI argument/options parser, app framework, arg value coercions, multi/sub-commands, usage generation, error handling etc.
571 lines (474 loc) ⢠17.9 kB
Markdown
<!-- This file is generated - DO NOT EDIT! -->
<!-- Please see: https://github.com/thi-ng/umbrella/blob/develop/CONTRIBUTING.md#changes-to-readme-files -->
# 
[](https://www.npmjs.com/package/@thi.ng/args)

[](https://mastodon.thi.ng/@toxi)
> [!NOTE]
> This is one of 210 standalone projects, maintained as part
> of the [@thi.ng/umbrella](https://github.com/thi-ng/umbrella/) monorepo
> and anti-framework.
>
> š Please help me to work full-time on these projects by [sponsoring me on
> GitHub](https://github.com/sponsors/postspectacular). Thank you! ā¤ļø
- [About](#about)
- [Built-in argument types](#built-in-argument-types)
- [Re-usable argument presets](#re-usable-argument-presets)
- [CLI app framework](#cli-app-framework)
- [Status](#status)
- [Breaking changes in 3.0.0](#breaking-changes-in-300)
- [Installation](#installation)
- [Dependencies](#dependencies)
- [Projects using this package](#projects-using-this-package)
- [API](#api)
- [Basic usage](#basic-usage)
- [Generate & display help](#generate--display-help)
- [Parsing, value coercions & side effects](#parsing-value-coercions--side-effects)
- [Declarative, multi-command CLI application](#declarative-multi-command-cli-application)
- [Authors](#authors)
- [License](#license)
## About
Declarative, functional CLI argument/options parser, app framework, arg value coercions, multi/sub-commands, usage generation, error handling etc..
### Built-in argument types
The parser includes built-in support for the following argument types (of course
custom arg types are supported too):
| **Argument type** | **Multiple** | **Example** | **Result** |
|----------------------|--------------|----------------------------|-----------------------------------|
| **Flag** | | `--force`, `-f` | `force: true` |
| **String** | ā
| `--foo bar` | `foo: "bar"` |
| **Float/int/hex** | ā
| `--bg ff997f` | `bg: 16750975` |
| **Enum** | ā
| `--type png` | `type: "png"` |
| **KV pairs** | ā
| `--define foo=bar` | `define: { foo: "bar" }` |
| **KV multi pairs** | ā
| `-D foo=bar -D foo=baz` | `define: { foo: ["bar", "baz"] }` |
| **JSON** | | `--config '{"foo": [23]}'` | `config: { foo: [23] }` |
| **Fixed size tuple** | | `--size 640x480` | `size: { value: [640, 480] }` |
If multiple values/repetitions are allowed for an argument, the values will be
collected into an array (apart from KV pairs, which will yield an object).
Furthermore, for multi-args and tuples, an optional delimiter can be specified
to extract individual values, e.g. `-a 1,2,3` equals `-a 1 -a 2 -a 3`
### Re-usable argument presets
The following commonly used arguments are available as predefined presets:
- [`ARG_DRY_RUN`](https://docs.thi.ng/umbrella/args/variables/ARG_DRY_RUN.html)
- [`ARG_QUIET`](https://docs.thi.ng/umbrella/args/variables/ARG_QUIET.html)
- [`ARG_VERBOSE`](https://docs.thi.ng/umbrella/args/variables/ARG_VERBOSE.html)
Higher order, configurable preset specs:
- [`ARG_OUT_DIR`](https://docs.thi.ng/umbrella/args/functions/ARG_OUT_DIR.html)
- [`ARG_OUT_FILE`](https://docs.thi.ng/umbrella/args/functions/ARG_OUT_FILE.html)
To use these presets, simply import and splice them into your own arg
definitions (see code examples below).
### CLI app framework
The package provides a simple framework to conveniently define single and
multi-command applications in a declarative and modular manner. Such apps are
defined via command specs and other configuration options. The framework then
handles all argument parsing, validation, usage display and delegation to
sub-commands.
The wrapper defines a user-customizable [command
context](https://docs.thi.ng/umbrella/args/interfaces/CommandCtx.html) with all
important information which is passed to the commands and also includes a logger
(writing to `stderr`). Other help/usage and error output also respects the
[`NO_COLOR` convention](https://no-color.org/).
A [fully documented code example](#declarative-multi-command-cli-application) is
further below.
For some _publicly available_ production uses, please see the [related packages
section](#projects-using-this-package) in this readme.
## Status
**STABLE** - used in production
[Search or submit any issues for this package](https://github.com/thi-ng/umbrella/issues?q=%5Bargs%5D+in%3Atitle)
### Breaking changes in 3.0.0
- Required arguments are now to be specified using either `required: true` or
given a `default` value
- All factory functions now only accept a single arg spec, with any type-specific
options moved into the spec, for example:
- old: `oneOf(["a","b","c"], {...})`
- new: `oneOf({ opts: ["a","b","c"], ...})`
- old: `tuple(identity, 3, {...})`
- new: `tuple({ size: 3, coerce: identity, ...})`
- Where applicable, `delim`iters are now to be included in the arg spec (rather
than given as separate function arg)
## Installation
```bash
yarn add @thi.ng/args
```
ESM import:
```ts
import * as args from "@thi.ng/args";
```
For Node.js REPL:
```js
const args = await import("@thi.ng/args");
```
Package sizes (brotli'd, pre-treeshake): ESM: 3.47 KB
## Dependencies
- [@thi.ng/api](https://github.com/thi-ng/umbrella/tree/develop/packages/api)
- [@thi.ng/checks](https://github.com/thi-ng/umbrella/tree/develop/packages/checks)
- [@thi.ng/errors](https://github.com/thi-ng/umbrella/tree/develop/packages/errors)
- [@thi.ng/logger](https://github.com/thi-ng/umbrella/tree/develop/packages/logger)
- [@thi.ng/strings](https://github.com/thi-ng/umbrella/tree/develop/packages/strings)
- [@thi.ng/text-format](https://github.com/thi-ng/umbrella/tree/develop/packages/text-format)
Note: @thi.ng/api is in _most_ cases a type-only import (not used at runtime)
## Projects using this package
- [@thi.ng/block-fs](https://thi.ng/block-fs): Customizable block-based storage,
adapters & file system layer
- [@thi.ng/meta-css](https://thi.ng/meta-css): Data-driven CSS framework
codegen, transpiler & bundler
- [@thi.ng/pointfree-lang](https://thi.ng/pointfree-lang): Forth style syntax
layer/compiler & CLI for the [@thi.ng/pointfree](https://thi.ng/pointfree) DSL
- [@thi.ng/tangle](https://thi.ng/tangle): Literate programming code block
tangling / codegen utility, inspired by org-mode & noweb
- [@thi.ng/wasm-api-bindgen](https://thi.ng/wasm-api-bindgen): Polyglot bindings
code generators (TS/JS, Zig, C11) for hybrid WebAssembly projects
- [thing-tools](https://codeberg.org/thi.ng/thing-tools)
## API
[Generated API docs](https://docs.thi.ng/umbrella/args/)
## Basic usage
```ts tangle:export/readme.ts
import {
ARG_VERBOSE,
flag,
hex,
json,
kvPairs,
oneOf,
parse,
size,
string,
vec,
type Args,
type KVDict,
type Tuple,
} from "@thi.ng/args";
type ImgType = "png" | "jpg" | "gif" | "tiff";
// CLI args will be validated against this interface
interface TestArgs {
configPath?: string;
force?: boolean;
bg: number;
type: ImgType;
size?: Tuple<number>;
pos?: Tuple<number>;
xtra?: { a: number; b: string };
define?: KVDict;
verbose: boolean;
}
// arg specifications
const specs: Args<TestArgs> = {
// re-use predefined preset (see readme section above)
...ARG_VERBOSE,
// string arg
configPath: string({
alias: "c",
hint: "PATH",
desc: "Config file path (CLI args always take precedence over those settings)",
}),
// boolean flag (default: false)
force: flag({
alias: "f",
desc: "Force operation",
// side effect and/or validation
// parsing only continues if function returns true
fn: (_) => (console.log("force mode enabled"), true),
}),
// hex int value
bg: hex({
desc: "Background color",
// mandatory args require a `default` value and/or `required: true`
default: 0xffffff,
defaultHint: "ffffff",
}),
// enum value (mandatory)
type: oneOf({
alias: "t",
desc: "Image type",
opts: ["png", "jpg", "gif", "tiff"],
// mandatory args require a `default` value and/or `required: true`
required: true,
}),
// fixed size numeric tuple w/ `x` as delimiter
size: size({ size: 2, hint: "WxH", desc: "Target size", delim: "x" }),
// syntax sugar for:
// size: tuple(2, coerceInt, { hint: "WxH", desc: "Target size" }, "x"),
// another version for tuples of floating point values
pos: vec({ size: 2, desc: "Lat/Lon coordinates", hint: "LAT,LON" }),
// syntax sugar for:
// pos: tuple(2, coerceFloat, { desc: "Lat/Lon" }),
// JSON string arg
xtra: json({
alias: "x",
desc: "Extra options",
group: "extra",
}),
// key-value pairs parsed into an object (multiple allowed)
define: kvPairs({
alias: "D",
desc: "Define dict entry",
group: "extra"
}),
};
try {
// parse argv w/ above argument specs & default options
// (by default usage is shown if error occurs)
const args = parse(specs, process.argv, {
usageOpts: {
prefix: `
ā ā ā ā
āā ā ā
ā ā ā ā ā ā ā ā ā @thi.ng/args demo app
ā ā ā ā ā ā ā ā ā ā v1.0.0
ā ā
ā ā ā\n\n`,
showGroupNames: true,
groups: ["flags", "main", "extra"],
lineWidth: 72,
},
});
console.log(args);
} catch (_) {}
```
Invoking this as CLI script without arguments will generate an error about a
missing `--type` arg and output the generated usage info (by default with ANSI
color highlights):
```text
illegal argument(s): missing arg: --type
ā ā ā ā
āā ā ā
ā ā ā ā ā ā ā ā ā @thi.ng/args demo app
ā ā ā ā ā ā ā ā ā ā v1.0.0
ā ā
ā ā ā
Flags:
-f, --force Force operation
-v, --verbose Display extra information
Main:
--bg HEX Background color (default: "ffffff")
-c PATH, --config-path PATH Config file path (CLI args always take
precedence over those settings)
--pos N,N Lat/Lon coordinates
--size WxH Target size
-t ID, --type ID [required] Image type: "png", "jpg",
"gif", "tiff"
Extra:
-D key=val, --define key=val [multiple] Define dict entry
-x JSON, --xtra JSON Extra options
```
### Generate & display help
Usage information can be generated via `usage()` and is automatically triggered
via the special `--help` option (configurable, see
[ParseOpts](https://docs.thi.ng/umbrella/args/interfaces/ParseOpts.html)).
Each arg can be associated with arbitrary group IDs, which are then used to
segment usage output. By default, `flag()` args are assigned to a `"flags"`
group, all others to `"main"`. The default output order too is `["flags",
"main"]`, but can be configured via a `group` option given an arg spec or
factory function.
By default, ANSI colors are used to format the result string of `usage()`, but
can be disabled (see
[`UsageOpts`](https://docs.thi.ng/umbrella/args/interfaces/UsageOpts.html)).
### Parsing, value coercions & side effects
The below invocation demonstrates how the various argument types are handled &
represented in the result. Parsing stops with the first non-argument value (here
`sourcefile.png`) and the remaining values are made available via `rest` in the
result object.
```bash
bun index.ts \
-f -t png --bg ff00ff --size 640x480 \
-D author=toxi -D date=2018-03-24 \
--xtra '{"foo": [23]}' \
sourcefile.png
# force mode enabled
# {
# result: {
# force: true,
# type: 'png',
# bg: 16711935,
# size: Tuple { value: [640, 480] }
# define: { author: 'toxi', date: '2018-03-24' },
# xtra: { foo: [23] },
# verbose: false,
# },
# index: 15,
# rest: [ 'sourcefile.png' ],
# done: false
# }
```
## Declarative, multi-command CLI application
The following example defines a CLI app with two sub-commands: `hello` and
`list`. Each command has its own options, in addition to common/shared ones.
Each command is defined in a modular manner (usually in its own source file).
All aspects like arg parsing, validation, and command selection/delegation is
handled by the `cliApp()` wrapper.
```ts tangle:export/readme-cliapp.ts
import {
ARG_VERBOSE,
cliApp,
configureLogLevel,
int,
string,
type Command,
type CommandCtx,
} from "@thi.ng/args";
import { files } from "@thi.ng/file-io";
// common command opts
interface CommonOpts {
verbose: boolean;
}
// custom command context
interface AppCtx<T extends CommonOpts> extends CommandCtx<T, CommonOpts> {
// plus any custom additions here...
}
// command-specific options
interface HelloOpts extends CommonOpts {
name: string;
}
// command definition
const HELLO: Command<HelloOpts, CommonOpts> = {
// brief description (for `--help` usage)
desc: "Print out a greeting",
// command specific options (arguments)
// (will be combined with common opts)
opts: {
name: string({
alias: "n",
desc: "Name for greeting",
required: true,
}),
},
// this command does not accept any inputs
inputs: 0,
// command implementation
fn: async (ctx) => {
// log message only shown if `--verbose`/`-v` given
ctx.logger.debug("opts", ctx.opts);
console.log(`Hello, ${ctx.opts.name}!`);
},
};
// command-specific options
interface ListFilesOpts extends CommonOpts {
depth: number;
filter?: string;
}
// command definition
const LIST_FILES: Command<ListFilesOpts, CommonOpts> = {
// brief description (for `--help` usage)
desc: "List files in given dir",
// command specific options
opts: {
filter: string({
alias: "f",
desc: "Filter regexp",
}),
depth: int({
alias: "d",
desc: "Recursion depth (directory levels)",
default: Infinity,
}),
},
// this command requires exactly 1 input
// (if supporting a range, use `[min, max]`)
inputs: 1,
// command implementation
fn: async (ctx) => {
for (let f of files(ctx.inputs[0], ctx.opts.filter, ctx.opts.depth)) {
console.log(f);
}
},
};
// define & start CLI app
cliApp<CommonOpts, AppCtx<any>>({
// app name
name: "example",
// process.argv index from which to start parsing from
start: 2,
// list common command opts here
opts: {
// re-use verbose flag arg spec preset
...ARG_VERBOSE,
},
// list of commands
commands: {
hello: HELLO,
list: LIST_FILES,
},
// set to true if only a single command
// in this case the command name would NOT be required/expected
// single: true,
// usage opts
usage: {
// prefix/header string
prefix: `Example app
===================================
Usage: example [opts] [inputs]\n`,
// configure column width for param usage info
paramWidth: 24,
lineWidth: 80,
},
// context initialization/augmentation
// (called before arg parsing commences)
ctx: async (ctx) => {
configureLogLevel(ctx.logger, ctx.opts.verbose);
return ctx;
},
});
```
Example usage (here using `bun` to launch the above CLI app, though the usage
info is written to assume an `example` launcher/wrapper):
```bash
bun readme-cliapp.ts
# Example app
# ===================================
# Usage: example [opts] [inputs]
#
# Available commands:
#
# hello : Print out a greeting
# list : List files in given dir
#
# -v, --verbose Display extra information
```
```bash
# displaying help for a sub-command
bun readme-cliapp.ts hello --help
# Example app
# ===================================
# Usage: example [opts] [inputs]
#
# Current command:
#
# hello : Print out a greeting
#
# -v, --verbose Display extra information
#
# -n STR, --name STR [required] Name for greeting
```
```bash
# invoking `hello` sub-command (with verbose flag)
bun readme-cliapp.ts hello --name thi.ng -v
# [DEBUG] example: opts {"name":"thi.ng","verbose":true}
# Hello, thi.ng!
```
```bash
# invoking `list` sub-command
bun readme-cliapp.ts list -d 2 -f '.js' .
# ./dev/api.js
# ./dev/runtime.js
# ./dev/test/main.js
# ./index.js
```
```bash
# missing arg error
bun readme-cliapp.ts hello
# illegal argument(s): missing arg: --name
#
# (...additional usage output omitted for brevity)
```
## Authors
- [Karsten Schmidt](https://thi.ng)
If this project contributes to an academic publication, please cite it as:
```bibtex
@misc{thing-args,
title = "@thi.ng/args",
author = "Karsten Schmidt",
note = "https://thi.ng/args",
year = 2018
}
```
## License
© 2018 - 2025 Karsten Schmidt // Apache License 2.0