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
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}
Clad encodes the arguments without any knowledge of your target record. Therefore 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 decoders for String
, Int
, Float
, and Bool
fields.
Clad’s bool
decoder assumes missing Bool arguments are False
:
--name Lucy --count 3
use verbose <- clad.bool(long_name: "verbose", short_name: "v")
// -> False
Clad’s decoders decode the long name first, then the short name if the long name is missing:
--name Lucy -n Joe
use name <- clad.string(long_name: "name", short_name: "n")
// -> "Lucy"
It’s common for CLI’s to have default values for arguments. Clad provides _with_default
functions for this:
--name Lucy
use count <- clad.int_with_default(
long_name: "count",
short_name: "c",
default: 1,
)
// -> 1
Decoding Records
Clad’s API is heavily inspired by (read: copied from) toy.
fn arg_decoder() {
use name <- clad.string("name", "n")
use count <- clad.int_with_default("count", "c", 1)
use verbose <- clad.bool("verbose", "v")
clad.decoded(Args(name:, count:, verbose:))
}
And 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 bool(
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.
Missing Bool arguments default to False
.
Examples
// data: ["-v"]
use verbose <- clad.bool(long_name: "verbose", short_name: "v")
// -> True
// data: []
use verbose <- clad.bool(long_name: "verbose", short_name: "v")
// -> False
pub fn bool_with_default(
long_name long_name: String,
short_name short_name: String,
default default: Bool,
then next: fn(Bool) ->
fn(Dynamic) -> Result(a, List(DecodeError)),
) -> fn(Dynamic) -> Result(a, List(DecodeError))
A decoder that decodes Bool arguments. Assigns a default value if the argument is missing.
This function is only necessary if you want to assign the default value as True
.
Examples
// data: []
use verbose <- clad.bool(
long_name: "verbose",
short_name: "v",
default: True,
)
// -> True
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.string("name", "n")
use email <- clad.string("email", "e"),
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.string("name", "n")
use email <- clad.string("email", "e"),
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 float(
long_name long_name: String,
short_name short_name: String,
then next: fn(Float) ->
fn(Dynamic) -> Result(a, List(DecodeError)),
) -> fn(Dynamic) -> Result(a, List(DecodeError))
A decoder that decodes Float arguments.
Examples
// data: ["--price", "2.50"]
use price <- clad.float(long_name: "price", short_name: "p")
// -> 2.5
pub fn float_with_default(
long_name long_name: String,
short_name short_name: String,
default default: Float,
then next: fn(Float) ->
fn(Dynamic) -> Result(a, List(DecodeError)),
) -> fn(Dynamic) -> Result(a, List(DecodeError))
A decoder that decodes Float arguments. Assigns a default value if the argument is missing.
Examples
// data: []
use price <- clad.float(
long_name: "price",
short_name: "p",
default: 2.50,
)
// -> 2.5
pub fn int(
long_name long_name: String,
short_name short_name: String,
then next: fn(Int) ->
fn(Dynamic) -> Result(a, List(DecodeError)),
) -> fn(Dynamic) -> Result(a, List(DecodeError))
A decoder that decodes Int arguments.
Examples
// data: ["-c", "2"]
use count <- clad.int(long_name: "count", short_name: "c")
// -> 2
pub fn int_with_default(
long_name long_name: String,
short_name short_name: String,
default default: Int,
then next: fn(Int) ->
fn(Dynamic) -> Result(a, List(DecodeError)),
) -> fn(Dynamic) -> Result(a, List(DecodeError))
A decoder that decodes Int arguments. Assigns a default value if the argument is missing.
Examples
// data: []
use count <- clad.int(
long_name: "count",
short_name: "c",
default: 2,
)
// -> 2
pub fn string(
long_name long_name: String,
short_name short_name: String,
then next: fn(String) ->
fn(Dynamic) -> Result(a, List(DecodeError)),
) -> fn(Dynamic) -> Result(a, List(DecodeError))
A decoder that decodes String arguments.
Examples
// data: ["--name", "Lucy"]
use name <- clad.string(long_name: "name", short_name: "n")
// -> "Lucy"
pub fn string_with_default(
long_name long_name: String,
short_name short_name: String,
default default: String,
then next: fn(String) ->
fn(Dynamic) -> Result(a, List(DecodeError)),
) -> fn(Dynamic) -> Result(a, List(DecodeError))
A decoder that decodes String arguments. Assigns a default value if the argument is missing.
Examples
// data: []
use name <- clad.string(
long_name: "name",
short_name: "n",
default: "Lucy",
)
// -> "Lucy"