Skip to content

Commit

Permalink
Std functions (#115)
Browse files Browse the repository at this point in the history
* Missing STD library functions implemented
  • Loading branch information
javaduke authored Jan 16, 2023
1 parent 410d6ca commit 750d072
Show file tree
Hide file tree
Showing 9 changed files with 164 additions and 24 deletions.
5 changes: 4 additions & 1 deletion pom.xml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
Copyright 2019-2022 the original author or authors.
Copyright 2019-2023 the original author or authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -85,6 +85,8 @@
<fastparse.version>2.3.0</fastparse.version>
<pprint.version>0.5.9</pprint.version>
<scalatags.version>0.9.1</scalatags.version>

<jakarta-xml-bind-api-version>4.0.0</jakarta-xml-bind-api-version>
</properties>

<dependencies>
Expand Down Expand Up @@ -475,6 +477,7 @@
</dependency>
</dependencies>
</profile>

<profile>
<id>release</id>
<build>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package com.datasonnet.plugins;

/*-
* Copyright 2019-2021 the original author or authors.
* Copyright 2019-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand All @@ -15,7 +15,6 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/

import com.datasonnet.RecentsMap;
import com.datasonnet.document.DefaultDocument;
import com.datasonnet.document.Document;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package com.datasonnet.plugins.jackson;

/*-
* Copyright 2019-2020 the original author or authors.
* Copyright 2019-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand All @@ -15,7 +15,6 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/

import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonProperty;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package com.datasonnet.plugins.jackson;

/*-
* Copyright 2019-2020 the original author or authors.
* Copyright 2019-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand All @@ -15,7 +15,6 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/

import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.SerializerProvider;
import com.fasterxml.jackson.databind.ser.std.StdSerializer;
Expand Down
92 changes: 81 additions & 11 deletions src/main/scala/com/datasonnet/jsonnet/Std.scala
Original file line number Diff line number Diff line change
Expand Up @@ -18,19 +18,14 @@ package com.datasonnet.jsonnet
import java.io.StringWriter
import java.nio.charset.StandardCharsets.UTF_8
import java.util.Base64
import java.util.zip.GZIPOutputStream

import com.datasonnet.jsonnet.Expr.Member.Visibility
import com.datasonnet.jsonnet.Expr.{BinaryOp, False, Params}

import scala.collection.mutable
import scala.collection.compat._
import com.datasonnet.jsonnet.Std.builtinWithDefaults
import scala.util.matching.Regex

import ujson.Value
import sourcecode.Text.generate
import ujson.Bool

import util.control.Breaks._
import scala.util.matching.Regex

/**
* The Jsonnet standard library, `std`, with each builtin function implemented
Expand Down Expand Up @@ -80,8 +75,8 @@ object Std {
val default = args("default")

val fieldValue = args("inc_hidden") match {
case Val.False => if (o.getVisibleKeys().get(f.value) == Some(false)) o.value(f.value, 0)(new FileScope(null, Map.empty), ev) else default
case Val.True => if (o.getVisibleKeys().get(f.value).isDefined) o.value(f.value, 0)(new FileScope(null, Map.empty), ev) else default
case Val.False => if (o.getVisibleKeys().get(f.value) == Some(false)) o.value(f.value, -1)(new FileScope(null, Map.empty), ev) else default
case Val.True => if (o.getVisibleKeys().get(f.value).isDefined) o.value(f.value, -1)(new FileScope(null, Map.empty), ev) else default
case _ => throw Error.Delegate("inc_hidden has to be a boolean, got" + args("inc_hidden").getClass)
}

Expand Down Expand Up @@ -115,6 +110,28 @@ object Std {
}
Val.Arr(maybeSorted.map(k => Val.Lazy(Val.Str(k))))
},
builtin("objectValues", "o"){ (ev, fs, v1: Val.Obj) =>
val keys = v1.getVisibleKeys()
.collect{case (k, false) => k}
.toSeq
val maybeSorted = if(ev.preserveOrder) {
keys
} else {
keys.sorted
}
Val.Arr(maybeSorted.map(k => Val.Lazy(v1.value(k, -1)(fs, ev))))
},
builtin("objectValuesAll", "o"){ (ev, fs, v1: Val.Obj) =>
val keys = v1.getVisibleKeys()
.collect{case (k, _) => k}
.toSeq
val maybeSorted = if(ev.preserveOrder) {
keys
} else {
keys.sorted
}
Val.Arr(maybeSorted.map(k => Val.Lazy(v1.value(k, -1)(fs, ev))))
},
builtin("type", "x"){ (ev, fs, v1: Val) =>
v1 match{
case Val.True | Val.False => "boolean"
Expand Down Expand Up @@ -487,6 +504,10 @@ object Std {
Val.Arr(out.toSeq)
},

builtin("reverse", "arr"){ (ev, fs, arr: Val.Arr) =>
Val.Arr(arr.value.reverse)
},

builtin("manifestIni", "v"){ (ev, fs, v: Val) =>
val materialized = Materializer(v)(ev)
def render(x: ujson.Value) = x match{
Expand Down Expand Up @@ -601,6 +622,16 @@ object Std {
rec(Materializer(value)(ev)).render

},

builtin("manifestTomlEx", "toml", "indent") { (ev, fs, toml: Val, indent: Int) =>
throw new Error.Delegate("Function manifestTomlEx is not yet implemented")
false
},
builtin("parseYaml", "str") { (ev, fs, str: Val) =>
throw new Error.Delegate("Function parseYaml is not yet implemented")
false
},

builtin("base64", "v"){ (ev, fs, v: Val) =>
v match{
case Val.Str(value) => Base64.getEncoder().encodeToString(value.getBytes)
Expand Down Expand Up @@ -781,6 +812,14 @@ object Std {
builtin("splitLimit", "str", "c", "maxSplits"){ (ev, fs, str: String, c: String, maxSplits: Int) =>
Val.Arr(str.split(java.util.regex.Pattern.quote(c), maxSplits + 1).map(s => Val.Lazy(Val.Str(s))))
},
builtin("splitLimitR", "str", "c", "maxSplits"){ (ev, fs, str: String, c: String, maxSplits: Int) =>
if (maxSplits == -1) {
Val.Arr(str.split(java.util.regex.Pattern.quote(c), maxSplits + 1).map(s => Val.Lazy(Val.Str(s))))
} else {
val split = str.reverse.split(java.util.regex.Pattern.quote(c.reverse), maxSplits + 1)
Val.Arr(split.map(s => Val.Lazy(Val.Str(s.reverse))).reverse)
}
},
builtin("stringChars", "str"){ (ev, fs, str: String) =>
stringChars(str)
},
Expand Down Expand Up @@ -852,7 +891,31 @@ object Std {
scope.bindings(1).get.force
}
),

builtin("slice", "indexable", "index", "end", "step") { (ev, fs, indexable: Val, index: Int, end: Int, step: Int) =>
val slice: Val = indexable match {
case Val.Arr(arr) =>
val a = arr.value
Val.Arr(List.tabulate(a.size) {i =>
if ((i+1) % step == 0 && i >= index && i < end) Some(a(i))
else None }.flatten)
case Val.Str(str) =>
val a = str.value
Val.Str(List.tabulate(a.size) {i =>
if ((i+1) % step == 0 && i >= index && i < end) Some(a(i))
else None }.flatten.mkString)
case i => throw Error.Delegate("Expected Array or String, got: " + i.prettyName)
}
slice
},
builtin("any", "arr") { (ev, fs, arr: Val.Arr) =>
val a = arr.value
//First see if all values are boolean
val allBool = a.find(b => !(Materializer.apply(b.force)(ev).isInstanceOf[Bool])) == None
if (!allBool) {
throw Error.Delegate("Array must contain only boolean values")
}
a.find(b => Materializer.apply(b.force)(ev).value == true) != None
},
"extVar" -> Val.Func(
None,
Params(Array(("x", None, 0))),
Expand Down Expand Up @@ -928,6 +991,13 @@ object Std {
val Seq(v1: T1, v2: T2, v3: T3) = validate(vs, ev, fs, Array(implicitly[ReadWriter[T1]], implicitly[ReadWriter[T2]], implicitly[ReadWriter[T3]]))
eval(ev, fs, v1, v2, v3)
}

def builtin[R: ReadWriter, T1: ReadWriter, T2: ReadWriter, T3: ReadWriter, T4: ReadWriter](name: String, p1: String, p2: String, p3: String, p4: String)
(eval: (EvalScope, FileScope, T1, T2, T3, T4) => R): (String, Val.Func) = builtin0(name, p1, p2, p3, p4){ (vs, ev, fs) =>
val Seq(v1: T1, v2: T2, v3: T3, v4: T4) = validate(vs, ev, fs, Array(implicitly[ReadWriter[T1]], implicitly[ReadWriter[T2]], implicitly[ReadWriter[T3]], implicitly[ReadWriter[T4]]))
eval(ev, fs, v1, v2, v3, v4)
}

def builtin0[R: ReadWriter](name: String, params: String*)(eval: (Array[Val], EvalScope, FileScope) => R) = {
val paramData = params.zipWithIndex.map{case (k, i) => (k, None, i)}.toArray
val paramIndices = params.indices.toArray
Expand Down
2 changes: 1 addition & 1 deletion src/test/java/com/datasonnet/JavaWriterTest.java
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package com.datasonnet;

/*-
* Copyright 2019-2021 the original author or authors.
* Copyright 2019-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand Down
69 changes: 66 additions & 3 deletions src/test/java/com/datasonnet/StdTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -27,15 +27,78 @@
import java.io.IOException;
import java.net.URISyntaxException;

import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.Assertions.fail;
import static org.junit.jupiter.api.Assertions.*;

public class StdTest {

@Test
void testStdGet() throws IOException, URISyntaxException, JSONException {
Mapper mapper = new Mapper(TestResourceReader.readFileAsString("stdGet.ds"));
Document<String> response = mapper.transform(new DefaultDocument<>("{}", MediaTypes.APPLICATION_JSON));
Document<String> response = mapper.transform(new DefaultDocument<>("{}", MediaTypes.APPLICATION_JSON));
JSONAssert.assertEquals("{\"hidden\":\"HiddenMessage\",\"noHidden\":\"NONE\",\"obj\":{\"Hello\":\"World\"},\"nonExistent\":null,\"nonExistentD\":\"DefaultNonExistent\",\"a\":[1,2,3],\"b\":[9,8,7]}", response.getContent(), true);
}

@Test
void testStdObjectValues() throws IOException, URISyntaxException, JSONException {
Mapper mapper = new Mapper("std.objectValues(" + TestResourceReader.readFileAsString("stdObjectValues.ds") + ")");
Document<String> response = mapper.transform(new DefaultDocument<>("{}", MediaTypes.APPLICATION_JSON));
JSONAssert.assertEquals("[\"Hello\",{\"Hello\":\"World\"},[1,2,3]]", response.getContent(), true);
}

@Test
void testStdObjectValuesAll() throws IOException, URISyntaxException, JSONException {
Mapper mapper = new Mapper("std.objectValuesAll(" + TestResourceReader.readFileAsString("stdObjectValues.ds") + ")");
Document<String> response = mapper.transform(new DefaultDocument<>("{}", MediaTypes.APPLICATION_JSON));
JSONAssert.assertEquals("[\"Hello\",{\"Hello\":\"World\"},[1,2,3],\"HiddenMessage\"]", response.getContent(), true);
}

@Test
void testStdReverse() throws IOException, URISyntaxException, JSONException {
Mapper mapper = new Mapper("std.reverse([1,2,3])");
Document<String> response = mapper.transform(new DefaultDocument<>("{}", MediaTypes.APPLICATION_JSON));
JSONAssert.assertEquals("[3,2,1]", response.getContent(), true);
}

@Test
void testStdSplitLimitR() throws IOException, URISyntaxException, JSONException {
Mapper mapper = new Mapper("std.splitLimitR(\"testX1YsplitX1YrightX1Ytest2\", \"X1Y\", 2)");
Document<String> response = mapper.transform(new DefaultDocument<>("{}", MediaTypes.APPLICATION_JSON));
JSONAssert.assertEquals("[\"testX1Ysplit\",\"right\",\"test2\"]", response.getContent(), true);
}

@Test
void testStdSlice() throws IOException, URISyntaxException, JSONException {
Mapper mapper = new Mapper("std.slice([1, 2, 3, 4, 5, 6], 0, 4, 1)");
Document<String> response = mapper.transform(new DefaultDocument<>("{}", MediaTypes.APPLICATION_JSON));
JSONAssert.assertEquals("[ 1, 2, 3, 4 ]", response.getContent(), true);

mapper = new Mapper("std.slice([1, 2, 3, 4, 5, 6], 1, 6, 2)");
response = mapper.transform(new DefaultDocument<>("{}", MediaTypes.APPLICATION_JSON));
JSONAssert.assertEquals("[ 2, 4, 6 ]", response.getContent(), true);

mapper = new Mapper("std.slice(\"jsonnet\", 0, 4, 1)");
response = mapper.transform(new DefaultDocument<>("{}", MediaTypes.APPLICATION_JSON));
JSONAssert.assertEquals("\"json\"", response.getContent(), true);
}

@Test
void testStdAny() throws IOException, URISyntaxException, JSONException {
Mapper mapper = new Mapper("std.any([false, true, false])");
Document<String> response = mapper.transform(new DefaultDocument<>("{}", MediaTypes.APPLICATION_JSON));
assertEquals("true", response.getContent());

mapper = new Mapper("std.any([false, false, false])");
response = mapper.transform(new DefaultDocument<>("{}", MediaTypes.APPLICATION_JSON));
assertEquals("false", response.getContent());

try {
mapper = new Mapper("std.any([false, \"HELLO\", false])");
response = mapper.transform(new DefaultDocument<>("{}", MediaTypes.APPLICATION_JSON));
fail("This should fail with java.lang.IllegalArgumentException:");
} catch (Exception e) {
String msg = e.getMessage();
assertTrue(msg != null && msg.contains("Array must contain only boolean values"));
}

}
}
3 changes: 1 addition & 2 deletions src/test/java/com/datasonnet/javatest/WsdlGeneratedObj.java
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package com.datasonnet.javatest;

/*-
* Copyright 2019-2020 the original author or authors.
* Copyright 2019-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand All @@ -15,7 +15,6 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/

import javax.xml.bind.JAXBElement;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
Expand Down
8 changes: 8 additions & 0 deletions src/test/resources/stdObjectValues.ds
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
s: "Hello",
o: {
Hello: "World"
},
a: [1,2,3],
hidden:: "HiddenMessage"
}

0 comments on commit 750d072

Please sign in to comment.