1
0
mirror of https://github.com/MontFerret/ferret.git synced 2025-11-06 08:39:09 +02:00

stdlib.strings

Added string functions to standard library
This commit is contained in:
Tim Voronov
2018-09-21 20:36:33 -04:00
parent e05b1ea88c
commit e6d692010c
37 changed files with 2413 additions and 188 deletions

View File

@@ -7,15 +7,16 @@ import (
)
var (
ErrMissedArgument = errors.New("missed argument")
ErrInvalidArgument = errors.New("invalid argument")
ErrInvalidType = errors.New("invalid type")
ErrInvalidOperation = errors.New("invalid operation")
ErrNotFound = errors.New("not found")
ErrNotUnique = errors.New("not unique")
ErrTerminated = errors.New("operation is terminated")
ErrUnexpected = errors.New("unexpected error")
ErrNotImplemented = errors.New("not implemented")
ErrMissedArgument = errors.New("missed argument")
ErrInvalidArgument = errors.New("invalid argument")
ErrInvalidArgumentNumber = errors.New("invalid argument number")
ErrInvalidType = errors.New("invalid type")
ErrInvalidOperation = errors.New("invalid operation")
ErrNotFound = errors.New("not found")
ErrNotUnique = errors.New("not unique")
ErrTerminated = errors.New("operation is terminated")
ErrUnexpected = errors.New("unexpected error")
ErrNotImplemented = errors.New("not implemented")
)
const typeErrorTemplate = "expected %s, but got %s"

View File

@@ -5,11 +5,21 @@ import (
"fmt"
)
const MaxArgs = 65536
type Function = func(ctx context.Context, args ...Value) (Value, error)
func ValidateArgs(args []Value, required int) error {
if len(args) != required {
return Error(ErrMissedArgument, fmt.Sprintf("expected %d, but got %d arguments", required, len(args)))
func ValidateArgs(args []Value, minimum, maximum int) error {
count := len(args)
if count < minimum || count > maximum {
return Error(
ErrInvalidArgumentNumber,
fmt.Sprintf(
"expected number of arguments %d-%d, but got %d",
minimum,
maximum,
len(args)))
}
return nil

View File

@@ -22,6 +22,14 @@ func NewString(input string) String {
return String(input)
}
func NewStringFromRunes(input []rune) String {
if len(input) == 0 {
return EmptyString
}
return String(input)
}
func ParseString(input interface{}) (String, error) {
if core.IsNil(input) {
return EmptyString, nil

View File

@@ -8,7 +8,7 @@ import (
)
func Length(_ context.Context, inputs ...core.Value) (core.Value, error) {
err := core.ValidateArgs(inputs, 1)
err := core.ValidateArgs(inputs, 1, 1)
if err != nil {
return values.None, err

View File

@@ -0,0 +1,42 @@
package strings
import (
"context"
"github.com/MontFerret/ferret/pkg/runtime/core"
"github.com/MontFerret/ferret/pkg/runtime/values"
"strings"
)
/*
* Converts strings to their lower-case counterparts. All other characters are returned unchanged.
* @param src (String) - The source string.
* @returns (String) - THis string in lower case.
*/
func Lower(_ context.Context, args ...core.Value) (core.Value, error) {
err := core.ValidateArgs(args, 1, 1)
if err != nil {
return values.EmptyString, err
}
text := strings.ToLower(args[0].String())
return values.NewString(text), nil
}
/*
* Converts strings to their upper-case counterparts. All other characters are returned unchanged.
* @param src (String) - The source string.
* @returns (String) - THis string in upper case.
*/
func Upper(_ context.Context, args ...core.Value) (core.Value, error) {
err := core.ValidateArgs(args, 1, 1)
if err != nil {
return values.EmptyString, err
}
text := strings.ToUpper(args[0].String())
return values.NewString(text), nil
}

View File

@@ -0,0 +1,51 @@
package strings_test
import (
"context"
"github.com/MontFerret/ferret/pkg/runtime/values"
"github.com/MontFerret/ferret/pkg/stdlib/strings"
. "github.com/smartystreets/goconvey/convey"
"testing"
)
func TestLower(t *testing.T) {
Convey("When args are not passed", t, func() {
Convey("It should return an error", func() {
var err error
_, err = strings.Lower(context.Background())
So(err, ShouldBeError)
})
})
Convey("Lower('FOOBAR') should return 'foobar'", t, func() {
out, _ := strings.Lower(
context.Background(),
values.NewString("FOOBAR"),
)
So(out, ShouldEqual, "foobar")
})
}
func TestUpper(t *testing.T) {
Convey("When args are not passed", t, func() {
Convey("It should return an error", func() {
var err error
_, err = strings.Upper(context.Background())
So(err, ShouldBeError)
})
})
Convey("Lower('foobar') should return 'FOOBAR'", t, func() {
out, _ := strings.Upper(
context.Background(),
values.NewString("foobar"),
)
So(out, ShouldEqual, "FOOBAR")
})
}

View File

@@ -0,0 +1,92 @@
package strings
import (
"context"
"github.com/MontFerret/ferret/pkg/runtime/core"
"github.com/MontFerret/ferret/pkg/runtime/values"
)
/*
* Concatenates one or more instances of Value, or an Array.
* @params src (String...|Array) - The source string / array.
* @returns String
*/
func Concat(_ context.Context, args ...core.Value) (core.Value, error) {
err := core.ValidateArgs(args, 1, core.MaxArgs)
if err != nil {
return values.EmptyString, err
}
argsCount := len(args)
res := values.EmptyString
if argsCount == 1 && args[0].Type() == core.ArrayType {
arr := args[0].(*values.Array)
arr.ForEach(func(value core.Value, _ int) bool {
res = res.Concat(value)
return true
})
return res, nil
}
for _, str := range args {
res = res.Concat(str)
}
return res, nil
}
/*
* Concatenates one or more instances of Value, or an Array with a given separator.
* @params separator (string) - The separator string.
* @params src (string...|array) - The source string / array.
* @returns string
*/
func ConcatWithSeparator(_ context.Context, args ...core.Value) (core.Value, error) {
err := core.ValidateArgs(args, 2, core.MaxArgs)
if err != nil {
return values.EmptyString, err
}
separator := args[0]
if separator.Type() != core.StringType {
separator = values.NewString(separator.String())
}
res := values.EmptyString
for idx, arg := range args[1:] {
if arg.Type() != core.ArrayType {
if arg.Type() != core.NoneType {
if idx > 0 {
res = res.Concat(separator)
}
res = res.Concat(arg)
}
} else {
arr := arg.(*values.Array)
arr.ForEach(func(value core.Value, idx int) bool {
if value.Type() != core.NoneType {
if idx > 0 {
res = res.Concat(separator)
}
res = res.Concat(value)
}
return true
})
}
}
return res, nil
}

View File

@@ -0,0 +1,145 @@
package strings_test
import (
"context"
"github.com/MontFerret/ferret/pkg/runtime/values"
"github.com/MontFerret/ferret/pkg/stdlib/strings"
. "github.com/smartystreets/goconvey/convey"
"testing"
)
func TestConcat(t *testing.T) {
Convey("When args are not passed", t, func() {
Convey("It should return an error", func() {
_, err := strings.Concat(context.Background())
So(err, ShouldBeError)
})
})
Convey("When args are strings", t, func() {
Convey("Concat('foo', 'bar', 'qaz') should return 'foobarqaz'", func() {
out, _ := strings.Concat(
context.Background(),
values.NewString("foo"),
values.NewString("bar"),
values.NewString("qaz"),
)
So(out, ShouldEqual, "foobarqaz")
})
})
Convey("When args are not strings", t, func() {
Convey("Concat('foo', None, 'bar') should return 'foobar'", func() {
out, _ := strings.Concat(
context.Background(),
values.NewString("foo"),
values.None,
values.NewString("bar"),
)
So(out, ShouldEqual, "foobar")
})
Convey("Concat('foo', 1, false) should return 'foo1false'", func() {
out, _ := strings.Concat(
context.Background(),
values.NewString("foo"),
values.NewInt(1),
values.False,
)
So(out, ShouldEqual, "foo1false")
})
Convey("Concat(['foo', 'bar']) should return 'foobar'", func() {
out, _ := strings.Concat(
context.Background(),
values.NewArrayWith(values.NewString("foo"), values.NewString("bar")),
)
So(out, ShouldEqual, "foobar")
})
Convey("Concat([1,2,3]) should return '123'", func() {
out, _ := strings.Concat(
context.Background(),
values.NewArrayWith(
values.NewInt(1),
values.NewInt(2),
values.NewInt(3),
),
)
So(out, ShouldEqual, "123")
})
})
}
func TestConcatWithSeparator(t *testing.T) {
Convey("When args are not passed", t, func() {
Convey("It should return an error", func() {
_, err := strings.ConcatWithSeparator(context.Background())
So(err, ShouldBeError)
})
})
Convey("When args are strings", t, func() {
Convey("ConcatWithSeparator(',' 'foo', 'bar', 'qaz') should return 'foo,bar,qaz'", func() {
out, _ := strings.ConcatWithSeparator(
context.Background(),
values.NewString(","),
values.NewString("foo"),
values.NewString("bar"),
values.NewString("qaz"),
)
So(out, ShouldEqual, "foo,bar,qaz")
})
})
Convey("When args are not strings", t, func() {
Convey("ConcatWithSeparator(',' ['foo', 'bar', 'qaz']) should return 'foo,bar,qaz'", func() {
out, _ := strings.ConcatWithSeparator(
context.Background(),
values.NewString(","),
values.NewArrayWith(
values.NewString("foo"),
values.NewString("bar"),
values.NewString("qaz"),
),
)
So(out, ShouldEqual, "foo,bar,qaz")
})
Convey("ConcatWithSeparator(',' ['foo', None, 'qaz']) should return 'foo,qaz'", func() {
out, _ := strings.ConcatWithSeparator(
context.Background(),
values.NewString(","),
values.NewArrayWith(
values.NewString("foo"),
values.None,
values.NewString("qaz"),
),
)
So(out, ShouldEqual, "foo,qaz")
})
Convey("ConcatWithSeparator(',' 'foo', None, 'qaz') should return 'foo,qaz'", func() {
out, _ := strings.ConcatWithSeparator(
context.Background(),
values.NewString(","),
values.NewArrayWith(
values.NewString("foo"),
values.None,
values.NewString("qaz"),
),
)
So(out, ShouldEqual, "foo,qaz")
})
})
}

View File

@@ -0,0 +1,56 @@
package strings
import (
"context"
"github.com/MontFerret/ferret/pkg/runtime/core"
"github.com/MontFerret/ferret/pkg/runtime/values"
)
/*
* Returns a value indicating whether a specified substring occurs within a string.
* @param src (String) - The source string.
* @param search (String) - The string to seek.
* @param returnIndex (Boolean) - Values which indicates whether to return the character position of the match is returned instead of a boolean.
* The default is false.
* @returns (Boolean|Int)
*/
func Contains(_ context.Context, args ...core.Value) (core.Value, error) {
err := core.ValidateArgs(args, 2, 3)
if err != nil {
return values.False, err
}
var text values.String
var search values.String
returnIndex := values.False
arg1 := args[0]
arg2 := args[1]
if arg1.Type() == core.StringType {
text = arg1.(values.String)
} else {
text = values.NewString(arg1.String())
}
if arg2.Type() == core.StringType {
search = arg2.(values.String)
} else {
search = values.NewString(arg2.String())
}
if len(args) > 2 {
arg3 := args[2]
if arg3.Type() == core.BooleanType {
returnIndex = arg3.(values.Boolean)
}
}
if returnIndex == values.True {
return text.IndexOf(search), nil
}
return text.Contains(search), nil
}

View File

@@ -0,0 +1,95 @@
package strings_test
import (
"context"
"github.com/MontFerret/ferret/pkg/runtime/values"
"github.com/MontFerret/ferret/pkg/stdlib/strings"
. "github.com/smartystreets/goconvey/convey"
"testing"
)
func TestContains(t *testing.T) {
Convey("When args are not passed", t, func() {
Convey("It should return an error", func() {
_, err := strings.Contains(context.Background())
So(err, ShouldBeError)
})
})
Convey("When args are strings", t, func() {
Convey("Contains('foobar', 'foo') should return 'true'", func() {
out, _ := strings.Contains(
context.Background(),
values.NewString("foobar"),
values.NewString("bar"),
)
So(out, ShouldEqual, values.True)
})
Convey("Contains('foobar', 'qaz') should return 'false'", func() {
out, _ := strings.Contains(
context.Background(),
values.NewString("foobar"),
values.NewString("qaz"),
)
So(out, ShouldEqual, values.False)
})
Convey("Contains('foobar', 'foo', true) should return '3'", func() {
out, _ := strings.Contains(
context.Background(),
values.NewString("foobar"),
values.NewString("bar"),
values.True,
)
So(out, ShouldEqual, 3)
})
Convey("Contains('foobar', 'qaz', true) should return '-1'", func() {
out, _ := strings.Contains(
context.Background(),
values.NewString("foobar"),
values.NewString("bar"),
values.True,
)
So(out, ShouldEqual, 3)
})
})
Convey("When args are not strings", t, func() {
Convey("Contains('foo123', 1) should return 'true'", func() {
out, _ := strings.Contains(
context.Background(),
values.NewString("foo123"),
values.NewInt(1),
)
So(out, ShouldEqual, values.True)
})
Convey("Contains(123, 1) should return 'true'", func() {
out, _ := strings.Contains(
context.Background(),
values.NewInt(123),
values.NewInt(1),
)
So(out, ShouldEqual, values.True)
})
Convey("Contains([1,2,3], 1) should return 'true'", func() {
out, _ := strings.Contains(
context.Background(),
values.NewArrayWith(values.NewInt(1), values.NewInt(2), values.NewInt(3)),
values.NewInt(1),
)
So(out, ShouldEqual, values.True)
})
})
}

View File

@@ -0,0 +1,101 @@
package strings
import (
"context"
"crypto/md5"
"crypto/sha1"
"crypto/sha512"
"encoding/base64"
"github.com/MontFerret/ferret/pkg/runtime/core"
"github.com/MontFerret/ferret/pkg/runtime/values"
"net/url"
)
/*
* Returns the encoded String of uri.
* @param (String) - Uri to encode.
* @returns String - Encoded string.
*/
func EncodeURIComponent(_ context.Context, args ...core.Value) (core.Value, error) {
err := core.ValidateArgs(args, 1, 1)
if err != nil {
return values.EmptyString, err
}
str := url.QueryEscape(args[0].String())
return values.NewString(str), nil
}
/*
* Calculates the MD5 checksum for text and return it in a hexadecimal string representation.
* @param text (String) - The string to do calculations against to.
* @return (String) - MD5 checksum as hex string.
*/
func Md5(_ context.Context, args ...core.Value) (core.Value, error) {
err := core.ValidateArgs(args, 1, 1)
if err != nil {
return values.EmptyString, err
}
text := args[0].String()
res := md5.Sum([]byte(text))
return values.NewString(string(res[:])), nil
}
/*
* Calculates the SHA1 checksum for text and returns it in a hexadecimal string representation.
* @param text (String) - The string to do calculations against to.
* @return (String) - Sha1 checksum as hex string.
*/
func Sha1(_ context.Context, args ...core.Value) (core.Value, error) {
err := core.ValidateArgs(args, 1, 1)
if err != nil {
return values.EmptyString, err
}
text := args[0].String()
res := sha1.Sum([]byte(text))
return values.NewString(string(res[:])), nil
}
/*
* Calculates the SHA512 checksum for text and returns it in a hexadecimal string representation.
* @param text (String) - The string to do calculations against to.
* @return (String) - SHA512 checksum as hex string.
*/
func Sha512(_ context.Context, args ...core.Value) (core.Value, error) {
err := core.ValidateArgs(args, 1, 1)
if err != nil {
return values.EmptyString, err
}
text := args[0].String()
res := sha512.Sum512([]byte(text))
return values.NewString(string(res[:])), nil
}
/*
* Returns the base64 representation of value.
* @param value (string) - The string to encode.
* @returns toBase64String (String) - A base64 representation of the string.
*/
func ToBase64(_ context.Context, args ...core.Value) (core.Value, error) {
err := core.ValidateArgs(args, 1, 1)
if err != nil {
return values.EmptyString, err
}
value := args[0].String()
out := base64.StdEncoding.EncodeToString([]byte(value))
return values.NewString(string(out)), nil
}

View File

@@ -0,0 +1,122 @@
package strings_test
import (
"context"
"github.com/MontFerret/ferret/pkg/runtime/values"
"github.com/MontFerret/ferret/pkg/stdlib/strings"
. "github.com/smartystreets/goconvey/convey"
"testing"
)
func TestEncodedURIComponent(t *testing.T) {
Convey("When args are not passed", t, func() {
Convey("It should return an error", func() {
_, err := strings.EncodeURIComponent(context.Background())
So(err, ShouldBeError)
_, err = strings.EncodeURIComponent(
context.Background(),
values.NewString("https://github.com/MontFerret/ferret"),
values.NewString("https://github.com/MontFerret/ferret"),
)
So(err, ShouldBeError)
})
})
Convey("When args are strings", t, func() {
Convey("EncodeURIComponent('https://github.com/MontFerret/ferret') should return encoded uri", func() {
out, _ := strings.EncodeURIComponent(
context.Background(),
values.NewString("https://github.com/MontFerret/ferret"),
)
So(out, ShouldEqual, "https%3A%2F%2Fgithub.com%2FMontFerret%2Fferret")
})
})
}
func TestMd5(t *testing.T) {
Convey("When args are not passed", t, func() {
Convey("It should return an error", func() {
var err error
_, err = strings.Md5(context.Background())
So(err, ShouldBeError)
})
})
Convey("Should return hash sum of a string", t, func() {
str := values.NewString("foobar")
out, _ := strings.Md5(
context.Background(),
str,
)
So(out, ShouldNotEqual, str)
})
}
func TestSha1(t *testing.T) {
Convey("When args are not passed", t, func() {
Convey("It should return an error", func() {
var err error
_, err = strings.Sha1(context.Background())
So(err, ShouldBeError)
})
})
Convey("Should return hash sum of a string", t, func() {
str := values.NewString("foobar")
out, _ := strings.Sha1(
context.Background(),
str,
)
So(out, ShouldNotEqual, str)
})
}
func TestSha512(t *testing.T) {
Convey("When args are not passed", t, func() {
Convey("It should return an error", func() {
var err error
_, err = strings.Sha512(context.Background())
So(err, ShouldBeError)
})
})
Convey("Should return hash sum of a string", t, func() {
str := values.NewString("foobar")
out, _ := strings.Sha512(
context.Background(),
str,
)
So(out, ShouldNotEqual, str)
})
}
func TestToBase64(t *testing.T) {
Convey("When args are not passed", t, func() {
Convey("It should return an error", func() {
var err error
_, err = strings.ToBase64(context.Background())
So(err, ShouldBeError)
})
})
Convey("Should encode a given value", t, func() {
out, err := strings.ToBase64(
context.Background(),
values.NewString("foobar"),
)
So(err, ShouldBeNil)
So(out, ShouldNotEqual, "foobar")
})
}

106
pkg/stdlib/strings/find.go Normal file
View File

@@ -0,0 +1,106 @@
package strings
import (
"context"
"github.com/MontFerret/ferret/pkg/runtime/core"
"github.com/MontFerret/ferret/pkg/runtime/values"
"strings"
)
/*
* Returns the position of the first occurrence of the string search inside the string text. Positions start at 0.
* @param src (String) - The source string.
* @param search (String) - The string to seek.
* @param start (Int, optional) - Limit the search to a subset of the text, beginning at start.
* @param end (Int, optional) - Limit the search to a subset of the text, ending at end
* @returns (Int) - The character position of the match.
* If search is not contained in text, -1 is returned. If search is empty, start is returned.
*/
func FindFirst(_ context.Context, args ...core.Value) (core.Value, error) {
err := core.ValidateArgs(args, 2, 4)
if err != nil {
return values.NewInt(-1), err
}
argsCount := len(args)
text := args[0].String()
runes := []rune(text)
search := args[1].String()
start := values.NewInt(0)
end := values.NewInt(int(len(text)))
if argsCount == 3 {
arg3 := args[2]
if arg3.Type() == core.IntType {
start = arg3.(values.Int)
}
}
if argsCount == 4 {
arg4 := args[3]
if arg4.Type() == core.IntType {
end = arg4.(values.Int)
}
}
found := strings.Index(string(runes[start:end]), search)
if found > -1 {
return values.NewInt(found + int(start)), nil
}
return values.NewInt(found), nil
}
/*
* Returns the position of the last occurrence of the string search inside the string text. Positions start at 0.
* @param src (String) - The source string.
* @param search (String) - The string to seek.
* @param start (Int, optional) - Limit the search to a subset of the text, beginning at start.
* @param end (Int, optional) - Limit the search to a subset of the text, ending at end
* @returns (Int) - The character position of the match.
* If search is not contained in text, -1 is returned. If search is empty, start is returned.
*/
func FindLast(_ context.Context, args ...core.Value) (core.Value, error) {
err := core.ValidateArgs(args, 2, 4)
if err != nil {
return values.NewInt(-1), err
}
argsCount := len(args)
text := args[0].String()
runes := []rune(text)
search := args[1].String()
start := values.NewInt(0)
end := values.NewInt(int(len(text)))
if argsCount == 3 {
arg3 := args[2]
if arg3.Type() == core.IntType {
start = arg3.(values.Int)
}
}
if argsCount == 4 {
arg4 := args[3]
if arg4.Type() == core.IntType {
end = arg4.(values.Int)
}
}
found := strings.LastIndex(string(runes[start:end]), search)
if found > -1 {
return values.NewInt(found + int(start)), nil
}
return values.NewInt(found), nil
}

View File

@@ -0,0 +1,126 @@
package strings_test
import (
"context"
"github.com/MontFerret/ferret/pkg/runtime/values"
"github.com/MontFerret/ferret/pkg/stdlib/strings"
. "github.com/smartystreets/goconvey/convey"
"testing"
)
func TestFindFirst(t *testing.T) {
Convey("When args are not passed", t, func() {
Convey("It should return an error", func() {
var err error
_, err = strings.FindFirst(context.Background())
So(err, ShouldBeError)
_, err = strings.FindFirst(
context.Background(),
values.NewString("foo"),
)
So(err, ShouldBeError)
})
})
Convey("When args are strings", t, func() {
Convey("FindFirst('foobarbaz', 'ba') should return 3", func() {
out, _ := strings.FindFirst(
context.Background(),
values.NewString("foobarbaz"),
values.NewString("ba"),
)
So(out, ShouldEqual, 3)
})
Convey("FindFirst('foobarbaz', 'ba', 4) should return 6", func() {
out, _ := strings.FindFirst(
context.Background(),
values.NewString("foobarbaz"),
values.NewString("ba"),
values.NewInt(4),
)
So(out, ShouldEqual, 6)
})
Convey("FindFirst('foobarbaz', 'ba', 4) should return -1", func() {
out, _ := strings.FindFirst(
context.Background(),
values.NewString("foobarbaz"),
values.NewString("ba"),
values.NewInt(7),
)
So(out, ShouldEqual, -1)
})
Convey("FindFirst('foobarbaz', 'ba', 0, 3) should return -1", func() {
out, _ := strings.FindFirst(
context.Background(),
values.NewString("foobarbaz"),
values.NewString("ba"),
values.NewInt(0),
values.NewInt(3),
)
So(out, ShouldEqual, -1)
})
})
}
func TestFindLast(t *testing.T) {
Convey("When args are not passed", t, func() {
Convey("It should return an error", func() {
var err error
_, err = strings.FindLast(context.Background())
So(err, ShouldBeError)
_, err = strings.FindLast(
context.Background(),
values.NewString("foo"),
)
So(err, ShouldBeError)
})
})
Convey("When args are strings", t, func() {
Convey("FindLast('foobarbaz', 'ba') should return 6", func() {
out, _ := strings.FindLast(
context.Background(),
values.NewString("foobarbaz"),
values.NewString("ba"),
)
So(out, ShouldEqual, 6)
})
Convey("FindLast('foobarbaz', 'ba', 7) should return -1", func() {
out, _ := strings.FindLast(
context.Background(),
values.NewString("foobarbaz"),
values.NewString("ba"),
values.NewInt(7),
)
So(out, ShouldEqual, -1)
})
Convey("FindLast('foobarbaz', 'ba', 0, 5) should return 3", func() {
out, _ := strings.FindLast(
context.Background(),
values.NewString("foobarbaz"),
values.NewString("ba"),
values.NewInt(0),
values.NewInt(5),
)
So(out, ShouldEqual, 3)
})
})
}

View File

@@ -0,0 +1,52 @@
package strings
import (
"context"
"encoding/json"
"github.com/MontFerret/ferret/pkg/runtime/core"
"github.com/MontFerret/ferret/pkg/runtime/values"
)
/*
* Returns a FQL value described by the JSON-encoded input string.
* @params text (String) - The string to parse as JSON.
* @returns FQL value (Value)
*/
func JsonParse(_ context.Context, args ...core.Value) (core.Value, error) {
err := core.ValidateArgs(args, 1, 1)
if err != nil {
return values.EmptyString, err
}
var val interface{}
err = json.Unmarshal([]byte(args[0].String()), &val)
if err != nil {
return values.EmptyString, err
}
return values.Parse(val), nil
}
/*
* Returns a JSON string representation of the input value.
* @params value (Value) - The input value to serialize.
* @returns json (String)
*/
func JsonStringify(_ context.Context, args ...core.Value) (core.Value, error) {
err := core.ValidateArgs(args, 1, 1)
if err != nil {
return values.EmptyString, err
}
out, err := json.Marshal(args[0])
if err != nil {
return values.EmptyString, err
}
return values.NewString(string(out)), nil
}

View File

@@ -0,0 +1,241 @@
package strings_test
import (
"context"
"github.com/MontFerret/ferret/pkg/runtime/core"
"github.com/MontFerret/ferret/pkg/runtime/values"
"github.com/MontFerret/ferret/pkg/stdlib/strings"
. "github.com/smartystreets/goconvey/convey"
"testing"
)
func TestJsonParse(t *testing.T) {
Convey("When args are not passed", t, func() {
Convey("It should return an error", func() {
var err error
_, err = strings.JsonParse(context.Background())
So(err, ShouldBeError)
})
})
Convey("It should parse none", t, func() {
val := values.None
b, err := val.MarshalJSON()
So(err, ShouldBeNil)
out, err := strings.JsonParse(
context.Background(),
values.NewString(string(b)),
)
So(err, ShouldBeNil)
So(out.Type(), ShouldEqual, core.NoneType)
})
Convey("It should parse a string", t, func() {
val := values.NewString("foobar")
b, err := val.MarshalJSON()
So(err, ShouldBeNil)
out, err := strings.JsonParse(
context.Background(),
values.NewString(string(b)),
)
So(err, ShouldBeNil)
So(out.Type(), ShouldEqual, core.StringType)
})
Convey("It should parse an int", t, func() {
val := values.NewInt(1)
b, err := val.MarshalJSON()
So(err, ShouldBeNil)
out, err := strings.JsonParse(
context.Background(),
values.NewString(string(b)),
)
So(err, ShouldBeNil)
So(out.Type(), ShouldEqual, core.FloatType)
})
Convey("It should parse a float", t, func() {
val := values.NewFloat(1.1)
b, err := val.MarshalJSON()
So(err, ShouldBeNil)
out, err := strings.JsonParse(
context.Background(),
values.NewString(string(b)),
)
So(err, ShouldBeNil)
So(out.Type(), ShouldEqual, core.FloatType)
})
Convey("It should parse a boolean", t, func() {
val := values.True
b, err := val.MarshalJSON()
So(err, ShouldBeNil)
out, err := strings.JsonParse(
context.Background(),
values.NewString(string(b)),
)
So(err, ShouldBeNil)
So(out.Type(), ShouldEqual, core.BooleanType)
})
Convey("It should parse an array", t, func() {
val := values.NewArrayWith(
values.Int(1),
values.Int(2),
values.Int(3),
)
b, err := val.MarshalJSON()
So(err, ShouldBeNil)
out, err := strings.JsonParse(
context.Background(),
values.NewString(string(b)),
)
So(err, ShouldBeNil)
So(out.Type(), ShouldEqual, core.ArrayType)
So(out.String(), ShouldEqual, "[1,2,3]")
})
Convey("It should parse an object", t, func() {
val := values.NewObject()
val.Set(values.NewString("foo"), values.NewString("bar"))
b, err := val.MarshalJSON()
So(err, ShouldBeNil)
out, err := strings.JsonParse(
context.Background(),
values.NewString(string(b)),
)
So(err, ShouldBeNil)
So(out.Type(), ShouldEqual, core.ObjectType)
So(out.String(), ShouldEqual, `{"foo":"bar"}`)
})
}
func TestJsonStringify(t *testing.T) {
Convey("When args are not passed", t, func() {
Convey("It should return an error", func() {
var err error
_, err = strings.JsonStringify(context.Background())
So(err, ShouldBeError)
})
})
Convey("It should serialize none", t, func() {
out, err := strings.JsonStringify(
context.Background(),
values.None,
)
So(err, ShouldBeNil)
So(out.String(), ShouldEqual, "null")
})
Convey("It should serialize boolean", t, func() {
out, err := strings.JsonStringify(
context.Background(),
values.False,
)
So(err, ShouldBeNil)
So(out.String(), ShouldEqual, "false")
})
Convey("It should serialize string", t, func() {
out, err := strings.JsonStringify(
context.Background(),
values.NewString("foobar"),
)
So(err, ShouldBeNil)
So(out.String(), ShouldEqual, `"foobar"`)
})
Convey("It should serialize int", t, func() {
out, err := strings.JsonStringify(
context.Background(),
values.NewInt(1),
)
So(err, ShouldBeNil)
So(out.String(), ShouldEqual, `1`)
})
Convey("It should serialize float", t, func() {
out, err := strings.JsonStringify(
context.Background(),
values.NewFloat(1.1),
)
So(err, ShouldBeNil)
So(out.String(), ShouldEqual, `1.1`)
})
Convey("It should serialize array", t, func() {
out, err := strings.JsonStringify(
context.Background(),
values.NewArrayWith(
values.NewString("foo"),
values.NewString("bar"),
),
)
So(err, ShouldBeNil)
So(out.String(), ShouldEqual, `["foo","bar"]`)
})
Convey("It should serialize object", t, func() {
obj := values.NewObject()
obj.Set(values.NewString("foo"), values.NewString("bar"))
out, err := strings.JsonStringify(
context.Background(),
obj,
)
So(err, ShouldBeNil)
So(out.String(), ShouldEqual, `{"foo":"bar"}`)
})
Convey("It should serialize datetime", t, func() {
obj, err := values.ParseDateTime("2006-01-02T15:04:05Z")
So(err, ShouldBeNil)
out, err := strings.JsonStringify(
context.Background(),
obj,
)
So(err, ShouldBeNil)
So(out.String(), ShouldEqual, `"2006-01-02T15:04:05Z"`)
})
}

View File

@@ -4,22 +4,34 @@ import "github.com/MontFerret/ferret/pkg/runtime/core"
func NewLib() map[string]core.Function {
return map[string]core.Function{
"CONCAT": Concat,
"CONCAT_SEPARATOR": ConcatWithSeparator,
"CONTAINS": Contains,
"FIND_FIRST": FindFirst,
"FIND_LAST": FindLast,
"JSON_PARSE": JsonParse,
"JSON_STRINGIFY": JsonStringify,
"LEFT": Left,
"LIKE": Like,
"LOWER": Lower,
"LTRIM": LTrim,
"RIGHT": RTrim,
"SPLIT": Split,
"SUBSTITUTE": Substitute,
"SUBSTRING": Substring,
"TRIM": Trim,
"UPPER": Upper,
"CONCAT": Concat,
"CONCAT_SEPARATOR": ConcatWithSeparator,
"CONTAINS": Contains,
"ENCODE_URI_COMPONENT": EncodeURIComponent,
"FIND_FIRST": FindFirst,
"FIND_LAST": FindLast,
"JSON_PARSE": JsonParse,
"JSON_STRINGIFY": JsonStringify,
"LEFT": Left,
"LIKE": Like,
"LOWER": Lower,
"LTRIM": LTrim,
"RANDOM_TOKEN": RandomToken,
"MD5": Md5,
"REGEXP_MATCH": RegexMatch,
"REGEXP_SPLIT": RegexSplit,
"REGEXP_TEST": RegexTest,
"REGEXP_REPLACE": RegexReplace,
"REVERSE": Reverse,
"RIGHT": Right,
"RTRIM": RTrim,
"SHA1": Sha1,
"SHA512": Sha512,
"SPLIT": Split,
"SUBSTITUTE": Substitute,
"SUBSTRING": Substring,
"TO_BASE64": ToBase64,
"TRIM": Trim,
"UPPER": Upper,
}
}

View File

@@ -0,0 +1,20 @@
package strings
import (
"context"
"github.com/MontFerret/ferret/pkg/runtime/core"
"github.com/MontFerret/ferret/pkg/runtime/values"
)
/*
* Checks whether the pattern search is contained in the string text, using wildcard matching.
*/
func Like(_ context.Context, args ...core.Value) (core.Value, error) {
err := core.ValidateArgs(args, 2, 3)
if err != nil {
return values.False, err
}
return values.None, core.Error(core.ErrNotImplemented, "LIKE")
}

View File

@@ -0,0 +1 @@
package strings_test

View File

@@ -0,0 +1,56 @@
package strings
import (
"context"
"github.com/MontFerret/ferret/pkg/runtime/core"
"github.com/MontFerret/ferret/pkg/runtime/values"
"math/rand"
"time"
)
const (
letterBytes = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
letterIdxBits = 6 // 6 bits to represent a letter index
letterIdxMask = 1<<letterIdxBits - 1 // All 1-bits, as many as letterIdxBits
letterIdxMax = 63 / letterIdxBits // # of letter indices fitting in 63 bits
)
/*
* Generates a pseudo-random token string with the specified length. The algorithm for token generation should be treated as opaque.
* @param length (Int) - The desired string length for the token. It must be greater than 0 and at most 65536.
* @return (String) - A generated token consisting of lowercase letters, uppercase letters and numbers.
*/
func RandomToken(_ context.Context, args ...core.Value) (core.Value, error) {
err := core.ValidateArgs(args, 1, 1)
if err != nil {
return values.EmptyString, err
}
err = core.ValidateType(args[0], core.IntType)
if err != nil {
return values.EmptyString, err
}
size := args[0].(values.Int)
randSrc := rand.NewSource(time.Now().UnixNano())
b := make([]byte, size)
for i, cache, remain := size-1, randSrc.Int63(), letterIdxMax; i >= 0; {
if remain == 0 {
cache, remain = randSrc.Int63(), letterIdxMax
}
if idx := int(cache & letterIdxMask); idx < len(letterBytes) {
b[i] = letterBytes[idx]
i--
}
cache >>= letterIdxBits
remain--
}
return values.NewString(string(b)), nil
}

View File

@@ -0,0 +1,49 @@
package strings_test
import (
"context"
"github.com/MontFerret/ferret/pkg/runtime/values"
"github.com/MontFerret/ferret/pkg/stdlib/strings"
. "github.com/smartystreets/goconvey/convey"
"testing"
)
func TestRandomToken(t *testing.T) {
Convey("When args are not passed", t, func() {
Convey("It should return an error", func() {
var err error
_, err = strings.RandomToken(context.Background())
So(err, ShouldBeError)
})
})
Convey("When args are invalid", t, func() {
Convey("It should return an error", func() {
var err error
_, err = strings.RandomToken(context.Background(), values.NewString("foo"))
So(err, ShouldBeError)
})
})
Convey("Should generate random string", t, func() {
str1, _ := strings.RandomToken(
context.Background(),
values.NewInt(8),
)
So(str1, ShouldHaveLength, 8)
str2, _ := strings.RandomToken(
context.Background(),
values.NewInt(8),
)
So(str2, ShouldHaveLength, 8)
So(str1, ShouldNotEqual, str2)
})
}

166
pkg/stdlib/strings/regex.go Normal file
View File

@@ -0,0 +1,166 @@
package strings
import (
"context"
"github.com/MontFerret/ferret/pkg/runtime/core"
"github.com/MontFerret/ferret/pkg/runtime/values"
"regexp"
)
/*
* Returns the matches in the given string text, using the regex.
* @param text (String) - The string to search in.
* @param regex (String) - A regular expression to use for matching the text.
* @param caseInsensitive (Boolean) - If set to true, the matching will be case-insensitive. The default is false.
* @return (Array) - An array of strings containing the matches.
*/
func RegexMatch(_ context.Context, args ...core.Value) (core.Value, error) {
err := core.ValidateArgs(args, 2, 3)
if err != nil {
return values.None, err
}
text := args[0].String()
exp := args[1].String()
if len(args) > 2 {
if args[2] == values.True {
exp = "(?i)" + exp
}
}
reg, err := regexp.Compile(exp)
if err != nil {
return values.None, err
}
matches := reg.FindAllStringSubmatch(text, -1)
res := values.NewArray(10)
if len(matches) == 0 {
return res, nil
}
for _, m := range matches[0] {
res.Push(values.NewString(m))
}
return res, nil
}
/*
* Splits the given string text into a list of strings, using the separator.
* @param text (String) - The string to split.
* @param regex (String) - A regular expression to use for splitting the text.
* @param caseInsensitive (Boolean) - If set to true, the matching will be case-insensitive. The default is false.
* @param limit (Int) - Limit the number of split values in the result. If no limit is given, the number of splits returned is not bounded.
* @return (Array) - An array of strings splited by teh expression.
*/
func RegexSplit(_ context.Context, args ...core.Value) (core.Value, error) {
err := core.ValidateArgs(args, 2, 4)
if err != nil {
return values.None, err
}
text := args[0].String()
exp := args[1].String()
limit := -1
if len(args) > 2 {
if args[2].Type() == core.IntType {
limit = int(args[2].(values.Int))
}
}
reg, err := regexp.Compile(exp)
if err != nil {
return values.None, err
}
matches := reg.Split(text, limit)
res := values.NewArray(10)
if len(matches) == 0 {
return res, nil
}
for _, m := range matches {
res.Push(values.NewString(m))
}
return res, nil
}
/*
* Splits the given string text into a list of strings, using the separator.
* @param text (String) - The string to split.
* @param regex (String) - A regular expression to use for splitting the text.
* @param caseInsensitive (Boolean) - If set to true, the matching will be case-insensitive. The default is false.
* @return (Boolean) - Returns true if the pattern is contained in text, and false otherwise.
*/
func RegexTest(_ context.Context, args ...core.Value) (core.Value, error) {
err := core.ValidateArgs(args, 2, 3)
if err != nil {
return values.None, err
}
text := args[0].String()
exp := args[1].String()
if len(args) > 2 {
if args[2] == values.True {
exp = "(?i)" + exp
}
}
reg, err := regexp.Compile(exp)
if err != nil {
return values.None, err
}
matches := reg.MatchString(text)
return values.NewBoolean(matches), nil
}
/*
* Splits the given string text into a list of strings, using the separator.
* @param text (String) - The string to split.
* @param regex (String) - A regular expression search pattern.
* @param replacement (String) - The string to replace the search pattern with
* @param caseInsensitive (Boolean) - If set to true, the matching will be case-insensitive. The default is false.
* @return (String) - Returns the string text with the search regex pattern replaced with the replacement string wherever the pattern exists in text
*/
func RegexReplace(_ context.Context, args ...core.Value) (core.Value, error) {
err := core.ValidateArgs(args, 3, 4)
if err != nil {
return values.EmptyString, err
}
text := args[0].String()
exp := args[1].String()
repl := args[2].String()
if len(args) > 3 {
if args[3] == values.True {
exp = "(?i)" + exp
}
}
reg, err := regexp.Compile(exp)
if err != nil {
return values.None, err
}
out := reg.ReplaceAllString(text, repl)
return values.NewString(out), nil
}

View File

@@ -0,0 +1,138 @@
package strings_test
import (
"context"
"github.com/MontFerret/ferret/pkg/runtime/values"
"github.com/MontFerret/ferret/pkg/stdlib/strings"
. "github.com/smartystreets/goconvey/convey"
"testing"
)
func TestRegexMatch(t *testing.T) {
Convey("When args are not passed", t, func() {
Convey("It should return an error", func() {
var err error
_, err = strings.RegexMatch(context.Background())
So(err, ShouldBeError)
_, err = strings.RegexMatch(context.Background(), values.NewString(""))
So(err, ShouldBeError)
})
})
Convey("Should match with case insensitive regexp", t, func() {
out, err := strings.RegexMatch(
context.Background(),
values.NewString("My-us3r_n4m3"),
values.NewString("[a-z0-9_-]{3,16}$"),
values.True,
)
So(err, ShouldBeNil)
So(out.String(), ShouldEqual, `["My-us3r_n4m3"]`)
})
Convey("Should match with case sensitive regexp", t, func() {
out, err := strings.RegexMatch(
context.Background(),
values.NewString("john@doe.com"),
values.NewString(`([a-z0-9_\.-]+)@([\da-z-]+)\.([a-z\.]{2,6})$`),
)
So(err, ShouldBeNil)
So(out.String(), ShouldEqual, `["john@doe.com","john","doe","com"]`)
})
}
func TestRegexSplit(t *testing.T) {
Convey("When args are not passed", t, func() {
Convey("It should return an error", func() {
var err error
_, err = strings.RegexSplit(context.Background())
So(err, ShouldBeError)
_, err = strings.RegexSplit(context.Background(), values.NewString(""))
So(err, ShouldBeError)
})
})
Convey("Should split with regexp", t, func() {
out, err := strings.RegexSplit(
context.Background(),
values.NewString("This is a line.\n This is yet another line\r\n This again is a line.\r Mac line "),
values.NewString(`\.?(\n|\r)`),
)
So(err, ShouldBeNil)
So(out.String(), ShouldEqual, `["This is a line"," This is yet another line",""," This again is a line"," Mac line "]`)
})
}
func TestRegexTest(t *testing.T) {
Convey("When args are not passed", t, func() {
Convey("It should return an error", func() {
var err error
_, err = strings.RegexTest(context.Background())
So(err, ShouldBeError)
_, err = strings.RegexTest(context.Background(), values.NewString(""))
So(err, ShouldBeError)
})
})
Convey("Should return true when matches", t, func() {
out, _ := strings.RegexTest(
context.Background(),
values.NewString("the quick brown fox"),
values.NewString("the.*fox"),
)
So(out, ShouldEqual, true)
})
}
func TestRegexReplace(t *testing.T) {
Convey("When args are not passed", t, func() {
Convey("It should return an error", func() {
var err error
_, err = strings.RegexReplace(context.Background())
So(err, ShouldBeError)
_, err = strings.RegexReplace(context.Background(), values.NewString(""))
So(err, ShouldBeError)
_, err = strings.RegexReplace(context.Background(), values.NewString(""), values.NewString(""))
So(err, ShouldBeError)
})
})
Convey("Should replace with regexp", t, func() {
out, _ := strings.RegexReplace(
context.Background(),
values.NewString("the quick brown fox"),
values.NewString("the.*fox"),
values.NewString("jumped over"),
)
So(out, ShouldEqual, "jumped over")
out, _ = strings.RegexReplace(
context.Background(),
values.NewString("the quick brown fox"),
values.NewString("o"),
values.NewString("i"),
)
So(out, ShouldEqual, "the quick briwn fix")
})
}

View File

@@ -0,0 +1,31 @@
package strings
import (
"context"
"github.com/MontFerret/ferret/pkg/runtime/core"
"github.com/MontFerret/ferret/pkg/runtime/values"
)
/*
* Returns the reverse of the string value.
* @param text (String) - The string to revers
* @returns (String) - Returns a reversed version of the string.
*/
func Reverse(_ context.Context, args ...core.Value) (core.Value, error) {
err := core.ValidateArgs(args, 1, 1)
if err != nil {
return values.EmptyString, err
}
text := args[0].String()
runes := []rune(text)
size := len(runes)
// Reverse
for i := 0; i < size/2; i++ {
runes[i], runes[size-1-i] = runes[size-1-i], runes[i]
}
return values.NewString(string(runes)), nil
}

View File

@@ -0,0 +1,29 @@
package strings_test
import (
"context"
"github.com/MontFerret/ferret/pkg/runtime/values"
"github.com/MontFerret/ferret/pkg/stdlib/strings"
. "github.com/smartystreets/goconvey/convey"
"testing"
)
func TestReverse(t *testing.T) {
Convey("When args are not passed", t, func() {
Convey("It should return an error", func() {
var err error
_, err = strings.Reverse(context.Background())
So(err, ShouldBeError)
})
})
Convey("Should reverse a text with right encoding", t, func() {
out, _ := strings.Reverse(
context.Background(),
values.NewString("The quick brown 狐 jumped over the lazy 犬"),
)
So(out, ShouldEqual, "犬 yzal eht revo depmuj 狐 nworb kciuq ehT")
})
}

View File

@@ -0,0 +1,49 @@
package strings
import (
"context"
"github.com/MontFerret/ferret/pkg/runtime/core"
"github.com/MontFerret/ferret/pkg/runtime/values"
"strings"
)
/*
* Splits the given string value into a list of strings, using the separator.
* @params text (String) - The string to split.
* @params separator (String) - The sperator.
* @params limit (Int) - Limit the number of split values in the result. If no limit is given, the number of splits returned is not bounded.
* @returns strings (Array<String>) - Array of strings.
*/
func Split(_ context.Context, args ...core.Value) (core.Value, error) {
err := core.ValidateArgs(args, 2, 3)
if err != nil {
return values.None, err
}
text := args[0].String()
separator := args[1].String()
limit := -1
if len(args) > 2 {
if args[2].Type() == core.IntType {
limit = int(args[2].(values.Int))
}
}
var strs []string
if limit < 0 {
strs = strings.Split(text, separator)
} else {
strs = strings.SplitN(text, separator, limit)
}
arr := values.NewArray(len(strs))
for _, str := range strs {
arr.Push(values.NewString(str))
}
return arr, nil
}

View File

@@ -0,0 +1,49 @@
package strings_test
import (
"context"
"github.com/MontFerret/ferret/pkg/runtime/values"
"github.com/MontFerret/ferret/pkg/stdlib/strings"
. "github.com/smartystreets/goconvey/convey"
"testing"
)
func TestSplit(t *testing.T) {
Convey("When args are not passed", t, func() {
Convey("It should return an error", func() {
var err error
_, err = strings.Split(context.Background())
So(err, ShouldBeError)
_, err = strings.Split(context.Background(), values.NewString("foo"))
So(err, ShouldBeError)
})
})
Convey("Split('foo-bar-baz', '-' ) should return an array", t, func() {
out, err := strings.Split(
context.Background(),
values.NewString("foo-bar-baz"),
values.NewString("-"),
)
So(err, ShouldBeNil)
So(out.String(), ShouldEqual, `["foo","bar","baz"]`)
})
Convey("Split('foo-bar-baz', '-', 2) should return an array", t, func() {
out, err := strings.Split(
context.Background(),
values.NewString("foo-bar-baz"),
values.NewString("-"),
values.NewInt(2),
)
So(err, ShouldBeNil)
So(out.String(), ShouldEqual, `["foo","bar-baz"]`)
})
}

View File

@@ -1,157 +0,0 @@
package strings
import (
"context"
"github.com/MontFerret/ferret/pkg/runtime/core"
"github.com/MontFerret/ferret/pkg/runtime/values"
"strings"
)
func arg1(inputs []core.Value) (core.Value, error) {
if len(inputs) == 0 {
return values.None, core.Error(core.ErrMissedArgument, "value")
}
return inputs[0], nil
}
func Contains(_ context.Context, inputs ...core.Value) (core.Value, error) {
if len(inputs) < 2 {
return values.None, core.ErrMissedArgument
}
var text values.String
var search values.String
returnIndex := values.False
err := core.ValidateType(inputs[0], core.StringType)
if err != nil {
return values.None, err
}
err = core.ValidateType(inputs[1], core.StringType)
if err != nil {
return values.None, err
}
text = inputs[0].(values.String)
search = inputs[1].(values.String)
if len(inputs) > 2 {
err = core.ValidateType(inputs[2], core.BooleanType)
if err != nil {
return values.None, err
}
returnIndex = inputs[2].(values.Boolean)
}
if returnIndex == values.True {
return text.IndexOf(search), nil
}
return text.Contains(search), nil
}
func Concat(_ context.Context, inputs ...core.Value) (core.Value, error) {
if len(inputs) == 0 {
return values.EmptyString, nil
}
res := values.EmptyString
for _, str := range inputs {
res = res.Concat(str)
}
return res, nil
}
func ConcatWithSeparator(_ context.Context, inputs ...core.Value) (core.Value, error) {
if len(inputs) == 0 || len(inputs) == 1 {
return values.EmptyString, nil
}
separator := inputs[0]
if separator.Type() != core.StringType {
separator = values.NewString(separator.String())
}
res := values.EmptyString
for _, str := range inputs[1:] {
res = res.Concat(separator).Concat(str)
}
return res, nil
}
func FindFirst(_ context.Context, _ ...core.Value) (core.Value, error) {
return values.None, core.Error(core.ErrNotImplemented, "FIND_FIRST")
}
func FindLast(_ context.Context, _ ...core.Value) (core.Value, error) {
return values.None, core.Error(core.ErrNotImplemented, "FIND_LAST")
}
func JsonParse(_ context.Context, _ ...core.Value) (core.Value, error) {
return values.None, core.Error(core.ErrNotImplemented, "JSON_PARSE")
}
func JsonStringify(_ context.Context, _ ...core.Value) (core.Value, error) {
return values.None, core.Error(core.ErrNotImplemented, "JSON_STRINGIFY")
}
func Left(_ context.Context, _ ...core.Value) (core.Value, error) {
return values.None, core.Error(core.ErrNotImplemented, "LEFT")
}
func Like(_ context.Context, _ ...core.Value) (core.Value, error) {
return values.None, core.Error(core.ErrNotImplemented, "LIKE")
}
func Lower(_ context.Context, _ ...core.Value) (core.Value, error) {
return values.None, core.Error(core.ErrNotImplemented, "LOWER")
}
func LTrim(_ context.Context, _ ...core.Value) (core.Value, error) {
return values.None, core.Error(core.ErrNotImplemented, "LTRIM")
}
func Right(_ context.Context, _ ...core.Value) (core.Value, error) {
return values.None, core.Error(core.ErrNotImplemented, "RIGHT")
}
func RTrim(_ context.Context, _ ...core.Value) (core.Value, error) {
return values.None, core.Error(core.ErrNotImplemented, "RTRIM")
}
func Split(_ context.Context, _ ...core.Value) (core.Value, error) {
return values.None, core.Error(core.ErrNotImplemented, "SPLIT")
}
func Substitute(_ context.Context, _ ...core.Value) (core.Value, error) {
return values.None, core.Error(core.ErrNotImplemented, "SUBSTITUTE")
}
func Substring(_ context.Context, _ ...core.Value) (core.Value, error) {
return values.None, core.Error(core.ErrNotImplemented, "SUBSTRING")
}
func Trim(_ context.Context, inputs ...core.Value) (core.Value, error) {
val, err := arg1(inputs)
if err != nil {
return values.None, err
}
return values.NewString(strings.TrimSpace(val.String())), nil
}
func Upper(_ context.Context, _ ...core.Value) (core.Value, error) {
return values.None, core.Error(core.ErrNotImplemented, "UPPER")
}

View File

@@ -0,0 +1,43 @@
package strings
import (
"context"
"github.com/MontFerret/ferret/pkg/runtime/core"
"github.com/MontFerret/ferret/pkg/runtime/values"
"strings"
)
/*
* Replaces search values in the string value.
* @params text (String) - The string to modify
* @params search (String) - The string representing a search pattern
* @params replace (String) - The string representing a replace value
* @param limit (Int) - The cap the number of replacements to this value.
* @return (String) - Returns a string with replace substring.
*/
func Substitute(_ context.Context, args ...core.Value) (core.Value, error) {
err := core.ValidateArgs(args, 2, 4)
if err != nil {
return values.EmptyString, err
}
text := args[0].String()
search := args[1].String()
replace := ""
limit := -1
if len(args) > 2 {
replace = args[2].String()
}
if len(args) > 3 {
if args[3].Type() == core.IntType {
limit = int(args[3].(values.Int))
}
}
out := strings.Replace(text, search, replace, limit)
return values.NewString(out), nil
}

View File

@@ -0,0 +1,49 @@
package strings_test
import (
"context"
"github.com/MontFerret/ferret/pkg/runtime/values"
"github.com/MontFerret/ferret/pkg/stdlib/strings"
. "github.com/smartystreets/goconvey/convey"
"testing"
)
func TestSubstitute(t *testing.T) {
Convey("When args are not passed", t, func() {
Convey("It should return an error", func() {
var err error
_, err = strings.Substitute(context.Background())
So(err, ShouldBeError)
_, err = strings.Substitute(context.Background(), values.NewString("foo"))
So(err, ShouldBeError)
})
})
Convey("Substitute('foo-bar-baz', 'a', 'o') should return 'foo-bor-boz'", t, func() {
out, err := strings.Substitute(
context.Background(),
values.NewString("foo-bar-baz"),
values.NewString("a"),
values.NewString("o"),
)
So(err, ShouldBeNil)
So(out, ShouldEqual, "foo-bor-boz")
})
Convey("Substitute('foo-bar-baz', 'a', 'o', 1) should return 'foo-bor-baz'", t, func() {
out, err := strings.Substitute(
context.Background(),
values.NewString("foo-bar-baz"),
values.NewString("a"),
values.NewString("o"),
values.NewInt(1),
)
So(err, ShouldBeNil)
So(out, ShouldEqual, "foo-bor-baz")
})
}

View File

@@ -0,0 +1,114 @@
package strings
import (
"context"
"github.com/MontFerret/ferret/pkg/runtime/core"
"github.com/MontFerret/ferret/pkg/runtime/values"
)
/*
* Returns a substring of value.
* @params value (String) - The source string.
* @param offset (Int) - Start at offset, offsets start at position 0.
* @param length (Int, optional) - At most length characters, omit to get the substring from offset to the end of the string. Optional.
* @returns substring (String) - A substring of value.
*/
func Substring(_ context.Context, args ...core.Value) (core.Value, error) {
err := core.ValidateArgs(args, 2, 3)
if err != nil {
return values.EmptyString, err
}
err = core.ValidateType(args[1], core.IntType)
if err != nil {
return values.EmptyString, err
}
text := args[0].String()
runes := []rune(text)
size := len(runes)
offset := int(args[1].(values.Int))
length := size
if len(args) > 2 {
if args[2].Type() == core.IntType {
length = int(args[2].(values.Int))
}
}
var substr []rune
if length == size {
substr = runes[offset:]
} else {
end := offset + length
if size > end {
substr = runes[offset:end]
} else {
substr = runes[offset:]
}
}
return values.NewStringFromRunes(substr), nil
}
/*
* Returns the leftmost characters of the string value by index.
* @param src (String) - The source string.
* @params length (Int) - The amount of characters to return.
* @returns substr (String)
*/
func Left(_ context.Context, args ...core.Value) (core.Value, error) {
err := core.ValidateArgs(args, 2, 2)
if err != nil {
return values.EmptyString, err
}
text := args[0].String()
runes := []rune(text)
var pos int
if args[1].Type() == core.IntType {
pos = int(args[1].(values.Int))
}
if len(text) < pos {
return values.NewString(text), nil
}
return values.NewStringFromRunes(runes[0:pos]), nil
}
/*
* Returns the rightmost characters of the string value.
* @param src (String) - The source string.
* @params length (Int) - The amount of characters to return.
* @returns substr (String)
*/
func Right(_ context.Context, args ...core.Value) (core.Value, error) {
err := core.ValidateArgs(args, 2, 2)
if err != nil {
return values.EmptyString, err
}
text := args[0].String()
runes := []rune(text)
size := len(runes)
pos := size
if args[1].Type() == core.IntType {
pos = int(args[1].(values.Int))
}
if len(text) < pos {
return values.NewString(string(text)), nil
}
return values.NewStringFromRunes(runes[pos:size]), nil
}

View File

@@ -0,0 +1,129 @@
package strings_test
import (
"context"
"github.com/MontFerret/ferret/pkg/runtime/values"
"github.com/MontFerret/ferret/pkg/stdlib/strings"
. "github.com/smartystreets/goconvey/convey"
"testing"
)
func TestSubstring(t *testing.T) {
Convey("When args are not passed", t, func() {
Convey("It should return an error", func() {
var err error
_, err = strings.Substring(context.Background())
So(err, ShouldBeError)
_, err = strings.Substring(context.Background(), values.NewString("foo"))
So(err, ShouldBeError)
})
})
Convey("Substring('foobar', 3) should return 'bar'", t, func() {
out, err := strings.Substring(
context.Background(),
values.NewString("foobar"),
values.NewInt(3),
)
So(err, ShouldBeNil)
So(out, ShouldEqual, "bar")
})
Convey("Substring('foobar', 3, 2) should return 'ba'", t, func() {
out, err := strings.Substring(
context.Background(),
values.NewString("foobar"),
values.NewInt(3),
values.NewInt(2),
)
So(err, ShouldBeNil)
So(out, ShouldEqual, "ba")
})
Convey("Substring('foobar', 3, 5) should return 'bar'", t, func() {
out, err := strings.Substring(
context.Background(),
values.NewString("foobar"),
values.NewInt(3),
values.NewInt(5),
)
So(err, ShouldBeNil)
So(out, ShouldEqual, "bar")
})
}
func TestLeft(t *testing.T) {
Convey("When args are not passed", t, func() {
Convey("It should return an error", func() {
var err error
_, err = strings.Left(context.Background())
So(err, ShouldBeError)
_, err = strings.Left(context.Background(), values.NewString("foo"))
So(err, ShouldBeError)
})
})
Convey("Left('foobar', 3) should return 'foo'", t, func() {
out, _ := strings.Left(
context.Background(),
values.NewString("foobar"),
values.NewInt(3),
)
So(out, ShouldEqual, "foo")
})
Convey("Left('foobar', 10) should return 'foobar'", t, func() {
out, _ := strings.Left(
context.Background(),
values.NewString("foobar"),
values.NewInt(10),
)
So(out, ShouldEqual, "foobar")
})
}
func TestRight(t *testing.T) {
Convey("When args are not passed", t, func() {
Convey("It should return an error", func() {
var err error
_, err = strings.Right(context.Background())
So(err, ShouldBeError)
_, err = strings.Right(context.Background(), values.NewString("foo"))
So(err, ShouldBeError)
})
})
Convey("Right('foobar', 3) should return 'bar'", t, func() {
out, _ := strings.Right(
context.Background(),
values.NewString("foobar"),
values.NewInt(3),
)
So(out, ShouldEqual, "bar")
})
Convey("Right('foobar', 10) should return 'foobar'", t, func() {
out, _ := strings.Right(
context.Background(),
values.NewString("foobar"),
values.NewInt(10),
)
So(out, ShouldEqual, "foobar")
})
}

View File

@@ -0,0 +1,77 @@
package strings
import (
"context"
"github.com/MontFerret/ferret/pkg/runtime/core"
"github.com/MontFerret/ferret/pkg/runtime/values"
"strings"
)
/*
* Returns the string value with whitespace stripped from the start and/or end.
* @param value (String) - The string.
* @param chars (String) - Overrides the characters that should be removed from the string. It defaults to \r\n \t.
* @returns (String) - The string without chars on both sides.
*/
func Trim(_ context.Context, args ...core.Value) (core.Value, error) {
err := core.ValidateArgs(args, 1, 2)
if err != nil {
return values.EmptyString, err
}
text := args[0].String()
chars := " "
if len(args) > 1 {
chars = args[1].String()
}
return values.NewString(strings.Trim(text, chars)), nil
}
/*
* Returns the string value with whitespace stripped from the start only.
* @param value (String) - The string.
* @param chars (String) - Overrides the characters that should be removed from the string. It defaults to \r\n \t.
* @returns (String) - The string without chars at the left-hand side.
*/
func LTrim(_ context.Context, args ...core.Value) (core.Value, error) {
err := core.ValidateArgs(args, 1, 2)
if err != nil {
return values.EmptyString, err
}
text := args[0].String()
chars := " "
if len(args) > 1 {
chars = args[1].String()
}
return values.NewString(strings.TrimLeft(text, chars)), nil
}
/*
* Returns the string value with whitespace stripped from the end only.
* @param value (String) - The string.
* @param chars (String) - Overrides the characters that should be removed from the string. It defaults to \r\n \t.
* @returns (String) - The string without chars at the right-hand side.
*/
func RTrim(_ context.Context, args ...core.Value) (core.Value, error) {
err := core.ValidateArgs(args, 1, 2)
if err != nil {
return values.EmptyString, err
}
text := args[0].String()
chars := " "
if len(args) > 1 {
chars = args[1].String()
}
return values.NewString(strings.TrimRight(text, chars)), nil
}

View File

@@ -0,0 +1,102 @@
package strings_test
import (
"context"
"github.com/MontFerret/ferret/pkg/runtime/values"
"github.com/MontFerret/ferret/pkg/stdlib/strings"
. "github.com/smartystreets/goconvey/convey"
"testing"
)
func TestLTrim(t *testing.T) {
Convey("When args are not passed", t, func() {
Convey("It should return an error", func() {
var err error
_, err = strings.LTrim(context.Background())
So(err, ShouldBeError)
})
})
Convey("LTrim(' foo bar ') should return 'foo bar '", t, func() {
out, _ := strings.LTrim(
context.Background(),
values.NewString(" foo bar "),
)
So(out, ShouldEqual, "foo bar ")
})
Convey("LTrim('--==[foo-bar]==--', '-=[]') should return 'foo-bar]==--'", t, func() {
out, _ := strings.LTrim(
context.Background(),
values.NewString("--==[foo-bar]==--"),
values.NewString("-=[]"),
)
So(out, ShouldEqual, "foo-bar]==--")
})
}
func TestRTrim(t *testing.T) {
Convey("When args are not passed", t, func() {
Convey("It should return an error", func() {
var err error
_, err = strings.RTrim(context.Background())
So(err, ShouldBeError)
})
})
Convey("RTrim(' foo bar ') should return ' foo bar'", t, func() {
out, _ := strings.RTrim(
context.Background(),
values.NewString(" foo bar "),
)
So(out, ShouldEqual, " foo bar")
})
Convey("LTrim('--==[foo-bar]==--', '-=[]') should return '--==[foo-bar'", t, func() {
out, _ := strings.RTrim(
context.Background(),
values.NewString("--==[foo-bar]==--"),
values.NewString("-=[]"),
)
So(out, ShouldEqual, "--==[foo-bar")
})
}
func TestTrim(t *testing.T) {
Convey("When args are not passed", t, func() {
Convey("It should return an error", func() {
var err error
_, err = strings.Trim(context.Background())
So(err, ShouldBeError)
})
})
Convey("Trim(' foo bar ') should return 'foo bar'", t, func() {
out, _ := strings.Trim(
context.Background(),
values.NewString(" foo bar "),
)
So(out, ShouldEqual, "foo bar")
})
Convey("Trim('--==[foo-bar]==--', '-=[]') should return 'foo-bar'", t, func() {
out, _ := strings.Trim(
context.Background(),
values.NewString("--==[foo-bar]==--"),
values.NewString("-=[]"),
)
So(out, ShouldEqual, "foo-bar")
})
}

View File

@@ -9,7 +9,7 @@ import (
)
func Sleep(_ context.Context, inputs ...core.Value) (core.Value, error) {
err := core.ValidateArgs(inputs, 1)
err := core.ValidateArgs(inputs, 1, 1)
if err != nil {
return values.None, nil