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"
Search Document