diff --git a/CHANGELOG.md b/CHANGELOG.md index 7acddda0..a30ba8f1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,14 @@ ## 2.1.0 (upcoming) +Features: + + * The presenter now honors the node style set in the events it presents, + if possible. So if a scalar is set to be a literal block scalar, it is + presented as such unless impossible or presenter options specifically + prevent this. + * The presenter can now output single-quoted scalars. It only does so when + this scalar style is explicitly set on an event. + Changes: * renamed ``canonicalDumper`` / ``setCanonicalStyle`` to @@ -7,6 +16,7 @@ Changes: a misnomer and there is nothing canonical about this output style. The terminology *canonical* was carried over from PyYAML, but the YAML specification uses that term for different things. + The old names are kept with a ``deprecated`` pragma. * The ``explanatoryDumper`` now automatically enables the tag shorthand ``!n!``, because in this style you want that for readability. @@ -14,11 +24,18 @@ Bugfixes: * Fixed a bug that prevented instances of generic types to be used in ``Option`` fields (e.g. ``Option[seq[string]]``) (#101) - * Fixed a problem that caused invalid indentation when dumping with certain + * Fixed a bug that caused invalid indentation when dumping with certain settings (#140) * Fixed parsing errors for verbatim tags in flow style (#140) - * Fixed a problem that caused presentation of block scalars in + * Fixed a bug that caused presentation of block scalars in flow collections (#140) + * Fixed a bug that sometimes caused the last word of a folded block scalar + not to be presented. + * Fixed maximum line length not properly implemented in presenter in a number + of cases. + * Fixed a bug that prevented the presenter from outputting compact + flow mappings in cMixed mode. + * Fixed block scalars as mapping keys not being presented properly. ## 2.0.0 diff --git a/doc/testing.rst b/doc/testing.rst index fb66cbfb..5e9161ed 100644 --- a/doc/testing.rst +++ b/doc/testing.rst @@ -48,8 +48,8 @@ updated as you type.
- - + +
diff --git a/flake.nix b/flake.nix index 926be82f..c63157f7 100644 --- a/flake.nix +++ b/flake.nix @@ -6,7 +6,7 @@ }; outputs = { self, nixpkgs, utils, nix-filter }: let - version = "1.1.0"; + version = "2.1.0"; systemDependent = with utils.lib; eachSystem allSystems (system: let pkgs = nixpkgs.legacyPackages.${system}; @@ -14,13 +14,13 @@ devShell = pkgs.mkShell { buildInputs = with pkgs; [ nim2 ]; }; packages.webdocs = let nim-jester = pkgs.stdenv.mkDerivation { - name = "nim-jester-0.5.0"; + name = "nim-jester-0.6.0"; src = pkgs.fetchFromGitHub { owner = "dom96"; repo = "jester"; - rev = "v0.5.0"; + rev = "v0.6.0"; sha256 = - "0m8a4ss4460jd2lcbqcbdd68jhcy35xg7qdyr95mh8rflwvmcvhk"; + "sha256-F/zWWGipJ4lBE3njceXn5HBFTYEXB4l2rk6+FfqqZTQ="; }; dontBuild = true; installPhase = '' @@ -29,17 +29,18 @@ ''; }; nim-httpbeast = pkgs.stdenv.mkDerivation { - name = "nim-httpbeast-0.2.2"; + name = "nim-httpbeast-0.4.1"; src = pkgs.fetchFromGitHub { owner = "dom96"; repo = "httpbeast"; - rev = "v0.2.2"; + rev = "v0.4.1"; sha256 = - "1f8ch7sd5kcyaw1b1lpqywvhx2h6aa5an37zm7x0j22giqlml5c6"; + "sha256-8ncCj94UeirSevgZP717NiNtecDyH5jHky+QId31IvQ="; }; dontBuild = true; installPhase = '' mkdir -p $out/lib + ls -alh cp -r src/* $out/lib ''; }; diff --git a/server/server.nim b/server/server.nim index 1372b4d4..84a716bd 100644 --- a/server/server.nim +++ b/server/server.nim @@ -23,7 +23,7 @@ router nyRouter: try: case @"style" of "minimal": dumper.setMinimalStyle() - of "canonical": dumper.setCanonicalStyle() + of "explanatory": dumper.setExplanatoryStyle() of "default": dumper.setDefaultStyle() of "json": dumper.setJsonStyle() of "block": dumper.setBlockOnlyStyle() @@ -48,7 +48,7 @@ router nyRouter: var output = newStringStream() highlighted = "" - dumper.transform(newStringStream(@"input"), output, true) + dumper.transform(newStringStream(@"input"), output, @"style" == "explanatory") # syntax highlighting (stolen and modified from stlib's rstgen) var g: GeneralTokenizer diff --git a/test/tpresenter.nim b/test/tpresenter.nim index 83d6db00..4458420d 100644 --- a/test/tpresenter.nim +++ b/test/tpresenter.nim @@ -4,6 +4,7 @@ # See the file "copying.txt", included in this # distribution, for details about the copyright. +import std/[ options, strutils ] import ../yaml/[ presenter, data, stream ] import unittest import commonTestUtils @@ -32,6 +33,15 @@ suite "Presenter": test "Scalar without tag": var input = inputSingle(scalarEvent("droggeljug")) assertOutput(input, "droggeljug\n") + + test "Root block scalar": + var input = inputSingle(scalarEvent("I am a dwarf and I'm digging a hole\n diggy diggy hole\n diggy diggy hole\n")) + assertOutput(input, + "--- |\n" & + "I am a dwarf and I'm digging a hole\n" & + " diggy diggy hole\n" & + " diggy diggy hole\n", + PresentationOptions(maxLineLength: some(40))) test "Compact flow sequence": var input = inputSingle(startSeqEvent(), scalarEvent("1"), scalarEvent("2"), endSeqEvent()) @@ -87,4 +97,55 @@ suite "Presenter": var input = inputSingle(startMapEvent(), scalarEvent("root"), startSeqEvent(), scalarEvent("a"), startMapEvent(), scalarEvent("1"), scalarEvent("2"), scalarEvent("3"), scalarEvent("4"), endMapEvent(), endSeqEvent(), endMapEvent()) - assertOutput(input, "root:\n - a\n - 1: 2\n 3: 4\n", PresentationOptions(outputVersion: ovNone, indentationStep: 4)) \ No newline at end of file + assertOutput(input, "root:\n - a\n - 1: 2\n 3: 4\n", PresentationOptions(outputVersion: ovNone, indentationStep: 4)) + + test "Scalar output with explicit style set": + var input = inputSingle( + startSeqEvent(), scalarEvent("plain", style = ssPlain), + scalarEvent("@noplain", style = ssPlain), + scalarEvent("literal\n", style = ssLiteral), + scalarEvent("nofolded ", style = ssFolded), + scalarEvent("folded scalar", style = ssFolded), + scalarEvent("single'", style = ssSingleQuoted), + endSeqEvent() + ) + assertOutput(input, "- plain\n" & + "- \"@noplain\"\n" & + "- |\n" & + " literal\n" & + "- \"nofolded \"\n" & + "- >-\n" & + " folded scalar\n" & + "- 'single'''\n") + + test "Collection output with explicit style set": + var input = inputSingle( + startMapEvent(), scalarEvent("a", style = ssDoubleQuoted), + startSeqEvent(style = csFlow), scalarEvent("b"), scalarEvent("c"), scalarEvent("d"), endSeqEvent(), + scalarEvent("? e"), startSeqEvent(style = csBlock), scalarEvent("f"), endSeqEvent(), + scalarEvent("g"), startMapEvent(), scalarEvent("h"), scalarEvent("i"), endMapEvent(), + scalarEvent("j"), startMapEvent(), scalarEvent("k", style = ssLiteral), scalarEvent("l"), endMapEvent(), + endMapEvent() + ) + assertOutput(input, "\"a\": [b, c, d]\n" & + "\"? e\":\n" & + " - f\n" & + "g: {h: i}\n" & + "j:\n" & + " ? |-\n" & + " k\n" & + " : l\n") + + test "Block mapping with multiline keys": + var input = inputSingle( + startMapEvent(), scalarEvent(repeat("a", 18)), scalarEvent("b"), + scalarEvent(repeat("\"", 8)), scalarEvent("c"), + scalarEvent(repeat("d", 17)), scalarEvent("e"), endMapEvent()) + assertOutput(input, "? \"aaaaaaaaaaaaaaa\\\n" & + " aaa\"\n" & + ": b\n" & + "? \"\\\"\\\"\\\"\\\"\\\"\\\"\\\"\\\n" & + " \\\"\"\n" & + ": c\n" & + "ddddddddddddddddd:\n" & + " e\n", PresentationOptions(maxLineLength: some(19))) \ No newline at end of file diff --git a/yaml/presenter.nim b/yaml/presenter.nim index 761ccab5..6a1bf12e 100644 --- a/yaml/presenter.nim +++ b/yaml/presenter.nim @@ -103,15 +103,17 @@ type singleLine: bool wroteAnything: bool - ScalarStyle = enum - sLiteral, sFolded, sPlain, sDoubleQuoted - Context = object target: Stream options: PresentationOptions handles: seq[tuple[handle, uriPrefix: string]] levels: seq[DumperLevel] needsWhitespace: int + wroteDirectivesEnd: bool + lastImplicitKeyLen: int + + ItemKind = enum + ikCompactScalar, ikMultilineFlowScalar, ikBlockScalar, ikCollection proc level(ctx: var Context): var DumperLevel = ctx.levels[^1] @@ -122,6 +124,13 @@ proc state(ctx: Context): DumperState = ctx.level.state proc `state=`(ctx: var Context, v: DumperState) = ctx.level.state = v +proc isFlow(state: DumperState): bool = + result = state in [ + dFlowImplicitMapKey, dFlowMapValue, + dFlowExplicitMapKey, dFlowSequenceItem, + dFlowMapStart, dFlowSequenceStart + ] + proc indentation(ctx: Context): int = result = if ctx.levels.len == 0: 0 else: ctx.level.indentation @@ -142,9 +151,39 @@ proc inspect( scalar : string, indentation : int, words, lines: var seq[tuple[start, finish: int]], + multiLine : var bool, lineLength : Option[int], inFlow : bool, + proposed : ScalarStyle, ): ScalarStyle {.raises: [].} = + ## inspects the given scalar and returns the style in which it should be + ## presented. fills information in words, lines and multiLine that can be + ## used later for presenting it: + ## + ## * words will contain substring boundaries of parts of the string that + ## are separated by exactly one, not more, spaces. occurrences of multiple + ## spaces will become part of a single word. This is used for folded block + ## scalars, which can only break at single spaces. + ## * lines will contain substring boundaries of parts of the string that + ## can be emitted as lines within a literal block scalar. + ## * multiLine is set to true for double quoted scalars iff the scalar will + ## be written on multiple lines. important when using the scalar as block + ## mapping key. Will not be set for other styles since single quoted and + ## plain scalars always occupy only one line, and block scalars always + ## occupy multiple. + ## + ## The proposed style will take precedence over other decisions and will be + ## returned if possible. a proposed folded style will decay into a literal + ## style if folded style is not possible but literal is. Otherwise, if the + ## proposed style is not a valid style, it is ignored. + ## + ## A proposed style of ssAny is not valid and is therefore always ignored. + ## This proc will never emit ssAny. + ## + ## This inspector will not always allow a style that would be possible. + ## For example, the presenter is currently unable to emit multi-line plain + ## scalars, therefore multi-line string will never yield ssPlain. Similarly, + ## ssFolded will never be returned if there are more-indented lines. var inLine = false inWord = false @@ -153,7 +192,10 @@ proc inspect( canUseFolded = not inFlow canUseLiteral = not inFlow canUsePlain = scalar.len > 0 and - scalar[0] notin {'@', '`', '|', '>', '&', '*', '!', ' ', '\t'} + scalar[0] notin {'@', '`', '|', '>', '&', '*', '!', ' ', '\t'} and + (not lineLength.isSome or scalar.len <= indentation + lineLength.get()) + canUseSingleQuoted = true + curDqLen = indentation + 2 for i, c in scalar: case c of ' ': @@ -170,14 +212,22 @@ proc inspect( # space at beginning of line will preserve previous and next # line break. that is currently too complex to handle. canUseFolded = false + inc(curDqLen) of '\l': canUsePlain = false # we don't use multiline plain scalars - curWord.finish = i - 1 - if lineLength.isSome and - curWord.finish - curWord.start + 1 > lineLength.get() - indentation: - return if canUsePlain: sPlain else: sDoubleQuoted - words.add(curWord) - inWord = false + canUseSingleQuoted = false + if inWord: + curWord.finish = i - 1 + if lineLength.isSome and + curWord.finish - curWord.start + 1 > lineLength.get() - indentation: + multiLine = lineLength.isSome and curDqLen > lineLength.get() + return ssDoubleQuoted + words.add(curWord) + inWord = false + if inLine and scalar[i - 1] in ['\t', ' ']: + # cannot use block scalars if line ends with space + canUseLiteral = false + canUseFolded = false curWord.start = i + 1 multipleSpaces = true if not inLine: curLine.start = i @@ -187,17 +237,24 @@ proc inspect( curLine.finish - curLine.start + 1 > lineLength.get() - indentation: canUseLiteral = false lines.add(curLine) + inc(curDqLen, 2) else: - if c in {'{', '}', '[', ']', ',', '#', '-', ':', '?', '%', '"', '\''} or - c.ord < 32: canUsePlain = false + inc(curDqLen, if c in {'"', '\\', '\t', '\''}: 2 else: 1) + + if c in {'{', '}', '[', ']', ',', '#', '-', ':', '?', '%', '"', '\''}: + canUsePlain = false + elif c.ord < 32: + canUsePlain = false + canUseSingleQuoted = false if not inLine: curLine.start = i inLine = true if not inWord: if not multipleSpaces: if lineLength.isSome and - curWord.finish - curWord.start + 1 > lineLength.get() - indentation: - return if canUsePlain: sPlain else: sDoubleQuoted + curWord.finish - curWord.start + 1 > lineLength.get() - indentation: + multiLine = lineLength.isSome and curDqLen > lineLength.get() + return ssDoubleQuoted words.add(curWord) curWord.start = i inWord = true @@ -205,21 +262,46 @@ proc inspect( if inWord: curWord.finish = scalar.len - 1 if lineLength.isSome and - curWord.finish - curWord.start + 1 > lineLength.get() - indentation: - return if canUsePlain: sPlain else: sDoubleQuoted + curWord.finish - curWord.start + 1 > lineLength.get() - indentation: + multiLine = lineLength.isSome and curDqLen > lineLength.get() + return ssDoubleQuoted words.add(curWord) if inLine: + if scalar[^1] in ['\t', ' ']: + canUseLiteral = false + canUseFolded = false + canUsePlain = false curLine.finish = scalar.len - 1 if lineLength.isSome and curLine.finish - curLine.start + 1 > lineLength.get() - indentation: canUseLiteral = false lines.add(curLine) + if lineLength.isSome and curDqLen > lineLength.get(): + canUseSingleQuoted = false + + case proposed + of ssLiteral: + if canUseLiteral: return ssLiteral + of ssFolded: + if canUseFolded: return ssFolded + elif canUseLiteral: return ssLiteral + of ssPlain: + if canUsePlain: return ssPlain + of ssSingleQuoted: + if canUseSingleQuoted: return ssSingleQuoted + of ssDoubleQuoted: + multiLine = lineLength.isSome and curDqLen > lineLength.get() + return ssDoubleQuoted + else: discard + if lineLength.isNone or scalar.len <= lineLength.get() - indentation: - result = if canUsePlain: sPlain else: sDoubleQuoted - elif canUseLiteral: result = sLiteral - elif canUseFolded: result = sFolded - elif canUsePlain: result = sPlain - else: result = sDoubleQuoted + result = if canUsePlain: ssPlain else: ssDoubleQuoted + elif canUseLiteral: result = ssLiteral + elif canUseFolded: result = ssFolded + elif canUsePlain: result = ssPlain + else: result = ssDoubleQuoted + if result == ssDoubleQuoted: + multiLine = lineLength.isSome and curDqLen > lineLength.get() proc append(ctx: var Context, val: string | char) {.inline.} = if ctx.needsWhitespace > 0: @@ -243,68 +325,112 @@ proc newline(ctx: var Context) {.inline.} = proc writeDoubleQuoted( ctx : var Context, scalar: string, -) {.raises: [YamlPresenterOutputError].} = +): int {.raises: [YamlPresenterOutputError].} = let indentation = ctx.indentation + ctx.options.indentationStep var curPos = indentation let t = ctx.target try: + result = 2 ctx.append('"') curPos.inc() - for c in scalar: + for i, c in scalar: + var nextLength = 1 + case c + of '"', '\l', '\t', '\\': + nextLength = 2 + else: + if ord(c) < 32: + nextLength = 4 + if ctx.options.maxLineLength.isSome and - curPos == ctx.options.maxLineLength.get() - 1: + (curPos + nextLength >= ctx.options.maxLineLength.get() or + curPos + nextLength == ctx.options.maxLineLength.get() - 1 and i == scalar.len - 2): t.write('\\') ctx.newline() t.write(repeat(' ', indentation)) + result.inc(2 + indentation) curPos = indentation if c == ' ': t.write('\\') curPos.inc() + result.inc() + else: + curPos.inc(nextLength) + result.inc(nextLength) + case c + of '"': t.write("\\\"") + of '\l': t.write("\\n") + of '\t': t.write("\\t") + of '\\': t.write("\\\\") + else: + if ord(c) < 32: t.write("\\x" & toHex(ord(c), 2)) + else: t.write(c) + t.write('"') + except CatchableError as ce: + var e = newException(YamlPresenterOutputError, + "Error while writing to output stream") + e.parent = ce + raise e + +proc writeDoubleQuotedJson( + ctx : var Context, + scalar: string, +): int {.raises: [YamlPresenterOutputError].} = + let t = ctx.target + try: + ctx.append('"') + result = 2 + for c in scalar: case c of '"': t.write("\\\"") - curPos.inc(2) + result.inc(2) + of '\\': + t.write("\\\\") + result.inc(2) of '\l': t.write("\\n") - curPos.inc(2) + result.inc(2) of '\t': t.write("\\t") - curPos.inc(2) - of '\\': - t.write("\\\\") - curPos.inc(2) + result.inc(2) + of '\f': + t.write("\\f") + result.inc(2) + of '\b': + t.write("\\b") + result.inc(2) else: if ord(c) < 32: - t.write("\\x" & toHex(ord(c), 2)) - curPos.inc(4) + t.write("\\u" & toHex(ord(c), 4)) + result.inc(4) else: t.write(c) - curPos.inc() + result.inc() t.write('"') - except CatchableError as ce: + except: var e = newException(YamlPresenterOutputError, "Error while writing to output stream") - e.parent = ce + e.parent = getCurrentException() raise e -proc writeDoubleQuotedJson( +proc writeSingleQuoted( ctx : var Context, scalar: string, -) {.raises: [YamlPresenterOutputError].} = +): int {.raises: [YamlPresenterOutputError].} = let t = ctx.target try: - ctx.append('"') + # writing \39 instead of \' because my syntax highlighter is dumb + ctx.append('\39') + result = 2 for c in scalar: - case c - of '"': t.write("\\\"") - of '\\': t.write("\\\\") - of '\l': t.write("\\n") - of '\t': t.write("\\t") - of '\f': t.write("\\f") - of '\b': t.write("\\b") + if c == '\39': + t.write("''") + result.inc(2) else: - if ord(c) < 32: t.write("\\u" & toHex(ord(c), 4)) else: t.write(c) - t.write('"') + t.write(c) + result.inc() + ctx.append('\39') except: var e = newException(YamlPresenterOutputError, "Error while writing to output stream") @@ -316,7 +442,8 @@ proc writeLiteral( scalar: string, lines : seq[tuple[start, finish: int]], ) {.raises: [YamlPresenterOutputError].} = - let indentation = ctx.indentation + ctx.options.indentationStep + var indentation = ctx.indentation + if ctx.levels.len > 0: inc(indentation, ctx.options.indentationStep) let t = ctx.target try: ctx.append('|') @@ -384,9 +511,16 @@ template safeNewline(c: var Context) = raise e proc startItem( - ctx : var Context, - isObject: bool, + ctx : var Context, + kind: ItemKind, ) {.raises: [YamlPresenterOutputError].} = + if ctx.levels.len == 0: + if kind == ikBlockScalar: + if not ctx.wroteDirectivesEnd: + ctx.wroteDirectivesEnd = true + ctx.safeWrite("---") + ctx.whitespace(true) + return let t = ctx.target try: case ctx.state @@ -396,7 +530,7 @@ proc startItem( t.write(repeat(' ', ctx.indentation)) else: ctx.level.wroteAnything = true - if isObject or ctx.options.explicitKeys: + if kind != ikCompactScalar or ctx.options.explicitKeys: ctx.append('?') ctx.whitespace() ctx.state = dBlockExplicitMapKey @@ -425,7 +559,7 @@ proc startItem( if not ctx.level.singleLine: ctx.newline() t.write(repeat(' ', ctx.indentation)) - if not isObject and not ctx.options.explicitKeys: + if kind == ikCompactScalar and not ctx.options.explicitKeys: ctx.state = dFlowImplicitMapKey else: t.write('?') @@ -435,7 +569,7 @@ proc startItem( if not ctx.level.singleLine: ctx.newline() t.write(repeat(' ', ctx.indentation)) - if not isObject and not ctx.options.explicitKeys: + if kind == ikCompactScalar and not ctx.options.explicitKeys: ctx.state = dFlowImplicitMapKey else: ctx.append('?') @@ -519,19 +653,19 @@ proc doPresent( ].} = var cached = initDeQue[Event]() - firstDoc = true - wroteDirectivesEnd = false + unclosedDoc = false + ctx.wroteDirectivesEnd = false while true: let item = nextItem(cached, s) case item.kind of yamlStartStream: discard of yamlEndStream: break of yamlStartDoc: - if not firstDoc: + if unclosedDoc: ctx.safeWrite("...") ctx.safeNewline() - wroteDirectivesEnd = - ctx.options.directivesEnd == deAlways or not s.peek().emptyProperties() + ctx.wroteDirectivesEnd = + item.explicitDirectivesEnd or ctx.options.directivesEnd == deAlways or not s.peek().emptyProperties() if ctx.options.directivesEnd != deNever: resetHandles(ctx.handles) @@ -543,40 +677,77 @@ proc doPresent( of ov1_2: ctx.target.write("%YAML 1.2") ctx.newline() - wroteDirectivesEnd = true + ctx.wroteDirectivesEnd = true of ov1_1: ctx.target.write("%YAML 1.1") ctx.newline() - wroteDirectivesEnd = true + ctx.wroteDirectivesEnd = true of ovNone: discard for v in ctx.handles: if v.handle == "!": if v.uriPrefix != "!": ctx.target.write("%TAG ! " & v.uriPrefix) ctx.newline() - wroteDirectivesEnd = true + ctx.wroteDirectivesEnd = true elif v.handle == "!!": if v.uriPrefix != yamlTagRepositoryPrefix: ctx.target.write("%TAG !! " & v.uriPrefix) ctx.newline() - wroteDirectivesEnd = true + ctx.wroteDirectivesEnd = true else: ctx.target.write("%TAG " & v.handle & ' ' & v.uriPrefix) ctx.newline() - wroteDirectivesEnd = true + ctx.wroteDirectivesEnd = true except CatchableError as ce: var e = newException(YamlPresenterOutputError, "") e.parent = ce raise e - if wroteDirectivesEnd: + if ctx.wroteDirectivesEnd: ctx.safeWrite("---") ctx.whitespace(true) of yamlScalar: - if ctx.levels.len > 0: - ctx.startItem(false) + var + words, lines: seq[tuple[start, finish: int]] + scalarStyle = ssAny + multiLine = false + needsNextLine = false + if ctx.options.quoting in [sqUnset, sqDouble]: + words = @[] + lines = @[] + if ctx.levels.len > 0 and ctx.state == dBlockImplicitMapKey: + if ctx.options.maxLineLength.isNone or + ctx.indentation + ctx.options.indentationStep + ctx.lastImplicitKeyLen + 4 < + ctx.options.maxLineLength.get(): + scalarStyle = item.scalarContent.inspect( + ctx.indentation + ctx.options.indentationStep + ctx.lastImplicitKeyLen + 2, + words, lines, multiLine, + ctx.options.maxLineLength, ctx.levels.len > 0 and ctx.state.isFlow, item.scalarStyle) + case scalarStyle + of ssPlain, ssSingleQuoted: discard + of ssDoubleQuoted: + if multiLine: + multiLine = false + scalarStyle = ssAny + needsNextLine = true + else: + scalarStyle = ssAny + needsNextLine = true + else: needsNextLine = true + if scalarStyle == ssAny: + scalarStyle = item.scalarContent.inspect( + ctx.indentation + ctx.options.indentationStep, words, lines, multiLine, + ctx.options.maxLineLength, ctx.levels.len > 0 and ctx.state.isFlow, item.scalarStyle) + ctx.startItem(case scalarStyle + of ssLiteral, ssFolded: ikBlockScalar + of ssDoubleQuoted: + if multiLine: ikMultilineFlowScalar else: ikCompactScalar + else: ikCompactScalar) discard ctx.writeTagAndAnchor(item.scalarProperties) if ctx.levels.len == 0: - if wroteDirectivesEnd: ctx.safeNewline() + if ctx.wroteDirectivesEnd and scalarStyle notin [ssLiteral, ssFolded]: ctx.safeNewline() + elif needsNextLine and scalarStyle in [ssDoubleQuoted, ssSingleQuoted, ssPlain]: + ctx.safeNewline() + ctx.safeWrite(repeat(' ', ctx.indentation + ctx.options.indentationStep)) case ctx.options.quoting of sqJson: var hint = yTypeUnknown @@ -600,28 +771,28 @@ proc doPresent( elif tag in [yTagQuestionMark, yTagFloat] and hint == yTypeFloat: ctx.safeWrite(item.scalarContent) - else: ctx.writeDoubleQuotedJson(item.scalarContent) + else: + ctx.lastImplicitKeyLen = ctx.writeDoubleQuotedJson(item.scalarContent) of sqDouble: - ctx.writeDoubleQuoted(item.scalarContent) + ctx.lastImplicitKeyLen = ctx.writeDoubleQuoted(item.scalarContent) else: - var words, lines = newSeq[tuple[start, finish: int]]() - case item.scalarContent.inspect( - ctx.indentation + ctx.options.indentationStep, words, lines, - ctx.options.maxLineLength, ctx.levels.len > 0 and ctx.state in [ - dFlowImplicitMapKey, dFlowMapValue, - dFlowExplicitMapKey, dFlowSequenceItem, - dFlowMapStart, dFlowSequenceStart - ]) - of sLiteral: ctx.writeLiteral(item.scalarContent, lines) - of sFolded: ctx.writeFolded(item.scalarContent, words) - of sPlain: ctx.safeWrite(item.scalarContent) - of sDoubleQuoted: ctx.writeDoubleQuoted(item.scalarContent) + case scalarStyle + of ssLiteral: ctx.writeLiteral(item.scalarContent, lines) + of ssFolded: ctx.writeFolded(item.scalarContent, words) + of ssPlain: + ctx.safeWrite(item.scalarContent) + ctx.lastImplicitKeyLen = item.scalarContent.len + of ssSingleQuoted: + ctx.lastImplicitKeyLen = ctx.writeSingleQuoted(item.scalarContent) + of ssDoubleQuoted: + ctx.lastImplicitKeyLen = ctx.writeDoubleQuoted(item.scalarContent) + else: discard # ssAny, can never happen of yamlAlias: if ctx.options.quoting == sqJson: raise newException(YamlPresenterJsonError, "Alias not allowed in JSON output") yAssert ctx.levels.len > 0 - ctx.startItem(false) + ctx.startItem(ikCompactScalar) try: ctx.append('*') ctx.target.write($item.aliasTarget) @@ -631,25 +802,35 @@ proc doPresent( raise e of yamlStartSeq: var nextState: DumperState - case ctx.options.containers - of cMixed: - var length = 0 - while true: - let next = s.next() - cached.addLast(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 cFlow: nextState = dFlowSequenceStart - of cBlock: + if (ctx.levels.len > 0 and ctx.state.isFlow) or item.seqStyle == csFlow: + nextState = dFlowSequenceStart + elif item.seqStyle == csBlock: let next = s.peek() - if next.kind == yamlEndSeq: nextState = dFlowSequenceStart - else: nextState = dBlockSequenceItem + nextState = if next.kind == yamlEndSeq: dFlowSequenceStart else: dBlockSequenceItem + else: + case ctx.options.containers + of cMixed: + var length = 0 + while true: + let next = s.next() + cached.addLast(next) + case next.kind + of yamlScalar: + length += 2 + next.scalarContent.len + if next.scalarStyle in [ssFolded, ssLiteral]: + length = high(int) + break + of yamlAlias: length += 6 + of yamlEndSeq: break + else: + length = high(int) + break + nextState = if length <= 60: dFlowSequenceStart else: dBlockSequenceItem + of cFlow: nextState = dFlowSequenceStart + of cBlock: + let next = s.peek() + if next.kind == yamlEndSeq: nextState = dFlowSequenceStart + else: nextState = dBlockSequenceItem var indentation = 0 var singleLine = ctx.options.condenseFlow or ctx.options.newlines == nlNone @@ -661,16 +842,16 @@ proc doPresent( if nextState == dFlowSequenceStart: indentation = ctx.options.indentationStep else: - ctx.startItem(true) + ctx.startItem(ikCollection) indentation = ctx.indentation + ctx.options.indentationStep let wroteAttrs = ctx.writeTagAndAnchor(item.seqProperties) - if wroteAttrs or (wroteDirectivesEnd and ctx.levels.len == 0): + if wroteAttrs or (ctx.wroteDirectivesEnd and ctx.levels.len == 0): wroteAnything = true if nextState == dFlowSequenceStart: if ctx.levels.len == 0: - if wroteAttrs or wroteDirectivesEnd: ctx.safeNewline() + if wroteAttrs or ctx.wroteDirectivesEnd: ctx.safeNewline() ctx.safeWrite('[') if ctx.levels.len > 0 and not ctx.options.condenseFlow and @@ -680,26 +861,46 @@ proc doPresent( ctx.levels.add (nextState, indentation, singleLine, wroteAnything) of yamlStartMap: var nextState: DumperState - case ctx.options.containers - of cMixed: - 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 cFlow: nextState = dFlowMapStart - of cBlock: + if (ctx.levels.len > 0 and ctx.state.isFlow) or item.mapStyle == csFlow: + nextState = dFlowMapStart + elif item.mapStyle == csBlock: let next = s.peek() - if next.kind == yamlEndMap: nextState = dFlowMapStart - else: nextState = dBlockMapValue + nextState = if next.kind == yamlEndMap: dFlowMapStart else: dBlockMapValue + else: + case ctx.options.containers + of cMixed: + type MapParseState = enum + mpInitial, mpKey, mpValue, mpNeedBlock + var mps: MapParseState = mpInitial + while mps != mpNeedBlock: + let next = s.next() + cached.addLast(next) + case next.kind + of yamlScalar: + case mps + of mpInitial: mps = mpKey + of mpKey: mps = mpValue + else: mps = mpNeedBlock + if next.scalarStyle in [ssFolded, ssLiteral]: + mps = mpNeedBlock + of yamlAlias: + case mps + of mpInitial: mps = mpKey + of mpKey: mps = mpValue + else: mps = mpNeedBlock + of yamlEndMap: break + else: mps = mpNeedBlock + if mps == mpNeedBlock: + nextState = dBlockMapValue + elif ctx.levels.len == 0 or ctx.state == dBlockSequenceItem and item.emptyProperties: + nextState = dBlockInlineMap + else: + nextState = dFlowMapStart + of cFlow: nextState = dFlowMapStart + of cBlock: + let next = s.peek() + if next.kind == yamlEndMap: nextState = dFlowMapStart + else: nextState = dBlockMapValue var indentation = 0 var singleLine = ctx.options.condenseFlow or ctx.options.newlines == nlNone @@ -711,16 +912,16 @@ proc doPresent( if nextState == dFlowMapStart: indentation = ctx.options.indentationStep else: - ctx.startItem(true) + ctx.startItem(ikCollection) indentation = ctx.indentation + ctx.options.indentationStep let wroteAttrs = ctx.writeTagAndAnchor(item.properties) - if wroteAttrs or (wroteDirectivesEnd and ctx.levels.len == 0): + if wroteAttrs or (ctx.wroteDirectivesEnd and ctx.levels.len == 0): wroteAnything = true if nextState == dFlowMapStart: if ctx.levels.len == 0: - if wroteAttrs or wroteDirectivesEnd: ctx.safeNewline() + if wroteAttrs or ctx.wroteDirectivesEnd: ctx.safeNewline() ctx.safeWrite('{') if ctx.levels.len > 0 and not ctx.options.condenseFlow and @@ -763,8 +964,12 @@ proc doPresent( of dBlockMapValue, dBlockInlineMap: discard else: internalError("Invalid level: " & $level) of yamlEndDoc: - firstDoc = false ctx.safeNewline() + if item.explicitDocumentEnd: + ctx.safeWrite("...") + ctx.safeNewline() + else: + unclosedDoc = true proc present*( s : YamlStream, @@ -802,37 +1007,38 @@ proc doTransform( parser.init() var events = parser.parse(input) try: - if ctx.options.explicitKeys: - var bys: YamlStream = newBufferYamlStream() - for e in events: + var bys: YamlStream = newBufferYamlStream() + for e in events: + var event = e + case event.kind + of yamlStartStream, yamlEndStream, yamlStartDoc, yamlEndDoc, yamlEndMap, yamlAlias, yamlEndSeq: + discard + of yamlStartMap: + event.mapStyle = csAny if resolveToCoreYamlTags: - var event = e - case event.kind - of yamlStartStream, yamlEndStream, yamlStartDoc, yamlEndDoc, yamlEndMap, yamlAlias, yamlEndSeq: - discard - of yamlStartMap: - if event.mapProperties.tag in [yTagQuestionMark, yTagExclamationMark]: - event.mapProperties.tag = yTagMapping - of yamlStartSeq: - if event.seqProperties.tag in [yTagQuestionMark, yTagExclamationMark]: - event.seqProperties.tag = yTagSequence - of yamlScalar: - if event.scalarProperties.tag == yTagQuestionMark: - case guessType(event.scalarContent) - of yTypeInteger: event.scalarProperties.tag = yTagInteger - of yTypeFloat, yTypeFloatInf, yTypeFloatNaN: - event.scalarProperties.tag = yTagFloat - of yTypeBoolTrue, yTypeBoolFalse: event.scalarProperties.tag = yTagBoolean - of yTypeNull: event.scalarProperties.tag = yTagNull - of yTypeTimestamp: event.scalarProperties.tag = yTagTimestamp - of yTypeUnknown: event.scalarProperties.tag = yTagString - elif event.scalarProperties.tag == yTagExclamationMark: - event.scalarProperties.tag = yTagString - BufferYamlStream(bys).put(event) - else: BufferYamlStream(bys).put(e) - doPresent(ctx, bys) - else: - doPresent(ctx, events) + if event.mapProperties.tag in [yTagQuestionMark, yTagExclamationMark]: + event.mapProperties.tag = yTagMapping + of yamlStartSeq: + event.seqStyle = csAny + if resolveToCoreYamlTags: + if event.seqProperties.tag in [yTagQuestionMark, yTagExclamationMark]: + event.seqProperties.tag = yTagSequence + of yamlScalar: + event.scalarStyle = ssAny + if resolveToCoreYamlTags: + if event.scalarProperties.tag == yTagQuestionMark: + case guessType(event.scalarContent) + of yTypeInteger: event.scalarProperties.tag = yTagInteger + of yTypeFloat, yTypeFloatInf, yTypeFloatNaN: + event.scalarProperties.tag = yTagFloat + of yTypeBoolTrue, yTypeBoolFalse: event.scalarProperties.tag = yTagBoolean + of yTypeNull: event.scalarProperties.tag = yTagNull + of yTypeTimestamp: event.scalarProperties.tag = yTagTimestamp + of yTypeUnknown: event.scalarProperties.tag = yTagString + elif event.scalarProperties.tag == yTagExclamationMark: + event.scalarProperties.tag = yTagString + BufferYamlStream(bys).put(event) + doPresent(ctx, bys) except YamlStreamError as e: var curE: ref Exception = e while curE.parent of YamlStreamError: curE = curE.parent