Improved presenter.

* added maxLineLength to PresentationOptions. Fixes #119
 * write a newline to the end of the document.
   required for the output files to be POSIX-compliant text files.
   also improves QoL when dumping to stdout.
This commit is contained in:
Felix Krause 2022-09-07 15:11:59 +02:00
parent a5552a1a18
commit 5f7677d914
3 changed files with 57 additions and 42 deletions

12
flake.lock generated
View File

@ -2,11 +2,11 @@
"nodes": { "nodes": {
"nix-filter": { "nix-filter": {
"locked": { "locked": {
"lastModified": 1653590866, "lastModified": 1661201956,
"narHash": "sha256-E4yKIrt/S//WfW5D9IhQ1dVuaAy8RE7EiCMfnbrOC78=", "narHash": "sha256-RizGJH/buaw9A2+fiBf9WnXYw4LZABB5kMAZIEE5/T8=",
"owner": "numtide", "owner": "numtide",
"repo": "nix-filter", "repo": "nix-filter",
"rev": "3e81a637cdf9f6e9b39aeb4d6e6394d1ad158e16", "rev": "3b821578685d661a10b563cba30b1861eec05748",
"type": "github" "type": "github"
}, },
"original": { "original": {
@ -40,11 +40,11 @@
}, },
"utils": { "utils": {
"locked": { "locked": {
"lastModified": 1653893745, "lastModified": 1659877975,
"narHash": "sha256-0jntwV3Z8//YwuOjzhV2sgJJPt+HY6KhU7VZUL0fKZQ=", "narHash": "sha256-zllb8aq3YO3h8B/U0/J1WBgAL8EX5yWf5pMj3G0NAmc=",
"owner": "numtide", "owner": "numtide",
"repo": "flake-utils", "repo": "flake-utils",
"rev": "1ed9fb1935d260de5fe1c2f7ee0ebaae17ed2fa1", "rev": "c0e246b9b83f637f4681389ecabcb2681b4f3af0",
"type": "github" "type": "github"
}, },
"original": { "original": {

View File

@ -147,7 +147,7 @@ suite "Serialization":
test "Dump integer without fixed length": test "Dump integer without fixed length":
var input = -4247 var input = -4247
var output = dump(input, tsNone, asTidy, blockOnly) var output = dump(input, tsNone, asTidy, blockOnly)
assertStringEqual yamlDirs & "\n\"-4247\"", output assertStringEqual yamlDirs & "\n\"-4247\"\n", output
when sizeof(int) == sizeof(int64): when sizeof(int) == sizeof(int64):
input = int(int32.high) + 1 input = int(int32.high) + 1
@ -232,7 +232,7 @@ suite "Serialization":
test "Dump string sequence": test "Dump string sequence":
var input = @["a", "b"] var input = @["a", "b"]
var output = dump(input, tsNone, asTidy, blockOnly) var output = dump(input, tsNone, asTidy, blockOnly)
assertStringEqual yamlDirs & "\n- a\n- b", output assertStringEqual yamlDirs & "\n- a\n- b\n", output
test "Load char set": test "Load char set":
let input = "- a\n- b" let input = "- a\n- b"
@ -245,7 +245,7 @@ suite "Serialization":
test "Dump char set": test "Dump char set":
var input = {'a', 'b'} var input = {'a', 'b'}
var output = dump(input, tsNone, asTidy, blockOnly) var output = dump(input, tsNone, asTidy, blockOnly)
assertStringEqual yamlDirs & "\n- a\n- b", output assertStringEqual yamlDirs & "\n- a\n- b\n", output
test "Load array": test "Load array":
let input = "- 23\n- 42\n- 47" let input = "- 23\n- 42\n- 47"
@ -258,7 +258,7 @@ suite "Serialization":
test "Dump array": test "Dump array":
let input = [23'i32, 42'i32, 47'i32] let input = [23'i32, 42'i32, 47'i32]
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\n", output
test "Load Option": test "Load Option":
let input = "- Some\n- !!null ~" let input = "- Some\n- !!null ~"
@ -271,7 +271,7 @@ suite "Serialization":
test "Dump Option": test "Dump Option":
let input = [none(int32), some(42'i32), none(int32)] let input = [none(int32), some(42'i32), none(int32)]
let output = dump(input, tsNone, asTidy, blockOnly) let output = dump(input, tsNone, asTidy, blockOnly)
assertStringEqual yamlDirs & "\n- !!null ~\n- 42\n- !!null ~", output assertStringEqual yamlDirs & "\n- !!null ~\n- 42\n- !!null ~\n", output
test "Load Table[int, string]": test "Load Table[int, string]":
let input = "23: dreiundzwanzig\n42: zweiundvierzig" let input = "23: dreiundzwanzig\n42: zweiundvierzig"
@ -286,7 +286,7 @@ suite "Serialization":
input[23] = "dreiundzwanzig" input[23] = "dreiundzwanzig"
input[42] = "zweiundvierzig" input[42] = "zweiundvierzig"
var output = dump(input, tsNone, asTidy, blockOnly) var output = dump(input, tsNone, asTidy, blockOnly)
assertStringEqual(yamlDirs & "\n23: dreiundzwanzig\n42: zweiundvierzig", assertStringEqual(yamlDirs & "\n23: dreiundzwanzig\n42: zweiundvierzig\n",
output) output)
test "Load OrderedTable[tuple[int32, int32], string]": test "Load OrderedTable[tuple[int32, int32], string]":
@ -321,7 +321,7 @@ suite "Serialization":
" ? \n" & " ? \n" &
" a: 13\n" & " a: 13\n" &
" b: 47\n" & " b: 47\n" &
" : dreizehnsiebenundvierzig", output) " : dreizehnsiebenundvierzig\n", output)
test "Load Sequences in Sequence": test "Load Sequences in Sequence":
let input = " - [1, 2, 3]\n - [4, 5]\n - [6]" let input = " - [1, 2, 3]\n - [4, 5]\n - [6]"
@ -335,7 +335,7 @@ suite "Serialization":
test "Dump Sequences in Sequence": test "Dump Sequences in Sequence":
let input = @[@[1.int32, 2.int32, 3.int32], @[4.int32, 5.int32], @[6.int32]] let input = @[@[1.int32, 2.int32, 3.int32], @[4.int32, 5.int32], @[6.int32]]
var output = dump(input, tsNone) var output = dump(input, tsNone)
assertStringEqual yamlDirs & "\n- [1, 2, 3]\n- [4, 5]\n- [6]", output assertStringEqual yamlDirs & "\n- [1, 2, 3]\n- [4, 5]\n- [6]\n", output
test "Load Enum": test "Load Enum":
let input = let input =
@ -350,7 +350,7 @@ suite "Serialization":
test "Dump Enum": test "Dump Enum":
let input = @[tlRed, tlGreen, tlYellow] let input = @[tlRed, tlGreen, tlYellow]
var output = dump(input, tsNone, asTidy, blockOnly) var output = dump(input, tsNone, asTidy, blockOnly)
assertStringEqual yamlDirs & "\n- tlRed\n- tlGreen\n- tlYellow", output assertStringEqual yamlDirs & "\n- tlRed\n- tlGreen\n- tlYellow\n", output
test "Load Tuple": test "Load Tuple":
let input = "str: value\ni: 42\nb: true" let input = "str: value\ni: 42\nb: true"
@ -363,7 +363,7 @@ suite "Serialization":
test "Dump Tuple": test "Dump Tuple":
let input = (str: "value", i: 42.int32, b: true) let input = (str: "value", i: 42.int32, b: true)
var output = dump(input, tsNone) var output = dump(input, tsNone)
assertStringEqual yamlDirs & "\nstr: value\ni: 42\nb: true", output assertStringEqual yamlDirs & "\nstr: value\ni: 42\nb: true\n", output
test "Load Tuple - unknown field": test "Load Tuple - unknown field":
let input = "str: value\nfoo: bar\ni: 42\nb: true" let input = "str: value\nfoo: bar\ni: 42\nb: true"
@ -410,7 +410,7 @@ suite "Serialization":
let input = Person(firstnamechar: 'P', surname: "Pan", age: 12) let input = Person(firstnamechar: 'P', surname: "Pan", age: 12)
var output = dump(input, tsNone, asTidy, blockOnly) var output = dump(input, tsNone, asTidy, blockOnly)
assertStringEqual(yamlDirs & assertStringEqual(yamlDirs &
"\nfirstnamechar: P\nsurname: Pan\nage: 12", output) "\nfirstnamechar: P\nsurname: Pan\nage: 12\n", output)
test "Load custom object - unknown field": test "Load custom object - unknown field":
let input = " firstnamechar: P\n surname: Pan\n age: 12\n occupation: free" let input = " firstnamechar: P\n surname: Pan\n age: 12\n occupation: free"
@ -442,7 +442,7 @@ suite "Serialization":
let input = @["one", "two"] let input = @["one", "two"]
var output = dump(input, tsAll, asTidy, blockOnly) var output = dump(input, tsAll, asTidy, blockOnly)
assertStringEqual(yamlDirs & "!n!system:seq(" & assertStringEqual(yamlDirs & "!n!system:seq(" &
"tag:yaml.org;2002:str) \n- !!str one\n- !!str two", output) "tag:yaml.org;2002:str) \n- !!str one\n- !!str two\n", output)
test "Load custom object with explicit root tag": test "Load custom object with explicit root tag":
let input = let input =
@ -457,7 +457,7 @@ suite "Serialization":
let input = Person(firstnamechar: 'P', surname: "Pan", age: 12) let input = Person(firstnamechar: 'P', surname: "Pan", age: 12)
var output = dump(input, tsRootOnly, asTidy, blockOnly) var output = dump(input, tsRootOnly, asTidy, blockOnly)
assertStringEqual(yamlDirs & assertStringEqual(yamlDirs &
"!n!custom:Person \nfirstnamechar: P\nsurname: Pan\nage: 12", output) "!n!custom:Person \nfirstnamechar: P\nsurname: Pan\nage: 12\n", output)
test "Load custom variant object": test "Load custom variant object":
let input = let input =
@ -491,7 +491,7 @@ suite "Serialization":
" - \n" & " - \n" &
" kind: akDog\n" & " kind: akDog\n" &
" - \n" & " - \n" &
" barkometer: 13", output " barkometer: 13\n", output
test "Load custom variant object - missing field": test "Load custom variant object - missing field":
let input = "[{name: Bastet}, {kind: akCat}]" let input = "[{name: Bastet}, {kind: akCat}]"
@ -517,7 +517,7 @@ suite "Serialization":
test "Dump non-variant object with transient fields": test "Dump non-variant object with transient fields":
let input = NonVariantWithTransient(a: "a", b: "b", c: "c", d: "d") let input = NonVariantWithTransient(a: "a", b: "b", c: "c", d: "d")
let output = dump(input, tsNone, asTidy, blockOnly) let output = dump(input, tsNone, asTidy, blockOnly)
assertStringEqual yamlDirs & "\nb: b\nd: d", output assertStringEqual yamlDirs & "\nb: b\nd: d\n", output
test "Load variant object with transient fields": test "Load variant object with transient fields":
let input = "[[gStorable: gs, kind: deA, cStorable: cs], [gStorable: a, kind: deC]]" let input = "[[gStorable: gs, kind: deA, cStorable: cs], [gStorable: a, kind: deC]]"
@ -554,7 +554,7 @@ suite "Serialization":
" - \n" & " - \n" &
" gStorable: a\n" & " gStorable: a\n" &
" - \n" & " - \n" &
" kind: deC", output " kind: deC\n", output
test "Load object with ignored key": test "Load object with ignored key":
let input = "[{x: 1, y: 2}, {x: 3, z: 4, y: 5}, {z: [1, 2, 3], x: 4, y: 5}]" let input = "[{x: 1, y: 2}, {x: 3, z: 4, y: 5}, {z: [1, 2, 3], x: 4, y: 5}]"
@ -590,7 +590,7 @@ suite "Serialization":
" value: b\n" & " value: b\n" &
" next: \n" & " next: \n" &
" value: c\n" & " value: c\n" &
" next: *a", output " next: *a\n", output
test "Load cyclic data structure": test "Load cyclic data structure":
let input = yamlDirs & """!n!system:seq(example.net:Node) let input = yamlDirs & """!n!system:seq(example.net:Node)
@ -652,4 +652,4 @@ suite "Serialization":
assertStringEqual yamlDirs & "!n!system:seq(test:BetterInt) \n" & assertStringEqual yamlDirs & "!n!system:seq(test:BetterInt) \n" &
"- !test:BetterInt 1\n" & "- !test:BetterInt 1\n" &
"- !test:BetterInt 9_998_887\n" & "- !test:BetterInt 9_998_887\n" &
"- !test:BetterInt 98_312", output "- !test:BetterInt 98_312\n", output

View File

@ -10,7 +10,7 @@
## ##
## This is the presenter API, used for generating YAML character streams. ## This is the presenter API, used for generating YAML character streams.
import streams, deques, strutils import std / [streams, deques, strutils, options]
import data, taglib, stream, private/internal, hints, parser import data, taglib, stream, private/internal, hints, parser
type type
@ -85,6 +85,7 @@ type
indentationStep*: int indentationStep*: int
newlines*: NewLineStyle newlines*: NewLineStyle
outputVersion*: OutputYamlVersion outputVersion*: OutputYamlVersion
maxLineLength*: Option[int]
YamlPresenterJsonError* = object of ValueError YamlPresenterJsonError* = object of ValueError
## Exception that may be raised by the YAML presenter when it is ## Exception that may be raised by the YAML presenter when it is
@ -116,17 +117,19 @@ type
const const
defaultPresentationOptions* = defaultPresentationOptions* =
PresentationOptions(style: psDefault, indentationStep: 2, PresentationOptions(style: psDefault, indentationStep: 2,
newlines: nlOSDefault) newlines: nlOSDefault, maxLineLength: some(80))
proc defineOptions*(style: PresentationStyle = psDefault, proc defineOptions*(style: PresentationStyle = psDefault,
indentationStep: int = 2, indentationStep: int = 2,
newlines: NewLineStyle = nlOSDefault, newlines: NewLineStyle = nlOSDefault,
outputVersion: OutputYamlVersion = ov1_2): outputVersion: OutputYamlVersion = ov1_2,
maxLineLength: Option[int] = some(80)):
PresentationOptions {.raises: [].} = PresentationOptions {.raises: [].} =
## Define a set of options for presentation. Convenience proc that requires ## Define a set of options for presentation. Convenience proc that requires
## you to only set those values that should not equal the default. ## you to only set those values that should not equal the default.
PresentationOptions(style: style, indentationStep: indentationStep, PresentationOptions(style: style, indentationStep: indentationStep,
newlines: newlines, outputVersion: outputVersion) newlines: newlines, outputVersion: outputVersion,
maxLineLength: maxLineLength)
proc state(c: Context): DumperState = c.levels[^1] proc state(c: Context): DumperState = c.levels[^1]
@ -147,7 +150,8 @@ proc searchHandle(c: Context, tag: string):
result.handle = item.handle result.handle = item.handle
proc inspect(scalar: string, indentation: int, proc inspect(scalar: string, indentation: int,
words, lines: var seq[tuple[start, finish: int]]): words, lines: var seq[tuple[start, finish: int]],
lineLength: Option[int]):
ScalarStyle {.raises: [].} = ScalarStyle {.raises: [].} =
var var
inLine = false inLine = false
@ -177,7 +181,8 @@ proc inspect(scalar: string, indentation: int,
of '\l': of '\l':
canUsePlain = false # we don't use multiline plain scalars canUsePlain = false # we don't use multiline plain scalars
curWord.finish = i - 1 curWord.finish = i - 1
if curWord.finish - curWord.start + 1 > 80 - indentation: if lineLength.isSome and
curWord.finish - curWord.start + 1 > lineLength.get() - indentation:
return if canUsePlain: sPlain else: sDoubleQuoted return if canUsePlain: sPlain else: sDoubleQuoted
words.add(curWord) words.add(curWord)
inWord = false inWord = false
@ -186,7 +191,8 @@ proc inspect(scalar: string, indentation: int,
if not inLine: curLine.start = i if not inLine: curLine.start = i
inLine = false inLine = false
curLine.finish = i - 1 curLine.finish = i - 1
if curLine.finish - curLine.start + 1 > 80 - indentation: if lineLength.isSome and
curLine.finish - curLine.start + 1 > lineLength.get() - indentation:
canUseLiteral = false canUseLiteral = false
lines.add(curLine) lines.add(curLine)
else: else:
@ -197,7 +203,8 @@ proc inspect(scalar: string, indentation: int,
inLine = true inLine = true
if not inWord: if not inWord:
if not multipleSpaces: if not multipleSpaces:
if curWord.finish - curWord.start + 1 > 80 - indentation: if lineLength.isSome and
curWord.finish - curWord.start + 1 > lineLength.get() - indentation:
return if canUsePlain: sPlain else: sDoubleQuoted return if canUsePlain: sPlain else: sDoubleQuoted
words.add(curWord) words.add(curWord)
curWord.start = i curWord.start = i
@ -205,15 +212,17 @@ proc inspect(scalar: string, indentation: int,
multipleSpaces = false multipleSpaces = false
if inWord: if inWord:
curWord.finish = scalar.len - 1 curWord.finish = scalar.len - 1
if curWord.finish - curWord.start + 1 > 80 - indentation: if lineLength.isSome and
curWord.finish - curWord.start + 1 > lineLength.get() - indentation:
return if canUsePlain: sPlain else: sDoubleQuoted return if canUsePlain: sPlain else: sDoubleQuoted
words.add(curWord) words.add(curWord)
if inLine: if inLine:
curLine.finish = scalar.len - 1 curLine.finish = scalar.len - 1
if curLine.finish - curLine.start + 1 > 80 - indentation: if lineLength.isSome and
curLine.finish - curLine.start + 1 > lineLength.get() - indentation:
canUseLiteral = false canUseLiteral = false
lines.add(curLine) lines.add(curLine)
if scalar.len <= 80 - indentation: if lineLength.isNone or scalar.len <= lineLength.get() - indentation:
result = if canUsePlain: sPlain else: sDoubleQuoted result = if canUsePlain: sPlain else: sDoubleQuoted
elif canUseLiteral: result = sLiteral elif canUseLiteral: result = sLiteral
elif canUseFolded: result = sFolded elif canUseFolded: result = sFolded
@ -227,7 +236,7 @@ template append(target: ptr[string], val: string | char) =
target[].add(val) target[].add(val)
proc writeDoubleQuoted(c: Context, scalar: string, indentation: int, proc writeDoubleQuoted(c: Context, scalar: string, indentation: int,
newline: string) newline: string, lineLength: Option[int])
{.raises: [YamlPresenterOutputError].} = {.raises: [YamlPresenterOutputError].} =
var curPos = indentation var curPos = indentation
let t = c.target let t = c.target
@ -235,7 +244,7 @@ proc writeDoubleQuoted(c: Context, scalar: string, indentation: int,
t.append('"') t.append('"')
curPos.inc() curPos.inc()
for c in scalar: for c in scalar:
if curPos == 79: if lineLength.isSome and curPos == lineLength.get() - 1:
t.append('\\') t.append('\\')
t.append(newline) t.append(newline)
t.append(repeat(' ', indentation)) t.append(repeat(' ', indentation))
@ -316,17 +325,19 @@ proc writeFolded(c: Context, scalar: string, indentation, indentStep: int,
newline: string) newline: string)
{.raises: [YamlPresenterOutputError].} = {.raises: [YamlPresenterOutputError].} =
let t = c.target let t = c.target
let lineLength = (
if c.options.maxLineLength.isSome: c.options.maxLineLength.get() else: 1024)
try: try:
t.append(">") t.append(">")
if scalar[^1] != '\l': t.append('-') if scalar[^1] != '\l': t.append('-')
if scalar[0] in [' ', '\t']: t.append($indentStep) if scalar[0] in [' ', '\t']: t.append($indentStep)
var curPos = 80 var curPos = lineLength
for word in words: for word in words:
if word.start > 0 and scalar[word.start - 1] == '\l': if word.start > 0 and scalar[word.start - 1] == '\l':
t.append(newline & newline) t.append(newline & newline)
t.append(repeat(' ', indentation + indentStep)) t.append(repeat(' ', indentation + indentStep))
curPos = indentation + indentStep curPos = indentation + indentStep
elif curPos + (word.finish - word.start + 1) > 80: elif curPos + (word.finish - word.start + 1) > lineLength:
t.append(newline) t.append(newline)
t.append(repeat(' ', indentation + indentStep)) t.append(repeat(' ', indentation + indentStep))
curPos = indentation + indentStep curPos = indentation + indentStep
@ -528,18 +539,21 @@ proc doPresent(c: var Context, s: var YamlStream) =
else: c.writeDoubleQuotedJson(item.scalarContent) else: c.writeDoubleQuotedJson(item.scalarContent)
elif c.options.style == psCanonical: elif c.options.style == psCanonical:
c.writeDoubleQuoted(item.scalarContent, c.writeDoubleQuoted(item.scalarContent,
indentation + c.options.indentationStep, newline) indentation + c.options.indentationStep, newline,
c.options.maxLineLength)
else: else:
var words, lines = newSeq[tuple[start, finish: int]]() var words, lines = newSeq[tuple[start, finish: int]]()
case item.scalarContent.inspect( case item.scalarContent.inspect(
indentation + c.options.indentationStep, words, lines) indentation + c.options.indentationStep, words, lines,
c.options.maxLineLength)
of sLiteral: c.writeLiteral(item.scalarContent, indentation, of sLiteral: c.writeLiteral(item.scalarContent, indentation,
c.options.indentationStep, lines, newline) c.options.indentationStep, lines, newline)
of sFolded: c.writeFolded(item.scalarContent, indentation, of sFolded: c.writeFolded(item.scalarContent, indentation,
c.options.indentationStep, words, newline) c.options.indentationStep, words, newline)
of sPlain: c.safeWrite(item.scalarContent) of sPlain: c.safeWrite(item.scalarContent)
of sDoubleQuoted: c.writeDoubleQuoted(item.scalarContent, of sDoubleQuoted: c.writeDoubleQuoted(item.scalarContent,
indentation + c.options.indentationStep, newline) indentation + c.options.indentationStep, newline,
c.options.maxLineLength)
of yamlAlias: of yamlAlias:
if c.options.style == psJson: if c.options.style == psJson:
raise newException(YamlPresenterJsonError, raise newException(YamlPresenterJsonError,
@ -727,6 +741,7 @@ proc doPresent(c: var Context, s: var YamlStream) =
indentation -= c.options.indentationStep indentation -= c.options.indentationStep
of yamlEndDoc: of yamlEndDoc:
firstDoc = false firstDoc = false
c.safeWrite(newline)
proc present*(s: var YamlStream, target: Stream, proc present*(s: var YamlStream, target: Stream,
options: PresentationOptions = defaultPresentationOptions) options: PresentationOptions = defaultPresentationOptions)