Skip to content

Commit a5c3bc2

Browse files
Implement TOML manifest functions (#230)
1 parent 73b3826 commit a5c3bc2

8 files changed

Lines changed: 409 additions & 174 deletions

File tree

sjsonnet/src/sjsonnet/Materializer.scala

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -61,9 +61,9 @@ abstract class Materializer {
6161
case _ =>
6262
Error.fail("Unknown value type " + v.prettyName)
6363
}
64-
65-
}catch {case _: StackOverflowError =>
66-
Error.fail("Stackoverflow while materializing, possibly due to recursive value")
64+
} catch {
65+
case _: StackOverflowError =>
66+
Error.fail("Stackoverflow while materializing, possibly due to recursive value")
6767
}
6868

6969
def reverse(pos: Position, v: ujson.Value): Val = v match{

sjsonnet/src/sjsonnet/Std.scala

Lines changed: 64 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -697,7 +697,7 @@ class Std {
697697

698698
private object ManifestJson extends Val.Builtin1("manifestJson", "v") {
699699
def evalRhs(v: Val, ev: EvalScope, pos: Position): Val =
700-
Val.Str(pos, Materializer.apply0(v, new MaterializeJsonRenderer())(ev).toString)
700+
Val.Str(pos, Materializer.apply0(v, MaterializeJsonRenderer())(ev).toString)
701701
}
702702

703703
private object ManifestJsonMinified extends Val.Builtin1("manifestJsonMinified", "v") {
@@ -727,6 +727,65 @@ class Std {
727727
}
728728
}
729729

730+
private object ManifestTomlEx extends Val.Builtin2("manifestTomlEx", "value", "indent") {
731+
private def isTableArray(v: Val) = v match {
732+
case s: Val.Arr => s.length > 0 && s.asLazyArray.forall(_.isInstanceOf[Val.Obj])
733+
case _ => false
734+
}
735+
736+
private def isSection(v: Val) = v.isInstanceOf[Val.Obj] || isTableArray(v)
737+
738+
private def renderTableInternal(out: StringWriter, v: Val.Obj, cumulatedIndent: String, indent: String, path: Seq[String], indexedPath: Seq[String])(implicit ev : EvalScope): StringWriter = {
739+
val (sections, nonSections) = v.visibleKeyNames.partition(k => isSection(v.value(k, v.pos)(ev)))
740+
for (k <- nonSections.sorted) {
741+
out.write(cumulatedIndent)
742+
out.write(TomlRenderer.escapeKey(k))
743+
out.write(" = ")
744+
Materializer.apply0(v.value(k, v.pos)(ev), new TomlRenderer(out, cumulatedIndent, indent))(ev)
745+
}
746+
out.write('\n')
747+
748+
for (k <- sections.sorted) {
749+
val v0 = v.value(k, v.pos, v)(ev)
750+
if (isTableArray(v0)) {
751+
for (i <- 0 until v0.asArr.length) {
752+
out.write(cumulatedIndent)
753+
renderTableArrayHeader(out, path :+ k)
754+
out.write('\n')
755+
renderTableInternal(out, v0.asArr.force(i).asObj, cumulatedIndent + indent, indent, path :+ k,
756+
indexedPath ++ Seq(k, i.toString))
757+
}
758+
} else {
759+
out.write(cumulatedIndent)
760+
renderTableHeader(out, path :+ k)
761+
out.write('\n')
762+
renderTableInternal(out, v0.asObj, cumulatedIndent + indent, indent, path :+ k, indexedPath :+ k)
763+
}
764+
}
765+
out
766+
}
767+
768+
private def renderTableHeader(out: StringWriter, path: Seq[String]) = {
769+
out.write('[')
770+
out.write(path.map(TomlRenderer.escapeKey).mkString("."))
771+
out.write(']')
772+
out
773+
}
774+
775+
private def renderTableArrayHeader(out: StringWriter, path: Seq[String]) = {
776+
out.write('[')
777+
renderTableHeader(out, path)
778+
out.write(']')
779+
out
780+
}
781+
782+
def evalRhs(v: Val, indent: Val, ev: EvalScope, pos: Position): Val = {
783+
val out = new StringWriter
784+
renderTableInternal(out, v.force.asObj, "", indent.asString, Seq.empty[String], Seq.empty[String])(ev)
785+
Val.Str(pos, out.toString.strip)
786+
}
787+
}
788+
730789
private object Set_ extends Val.Builtin2("set", "arr", "keyF", Array(null, Val.False(dummyPos))) {
731790
def evalRhs(arr: Val, keyF: Val, ev: EvalScope, pos: Position): Val = {
732791
uniqArr(pos, ev, sortArr(pos, ev, arr, keyF), keyF)
@@ -1056,6 +1115,10 @@ class Std {
10561115
builtin(ManifestJson),
10571116
builtin(ManifestJsonMinified),
10581117
builtin(ManifestJsonEx),
1118+
builtin("manifestToml", "value"){ (pos, ev, value: Val) =>
1119+
ManifestTomlEx.evalRhs(value, Val.Str(pos, ""), ev, pos)
1120+
},
1121+
builtin(ManifestTomlEx),
10591122
builtinWithDefaults("manifestYamlDoc",
10601123
"v" -> null,
10611124
"indent_array_in_object" -> Val.False(dummyPos)){ (args, pos, ev) =>
Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
package sjsonnet
2+
3+
import upickle.core.{ArrVisitor, CharBuilder, ObjVisitor, SimpleVisitor, Visitor}
4+
5+
import java.io.StringWriter
6+
import java.util.regex.Pattern
7+
8+
9+
class TomlRenderer(out: StringWriter = new java.io.StringWriter(), cumulatedIndent: String, indent: String) extends SimpleVisitor[StringWriter, StringWriter]{
10+
override def expectedMsg: String = "unimplemented type in Materializer"
11+
private object objectKeyRenderer extends upickle.core.SimpleVisitor[StringWriter, StringWriter] {
12+
override def expectedMsg = "expected string"
13+
14+
override def visitNull(index: Int): StringWriter = {
15+
TomlRenderer.this.visitNull(index)
16+
}
17+
18+
override def visitString(s: CharSequence, index: Int): StringWriter = {
19+
if (s == null) visitNull(index)
20+
else {
21+
out.write(TomlRenderer.escapeKey(s.toString))
22+
out
23+
}
24+
}
25+
}
26+
27+
private var depth = 0
28+
29+
private def flush = {
30+
if (depth == 0) out.write("\n")
31+
out.flush()
32+
out
33+
}
34+
35+
override def visitNull(index: Int): StringWriter = Error.fail("Tried to manifest \"null\"")
36+
37+
override def visitTrue(index: Int): StringWriter = {
38+
out.write("true")
39+
flush
40+
}
41+
42+
override def visitFalse(index: Int): StringWriter = {
43+
out.write("false")
44+
flush
45+
}
46+
47+
override def visitString(s: CharSequence, index: Int): StringWriter = {
48+
if (s == null) {
49+
visitNull(index)
50+
} else {
51+
val charBuilder = new CharBuilder()
52+
upickle.core.RenderUtils.escapeChar(null, charBuilder, s, unicode = true)
53+
out.write(charBuilder.makeString())
54+
flush
55+
}
56+
}
57+
58+
override def visitFloat64(d: Double, index: Int): StringWriter = {
59+
d match {
60+
case Double.PositiveInfinity => out.write("inf")
61+
case Double.NegativeInfinity => out.write("-inf")
62+
case d if java.lang.Double.isNaN(d) => out.write("nan")
63+
case d if math.round(d).toDouble == d => out.write(java.lang.Long.toString(d.toLong))
64+
case d => out.write(java.lang.Double.toString(d))
65+
}
66+
flush
67+
}
68+
69+
override def visitArray(length: Int, index: Int): ArrVisitor[StringWriter, StringWriter] = new ArrVisitor[StringWriter, StringWriter] {
70+
private val inline = length == 0 || depth > 0
71+
private val newElementIndent = if (inline) "" else cumulatedIndent + indent
72+
private val separator = if (inline && length > 0) " " else if (inline && length == 0) "" else "\n"
73+
private var addComma = false
74+
75+
depth += 1
76+
out.write("[" + separator)
77+
def subVisitor: Visitor[StringWriter, StringWriter] = {
78+
if (addComma) out.write("," + separator)
79+
out.write(newElementIndent)
80+
TomlRenderer.this
81+
}
82+
def visitValue(v: StringWriter, index: Int): Unit = {
83+
addComma = true
84+
}
85+
def visitEnd(index: Int): StringWriter = {
86+
addComma = false
87+
depth -= 1
88+
out.write(separator)
89+
if (!inline) out.write(cumulatedIndent)
90+
out.write("]")
91+
flush
92+
}
93+
}
94+
95+
override def visitObject(length: Int, index: Int): ObjVisitor[StringWriter, StringWriter] = new ObjVisitor[StringWriter, StringWriter] {
96+
private var addComma = false
97+
depth += 1
98+
out.write("{ ")
99+
def subVisitor: Visitor[StringWriter, StringWriter] = TomlRenderer.this
100+
def visitKey(index: Int): Visitor[StringWriter, StringWriter] = {
101+
if (addComma) out.write(", ")
102+
objectKeyRenderer
103+
}
104+
def visitKeyValue(s: Any): Unit = {
105+
out.write(" = ")
106+
}
107+
def visitValue(v: StringWriter, index: Int): Unit = {
108+
addComma = true
109+
}
110+
def visitEnd(index: Int): StringWriter = {
111+
addComma = false
112+
depth -= 1
113+
out.write(" }")
114+
flush
115+
}
116+
}
117+
}
118+
119+
object TomlRenderer {
120+
private val bareAllowed = Pattern.compile("[A-Za-z0-9_-]+")
121+
def escapeKey(key: String): String = if (bareAllowed.matcher(key).matches()) key else {
122+
val out = new StringWriter()
123+
BaseRenderer.escape(out, key, unicode = true)
124+
out.toString
125+
}
126+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
/*
2+
Copyright 2020 Google Inc. All rights reserved.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
std.manifestTomlEx({ a: [{ b: { c: null } }] }, ' ')
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
/*
2+
Copyright 2020 Google Inc. All rights reserved.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
std.manifestTomlEx([], ' ')

0 commit comments

Comments
 (0)