clad
This module encodes a list of command line arguments as a dynamic.Dynamic
and
provides functions to decode those arguments using a decode/zero.Decoder
.
Encoding
The following arguments:
-x=3 -y 4 -n5 -abc --hello world --beep=boop foo bar baz
will be encoded as a dynamic.Dynamic
in this shape:
{
"name": 3,
"y": 4,
"a": True,
"b": True,
"c": True,
"hello": "world",
"beep": "boop",
"_": ["foo", "bar", "baz"]
}
Decoding
Arguments can be decoded with a normal zero.Decoder
// args: --name Lucy --age 8 --enrolled true
let decoder = {
use name <- zero.field("name", zero.string)
use age <- zero.field("age", zero.int)
use enrolled <- zero.field("enrolled", zero.bool)
zero.success(Student(name:, age:, enrolled:))
}
let result = clad.decode(args, decoder)
assert result == Ok(Student("Lucy", 8, True))
Clad provides additional functions to support some common CLI behaviors.
Boolean Flags
CLI’s commonly represent boolean flags just by the precense or absence of the option. Since Clad has no knowledge of your target record, it cannot encode missing flags as False.
Clad provides the flag()
decoder to handle this case.
// args1: --name Lucy --age 8 --enrolled
// args2: --name Bob --age 3
let decoder = {
use name <- zero.field("name", zero.string)
use age <- zero.field("age", zero.int)
use enrolled <- zero.field("enrolled", clad.flag())
zero.success(Student(name:, age:, enrolled:))
}
let result = clad.decode(args1, decoder)
assert result == Ok(Student("Lucy", 8, True))
let result = clad.decode(args2, decoder)
assert result == Ok(Student("Bob", 3, False))
Alternate Names
It is also common for CLI’s to support long names and short names for options
(e.g. --name
and -n
).
Clad provides the opt()
function for this.
// args1: -n Lucy -a 8 -e
// args2: --name Bob --age 3
let decoder = {
use name <- clad.opt(long_name: "name", short_name: "n", zero.string)
use age <- clad.opt(long_name: "age", short_name: "a", zero.int)
use enrolled <- clad.opt(long_name: "enrolled", short_name: "e" clad.flag())
zero.success(Student(name:, age:, enrolled:))
}
let result = clad.decode(args1, decoder)
assert result == Ok(Student("Lucy", 8, True))
let result = clad.decode(args2, decoder)
assert result == Ok(Student("Bob", 3, False))
Positional Arguments
A CLI may also support positional arguments. These are any arguments that are
not attributed to a named option. Clad provides the positional_arguments()
decoder to
retrieve these values.
// args1: -n Lucy -ea8 math science art
// args2: --name Bob --age 3
let decoder = {
use name <- clad.opt("name", "n", zero.string)
use age <- clad.opt("age", "a", zero.int)
use enrolled <- clad.opt("enrolled", "e" clad.flag())
use classes <- clad.positional_arguments()
zero.success(Student(name:, age:, enrolled:, classes:))
}
let result = clad.decode(args1, decoder)
assert result == Ok(Student("Lucy", 8, True, ["math", "science", "art"]))
let result = clad.decode(args2, decoder)
assert result == Ok(Student("Bob", 3, False, []))
Functions
pub fn decode(
args: List(String),
decoder: Decoder(a),
) -> Result(a, List(DecodeError))
Run a decoder on a list of command line arguments, decoding the value if it is of the desired type, or returning errors.
This function pairs well with the argv package.
Examples
// args: --name Lucy --email=lucy@example.com
let decoder = {
use name <- zero.field("name", dynamic.string)
use email <- zero.field("email", dynamic.string),
clad.decoded(SignUp(name:, email:))
}
let result = clad.decode(argv.load().arguments, decoder)
assert result == Ok(SignUp(name: "Lucy", email: "lucy@example.com"))
pub fn flag() -> Decoder(Bool)
A Bool decoder that returns False if value is not present
let decoder = {
use verbose <- zero.field("v", clad.flag())
zero.success(verbose)
}
let result = clad.decode(["-v"], decoder)
assert result == Ok(True)
let result = clad.decode([], decoder)
assert result == Ok(False)
pub fn opt(
long_name: String,
short_name: String,
field_decoder: Decoder(a),
next: fn(a) -> Decoder(b),
) -> Decoder(b)
Decode a command line option by either a long name or short name
let decoder = {
use name <- clad.opt("name", "n", zero.string)
zero.success(name)
}
clad.decode(["--name", "Lucy"], decoder)
// -> Ok("Lucy")
clad.decode(["-n", "Lucy"], decoder)
// -> Ok("Lucy")
pub fn positional_arguments(
next: fn(List(String)) -> Decoder(a),
) -> Decoder(a)
Get all of the unnamed, positional arguments
let decoder = {
use positional <- clad.positional_arguments
zero.success(positional)
}
let result = clad.decode(["-a1", "hello", "-b", "2", "world"], decoder)
assert result == Ok(["hello", "world"])
let result = clad.decode(["-a1", "-b", "2"], decoder)
assert result == Ok([])