Skip to content

Commit

Permalink
feat: more tests; more expression-funcs; added data.Value#List()
Browse files Browse the repository at this point in the history
  • Loading branch information
lukasjarosch committed May 1, 2024
1 parent 45b8b8d commit f6839f3
Show file tree
Hide file tree
Showing 6 changed files with 153 additions and 14 deletions.
15 changes: 15 additions & 0 deletions data/value.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package data

import (
"fmt"
"reflect"
"time"
)

Expand Down Expand Up @@ -58,3 +59,17 @@ func (val Value) Float64() (float64, error) {
}
return f, nil
}

// List will return a slice if the underlying type is either a slice or an array.
// In all other cases, an error is returned.
func (val Value) List() ([]interface{}, error) {
if !IsArray(val.Raw) {
return nil, fmt.Errorf("value is not a list")
}
rawSlice := reflect.ValueOf(val.Raw)
list := make([]interface{}, rawSlice.Len())
for i := 0; i < rawSlice.Len(); i++ {
list[i] = rawSlice.Index(i).Interface()
}
return list, nil
}
10 changes: 4 additions & 6 deletions expression.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,6 @@ import (
"regexp"
"strings"

"github.com/davecgh/go-spew/spew"

"github.com/lukasjarosch/skipper/data"
"github.com/lukasjarosch/skipper/expression"
"github.com/lukasjarosch/skipper/graph"
Expand Down Expand Up @@ -121,6 +119,9 @@ func (m *ExpressionManager) executeExpression(expr *expression.ExpressionNode) (
return data.NilValue, nil
}

// pathValueProvider is used as a data context for executing expressions.
// It is used to dynamically create a map of paths which provide all
// necessary data to execute an expression.
type pathValueProvider map[string]interface{}

func (pvp pathValueProvider) GetPath(path data.Path) (interface{}, error) {
Expand Down Expand Up @@ -219,7 +220,7 @@ func (m *ExpressionManager) ExecuteInput(input string) (data.Value, error) {
for i, expr := range expressions {
result, err := expression.Execute(expr, valueProvider, m.variables.GetAll(), nil)
if err != nil {
return data.NilValue, fmt.Errorf("failed to execute expression at path '%s': %w", pathVertex, err)
return data.NilValue, fmt.Errorf("failed to execute expression '%s' at path '%s': %w", expr.Text(), pathVertex, err)
}
pathResults[i] = result
}
Expand Down Expand Up @@ -248,12 +249,9 @@ func (m *ExpressionManager) ExecuteInput(input string) (data.Value, error) {
oldValue := sourceValue.String()[exprOffsets[0]:exprOffsets[1]]
newValue := strings.Replace(sourceValue.String(), oldValue, data.NewValue(result).String(), 1)
valueProvider[pathVertex] = newValue
spew.Println("ASDF", newValue)
}
}

spew.Dump(valueProvider)

return valueProvider.GetPathValue(data.NewPath(tmpVertexHash))
}

Expand Down
63 changes: 62 additions & 1 deletion expression/func.go
Original file line number Diff line number Diff line change
@@ -1,13 +1,17 @@
package expression

import (
"crypto/sha256"
"encoding/hex"
"fmt"
"os"
"reflect"
"strings"
"sync"

"github.com/iancoleman/strcase"

"github.com/lukasjarosch/skipper/data"
)

type FuncMap map[string]any
Expand All @@ -19,7 +23,16 @@ var builtins = FuncMap{
"set_env": set_env,
"default": defaultFunc,

// string casing helpers
// string
"replace": func(input, search, replace string) string { return strings.ReplaceAll(input, search, replace) },
"trim_space": func(input string) string { return strings.TrimSpace(input) },
"trim_all": func(input, cutset string) string { return strings.Trim(input, cutset) },
"trim_left": func(input, cutset string) string { return strings.TrimLeft(input, cutset) },
"trim_right": func(input, cutset string) string { return strings.TrimRight(input, cutset) },
"trim_suffix": func(input, suffix string) string { return strings.TrimSuffix(input, suffix) },
"trim_prefix": func(input, prefix string) string { return strings.TrimPrefix(input, prefix) },

// string case
"to_upper": func(s string) string { return strings.ToUpper(s) },
"to_lower": func(s string) string { return strings.ToLower(s) },
"to_snake": strcase.ToSnake,
Expand All @@ -30,6 +43,22 @@ var builtins = FuncMap{
"to_lower_camel": strcase.ToLowerCamel,
"to_delimited": to_delimited,
"to_screaming_delimited": to_screaming_delimited,

// lists
"must_first": mustFirst,
"first": func(input interface{}) interface{} {
first, err := mustFirst(input)
if err != nil {
panic(err)
}
return first
},

// crypto
"sha256": func(s string) string {
hash := sha256.Sum256([]byte(s))
return hex.EncodeToString(hash[:])
},
}

var builtinFuncsOnce struct {
Expand Down Expand Up @@ -181,3 +210,35 @@ func to_screaming_delimited(str, delim string) string {

return strcase.ToScreamingDelimited(str, byte(delim[0]), "", true)
}

// mustFirst attempts to return the first element of the given list.
func mustFirst(list interface{}) (interface{}, error) {
tp := reflect.TypeOf(list).Kind()

// in case the parameter is `data.Value`, use its means to resolve the first element
if val, ok := list.(data.Value); ok {
valList, err := val.List()
if err != nil {
return nil, fmt.Errorf("mustFirst: failed to convert data.Value to list: %w", err)
}
if len(valList) == 0 {
return nil, nil
}
return valList[0], nil
}

// all other cases: list is not [data.Value]
switch tp {
case reflect.Slice, reflect.Array:
l2 := reflect.ValueOf(list)

l := l2.Len()
if l == 0 {
return nil, nil
}

return l2.Index(0).Interface(), nil
default:
return nil, fmt.Errorf("mustFirst: unable to find first element on type %s", tp)
}
}
59 changes: 54 additions & 5 deletions expression_test.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package skipper_test

import (
"fmt"
"testing"

"github.com/stretchr/testify/assert"
Expand Down Expand Up @@ -59,14 +60,62 @@ func TestExpressionManager_ExecuteInput(t *testing.T) {
errExpected error
}{
{
name: "ExpressionManager_ExecuteInput",
input: "${john:name}",
name: "Simple valid path",
input: "This is ${project:name}",
pathValues: map[string]data.Value{
"john.name": data.NewValue("${john:first} ${john:last}"),
"project.name": data.NewValue("skipper"),
},
expected: data.NewValue("This is skipper"),
},
{
name: "Simple path with dependencies",
input: "Hello there, ${john:name}",
pathValues: map[string]data.Value{
"john.name": data.NewValue("${to_upper(john:first)} ${to_lower(john:last)}"),
"john.first": data.NewValue("john"),
"john.last": data.NewValue("doe"),
},
expected: data.NewValue("john doe"),
expected: data.NewValue("Hello there, JOHN doe"),
},
{
name: "Function call",
input: "This is ${to_upper(project:name)}",
pathValues: map[string]data.Value{
"project.name": data.NewValue("skipper"),
},
expected: data.NewValue("This is SKIPPER"),
},
{
name: "Function call with two params",
input: `This is ${replace(project:name, "skipper", "peter")}`,
pathValues: map[string]data.Value{
"project.name": data.NewValue("skipper"),
},
expected: data.NewValue("This is peter"),
},
{
name: "List function call with valid string list",
input: `This is ${first(project:names)}`,
pathValues: map[string]data.Value{
"project.names": data.NewValue([]string{"skipper", "bob"}),
},
expected: data.NewValue("This is skipper"),
},
{
name: "List function call with valid int list",
input: `This is ${first(project:names)}`,
pathValues: map[string]data.Value{
"project.names": data.NewValue([]int{1, 2, 3, 4}),
},
expected: data.NewValue("This is 1"),
},
{
name: "List function call with invalid list",
input: `This is ${first(project:names)}`,
pathValues: map[string]data.Value{
"project.names": data.NewValue("def-not-a-list"),
},
errExpected: fmt.Errorf("failed to convert data.Value to list"),
},
}

Expand All @@ -89,7 +138,7 @@ func TestExpressionManager_ExecuteInput(t *testing.T) {
ret, err := exprManager.ExecuteInput(tt.input)

if tt.errExpected != nil {
assert.ErrorIs(t, err, tt.errExpected)
assert.ErrorContains(t, err, tt.errExpected.Error())
return
}

Expand Down
9 changes: 7 additions & 2 deletions go.mod
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
module github.com/lukasjarosch/skipper

go 1.21
go 1.22

toolchain go1.22.0

require (
github.com/dominikbraun/graph v0.23.0
Expand All @@ -11,8 +13,11 @@ require (
require (
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/iancoleman/strcase v0.3.0 // indirect
github.com/kr/pretty v0.2.0 // indirect
github.com/kr/pretty v0.3.1 // indirect
github.com/kr/text v0.2.0 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/rogpeppe/go-internal v1.12.0 // indirect
github.com/ryboe/q v1.0.21 // indirect
github.com/stretchr/objx v0.5.0 // indirect
github.com/vektra/mockery v1.1.2 // indirect
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 // indirect
Expand Down
11 changes: 11 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
Expand All @@ -7,11 +8,21 @@ github.com/iancoleman/strcase v0.3.0 h1:nTXanmYxhfFAMjZL34Ov6gkzEsSJZ5DbhxWjvSAS
github.com/iancoleman/strcase v0.3.0/go.mod h1:iwCmte+B7n89clKwxIoIXy/HfoL7AsD47ZCWhYzw7ho=
github.com/kr/pretty v0.2.0 h1:s5hAObm+yFO5uHYt5dYjxi2rXrsnmRpJx4OYvIWUaQs=
github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8=
github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4=
github.com/ryboe/q v1.0.21 h1:sbLQFC3eMDEs8q3g4otbrWExLTHEyKISCFuN7Akropc=
github.com/ryboe/q v1.0.21/go.mod h1:g4aI/DhdScxW3WLRGxHXgg5ZkGrk+b4h1h5u2PTCvDc=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
Expand Down

0 comments on commit f6839f3

Please sign in to comment.