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:
@@ -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"
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
42
pkg/stdlib/strings/case.go
Normal file
42
pkg/stdlib/strings/case.go
Normal 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
|
||||
}
|
||||
51
pkg/stdlib/strings/case_test.go
Normal file
51
pkg/stdlib/strings/case_test.go
Normal 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")
|
||||
})
|
||||
}
|
||||
92
pkg/stdlib/strings/concat.go
Normal file
92
pkg/stdlib/strings/concat.go
Normal 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
|
||||
}
|
||||
145
pkg/stdlib/strings/concat_test.go
Normal file
145
pkg/stdlib/strings/concat_test.go
Normal 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")
|
||||
})
|
||||
})
|
||||
}
|
||||
56
pkg/stdlib/strings/contains.go
Normal file
56
pkg/stdlib/strings/contains.go
Normal 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
|
||||
}
|
||||
95
pkg/stdlib/strings/contains_test.go
Normal file
95
pkg/stdlib/strings/contains_test.go
Normal 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)
|
||||
})
|
||||
})
|
||||
}
|
||||
101
pkg/stdlib/strings/encode.go
Normal file
101
pkg/stdlib/strings/encode.go
Normal 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
|
||||
}
|
||||
122
pkg/stdlib/strings/encode_test.go
Normal file
122
pkg/stdlib/strings/encode_test.go
Normal 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
106
pkg/stdlib/strings/find.go
Normal 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
|
||||
}
|
||||
126
pkg/stdlib/strings/find_test.go
Normal file
126
pkg/stdlib/strings/find_test.go
Normal 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)
|
||||
})
|
||||
})
|
||||
}
|
||||
52
pkg/stdlib/strings/json.go
Normal file
52
pkg/stdlib/strings/json.go
Normal 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
|
||||
}
|
||||
241
pkg/stdlib/strings/json_test.go
Normal file
241
pkg/stdlib/strings/json_test.go
Normal 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"`)
|
||||
})
|
||||
}
|
||||
@@ -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,
|
||||
}
|
||||
}
|
||||
|
||||
20
pkg/stdlib/strings/like.go
Normal file
20
pkg/stdlib/strings/like.go
Normal 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")
|
||||
}
|
||||
1
pkg/stdlib/strings/like_test.go
Normal file
1
pkg/stdlib/strings/like_test.go
Normal file
@@ -0,0 +1 @@
|
||||
package strings_test
|
||||
56
pkg/stdlib/strings/random.go
Normal file
56
pkg/stdlib/strings/random.go
Normal 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
|
||||
}
|
||||
49
pkg/stdlib/strings/random_test.go
Normal file
49
pkg/stdlib/strings/random_test.go
Normal 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
166
pkg/stdlib/strings/regex.go
Normal 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
|
||||
}
|
||||
138
pkg/stdlib/strings/regex_test.go
Normal file
138
pkg/stdlib/strings/regex_test.go
Normal 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")
|
||||
})
|
||||
}
|
||||
31
pkg/stdlib/strings/reverse.go
Normal file
31
pkg/stdlib/strings/reverse.go
Normal 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
|
||||
}
|
||||
29
pkg/stdlib/strings/reverse_test.go
Normal file
29
pkg/stdlib/strings/reverse_test.go
Normal 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")
|
||||
})
|
||||
}
|
||||
49
pkg/stdlib/strings/split.go
Normal file
49
pkg/stdlib/strings/split.go
Normal 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
|
||||
}
|
||||
49
pkg/stdlib/strings/split_test.go
Normal file
49
pkg/stdlib/strings/split_test.go
Normal 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"]`)
|
||||
})
|
||||
}
|
||||
@@ -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")
|
||||
}
|
||||
43
pkg/stdlib/strings/substitute.go
Normal file
43
pkg/stdlib/strings/substitute.go
Normal 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
|
||||
}
|
||||
49
pkg/stdlib/strings/substitute_test.go
Normal file
49
pkg/stdlib/strings/substitute_test.go
Normal 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")
|
||||
})
|
||||
}
|
||||
114
pkg/stdlib/strings/substr.go
Normal file
114
pkg/stdlib/strings/substr.go
Normal 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
|
||||
}
|
||||
129
pkg/stdlib/strings/substr_test.go
Normal file
129
pkg/stdlib/strings/substr_test.go
Normal 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")
|
||||
})
|
||||
}
|
||||
77
pkg/stdlib/strings/trim.go
Normal file
77
pkg/stdlib/strings/trim.go
Normal 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
|
||||
}
|
||||
102
pkg/stdlib/strings/trim_test.go
Normal file
102
pkg/stdlib/strings/trim_test.go
Normal 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")
|
||||
})
|
||||
}
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user