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.
|
# distribution, for details about the copyright.
|
||||||
|
|
||||||
import "../yaml"
|
import "../yaml"
|
||||||
import unittest, strutils, tables, times, math
|
import unittest, strutils, tables, times, math, options
|
||||||
|
|
||||||
type
|
type
|
||||||
MyTuple = tuple
|
MyTuple = tuple
|
||||||
|
@ -270,6 +270,19 @@ suite "Serialization":
|
||||||
var output = dump(input, tsNone, asTidy, blockOnly)
|
var output = dump(input, tsNone, asTidy, blockOnly)
|
||||||
assertStringEqual yamlDirs & "\n- 23\n- 42\n- 47", output
|
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]":
|
test "Load Table[int, string]":
|
||||||
let input = "23: dreiundzwanzig\n42: zweiundvierzig"
|
let input = "23: dreiundzwanzig\n42: zweiundvierzig"
|
||||||
var result: Table[int32, string]
|
var result: Table[int32, string]
|
||||||
|
|
|
@ -358,14 +358,14 @@ template capitalize(s: string): string =
|
||||||
when declared(strutils.capitalizeAscii): strutils.capitalizeAscii(s)
|
when declared(strutils.capitalizeAscii): strutils.capitalizeAscii(s)
|
||||||
else: strutils.capitalize(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:
|
## generates proc declaration for each state in list like this:
|
||||||
##
|
##
|
||||||
## proc name(s: YamlStream, e: var YamlStreamEvent):
|
## proc name(s: YamlStream, e: var YamlStreamEvent):
|
||||||
## bool {.raises: [YamlParserError].}
|
## bool {.raises: [YamlParserError].}
|
||||||
result = newStmtList()
|
result = newStmtList()
|
||||||
for name in names:
|
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"),
|
result.add(newProc(nameId, [ident("bool"), newIdentDefs(ident("s"),
|
||||||
ident("YamlStream")), newIdentDefs(ident("e"), newNimNode(nnkVarTy).add(
|
ident("YamlStream")), newIdentDefs(ident("e"), newNimNode(nnkVarTy).add(
|
||||||
ident("YamlStreamEvent")))], newEmptyNode()))
|
ident("YamlStreamEvent")))], newEmptyNode()))
|
||||||
|
@ -378,21 +378,21 @@ proc processStateAsgns(source, target: NimNode) {.compileTime.} =
|
||||||
## `state = [name]` with the appropriate code for changing states.
|
## `state = [name]` with the appropriate code for changing states.
|
||||||
for child in source.children:
|
for child in source.children:
|
||||||
if child.kind == nnkAsgn and child[0].kind == nnkIdent:
|
if child.kind == nnkAsgn and child[0].kind == nnkIdent:
|
||||||
if $child[0].ident == "state":
|
if child[0].strVal == "state":
|
||||||
assert child[1].kind == nnkIdent
|
assert child[1].kind == nnkIdent
|
||||||
var newNameId: NimNode
|
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"))
|
newNameId = newDotExpr(ident("c"), ident("storedState"))
|
||||||
else:
|
else:
|
||||||
newNameId =
|
newNameId =
|
||||||
newIdentNode("state" & capitalize($child[1].ident))
|
newIdentNode("state" & capitalize(child[1].strVal))
|
||||||
target.add(newAssignment(newDotExpr(
|
target.add(newAssignment(newDotExpr(
|
||||||
newIdentNode("s"), newIdentNode("nextImpl")), newNameId))
|
newIdentNode("s"), newIdentNode("nextImpl")), newNameId))
|
||||||
continue
|
continue
|
||||||
elif $child[0].ident == "stored":
|
elif child[0].strVal == "stored":
|
||||||
assert child[1].kind == nnkIdent
|
assert child[1].kind == nnkIdent
|
||||||
let newNameId =
|
let newNameId =
|
||||||
newIdentNode("state" & capitalize($child[1].ident))
|
newIdentNode("state" & capitalize(child[1].strVal))
|
||||||
target.add(newAssignment(newDotExpr(newIdentNode("c"),
|
target.add(newAssignment(newDotExpr(newIdentNode("c"),
|
||||||
newIdentNode("storedState")), newNameId))
|
newIdentNode("storedState")), newNameId))
|
||||||
continue
|
continue
|
||||||
|
@ -400,7 +400,7 @@ proc processStateAsgns(source, target: NimNode) {.compileTime.} =
|
||||||
processStateAsgns(child, processed)
|
processStateAsgns(child, processed)
|
||||||
target.add(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
|
## Creates a parser state. Every parser state is a proc with the signature
|
||||||
##
|
##
|
||||||
## proc(s: YamlStream, e: var YamlStreamEvent):
|
## 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]`.
|
## `c`. You can change the parser state by a assignment `state = [newState]`.
|
||||||
## The [newState] must have been declared with states(...) previously.
|
## The [newState] must have been declared with states(...) previously.
|
||||||
let
|
let
|
||||||
nameStr = $name.ident
|
nameStr = name.strVal
|
||||||
nameId = newIdentNode("state" & capitalize(nameStr))
|
nameId = newIdentNode("state" & capitalize(nameStr))
|
||||||
var procImpl = quote do:
|
var procImpl = quote do:
|
||||||
debug("state: " & `nameStr`)
|
debug("state: " & `nameStr`)
|
||||||
|
|
|
@ -16,7 +16,7 @@
|
||||||
## type. Please consult the serialization guide on the NimYAML website for more
|
## type. Please consult the serialization guide on the NimYAML website for more
|
||||||
## information.
|
## 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
|
import parser, taglib, presenter, stream, private/internal, hints
|
||||||
export stream
|
export stream
|
||||||
# *something* in here needs externally visible `==`(x,y: AnchorId),
|
# *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,
|
macro ensureAllFieldsPresent(s: YamlStream, t: typedesc, tIndex: int, o: typed,
|
||||||
matched: typed): typed =
|
matched: typed) =
|
||||||
let
|
let
|
||||||
dbp = defaultBitvectorProc
|
dbp = defaultBitvectorProc
|
||||||
defaultValues = genSym(nskConst, "defaultValues")
|
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)]))
|
[checkMissing(s, t, tName, child, field, matched, o, defaultValues)]))
|
||||||
inc(field)
|
inc(field)
|
||||||
|
|
||||||
macro fetchTransientIndex(t: typedesc, tIndex: untyped): typed =
|
macro fetchTransientIndex(t: typedesc, tIndex: untyped) =
|
||||||
quote do:
|
quote do:
|
||||||
when compiles(`transientBitvectorProc`(`t`)):
|
when compiles(`transientBitvectorProc`(`t`)):
|
||||||
const `tIndex` = `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,
|
macro constructFieldValue(t: typedesc, tIndex: int, stream: untyped,
|
||||||
context: untyped, name: untyped, o: untyped,
|
context: untyped, name: untyped, o: untyped,
|
||||||
matched: untyped, failOnUnknown: bool): typed =
|
matched: untyped, failOnUnknown: bool) =
|
||||||
let
|
let
|
||||||
tDecl = getType(t)
|
tDecl = getType(t)
|
||||||
tName = $tDecl[1]
|
tName = $tDecl[1]
|
||||||
|
@ -855,14 +855,14 @@ proc isVariantObject(t: NimNode): bool {.compileTime.} =
|
||||||
if child.kind == nnkRecCase: return true
|
if child.kind == nnkRecCase: return true
|
||||||
return false
|
return false
|
||||||
|
|
||||||
macro injectIgnoredKeyList(t: typedesc, ident: untyped): typed =
|
macro injectIgnoredKeyList(t: typedesc, ident: untyped) =
|
||||||
result = quote do:
|
result = quote do:
|
||||||
when compiles(`ignoredKeyListProc`(`t`)):
|
when compiles(`ignoredKeyListProc`(`t`)):
|
||||||
const `ident` = ignoredKeyLists[`ignoredKeyListproc`(`t`)]
|
const `ident` = ignoredKeyLists[`ignoredKeyListproc`(`t`)]
|
||||||
else:
|
else:
|
||||||
const `ident` = newSeq[string]()
|
const `ident` = newSeq[string]()
|
||||||
|
|
||||||
macro injectFailOnUnknownKeys(t: typedesc, ident: untyped): typed =
|
macro injectFailOnUnknownKeys(t: typedesc, ident: untyped) =
|
||||||
result = quote do:
|
result = quote do:
|
||||||
when compiles(`ignoreUnknownKeysProc`(`t`)):
|
when compiles(`ignoreUnknownKeysProc`(`t`)):
|
||||||
const `ident` = false
|
const `ident` = false
|
||||||
|
@ -948,7 +948,7 @@ proc constructObject*[O: object|tuple](
|
||||||
## Overridable default implementation for custom object and tuple types
|
## Overridable default implementation for custom object and tuple types
|
||||||
constructObjectDefault(s, c, result)
|
constructObjectDefault(s, c, result)
|
||||||
|
|
||||||
macro genRepresentObject(t: typedesc, value, childTagStyle: typed): typed =
|
macro genRepresentObject(t: typedesc, value, childTagStyle: typed) =
|
||||||
result = newStmtList()
|
result = newStmtList()
|
||||||
let tSym = genSym(nskConst, ":tSym")
|
let tSym = genSym(nskConst, ":tSym")
|
||||||
result.add(quote do:
|
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)
|
proc yamlTag*[O](T: typedesc[ref O]): TagId {.inline, raises: [].} = yamlTag(O)
|
||||||
|
|
||||||
macro constructImplicitVariantObject(s, c, r, possibleTagIds: untyped,
|
macro constructImplicitVariantObject(s, c, r, possibleTagIds: untyped,
|
||||||
t: typedesc): typed =
|
t: typedesc) =
|
||||||
let tDesc = getType(getType(t)[1])
|
let tDesc = getType(getType(t)[1])
|
||||||
yAssert tDesc.kind == nnkObjectTy
|
yAssert tDesc.kind == nnkObjectTy
|
||||||
let recCase = tDesc[2][0]
|
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")
|
raise s.constructionError("Anchor on non-ref type")
|
||||||
constructObject(s, c, result)
|
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):
|
when defined(JS):
|
||||||
# in JS, Time is a ref type. Therefore, we need this specialization so that
|
# in JS, Time is a ref type. Therefore, we need this specialization so that
|
||||||
# it is not handled by the general ref-type handler.
|
# 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)
|
c.put(ex)
|
||||||
representChild(value[], childTagStyle, c)
|
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,
|
proc representChild*[O](value: O, ts: TagStyle,
|
||||||
c: SerializationContext) =
|
c: SerializationContext) =
|
||||||
when isImplicitVariantObject(value):
|
when isImplicitVariantObject(value):
|
||||||
|
@ -1466,7 +1489,7 @@ macro setImplicitVariantObjectMarker(t: typedesc): untyped =
|
||||||
{.fatal: "Cannot mark object with transient fields as implicit".}
|
{.fatal: "Cannot mark object with transient fields as implicit".}
|
||||||
proc `implicitVariantObjectMarker`*(unused: `t`) = discard
|
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
|
## 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
|
## of nothing but a case expression and each branch of the case expression
|
||||||
## containing exactly one field - with the exception that one branch may
|
## 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):" &
|
raise newException(Exception, "invalid node type (expected ident):" &
|
||||||
$field.kind)
|
$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
|
## 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
|
## 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
|
## 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(
|
transientVectors[`transientBitvectorProc`(`t`)].incl(
|
||||||
getFieldIndex(`t`, `fieldName`))
|
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
|
## 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
|
## 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
|
## 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`
|
`defaultValueGetter`(`t`).`fieldName` = `value`
|
||||||
defaultVectors[`defaultBitvectorProc`(`t`)].incl(getFieldIndex(`t`, `fieldName`))
|
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
|
## 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
|
## 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``,
|
## 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:
|
static:
|
||||||
ignoredKeyLists[`ignoredKeyListProc`(`t`)].add(`name`)
|
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
|
## 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
|
## mapping key that does not map to an existing field inside the object or
|
||||||
## tuple shall be ignored.
|
## tuple shall be ignored.
|
||||||
|
|
|
@ -224,7 +224,7 @@ var
|
||||||
## registered URIs here to be able to generate a static compiler error
|
## registered URIs here to be able to generate a static compiler error
|
||||||
## when the user tries to register an URI more than once.
|
## 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
|
## Associate the given uri with a certain type. This uri is used as YAML tag
|
||||||
## when loading and dumping values of this type.
|
## when loading and dumping values of this type.
|
||||||
when uri in registeredUris:
|
when uri in registeredUris:
|
||||||
|
@ -239,7 +239,7 @@ template setTagUri*(t: typedesc, uri: string): typed =
|
||||||
proc yamlTag*(T: typedesc[t]): TagId {.inline, raises: [].} = id
|
proc yamlTag*(T: typedesc[t]): TagId {.inline, raises: [].} = id
|
||||||
## autogenerated
|
## 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
|
## Like `setTagUri <#setTagUri.t,typedesc,string>`_, but lets
|
||||||
## you choose a symbol for the `TagId <#TagId>`_ of the uri. This is only
|
## you choose a symbol for the `TagId <#TagId>`_ of the uri. This is only
|
||||||
## necessary if you want to implement serialization / construction yourself.
|
## necessary if you want to implement serialization / construction yourself.
|
||||||
|
|
Loading…
Reference in New Issue