Skip to content

Commit

Permalink
Merge branch 'P3trur0-issue-372'
Browse files Browse the repository at this point in the history
  • Loading branch information
saig0 committed Dec 28, 2021
2 parents f987b27 + ca24a81 commit 005aa0d
Show file tree
Hide file tree
Showing 3 changed files with 115 additions and 40 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -318,3 +318,36 @@ flatten([[1,2],[[3]], 4])
sort(list: [3,1,4,5,2], precedes: function(x,y) x < y)
// [1,2,3,4,5]
```

## string join()

Joins a list of strings into a single string. Similar to
Java's [joining](https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/util/stream/Collectors.html#joining(java.lang.CharSequence,java.lang.CharSequence,java.lang.CharSequence))
function.

If an item of the list is `null` then the item is ignored for the result string. If an item is
neither a string nor `null` then the function returns `null` instead of a string.

* parameters:
* `list`: the list of strings to join
* `delimiter`: (optional) the string that is used between each element (default: empty string)
* `prefix`: (optional) the string that is used at the beginning of the joined result (default:
empty string)
* `suffix`: (optional) the string that is used at the end of the joined result (default: empty
string)
* result: the joined list as a string

```js
string join(["a","b","c"])
// "abc"
string join(["a"], "X")
// "a"
string join(["a","b","c"], ", ")
// "a, b, c"
string join(["a","b","c"], ", ", "[", "]")
// "[a, b, c]"
string join(["a",null,"c"])
// "ac"
string join([])
// ""
```
Original file line number Diff line number Diff line change
Expand Up @@ -43,9 +43,9 @@ object ListBuiltinFunctions {
"distinct values" -> List(distinctValuesFunction),
"flatten" -> List(flattenFunction),
"sort" -> List(sortFunction),
"joining" -> List(joiningFunction,
joiningWithDelimiterFunction,
joiningWithDelimiterAndPrefixAndSuffixFunction)
"string join" -> List(joinFunction,
joinWithDelimiterFunction,
joinWithDelimiterAndPrefixAndSuffixFunction)
)

private def listContainsFunction =
Expand Down Expand Up @@ -385,48 +385,66 @@ object ListBuiltinFunctions {
}
)

private def joiningFunction = builtinFunction(
private def joinFunction = builtinFunction(
params = List("list"),
invoke = {
case List(ValList(list)) =>
withListOfStrings(list, strings => ValString(strings.mkString))
case List(ValList(list)) => joinStringList(list = list)
}
)

private def joiningWithDelimiterFunction = builtinFunction(
private def joinWithDelimiterFunction = builtinFunction(
params = List("list", "delimiter"),
invoke = {
case List(ValList(list), ValString(delimiter)) =>
withListOfStrings(list,
strings => ValString(strings.mkString(delimiter)))
joinStringList(list = list, delimiter = delimiter)
case List(ValList(list), ValNull) => joinStringList(list = list)
}
)

private def joiningWithDelimiterAndPrefixAndSuffixFunction = builtinFunction(
private def joinWithDelimiterAndPrefixAndSuffixFunction = builtinFunction(
params = List("list", "delimiter", "prefix", "suffix"),
invoke = {
case List(ValList(list),
ValString(delimiter),
ValString(prefix),
ValString(suffix)) =>
withListOfStrings(
list,
strings =>
ValString(
strings.mkString(start = prefix, sep = delimiter, end = suffix)))
joinStringList(list = list,
delimiter = delimiter,
prefix = prefix,
suffix = suffix)

case List(ValList(list), ValNull, ValString(prefix), ValString(suffix)) =>
joinStringList(list = list, prefix = prefix, suffix = suffix)

case List(ValList(list), ValString(delimiter), ValNull, ValNull) =>
joinStringList(list = list, delimiter = delimiter)
case List(ValList(list), ValNull, ValNull, ValNull) =>
joinStringList(list = list)
}
)

private def withListOfStrings(list: List[Val],
f: List[String] => Val): Val = {
list
.map(_ match {
case n: ValString => n
case x => ValError(s"expected string but found '$x'")
})
.find(_.isInstanceOf[ValError]) match {
case Some(e) => e
case None => f(list.asInstanceOf[List[ValString]].map(_.value))
private def joinStringList(list: List[Val],
delimiter: String = "",
prefix: String = "",
suffix: String = ""): Val = {

val isStringList = list.forall {
case _: ValString => true
case ValNull => true
case _ => false
}

if (!isStringList) {
ValError(s"expected a list of strings but found '$list'")

} else {
val stringList = list
.filterNot(_ == ValNull)
.map { case ValString(x) => x }

ValString(
stringList.mkString(start = prefix, sep = delimiter, end = suffix))
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ import scala.math.BigDecimal.int2bigDecimal
* @author Philipp
*/
class BuiltinListFunctionsTest
extends AnyFlatSpec
extends AnyFlatSpec
with Matchers
with FeelIntegrationTest {

Expand Down Expand Up @@ -189,13 +189,13 @@ class BuiltinListFunctionsTest
it should "return true if all items are true (huge list)" in {
val hugeList = (1 to 10_000).map(_ => true).toList

eval("all(xs)", Map("xs" -> hugeList)) should be (ValBoolean(true))
eval("all(xs)", Map("xs" -> hugeList)) should be(ValBoolean(true))
}

it should "return null if items are not boolean values (huge list)" in {
val hugeList = (1 to 10_000).toList

eval("all(xs)", Map("xs" -> hugeList)) should be (ValNull)
eval("all(xs)", Map("xs" -> hugeList)) should be(ValNull)
}

"A or() / any() function" should "return false if empty list" in {
Expand Down Expand Up @@ -234,13 +234,13 @@ class BuiltinListFunctionsTest
it should "return false if all items are false (huge list)" in {
val hugeList = (1 to 10_000).map(_ => false).toList

eval("any(xs)", Map("xs" -> hugeList)) should be (ValBoolean(false))
eval("any(xs)", Map("xs" -> hugeList)) should be(ValBoolean(false))
}

it should "return null if items are not boolean values (huge list)" in {
val hugeList = (1 to 10_000).toList

eval("any(xs)", Map("xs" -> hugeList)) should be (ValNull)
eval("any(xs)", Map("xs" -> hugeList)) should be(ValNull)
}

"A sublist() function" should "return list starting with _" in {
Expand Down Expand Up @@ -325,7 +325,7 @@ class BuiltinListFunctionsTest
it should "flatten a huge list of lists" in {
val hugeList = (1 to 10_000).map(List(_)).toList

eval("flatten(xs)", Map("xs" -> hugeList)) should be (
eval("flatten(xs)", Map("xs" -> hugeList)) should be(
ValList(
hugeList.flatten.map(ValNumber(_))
)
Expand All @@ -337,10 +337,10 @@ class BuiltinListFunctionsTest
eval(" sort(list: [3,1,4,5,2], precedes: function(x,y) x < y) ") should be(
ValList(
List(ValNumber(1),
ValNumber(2),
ValNumber(3),
ValNumber(4),
ValNumber(5))))
ValNumber(2),
ValNumber(3),
ValNumber(4),
ValNumber(5))))
}

"A product() function" should "return null if empty list" in {
Expand All @@ -354,22 +354,46 @@ class BuiltinListFunctionsTest
eval(" product(2,3,4) ") should be(ValNumber(24))
}

"A joining function" should "return an empty string if the input list is empty" in {
eval(" joining([]) ") should be(ValString(""))
"A join function" should "return an empty string if the input list is empty" in {
eval(" string join([]) ") should be(ValString(""))
}

it should "return an empty string if the input list is empty and a delimiter is defined" in {
eval(""" string join([], "X") """) should be(ValString(""))
}

it should "return joined strings" in {
eval(""" joining(["foo","bar","baz"]) """) should be(ValString("foobarbaz"))
eval(""" string join(["foo","bar","baz"]) """) should be(ValString("foobarbaz"))
}

it should "return joined strings when delimiter is null" in {
eval(""" string join(["foo","bar","baz"], null) """) should be(ValString("foobarbaz"))
}

it should "return original string when list contains a single entry" in {
eval(""" string join(["a"], "X") """) should be(ValString("a"))
}

it should "ignore null strings" in {
eval(""" string join(["foo", null, "baz"], null) """) should be(ValString("foobaz"))
}

it should "ignore null strings with delimiter" in {
eval(""" string join(["foo", null, "baz"], "X") """) should be(ValString("fooXbaz"))
}

it should "return joined strings with custom separator" in {
eval(""" joining(["foo","bar","baz"], "::") """) should be(
eval(""" string join(["foo","bar","baz"], "::") """) should be(
ValString("foo::bar::baz"))
}

it should "return joined strings with custom separator, a prefix and a suffix" in {
eval(""" joining(["foo","bar","baz"], "::", "hello-", "-goodbye") """) should be(
eval(""" string join(["foo","bar","baz"], "::", "hello-", "-goodbye") """) should be(
ValString("hello-foo::bar::baz-goodbye"))
}

it should "return null if the list contains other values than strings" in {
eval(""" string join(["foo", 123, "bar"]) """) should be(ValNull)
}

}

0 comments on commit 005aa0d

Please # to comment.