diff --git a/README.md b/README.md index e288203..87b1b6e 100644 --- a/README.md +++ b/README.md @@ -25,7 +25,7 @@ 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)) @@ -33,11 +33,11 @@ pub type Student { 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 @@ -51,7 +51,7 @@ 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)) @@ -59,11 +59,11 @@ pub type Student { 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 diff --git a/gleam.toml b/gleam.toml index 9484a3d..7cbc9f8 100644 --- a/gleam.toml +++ b/gleam.toml @@ -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. @@ -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] diff --git a/manifest.toml b/manifest.toml index e5957a9..615dac9 100644 --- a/manifest.toml +++ b/manifest.toml @@ -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" } diff --git a/src/clad.gleam b/src/clad.gleam index d1d491e..d744763 100644 --- a/src/clad.gleam +++ b/src/clad.gleam @@ -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 //// @@ -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) @@ -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 @@ -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) @@ -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 @@ -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:)) /// } /// @@ -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 @@ -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"]) @@ -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) @@ -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) } } @@ -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) @@ -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) } } @@ -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 { diff --git a/test/clad_test.gleam b/test/clad_test.gleam index d63e239..461c81f 100644 --- a/test/clad_test.gleam +++ b/test/clad_test.gleam @@ -1,6 +1,5 @@ import clad -import decode/zero -import gleam/dynamic.{DecodeError} +import gleam/dynamic/decode.{DecodeError} import gleeunit import gleeunit/should @@ -13,41 +12,41 @@ type Options { } pub fn decode_test() { - clad.opt("foo", "f", zero.string, zero.success) + clad.opt("foo", "f", decode.string, decode.success) |> clad.decode(["-f", "hello"], _) |> should.equal(Ok("hello")) - clad.opt("foo", "f", zero.string, zero.success) + clad.opt("foo", "f", decode.string, decode.success) |> clad.decode(["--foo", "hello"], _) |> should.equal(Ok("hello")) - zero.field("b", zero.int, zero.success) + decode.field("b", decode.int, decode.success) |> clad.decode(["-b", "1"], _) |> should.equal(Ok(1)) - clad.flag("baz", "z", zero.success) + clad.flag("baz", "z", decode.success) |> clad.decode(["-z"], _) |> should.equal(Ok(True)) - clad.flag("baz", "z", zero.success) + clad.flag("baz", "z", decode.success) |> clad.decode([], _) |> should.equal(Ok(False)) - zero.field("q", zero.float, zero.success) + decode.field("q", decode.float, decode.success) |> clad.decode(["-q", "2.5"], _) |> should.equal(Ok(2.5)) - zero.field("z", zero.float, zero.success) + decode.field("z", decode.float, decode.success) |> clad.decode([], _) |> should.be_error let decoder = { - use foo <- clad.opt("foo", "f", zero.string) - use bar <- clad.opt("bar", "b", zero.int) + use foo <- clad.opt("foo", "f", decode.string) + use bar <- clad.opt("bar", "b", decode.int) use baz <- clad.flag("baz", "z") - use qux <- clad.opt("qux", "q", zero.float) + use qux <- clad.opt("qux", "q", decode.float) use names <- clad.positional_arguments - zero.success(Options(foo:, bar:, baz:, qux:, names:)) + decode.success(Options(foo:, bar:, baz:, qux:, names:)) } // all fields set @@ -79,21 +78,21 @@ pub fn decode_test() { } pub fn decode_errors_test() { - zero.field("f", zero.string, zero.success) + decode.field("f", decode.string, decode.success) |> clad.decode(["--bar", "hello"], _) |> should.equal(Error([DecodeError("Field", "Nothing", ["f"])])) - zero.field("foo", zero.string, zero.success) + decode.field("foo", decode.string, decode.success) |> clad.decode(["--foo", "1"], _) |> should.equal(Error([DecodeError("String", "Int", ["foo"])])) let decoder = { - use foo <- clad.opt("foo", "f", zero.string) - use bar <- clad.opt("bar", "b", zero.int) + use foo <- clad.opt("foo", "f", decode.string) + use bar <- clad.opt("bar", "b", decode.int) use baz <- clad.flag("baz", "z") - use qux <- clad.opt("qux", "q", zero.float) + use qux <- clad.opt("qux", "q", decode.float) use names <- clad.positional_arguments - zero.success(Options(foo:, bar:, baz:, qux:, names:)) + decode.success(Options(foo:, bar:, baz:, qux:, names:)) } // no fields @@ -139,22 +138,22 @@ pub fn decode_errors_test() { } pub fn opt_test() { - clad.opt("foo", "f", zero.string, zero.success) + clad.opt("foo", "f", decode.string, decode.success) |> clad.decode(["--foo", "hello"], _) |> should.equal(Ok("hello")) - clad.opt("foo", "f", zero.string, zero.success) + clad.opt("foo", "f", decode.string, decode.success) |> clad.decode(["-f", "hello"], _) |> should.equal(Ok("hello")) - // clad.opt("foo", "f", zero.string, zero.success) + // clad.opt("foo", "f", decode.string, decode.success) // |> clad.decode([], _) // |> should.equal(Error([DecodeError("String", "Nothing", ["f"])])) - // clad.opt("foo", "f", zero.optional(zero.string), zero.success) + // clad.opt("foo", "f", decode.optional(decode.string), decode.success) // |> clad.decode(["-f", "hello"], _) // |> should.equal(Ok(Some("hello"))) - // clad.opt("foo", "f", zero.optional(zero.string), zero.success) + // clad.opt("foo", "f", decode.optional(decode.string), decode.success) // |> clad.decode([], _) // |> should.equal(Ok(None)) } @@ -162,7 +161,7 @@ pub fn opt_test() { pub fn flag_test() { let decoder = { use verbose <- clad.flag("verbose", "v") - zero.success(verbose) + decode.success(verbose) } clad.decode(["-v"], decoder) @@ -183,10 +182,10 @@ pub fn flag_test() { pub fn positional_arguments_test() { let decoder = { - use a <- zero.field("a", zero.bool) - use b <- zero.field("b", zero.int) + use a <- decode.field("a", decode.bool) + use b <- decode.field("b", decode.int) use c <- clad.positional_arguments() - zero.success(#(a, b, c)) + decode.success(#(a, b, c)) } clad.decode(["-ab5", "foo", "--hello", "world", "bar", "baz"], decoder) @@ -198,8 +197,8 @@ pub fn positional_arguments_test() { pub fn list_test() { let decoder = { - use list <- zero.field("a", clad.list(zero.int)) - zero.success(list) + use list <- decode.field("a", clad.list(decode.int)) + decode.success(list) } clad.decode(["-a", "1", "-a", "2", "-a", "3"], decoder) diff --git a/test/examples/student.gleam b/test/examples/student.gleam index 35c2023..4d691a0 100644 --- a/test/examples/student.gleam +++ b/test/examples/student.gleam @@ -1,6 +1,6 @@ import argv import clad -import decode/zero +import gleam/dynamic/decode import gleam/string pub type Student { @@ -15,13 +15,13 @@ pub type Student { 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)) + use classes <- clad.opt("class", "c", clad.list(decode.string)) use notes <- clad.positional_arguments() let notes = string.join(notes, " ") - zero.success(Student(name:, age:, enrolled:, classes:, notes:)) + decode.success(Student(name:, age:, enrolled:, classes:, notes:)) } // args: --name=Lucy -ea8 -c math -c art -- Lucy is a star student! diff --git a/test/examples/student_simple.gleam b/test/examples/student_simple.gleam index d0bf232..82eb3b8 100644 --- a/test/examples/student_simple.gleam +++ b/test/examples/student_simple.gleam @@ -1,6 +1,6 @@ import argv import clad -import decode/zero +import gleam/dynamic/decode import gleam/string pub type Student { @@ -15,13 +15,13 @@ pub type Student { 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)) + 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)) use notes <- clad.positional_arguments() let notes = string.join(notes, " ") - zero.success(Student(name:, age:, enrolled:, classes:, notes:)) + decode.success(Student(name:, age:, enrolled:, classes:, notes:)) } // args: --name Lucy --age 8 --enrolled true --class math --class art -- Lucy is a star student!