From ad746c05aa896e8d3a6611797c7ad4a45bd3fccb Mon Sep 17 00:00:00 2001 From: Leon Mika Date: Fri, 10 May 2024 00:10:42 +0000 Subject: [PATCH] Added the keys built-in --- ucl/builtins.go | 23 ++++++++++++++++ ucl/inst.go | 1 + ucl/testbuiltins_test.go | 57 +++++++++++++++++++++++++++++++++++++++- 3 files changed, 80 insertions(+), 1 deletion(-) diff --git a/ucl/builtins.go b/ucl/builtins.go index b934f5e..b14f3a4 100644 --- a/ucl/builtins.go +++ b/ucl/builtins.go @@ -165,6 +165,29 @@ func indexBuiltin(ctx context.Context, args invocationArgs) (object, error) { return val, nil } +func keysBuiltin(ctx context.Context, args invocationArgs) (object, error) { + if err := args.expectArgn(1); err != nil { + return nil, err + } + + val := args.args[0] + switch v := val.(type) { + case hashable: + keys := make(listObject, 0, v.Len()) + if err := v.Each(func(k string, _ object) error { + keys = append(keys, strObject(k)) + return nil + }); err != nil { + return nil, err + } + return keys, nil + default: + return nil, nil + } + + return nil, nil +} + func mapBuiltin(ctx context.Context, args invocationArgs) (object, error) { if err := args.expectArgn(2); err != nil { return nil, err diff --git a/ucl/inst.go b/ucl/inst.go index 61864dc..b0d7ae7 100644 --- a/ucl/inst.go +++ b/ucl/inst.go @@ -50,6 +50,7 @@ func New(opts ...InstOption) *Inst { rootEC.addCmd("set", invokableFunc(setBuiltin)) rootEC.addCmd("toUpper", invokableFunc(toUpperBuiltin)) rootEC.addCmd("len", invokableFunc(lenBuiltin)) + rootEC.addCmd("keys", invokableFunc(keysBuiltin)) rootEC.addCmd("index", invokableFunc(indexBuiltin)) rootEC.addCmd("call", invokableFunc(callBuiltin)) diff --git a/ucl/testbuiltins_test.go b/ucl/testbuiltins_test.go index ede1137..fe1c71a 100644 --- a/ucl/testbuiltins_test.go +++ b/ucl/testbuiltins_test.go @@ -4,9 +4,10 @@ import ( "bytes" "context" "fmt" - "github.com/stretchr/testify/assert" "strings" "testing" + + "github.com/stretchr/testify/assert" ) // Builtins used for test @@ -642,3 +643,57 @@ func TestBuiltins_Len(t *testing.T) { }) } } + +func TestBuiltins_Keys(t *testing.T) { + type testNested struct { + Nested string + Type string + } + + tests := []struct { + desc string + expr string + wantItems []string + }{ + {desc: "keys of map", expr: `keys [alpha: "hello" bravo: "world"]`, wantItems: []string{"alpha", "bravo"}}, + {desc: "keys of go struct 1", expr: `goStruct | keys`, wantItems: []string{"Alpha", "Beta", "Gamma"}}, + {desc: "keys of go struct 2", expr: `index (goStruct) Gamma | keys`, wantItems: []string{"Nested", "Type"}}, + } + + for _, tt := range tests { + t.Run(tt.desc, func(t *testing.T) { + ctx := context.Background() + outW := bytes.NewBuffer(nil) + + inst := New(WithOut(outW), WithTestBuiltin()) + inst.SetBuiltin("goInt", func(ctx context.Context, args CallArgs) (any, error) { + return []int{6, 5, 4}, nil + }) + inst.SetBuiltin("goStruct", func(ctx context.Context, args CallArgs) (any, error) { + return struct { + Alpha string + Beta string + Gamma testNested + hidden string + missing string + }{ + Alpha: "foo", + Beta: "bar", + Gamma: testNested{ + Nested: "ads", + Type: "asd", + }, + hidden: "hidden", + missing: "missing", + }, nil + }) + + res, err := inst.Eval(ctx, tt.expr) + assert.NoError(t, err) + assert.Len(t, res, len(tt.wantItems)) + for _, i := range tt.wantItems { + assert.Contains(t, res, i) + } + }) + } +}