Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 11 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,19 +25,19 @@ This program is in the [examples directory](https://github.com/ryanmiville/clad/
```gleam
import argv
import clad
import decode/zero
import gleam/dynamic/decode

pub type Student {
Student(name: String, age: Int, enrolled: Bool, classes: List(String))
}

pub fn main() {
let decoder = {
use name <- zero.field("name", zero.string)
use age <- zero.field("age", zero.int)
use enrolled <- zero.field("enrolled", zero.bool)
use classes <- zero.field("class", zero.list(zero.string))
zero.success(Student(name:, age:, enrolled:, classes:))
use name <- decode.field("name", decode.string)
use age <- decode.field("age", decode.int)
use enrolled <- decode.field("enrolled", decode.bool)
use classes <- decode.field("class", decode.list(decode.string))
decode.success(Student(name:, age:, enrolled:, classes:))
}

// args: --name Lucy --age 8 --enrolled true --class math --class art
Expand All @@ -51,19 +51,19 @@ Or, for more flexibility:
```gleam
import argv
import clad
import decode/zero
import gleam/dynamic/decode

pub type Student {
Student(name: String, age: Int, enrolled: Bool, classes: List(String))
}

pub fn main() {
let decoder = {
use name <- clad.opt("name", "n", zero.string)
use age <- clad.opt("age", "a", zero.int)
use name <- clad.opt("name", "n", decode.string)
use age <- clad.opt("age", "a", decode.int)
use enrolled <- clad.flag("enrolled", "e")
use classes <- clad.opt("class", "c", clad.list(zero.string))
zero.success(Student(name:, age:, enrolled:, classes:))
use classes <- clad.opt("class", "c", clad.list(decode.string))
decode.success(Student(name:, age:, enrolled:, classes:))
}

// args: --name=Lucy -ea8 -c math -c art
Expand Down
3 changes: 1 addition & 2 deletions gleam.toml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
name = "clad"
version = "0.3.1"
version = "1.0.0"

# Fill out these fields if you intend to generate HTML documentation or publish
# your project to the Hex package manager.
Expand All @@ -14,7 +14,6 @@ repository = { type = "github", user = "ryanmiville", repo = "clad" }

[dependencies]
gleam_stdlib = ">= 0.34.0 and < 2.0.0"
decode = ">= 0.4.1 and < 1.0.0"
gleam_regexp = ">= 1.0.0 and < 2.0.0"

[dev-dependencies]
Expand Down
6 changes: 2 additions & 4 deletions manifest.toml
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,14 @@

packages = [
{ name = "argv", version = "1.0.2", build_tools = ["gleam"], requirements = [], otp_app = "argv", source = "hex", outer_checksum = "BA1FF0929525DEBA1CE67256E5ADF77A7CDDFE729E3E3F57A5BDCAA031DED09D" },
{ name = "decode", version = "0.5.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "decode", source = "hex", outer_checksum = "05E14DC95A550BA51B8774485B04894B87A898C588B9B1C920104B110AED218B" },
{ name = "gleam_json", version = "2.1.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleam_json", source = "hex", outer_checksum = "0A57FB5666E695FD2BEE74C0428A98B0FC11A395D2C7B4CDF5E22C5DD32C74C6" },
{ name = "gleam_json", version = "2.2.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleam_json", source = "hex", outer_checksum = "093214EB186A88D301795A94F0A8128C2E24CF1423997ED31A6C6CC67FC3E1A1" },
{ name = "gleam_regexp", version = "1.0.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleam_regexp", source = "hex", outer_checksum = "A3655FDD288571E90EE9C4009B719FEF59FA16AFCDF3952A76A125AF23CF1592" },
{ name = "gleam_stdlib", version = "0.46.0", build_tools = ["gleam"], requirements = [], otp_app = "gleam_stdlib", source = "hex", outer_checksum = "53940A91251A6BE9AEBB959D46E1CB45B510551D81342A52213850947732D4AB" },
{ name = "gleam_stdlib", version = "0.51.0", build_tools = ["gleam"], requirements = [], otp_app = "gleam_stdlib", source = "hex", outer_checksum = "14AFA8D3DDD7045203D422715DBB822D1725992A31DF35A08D97389014B74B68" },
{ name = "gleeunit", version = "1.2.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleeunit", source = "hex", outer_checksum = "F7A7228925D3EE7D0813C922E062BFD6D7E9310F0BEE585D3A42F3307E3CFD13" },
]

[requirements]
argv = { version = ">= 1.0.2 and < 2.0.0" }
decode = { version = ">= 0.4.1 and < 1.0.0" }
gleam_json = { version = ">= 2.0.0 and < 3.0.0" }
gleam_regexp = { version = ">= 1.0.0 and < 2.0.0" }
gleam_stdlib = { version = ">= 0.34.0 and < 2.0.0" }
Expand Down
140 changes: 53 additions & 87 deletions src/clad.gleam
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
//// 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`.
//// provides functions to decode those arguments using a `dynamic/decode.Decoder`.
////
//// # Encoding
////
Expand All @@ -24,17 +24,17 @@
////
//// # Decoding
////
//// Arguments can be decoded with a normal `zero.Decoder`
//// Arguments can be decoded with a normal `dynamic/decode.Decoder`
////
//// ```gleam
//// // args: --name Lucy --age 8 --enrolled true --class math --class art
////
//// let decoder = {
//// use name <- zero.field("name", zero.string)
//// use age <- zero.field("age", zero.int)
//// use enrolled <- zero.field("enrolled", zero.bool)
//// use classes <- zero.field("class", zero.list(zero.string))
//// zero.success(Student(name:, age:, enrolled:, classes:))
//// use name <- decode.field("name", decode.string)
//// use age <- decode.field("age", decode.int)
//// use enrolled <- decode.field("enrolled", decode.bool)
//// use classes <- decode.field("class", decode.list(decode.string))
//// decode.success(Student(name:, age:, enrolled:, classes:))
//// }
////
//// let result = clad.decode(args, decoder)
Expand All @@ -55,43 +55,16 @@
//// // args: --name Lucy --age 8 --enrolled true --class math
////
//// let decoder = {
//// use name <- zero.field("name", zero.string)
//// use age <- zero.field("age", zero.int)
//// use enrolled <- zero.field("enrolled", zero.bool)
//// use classes <- zero.field("class", clad.list(zero.string))
//// zero.success(Student(name:, age:, enrolled:, classes:))
//// use name <- decode.field("name", decode.string)
//// use age <- decode.field("age", decode.int)
//// use enrolled <- decode.field("enrolled", decode.bool)
//// use classes <- decode.field("class", clad.list(decode.string))
//// decode.success(Student(name:, age:, enrolled:, classes:))
//// }
////
//// let result = clad.decode(args, decoder)
//// assert result == Ok(Student("Lucy", 8, True, ["math"]))
//// ```
//// ## 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.
////
//// ```gleam
//// // args1: --name Lucy --age 8 --class math --class art --enrolled
//// // args2: --name Bob --age 3 --class math
////
//// let decoder = {
//// use name <- zero.field("name", zero.string)
//// use age <- zero.field("age", zero.int)
//// use enrolled <- zero.field("enrolled", clad.flag())
//// use classes <- zero.field("class", clad.list(zero.string))
//// zero.success(Student(name:, age:, enrolled:, classes:))
//// }
////
//// let result = clad.decode(args1, decoder)
//// assert result == Ok(Student("Lucy", 8, True, ["math", "art"]))
////
//// let result = clad.decode(args2, decoder)
//// assert result == Ok(Student("Bob", 3, False, ["math"]))
//// ```
////
//// ## Alternate Names
////
//// It is also common for CLI's to support long names and short names for options
Expand All @@ -100,15 +73,15 @@
//// Clad provides the `opt()` function for this.
////
//// ```gleam
//// // args1: -n Lucy -a 8 -e -c math -c art
//// // args2: --name Bob --age 3 --class math
//// // args1: -n Lucy -a 8 -e true -c math -c art
//// // args2: --name Bob --age 3 --enrolled false --class math
////
//// 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())
//// use classes <- clad.opt(long_name: "class", short_name: "c", clad.list(zero.string))
//// zero.success(Student(name:, age:, enrolled:, classes:))
//// use name <- clad.opt(long_name: "name", short_name: "n", decode.string)
//// use age <- clad.opt(long_name: "age", short_name: "a", decode.int)
//// use enrolled <- clad.opt(long_name: "enrolled", short_name: "e", decode.bool)
//// use classes <- clad.opt(long_name: "class", short_name: "c", clad.list(decode.string))
//// decode.success(Student(name:, age:, enrolled:, classes:))
//// }
////
//// let result = clad.decode(args1, decoder)
Expand All @@ -117,43 +90,36 @@
//// let result = clad.decode(args2, decoder)
//// assert result == Ok(Student("Bob", 3, False, ["math"]))
//// ```
//// ## Boolean Flags
////
//// ## Positional Arguments
//// 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.
////
//// 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. All arguments followed by a `--` will be added to the positional arguemnts.
//// Clad provides the `flag()` decoder to handle this case.
////
//// ```gleam
//// // args1: -n Lucy -ea8 -c math -c art -- Lucy is a star student!
//// // args2: --name Bob who is --age 3 --class math Bob -- -idk
//// // args1: --name Lucy --age 8 --class math --class art --enrolled
//// // args2: --name Bob --age 3 --class math
////
//// 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.opt(long_name: "class", short_name: "c", clad.list(zero.string))
//// use notes <- clad.positional_arguments()
//// let notes = string.join(notes, " ")
//// zero.success(Student(name:, age:, enrolled:, classes:, notes:))
//// use name <- clad.opt(long_name: "name", short_name: "n", decode.string)
//// use age <- clad.opt(long_name: "age", short_name: "a", decode.int)
//// use enrolled <- clad.flag(long_name: "enrolled", short_name: "e")
//// use classes <- clad.opt(long_name: "class", short_name: "c", clad.list(decode.string))
//// decode.success(Student(name:, age:, enrolled:, classes:))
//// }
////
//// let result = clad.decode(args1, decoder)
//// let assert Ok(Student(
//// "Lucy",
//// 8,
//// True,
//// ["math", "art"],
//// "Lucy is a star student!",
//// )) = result
//// assert result == Ok(Student("Lucy", 8, True, ["math", "art"]))
////
//// let result = clad.decode(args2, decoder)
//// assert result == Ok(Student("Bob", 3, False, ["math"], "who is Bob -idk"))
//// assert result == Ok(Student("Bob", 3, False, ["math"]))
//// ```

import decode/zero.{type Decoder}
import gleam/dict.{type Dict}
import gleam/dynamic.{type Dynamic}
import gleam/dynamic
import gleam/dynamic/decode.{type DecodeError, type Decoder, type Dynamic}
import gleam/float
import gleam/int
import gleam/list
Expand Down Expand Up @@ -182,8 +148,8 @@ type State {
/// // args: --name Lucy --email=lucy@example.com
///
/// let decoder = {
/// use name <- zero.field("name", dynamic.string)
/// use email <- zero.field("email", dynamic.string),
/// use name <- decode.field("name", dynamic.string)
/// use email <- decode.field("email", dynamic.string),
/// clad.decoded(SignUp(name:, email:))
/// }
///
Expand All @@ -193,10 +159,10 @@ type State {
pub fn decode(
args: List(String),
decoder: Decoder(t),
) -> Result(t, List(dynamic.DecodeError)) {
) -> Result(t, List(DecodeError)) {
parse(args)
|> to_dynamic
|> zero.run(decoder)
|> decode.run(decoder)
}

/// Get all of the unnamed, positional arguments
Expand All @@ -205,7 +171,7 @@ pub fn decode(
/// ```gleam
/// let decoder = {
/// use positional <- clad.positional_arguments
/// zero.success(positional)
/// decode.success(positional)
/// }
/// let result = clad.decode(["-a1", "hello", "-b", "2", "world"], decoder)
/// assert result == Ok(["hello", "world"])
Expand All @@ -219,15 +185,15 @@ pub fn decode(
pub fn positional_arguments(
next: fn(List(String)) -> Decoder(final),
) -> Decoder(final) {
use args <- zero.field(positional_arg_name, zero.list(zero.string))
use args <- decode.field(positional_arg_name, decode.list(decode.string))
next(args)
}

/// Decode a command line flag as a Bool. Returns False if value is not present
/// ```gleam
/// let decoder = {
/// use verbose <- clad.flag("verbose", "v", clad.flag())
/// zero.success(verbose)
/// use verbose <- clad.flag("verbose", "v", decode.bool)
/// decode.success(verbose)
/// }
/// let result = clad.decode(["-v"], decoder)
/// assert result == Ok(True)
Expand All @@ -243,10 +209,10 @@ pub fn flag(
short_name: String,
next: fn(Bool) -> Decoder(final),
) -> Decoder(final) {
use value <- optional_field(long_name, zero.bool)
use value <- optional_field(long_name, decode.bool)
case value {
Some(v) -> next(v)
None -> zero.optional_field(short_name, False, zero.bool, next)
None -> decode.optional_field(short_name, False, decode.bool, next)
}
}

Expand All @@ -255,14 +221,14 @@ fn optional_field(
field_decoder: Decoder(t),
next: fn(Option(t)) -> Decoder(final),
) -> Decoder(final) {
zero.optional_field(field_name, None, zero.optional(field_decoder), next)
decode.optional_field(field_name, None, decode.optional(field_decoder), next)
}

/// Decode a command line option by either a long name or short name
/// ```gleam
/// let decoder = {
/// use name <- clad.opt("name", "n", zero.string)
/// zero.success(name)
/// use name <- clad.opt("name", "n", decode.string)
/// decode.success(name)
/// }
///
/// let result = clad.decode(["--name", "Lucy"], decoder)
Expand All @@ -280,7 +246,7 @@ pub fn opt(
use value <- optional_field(long_name, field_decoder)
case value {
Some(v) -> next(v)
None -> zero.field(short_name, field_decoder, next)
None -> decode.field(short_name, field_decoder, next)
}
}

Expand All @@ -289,15 +255,15 @@ pub fn opt(
/// encoded as the inner type rather than a list.
/// ```gleam
/// let decoder = {
/// use classes <- zero.field("class", clad.list(zero.string))
/// zero.success(classes)
/// use classes <- decode.field("class", clad.list(decode.string))
/// decode.success(classes)
/// }
/// let result = clad.decode(["--class", "art"], decoder)
/// assert result == Ok(["art"])
/// ```
pub fn list(of inner: Decoder(a)) -> Decoder(List(a)) {
let single = inner |> zero.map(list.wrap)
zero.one_of(zero.list(inner), [single])
let single = inner |> decode.map(list.wrap)
decode.one_of(decode.list(inner), [single])
}

fn parse(args: List(String)) -> State {
Expand Down
Loading
Loading