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([])
Search Document