mirror of https://github.com/status-im/NimYAML.git
274 lines
11 KiB
Nim
274 lines
11 KiB
Nim
import hashes
|
|
import private/internal
|
|
|
|
type
|
|
Anchor* = distinct string ## \
|
|
## An ``Anchor`` identifies an anchor in the current document.
|
|
## It is not necessarily unique and references to an anchor must be
|
|
## resolved immediately on occurrence.
|
|
##
|
|
## Anchor provides the operator `$` for converting to string, `==` for
|
|
## comparison, and `hash` for usage in a hashmap.
|
|
|
|
Tag* = distinct string ## \
|
|
## A ``Tag`` contains an URI, like for example ``"tag:yaml.org,2002:str"``.
|
|
|
|
ScalarStyle* = enum
|
|
## Original style of the scalar (for input),
|
|
## or desired style of the scalar (for output).
|
|
ssAny, ssPlain, ssSingleQuoted, ssDoubleQuoted, ssLiteral, ssFolded
|
|
|
|
CollectionStyle* = enum
|
|
csAny, csBlock, csFlow, csPair
|
|
|
|
EventKind* = enum
|
|
## Kinds of YAML events that may occur in an ``YamlStream``. Event kinds
|
|
## are discussed in `YamlStreamEvent <#YamlStreamEvent>`_.
|
|
yamlStartStream, yamlEndStream,
|
|
yamlStartDoc, yamlEndDoc, yamlStartMap, yamlEndMap,
|
|
yamlStartSeq, yamlEndSeq, yamlScalar, yamlAlias
|
|
|
|
Event* = object
|
|
## An element from a `YamlStream <#YamlStream>`_. Events that start an
|
|
## object (``yamlStartMap``, ``yamlStartSeq``, ``yamlScalar``) have
|
|
## an optional anchor and a tag associated with them. The anchor will be
|
|
## set to ``yAnchorNone`` if it doesn't exist.
|
|
##
|
|
## A missing tag in the YAML character stream generates
|
|
## the non-specific tags ``?`` or ``!`` according to the YAML
|
|
## specification. These are by convention mapped to the ``TagId`` s
|
|
## ``yTagQuestionMark`` and ``yTagExclamationMark`` respectively.
|
|
## Mapping is done by a `TagLibrary <#TagLibrary>`_.
|
|
##
|
|
## ``startPos`` and ``endPos`` are only relevant for events from an input
|
|
## stream - they are generally ignored if used with events that generate
|
|
## output.
|
|
startPos*, endPos*: Mark
|
|
case kind*: EventKind
|
|
of yamlStartStream, yamlEndStream: discard
|
|
of yamlStartMap:
|
|
mapProperties*: Properties
|
|
mapStyle*: CollectionStyle
|
|
of yamlStartSeq:
|
|
seqProperties*: Properties
|
|
seqStyle*: CollectionStyle
|
|
of yamlScalar:
|
|
scalarProperties*: Properties
|
|
scalarStyle* : ScalarStyle
|
|
scalarContent*: string
|
|
of yamlStartDoc:
|
|
explicitDirectivesEnd*: bool
|
|
version*: string
|
|
handles*: seq[tuple[handle, uriPrefix: string]]
|
|
of yamlEndDoc:
|
|
explicitDocumentEnd*: bool
|
|
of yamlEndMap, yamlEndSeq: discard
|
|
of yamlAlias:
|
|
aliasTarget* : Anchor
|
|
|
|
Mark* = tuple[line, column: Positive]
|
|
|
|
Properties* = tuple[anchor: Anchor, tag: Tag]
|
|
|
|
const
|
|
yamlTagRepositoryPrefix* = "tag:yaml.org,2002:"
|
|
nimyamlTagRepositoryPrefix* = "tag:nimyaml.org,2016:"
|
|
|
|
proc defineTag*(uri: string): Tag =
|
|
## defines a tag. Use this to optimize away copies of globally defined
|
|
## Tags.
|
|
result = uri.Tag
|
|
#shallow(result.string) # doesn't work at compile-time
|
|
|
|
proc defineCoreTag*(name: string): Tag =
|
|
## defines a tag in YAML's core namespace, ``tag:yaml.org,2002:``
|
|
result = defineTag(yamlTagRepositoryPrefix & name)
|
|
|
|
const
|
|
yAnchorNone*: Anchor = "".Anchor ## \
|
|
## yielded when no anchor was defined for a YAML node
|
|
|
|
defaultMark: Mark = (1.Positive, 1.Positive) ## \
|
|
## used for events that are not generated from input.
|
|
|
|
yTagExclamationMark*: Tag = defineTag("!")
|
|
yTagQuestionMark* : Tag = defineTag("?")
|
|
|
|
# failsafe schema
|
|
|
|
yTagString* = defineCoreTag("str")
|
|
yTagSequence* = defineCoreTag("seq")
|
|
yTagMapping* = defineCoreTag("map")
|
|
|
|
# json & core schema
|
|
|
|
yTagNull* = defineCoreTag("null")
|
|
yTagBoolean* = defineCoreTag("bool")
|
|
yTagInteger* = defineCoreTag("int")
|
|
yTagFloat* = defineCoreTag("float")
|
|
|
|
# other language-independent YAML types (from http://yaml.org/type/ )
|
|
|
|
yTagOrderedMap* = defineCoreTag("omap")
|
|
yTagPairs* = defineCoreTag("pairs")
|
|
yTagSet* = defineCoreTag("set")
|
|
yTagBinary* = defineCoreTag("binary")
|
|
yTagMerge* = defineCoreTag("merge")
|
|
yTagTimestamp* = defineCoreTag("timestamp")
|
|
yTagValue* = defineCoreTag("value")
|
|
yTagYaml* = defineCoreTag("yaml")
|
|
|
|
# NimYAML specific tags
|
|
|
|
yTagNimField* = defineTag(nimyamlTagRepositoryPrefix & "field")
|
|
|
|
proc properties*(event: Event): Properties =
|
|
## returns the tag of the given event
|
|
case event.kind
|
|
of yamlStartMap: result = event.mapProperties
|
|
of yamlStartSeq: result = event.seqProperties
|
|
of yamlScalar: result = event.scalarProperties
|
|
else: raise newException(FieldDefect, "Event " & $event.kind & " has no properties")
|
|
|
|
proc collectionStyle*(event: Event): CollectionStyle =
|
|
## returns the style of the given collection start event
|
|
case event.kind
|
|
of yamlStartMap: result = event.mapStyle
|
|
of yamlStartSeq: result = event.seqStyle
|
|
else: raise (ref FieldDefect)(msg: "Event " & $event.kind & " has no collectionStyle")
|
|
|
|
proc startStreamEvent*(): Event =
|
|
return Event(startPos: defaultMark, endPos: defaultMark, kind: yamlStartStream)
|
|
|
|
proc endStreamEvent*(): Event =
|
|
return Event(startPos: defaultMark, endPos: defaultMark, kind: yamlEndStream)
|
|
|
|
proc startDocEvent*(explicit: bool = false, version: string = "",
|
|
handles: seq[tuple[handle, uriPrefix: string]] = @[],
|
|
startPos, endPos: Mark = defaultMark): Event
|
|
{.inline, raises: [].} =
|
|
## creates a new event that marks the start of a YAML document
|
|
result = Event(startPos: startPos, endPos: endPos,
|
|
kind: yamlStartDoc, version: version, handles: handles,
|
|
explicitDirectivesEnd: explicit)
|
|
|
|
proc endDocEvent*(explicit: bool = false, startPos, endPos: Mark = defaultMark): Event
|
|
{.inline, raises: [].} =
|
|
## creates a new event that marks the end of a YAML document
|
|
result = Event(startPos: startPos, endPos: endPos,
|
|
kind: yamlEndDoc, explicitDocumentEnd: explicit)
|
|
|
|
proc startMapEvent*(style: CollectionStyle, props: Properties,
|
|
startPos, endPos: Mark = defaultMark): Event {.inline, raises: [].} =
|
|
## creates a new event that marks the start of a YAML mapping
|
|
result = Event(startPos: startPos, endPos: endPos,
|
|
kind: yamlStartMap, mapProperties: props,
|
|
mapStyle: style)
|
|
|
|
proc startMapEvent*(style: CollectionStyle = csAny,
|
|
tag: Tag = yTagQuestionMark,
|
|
anchor: Anchor = yAnchorNone,
|
|
startPos, endPos: Mark = defaultMark): Event {.inline.} =
|
|
return startMapEvent(style, (anchor, tag), startPos, endPos)
|
|
|
|
proc endMapEvent*(startPos, endPos: Mark = defaultMark): Event {.inline, raises: [].} =
|
|
## creates a new event that marks the end of a YAML mapping
|
|
result = Event(startPos: startPos, endPos: endPos, kind: yamlEndMap)
|
|
|
|
proc startSeqEvent*(style: CollectionStyle,
|
|
props: Properties,
|
|
startPos, endPos: Mark = defaultMark): Event {.inline, raises: [].} =
|
|
## creates a new event that marks the beginning of a YAML sequence
|
|
result = Event(startPos: startPos, endPos: endPos,
|
|
kind: yamlStartSeq, seqProperties: props,
|
|
seqStyle: style)
|
|
|
|
proc startSeqEvent*(style: CollectionStyle = csAny,
|
|
tag: Tag = yTagQuestionMark,
|
|
anchor: Anchor = yAnchorNone,
|
|
startPos, endPos: Mark = defaultMark): Event {.inline.} =
|
|
return startSeqEvent(style, (anchor, tag), startPos, endPos)
|
|
|
|
proc endSeqEvent*(startPos, endPos: Mark = defaultMark): Event {.inline, raises: [].} =
|
|
## creates a new event that marks the end of a YAML sequence
|
|
result = Event(startPos: startPos, endPos: endPos, kind: yamlEndSeq)
|
|
|
|
proc scalarEvent*(content: string, props: Properties,
|
|
style: ScalarStyle = ssAny,
|
|
startPos, endPos: Mark = defaultMark): Event {.inline, raises: [].} =
|
|
## creates a new event that represents a YAML scalar
|
|
result = Event(startPos: startPos, endPos: endPos,
|
|
kind: yamlScalar, scalarProperties: props,
|
|
scalarContent: content, scalarStyle: style)
|
|
|
|
proc scalarEvent*(content: string = "", tag: Tag = yTagQuestionMark,
|
|
anchor: Anchor = yAnchorNone,
|
|
style: ScalarStyle = ssAny,
|
|
startPos, endPos: Mark = defaultMark): Event {.inline.} =
|
|
return scalarEvent(content, (anchor, tag), style, startPos, endPos)
|
|
|
|
proc aliasEvent*(target: Anchor, startPos, endPos: Mark = defaultMark): Event {.inline, raises: [].} =
|
|
## creates a new event that represents a YAML alias
|
|
result = Event(startPos: startPos, endPos: endPos, kind: yamlAlias, aliasTarget: target)
|
|
|
|
proc `==`*(left, right: Anchor): bool {.borrow.}
|
|
proc `$`*(id: Anchor): string {.borrow.}
|
|
proc hash*(id: Anchor): Hash {.borrow.}
|
|
|
|
proc `==`*(left, right: Tag): bool {.borrow.}
|
|
proc `$`*(tag: Tag): string {.borrow.}
|
|
proc hash*(tag: Tag): Hash {.borrow.}
|
|
|
|
proc `==`*(left: Event, right: Event): bool {.raises: [].} =
|
|
## compares all existing fields of the given items
|
|
if left.kind != right.kind: return false
|
|
case left.kind
|
|
of yamlStartStream, yamlEndStream, yamlStartDoc, yamlEndDoc, yamlEndMap, yamlEndSeq:
|
|
result = true
|
|
of yamlStartMap:
|
|
result = left.mapProperties == right.mapProperties
|
|
of yamlStartSeq:
|
|
result = left.seqProperties == right.seqProperties
|
|
of yamlScalar:
|
|
result = left.scalarProperties == right.scalarProperties and
|
|
left.scalarContent == right.scalarContent
|
|
of yamlAlias: result = left.aliasTarget == right.aliasTarget
|
|
|
|
proc renderAttrs*(props: Properties, isPlain: bool = true): string =
|
|
result = ""
|
|
if props.anchor != yAnchorNone: result &= " &" & $props.anchor
|
|
case props.tag
|
|
of yTagQuestionMark: discard
|
|
of yTagExclamationMark:
|
|
if isPlain: result &= " <!>"
|
|
else:
|
|
result &= " <" & $props.tag & ">"
|
|
|
|
proc `$`*(event: Event): string {.raises: [].} =
|
|
## outputs a human-readable string describing the given event.
|
|
## This string is compatible to the format used in the yaml test suite.
|
|
case event.kind
|
|
of yamlStartStream: result = "+STR"
|
|
of yamlEndStream: result = "-STR"
|
|
of yamlEndMap: result = "-MAP"
|
|
of yamlEndSeq: result = "-SEQ"
|
|
of yamlStartDoc:
|
|
result = "+DOC"
|
|
if event.explicitDirectivesEnd: result &= " ---"
|
|
of yamlEndDoc:
|
|
result = "-DOC"
|
|
if event.explicitDocumentEnd: result &= " ..."
|
|
of yamlStartMap: result = "+MAP" & renderAttrs(event.mapProperties)
|
|
of yamlStartSeq: result = "+SEQ" & renderAttrs(event.seqProperties)
|
|
of yamlScalar:
|
|
result = "=VAL" & renderAttrs(event.scalarProperties,
|
|
event.scalarStyle == ssPlain or
|
|
event.scalarStyle == ssAny)
|
|
case event.scalarStyle
|
|
of ssPlain, ssAny: result &= " :"
|
|
of ssSingleQuoted: result &= " \'"
|
|
of ssDoubleQuoted: result &= " \""
|
|
of ssLiteral: result &= " |"
|
|
of ssFolded: result &= " >"
|
|
result &= yamlTestSuiteEscape(event.scalarContent)
|
|
of yamlAlias: result = "=ALI *" & $event.aliasTarget |