mirror of
https://github.com/logos-storage/questionable.git
synced 2026-01-02 05:43:11 +00:00
Initial version of questionable
Syntactic sugar for std/options, pkg/result and pkg/stew
This commit is contained in:
commit
81ed9b652c
5
.editorconfig
Normal file
5
.editorconfig
Normal file
@ -0,0 +1,5 @@
|
||||
[*]
|
||||
indent_style = space
|
||||
insert_final_newline = true
|
||||
indent_size = 2
|
||||
trim_trailing_whitespace = true
|
||||
4
.gitignore
vendored
Normal file
4
.gitignore
vendored
Normal file
@ -0,0 +1,4 @@
|
||||
*
|
||||
!*/
|
||||
!*.*
|
||||
nimbledeps
|
||||
1
.tool-versions
Normal file
1
.tool-versions
Normal file
@ -0,0 +1 @@
|
||||
nim 1.4.4
|
||||
3
questionable.nim
Normal file
3
questionable.nim
Normal file
@ -0,0 +1,3 @@
|
||||
import ./questionable/options
|
||||
|
||||
export options
|
||||
10
questionable.nimble
Normal file
10
questionable.nimble
Normal file
@ -0,0 +1,10 @@
|
||||
version = "0.1.0"
|
||||
author = "Questionable Authors"
|
||||
description = "Elegant optional types"
|
||||
license = "MIT"
|
||||
|
||||
task test, "Runs the test suite":
|
||||
for module in ["options", "result", "stew"]:
|
||||
withDir "testmodules/" & module:
|
||||
exec "nimble install -d -y"
|
||||
exec "nimble test -y"
|
||||
8
questionable/errorban.nim
Normal file
8
questionable/errorban.nim
Normal file
@ -0,0 +1,8 @@
|
||||
## Include this file to indicate that your module does not raise Errors.
|
||||
## Disables compiler hints about unused declarations in Nim < 1.4.0
|
||||
|
||||
when (NimMajor, NimMinor, NimPatch) >= (1, 4, 0):
|
||||
{.push raises:[].}
|
||||
else:
|
||||
{.push raises: [Defect].}
|
||||
{.hint[XDeclaredButNotUsed]: off.}
|
||||
33
questionable/options.nim
Normal file
33
questionable/options.nim
Normal file
@ -0,0 +1,33 @@
|
||||
import std/options
|
||||
|
||||
include ./errorban
|
||||
|
||||
export options
|
||||
|
||||
template `?`*(T: typed): type Option[T] =
|
||||
Option[T]
|
||||
|
||||
template `.?`*(option: ?typed, field: untyped{nkIdent}): ?untyped =
|
||||
type T = type option.get.field
|
||||
if option.isSome:
|
||||
option.unsafeGet().field.some
|
||||
else:
|
||||
T.none
|
||||
|
||||
template `[]`*(option: ?typed, index: typed): ?typed =
|
||||
type T = type option.get[index]
|
||||
if option.isSome:
|
||||
option.unsafeGet()[index].some
|
||||
else:
|
||||
T.none
|
||||
|
||||
template `|?`*[T](option: ?T, fallback: T): T =
|
||||
if option.isSome:
|
||||
option.unsafeGet()
|
||||
else:
|
||||
fallback
|
||||
|
||||
template `=?`*[T](name: untyped{nkIdent}, option: ?T): bool =
|
||||
template name: T {.used.} = option.unsafeGet()
|
||||
option.isSome
|
||||
|
||||
35
questionable/results.nim
Normal file
35
questionable/results.nim
Normal file
@ -0,0 +1,35 @@
|
||||
import ./resultsbase
|
||||
|
||||
include ./errorban
|
||||
|
||||
export resultsbase
|
||||
|
||||
template `?!`*(T: typed): type Result[T, ref CatchableError] =
|
||||
Result[T, ref CatchableError]
|
||||
|
||||
template success*[T](value: T): ?!T =
|
||||
ok(?!T, value)
|
||||
|
||||
template failure*(T: type, error: ref CatchableError): ?!T =
|
||||
err(?!T, error)
|
||||
|
||||
template `.?`*(value: ?!typed, field: untyped{nkIdent}): ?!untyped =
|
||||
type T = type value.get.field
|
||||
if value.isOk:
|
||||
ok(?!T, value.unsafeGet().field)
|
||||
else:
|
||||
err(?!T, error(value))
|
||||
|
||||
template `[]`*(value: ?!typed, index: typed): ?!typed =
|
||||
type T = type value.get[index]
|
||||
if value.isOk:
|
||||
ok(?!T, value.unsafeGet()[index])
|
||||
else:
|
||||
err(?!T, error(value))
|
||||
|
||||
template `|?`*[T](value: ?!T, fallback: T): T =
|
||||
value.valueOr(fallback)
|
||||
|
||||
template `=?`*[T](name: untyped{nkIdent}, value: ?!T): bool =
|
||||
template name: T {.used.} = value.unsafeGet()
|
||||
value.isOk
|
||||
8
questionable/resultsbase.nim
Normal file
8
questionable/resultsbase.nim
Normal file
@ -0,0 +1,8 @@
|
||||
template tryImport(module) = import module
|
||||
|
||||
when compiles tryimport pkg/result:
|
||||
import pkg/result/../results
|
||||
else:
|
||||
import pkg/stew/results
|
||||
|
||||
export results
|
||||
1
testmodules/options/nim.cfg
Normal file
1
testmodules/options/nim.cfg
Normal file
@ -0,0 +1 @@
|
||||
--path:"../.."
|
||||
0
testmodules/options/nimbledeps/.keep
Normal file
0
testmodules/options/nimbledeps/.keep
Normal file
58
testmodules/options/test.nim
Normal file
58
testmodules/options/test.nim
Normal file
@ -0,0 +1,58 @@
|
||||
import std/unittest
|
||||
import pkg/questionable
|
||||
|
||||
suite "optionals":
|
||||
|
||||
test "?Type is shorthand for Option[Type]":
|
||||
check (?int is Option[int])
|
||||
check (?string is Option[string])
|
||||
check (?seq[bool] is Option[seq[bool]])
|
||||
|
||||
test ".? can be used for chaining optionals":
|
||||
let a: ?seq[int] = @[41, 42].some
|
||||
let b: ?seq[int] = seq[int].none
|
||||
check a.?len == 2.some
|
||||
check b.?len == int.none
|
||||
check a.?len.?uint8 == 2'u8.some
|
||||
check b.?len.?uint8 == uint8.none
|
||||
|
||||
test "[] can be used for indexing optionals":
|
||||
let a: ?seq[int] = @[1, 2, 3].some
|
||||
let b: ?seq[int] = seq[int].none
|
||||
check a[1] == 2.some
|
||||
check a[^1] == 3.some
|
||||
check a[0..1] == @[1, 2].some
|
||||
check b[1] == int.none
|
||||
|
||||
test "|? can be used to specify a fallback value":
|
||||
check 42.some |? 40 == 42
|
||||
check int.none |? 42 == 42
|
||||
|
||||
test "=? can be used for optional binding":
|
||||
if a =? int.none:
|
||||
check false
|
||||
|
||||
if b =? 42.some:
|
||||
check b == 42
|
||||
else:
|
||||
check false
|
||||
|
||||
while a =? 42.some:
|
||||
check a == 42
|
||||
break
|
||||
|
||||
while a =? int.none:
|
||||
check false
|
||||
break
|
||||
|
||||
test "=? can appear multiple times in conditional expression":
|
||||
if a =? 42.some and b =? "foo".some:
|
||||
check a == 42
|
||||
check b == "foo"
|
||||
else:
|
||||
check false
|
||||
|
||||
test "=? works with variable hiding":
|
||||
let a = 42.some
|
||||
if a =? a:
|
||||
check a == 42
|
||||
7
testmodules/options/test.nimble
Normal file
7
testmodules/options/test.nimble
Normal file
@ -0,0 +1,7 @@
|
||||
version = "0.1.0"
|
||||
author = "Questionable Authors"
|
||||
description = "Questionable tests for std/option"
|
||||
license = "MIT"
|
||||
|
||||
task test, "Runs the test suite":
|
||||
exec "nim c -f -r test.nim"
|
||||
1
testmodules/result/nim.cfg
Normal file
1
testmodules/result/nim.cfg
Normal file
@ -0,0 +1 @@
|
||||
--path:"../.."
|
||||
0
testmodules/result/nimbledeps/.keep
Normal file
0
testmodules/result/nimbledeps/.keep
Normal file
65
testmodules/result/test.nim
Normal file
65
testmodules/result/test.nim
Normal file
@ -0,0 +1,65 @@
|
||||
import std/unittest
|
||||
import std/strutils
|
||||
import pkg/questionable/results
|
||||
|
||||
suite "result":
|
||||
|
||||
let error = newException(CatchableError, "error")
|
||||
|
||||
test "?!Type is shorthand for Result[Type, ref CatchableError]":
|
||||
check (?!int is Result[int, ref CatchableError])
|
||||
check (?!string is Result[string, ref CatchableError])
|
||||
check (?!seq[bool] is Result[seq[bool], ref CatchableError])
|
||||
|
||||
test ".? can be used for chaining results":
|
||||
let a: ?!seq[int] = @[41, 42].success
|
||||
let b: ?!seq[int] = seq[int].failure error
|
||||
check a.?len == 2.success
|
||||
check b.?len == int.failure error
|
||||
check a.?len.?uint8 == 2'u8.success
|
||||
check b.?len.?uint8 == uint8.failure error
|
||||
|
||||
test "[] can be used for indexing optionals":
|
||||
let a: ?!seq[int] = @[1, 2, 3].success
|
||||
let b: ?!seq[int] = seq[int].failure error
|
||||
check a[1] == 2.success
|
||||
check a[^1] == 3.success
|
||||
check a[0..1] == @[1, 2].success
|
||||
check b[1] == int.failure error
|
||||
|
||||
test "|? can be used to specify a fallback value":
|
||||
check 42.success |? 40 == 42
|
||||
check int.failure(error) |? 42 == 42
|
||||
|
||||
test "=? can be used for optional binding":
|
||||
if a =? int.failure(error):
|
||||
check false
|
||||
|
||||
if b =? 42.success:
|
||||
check b == 42
|
||||
else:
|
||||
check false
|
||||
|
||||
while a =? 42.success:
|
||||
check a == 42
|
||||
break
|
||||
|
||||
while a =? int.failure(error):
|
||||
check false
|
||||
break
|
||||
|
||||
test "=? can appear multiple times in conditional expression":
|
||||
if a =? 42.success and b =? "foo".success:
|
||||
check a == 42
|
||||
check b == "foo"
|
||||
else:
|
||||
check false
|
||||
|
||||
test "=? works with variable hiding":
|
||||
let a = 42.success
|
||||
if a =? a:
|
||||
check a == 42
|
||||
|
||||
test "catch can be used to convert exceptions to results":
|
||||
check parseInt("42").catch == 42.success
|
||||
check parseInt("foo").catch.error of ValueError
|
||||
9
testmodules/result/test.nimble
Normal file
9
testmodules/result/test.nimble
Normal file
@ -0,0 +1,9 @@
|
||||
version = "0.1.0"
|
||||
author = "Questionable Authors"
|
||||
description = "Questionable tests for pkg/result"
|
||||
license = "MIT"
|
||||
|
||||
requires "result"
|
||||
|
||||
task test, "Runs the test suite":
|
||||
exec "nim c -f -r test.nim"
|
||||
1
testmodules/stew/nim.cfg
Normal file
1
testmodules/stew/nim.cfg
Normal file
@ -0,0 +1 @@
|
||||
--path:"../.."
|
||||
0
testmodules/stew/nimbledeps/.keep
Normal file
0
testmodules/stew/nimbledeps/.keep
Normal file
1
testmodules/stew/test.nim
Normal file
1
testmodules/stew/test.nim
Normal file
@ -0,0 +1 @@
|
||||
include ../result/test
|
||||
9
testmodules/stew/test.nimble
Normal file
9
testmodules/stew/test.nimble
Normal file
@ -0,0 +1,9 @@
|
||||
version = "0.1.0"
|
||||
author = "Questionable Authors"
|
||||
description = "Questionable tests for pkg/stew"
|
||||
license = "MIT"
|
||||
|
||||
requires "stew"
|
||||
|
||||
task test, "Runs the test suite":
|
||||
exec "nim c -f -r test.nim"
|
||||
Loading…
x
Reference in New Issue
Block a user