Added a log builtin
This commit is contained in:
parent
23f0f16d26
commit
485e839ae3
|
@ -20,6 +20,8 @@ func main() {
|
||||||
instRepl := repl.New(
|
instRepl := repl.New(
|
||||||
ucl.WithModule(builtins.OS()),
|
ucl.WithModule(builtins.OS()),
|
||||||
ucl.WithModule(builtins.FS(nil)),
|
ucl.WithModule(builtins.FS(nil)),
|
||||||
|
ucl.WithModule(builtins.Log(nil)),
|
||||||
|
ucl.WithModule(builtins.Strs()),
|
||||||
)
|
)
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
|
|
||||||
|
|
|
@ -8,6 +8,7 @@ import (
|
||||||
"github.com/alecthomas/participle/v2"
|
"github.com/alecthomas/participle/v2"
|
||||||
"strings"
|
"strings"
|
||||||
"syscall/js"
|
"syscall/js"
|
||||||
|
"ucl.lmika.dev/repl"
|
||||||
"ucl.lmika.dev/ucl"
|
"ucl.lmika.dev/ucl"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -20,9 +21,12 @@ 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(ucl.LineHandler(func(line string) {
|
replInst := repl.New(
|
||||||
invokeUCLCallback("onOutLine", line)
|
ucl.WithModule(builtins.Log(nil)),
|
||||||
})))
|
ucl.WithModule(builtins.Strs()),
|
||||||
|
ucl.WithOut(ucl.LineHandler(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 {
|
||||||
|
@ -36,7 +40,7 @@ func initJS(ctx context.Context) {
|
||||||
}
|
}
|
||||||
|
|
||||||
wantContinue := args[1].Bool()
|
wantContinue := args[1].Bool()
|
||||||
if err := ucl.EvalAndDisplay(ctx, inst, cmdLine); err != nil {
|
if err := replInst.EvalAndDisplay(ctx, cmdLine); err != nil {
|
||||||
var p participle.Error
|
var p participle.Error
|
||||||
if errors.As(err, &p) && wantContinue {
|
if errors.As(err, &p) && wantContinue {
|
||||||
invokeUCLCallback("onContinue")
|
invokeUCLCallback("onContinue")
|
||||||
|
@ -50,21 +54,3 @@ func initJS(ctx context.Context) {
|
||||||
})
|
})
|
||||||
js.Global().Set("ucl", uclObj)
|
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
|
|
||||||
//}
|
|
||||||
|
|
44
ucl/builtins/log.go
Normal file
44
ucl/builtins/log.go
Normal file
|
@ -0,0 +1,44 @@
|
||||||
|
package builtins
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"log"
|
||||||
|
"strings"
|
||||||
|
"ucl.lmika.dev/ucl"
|
||||||
|
)
|
||||||
|
|
||||||
|
type LogMessageHandler func(s string) error
|
||||||
|
|
||||||
|
func Log(handler LogMessageHandler) ucl.Module {
|
||||||
|
if handler == nil {
|
||||||
|
handler = func(s string) error {
|
||||||
|
log.Println(s)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
lh := logHandler{handler: handler}
|
||||||
|
|
||||||
|
return ucl.Module{
|
||||||
|
Name: "log",
|
||||||
|
Builtins: map[string]ucl.BuiltinHandler{
|
||||||
|
"puts": lh.logMessage,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type logHandler struct {
|
||||||
|
handler LogMessageHandler
|
||||||
|
}
|
||||||
|
|
||||||
|
func (lh logHandler) logMessage(ctx context.Context, args ucl.CallArgs) (any, error) {
|
||||||
|
var sb strings.Builder
|
||||||
|
var s ucl.Object
|
||||||
|
|
||||||
|
for args.NArgs() > 0 {
|
||||||
|
if err := args.Bind(&s); err == nil {
|
||||||
|
sb.WriteString(s.String())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil, lh.handler(sb.String())
|
||||||
|
}
|
47
ucl/builtins/log_test.go
Normal file
47
ucl/builtins/log_test.go
Normal file
|
@ -0,0 +1,47 @@
|
||||||
|
package builtins_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"context"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"testing"
|
||||||
|
"ucl.lmika.dev/ucl"
|
||||||
|
"ucl.lmika.dev/ucl/builtins"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestLog_Puts(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
desc string
|
||||||
|
eval string
|
||||||
|
want any
|
||||||
|
wantErr bool
|
||||||
|
wantLog string
|
||||||
|
}{
|
||||||
|
{desc: "log 1", eval: `log:puts "bad stuff here"`, wantLog: "bad stuff here\n"},
|
||||||
|
{desc: "log 2", eval: `log:puts "bad " 123 " here " [1 2 3]`, wantLog: "bad 123 here [1 2 3]\n"},
|
||||||
|
{desc: "log 3", eval: `log:puts`, wantLog: "\n"},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.desc, func(t *testing.T) {
|
||||||
|
var logMessage bytes.Buffer
|
||||||
|
|
||||||
|
inst := ucl.New(
|
||||||
|
ucl.WithModule(builtins.Log(func(s string) error {
|
||||||
|
logMessage.WriteString(s)
|
||||||
|
logMessage.WriteByte('\n')
|
||||||
|
return nil
|
||||||
|
})),
|
||||||
|
)
|
||||||
|
|
||||||
|
res, err := inst.Eval(context.Background(), tt.eval)
|
||||||
|
if tt.wantErr {
|
||||||
|
assert.Error(t, err)
|
||||||
|
} else {
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, tt.want, res)
|
||||||
|
}
|
||||||
|
assert.Equal(t, tt.wantLog, logMessage.String())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
|
@ -94,20 +94,44 @@ func (u userBuiltin) invoke(ctx context.Context, args invocationArgs) (object, e
|
||||||
|
|
||||||
func bindArg(v interface{}, arg object) error {
|
func bindArg(v interface{}, arg object) error {
|
||||||
switch t := v.(type) {
|
switch t := v.(type) {
|
||||||
|
case *Object:
|
||||||
|
*t = arg
|
||||||
|
return nil
|
||||||
case *interface{}:
|
case *interface{}:
|
||||||
*t, _ = toGoValue(arg)
|
*t, _ = toGoValue(arg)
|
||||||
|
case *Invokable:
|
||||||
|
i, ok := arg.(invokable)
|
||||||
|
if !ok {
|
||||||
|
return errors.New("exepected invokable")
|
||||||
|
}
|
||||||
|
*t = Invokable{
|
||||||
|
inv: i,
|
||||||
|
eval: ca.args.eval,
|
||||||
|
inst: ca.args.inst,
|
||||||
|
ec: ca.args.ec,
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
case *Listable:
|
||||||
|
i, ok := arg.(Listable)
|
||||||
|
if !ok {
|
||||||
|
return errors.New("exepected listable")
|
||||||
|
}
|
||||||
|
*t = i
|
||||||
|
return nil
|
||||||
case *string:
|
case *string:
|
||||||
if arg != nil {
|
if arg != nil {
|
||||||
*t = arg.String()
|
*t = arg.String()
|
||||||
} else {
|
} else {
|
||||||
*t = ""
|
*t = ""
|
||||||
}
|
}
|
||||||
|
return nil
|
||||||
case *int:
|
case *int:
|
||||||
if iArg, ok := arg.(intObject); ok {
|
if iArg, ok := arg.(intObject); ok {
|
||||||
*t = int(iArg)
|
*t = int(iArg)
|
||||||
} else {
|
} else {
|
||||||
return errors.New("invalid arg")
|
return errors.New("invalid arg")
|
||||||
}
|
}
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
switch t := arg.(type) {
|
switch t := arg.(type) {
|
||||||
|
@ -198,3 +222,45 @@ func (m missingHandlerInvokable) invoke(ctx context.Context, args invocationArgs
|
||||||
|
|
||||||
return fromGoValue(v)
|
return fromGoValue(v)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type Invokable struct {
|
||||||
|
inv invokable
|
||||||
|
eval evaluator
|
||||||
|
inst *Inst
|
||||||
|
ec *evalCtx
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i Invokable) IsNil() bool {
|
||||||
|
return i.inv == nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i Invokable) Invoke(ctx context.Context, args ...any) (any, error) {
|
||||||
|
if i.inv == nil {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var err error
|
||||||
|
invArgs := invocationArgs{
|
||||||
|
eval: i.eval,
|
||||||
|
ec: i.ec,
|
||||||
|
inst: i.inst,
|
||||||
|
}
|
||||||
|
|
||||||
|
invArgs.args, err = slices.MapWithError(args, func(a any) (Object, error) {
|
||||||
|
return fromGoValue(a)
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
res, err := i.inv.invoke(ctx, invArgs)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
goRes, ok := toGoValue(res)
|
||||||
|
if !ok {
|
||||||
|
return nil, errors.New("cannot convert result to Go Value")
|
||||||
|
}
|
||||||
|
return goRes, err
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in a new issue