209 lines
5.0 KiB
Nim
Raw Normal View History

2022-05-11 10:50:05 -05:00
import std/algorithm
import std/hashes
import std/oids
import std/sequtils
import std/strutils
import pkg/questionable
import pkg/questionable/results
from pkg/stew/results as stewResults import get, isErr
2022-05-11 10:50:05 -05:00
import pkg/upraises
export hashes
push: {.upraises: [].}
type
Namespace* = object
2022-09-10 13:50:05 -06:00
field*: string
value*: string
2022-05-11 10:50:05 -05:00
Key* = object
2022-09-10 13:50:05 -06:00
namespaces*: seq[Namespace]
2022-05-11 10:50:05 -05:00
const
delimiter = ":"
separator = "/"
2022-05-11 10:50:05 -05:00
# TODO: operator/s for combining string|Namespace,string|Namespace
# TODO: lifting from ?![Namespace|Key] for various ops
proc init*(
2022-05-11 10:50:05 -05:00
T: type Namespace,
field, value: string): ?!T =
if value.strip == "":
2022-09-10 13:50:05 -06:00
return failure "value string must not be all whitespace or empty"
2022-05-11 10:50:05 -05:00
if value.contains(delimiter):
return failure "value string must not contain delimiter \"" &
delimiter & "\""
2022-05-11 10:50:05 -05:00
if value.contains(separator):
return failure "value string must not contain separator \"" &
separator & "\""
2022-05-11 10:50:05 -05:00
if field != "":
if field.strip == "":
return failure "field string must not be all whitespace"
if field.contains(delimiter):
return failure "field string must not contain delimiter \"" &
delimiter & "\""
2022-05-11 10:50:05 -05:00
if field.contains(separator):
return failure "field string must not contain separator \"" &
separator & "\""
2022-05-11 10:50:05 -05:00
2022-09-10 13:50:05 -06:00
success T(field: field, value: value)
2022-05-11 10:50:05 -05:00
proc init*(T: type Namespace, id: string): ?!T =
2022-05-11 10:50:05 -05:00
if id.strip == "":
2022-09-10 13:50:05 -06:00
return failure "id string must not be all whitespace or empty"
2022-05-11 10:50:05 -05:00
if id.contains(separator):
return failure "id string must not contain separator \"" & separator & "\""
2022-05-11 10:50:05 -05:00
if id == delimiter:
return failure "value in id string \"[field]" & delimiter &
2022-05-11 10:50:05 -05:00
"[value]\" must not be empty"
if id.count(delimiter) > 1:
return failure "id string must not contain more than one delimiter \"" &
delimiter & "\""
2022-05-11 10:50:05 -05:00
2022-09-10 13:50:05 -06:00
let
(field, value) = block:
let parts = id.split(delimiter)
2022-09-10 13:50:05 -06:00
if parts.len > 1:
(parts[0], parts[^1])
else:
("", parts[^1])
2022-05-11 10:50:05 -05:00
2022-09-10 13:50:05 -06:00
T.init(field, value)
2022-05-11 10:50:05 -05:00
proc id*(self: Namespace): string =
2022-09-10 13:50:05 -06:00
if self.field.len > 0:
self.field & delimiter & self.value
2022-09-10 13:50:05 -06:00
else:
self.value
2022-05-11 10:50:05 -05:00
proc `$`*(namespace: Namespace): string =
2022-05-11 10:50:05 -05:00
"Namespace(" & namespace.id & ")"
proc init*(T: type Key, namespaces: varargs[Namespace]): ?!T =
2022-05-11 10:50:05 -05:00
if namespaces.len == 0:
failure "namespaces must contain at least one Namespace"
else:
success T(namespaces: @namespaces)
proc init*(T: type Key, namespaces: varargs[string]): ?!T =
2022-05-11 10:50:05 -05:00
if namespaces.len == 0:
failure "namespaces must contain at least one Namespace id string"
else:
2022-09-10 13:50:05 -06:00
success T(
namespaces: namespaces.mapIt(
?Namespace.init(it)
))
2022-05-11 10:50:05 -05:00
proc init*(T: type Key, id: string): ?!T =
2022-05-11 10:50:05 -05:00
if id == "":
return failure "id string must contain at least one Namespace"
if id.strip == "":
return failure "id string must not be all whitespace"
let
nsStrs = id.split(separator).filterIt(it != "")
2022-05-11 10:50:05 -05:00
if nsStrs.len == 0:
return failure "id string must not contain only one or more separator " &
"\"" & separator & "\""
2022-05-11 10:50:05 -05:00
2022-09-10 13:50:05 -06:00
Key.init(nsStrs)
2022-05-11 10:50:05 -05:00
proc list*(self: Key): seq[Namespace] =
2022-05-11 10:50:05 -05:00
self.namespaces
proc random*(T: type Key): string =
$genOid()
2022-09-10 13:50:05 -06:00
template `[]`*(key: Key, x: auto): auto =
2022-05-11 10:50:05 -05:00
key.namespaces[x]
proc len*(self: Key): int =
2022-05-11 10:50:05 -05:00
self.namespaces.len
2022-07-15 15:28:42 -05:00
iterator items*(key: Key): Namespace =
2022-09-10 13:50:05 -06:00
for k in key.namespaces:
yield k
2022-05-11 10:50:05 -05:00
proc reversed*(self: Key): Key =
2022-05-11 10:50:05 -05:00
Key(namespaces: self.namespaces.reversed)
proc reverse*(self: Key): Key =
2022-05-11 10:50:05 -05:00
self.reversed
proc name*(self: Key): string =
2022-09-10 13:50:05 -06:00
if self.len > 0:
return self[^1].value
2022-05-11 10:50:05 -05:00
proc `type`*(self: Key): string =
2022-09-10 13:50:05 -06:00
if self.len > 0:
return self[^1].field
2022-05-11 10:50:05 -05:00
proc id*(self: Key): string =
separator & self.namespaces.mapIt(it.id).join(separator)
2022-05-11 10:50:05 -05:00
proc isTopLevel*(self: Key): bool =
2022-05-11 10:50:05 -05:00
self.len == 1
proc parent*(self: Key): ?!Key =
if self.isTopLevel:
2022-05-11 10:50:05 -05:00
failure "key has no parent"
else:
success Key(namespaces: self.namespaces[0..^2])
proc path*(self: Key): ?!Key =
2022-05-11 10:50:05 -05:00
let
parent = ? self.parent
2022-05-11 10:50:05 -05:00
2022-09-10 13:50:05 -06:00
if self[^1].field == "":
2022-05-11 10:50:05 -05:00
return success parent
success Key(namespaces: parent.namespaces & @[Namespace(value: self[^1].field)])
2022-05-11 10:50:05 -05:00
proc child*(self: Key, ns: Namespace): Key =
2022-05-11 10:50:05 -05:00
Key(namespaces: self.namespaces & @[ns])
proc `/`*(self: Key, ns: Namespace): Key =
2022-05-11 10:50:05 -05:00
self.child(ns)
proc child*(self: Key, namespaces: varargs[Namespace]): Key =
2022-05-11 10:50:05 -05:00
Key(namespaces: self.namespaces & @namespaces)
proc child*(self, key: Key): Key =
2022-05-11 10:50:05 -05:00
Key(namespaces: self.namespaces & key.namespaces)
proc `/`*(self, key: Key): Key =
2022-05-11 10:50:05 -05:00
self.child(key)
proc child*(self: Key, keys: varargs[Key]): Key =
2022-05-11 10:50:05 -05:00
Key(namespaces: self.namespaces & concat(keys.mapIt(it.namespaces)))
proc child*(self: Key, ids: varargs[string]): ?!Key =
2022-09-10 13:50:05 -06:00
success self.child(ids.filterIt(it != "").mapIt( ?Key.init(it) ))
2022-05-11 10:50:05 -05:00
proc `/`*(self: Key, id: string): ?!Key =
2022-05-11 10:50:05 -05:00
self.child(id)
proc isAncestorOf*(self, other: Key): bool =
2022-05-11 10:50:05 -05:00
if other.len <= self.len: false
else: other.namespaces[0..<self.len] == self.namespaces
proc isDescendantOf*(self, other: Key): bool =
other.isAncestorOf(self)
2022-05-11 10:50:05 -05:00
proc `$`*(key: Key): string =
2022-05-11 10:50:05 -05:00
"Key(" & key.id & ")"