Added a few things needed by Dynamo Browse
- Added a NArg returning the total number of arguments - Added a "missing command" handler - Added a utility writer which will print out lines as they're collected
This commit is contained in:
parent
57781b5e3b
commit
25594c80d2
|
@ -3,7 +3,6 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"errors"
|
||||
"github.com/alecthomas/participle/v2"
|
||||
|
@ -21,12 +20,9 @@ func invokeUCLCallback(name string, args ...any) {
|
|||
func initJS(ctx context.Context) {
|
||||
uclObj := make(map[string]any)
|
||||
|
||||
inst := ucl.New(ucl.WithOut(&uclOut{
|
||||
lineBuffer: new(bytes.Buffer),
|
||||
writeLine: func(line string) {
|
||||
invokeUCLCallback("onOutLine", line)
|
||||
},
|
||||
}))
|
||||
inst := ucl.New(ucl.WithOut(ucl.LineHandler(func(line string) {
|
||||
invokeUCLCallback("onOutLine", line)
|
||||
})))
|
||||
|
||||
uclObj["eval"] = js.FuncOf(func(this js.Value, args []js.Value) any {
|
||||
if len(args) != 2 {
|
||||
|
@ -55,19 +51,20 @@ func initJS(ctx context.Context) {
|
|||
js.Global().Set("ucl", uclObj)
|
||||
}
|
||||
|
||||
type uclOut struct {
|
||||
lineBuffer *bytes.Buffer
|
||||
writeLine func(line string)
|
||||
}
|
||||
|
||||
func (uo *uclOut) Write(p []byte) (n int, err error) {
|
||||
for _, b := range p {
|
||||
if b == '\n' {
|
||||
uo.writeLine(uo.lineBuffer.String())
|
||||
uo.lineBuffer.Reset()
|
||||
} else {
|
||||
uo.lineBuffer.WriteByte(b)
|
||||
}
|
||||
}
|
||||
return len(p), nil
|
||||
}
|
||||
//
|
||||
//type uclOut struct {
|
||||
// lineBuffer *bytes.Buffer
|
||||
// writeLine func(line string)
|
||||
//}
|
||||
//
|
||||
//func (uo *uclOut) Write(p []byte) (n int, err error) {
|
||||
// for _, b := range p {
|
||||
// if b == '\n' {
|
||||
// uo.writeLine(uo.lineBuffer.String())
|
||||
// uo.lineBuffer.Reset()
|
||||
// } else {
|
||||
// uo.lineBuffer.WriteByte(b)
|
||||
// }
|
||||
// }
|
||||
// return len(p), nil
|
||||
//}
|
||||
|
|
|
@ -79,6 +79,8 @@ func (e evaluator) evalCmd(ctx context.Context, ec *evalCtx, currentPipe object,
|
|||
return e.evalInvokable(ctx, ec, currentPipe, ast, cmd)
|
||||
} else if macro := ec.lookupMacro(name); macro != nil {
|
||||
return e.evalMacro(ctx, ec, currentPipe != nil, currentPipe, ast, macro)
|
||||
} else if missingHandler := e.inst.missingBuiltinHandler; missingHandler != nil {
|
||||
return e.evalInvokable(ctx, ec, currentPipe, ast, e.inst.missingHandlerInvokable(name))
|
||||
} else {
|
||||
return nil, errors.New("unknown command: " + name)
|
||||
}
|
||||
|
|
|
@ -9,7 +9,8 @@ import (
|
|||
)
|
||||
|
||||
type Inst struct {
|
||||
out io.Writer
|
||||
out io.Writer
|
||||
missingBuiltinHandler MissingBuiltinHandler
|
||||
|
||||
rootEC *evalCtx
|
||||
}
|
||||
|
@ -22,6 +23,12 @@ func WithOut(out io.Writer) InstOption {
|
|||
}
|
||||
}
|
||||
|
||||
func WithMissingBuiltinHandler(handler MissingBuiltinHandler) InstOption {
|
||||
return func(i *Inst) {
|
||||
i.missingBuiltinHandler = handler
|
||||
}
|
||||
}
|
||||
|
||||
func New(opts ...InstOption) *Inst {
|
||||
rootEC := &evalCtx{}
|
||||
rootEC.root = rootEC
|
||||
|
|
30
ucl/io.go
Normal file
30
ucl/io.go
Normal file
|
@ -0,0 +1,30 @@
|
|||
package ucl
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io"
|
||||
)
|
||||
|
||||
func LineHandler(line func(string)) io.Writer {
|
||||
return &lineHandlerWriter{
|
||||
lineBuffer: new(bytes.Buffer),
|
||||
writeLine: line,
|
||||
}
|
||||
}
|
||||
|
||||
type lineHandlerWriter struct {
|
||||
lineBuffer *bytes.Buffer
|
||||
writeLine func(line string)
|
||||
}
|
||||
|
||||
func (uo *lineHandlerWriter) Write(p []byte) (n int, err error) {
|
||||
for _, b := range p {
|
||||
if b == '\n' {
|
||||
uo.writeLine(uo.lineBuffer.String())
|
||||
uo.lineBuffer.Reset()
|
||||
} else {
|
||||
uo.lineBuffer.WriteByte(b)
|
||||
}
|
||||
}
|
||||
return len(p), nil
|
||||
}
|
|
@ -6,10 +6,18 @@ import (
|
|||
"reflect"
|
||||
)
|
||||
|
||||
type BuiltinHandler func(ctx context.Context, args CallArgs) (any, error)
|
||||
|
||||
type MissingBuiltinHandler func(ctx context.Context, name string, args CallArgs) (any, error)
|
||||
|
||||
type CallArgs struct {
|
||||
args invocationArgs
|
||||
}
|
||||
|
||||
func (ca *CallArgs) NArgs() int {
|
||||
return len(ca.args.args)
|
||||
}
|
||||
|
||||
func (ca *CallArgs) Bind(vars ...interface{}) error {
|
||||
if len(ca.args.args) < len(vars) {
|
||||
return errors.New("wrong number of arguments")
|
||||
|
@ -67,7 +75,7 @@ func (ca CallArgs) BindSwitch(name string, val interface{}) error {
|
|||
return bindArg(val, (*vars)[0])
|
||||
}
|
||||
|
||||
func (inst *Inst) SetBuiltin(name string, fn func(ctx context.Context, args CallArgs) (any, error)) {
|
||||
func (inst *Inst) SetBuiltin(name string, fn BuiltinHandler) {
|
||||
inst.rootEC.addCmd(name, userBuiltin{fn: fn})
|
||||
}
|
||||
|
||||
|
@ -166,3 +174,21 @@ func canBindProxyObject(v interface{}, r reflect.Value) bool {
|
|||
r = r.Elem()
|
||||
}
|
||||
}
|
||||
|
||||
func (inst *Inst) missingHandlerInvokable(name string) missingHandlerInvokable {
|
||||
return missingHandlerInvokable{name: name, handler: inst.missingBuiltinHandler}
|
||||
}
|
||||
|
||||
type missingHandlerInvokable struct {
|
||||
name string
|
||||
handler MissingBuiltinHandler
|
||||
}
|
||||
|
||||
func (m missingHandlerInvokable) invoke(ctx context.Context, args invocationArgs) (object, error) {
|
||||
v, err := m.handler(ctx, m.name, CallArgs{args: args})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return fromGoValue(v)
|
||||
}
|
||||
|
|
|
@ -3,6 +3,7 @@ package ucl_test
|
|||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
"testing"
|
||||
"ucl.lmika.dev/ucl"
|
||||
|
@ -184,90 +185,117 @@ func TestInst_SetBuiltin(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestCallArgs_Bind(t *testing.T) {
|
||||
t.Run("bind to an interface", func(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
ctx := context.Background()
|
||||
|
||||
inst := ucl.New()
|
||||
inst.SetBuiltin("sa", func(ctx context.Context, args ucl.CallArgs) (any, error) {
|
||||
return doStringA{this: "a val"}, nil
|
||||
})
|
||||
inst.SetBuiltin("sb", func(ctx context.Context, args ucl.CallArgs) (any, error) {
|
||||
return doStringB{left: "foo", right: "bar"}, nil
|
||||
})
|
||||
inst.SetBuiltin("dostr", func(ctx context.Context, args ucl.CallArgs) (any, error) {
|
||||
var ds doStringable
|
||||
|
||||
if err := args.Bind(&ds); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return ds.DoString(), nil
|
||||
})
|
||||
|
||||
va, err := inst.Eval(ctx, `dostr (sa)`)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, "do string A: a val", va)
|
||||
|
||||
vb, err := inst.Eval(ctx, `dostr (sb)`)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, "do string B: foo bar", vb)
|
||||
inst := ucl.New()
|
||||
inst.SetBuiltin("sa", func(ctx context.Context, args ucl.CallArgs) (any, error) {
|
||||
return doStringA{this: "a val"}, nil
|
||||
})
|
||||
inst.SetBuiltin("sb", func(ctx context.Context, args ucl.CallArgs) (any, error) {
|
||||
return doStringB{left: "foo", right: "bar"}, nil
|
||||
})
|
||||
inst.SetBuiltin("dostr", func(ctx context.Context, args ucl.CallArgs) (any, error) {
|
||||
var ds doStringable
|
||||
|
||||
if err := args.Bind(&ds); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return ds.DoString(), nil
|
||||
})
|
||||
|
||||
va, err := inst.Eval(ctx, `dostr (sa)`)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, "do string A: a val", va)
|
||||
|
||||
vb, err := inst.Eval(ctx, `dostr (sb)`)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, "do string B: foo bar", vb)
|
||||
}
|
||||
|
||||
func TestCallArgs_CanBind(t *testing.T) {
|
||||
t.Run("returns ture of all passed in arguments can be bound without consuming them", func(t *testing.T) {
|
||||
tests := []struct {
|
||||
descr string
|
||||
eval string
|
||||
want []string
|
||||
}{
|
||||
{descr: "bind nothing", eval: `test`, want: []string{}},
|
||||
{descr: "bind one", eval: `test "yes"`, want: []string{"str"}},
|
||||
{descr: "bind two", eval: `test "yes" 213`, want: []string{"str", "int"}},
|
||||
{descr: "bind three", eval: `test "yes" 213 (proxy)`, want: []string{"all", "str", "int", "proxy"}},
|
||||
}
|
||||
tests := []struct {
|
||||
descr string
|
||||
eval string
|
||||
want []string
|
||||
}{
|
||||
{descr: "bind nothing", eval: `test`, want: []string{}},
|
||||
{descr: "bind one", eval: `test "yes"`, want: []string{"str"}},
|
||||
{descr: "bind two", eval: `test "yes" 213`, want: []string{"str", "int"}},
|
||||
{descr: "bind three", eval: `test "yes" 213 (proxy)`, want: []string{"all", "str", "int", "proxy"}},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.descr, func(t *testing.T) {
|
||||
type proxyObj struct{}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.descr, func(t *testing.T) {
|
||||
type proxyObj struct{}
|
||||
|
||||
ctx := context.Background()
|
||||
res := make([]string, 0)
|
||||
ctx := context.Background()
|
||||
res := make([]string, 0)
|
||||
|
||||
inst := ucl.New()
|
||||
inst.SetBuiltin("proxy", func(ctx context.Context, args ucl.CallArgs) (any, error) {
|
||||
return proxyObj{}, nil
|
||||
})
|
||||
inst.SetBuiltin("test", func(ctx context.Context, args ucl.CallArgs) (any, error) {
|
||||
var (
|
||||
s string
|
||||
i int
|
||||
p proxyObj
|
||||
)
|
||||
|
||||
if args.CanBind(&s, &i, &p) {
|
||||
res = append(res, "all")
|
||||
}
|
||||
if args.CanBind(&s) {
|
||||
res = append(res, "str")
|
||||
}
|
||||
args.Shift(1)
|
||||
if args.CanBind(&i) {
|
||||
res = append(res, "int")
|
||||
}
|
||||
args.Shift(1)
|
||||
if args.CanBind(&p) {
|
||||
res = append(res, "proxy")
|
||||
}
|
||||
return nil, nil
|
||||
})
|
||||
|
||||
_, err := inst.Eval(ctx, tt.eval)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, tt.want, res)
|
||||
inst := ucl.New()
|
||||
inst.SetBuiltin("proxy", func(ctx context.Context, args ucl.CallArgs) (any, error) {
|
||||
return proxyObj{}, nil
|
||||
})
|
||||
}
|
||||
})
|
||||
inst.SetBuiltin("test", func(ctx context.Context, args ucl.CallArgs) (any, error) {
|
||||
var (
|
||||
s string
|
||||
i int
|
||||
p proxyObj
|
||||
)
|
||||
|
||||
if args.CanBind(&s, &i, &p) {
|
||||
res = append(res, "all")
|
||||
}
|
||||
if args.CanBind(&s) {
|
||||
res = append(res, "str")
|
||||
}
|
||||
args.Shift(1)
|
||||
if args.CanBind(&i) {
|
||||
res = append(res, "int")
|
||||
}
|
||||
args.Shift(1)
|
||||
if args.CanBind(&p) {
|
||||
res = append(res, "proxy")
|
||||
}
|
||||
return nil, nil
|
||||
})
|
||||
|
||||
_, err := inst.Eval(ctx, tt.eval)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, tt.want, res)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestCallArgs_MissingCommandHandler(t *testing.T) {
|
||||
tests := []struct {
|
||||
descr string
|
||||
eval string
|
||||
want string
|
||||
}{
|
||||
{descr: "alpha", eval: `alpha`, want: "was alpha"},
|
||||
{descr: "bravo", eval: `bravo "this"`, want: "was bravo: this"},
|
||||
{descr: "charlie", eval: `charlie`, want: "was charlie"},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.descr, func(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
|
||||
inst := ucl.New(
|
||||
ucl.WithMissingBuiltinHandler(func(ctx context.Context, name string, args ucl.CallArgs) (any, error) {
|
||||
var msg string
|
||||
if err := args.Bind(&msg); err == nil {
|
||||
return fmt.Sprintf("was %v: %v", name, msg), nil
|
||||
}
|
||||
return fmt.Sprintf("was %v", name), nil
|
||||
}))
|
||||
|
||||
res, err := inst.Eval(ctx, tt.eval)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, tt.want, res)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestCallArgs_IsTopLevel(t *testing.T) {
|
||||
|
|
Loading…
Reference in a new issue