Skip to content

Commit

Permalink
constraint functions naming
Browse files Browse the repository at this point in the history
  • Loading branch information
TanklesXL committed Apr 22, 2024
1 parent 2b4944c commit a0ca243
Show file tree
Hide file tree
Showing 3 changed files with 111 additions and 102 deletions.
77 changes: 6 additions & 71 deletions src/glint.gleam
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import gleam/string
import gleam/string_builder as sb
import gleam_community/ansi
import gleam_community/colour.{type Colour}
import glint/constraint
import snag.{type Snag}

// --- CONFIGURATION ---
Expand Down Expand Up @@ -819,75 +820,6 @@ fn string_map(s: String, f: fn(String) -> String) -> String {

// ----- FLAGS -----

/// Constraint type for verifying flag values
///
pub type Constraint(a) =
fn(a) -> snag.Result(a)

/// one_of returns a Constraint that ensures the parsed flag value is
/// one of the allowed values.
///
pub fn one_of(allowed: List(a)) -> Constraint(a) {
let allowed_set = set.from_list(allowed)
fn(val: a) -> snag.Result(a) {
case set.contains(allowed_set, val) {
True -> Ok(val)
False ->
snag.error(
"invalid value '"
<> string.inspect(val)
<> "', must be one of: ["
<> {
allowed
|> list.map(fn(a) { "'" <> string.inspect(a) <> "'" })
|> string.join(", ")
}
<> "]",
)
}
}
}

/// none_of returns a Constraint that ensures the parsed flag value is not one of the disallowed values.
///
pub fn none_of(disallowed: List(a)) -> Constraint(a) {
let disallowed_set = set.from_list(disallowed)
fn(val: a) -> snag.Result(a) {
case set.contains(disallowed_set, val) {
False -> Ok(val)
True ->
snag.error(
"invalid value '"
<> string.inspect(val)
<> "', must not be one of: ["
<> {
{
disallowed
|> list.map(fn(a) { "'" <> string.inspect(a) <> "'" })
|> string.join(", ")
<> "]"
}
},
)
}
}
}

/// each is a convenience function for applying a Constraint(a) to a List(a).
/// This is useful because the default behaviour for constraints on lists is that they will apply to the list as a whole.
///
/// For example, to apply one_of to all items in a `List(Int)`:
/// ```gleam
/// [1, 2, 3, 4] |> one_of |> each
/// ```
pub fn each(constraint: Constraint(a)) -> Constraint(List(a)) {
fn(l: List(a)) -> snag.Result(List(a)) {
l
|> list.try_map(constraint)
|> result.replace(l)
}
}

/// FlagEntry inputs must start with this prefix
///
const prefix = "--"
Expand Down Expand Up @@ -1046,15 +978,18 @@ fn build_flag(fb: Flag(a)) -> FlagEntry {

/// attach a constraint to a flag
///
pub fn constraint(builder: Flag(a), constraint: Constraint(a)) -> Flag(a) {
pub fn constraint(
builder: Flag(a),
constraint: constraint.Constraint(a),
) -> Flag(a) {
Flag(..builder, parser: wrap_with_constraint(builder.parser, constraint))
}

/// attach a Constraint(a) to a Parser(a,Snag)
/// this function should not be used directly unless
fn wrap_with_constraint(
p: Parser(a, Snag),
constraint: Constraint(a),
constraint: constraint.Constraint(a),
) -> Parser(a, Snag) {
fn(input: String) -> snag.Result(a) { attempt(p(input), constraint) }
}
Expand Down
73 changes: 73 additions & 0 deletions src/glint/constraint.gleam
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import gleam/list
import gleam/result
import gleam/set
import gleam/string
import snag

/// Constraint type for verifying flag values
///
pub type Constraint(a) =
fn(a) -> snag.Result(a)

/// returns a Constraint that ensures the parsed flag value is one of the allowed values.
///
pub fn one_of(allowed: List(a)) -> Constraint(a) {
let allowed_set = set.from_list(allowed)
fn(val: a) -> snag.Result(a) {
case set.contains(allowed_set, val) {
True -> Ok(val)
False ->
snag.error(
"invalid value '"
<> string.inspect(val)
<> "', must be one of: ["
<> {
allowed
|> list.map(fn(a) { "'" <> string.inspect(a) <> "'" })
|> string.join(", ")
}
<> "]",
)
}
}
}

/// returns a Constraint that ensures the parsed flag value is not one of the disallowed values.
///
pub fn none_of(disallowed: List(a)) -> Constraint(a) {
let disallowed_set = set.from_list(disallowed)
fn(val: a) -> snag.Result(a) {
case set.contains(disallowed_set, val) {
False -> Ok(val)
True ->
snag.error(
"invalid value '"
<> string.inspect(val)
<> "', must not be one of: ["
<> {
{
disallowed
|> list.map(fn(a) { "'" <> string.inspect(a) <> "'" })
|> string.join(", ")
<> "]"
}
},
)
}
}
}

/// each is a convenience function for applying a Constraint(a) to a List(a).
/// This is useful because the default behaviour for constraints on lists is that they will apply to the list as a whole.
///
/// For example, to apply one_of to all items in a `List(Int)`:
/// ```gleam
/// [1, 2, 3, 4] |> one_of |> each
/// ```
pub fn each(constraint: Constraint(a)) -> Constraint(List(a)) {
fn(l: List(a)) -> snag.Result(List(a)) {
l
|> list.try_map(constraint)
|> result.replace(l)
}
}
63 changes: 32 additions & 31 deletions test/contraint_test.gleam
Original file line number Diff line number Diff line change
@@ -1,63 +1,64 @@
import gleeunit/should
import glint.{each, none_of, one_of}
import glint
import glint/constraint

pub fn one_of_test() {
1
|> one_of([1, 2, 3])
|> constraint.one_of([1, 2, 3])
|> should.equal(Ok(1))

1
|> one_of([2, 3, 4])
|> constraint.one_of([2, 3, 4])
|> should.be_error()

[1, 2, 3]
|> {
[5, 4, 3, 2, 1]
|> one_of
|> each
|> constraint.one_of
|> constraint.each
}
|> should.equal(Ok([1, 2, 3]))

[1, 6, 3]
|> {
[5, 4, 3, 2, 1]
|> one_of
|> each
|> constraint.one_of
|> constraint.each
}
|> should.be_error()
}

pub fn none_of_test() {
1
|> none_of([1, 2, 3])
|> constraint.none_of([1, 2, 3])
|> should.be_error

1
|> glint.none_of([2, 3, 4])
|> constraint.none_of([2, 3, 4])
|> should.equal(Ok(1))

[1, 2, 3]
|> {
[4, 5, 6, 7, 8]
|> none_of
|> each
|> constraint.none_of
|> constraint.each
}
|> should.equal(Ok([1, 2, 3]))

[1, 6, 3]
|> {
[4, 5, 6, 7, 8]
|> none_of
|> each
|> constraint.none_of
|> constraint.each
}
|> should.be_error()
}

pub fn flag_one_of_none_of_test() {
let #(test_flag, success, failure) = #(
glint.int("i")
|> glint.constraint(one_of([1, 2, 3]))
|> glint.constraint(none_of([4, 5, 6])),
|> glint.constraint(constraint.one_of([1, 2, 3]))
|> glint.constraint(constraint.none_of([4, 5, 6])),
"1",
"6",
)
Expand Down Expand Up @@ -86,13 +87,13 @@ pub fn flag_one_of_none_of_test() {
glint.ints("li")
|> glint.constraint(
[1, 2, 3]
|> one_of
|> each,
|> constraint.one_of
|> constraint.each,
)
|> glint.constraint(
[4, 5, 6]
|> none_of
|> each,
|> constraint.none_of
|> constraint.each,
),
"1,1,1",
"2,2,6",
Expand Down Expand Up @@ -120,8 +121,8 @@ pub fn flag_one_of_none_of_test() {

let #(test_flag, success, failure) = #(
glint.float("f")
|> glint.constraint(one_of([1.0, 2.0, 3.0]))
|> glint.constraint(none_of([4.0, 5.0, 6.0])),
|> glint.constraint(constraint.one_of([1.0, 2.0, 3.0]))
|> glint.constraint(constraint.none_of([4.0, 5.0, 6.0])),
"1.0",
"6.0",
)
Expand Down Expand Up @@ -149,13 +150,13 @@ pub fn flag_one_of_none_of_test() {
glint.floats("lf")
|> glint.constraint(
[1.0, 2.0, 3.0]
|> one_of()
|> each,
|> constraint.one_of()
|> constraint.each,
)
|> glint.constraint(
[4.0, 5.0, 6.0]
|> none_of()
|> each,
|> constraint.none_of()
|> constraint.each,
),
"3.0,2.0,1.0",
"2.0,3.0,6.0",
Expand All @@ -182,8 +183,8 @@ pub fn flag_one_of_none_of_test() {

let #(test_flag, success, failure) = #(
glint.string("s")
|> glint.constraint(one_of(["t1", "t2", "t3"]))
|> glint.constraint(none_of(["t4", "t5", "t6"])),
|> glint.constraint(constraint.one_of(["t1", "t2", "t3"]))
|> glint.constraint(constraint.none_of(["t4", "t5", "t6"])),
"t3",
"t4",
)
Expand Down Expand Up @@ -212,13 +213,13 @@ pub fn flag_one_of_none_of_test() {
glint.strings("ls")
|> glint.constraint(
["t1", "t2", "t3"]
|> one_of
|> each,
|> constraint.one_of
|> constraint.each,
)
|> glint.constraint(
["t4", "t5", "t6"]
|> none_of
|> each,
|> constraint.none_of
|> constraint.each,
),
"t3,t2,t1",
"t2,t4,t1",
Expand Down

0 comments on commit a0ca243

Please sign in to comment.