From 4c532e5005a40d007dad628c03ff1a1dc9a8ddd2 Mon Sep 17 00:00:00 2001 From: Leon Mika Date: Tue, 23 Apr 2024 00:41:36 +0000 Subject: [PATCH] Added support for listable proxy objects These are things like slices that can be iterated over. At the moment, they're only recognised by foreach. --- cmdlang/builtins.go | 6 +++-- cmdlang/objs.go | 49 +++++++++++++++++++++++++++++++++++-- cmdlang/userbuiltin_test.go | 29 ++++++++++++++++++++++ 3 files changed, 80 insertions(+), 4 deletions(-) diff --git a/cmdlang/builtins.go b/cmdlang/builtins.go index 729f648..9399feb 100644 --- a/cmdlang/builtins.go +++ b/cmdlang/builtins.go @@ -252,8 +252,10 @@ func foreachBuiltin(ctx context.Context, args macroArgs) (object, error) { var last object switch t := items.(type) { - case listObject: - for _, v := range t { + case listable: + l := t.Len() + for i := 0; i < l; i++ { + v := t.Index(i) last, err = args.evalBlock(ctx, 1, []object{v}, true) // TO INCLUDE: the index if err != nil { return nil, err diff --git a/cmdlang/objs.go b/cmdlang/objs.go index e1c340d..c582d2c 100644 --- a/cmdlang/objs.go +++ b/cmdlang/objs.go @@ -4,6 +4,7 @@ import ( "context" "errors" "fmt" + "reflect" "strconv" ) @@ -12,6 +13,11 @@ type object interface { Truthy() bool } +type listable interface { + Len() int + Index(i int) object +} + type listObject []object func (lo *listObject) Append(o object) { @@ -26,6 +32,14 @@ func (s listObject) Truthy() bool { return len(s) > 0 } +func (s listObject) Len() int { + return len(s) +} + +func (s listObject) Index(i int) object { + return s[i] +} + type hashObject map[string]object func (s hashObject) String() string { @@ -87,6 +101,8 @@ func toGoValue(obj object) (interface{}, bool) { return xs, true case proxyObject: return v.p, true + case listableProxyObject: + return v.v.Interface(), true } return nil, false @@ -98,9 +114,14 @@ func fromGoValue(v any) (object, error) { return nil, nil case string: return strObject(t), nil - default: - return proxyObject{t}, nil } + + resVal := reflect.ValueOf(v) + if resVal.Type().Kind() == reflect.Slice { + return listableProxyObject{resVal}, nil + } + + return proxyObject{v}, nil } type macroArgs struct { @@ -309,3 +330,27 @@ func (p proxyObject) Truthy() bool { //TODO implement me panic("implement me") } + +type listableProxyObject struct { + v reflect.Value +} + +func (p listableProxyObject) String() string { + return fmt.Sprintf("listableProxyObject{%v}", p.v.Type()) +} + +func (p listableProxyObject) Truthy() bool { + panic("implement me") +} + +func (p listableProxyObject) Len() int { + return p.v.Len() +} + +func (p listableProxyObject) Index(i int) object { + e, err := fromGoValue(p.v.Index(i).Interface()) + if err != nil { + return nil + } + return e +} diff --git a/cmdlang/userbuiltin_test.go b/cmdlang/userbuiltin_test.go index 5c93e1a..bb6cbee 100644 --- a/cmdlang/userbuiltin_test.go +++ b/cmdlang/userbuiltin_test.go @@ -1,6 +1,7 @@ package cmdlang_test import ( + "bytes" "context" "strings" "testing" @@ -132,6 +133,34 @@ func TestInst_SetBuiltin(t *testing.T) { }) } }) + + t.Run("slices returned by commands treated as lists", func(t *testing.T) { + tests := []struct { + descr string + expr string + want any + wantOut string + }{ + {descr: "return as is", expr: `countTo3`, want: []string{"1", "2", "3"}}, + {descr: "iterate over", expr: `foreach (countTo3) { |x| echo $x }`, wantOut: "1\n2\n3\n"}, + } + + for _, tt := range tests { + t.Run(tt.descr, func(t *testing.T) { + outW := bytes.NewBuffer(nil) + inst := cmdlang.New(cmdlang.WithOut(outW)) + + inst.SetBuiltin("countTo3", func(ctx context.Context, args cmdlang.CallArgs) (any, error) { + return []string{"1", "2", "3"}, nil + }) + + res, err := inst.Eval(context.Background(), tt.expr) + assert.NoError(t, err) + assert.Equal(t, tt.want, res) + assert.Equal(t, tt.wantOut, outW.String()) + }) + } + }) } func TestCallArgs_Bind(t *testing.T) {