Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add a few more methods to sjsonnet, like std.sha*, std.trim, std.isEmpty #210

Merged
merged 1 commit into from
Nov 5, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
}
}
}
Loading