mirror of
https://github.com/waku-org/nwaku.git
synced 2025-01-16 09:55:07 +00:00
450 lines
11 KiB
Nim
450 lines
11 KiB
Nim
# toml-serialization
|
|
# Copyright (c) 2020 Status Research & Development GmbH
|
|
# Licensed and distributed under either of
|
|
# * MIT license: [LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT
|
|
# * Apache License, Version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0)
|
|
# at your option. This file may not be copied, modified, or distributed except according to those terms.
|
|
|
|
import
|
|
typetraits, options, strutils, tables, unicode,
|
|
faststreams/[outputs, textio], serialization,
|
|
types, private/utils
|
|
|
|
type
|
|
TomlWriter* = object
|
|
stream*: OutputStream
|
|
level: int
|
|
flags: TomlFlags
|
|
hasPrettyOutput*: bool
|
|
state: CodecState
|
|
|
|
proc init*(T: type TomlWriter,
|
|
stream: OutputStream,
|
|
flags: TomlFlags = {}): T =
|
|
result.stream = stream
|
|
result.flags = flags
|
|
result.state = TopLevel
|
|
|
|
template append(x: untyped) =
|
|
write w.stream, x
|
|
|
|
template append(x: BiggestInt, len: Positive) =
|
|
writeInt w.stream, x, len
|
|
|
|
template indent =
|
|
for i in 0 ..< w.level:
|
|
append ' '
|
|
|
|
proc writeIterable*(w: var TomlWriter, collection: auto) =
|
|
mixin writeValue
|
|
|
|
append '['
|
|
|
|
if w.hasPrettyOutput:
|
|
append '\n'
|
|
w.level += 2
|
|
indent()
|
|
|
|
var first = true
|
|
for e in collection:
|
|
if not first:
|
|
append ", "
|
|
if w.hasPrettyOutput:
|
|
append '\n'
|
|
indent()
|
|
|
|
w.writeValue(e)
|
|
first = false
|
|
|
|
if w.hasPrettyOutput:
|
|
append '\n'
|
|
w.level -= 2
|
|
indent()
|
|
|
|
append ']'
|
|
|
|
template writeArray*[T](w: var TomlWriter, elements: openArray[T]) =
|
|
writeIterable(w, elements)
|
|
|
|
proc writeValue*(w: var TomlWriter, time: TomlTime) =
|
|
append(time.hour, 2)
|
|
append ':'
|
|
append(time.minute, 2)
|
|
|
|
if TomlHourMinute in w.flags and time.second == 0 and time.subsecond == 0:
|
|
return
|
|
|
|
append ':'
|
|
append(time.second, 2)
|
|
if time.subsecond > 0:
|
|
append '.'
|
|
w.stream.writeText time.subsecond
|
|
|
|
proc writeValue*(w: var TomlWriter, date: TomlDate) =
|
|
append(date.year, 4)
|
|
append '-'
|
|
append(date.month, 2)
|
|
append '-'
|
|
append(date.day, 2)
|
|
|
|
proc writeValue*(w: var TomlWriter, x: TomlDateTime) =
|
|
if x.date.isSome:
|
|
let date = x.date.get()
|
|
writeValue(w, date)
|
|
|
|
if x.time.isSome:
|
|
let time = x.time.get()
|
|
if x.date.isSome:
|
|
append 'T'
|
|
writeValue(w, time)
|
|
|
|
if x.zone.isSome:
|
|
let zone = x.zone.get()
|
|
if zone.hourShift == 0 and
|
|
zone.minuteShift == 0 and
|
|
zone.positiveShift:
|
|
append 'Z'
|
|
return
|
|
|
|
if zone.positiveShift:
|
|
append '+'
|
|
else:
|
|
append '-'
|
|
append(zone.hourShift, 2)
|
|
append ':'
|
|
append(zone.minuteShift, 2)
|
|
|
|
proc writeValue*(w: var TomlWriter, s: string) =
|
|
const
|
|
lowEscape = {'\0'..'\31'} - {'\b', '\n', '\t', '\f', '\r'}
|
|
highEscape = {'\127'..'\255'}
|
|
|
|
append '\"'
|
|
for c in runes(s):
|
|
if c.int <= 255:
|
|
case c.char
|
|
of lowEscape, highEscape:
|
|
if TomlHexEscape in w.flags:
|
|
append "\\x"
|
|
w.stream.toHex(c.int, 2)
|
|
else:
|
|
append "\\u"
|
|
w.stream.toHex(c.int, 4)
|
|
of '\b': append "\\b"
|
|
of '\t': append "\\t"
|
|
of '\n': append "\\n"
|
|
of '\f': append "\\f"
|
|
of '\r': append "\\r"
|
|
of '\'': append "\\\'"
|
|
of '\"': append "\\\""
|
|
of '\\': append "\\\\"
|
|
else: append c.char
|
|
else:
|
|
if c.int < 0xFFFF:
|
|
append "\\u"
|
|
w.stream.toHex(c.int, 4)
|
|
else:
|
|
append "\\U"
|
|
w.stream.toHex(c.int, 8)
|
|
append '\"'
|
|
|
|
proc writeKey(w: var TomlWriter, s: string) =
|
|
const
|
|
shouldEscape = {'\0'..'\32', '.', '\"', '\'', '#', '\127'..'\255'}
|
|
for c in runes(s):
|
|
if c.int < 255:
|
|
if c.char in shouldEscape:
|
|
writeValue(w, s)
|
|
return
|
|
else:
|
|
writeValue(w, s)
|
|
return
|
|
if s.len == 0:
|
|
append "\"\""
|
|
else:
|
|
append s
|
|
|
|
proc writeKey(w: var TomlWriter, s: openArray[string]) =
|
|
for i, k in s:
|
|
writeKey(w, k)
|
|
if i < s.high:
|
|
append '.'
|
|
|
|
proc writeKey(emptyTable: var seq[string], s: openArray[string]) =
|
|
var o = memoryOutput()
|
|
var w = TomlWriter.init(o)
|
|
append '['
|
|
writeKey(w, s)
|
|
append "]\n"
|
|
emptyTable.add o.getOutput(string)
|
|
|
|
proc writeToml(w: var TomlWriter,
|
|
value: TomlValueRef,
|
|
keyList: var seq[string],
|
|
emptyTable: var seq[string],
|
|
noKey: bool = false)
|
|
|
|
proc writeInlineTable(w: var TomlWriter,
|
|
value: TomlValueRef,
|
|
keyList: var seq[string],
|
|
emptyTable: var seq[string],
|
|
noKey: bool) =
|
|
append '{'
|
|
inc w.level
|
|
let len = value.tableVal.len - 1
|
|
var i = 0
|
|
inc w.level
|
|
for k, v in value.tableVal:
|
|
writeKey(w, k)
|
|
append " = "
|
|
writeToml(w, v, keyList, emptyTable, noKey)
|
|
if i < len:
|
|
append ','
|
|
inc i
|
|
dec w.level
|
|
append '}'
|
|
dec w.level
|
|
|
|
template writeKeyValue(body: untyped) =
|
|
if noKey:
|
|
body
|
|
else:
|
|
indent()
|
|
indent()
|
|
writeKey(w, keyList)
|
|
append '='
|
|
body
|
|
append '\n'
|
|
|
|
proc writeToml(w: var TomlWriter, value:
|
|
TomlValueRef,
|
|
keyList: var seq[string],
|
|
emptyTable: var seq[string],
|
|
noKey: bool = false) =
|
|
case value.kind
|
|
of TomlKind.Int:
|
|
writeKeyValue:
|
|
w.stream.writeText value.intVal
|
|
of TomlKind.Float:
|
|
writeKeyValue:
|
|
w.stream.writeText value.floatVal
|
|
of TomlKind.Bool:
|
|
writeKeyValue:
|
|
append if value.boolVal: "true" else: "false"
|
|
of TomlKind.DateTime:
|
|
writeKeyValue:
|
|
writeValue(w, value.dateTime)
|
|
of TomlKind.String:
|
|
writeKeyValue:
|
|
writeValue(w, value.stringVal)
|
|
of TomlKind.Array:
|
|
writeKeyValue:
|
|
append '['
|
|
inc w.level
|
|
for i, x in value.arrayVal:
|
|
writeToml(w, x, keyList, emptyTable, true)
|
|
if i < value.arrayVal.high:
|
|
append ','
|
|
dec w.level
|
|
append ']'
|
|
of TomlKind.Tables:
|
|
for x in value.tablesVal:
|
|
append "[["
|
|
writeKey(w, keyList)
|
|
append "]]\n"
|
|
|
|
inc w.level
|
|
# non array member come first to
|
|
# prevent key collision
|
|
for k, v in x:
|
|
if v.kind == TomlKind.Tables:
|
|
continue
|
|
var newKeyList = @[k]
|
|
writeToml(w, v, newKeyList, emptyTable, false)
|
|
|
|
for k, v in x:
|
|
if v.kind != TomlKind.Tables:
|
|
continue
|
|
keyList.add k
|
|
writeToml(w, v, keyList, emptyTable, true)
|
|
discard keyList.pop
|
|
|
|
dec w.level
|
|
|
|
of TomlKind.InlineTable:
|
|
if w.level == 1:
|
|
writeKey(w, keyList)
|
|
append '='
|
|
writeInlineTable(w, value, keyList, emptyTable, true)
|
|
if w.level == 1:
|
|
append'\n'
|
|
|
|
of TomlKind.Table:
|
|
if value.tableVal.len == 0 and keyList.len > 0:
|
|
# empty table
|
|
writeKey(emptyTable, keyList)
|
|
|
|
for k, v in value.tableVal:
|
|
if v.kind in {TomlKind.Table, TomlKind.Tables}:
|
|
continue
|
|
|
|
inc w.level
|
|
keyList.add k
|
|
writeToml(w, v, keyList, emptyTable, noKey)
|
|
dec w.level
|
|
discard keyList.pop
|
|
|
|
for k, v in value.tableVal:
|
|
if v.kind == TomlKind.Table:
|
|
keyList.add k
|
|
writeToml(w, v, keyList, emptyTable, noKey)
|
|
discard keyList.pop
|
|
|
|
for k, v in value.tableVal:
|
|
if v.kind == TomlKind.Tables:
|
|
keyList.add k
|
|
writeToml(w, v, keyList, emptyTable, noKey)
|
|
discard keyList.pop
|
|
|
|
proc writeFieldName(w: var TomlWriter, s: string) =
|
|
w.writeKey s
|
|
append " = "
|
|
|
|
template writeArrayOfTable*[T](w: var TomlWriter, fieldName: string, list: openArray[T]) =
|
|
mixin writeValue
|
|
|
|
w.state = InsideRecord
|
|
for val in list:
|
|
append "[["
|
|
append fieldName
|
|
append "]]"
|
|
append '\n'
|
|
|
|
inc w.level
|
|
w.writeValue(val)
|
|
dec w.level
|
|
w.state = prevState
|
|
|
|
proc writeValue*(w: var TomlWriter, value: auto) =
|
|
mixin enumInstanceSerializedFields, writeValue, writeFieldIMPL
|
|
|
|
when value is TomlValueRef:
|
|
doAssert(value.kind == TomlKind.Table)
|
|
var keyList = newSeqOfCap[string](5)
|
|
var emptyTable = newSeqOfCap[string](5)
|
|
writeToml(w, value, keyList, emptyTable)
|
|
for k in emptyTable:
|
|
append k
|
|
|
|
elif value is Option:
|
|
if value.isSome:
|
|
w.writeValue value.get
|
|
|
|
elif value is bool:
|
|
append if value: "true" else: "false"
|
|
|
|
elif value is enum:
|
|
w.stream.writeText ord(value)
|
|
|
|
elif value is range:
|
|
when low(value) < 0:
|
|
w.stream.writeText int64(value)
|
|
else:
|
|
w.stream.writeText uint64(value)
|
|
|
|
elif value is SomeInteger:
|
|
w.stream.writeText value
|
|
|
|
elif value is SomeFloat:
|
|
w.stream.writeText value
|
|
|
|
elif value is (seq or array or openArray):
|
|
w.writeArray(value)
|
|
|
|
elif value is (object or tuple):
|
|
let prevState = w.state
|
|
var firstField = true
|
|
type RecordType = type value
|
|
if w.state == ExpectValue:
|
|
append '{'
|
|
if TomlInlineTableNewline in w.flags:
|
|
append '\n'
|
|
value.enumInstanceSerializedFields(fieldName, field):
|
|
type FieldType = type field
|
|
|
|
template regularFieldWriter() =
|
|
inc w.level
|
|
w.writeFieldIMPL(FieldTag[RecordType, fieldName, FieldType], field, value)
|
|
dec w.level
|
|
w.state = prevState
|
|
|
|
when FieldType isnot (object or tuple) or FieldType is (TomlSpecial or Option):
|
|
append '\n'
|
|
|
|
case w.state
|
|
of TopLevel:
|
|
when FieldType is (object or tuple) and FieldType isnot (TomlSpecial or Option):
|
|
append '['
|
|
append fieldName
|
|
append ']'
|
|
append '\n'
|
|
w.state = InsideRecord
|
|
regularFieldWriter()
|
|
elif (FieldType is (seq or array)) and (FieldType isnot (TomlSpecial)) and uTypeIsRecord(FieldType):
|
|
writeArrayOfTable(w, fieldName, field)
|
|
else:
|
|
template shouldWriteField() =
|
|
w.writeFieldName(fieldName)
|
|
w.state = ExpectValue
|
|
regularFieldWriter()
|
|
|
|
when FieldType is Option:
|
|
if field.isSome:
|
|
shouldWriteField()
|
|
else:
|
|
shouldWriteField()
|
|
|
|
of ExpectValue:
|
|
if not firstField:
|
|
append ','
|
|
if TomlInlineTableNewline in w.flags:
|
|
append'\n'
|
|
else:
|
|
append ' '
|
|
|
|
if TomlInlineTableNewline in w.flags:
|
|
indent()
|
|
indent()
|
|
|
|
w.writeFieldName(fieldName)
|
|
inc w.level
|
|
w.writeFieldIMPL(FieldTag[RecordType, fieldName, FieldType], field, value)
|
|
dec w.level
|
|
firstField = false
|
|
|
|
of InsideRecord:
|
|
indent()
|
|
indent()
|
|
w.state = ExpectValue
|
|
w.writeFieldName(fieldName)
|
|
inc w.level
|
|
w.writeFieldIMPL(FieldTag[RecordType, fieldName, FieldType], field, value)
|
|
dec w.level
|
|
w.state = prevState
|
|
append '\n'
|
|
|
|
else:
|
|
discard
|
|
|
|
if w.state == ExpectValue:
|
|
if TomlInlineTableNewline in w.flags:
|
|
append'\n'
|
|
indent()
|
|
append '}'
|
|
elif w.state == InsideRecord:
|
|
append '\n'
|
|
|
|
else:
|
|
const typeName = typetraits.name(value.type)
|
|
{.fatal: "Failed to convert to TOML an unsupported type: " & typeName.}
|