mirror of https://github.com/status-im/NimYAML.git
Implemented Option serialization. Fixes #78
This commit is contained in:
parent
f714881ae9
commit
1dfc2a3333
|
@ -5,7 +5,7 @@
|
|||
# distribution, for details about the copyright.
|
||||
|
||||
import "../yaml"
|
||||
import unittest, strutils, tables, times, math
|
||||
import unittest, strutils, tables, times, math, options
|
||||
|
||||
type
|
||||
MyTuple = tuple
|
||||
|
@ -270,6 +270,19 @@ suite "Serialization":
|
|||
var output = dump(input, tsNone, asTidy, blockOnly)
|
||||
assertStringEqual yamlDirs & "\n- 23\n- 42\n- 47", output
|
||||
|
||||
test "Load Option":
|
||||
let input = "- Some\n- !!null ~"
|
||||
var result: array[0..1, Option[string]]
|
||||
load(input, result)
|
||||
assert result[0].isSome
|
||||
assert result[0].get() == "Some"
|
||||
assert not result[1].isSome
|
||||
|
||||
test "Dump Option":
|
||||
let input = [none(int32), some(42'i32), none(int32)]
|
||||
let output = dump(input, tsNone, asTidy, blockOnly)
|
||||
assertStringEqual yamlDirs & "\n- !!null ~\n- 42\n- !!null ~", output
|
||||
|
||||
test "Load Table[int, string]":
|
||||
let input = "23: dreiundzwanzig\n42: zweiundvierzig"
|
||||
var result: Table[int32, string]
|
||||
|
|
|
@ -358,14 +358,14 @@ template capitalize(s: string): string =
|
|||
when declared(strutils.capitalizeAscii): strutils.capitalizeAscii(s)
|
||||
else: strutils.capitalize(s)
|
||||
|
||||
macro parserStates(names: varargs[untyped]): typed =
|
||||
macro parserStates(names: varargs[untyped]) =
|
||||
## generates proc declaration for each state in list like this:
|
||||
##
|
||||
## proc name(s: YamlStream, e: var YamlStreamEvent):
|
||||
## bool {.raises: [YamlParserError].}
|
||||
result = newStmtList()
|
||||
for name in names:
|
||||
let nameId = newIdentNode("state" & capitalize($name.ident))
|
||||
let nameId = newIdentNode("state" & capitalize(name.strVal))
|
||||
result.add(newProc(nameId, [ident("bool"), newIdentDefs(ident("s"),
|
||||
ident("YamlStream")), newIdentDefs(ident("e"), newNimNode(nnkVarTy).add(
|
||||
ident("YamlStreamEvent")))], newEmptyNode()))
|
||||
|
@ -378,21 +378,21 @@ proc processStateAsgns(source, target: NimNode) {.compileTime.} =
|
|||
## `state = [name]` with the appropriate code for changing states.
|
||||
for child in source.children:
|
||||
if child.kind == nnkAsgn and child[0].kind == nnkIdent:
|
||||
if $child[0].ident == "state":
|
||||
if child[0].strVal == "state":
|
||||
assert child[1].kind == nnkIdent
|
||||
var newNameId: NimNode
|
||||
if child[1].kind == nnkIdent and $child[1].ident == "stored":
|
||||
if child[1].kind == nnkIdent and child[1].strVal == "stored":
|
||||
newNameId = newDotExpr(ident("c"), ident("storedState"))
|
||||
else:
|
||||
newNameId =
|
||||
newIdentNode("state" & capitalize($child[1].ident))
|
||||
newIdentNode("state" & capitalize(child[1].strVal))
|
||||
target.add(newAssignment(newDotExpr(
|
||||
newIdentNode("s"), newIdentNode("nextImpl")), newNameId))
|
||||
continue
|
||||
elif $child[0].ident == "stored":
|
||||
elif child[0].strVal == "stored":
|
||||
assert child[1].kind == nnkIdent
|
||||
let newNameId =
|
||||
newIdentNode("state" & capitalize($child[1].ident))
|
||||
newIdentNode("state" & capitalize(child[1].strVal))
|
||||
target.add(newAssignment(newDotExpr(newIdentNode("c"),
|
||||
newIdentNode("storedState")), newNameId))
|
||||
continue
|
||||
|
@ -400,7 +400,7 @@ proc processStateAsgns(source, target: NimNode) {.compileTime.} =
|
|||
processStateAsgns(child, processed)
|
||||
target.add(processed)
|
||||
|
||||
macro parserState(name: untyped, impl: untyped): typed =
|
||||
macro parserState(name: untyped, impl: untyped) =
|
||||
## Creates a parser state. Every parser state is a proc with the signature
|
||||
##
|
||||
## proc(s: YamlStream, e: var YamlStreamEvent):
|
||||
|
@ -413,7 +413,7 @@ macro parserState(name: untyped, impl: untyped): typed =
|
|||
## `c`. You can change the parser state by a assignment `state = [newState]`.
|
||||
## The [newState] must have been declared with states(...) previously.
|
||||
let
|
||||
nameStr = $name.ident
|
||||
nameStr = name.strVal
|
||||
nameId = newIdentNode("state" & capitalize(nameStr))
|
||||
var procImpl = quote do:
|
||||
debug("state: " & `nameStr`)
|
||||
|
|
|
@ -16,7 +16,7 @@
|
|||
## type. Please consult the serialization guide on the NimYAML website for more
|
||||
## information.
|
||||
|
||||
import tables, typetraits, strutils, macros, streams, times, parseutils
|
||||
import tables, typetraits, strutils, macros, streams, times, parseutils, options
|
||||
import parser, taglib, presenter, stream, private/internal, hints
|
||||
export stream
|
||||
# *something* in here needs externally visible `==`(x,y: AnchorId),
|
||||
|
@ -708,7 +708,7 @@ proc ifNotTransient(tSym: NimNode, fieldIndex: int, content: openarray[NimNode],
|
|||
))
|
||||
|
||||
macro ensureAllFieldsPresent(s: YamlStream, t: typedesc, tIndex: int, o: typed,
|
||||
matched: typed): typed =
|
||||
matched: typed) =
|
||||
let
|
||||
dbp = defaultBitvectorProc
|
||||
defaultValues = genSym(nskConst, "defaultValues")
|
||||
|
@ -751,7 +751,7 @@ macro ensureAllFieldsPresent(s: YamlStream, t: typedesc, tIndex: int, o: typed,
|
|||
[checkMissing(s, t, tName, child, field, matched, o, defaultValues)]))
|
||||
inc(field)
|
||||
|
||||
macro fetchTransientIndex(t: typedesc, tIndex: untyped): typed =
|
||||
macro fetchTransientIndex(t: typedesc, tIndex: untyped) =
|
||||
quote do:
|
||||
when compiles(`transientBitvectorProc`(`t`)):
|
||||
const `tIndex` = `transientBitvectorProc`(`t`)
|
||||
|
@ -760,7 +760,7 @@ macro fetchTransientIndex(t: typedesc, tIndex: untyped): typed =
|
|||
|
||||
macro constructFieldValue(t: typedesc, tIndex: int, stream: untyped,
|
||||
context: untyped, name: untyped, o: untyped,
|
||||
matched: untyped, failOnUnknown: bool): typed =
|
||||
matched: untyped, failOnUnknown: bool) =
|
||||
let
|
||||
tDecl = getType(t)
|
||||
tName = $tDecl[1]
|
||||
|
@ -855,14 +855,14 @@ proc isVariantObject(t: NimNode): bool {.compileTime.} =
|
|||
if child.kind == nnkRecCase: return true
|
||||
return false
|
||||
|
||||
macro injectIgnoredKeyList(t: typedesc, ident: untyped): typed =
|
||||
macro injectIgnoredKeyList(t: typedesc, ident: untyped) =
|
||||
result = quote do:
|
||||
when compiles(`ignoredKeyListProc`(`t`)):
|
||||
const `ident` = ignoredKeyLists[`ignoredKeyListproc`(`t`)]
|
||||
else:
|
||||
const `ident` = newSeq[string]()
|
||||
|
||||
macro injectFailOnUnknownKeys(t: typedesc, ident: untyped): typed =
|
||||
macro injectFailOnUnknownKeys(t: typedesc, ident: untyped) =
|
||||
result = quote do:
|
||||
when compiles(`ignoreUnknownKeysProc`(`t`)):
|
||||
const `ident` = false
|
||||
|
@ -948,7 +948,7 @@ proc constructObject*[O: object|tuple](
|
|||
## Overridable default implementation for custom object and tuple types
|
||||
constructObjectDefault(s, c, result)
|
||||
|
||||
macro genRepresentObject(t: typedesc, value, childTagStyle: typed): typed =
|
||||
macro genRepresentObject(t: typedesc, value, childTagStyle: typed) =
|
||||
result = newStmtList()
|
||||
let tSym = genSym(nskConst, ":tSym")
|
||||
result.add(quote do:
|
||||
|
@ -1068,7 +1068,7 @@ proc representObject*[O: enum](value: O, ts: TagStyle,
|
|||
proc yamlTag*[O](T: typedesc[ref O]): TagId {.inline, raises: [].} = yamlTag(O)
|
||||
|
||||
macro constructImplicitVariantObject(s, c, r, possibleTagIds: untyped,
|
||||
t: typedesc): typed =
|
||||
t: typedesc) =
|
||||
let tDesc = getType(getType(t)[1])
|
||||
yAssert tDesc.kind == nnkObjectTy
|
||||
let recCase = tDesc[2][0]
|
||||
|
@ -1198,6 +1198,19 @@ proc constructChild*[T](s: var YamlStream, c: ConstructionContext,
|
|||
raise s.constructionError("Anchor on non-ref type")
|
||||
constructObject(s, c, result)
|
||||
|
||||
proc constructChild*[T](s: var YamlStream, c: ConstructionContext,
|
||||
result: var Option[T]) =
|
||||
## constructs an optional value. A value with a !!null tag will be loaded
|
||||
## an empty value.
|
||||
let event = s.peek()
|
||||
if event.kind == yamlScalar and event.scalarTag == yTagNull:
|
||||
result = none(T)
|
||||
discard s.next()
|
||||
else:
|
||||
var inner: T
|
||||
constructChild(s, c, inner)
|
||||
result = some(inner)
|
||||
|
||||
when defined(JS):
|
||||
# in JS, Time is a ref type. Therefore, we need this specialization so that
|
||||
# it is not handled by the general ref-type handler.
|
||||
|
@ -1323,6 +1336,16 @@ proc representChild*[O](value: ref O, ts: TagStyle, c: SerializationContext) =
|
|||
c.put(ex)
|
||||
representChild(value[], childTagStyle, c)
|
||||
|
||||
proc representChild*[T](value: Option[T], ts: TagStyle,
|
||||
c: SerializationContext) =
|
||||
## represents an optional value. If the value is missing, a !!null scalar
|
||||
## will be produced.
|
||||
if value.isSome:
|
||||
representChild(value.get(), ts, c)
|
||||
else:
|
||||
let childTagStyle = if ts == tsRootOnly: tsNone else: ts
|
||||
c.put(scalarEvent("~", yTagNull))
|
||||
|
||||
proc representChild*[O](value: O, ts: TagStyle,
|
||||
c: SerializationContext) =
|
||||
when isImplicitVariantObject(value):
|
||||
|
@ -1466,7 +1489,7 @@ macro setImplicitVariantObjectMarker(t: typedesc): untyped =
|
|||
{.fatal: "Cannot mark object with transient fields as implicit".}
|
||||
proc `implicitVariantObjectMarker`*(unused: `t`) = discard
|
||||
|
||||
template markAsImplicit*(t: typedesc): typed =
|
||||
template markAsImplicit*(t: typedesc) =
|
||||
## Mark a variant object type as implicit. This requires the type to consist
|
||||
## of nothing but a case expression and each branch of the case expression
|
||||
## containing exactly one field - with the exception that one branch may
|
||||
|
@ -1525,7 +1548,7 @@ proc fieldIdent(field: NimNode): NimNode {.compileTime.} =
|
|||
raise newException(Exception, "invalid node type (expected ident):" &
|
||||
$field.kind)
|
||||
|
||||
macro markAsTransient*(t: typedesc, field: untyped): typed =
|
||||
macro markAsTransient*(t: typedesc, field: untyped) =
|
||||
## Mark an object field as *transient*, meaning that this object field will
|
||||
## not be serialized when an object instance is dumped as YAML, and also that
|
||||
## the field is not expected to be given in YAML input that is loaded to an
|
||||
|
@ -1556,7 +1579,7 @@ macro markAsTransient*(t: typedesc, field: untyped): typed =
|
|||
transientVectors[`transientBitvectorProc`(`t`)].incl(
|
||||
getFieldIndex(`t`, `fieldName`))
|
||||
|
||||
macro setDefaultValue*(t: typedesc, field: untyped, value: typed): typed =
|
||||
macro setDefaultValue*(t: typedesc, field: untyped, value: typed) =
|
||||
## Set the default value of an object field. Fields with default values may
|
||||
## be absent in YAML input when loading an instance of the object. If the
|
||||
## field is absent in the YAML input, the default value is assigned to the
|
||||
|
@ -1590,7 +1613,7 @@ macro setDefaultValue*(t: typedesc, field: untyped, value: typed): typed =
|
|||
`defaultValueGetter`(`t`).`fieldName` = `value`
|
||||
defaultVectors[`defaultBitvectorProc`(`t`)].incl(getFieldIndex(`t`, `fieldName`))
|
||||
|
||||
macro ignoreInputKey*(t: typedesc, name: string{lit}): typed =
|
||||
macro ignoreInputKey*(t: typedesc, name: string{lit}) =
|
||||
## Tell NimYAML that when loading an object of type ``t``, any mapping key
|
||||
## named ``name`` shall be ignored. Note that this even ignores the key if
|
||||
## the value of that key is necessary to construct a value of type ``t``,
|
||||
|
@ -1613,7 +1636,7 @@ macro ignoreInputKey*(t: typedesc, name: string{lit}): typed =
|
|||
static:
|
||||
ignoredKeyLists[`ignoredKeyListProc`(`t`)].add(`name`)
|
||||
|
||||
macro ignoreUnknownKeys*(t: typedesc): typed =
|
||||
macro ignoreUnknownKeys*(t: typedesc) =
|
||||
## Tell NimYAML that when loading an object or tuple of type ``t``, any
|
||||
## mapping key that does not map to an existing field inside the object or
|
||||
## tuple shall be ignored.
|
||||
|
|
|
@ -224,7 +224,7 @@ var
|
|||
## registered URIs here to be able to generate a static compiler error
|
||||
## when the user tries to register an URI more than once.
|
||||
|
||||
template setTagUri*(t: typedesc, uri: string): typed =
|
||||
template setTagUri*(t: typedesc, uri: string) =
|
||||
## Associate the given uri with a certain type. This uri is used as YAML tag
|
||||
## when loading and dumping values of this type.
|
||||
when uri in registeredUris:
|
||||
|
@ -239,7 +239,7 @@ template setTagUri*(t: typedesc, uri: string): typed =
|
|||
proc yamlTag*(T: typedesc[t]): TagId {.inline, raises: [].} = id
|
||||
## autogenerated
|
||||
|
||||
template setTagUri*(t: typedesc, uri: string, idName: untyped): typed =
|
||||
template setTagUri*(t: typedesc, uri: string, idName: untyped) =
|
||||
## Like `setTagUri <#setTagUri.t,typedesc,string>`_, but lets
|
||||
## you choose a symbol for the `TagId <#TagId>`_ of the uri. This is only
|
||||
## necessary if you want to implement serialization / construction yourself.
|
||||
|
|
Loading…
Reference in New Issue