mirror of
https://github.com/MontFerret/ferret.git
synced 2024-12-14 11:23:02 +02:00
Feature/#106 fmt function (#151)
This commit is contained in:
parent
b097527cd3
commit
f6e465d556
@ -2,7 +2,6 @@ package objects_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"log"
|
||||
"testing"
|
||||
|
||||
"github.com/MontFerret/ferret/pkg/runtime/values"
|
||||
@ -131,7 +130,6 @@ func TestMergeRecursive(t *testing.T) {
|
||||
|
||||
actual, err := objects.MergeRecursive(context.Background(), obj1, obj2)
|
||||
|
||||
log.Println(actual)
|
||||
So(err, ShouldBeNil)
|
||||
So(actual.Compare(expected), ShouldEqual, 0)
|
||||
})
|
||||
|
88
pkg/stdlib/strings/fmt.go
Normal file
88
pkg/stdlib/strings/fmt.go
Normal file
@ -0,0 +1,88 @@
|
||||
package strings
|
||||
|
||||
import (
|
||||
"context"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/MontFerret/ferret/pkg/runtime/core"
|
||||
"github.com/MontFerret/ferret/pkg/runtime/values"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
// Fmt formats the template using these arguments.
|
||||
// @params template (String) - template.
|
||||
// @params args (Any Values) - template arguments.
|
||||
// @returns (String) - string formed by template using arguments.
|
||||
func Fmt(_ context.Context, args ...core.Value) (core.Value, error) {
|
||||
err := core.ValidateArgs(args, 1, core.MaxArgs)
|
||||
if err != nil {
|
||||
return values.None, err
|
||||
}
|
||||
|
||||
err = core.ValidateType(args[0], core.StringType)
|
||||
if err != nil {
|
||||
return values.None, err
|
||||
}
|
||||
|
||||
formatted, err := format(args[0].String(), args[1:])
|
||||
if err != nil {
|
||||
return values.None, err
|
||||
}
|
||||
|
||||
return values.NewString(formatted), nil
|
||||
}
|
||||
|
||||
func format(template string, args []core.Value) (string, error) {
|
||||
rgx, err := regexp.Compile("{[0-9]*}")
|
||||
if err != nil {
|
||||
return "", errors.Errorf("failed to build regexp: %v", err)
|
||||
}
|
||||
|
||||
argsCount := len(args)
|
||||
emptyBracketsCount := strings.Count(template, "{}")
|
||||
|
||||
if argsCount > emptyBracketsCount && emptyBracketsCount != 0 {
|
||||
return "", errors.Errorf("there are arguments that have never been used")
|
||||
}
|
||||
|
||||
var betweenBrackets string
|
||||
var n int
|
||||
// index of the last value
|
||||
// inserted into the template
|
||||
var lastArgIdx int
|
||||
|
||||
template = rgx.ReplaceAllStringFunc(template, func(s string) string {
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
|
||||
betweenBrackets = s[1 : len(s)-1]
|
||||
|
||||
if betweenBrackets == "" {
|
||||
if argsCount <= lastArgIdx {
|
||||
err = errors.Errorf("not enought arguments")
|
||||
return ""
|
||||
}
|
||||
|
||||
lastArgIdx++
|
||||
return args[lastArgIdx-1].String()
|
||||
}
|
||||
|
||||
n, err = strconv.Atoi(betweenBrackets)
|
||||
if err != nil {
|
||||
err = errors.Errorf("failed to parse int: %v", err)
|
||||
return ""
|
||||
}
|
||||
|
||||
if n >= argsCount {
|
||||
err = errors.Errorf("invalid reference to argument `%d`", n)
|
||||
return ""
|
||||
}
|
||||
|
||||
return args[n].String()
|
||||
})
|
||||
|
||||
return template, err
|
||||
}
|
152
pkg/stdlib/strings/fmt_test.go
Normal file
152
pkg/stdlib/strings/fmt_test.go
Normal file
@ -0,0 +1,152 @@
|
||||
package strings_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"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"
|
||||
)
|
||||
|
||||
type testCase struct {
|
||||
Name string
|
||||
Expected string
|
||||
Format string
|
||||
Args []core.Value
|
||||
ShouldErr bool
|
||||
}
|
||||
|
||||
func TestFmt(t *testing.T) {
|
||||
tcs := []*testCase{
|
||||
&testCase{
|
||||
Name: `FMT("{}", 1) return "1"`,
|
||||
Expected: "1",
|
||||
Format: "{}",
|
||||
Args: []core.Value{
|
||||
values.NewInt(1),
|
||||
},
|
||||
},
|
||||
&testCase{
|
||||
Name: `FMT("{1} {} {0} {}", 1, 2) return "2 1 1 2"`,
|
||||
Expected: "2 1 1 2",
|
||||
Format: "{1} {} {0} {}",
|
||||
Args: []core.Value{
|
||||
values.NewInt(1),
|
||||
values.NewInt(2),
|
||||
},
|
||||
},
|
||||
&testCase{
|
||||
Name: `FMT("{1} {} {0} {} {}", 1, 2, 3) return "2 1 1 2 3"`,
|
||||
Expected: "2 1 1 2 3",
|
||||
Format: "{1} {} {0} {} {}",
|
||||
Args: []core.Value{
|
||||
values.NewInt(1),
|
||||
values.NewInt(2),
|
||||
values.NewInt(3),
|
||||
},
|
||||
},
|
||||
&testCase{
|
||||
Name: `FMT("{2}{1} {0}", "World!", ",", "Hello") return "Hello, World!"`,
|
||||
Expected: "Hello, World!",
|
||||
Format: "{2}{1} {0}",
|
||||
Args: []core.Value{
|
||||
values.NewString("World!"),
|
||||
values.NewString(","),
|
||||
values.NewString("Hello"),
|
||||
},
|
||||
},
|
||||
&testCase{
|
||||
Name: `FMT({}, {key:"value"}) return "{"key":"value"}"`,
|
||||
Expected: `{"key":"value"}`,
|
||||
Format: "{}",
|
||||
Args: []core.Value{
|
||||
values.NewObjectWith(
|
||||
values.NewObjectProperty(
|
||||
"key", values.NewString("value"),
|
||||
),
|
||||
),
|
||||
},
|
||||
},
|
||||
&testCase{
|
||||
Name: `FMT({}, {key:"value"}) return "{"key":"value"}"`,
|
||||
Expected: `{"key":"value","yek":"eulav"}`,
|
||||
Format: "{}",
|
||||
Args: []core.Value{
|
||||
values.NewObjectWith(
|
||||
values.NewObjectProperty(
|
||||
"key", values.NewString("value"),
|
||||
),
|
||||
values.NewObjectProperty(
|
||||
"yek", values.NewString("eulav"),
|
||||
),
|
||||
),
|
||||
},
|
||||
},
|
||||
&testCase{
|
||||
Name: `FMT("string") return "string"`,
|
||||
Expected: "string",
|
||||
Format: "string",
|
||||
},
|
||||
&testCase{
|
||||
Name: `FMT("string") return "string"`,
|
||||
Expected: "string",
|
||||
Format: "string",
|
||||
Args: []core.Value{
|
||||
values.NewInt(1),
|
||||
},
|
||||
},
|
||||
&testCase{
|
||||
Name: `FMT("{}") return error`,
|
||||
Format: "{}",
|
||||
Args: []core.Value{},
|
||||
ShouldErr: true,
|
||||
},
|
||||
&testCase{
|
||||
Name: `FMT("{1}", 10) return error`,
|
||||
Format: "{1}",
|
||||
Args: []core.Value{
|
||||
values.NewInt(10),
|
||||
},
|
||||
ShouldErr: true,
|
||||
},
|
||||
&testCase{
|
||||
Name: `FMT("{1} {} {0} {}", 1, 2, 3) return error`,
|
||||
Format: "{1} {} {0} {}",
|
||||
Args: []core.Value{
|
||||
values.NewInt(1),
|
||||
values.NewInt(2),
|
||||
values.NewInt(3),
|
||||
},
|
||||
ShouldErr: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range tcs {
|
||||
tc.Do(t)
|
||||
}
|
||||
}
|
||||
|
||||
func (tc *testCase) Do(t *testing.T) {
|
||||
Convey(tc.Name, t, func() {
|
||||
var expected core.Value
|
||||
|
||||
expected = values.NewString(tc.Expected)
|
||||
|
||||
args := []core.Value{values.NewString(tc.Format)}
|
||||
args = append(args, tc.Args...)
|
||||
|
||||
formatted, err := strings.Fmt(context.Background(), args...)
|
||||
|
||||
if tc.ShouldErr {
|
||||
So(err, ShouldBeError)
|
||||
expected = values.None
|
||||
} else {
|
||||
So(err, ShouldBeNil)
|
||||
}
|
||||
|
||||
So(formatted, ShouldEqual, expected)
|
||||
})
|
||||
}
|
@ -34,5 +34,6 @@ func NewLib() map[string]core.Function {
|
||||
"FROM_BASE64": FromBase64,
|
||||
"TRIM": Trim,
|
||||
"UPPER": Upper,
|
||||
"FMT": Fmt,
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user