NimYAML/yaml/presenter.nim

807 lines
31 KiB
Nim
Raw Normal View History

# NimYAML - YAML implementation in Nim
# (c) Copyright 2016 Felix Krause
#
# See the file "copying.txt", included in this
# distribution, for details about the copyright.
## =====================
## Module yaml.presenter
## =====================
##
## This is the presenter API, used for generating YAML character streams.
2016-09-20 19:53:38 +00:00
import streams, queues, strutils
2017-02-06 19:39:04 +00:00
import taglib, stream, private/internal, hints, parser, stream
2016-09-20 19:53:38 +00:00
type
2016-09-20 19:53:38 +00:00
PresentationStyle* = enum
## Different styles for YAML character stream output.
##
## - ``ypsMinimal``: Single-line flow-only output which tries to
## use as few characters as possible.
## - ``ypsCanonical``: Canonical YAML output. Writes all tags except
## for the non-specific tags ``?`` and ``!``, uses flow style, quotes
## all string scalars.
## - ``ypsDefault``: Tries to be as human-readable as possible. Uses
## block style by default, but tries to condense mappings and
## sequences which only contain scalar nodes into a single line using
## flow style.
## - ``ypsJson``: Omits the ``%YAML`` directive and the ``---``
## marker. Uses flow style. Flattens anchors and aliases, omits tags.
## Output will be parseable as JSON. ``YamlStream`` to dump may only
## contain one document.
## - ``ypsBlockOnly``: Formats all output in block style, does not use
## flow style at all.
psMinimal, psCanonical, psDefault, psJson, psBlockOnly
TagStyle* = enum
## Whether object should be serialized with explicit tags.
##
## - ``tsNone``: No tags will be outputted unless necessary.
## - ``tsRootOnly``: A tag will only be outputted for the root tag and
## where necessary.
## - ``tsAll``: Tags will be outputted for every object.
tsNone, tsRootOnly, tsAll
AnchorStyle* = enum
## How ref object should be serialized.
##
## - ``asNone``: No anchors will be outputted. Values present at
## multiple places in the content that should be serialized will be
## fully serialized at every occurence. If the content is cyclic, this
## will lead to an endless loop!
## - ``asTidy``: Anchors will only be generated for objects that
## actually occur more than once in the content to be serialized.
## This is a bit slower and needs more memory than ``asAlways``.
## - ``asAlways``: Achors will be generated for every ref object in the
## content to be serialized, regardless of whether the object is
## referenced again afterwards
asNone, asTidy, asAlways
NewLineStyle* = enum
## What kind of newline sequence is used when presenting.
##
## - ``nlLF``: Use a single linefeed char as newline.
## - ``nlCRLF``: Use a sequence of carriage return and linefeed as
## newline.
## - ``nlOSDefault``: Use the target operation system's default newline
## sequence (CRLF on Windows, LF everywhere else).
nlLF, nlCRLF, nlOSDefault
OutputYamlVersion* = enum
## Specify which YAML version number the presenter shall emit. The
## presenter will always emit content that is valid YAML 1.1, but by
## default will write a directive ``%YAML 1.2``. For compatibility with
## other YAML implementations, it is possible to change this here.
##
## It is also possible to specify that the presenter shall not emit any
## YAML version. The generated content is then guaranteed to be valid
## YAML 1.1 and 1.2 (but not 1.0 or any newer YAML version).
ov1_2, ov1_1, ovNone
PresentationOptions* = object
## Options for generating a YAML character stream
style*: PresentationStyle
indentationStep*: int
newlines*: NewLineStyle
outputVersion*: OutputYamlVersion
YamlPresenterJsonError* = object of Exception
## Exception that may be raised by the YAML presenter when it is
## instructed to output JSON, but is unable to do so. This may occur if:
##
## - The given `YamlStream <#YamlStream>`_ contains a map which has any
## non-scalar type as key.
## - Any float scalar bears a ``NaN`` or positive/negative infinity value
YamlPresenterOutputError* = object of Exception
## Exception that may be raised by the YAML presenter. This occurs if
## writing character data to the output stream raises any exception.
## The error that has occurred is available from ``parent``.
2016-04-02 15:48:22 +00:00
DumperState = enum
dBlockExplicitMapKey, dBlockImplicitMapKey, dBlockMapValue,
dBlockInlineMap, dBlockSequenceItem, dFlowImplicitMapKey, dFlowMapValue,
dFlowExplicitMapKey, dFlowSequenceItem, dFlowMapStart, dFlowSequenceStart
2016-04-02 15:48:22 +00:00
ScalarStyle = enum
sLiteral, sFolded, sPlain, sDoubleQuoted
2016-09-19 17:33:29 +00:00
PresenterTarget = Stream | ptr[string]
2016-09-20 19:53:38 +00:00
const
defaultPresentationOptions* =
PresentationOptions(style: psDefault, indentationStep: 2,
newlines: nlOSDefault)
proc defineOptions*(style: PresentationStyle = psDefault,
indentationStep: int = 2,
newlines: NewLineStyle = nlOSDefault,
outputVersion: OutputYamlVersion = ov1_2):
2016-09-20 19:53:38 +00:00
PresentationOptions {.raises: [].} =
## Define a set of options for presentation. Convenience proc that requires
## you to only set those values that should not equal the default.
2016-04-02 15:48:22 +00:00
PresentationOptions(style: style, indentationStep: indentationStep,
newlines: newlines, outputVersion: outputVersion)
proc inspect(scalar: string, indentation: int,
words, lines: var seq[tuple[start, finish: int]]):
2016-04-02 15:48:22 +00:00
ScalarStyle {.raises: [].} =
var
inLine = false
inWord = false
multipleSpaces = true
curWord, curLine: tuple[start, finish: int]
canUseFolded = true
canUseLiteral = true
canUsePlain = scalar.len > 0 and
scalar[0] notin {'@', '`', '|', '>', '&', '*', '!', ' ', '\t'}
for i, c in scalar:
case c
of ' ':
if inWord:
if not multipleSpaces:
curWord.finish = i - 1
inWord = false
else:
multipleSpaces = true
2016-04-02 15:48:22 +00:00
inWord = true
if not inLine:
inLine = true
curLine.start = i
# space at beginning of line will preserve previous and next
# linebreak. that is currently too complex to handle.
canUseFolded = false
of '\l':
canUsePlain = false # we don't use multiline plain scalars
curWord.finish = i - 1
if curWord.finish - curWord.start + 1 > 80 - indentation:
return if canUsePlain: sPlain else: sDoubleQuoted
words.add(curWord)
inWord = false
curWord.start = i + 1
multipleSpaces = true
if not inLine: curLine.start = i
inLine = false
curLine.finish = i - 1
if curLine.finish - curLine.start + 1 > 80 - indentation:
canUseLiteral = false
lines.add(curLine)
else:
if c in {'{', '}', '[', ']', ',', '#', '-', ':', '?', '%', '"', '\''} or
c.ord < 32: canUsePlain = false
if not inLine:
curLine.start = i
inLine = true
if not inWord:
if not multipleSpaces:
if curWord.finish - curWord.start + 1 > 80 - indentation:
return if canUsePlain: sPlain else: sDoubleQuoted
2016-04-02 15:48:22 +00:00
words.add(curWord)
curWord.start = i
inWord = true
multipleSpaces = false
if inWord:
curWord.finish = scalar.len - 1
if curWord.finish - curWord.start + 1 > 80 - indentation:
return if canUsePlain: sPlain else: sDoubleQuoted
words.add(curWord)
if inLine:
curLine.finish = scalar.len - 1
if curLine.finish - curLine.start + 1 > 80 - indentation:
canUseLiteral = false
lines.add(curLine)
if scalar.len <= 80 - indentation:
result = if canUsePlain: sPlain else: sDoubleQuoted
elif canUseLiteral: result = sLiteral
elif canUseFolded: result = sFolded
elif canUsePlain: result = sPlain
else: result = sDoubleQuoted
2016-09-19 17:33:29 +00:00
template append(target: Stream, val: string | char) =
target.write(val)
template append(target: ptr[string], val: string | char) =
target[].add(val)
proc writeDoubleQuoted(scalar: string, s: PresenterTarget, indentation: int,
newline: string)
{.raises: [YamlPresenterOutputError].} =
2016-04-02 15:48:22 +00:00
var curPos = indentation
try:
2016-09-19 17:33:29 +00:00
s.append('"')
2016-04-02 15:48:22 +00:00
curPos.inc()
for c in scalar:
if curPos == 79:
2016-09-19 17:33:29 +00:00
s.append('\\')
s.append(newline)
s.append(repeat(' ', indentation))
2016-04-02 15:48:22 +00:00
curPos = indentation
if c == ' ':
2016-09-19 17:33:29 +00:00
s.append('\\')
2016-04-02 15:48:22 +00:00
curPos.inc()
case c
of '"':
2016-09-19 17:33:29 +00:00
s.append("\\\"")
2016-04-02 15:48:22 +00:00
curPos.inc(2)
of '\l':
2016-09-19 17:33:29 +00:00
s.append("\\n")
2016-04-02 15:48:22 +00:00
curPos.inc(2)
of '\t':
2016-09-19 17:33:29 +00:00
s.append("\\t")
2016-04-02 15:48:22 +00:00
curPos.inc(2)
of '\\':
2016-09-19 17:33:29 +00:00
s.append("\\\\")
2016-04-02 15:48:22 +00:00
curPos.inc(2)
else:
if ord(c) < 32:
2016-09-19 17:33:29 +00:00
s.append("\\x" & toHex(ord(c), 2))
2016-04-02 15:48:22 +00:00
curPos.inc(4)
else:
2016-09-19 17:33:29 +00:00
s.append(c)
2016-04-02 15:48:22 +00:00
curPos.inc()
2016-09-19 17:33:29 +00:00
s.append('"')
2016-04-02 15:48:22 +00:00
except:
var e = newException(YamlPresenterOutputError,
"Error while writing to output stream")
e.parent = getCurrentException()
raise e
2016-09-19 17:33:29 +00:00
proc writeDoubleQuotedJson(scalar: string, s: PresenterTarget)
2016-04-02 15:48:22 +00:00
{.raises: [YamlPresenterOutputError].} =
try:
2016-09-19 17:33:29 +00:00
s.append('"')
2016-04-02 15:48:22 +00:00
for c in scalar:
case c
2016-09-19 17:33:29 +00:00
of '"': s.append("\\\"")
of '\\': s.append("\\\\")
of '\l': s.append("\\n")
of '\t': s.append("\\t")
of '\f': s.append("\\f")
of '\b': s.append("\\b")
2016-04-02 15:48:22 +00:00
else:
2016-09-19 17:33:29 +00:00
if ord(c) < 32: s.append("\\u" & toHex(ord(c), 4)) else: s.append(c)
s.append('"')
2016-04-02 15:48:22 +00:00
except:
var e = newException(YamlPresenterOutputError,
"Error while writing to output stream")
e.parent = getCurrentException()
raise e
2016-09-19 17:33:29 +00:00
proc writeLiteral(scalar: string, indentation, indentStep: int,
s: PresenterTarget, lines: seq[tuple[start, finish: int]],
newline: string)
2016-04-02 15:48:22 +00:00
{.raises: [YamlPresenterOutputError].} =
try:
2016-09-19 17:33:29 +00:00
s.append('|')
if scalar[^1] != '\l': s.append('-')
if scalar[0] in [' ', '\t']: s.append($indentStep)
2016-04-02 15:48:22 +00:00
for line in lines:
2016-09-19 17:33:29 +00:00
s.append(newline)
s.append(repeat(' ', indentation + indentStep))
2016-04-02 15:48:22 +00:00
if line.finish >= line.start:
2016-09-19 17:33:29 +00:00
s.append(scalar[line.start .. line.finish])
2016-04-02 15:48:22 +00:00
except:
var e = newException(YamlPresenterOutputError,
"Error while writing to output stream")
e.parent = getCurrentException()
raise e
2016-09-19 17:33:29 +00:00
proc writeFolded(scalar: string, indentation, indentStep: int,
s: PresenterTarget, words: seq[tuple[start, finish: int]],
newline: string)
2016-04-02 15:48:22 +00:00
{.raises: [YamlPresenterOutputError].} =
try:
2016-09-19 17:33:29 +00:00
s.append(">")
if scalar[^1] != '\l': s.append('-')
if scalar[0] in [' ', '\t']: s.append($indentStep)
2016-04-02 15:48:22 +00:00
var curPos = 80
for word in words:
if word.start > 0 and scalar[word.start - 1] == '\l':
2016-09-19 17:33:29 +00:00
s.append(newline & newline)
s.append(repeat(' ', indentation + indentStep))
2016-04-02 15:48:22 +00:00
curPos = indentation + indentStep
elif curPos + (word.finish - word.start) > 80:
2016-09-19 17:33:29 +00:00
s.append(newline)
s.append(repeat(' ', indentation + indentStep))
2016-04-02 15:48:22 +00:00
curPos = indentation + indentStep
else:
2016-09-19 17:33:29 +00:00
s.append(' ')
2016-04-02 15:48:22 +00:00
curPos.inc()
2016-09-19 17:33:29 +00:00
s.append(scalar[word.start .. word.finish])
2016-04-02 15:48:22 +00:00
curPos += word.finish - word.start + 1
except:
var e = newException(YamlPresenterOutputError,
"Error while writing to output stream")
e.parent = getCurrentException()
raise e
2016-09-19 17:33:29 +00:00
template safeWrite(target: PresenterTarget, s: string or char) =
try: target.append(s)
2016-04-02 15:48:22 +00:00
except:
var e = newException(YamlPresenterOutputError, "")
e.parent = getCurrentException()
raise e
2016-09-19 17:33:29 +00:00
proc startItem(target: PresenterTarget, style: PresentationStyle,
indentation: int, state: var DumperState, isObject: bool,
newline: string) {.raises: [YamlPresenterOutputError].} =
2016-04-02 15:48:22 +00:00
try:
case state
of dBlockMapValue:
2016-09-19 17:33:29 +00:00
target.append(newline)
target.append(repeat(' ', indentation))
2016-04-02 15:48:22 +00:00
if isObject or style == psCanonical:
2016-09-19 17:33:29 +00:00
target.append("? ")
2016-04-02 15:48:22 +00:00
state = dBlockExplicitMapKey
else: state = dBlockImplicitMapKey
of dBlockInlineMap: state = dBlockImplicitMapKey
of dBlockExplicitMapKey:
2016-09-19 17:33:29 +00:00
target.append(newline)
target.append(repeat(' ', indentation))
target.append(": ")
2016-04-02 15:48:22 +00:00
state = dBlockMapValue
of dBlockImplicitMapKey:
2016-09-19 17:33:29 +00:00
target.append(": ")
2016-04-02 15:48:22 +00:00
state = dBlockMapValue
of dFlowExplicitMapKey:
if style != psMinimal:
2016-09-19 17:33:29 +00:00
target.append(newline)
target.append(repeat(' ', indentation))
target.append(": ")
2016-04-02 15:48:22 +00:00
state = dFlowMapValue
of dFlowMapValue:
if (isObject and style != psMinimal) or style in [psJson, psCanonical]:
2016-09-19 17:33:29 +00:00
target.append(',' & newline & repeat(' ', indentation))
2016-04-02 15:48:22 +00:00
if style == psJson: state = dFlowImplicitMapKey
else:
2016-09-19 17:33:29 +00:00
target.append("? ")
2016-04-02 15:48:22 +00:00
state = dFlowExplicitMapKey
elif isObject and style == psMinimal:
2016-09-19 17:33:29 +00:00
target.append(", ? ")
2016-04-02 15:48:22 +00:00
state = dFlowExplicitMapKey
else:
2016-09-19 17:33:29 +00:00
target.append(", ")
2016-04-02 15:48:22 +00:00
state = dFlowImplicitMapKey
of dFlowMapStart:
if (isObject and style != psMinimal) or style in [psJson, psCanonical]:
2016-09-19 17:33:29 +00:00
target.append(newline & repeat(' ', indentation))
2016-04-02 15:48:22 +00:00
if style == psJson: state = dFlowImplicitMapKey
else:
2016-09-19 17:33:29 +00:00
target.append("? ")
2016-04-02 15:48:22 +00:00
state = dFlowExplicitMapKey
else: state = dFlowImplicitMapKey
of dFlowImplicitMapKey:
2016-09-19 17:33:29 +00:00
target.append(": ")
2016-04-02 15:48:22 +00:00
state = dFlowMapValue
of dBlockSequenceItem:
2016-09-19 17:33:29 +00:00
target.append(newline)
target.append(repeat(' ', indentation))
target.append("- ")
2016-04-02 15:48:22 +00:00
of dFlowSequenceStart:
case style
of psMinimal, psDefault: discard
of psCanonical, psJson:
2016-09-19 17:33:29 +00:00
target.append(newline)
target.append(repeat(' ', indentation))
2016-04-02 15:48:22 +00:00
of psBlockOnly: discard # can never happen
state = dFlowSequenceItem
of dFlowSequenceItem:
case style
2016-09-19 17:33:29 +00:00
of psMinimal, psDefault: target.append(", ")
2016-04-02 15:48:22 +00:00
of psCanonical, psJson:
2016-09-19 17:33:29 +00:00
target.append(',' & newline)
target.append(repeat(' ', indentation))
2016-04-02 15:48:22 +00:00
of psBlockOnly: discard # can never happen
except:
var e = newException(YamlPresenterOutputError, "")
e.parent = getCurrentException()
raise e
2016-01-28 20:59:26 +00:00
proc anchorName(a: AnchorId): string {.raises: [].} =
result = ""
var i = int(a)
while i >= 0:
let j = i mod 36
if j < 26: result.add(char(j + ord('a')))
else: result.add(char(j + ord('0') - 26))
i -= 36
2016-09-19 17:33:29 +00:00
proc writeTagAndAnchor(target: PresenterTarget, tag: TagId,
tagLib: TagLibrary,
anchor: AnchorId) {.raises:[YamlPresenterOutputError].} =
2016-04-02 15:48:22 +00:00
try:
if tag notin [yTagQuestionMark, yTagExclamationMark]:
let tagUri = tagLib.uri(tag)
let (handle, length) = tagLib.searchHandle(tagUri)
if length > 0:
target.append(handle)
target.append(tagUri[length..tagUri.high])
2016-09-19 17:33:29 +00:00
target.append(' ')
2016-04-02 15:48:22 +00:00
else:
2016-09-19 17:33:29 +00:00
target.append("!<")
target.append(tagUri)
target.append("> ")
2016-04-02 15:48:22 +00:00
if anchor != yAnchorNone:
2016-09-19 17:33:29 +00:00
target.append("&")
target.append(anchorName(anchor))
target.append(' ')
2016-04-02 15:48:22 +00:00
except:
var e = newException(YamlPresenterOutputError, "")
e.parent = getCurrentException()
raise e
proc nextItem(c: var Queue, s: var YamlStream):
YamlStreamEvent {.raises: [YamlStreamError].} =
if c.len > 0:
try: result = c.dequeue
except IndexError: internalError("Unexpected IndexError")
else:
result = s.next()
2016-09-19 17:33:29 +00:00
proc doPresent(s: var YamlStream, target: PresenterTarget,
tagLib: TagLibrary,
options: PresentationOptions = defaultPresentationOptions) =
2016-04-02 15:48:22 +00:00
var
indentation = 0
levels = newSeq[DumperState]()
cached = initQueue[YamlStreamEvent]()
let newline = if options.newlines == nlLF: "\l"
elif options.newlines == nlCRLF: "\c\l" else: "\n"
while cached.len > 0 or not s.finished():
let item = nextItem(cached, s)
2016-04-02 15:48:22 +00:00
case item.kind
of yamlStartDoc:
if options.style != psJson:
try:
case options.outputVersion
2016-09-19 17:33:29 +00:00
of ov1_2: target.append("%YAML 1.2" & newline)
of ov1_1: target.append("%YAML 1.1" & newLine)
2016-04-02 15:48:22 +00:00
of ovNone: discard
for prefix, handle in tagLib.handles():
if handle == "!":
if prefix != "!":
target.append("%TAG ! " & prefix & newline)
elif handle == "!!":
if prefix != yamlTagRepositoryPrefix:
target.append("%TAG !! " & prefix & newline)
else:
target.append("%TAG " & handle & ' ' & prefix & newline)
2016-09-19 17:33:29 +00:00
target.append("--- ")
2016-04-02 15:48:22 +00:00
except:
var e = newException(YamlPresenterOutputError, "")
e.parent = getCurrentException()
raise e
of yamlScalar:
if levels.len == 0:
2016-09-19 17:33:29 +00:00
if options.style != psJson: target.safeWrite(newline)
2016-04-02 15:48:22 +00:00
else:
startItem(target, options.style, indentation,
levels[levels.high], false, newline)
if options.style != psJson:
writeTagAndAnchor(target, item.scalarTag, tagLib, item.scalarAnchor)
2016-04-02 15:48:22 +00:00
if options.style == psJson:
let hint = guessType(item.scalarContent)
if item.scalarTag in [yTagQuestionMark, yTagBoolean] and
hint in {yTypeBoolTrue, yTypeBoolFalse}:
2016-09-19 17:33:29 +00:00
target.safeWrite(if hint == yTypeBoolTrue: "true" else: "false")
2016-04-02 15:48:22 +00:00
elif item.scalarTag in [yTagQuestionMark, yTagNull] and
hint == yTypeNull:
2016-09-19 17:33:29 +00:00
target.safeWrite("null")
2016-04-02 15:48:22 +00:00
elif item.scalarTag in [yTagQuestionMark, yTagInteger,
yTagNimInt8, yTagNimInt16, yTagNimInt32, yTagNimInt64,
yTagNimUInt8, yTagNimUInt16, yTagNimUInt32, yTagNimUInt64] and
hint == yTypeInteger:
2016-09-19 17:33:29 +00:00
target.safeWrite(item.scalarContent)
2016-04-02 15:48:22 +00:00
elif item.scalarTag in [yTagQuestionMark, yTagFloat, yTagNimFloat32,
yTagNimFloat64] and hint in {yTypeFloatInf, yTypeFloatNaN}:
raise newException(YamlPresenterJsonError,
"Infinity and not-a-number values cannot be presented as JSON!")
elif item.scalarTag in [yTagQuestionMark, yTagFloat] and
hint == yTypeFloat:
2016-09-19 17:33:29 +00:00
target.safeWrite(item.scalarContent)
2016-04-02 15:48:22 +00:00
else: writeDoubleQuotedJson(item.scalarContent, target)
elif options.style == psCanonical:
writeDoubleQuoted(item.scalarContent, target,
indentation + options.indentationStep, newline)
else:
var words, lines = newSeq[tuple[start, finish: int]]()
case item.scalarContent.inspect(
indentation + options.indentationStep, words, lines)
of sLiteral: writeLiteral(item.scalarContent, indentation,
options.indentationStep, target, lines, newline)
2016-04-02 15:48:22 +00:00
of sFolded: writeFolded(item.scalarContent, indentation,
options.indentationStep, target, words, newline)
2016-09-19 17:33:29 +00:00
of sPlain: target.safeWrite(item.scalarContent)
2016-04-02 15:48:22 +00:00
of sDoubleQuoted: writeDoubleQuoted(item.scalarContent, target,
indentation + options.indentationStep, newline)
2016-04-02 15:48:22 +00:00
of yamlAlias:
if options.style == psJson:
raise newException(YamlPresenterJsonError,
"Alias not allowed in JSON output")
yAssert levels.len > 0
2016-04-02 15:48:22 +00:00
startItem(target, options.style, indentation, levels[levels.high],
false, newline)
try:
2016-09-19 17:33:29 +00:00
target.append('*')
target.append(char(byte('a') + byte(item.aliasTarget)))
2016-04-02 15:48:22 +00:00
except:
var e = newException(YamlPresenterOutputError, "")
e.parent = getCurrentException()
raise e
of yamlStartSeq:
var nextState: DumperState
case options.style
of psDefault:
var length = 0
while true:
yAssert(not s.finished())
2016-04-02 15:48:22 +00:00
let next = s.next()
cached.enqueue(next)
case next.kind
of yamlScalar: length += 2 + next.scalarContent.len
of yamlAlias: length += 6
of yamlEndSeq: break
else:
length = high(int)
break
nextState = if length <= 60: dFlowSequenceStart else: dBlockSequenceItem
of psJson:
if levels.len > 0 and levels[levels.high] in
[dFlowMapStart, dFlowMapValue]:
raise newException(YamlPresenterJsonError,
"Cannot have sequence as map key in JSON output!")
nextState = dFlowSequenceStart
of psMinimal, psCanonical: nextState = dFlowSequenceStart
of psBlockOnly:
yAssert(not s.finished())
let next = s.peek()
if next.kind == yamlEndSeq: nextState = dFlowSequenceStart
else: nextState = dBlockSequenceItem
2016-04-02 15:48:22 +00:00
if levels.len == 0:
case nextState
of dBlockSequenceItem:
2016-04-02 15:48:22 +00:00
if options.style != psJson:
writeTagAndAnchor(target, item.seqTag, tagLib, item.seqAnchor)
of dFlowSequenceStart:
2016-09-19 17:33:29 +00:00
target.safeWrite(newline)
2016-04-02 15:48:22 +00:00
if options.style != psJson:
writeTagAndAnchor(target, item.seqTag, tagLib, item.seqAnchor)
indentation += options.indentationStep
else: internalError("Invalid nextState: " & $nextState)
2016-04-02 15:48:22 +00:00
else:
startItem(target, options.style, indentation,
levels[levels.high], true, newline)
if options.style != psJson:
writeTagAndAnchor(target, item.seqTag, tagLib, item.seqAnchor)
indentation += options.indentationStep
2016-09-19 17:33:29 +00:00
if nextState == dFlowSequenceStart: target.safeWrite('[')
2016-04-02 15:48:22 +00:00
if levels.len > 0 and options.style in [psJson, psCanonical] and
levels[levels.high] in [dBlockExplicitMapKey, dBlockMapValue,
dBlockImplicitMapKey, dBlockSequenceItem]:
indentation += options.indentationStep
levels.add(nextState)
of yamlStartMap:
var nextState: DumperState
case options.style
of psDefault:
type MapParseState = enum
mpInitial, mpKey, mpValue, mpNeedBlock
var mps: MapParseState = mpInitial
while mps != mpNeedBlock:
case s.peek().kind
of yamlScalar, yamlAlias:
case mps
of mpInitial: mps = mpKey
of mpKey: mps = mpValue
else: mps = mpNeedBlock
of yamlEndMap: break
else: mps = mpNeedBlock
nextState = if mps == mpNeedBlock: dBlockMapValue else: dBlockInlineMap
of psMinimal: nextState = dFlowMapStart
of psCanonical: nextState = dFlowMapStart
of psJson:
if levels.len > 0 and levels[levels.high] in
[dFlowMapStart, dFlowMapValue]:
raise newException(YamlPresenterJsonError,
"Cannot have map as map key in JSON output!")
nextState = dFlowMapStart
of psBlockOnly:
yAssert(not s.finished())
let next = s.peek()
if next.kind == yamlEndMap: nextState = dFlowMapStart
else: nextState = dBlockMapValue
2016-04-02 15:48:22 +00:00
if levels.len == 0:
case nextState
of dBlockMapValue:
2016-04-02 15:48:22 +00:00
if options.style != psJson:
writeTagAndAnchor(target, item.mapTag, tagLib, item.mapAnchor)
else:
if options.style != psJson:
2016-09-19 17:33:29 +00:00
target.safeWrite(newline)
2016-04-02 15:48:22 +00:00
writeTagAndAnchor(target, item.mapTag, tagLib, item.mapAnchor)
indentation += options.indentationStep
of dFlowMapStart:
2016-09-19 17:33:29 +00:00
target.safeWrite(newline)
if options.style != psJson:
writeTagAndAnchor(target, item.mapTag, tagLib, item.mapAnchor)
indentation += options.indentationStep
of dBlockInlineMap: discard
else: internalError("Invalid nextState: " & $nextState)
2016-04-02 15:48:22 +00:00
else:
if nextState in [dBlockMapValue, dBlockImplicitMapKey]:
startItem(target, options.style, indentation,
levels[levels.high], true, newline)
if options.style != psJson:
writeTagAndAnchor(target, item.mapTag, tagLib, item.mapAnchor)
else:
startItem(target, options.style, indentation,
levels[levels.high], true, newline)
if options.style != psJson:
writeTagAndAnchor(target, item.mapTag, tagLib, item.mapAnchor)
indentation += options.indentationStep
2016-09-19 17:33:29 +00:00
if nextState == dFlowMapStart: target.safeWrite('{')
2016-04-02 15:48:22 +00:00
if levels.len > 0 and options.style in [psJson, psCanonical] and
levels[levels.high] in
[dBlockExplicitMapKey, dBlockMapValue,
dBlockImplicitMapKey, dBlockSequenceItem]:
indentation += options.indentationStep
levels.add(nextState)
2016-04-02 15:48:22 +00:00
of yamlEndSeq:
yAssert levels.len > 0
2016-04-02 15:48:22 +00:00
case levels.pop()
of dFlowSequenceItem:
case options.style
2016-09-19 17:33:29 +00:00
of psDefault, psMinimal, psBlockOnly: target.safeWrite(']')
2016-04-02 15:48:22 +00:00
of psJson, psCanonical:
indentation -= options.indentationStep
try:
2016-09-19 17:33:29 +00:00
target.append(newline)
target.append(repeat(' ', indentation))
target.append(']')
2016-04-02 15:48:22 +00:00
except:
var e = newException(YamlPresenterOutputError, "")
e.parent = getCurrentException()
raise e
if levels.len == 0 or levels[levels.high] notin
[dBlockExplicitMapKey, dBlockMapValue,
dBlockImplicitMapKey, dBlockSequenceItem]:
continue
of dFlowSequenceStart:
if levels.len > 0 and options.style in [psJson, psCanonical] and
levels[levels.high] in [dBlockExplicitMapKey, dBlockMapValue,
dBlockImplicitMapKey, dBlockSequenceItem]:
indentation -= options.indentationStep
2016-09-19 17:33:29 +00:00
target.safeWrite(']')
2016-04-02 15:48:22 +00:00
of dBlockSequenceItem: discard
else: internalError("Invalid popped level")
2016-04-02 15:48:22 +00:00
indentation -= options.indentationStep
of yamlEndMap:
yAssert levels.len > 0
2016-04-02 15:48:22 +00:00
let level = levels.pop()
case level
of dFlowMapValue:
case options.style
2016-09-19 17:33:29 +00:00
of psDefault, psMinimal, psBlockOnly: target.safeWrite('}')
2016-04-02 15:48:22 +00:00
of psJson, psCanonical:
indentation -= options.indentationStep
try:
2016-09-19 17:33:29 +00:00
target.append(newline)
target.append(repeat(' ', indentation))
target.append('}')
2016-04-02 15:48:22 +00:00
except:
var e = newException(YamlPresenterOutputError, "")
e.parent = getCurrentException()
raise e
if levels.len == 0 or levels[levels.high] notin
[dBlockExplicitMapKey, dBlockMapValue,
dBlockImplicitMapKey, dBlockSequenceItem]:
continue
of dFlowMapStart:
if levels.len > 0 and options.style in [psJson, psCanonical] and
levels[levels.high] in [dBlockExplicitMapKey, dBlockMapValue,
dBlockImplicitMapKey, dBlockSequenceItem]:
indentation -= options.indentationStep
2016-09-19 17:33:29 +00:00
target.safeWrite('}')
2016-04-02 15:48:22 +00:00
of dBlockMapValue, dBlockInlineMap: discard
else: internalError("Invalid level: " & $level)
2016-04-02 15:48:22 +00:00
indentation -= options.indentationStep
of yamlEndDoc:
if finished(s): break
if options.style == psJson:
raise newException(YamlPresenterJsonError,
"Cannot output more than one document in JSON style")
2016-09-19 17:33:29 +00:00
target.safeWrite("..." & newline)
2016-09-19 17:33:29 +00:00
proc present*(s: var YamlStream, target: Stream,
tagLib: TagLibrary,
2016-09-20 19:53:38 +00:00
options: PresentationOptions = defaultPresentationOptions)
{.raises: [YamlPresenterJsonError, YamlPresenterOutputError,
YamlStreamError].} =
## Convert ``s`` to a YAML character stream and write it to ``target``.
2016-09-19 17:33:29 +00:00
doPresent(s, target, tagLib, options)
proc present*(s: var YamlStream, tagLib: TagLibrary,
options: PresentationOptions = defaultPresentationOptions):
2016-09-20 19:53:38 +00:00
string {.raises: [YamlPresenterJsonError, YamlPresenterOutputError,
YamlStreamError].} =
## Convert ``s`` to a YAML character stream and return it as string.
2016-09-19 17:33:29 +00:00
result = ""
doPresent(s, addr result, tagLib, options)
proc doTransform(input: Stream | string, output: PresenterTarget,
options: PresentationOptions, resolveToCoreYamlTags: bool) =
2016-04-02 15:48:22 +00:00
var
taglib = initExtendedTagLibrary()
parser = newYamlParser(tagLib)
events = parser.parse(input)
try:
if options.style == psCanonical:
var bys: YamlStream = newBufferYamlStream()
for e in events:
if resolveToCoreYamlTags:
var event = e
case event.kind
of yamlStartDoc, yamlEndDoc, yamlEndMap, yamlAlias, yamlEndSeq:
discard
of yamlStartMap:
if event.mapTag in [yTagQuestionMark, yTagExclamationMark]:
event.mapTag = yTagMapping
of yamlStartSeq:
if event.seqTag in [yTagQuestionMark, yTagExclamationMark]:
event.seqTag = yTagSequence
of yamlScalar:
if event.scalarTag == yTagQuestionMark:
case guessType(event.scalarContent)
of yTypeInteger: event.scalarTag = yTagInteger
of yTypeFloat, yTypeFloatInf, yTypeFloatNaN:
event.scalarTag = yTagFloat
of yTypeBoolTrue, yTypeBoolFalse: event.scalarTag = yTagBoolean
of yTypeNull: event.scalarTag = yTagNull
of yTypeTimestamp: event.scalarTag = yTagTimestamp
of yTypeUnknown: event.scalarTag = yTagString
elif event.scalarTag == yTagExclamationMark:
event.scalarTag = yTagString
BufferYamlStream(bys).put(event)
else: BufferYamlStream(bys).put(e)
when output is ptr[string]: output[] = present(bys, tagLib, options)
else: present(bys, output, tagLib, options)
else:
when output is ptr[string]: output[] = present(events, tagLib, options)
else: present(events, output, tagLib, options)
2016-04-02 15:48:22 +00:00
except YamlStreamError:
var e = getCurrentException()
while e.parent of YamlStreamError: e = e.parent
if e.parent of IOError: raise (ref IOError)(e.parent)
elif e.parent of YamlParserError: raise (ref YamlParserError)(e.parent)
else: internalError("Unexpected exception: " & e.parent.repr)
2016-09-19 17:33:29 +00:00
proc transform*(input: Stream | string, output: Stream,
options: PresentationOptions = defaultPresentationOptions,
resolveToCoreYamlTags: bool = false)
2016-09-20 19:53:38 +00:00
{.raises: [IOError, YamlParserError, YamlPresenterJsonError,
YamlPresenterOutputError].} =
## Parser ``input`` as YAML character stream and then dump it to ``output``
## while resolving non-specific tags to the ones in the YAML core tag
## library. If ``resolveToCoreYamlTags`` is ``true``, non-specific tags will
## be replaced by specific tags according to the YAML core schema.
doTransform(input, output, options, resolveToCoreYamlTags)
2016-09-19 17:33:29 +00:00
proc transform*(input: Stream | string,
options: PresentationOptions = defaultPresentationOptions,
resolveToCoreYamlTags: bool = false):
2016-09-20 19:53:38 +00:00
string {.raises: [IOError, YamlParserError, YamlPresenterJsonError,
YamlPresenterOutputError].} =
## Parser ``input`` as YAML character stream, resolves non-specific tags to
## the ones in the YAML core tag library, and then returns a serialized
## YAML string that represents the stream. If ``resolveToCoreYamlTags`` is
## ``true``, non-specific tags will be replaced by specific tags according to
## the YAML core schema.
2016-09-19 17:33:29 +00:00
result = ""
doTransform(input, addr result, options, resolveToCoreYamlTags)