Decoder
This module implements a deserializer for KDL documents, nodes and values into different types and objects:
- char
- bool
- string
- Option[T]
- SomeNumber
- StringTableRef
- enum and HoleyEnum
- KdlVal
- seq[T] and array[I, T]
- set[Ordinal], HashSet[A] and OrderedSet[A]
- Table[string, T] and OrderedTable[string, T]
- object, ref and tuple (including object variants with multiple discriminator fields)
- Plus any type you implement.
Example:
import src/kdl/decoder import kdl type Package = object name*, version*: string authors*: Option[seq[string]] description*, licenseFile*, edition*: Option[string] Deps = Table[string, string] const doc = parseKdl(""" package { name "kdl" version "0.0.0" description "kats document language" authors "Kat Marchán <kzm@zkat.tech>" license-file "LICENSE.md" edition "2018" } dependencies { nom "6.0.1" thiserror "1.0.22" }""") const package = doc.decodeKdl(Package, "package") const dependencies = doc.decodeKdl(Deps, "dependencies") assert package == Package( name: "kdl", version: "0.0.0", authors: @["Kat Marchán <kzm@zkat.tech>"].some, description: "kats document language".some, licenseFile: "LICENSE.md".some, edition: "2018".some ) assert dependencies == {"nom": "6.0.1", "thiserror": "1.0.22"}.toTable
Custom Hooks
Init hook
With init hooks you can initialize types with default values before decoding. Use the following signature when overloading decodeInitKdl:
proc initHookKdl*[T](v: var T) =Note: by default if you change a discrimantor field of an object variant in an init hook (`v.kind = kInt`), it will throw a compile error. If you want to disable it, compile with the following flag -d:kdlDecoderNoCaseTransitionError.
Example:
import src/kdl/decoder import kdl type Foo = object x*: int proc initHookKdl*(v: var Foo) = v.x = 5 # You may also do `v = Foo(x: 5)` assert parseKdl("").decodeKdl(Foo) == Foo(x: 5)
Post hook
Post hooks are called after decoding any (default, for custom decode hooks you have to call postHookable(v) explicitly) type.
Overloads of postHook must use the following signature:
proc postHookKdl(v: var MyType)
Example:
import src/kdl/decoder import kdl type Foo = object x*: int proc postHookKdl(v: var Foo) = inc v.x assert parseKdl("x 1").decodeKdl(Foo) == Foo( x: 2) # 2 because x after postHook got incremented by one
Enum hook
Enum hooks are useful for parsing enums in a custom manner.
You can overload enumHook with two different signatures:
proc enumHookKdl(a: string, v: var MyEnum)
proc enumHookKdl(a: int, v: var MyEnum)Note: by default decoding an integer into a holey enum raises an error, to override this behaviour compile with -d:kdlDecoderAllowHoleyEnums.
Example:
import src/kdl/decoder import std/[strformat, strutils] import kdl type MyEnum = enum meNorth, meSouth, meWest, meEast proc enumHookKdl(a: string, v: var MyEnum) = case a.toLowerAscii of "north": v = meNorth of "south": v = meSouth of "west": v = meWest of "east": v = meEast else: raise newException(ValueError, &"invalid enum value {a} for {$typeof(v)}") proc enumHookKdl(a: int, v: var MyEnum) = case a of 0xbeef: v = meNorth of 0xcafe: v = meSouth of 0xface: v = meWest of 0xdead: v = meEast else: raise newException(ValueError, &"invalid enum value {a} for {$typeof(v)}") assert parseKdl(""" node "north" "south" "west" "east" """).decodeKdl(seq[MyEnum], "node") == @[meNorth, meSouth, meWest, meEast] assert parseKdl(""" node 0xbeef 0xcafe 0xface 0xdead """).decodeKdl(seq[MyEnum], "node") == @[meNorth, meSouth, meWest, meEast]
Rename hook
As its name suggests, a rename hook renames the fields of an object in any way you want.
Follow this signature when overloading renameHook:
proc renameHookKdl(_: typedesc[MyType], fieldName: var string)
Example:
import src/kdl/decoder import kdl type Foo = object kind*: string list*: seq[int] proc renameHookKdl(_: typedesc[Foo], fieldName: var string) = fieldName = case fieldName of "type": "kind" of "array": "list" else: fieldName # Here we rename "type" to "kind" and "array" to "list". assert parseKdl(""" type "string" array 1 2 3 """).decodeKdl(Foo) == Foo(kind: "string", list: @[1, 2, 3])
Decode hook
Use custom decode hooks to decode your types, your way.
To do it you have to overload the decodeKdl procedure with the following signature:
proc decodeKdl*(a: KdlSome, v: var MyType) =Where KdlSome is one of KdlDoc, KdlNode or KdlVal:
- KdlDoc is called to decode a document.
- KdlNode is called to decode a node inside a document or inside another node's children.
- KdlVal is called to decode arguments or properties of a node.
To use all the hooks explained above you don't call them with initHookKdl instead use decodeInitKdl: . decodeInitKdl instead of initHookKdl. . decodePostKdl instead of postHookKdl. . decodeEnumKdl instead of enumHookKdl. . decodeRenameKdl instead of renameHookKdl. Note: you can check the signatures of those procedures below in the documentation. Read the following example:
Example:
import src/kdl/decoder import std/times import kdl import kdl/utils # kdl/utils define some useful internal procedures such as `eqIdent`, which checks the equality of two strings ignore case, underscores and dashes in an efficient way. proc decodeKdl*(a: KdlVal, v: var DateTime) = decodeInitKdl(v) assert a.isString v = a.getString.parse("yyyy-MM-dd") decodePostKdl(v) proc decodeKdl*(a: KdlNode, v: var DateTime) = decodeInitKdl(v) assert a.args.len in {1, 3, 6} case a.args.len of 6: # year month day hour minute second v = dateTime( a.args[0].decodeKdl(int), a.args[1].decodeKdl(Month), a.args[2].decodeKdl(MonthdayRange), a.args[3].decodeKdl(HourRange), a.args[4].decodeKdl(MinuteRange), a.args[5].decodeKdl(SecondRange) ) of 3: # year month day v = dateTime( a.args[0].decodeKdl(int), a.args[1].decodeKdl(Month), a.args[2].decodeKdl(MonthdayRange), ) of 1: # yyyy-MM-dd a.args[0].decodeKdl(v) else: discard if "hour" in a.props: v.hour = a.props["hour"].getInt if "minute" in a.props: v.minute = a.props["minute"].getInt if "second" in a.props: v.second = a.props["second"].getInt if "nanosecond" in a.props: v.nanosecond = a.props["nanosecond"].getInt if "offset" in a.props: v.utcOffset = a.props["offset"].get(int) decodePostKdl(v) proc decodeKdl*(a: KdlDoc, v: var DateTime) = decodeInitKdl(v) if a.len == 0: return var year: int month: Month day: MonthdayRange = 1 hour: HourRange minute: MinuteRange second: SecondRange nanosecond: NanosecondRange for node in a: if node.name.eqIdent "year": node.decodeKdl(year) elif node.name.eqIdent "month": node.decodeKdl(month) elif node.name.eqIdent "day": node.decodeKdl(day) elif node.name.eqIdent "hour": node.decodeKdl(hour) elif node.name.eqIdent "minute": node.decodeKdl(minute) elif node.name.eqIdent "second": node.decodeKdl(second) elif node.name.eqIdent "nanosecond": node.decodeKdl(nanosecond) v = dateTime(year, month, day, hour, minute, second, nanosecond) decodePostKdl(v) # Here we use the KdlDoc overload assert parseKdl(""" year 2022 month 10 // or "October" day 15 hour 12 minute 10 """).decodeKdl(DateTime) == dateTime(2022, mOct, 15, 12, 10) # Here we use the KdlNode overload assert parseKdl("date 2022 \"October\" 15 12 04 00").decodeKdl(DateTime, "date") == dateTime(2022, mOct, 15, 12, 04) # And here we use the KdlVal overload assert parseKdl("author birthday=\"2000-10-15\" name=\"Nobody\"")[0][ "birthday"].decodeKdl(DateTime) == dateTime(2000, mOct, 15)
As you may have noticed if you looked through the API, there is decodeInitKdl and initHookable, enumHook and enumHookable. Any hook suffixed -able, actually calls the hook itself after making sure there is an overload that matches it. You should not overload these as they are meant for internal use, the reason they are exported is because when implementing your custom decode hooks you may also want to use them.
So remember: for custom behaviour, overload -hook suffixed procedures; to make use of these hooks call the -hookable suffixed procedures, you don't call these unless you want their behavior within your custom decode hooks.
All of these examples were taken out from the tests, so if you need more, check them out.
Procs
proc decodeEnumKdl[T: enum](a: auto; v: var T)
- Source Edit
proc decodeInitKdl(v: var auto)
- Source Edit
proc decodeKdl(a: KdlDoc; v: var StringTableRef) {....raises: [Exception], tags: [RootEffect], forbids: [].}
- Source Edit
proc decodePostKdl(v: var auto)
- Source Edit
proc decodeRenameKdl(a: typedesc; fieldName: string): string
- Source Edit
proc enumHookKdl(a: KdlVal; v: var enum)
- Source Edit
proc enumHookKdl[T: enum](a: int; v: var T)
- Source Edit
proc enumHookKdl[T: enum](a: string; v: var T)
- Source Edit
proc initHookKdl[T](v: var ref T)
- Source Edit
proc initHookKdl[T](v: var T)
- Source Edit
proc postHookKdl(v: var auto)
- Source Edit
proc renameHookKdl(a: typedesc; fieldName: var string)
- Source Edit