Skip to content

Commit

Permalink
Add a few more methods to sjsonnet, like std.sha*, std.trim, std.isEmpty
Browse files Browse the repository at this point in the history
  • Loading branch information
stephenamar-db committed Nov 3, 2024
1 parent 2f130b6 commit b535b3c
Show file tree
Hide file tree
Showing 7 changed files with 151 additions and 18 deletions.
4 changes: 4 additions & 0 deletions readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -280,6 +280,10 @@ to ensure the output bytecode remains compatible with users on older JVMs.

## Changelog

### 0.4.11
- Implement `std.isEmpty`, `std.xor`, `std.xnor`, `std.trim`,
`std.equalsIgnoreCase`, `std.sha1`, `std.sha256`, `std.sha512`, `std.sha3`

### 0.4.10

- Implement `std.get` [#202](https://github.com/databricks/sjsonnet/pull/202),
Expand Down
12 changes: 12 additions & 0 deletions sjsonnet/src-js/sjsonnet/Platform.scala
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,18 @@ object Platform {
def md5(s: String): String = {
throw new Exception("MD5 not implemented in Scala.js")
}
def sha1(s: String): String = {
throw new Exception("SHA1 not implemented in Scala.js")
}
def sha256(s: String): String = {
throw new Exception("SHA256 not implemented in Scala.js")
}
def sha512(s: String): String = {
throw new Exception("SHA512 not implemented in Scala.js")
}
def sha3(s: String): String = {
throw new Exception("SHA3 not implemented in Scala.js")
}
def hashFile(file: File): String = {
throw new Exception("hashFile not implemented in Scala.js")
}
Expand Down
44 changes: 30 additions & 14 deletions sjsonnet/src-jvm/sjsonnet/Platform.scala
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,13 @@ object Platform {
def gzipBytes(b: Array[Byte]): String = {
val outputStream: ByteArrayOutputStream = new ByteArrayOutputStream(b.length)
val gzip: GZIPOutputStream = new GZIPOutputStream(outputStream)
gzip.write(b)
gzip.close()
val gzippedBase64: String = Base64.getEncoder.encodeToString(outputStream.toByteArray)
outputStream.close()
gzippedBase64
try {
gzip.write(b)
Base64.getEncoder.encodeToString(outputStream.toByteArray)
} finally {
gzip.close()
outputStream.close()
}
}
def gzipString(s: String): String = {
gzipBytes(s.getBytes())
Expand All @@ -33,11 +35,13 @@ object Platform {
// Set compression to specified level
val level = compressionLevel.getOrElse(LZMA2Options.PRESET_DEFAULT)
val xz: XZOutputStream = new XZOutputStream(outputStream, new LZMA2Options(level))
xz.write(b)
xz.close()
val xzedBase64: String = Base64.getEncoder.encodeToString(outputStream.toByteArray)
outputStream.close()
xzedBase64
try {
xz.write(b)
Base64.getEncoder.encodeToString(outputStream.toByteArray)
} finally {
xz.close()
outputStream.close()
}
}

def xzString(s: String, compressionLevel: Option[Int]): String = {
Expand All @@ -48,13 +52,25 @@ object Platform {
val yaml: java.util.LinkedHashMap[String, Object] = new Yaml(new Constructor(classOf[java.util.LinkedHashMap[String, Object]])).load(yamlString)
new JSONObject(yaml).toString()
}
def md5(s: String): String = {
java.security.MessageDigest.getInstance("MD5")

private def computeHash(algorithm: String, s: String) = {
java.security.MessageDigest.getInstance(algorithm)
.digest(s.getBytes("UTF-8"))
.map{ b => String.format("%02x", new java.lang.Integer(b & 0xff))}
.map{ b => String.format("%02x", (b & 0xff).asInstanceOf[Integer])}
.mkString
}

def md5(s: String): String = computeHash("MD5", s)

def sha1(s: String): String = computeHash("SHA-1", s)

def sha256(s: String): String = computeHash("SHA-256", s)

def sha512(s: String): String = computeHash("SHA-512", s)

// Same as go-jsonnet https://github.com/google/go-jsonnet/blob/2b4d7535f540f128e38830492e509a550eb86d57/builtins.go#L959
def sha3(s: String): String = computeHash("SHA3-512", s)

private[this] val xxHashFactory = XXHashFactory.fastestInstance()

def hashFile(file: File): String = {
Expand All @@ -75,6 +91,6 @@ object Platform {
fis.close()
}

hash.getValue().toString
hash.getValue.toString
}
}
12 changes: 12 additions & 0 deletions sjsonnet/src-native/sjsonnet/Platform.scala
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,18 @@ object Platform {
def md5(s: String): String = {
throw new Exception("MD5 not implemented in Scala Native")
}
def sha1(s: String): String = {
throw new Exception("SHA1 not implemented in Scala Native")
}
def sha256(s: String): String = {
throw new Exception("SHA256 not implemented in Scala Native")
}
def sha512(s: String): String = {
throw new Exception("SHA512 not implemented in Scala Native")
}
def sha3(s: String): String = {
throw new Exception("SHA3 not implemented in Scala Native")
}

def hashFile(file: File): String = {
// File hashes in Scala Native are just the file content
Expand Down
32 changes: 29 additions & 3 deletions sjsonnet/src/sjsonnet/Std.scala
Original file line number Diff line number Diff line change
Expand Up @@ -1253,9 +1253,36 @@ class Std {
}
},
"all" -> All,
"any" -> Any
"any" -> Any,
builtin("isEmpty", "str") { (_, _, str: String) =>
str.isEmpty
},
builtin("trim", "str") { (_, _, str: String) =>
str.trim
},
builtin("equalsIgnoreCase", "str1", "str2") { (_, _, str1: String, str2: String) =>
str1.equalsIgnoreCase(str2)
},
builtin("xor", "bool1", "bool2") { (_, _, bool1: Boolean, bool2: Boolean) =>
bool1 ^ bool2
},
builtin("xnor", "bool1", "bool2") { (_, _, bool1: Boolean, bool2: Boolean) =>
!(bool1 ^ bool2)
},
builtin("sha1", "str") { (_, _, str: String) =>
Platform.sha1(str)
},
builtin("sha256", "str") { (_, _, str: String) =>
Platform.sha256(str)
},
builtin("sha512", "str") { (_, _, str: String) =>
Platform.sha512(str)
},
builtin("sha3", "str") { (_, _, str: String) =>
Platform.sha3(str)
},
)
val Std = Val.Obj.mk(
val Std: Val.Obj = Val.Obj.mk(
null,
functions.toSeq
.map{
Expand Down Expand Up @@ -1382,7 +1409,6 @@ class Std {
case vs: Val.Arr =>
new Val.Arr(
pos,

if (vs.forall(_.isInstanceOf[Val.Str])){
vs.asStrictArray.map(_.cast[Val.Str]).sortBy(_.value)
}else if (vs.forall(_.isInstanceOf[Val.Num])) {
Expand Down
22 changes: 22 additions & 0 deletions sjsonnet/test/src-jvm/sjsonnet/StdShasTests.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package sjsonnet

import sjsonnet.TestUtils.eval
import utest._

object StdShasTests extends TestSuite {

def tests: Tests = Tests {
test {
eval("std.sha1('')") ==> ujson.Str("da39a3ee5e6b4b0d3255bfef95601890afd80709")
eval("std.sha256('')") ==> ujson.Str("e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855")
eval("std.sha512('')") ==> ujson.Str("cf83e1357eefb8bdf1542850d66d8007d620e4050b5715dc83f4a921d36ce9ce47d0d13c5d85f2b0ff8318d2877eec2f63b931bd47417a81a538327af927da3e")
eval("std.sha3('')") ==> ujson.Str("a69f73cca23a9ac5c8b567dc185a756e97c982164fe25859e0d1dcc1475c80a615b2123af1f5f94c11e3e9402c3ac558f500199d95b6d3e301758586281dcd26")
}
test {
eval("std.sha1('foo')") ==> ujson.Str("0beec7b5ea3f0fdbc95d0dd47f3c5bc275da8a33")
eval("std.sha256('foo')") ==> ujson.Str("2c26b46b68ffc68ff99b453c1d30413413422d706483bfa0f98a5e886266e7ae")
eval("std.sha512('foo')") ==> ujson.Str("f7fbba6e0636f890e56fbbf3283e524c6fa3204ae298382d624741d0dc6638326e282c41be5e4254d8820772c5518a2c5a8c0c7f7eda19594a7eb539453e1ed7")
eval("std.sha3('foo')") ==> ujson.Str("4bca2b137edc580fe50a88983ef860ebaca36c857b1f492839d6d7392452a63c82cbebc68e3b70a2a1480b4bb5d437a7cba6ecf9d89f9ff3ccd14cd6146ea7e7")
}
}
}
43 changes: 42 additions & 1 deletion sjsonnet/test/src/sjsonnet/Std0150FunctionsTests.scala
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package sjsonnet

import utest._
import TestUtils.eval
import TestUtils.{eval, evalErr}
object Std0150FunctionsTests extends TestSuite {

def tests = Tests {
Expand Down Expand Up @@ -180,5 +180,46 @@ object Std0150FunctionsTests extends TestSuite {
eval("""std.all([false, true, false])""") ==> ujson.Bool(false)
eval("""std.all([false, false, false])""") ==> ujson.Bool(false)
}

test("isEmpty") {
eval("""std.isEmpty("")""") ==> ujson.Bool(true)
eval("""std.isEmpty("non-empty string")""") ==> ujson.Bool(false)
assert(
evalErr("""std.isEmpty(10)""")
.startsWith("sjsonnet.Error: Wrong parameter type: expected String, got number")
)
}

test("trim") {
eval("""std.trim("already trimmed string")""") ==> ujson.Str("already trimmed string")
eval("""std.trim(" string with spaces on both ends ")""") ==> ujson.Str("string with spaces on both ends")
eval("""std.trim("string with newline character at end\n")""") ==> ujson.Str("string with newline character at end")
eval("""std.trim("string with tabs at end\t\t")""") ==> ujson.Str("string with tabs at end")
assert(
evalErr("""std.trim(10)""").startsWith("sjsonnet.Error: Wrong parameter type: expected String, got number"))
}

test("xnor") {
eval("""std.xnor(false, true)""") ==> ujson.False
eval("""std.xnor(false, false)""") ==> ujson.True
assert(
evalErr("""std.xnor("false", false)""")
.startsWith("sjsonnet.Error: Wrong parameter type: expected Boolean, got string")
)
}

test("xor") {
eval("""std.xor(false, true)""") ==> ujson.True
eval("""std.xor(true, true)""") ==> ujson.False
assert(
evalErr("""std.xor("false", false)""")
.startsWith("sjsonnet.Error: Wrong parameter type: expected Boolean, got string")
)
}

test("equalsIgnoreCase") {
eval("""std.equalsIgnoreCase("hello", "HELLO")""") ==> ujson.True
eval("""std.equalsIgnoreCase("hello", "world")""") ==> ujson.False
}
}
}

0 comments on commit b535b3c

Please sign in to comment.