clad
This module encodes a list of command line arguments as a dynamic.Dynamic
and
provides primitives to build a dynamic.Decoder
to decode records from command line
arguments.
Arguments are parsed from long names (--name
) or short names (-n
).
Values are decoded in the form --name value
or --name=value
.
Boolean flags do not need an explicit value. If the flag exists it is True
,
and False
if it is missing. (i.e. --verbose
)
Examples
Encoding
Clad encodes the arguments without any knowledge of your target record. It cannot know if a field is intended to be a single, basic type or a list with a single item. Therefore it encodes everything as a list.
All of the following get encoded the same:
--name Lucy --count 3 --verbose
--name Lucy --count 3 --verbose true
--name=Lucy --count=3 --verbose=true
// {"--name": ["Lucy"], "--count": [3], "--verbose": [true]}
Since the target record is unknown, missing Bool arguments are not encoded at all:
--name Lucy --count 3
// {"--name": ["Lucy"], "--count": [3]}
There is no way to know that a long name and a short name are the same argument when encoding. So they are encoded as separate fields:
--name Lucy -n Joe
// {"--name": ["Lucy"], "-n": ["Joe"]}
Decoding Fields
Clad provides the arg
function to handle these quirks of the Dynamic representation.
--name Lucy
use name <- clad.arg(long_name: "name", short_name: "n", of: dynamic.string)
// -> "Lucy"
-n Lucy
use name <- clad.arg(long_name: "name", short_name: "n", of: dynamic.string)
// -> "Lucy"
-n Lucy -n Joe
use names <- clad.arg("name", "n", of: dynamic.list(dynamic.string))
// -> ["Lucy", "Joe"]
Clad’s toggle
decoder only requires the name. Missing arguments are False
:
--verbose
use verbose <- clad.toggle(long_name: "verbose", short_name: "v")
// -> True
--name Lucy
use verbose <- clad.toggle(long_name: "verbose", short_name: "v")
// -> False
It’s common for CLI’s to have default values for arguments.
This can be accomplished with a dynamic.optional
, but
the arg_with_default
function is provided for convenience:
--name Lucy
use count <- clad.arg_with_default(
long_name: "count",
short_name: "c",
of: dynamic.int,
default: 1,
)
// -> 1
Decoding Records
Clad’s API is heavily inspired by (read: copied from) toy.
fn arg_decoder() {
use name <- clad.arg("name", "n", dynamic.string)
use count <- clad.arg_with_default("count", "c", dynamic.int, 1)
use verbose <- clad.toggle("verbose", "v")
clad.decoded(Args(name:, count:, verbose:))
}
And then use it to decode the arguments:
// arguments: ["--name", "Lucy", "--count", "3", "--verbose"]
let args =
arg_decoder()
|> clad.decode(arguments)
let assert Ok(Args("Lucy", 3, True)) = args
Here are a few examples of arguments that would decode the same:
--name Lucy --count 3 --verbose
--name=Lucy -c 3 -v=true
-n=Lucy -c=3 -v
Errors
Clad returns the first error it encounters. If multiple fields have errors, only the first one will be returned.
// arguments: ["--count", "three"]
let args =
arg_decoder()
|> clad.decode(arguments)
let assert Error([DecodeError("field", "nothing", ["--name"])]) = args
If a field has a default value, but the argument is supplied with the incorrect type, an error will be returned rather than falling back on the default value.
// arguments: ["-n", "Lucy" "-c", "three"]
let args =
arg_decoder()
|> clad.decode(arguments)
let assert Error([DecodeError("Int", "String", ["-c"])]) = args
Functions
pub fn arg(
long_name long_name: String,
short_name short_name: String,
of decoder: fn(Dynamic) -> Result(a, List(DecodeError)),
then next: fn(a) -> fn(Dynamic) -> Result(b, List(DecodeError)),
) -> fn(Dynamic) -> Result(b, List(DecodeError))
Decode an argument by either its long name (--name
) or short name (-n
).
List arguments are represented by repeated values.
Examples
// data: ["--name", "Lucy"]
use name <- clad.arg(long_name: "name", short_name: "n", of: dynamic.string)
// -> "Lucy"
// data: ["-n", "Lucy"]
use name <- clad.arg(long_name: "name", short_name: "n", of: dynamic.string)
// -> "Lucy"
// data: ["-n", "Lucy", "-n", "Joe"]
use name <- clad.arg(
long_name: "name",
short_name: "n",
of: dynamic.list(dynamic.string)
)
// -> ["Lucy", "Joe"]
pub fn arg_with_default(
long_name long_name: String,
short_name short_name: String,
of decoder: fn(Dynamic) -> Result(a, List(DecodeError)),
default default: a,
then next: fn(a) -> fn(Dynamic) -> Result(b, List(DecodeError)),
) -> fn(Dynamic) -> Result(b, List(DecodeError))
Decode an argument, returning a default value if the argument does not exist
Examples
// data: ["--name", "Lucy"]
use name <- clad.arg(
long_name: "name",
short_name: "n",
of: dynamic.string,
default: "Joe"
)
// -> "Lucy"
// data: []
use name <- clad.arg(
long_name: "name",
short_name: "n",
of: dynamic.string,
default: "Joe"
)
// -> "Joe"
pub fn decode(
decoder: fn(Dynamic) -> Result(a, List(DecodeError)),
arguments: List(String),
) -> 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
{
use name <- clad.arg("name", "n", dynamic.string)
use email <- clad.arg("email", "e", dynamic.string),
clad.decoded(SignUp(name:, email:))
}
|> clad.decode(["-n", "Lucy", "--email=lucy@example.com"])
// -> Ok(SignUp(name: "Lucy", email: "lucy@example.com"))
with argv:
{
use name <- clad.arg("name", "n", dynamic.string)
use email <- clad.arg("email", "e", dynamic.string),
clad.decoded(SignUp(name:, email:))
}
|> clad.decode(argv.load().arguments)
pub fn decoded(
value: a,
) -> fn(Dynamic) -> Result(a, List(DecodeError))
Creates a decoder which directly returns the provided value. Used to collect decoded values into a record.
Examples
pub fn user_decoder() {
use name <- clad.string("name", "n")
clad.decoded(User(name:))
}
pub fn long_name(
long_name: String,
decoder: fn(Dynamic) -> Result(a, List(DecodeError)),
next: fn(a) -> fn(Dynamic) -> Result(b, List(DecodeError)),
) -> fn(Dynamic) -> Result(b, List(DecodeError))
Decode an argument only by a long name
Examples
// data: ["--name", "Lucy"]
use name <- clad.long_name("name", dynamic.string)
// -> "Lucy"
pub fn short_name(
short_name: String,
decoder: fn(Dynamic) -> Result(a, List(DecodeError)),
next: fn(a) -> fn(Dynamic) -> Result(b, List(DecodeError)),
) -> fn(Dynamic) -> Result(b, List(DecodeError))
Decode an argument only by a short name
Examples
// data: ["-n", "Lucy"]
use name <- clad.short_name("n", dynamic.string)
// -> "Lucy"
pub fn toggle(
long_name long_name: String,
short_name short_name: String,
then next: fn(Bool) ->
fn(Dynamic) -> Result(a, List(DecodeError)),
) -> fn(Dynamic) -> Result(a, List(DecodeError))
A decoder that decodes Bool arguments.
Toggles do not need an explicit value. If the flag exists it is True
,
and False
if it is missing. (i.e. --verbose
)
Examples
// data: ["-v"]
use verbose <- clad.toggle(long_name: "verbose", short_name: "v")
// -> True
// data: []
use verbose <- clad.toggle(long_name: "verbose", short_name: "v")
// -> False