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
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"github.com/alecthomas/participle/v2"
|
"github.com/alecthomas/participle/v2"
|
||||||
|
@ -21,12 +20,9 @@ func invokeUCLCallback(name string, args ...any) {
|
||||||
func initJS(ctx context.Context) {
|
func initJS(ctx context.Context) {
|
||||||
uclObj := make(map[string]any)
|
uclObj := make(map[string]any)
|
||||||
|
|
||||||
inst := ucl.New(ucl.WithOut(&uclOut{
|
inst := ucl.New(ucl.WithOut(ucl.LineHandler(func(line string) {
|
||||||
lineBuffer: new(bytes.Buffer),
|
invokeUCLCallback("onOutLine", line)
|
||||||
writeLine: func(line string) {
|
})))
|
||||||
invokeUCLCallback("onOutLine", line)
|
|
||||||
},
|
|
||||||
}))
|
|
||||||
|
|
||||||
uclObj["eval"] = js.FuncOf(func(this js.Value, args []js.Value) any {
|
uclObj["eval"] = js.FuncOf(func(this js.Value, args []js.Value) any {
|
||||||
if len(args) != 2 {
|
if len(args) != 2 {
|
||||||
|
@ -55,19 +51,20 @@ func initJS(ctx context.Context) {
|
||||||
js.Global().Set("ucl", uclObj)
|
js.Global().Set("ucl", uclObj)
|
||||||
}
|
}
|
||||||
|
|
||||||
type uclOut struct {
|
//
|
||||||
lineBuffer *bytes.Buffer
|
//type uclOut struct {
|
||||||
writeLine func(line string)
|
// lineBuffer *bytes.Buffer
|
||||||
}
|
// writeLine func(line string)
|
||||||
|
//}
|
||||||
func (uo *uclOut) Write(p []byte) (n int, err error) {
|
//
|
||||||
for _, b := range p {
|
//func (uo *uclOut) Write(p []byte) (n int, err error) {
|
||||||
if b == '\n' {
|
// for _, b := range p {
|
||||||
uo.writeLine(uo.lineBuffer.String())
|
// if b == '\n' {
|
||||||
uo.lineBuffer.Reset()
|
// uo.writeLine(uo.lineBuffer.String())
|
||||||
} else {
|
// uo.lineBuffer.Reset()
|
||||||
uo.lineBuffer.WriteByte(b)
|
// } else {
|
||||||
}
|
// uo.lineBuffer.WriteByte(b)
|
||||||
}
|
// }
|
||||||
return len(p), nil
|
// }
|
||||||
}
|
// 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)
|
return e.evalInvokable(ctx, ec, currentPipe, ast, cmd)
|
||||||
} else if macro := ec.lookupMacro(name); macro != nil {
|
} else if macro := ec.lookupMacro(name); macro != nil {
|
||||||
return e.evalMacro(ctx, ec, currentPipe != nil, currentPipe, ast, macro)
|
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 {
|
} else {
|
||||||
return nil, errors.New("unknown command: " + name)
|
return nil, errors.New("unknown command: " + name)
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,7 +9,8 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
type Inst struct {
|
type Inst struct {
|
||||||
out io.Writer
|
out io.Writer
|
||||||
|
missingBuiltinHandler MissingBuiltinHandler
|
||||||
|
|
||||||
rootEC *evalCtx
|
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 {
|
func New(opts ...InstOption) *Inst {
|
||||||
rootEC := &evalCtx{}
|
rootEC := &evalCtx{}
|
||||||
rootEC.root = rootEC
|
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"
|
"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 {
|
type CallArgs struct {
|
||||||
args invocationArgs
|
args invocationArgs
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (ca *CallArgs) NArgs() int {
|
||||||
|
return len(ca.args.args)
|
||||||
|
}
|
||||||
|
|
||||||
func (ca *CallArgs) Bind(vars ...interface{}) error {
|
func (ca *CallArgs) Bind(vars ...interface{}) error {
|
||||||
if len(ca.args.args) < len(vars) {
|
if len(ca.args.args) < len(vars) {
|
||||||
return errors.New("wrong number of arguments")
|
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])
|
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})
|
inst.rootEC.addCmd(name, userBuiltin{fn: fn})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -166,3 +174,21 @@ func canBindProxyObject(v interface{}, r reflect.Value) bool {
|
||||||
r = r.Elem()
|
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 (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
"ucl.lmika.dev/ucl"
|
"ucl.lmika.dev/ucl"
|
||||||
|
@ -184,90 +185,117 @@ func TestInst_SetBuiltin(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestCallArgs_Bind(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 := ucl.New()
|
||||||
inst.SetBuiltin("sa", func(ctx context.Context, args ucl.CallArgs) (any, error) {
|
inst.SetBuiltin("sa", func(ctx context.Context, args ucl.CallArgs) (any, error) {
|
||||||
return doStringA{this: "a val"}, nil
|
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.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) {
|
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 {
|
||||||
tests := []struct {
|
descr string
|
||||||
descr string
|
eval string
|
||||||
eval string
|
want []string
|
||||||
want []string
|
}{
|
||||||
}{
|
{descr: "bind nothing", eval: `test`, want: []string{}},
|
||||||
{descr: "bind nothing", eval: `test`, want: []string{}},
|
{descr: "bind one", eval: `test "yes"`, want: []string{"str"}},
|
||||||
{descr: "bind one", eval: `test "yes"`, want: []string{"str"}},
|
{descr: "bind two", eval: `test "yes" 213`, want: []string{"str", "int"}},
|
||||||
{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"}},
|
||||||
{descr: "bind three", eval: `test "yes" 213 (proxy)`, want: []string{"all", "str", "int", "proxy"}},
|
}
|
||||||
}
|
|
||||||
|
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
t.Run(tt.descr, func(t *testing.T) {
|
t.Run(tt.descr, func(t *testing.T) {
|
||||||
type proxyObj struct{}
|
type proxyObj struct{}
|
||||||
|
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
res := make([]string, 0)
|
res := make([]string, 0)
|
||||||
|
|
||||||
inst := ucl.New()
|
inst := ucl.New()
|
||||||
inst.SetBuiltin("proxy", func(ctx context.Context, args ucl.CallArgs) (any, error) {
|
inst.SetBuiltin("proxy", func(ctx context.Context, args ucl.CallArgs) (any, error) {
|
||||||
return proxyObj{}, nil
|
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.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) {
|
func TestCallArgs_IsTopLevel(t *testing.T) {
|
||||||
|
|
Loading…
Reference in a new issue