Added switch options
This commit is contained in:
parent
478ba09ad5
commit
5265efedc4
|
@ -4,7 +4,6 @@ import (
|
|||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/lmika/gopkgs/fp/slices"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
@ -86,14 +85,32 @@ func (e evaluator) evalCmd(ctx context.Context, ec *evalCtx, currentStream strea
|
|||
}
|
||||
|
||||
func (e evaluator) evalInvokable(ctx context.Context, ec *evalCtx, currentStream stream, ast *astCmd, cmd invokable) (object, error) {
|
||||
args, err := slices.MapWithError(ast.Args, func(a astCmdArg) (object, error) {
|
||||
return e.evalArg(ctx, ec, a)
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
var (
|
||||
pargs listObject
|
||||
kwargs map[string]*listObject
|
||||
argsPtr *listObject
|
||||
)
|
||||
|
||||
argsPtr = &pargs
|
||||
for _, arg := range ast.Args {
|
||||
if ident := arg.Ident; ident != nil && (*ident)[0] == '-' {
|
||||
// Arg switch
|
||||
if kwargs == nil {
|
||||
kwargs = make(map[string]*listObject)
|
||||
}
|
||||
|
||||
argsPtr = &listObject{}
|
||||
kwargs[(*ident)[1:]] = argsPtr
|
||||
} else {
|
||||
ae, err := e.evalArg(ctx, ec, arg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
argsPtr.Append(ae)
|
||||
}
|
||||
}
|
||||
|
||||
invArgs := invocationArgs{ec: ec, inst: e.inst, args: args, currentStream: currentStream}
|
||||
invArgs := invocationArgs{ec: ec, inst: e.inst, args: pargs, kwargs: kwargs, currentStream: currentStream}
|
||||
|
||||
if currentStream != nil {
|
||||
if si, ok := cmd.(streamInvokable); ok {
|
||||
|
|
|
@ -14,6 +14,10 @@ type object interface {
|
|||
|
||||
type listObject []object
|
||||
|
||||
func (lo *listObject) Append(o object) {
|
||||
*lo = append(*lo, o)
|
||||
}
|
||||
|
||||
func (s listObject) String() string {
|
||||
return fmt.Sprintf("%v", []object(s))
|
||||
}
|
||||
|
@ -152,6 +156,7 @@ type invocationArgs struct {
|
|||
ec *evalCtx
|
||||
currentStream stream
|
||||
args []object
|
||||
kwargs map[string]*listObject
|
||||
}
|
||||
|
||||
func (ia invocationArgs) expectArgn(x int) error {
|
||||
|
|
|
@ -16,32 +16,35 @@ func (ca CallArgs) Bind(vars ...interface{}) error {
|
|||
}
|
||||
|
||||
for i, v := range vars {
|
||||
switch t := v.(type) {
|
||||
case *string:
|
||||
tv, err := ca.args.stringArg(i)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
*t = tv
|
||||
}
|
||||
|
||||
// Check for proxy object
|
||||
if po, ok := ca.args.args[i].(proxyObject); ok {
|
||||
poValue := reflect.ValueOf(po.p)
|
||||
argValue := reflect.ValueOf(v)
|
||||
|
||||
if argValue.Type().Kind() != reflect.Pointer {
|
||||
continue
|
||||
} else if !poValue.Type().AssignableTo(argValue.Elem().Type()) {
|
||||
continue
|
||||
}
|
||||
|
||||
argValue.Elem().Set(poValue)
|
||||
if err := bindArg(v, ca.args.args[i]); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ca CallArgs) HasSwitch(name string) bool {
|
||||
if ca.args.kwargs == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
_, ok := ca.args.kwargs[name]
|
||||
return ok
|
||||
}
|
||||
|
||||
func (ca CallArgs) BindSwitch(name string, val interface{}) error {
|
||||
if ca.args.kwargs == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
vars, ok := ca.args.kwargs[name]
|
||||
if !ok || len(*vars) != 1 {
|
||||
return nil
|
||||
}
|
||||
|
||||
return bindArg(val, (*vars)[0])
|
||||
}
|
||||
|
||||
func (inst *Inst) SetBuiltin(name string, fn func(ctx context.Context, args CallArgs) (any, error)) {
|
||||
inst.rootEC.addCmd(name, userBuiltin{fn: fn})
|
||||
}
|
||||
|
@ -58,3 +61,25 @@ func (u userBuiltin) invoke(ctx context.Context, args invocationArgs) (object, e
|
|||
|
||||
return fromGoValue(v)
|
||||
}
|
||||
|
||||
func bindArg(v interface{}, arg object) error {
|
||||
switch t := v.(type) {
|
||||
case *string:
|
||||
*t = arg.String()
|
||||
}
|
||||
|
||||
// Check for proxy object
|
||||
if po, ok := arg.(proxyObject); ok {
|
||||
poValue := reflect.ValueOf(po.p)
|
||||
argValue := reflect.ValueOf(v)
|
||||
|
||||
if argValue.Type().Kind() != reflect.Pointer {
|
||||
return nil
|
||||
} else if !poValue.Type().AssignableTo(argValue.Elem().Type()) {
|
||||
return nil
|
||||
}
|
||||
|
||||
argValue.Elem().Set(poValue)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -2,6 +2,7 @@ package cmdlang_test
|
|||
|
||||
import (
|
||||
"context"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/lmika/cmdlang-proto/cmdlang"
|
||||
|
@ -26,6 +27,48 @@ func TestInst_SetBuiltin(t *testing.T) {
|
|||
assert.Equal(t, "Hello, World", res)
|
||||
})
|
||||
|
||||
t.Run("simple builtin with optional switches and strings", func(t *testing.T) {
|
||||
inst := cmdlang.New()
|
||||
inst.SetBuiltin("add2", func(ctx context.Context, args cmdlang.CallArgs) (any, error) {
|
||||
var x, y, sep string
|
||||
|
||||
if err := args.BindSwitch("sep", &sep); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := args.BindSwitch("left", &x); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := args.BindSwitch("right", &y); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
v := x + sep + y
|
||||
if args.HasSwitch("upcase") {
|
||||
v = strings.ToUpper(v)
|
||||
}
|
||||
|
||||
return v, nil
|
||||
})
|
||||
|
||||
tests := []struct {
|
||||
descr string
|
||||
expr string
|
||||
want string
|
||||
}{
|
||||
{"plain eval", `add2 -sep ", " -right "world" -left "Hello"`, "Hello, world"},
|
||||
{"swap 1", `add2 -right "right" -left "left" -sep ":"`, "left:right"},
|
||||
{"swap 2", `add2 -left "left" -sep ":" -right "right" -upcase`, "LEFT:RIGHT"},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.descr, func(t *testing.T) {
|
||||
res, err := inst.Eval(context.Background(), tt.expr)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, tt.want, res)
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("builtin return proxy object", func(t *testing.T) {
|
||||
type pair struct {
|
||||
x, y string
|
||||
|
|
Loading…
Reference in a new issue