Skip to content

Commit 4825791

Browse files
author
Ryan Miville
committed
docs
1 parent d971242 commit 4825791

File tree

5 files changed

+170
-44
lines changed

5 files changed

+170
-44
lines changed

README.md

+8-8
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ Command line argument decoders for Gleam.
1010
- Clad provides primitives to build a `dynamic.Decoder` for command line arguments.
1111
- Arguments can be specified with long names (`--name`) or short names (`-n`).
1212
- Values are decoded in the form `--name value` or `--name=value`.
13-
- Boolean flags do not an explicit value. If the flag exists it is `True`, and if it is missing the value is `False`. (i.e. `--verbose`)
13+
- Boolean flags do not an explicit value. If the flag exists it is `True`, and if it is missing it is `False`. (i.e. `--verbose`)
1414

1515

1616
## Usage
@@ -19,7 +19,7 @@ Command line argument decoders for Gleam.
1919
gleam add clad
2020
```
2121

22-
This program is in [./test/example/greet.gleam](./test/example/greet.gleam)
22+
This program is in [./test/examples/greet.gleam](./test/examples/greet.gleam)
2323

2424
```gleam
2525
import argv
@@ -42,16 +42,16 @@ fn greet(args: Args) {
4242
}
4343
4444
pub fn main() {
45-
let decoder =
45+
let args =
4646
dynamic.decode3(
4747
Args,
48-
clad.arg(long_name: "name", short_name: "n", of: dynamic.string),
49-
clad.arg(long_name: "count", short_name: "c", of: dynamic.int)
50-
|> clad.with_default(1),
51-
clad.flag(long_name: "scream", short_name: "s"),
48+
clad.string(long_name: "name", short_name: "n"),
49+
clad.int(long_name: "count", short_name: "c") |> clad.with_default(1),
50+
clad.bool(long_name: "scream", short_name: "s"),
5251
)
52+
|> clad.decode(argv.load().arguments)
5353
54-
case clad.decode(argv.load().arguments, decoder) {
54+
case args {
5555
Ok(args) -> greet(args)
5656
_ ->
5757
io.println(

src/clad.gleam

+111-18
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import clad/internal/args
12
import gleam/dict
23
import gleam/dynamic.{
34
type DecodeError, type DecodeErrors, type Decoder, type Dynamic, DecodeError,
@@ -6,11 +7,34 @@ import gleam/float
67
import gleam/int
78
import gleam/list
89
import gleam/result
9-
import internal/args
1010

11+
/// Run a decoder on a list of command line arguments, decoding the value if it
12+
/// is of the desired type, or returning errors.
13+
///
14+
/// This function works well with the argv package.
15+
///
16+
/// # Examples
17+
/// ```gleam
18+
/// dynamic.decode2(
19+
/// SignUp,
20+
/// clad.string(long_name: "name", short_name: "n"),
21+
/// clad.string(long_name: "email", short_name: "e"),
22+
/// )
23+
/// |> clad.decode(["-n", "Lucy", "[email protected]"])
24+
/// // -> Ok(SignUp(name: "Lucy", email: "[email protected]"))
25+
/// ```
26+
/// with argv:
27+
/// ```gleam
28+
/// dynamic.decode2(
29+
/// SignUp,
30+
/// clad.string(long_name: "name", short_name: "n"),
31+
/// clad.string(long_name: "email", short_name: "e"),
32+
/// )
33+
/// |> clad.decode(argv.load().arguments)
34+
/// ```
1135
pub fn decode(
12-
from arguments: List(String),
13-
using decoder: Decoder(t),
36+
decoder: Decoder(t),
37+
arguments: List(String),
1438
) -> Result(t, DecodeErrors) {
1539
use arguments <- result.try(prepare_arguments(arguments))
1640
object(arguments)
@@ -36,28 +60,97 @@ fn prepare_arguments(
3660
result.all(chunked)
3761
}
3862

39-
pub fn flag(long_name long_name: String, short_name short_name: String) {
40-
arg(long_name, short_name, dynamic.bool) |> with_default(False)
63+
/// A decoder that decodes String arguments.
64+
/// # Examples
65+
/// ```gleam
66+
/// clad.string(long_name: "name", short_name: "n")
67+
/// |> clad.decode(["-n", "Lucy"])
68+
/// // -> Ok("Lucy")
69+
/// ```
70+
pub fn string(
71+
long_name long_name: String,
72+
short_name short_name: String,
73+
) -> Decoder(String) {
74+
arg(long_name, short_name, dynamic.string)
4175
}
4276

43-
pub fn arg(
77+
/// A decoder that decodes Int arguments.
78+
/// # Examples
79+
/// ```gleam
80+
/// clad.int(long_name: "count", short_name: "c")
81+
/// |> clad.decode(["-c", "2"])
82+
/// // -> Ok(2)
83+
/// ```
84+
pub fn int(
4485
long_name long_name: String,
4586
short_name short_name: String,
46-
of decoder: Decoder(t),
47-
) {
48-
dynamic.any([
49-
do_long_name(long_name, decoder),
50-
do_short_name(short_name, decoder),
51-
])
87+
) -> Decoder(Int) {
88+
arg(long_name, short_name, dynamic.int)
5289
}
5390

91+
/// A decoder that decodes Float arguments.
92+
/// # Examples
93+
/// ```gleam
94+
/// clad.float(long_name: "price", short_name: "p")
95+
/// |> clad.decode(["--price", "2.50"])
96+
/// // -> Ok(2.5)
97+
/// ```
98+
pub fn float(
99+
long_name long_name: String,
100+
short_name short_name: String,
101+
) -> Decoder(Float) {
102+
arg(long_name, short_name, dynamic.float)
103+
}
104+
105+
/// A decoder that decodes Bool arguments.
106+
/// # Examples
107+
/// ```gleam
108+
/// clad.bool(long_name: "verbose", short_name: "v")
109+
/// |> clad.decode(["-v"])
110+
/// // -> Ok(True)
111+
/// ```
112+
/// ```gleam
113+
/// clad.bool(long_name: "verbose", short_name: "v")
114+
/// |> clad.decode([])
115+
/// // -> Ok(False)
116+
/// ```
117+
pub fn bool(
118+
long_name long_name: String,
119+
short_name short_name: String,
120+
) -> Decoder(Bool) {
121+
arg(long_name, short_name, dynamic.bool) |> with_default(False)
122+
}
123+
124+
/// Provide a default value for a decoder.
125+
/// # Examples
126+
/// ```gleam
127+
/// clad.int(long_name: "count", short_name: "c") |> clad.with_default(1)
128+
/// |> clad.decode([])
129+
/// // -> Ok(1)
130+
/// ```
131+
/// ```gleam
132+
/// clad.int(long_name: "count", short_name: "c") |> clad.with_default(1)
133+
/// |> clad.decode(["-c", "2"])
134+
/// // -> Ok(2)
135+
/// ```
54136
pub fn with_default(decoder: Decoder(t), default: t) -> Decoder(t) {
55137
fn(data) {
56138
use _ <- result.try_recover(decoder(data))
57139
Ok(default)
58140
}
59141
}
60142

143+
fn arg(
144+
long_name long_name: String,
145+
short_name short_name: String,
146+
of decoder: Decoder(t),
147+
) -> Decoder(t) {
148+
dynamic.any([
149+
do_long_name(long_name, decoder),
150+
do_short_name(short_name, decoder),
151+
])
152+
}
153+
61154
fn do_long_name(long_name: String, decoder: Decoder(t)) {
62155
dynamic.field("--" <> long_name, decoder)
63156
}
@@ -71,23 +164,23 @@ fn fail(expected: String, found: String) {
71164
}
72165

73166
fn parse(input: String) -> Dynamic {
74-
try_float(input)
75-
|> result.or(try_int(input))
76-
|> result.or(try_bool(input))
167+
try_parse_float(input)
168+
|> result.or(try_parse_int(input))
169+
|> result.or(try_parse_bool(input))
77170
|> result.unwrap(dynamic.from(input))
78171
}
79172

80-
fn try_float(input: String) {
173+
fn try_parse_float(input: String) {
81174
float.parse(input)
82175
|> result.map(dynamic.from)
83176
}
84177

85-
fn try_int(input: String) {
178+
fn try_parse_int(input: String) {
86179
int.parse(input)
87180
|> result.map(dynamic.from)
88181
}
89182

90-
fn try_bool(input: String) {
183+
fn try_parse_bool(input: String) {
91184
case input {
92185
"true" | "True" -> Ok(dynamic.from(True))
93186
"false" | "False" -> Ok(dynamic.from(False))
File renamed without changes.

test/clad_test.gleam

+45-12
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
import clad
2+
import clad/internal/args
23
import gleam/dynamic
34
import gleeunit
45
import gleeunit/should
5-
import internal/args
66

77
pub fn main() {
88
gleeunit.main()
@@ -13,44 +13,77 @@ type Options {
1313
}
1414

1515
pub fn decode_test() {
16+
clad.string(long_name: "foo", short_name: "f")
17+
|> clad.decode(["-f", "hello"])
18+
|> should.equal(Ok("hello"))
19+
20+
clad.int(long_name: "bar", short_name: "b")
21+
|> clad.decode(["-b", "1"])
22+
|> should.equal(Ok(1))
23+
24+
clad.bool(long_name: "baz", short_name: "z")
25+
|> clad.decode(["-z"])
26+
|> should.equal(Ok(True))
27+
28+
clad.bool(long_name: "baz", short_name: "z")
29+
|> clad.decode([])
30+
|> should.equal(Ok(False))
31+
32+
clad.float(long_name: "qux", short_name: "q")
33+
|> clad.decode(["-q", "2.5"])
34+
|> should.equal(Ok(2.5))
35+
36+
clad.float(long_name: "qux", short_name: "q")
37+
|> clad.decode([])
38+
|> should.be_error
39+
40+
clad.float(long_name: "qux", short_name: "q")
41+
|> clad.with_default(0.0)
42+
|> clad.decode([])
43+
|> should.equal(Ok(0.0))
44+
45+
clad.float(long_name: "qux", short_name: "q")
46+
|> clad.with_default(0.0)
47+
|> clad.decode(["-q", "2.5"])
48+
|> should.equal(Ok(2.5))
49+
1650
let dec =
1751
dynamic.decode4(
1852
Options,
19-
clad.arg(long_name: "foo", short_name: "f", of: dynamic.string),
20-
clad.arg(long_name: "bar", short_name: "b", of: dynamic.int),
21-
clad.flag(long_name: "baz", short_name: "z"),
22-
clad.arg(long_name: "qux", short_name: "q", of: dynamic.float)
23-
|> clad.with_default(0.0),
53+
clad.string(long_name: "foo", short_name: "f"),
54+
clad.int(long_name: "bar", short_name: "b"),
55+
clad.bool(long_name: "baz", short_name: "z"),
56+
clad.float(long_name: "qux", short_name: "q") |> clad.with_default(0.0),
2457
)
2558

2659
// all fields set
2760
let args = ["--foo", "hello", "-b", "1", "--baz", "-q", "2.5"]
28-
clad.decode(args, dec)
61+
clad.decode(dec, args)
2962
|> should.equal(Ok(Options("hello", 1, True, 2.5)))
3063

3164
// using '='
3265
let args = ["--foo=hello", "-b=1", "--baz", "-q", "2.5"]
33-
clad.decode(args, dec)
66+
clad.decode(dec, args)
3467
|> should.equal(Ok(Options("hello", 1, True, 2.5)))
3568

3669
// missing field with default value
3770
let args = ["--foo", "hello", "--bar", "1", "--baz"]
38-
clad.decode(args, dec)
71+
clad.decode(dec, args)
3972
|> should.equal(Ok(Options("hello", 1, True, 0.0)))
4073

4174
// missing flag field
4275
let args = ["--foo", "hello", "--bar", "1"]
43-
clad.decode(args, dec)
76+
clad.decode(dec, args)
4477
|> should.equal(Ok(Options("hello", 1, False, 0.0)))
4578

4679
// explicit setting flag to 'true'
4780
let args = ["--foo", "hello", "--bar", "1", "-z", "true"]
48-
clad.decode(args, dec)
81+
clad.decode(dec, args)
4982
|> should.equal(Ok(Options("hello", 1, True, 0.0)))
5083

5184
// explicit setting flag to 'false'
5285
let args = ["--foo", "hello", "--bar", "1", "-z", "false"]
53-
clad.decode(args, dec)
86+
clad.decode(dec, args)
5487
|> should.equal(Ok(Options("hello", 1, False, 0.0)))
5588
}
5689

test/examples/greet.gleam

+6-6
Original file line numberDiff line numberDiff line change
@@ -18,16 +18,16 @@ fn greet(args: Args) {
1818
}
1919

2020
pub fn main() {
21-
let decoder =
21+
let args =
2222
dynamic.decode3(
2323
Args,
24-
clad.arg(long_name: "name", short_name: "n", of: dynamic.string),
25-
clad.arg(long_name: "count", short_name: "c", of: dynamic.int)
26-
|> clad.with_default(1),
27-
clad.flag(long_name: "scream", short_name: "s"),
24+
clad.string(long_name: "name", short_name: "n"),
25+
clad.int(long_name: "count", short_name: "c") |> clad.with_default(1),
26+
clad.bool(long_name: "scream", short_name: "s"),
2827
)
28+
|> clad.decode(argv.load().arguments)
2929

30-
case clad.decode(argv.load().arguments, decoder) {
30+
case args {
3131
Ok(args) -> greet(args)
3232
_ ->
3333
io.println(

0 commit comments

Comments
 (0)