Started working on builtins
This commit is contained in:
parent
cf3a12bf0d
commit
78720eeb5b
|
@ -5,6 +5,7 @@ import (
|
|||
"github.com/chzyer/readline"
|
||||
"log"
|
||||
"ucl.lmika.dev/ucl"
|
||||
"ucl.lmika.dev/ucl/builtins"
|
||||
)
|
||||
|
||||
func main() {
|
||||
|
@ -14,7 +15,10 @@ func main() {
|
|||
}
|
||||
defer rl.Close()
|
||||
|
||||
inst := ucl.New()
|
||||
inst := ucl.New(
|
||||
ucl.WithModule(builtins.OS()),
|
||||
ucl.WithModule(builtins.FS(nil)),
|
||||
)
|
||||
ctx := context.Background()
|
||||
|
||||
for {
|
||||
|
|
|
@ -83,7 +83,7 @@ var scanner = lexer.MustStateful(lexer.Rules{
|
|||
{"RC", `\}`, nil},
|
||||
{"NL", `[;\n][; \n\t]*`, nil},
|
||||
{"PIPE", `\|`, nil},
|
||||
{"Ident", `[-]*[a-zA-Z_][\w-]*`, nil},
|
||||
{"Ident", `[-]*[a-zA-Z_:][\w-:]*`, nil},
|
||||
},
|
||||
})
|
||||
var parser = participle.MustBuild[astScript](participle.Lexer(scanner),
|
||||
|
|
|
@ -0,0 +1,56 @@
|
|||
package builtins
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"context"
|
||||
"io/fs"
|
||||
"os"
|
||||
"ucl.lmika.dev/ucl"
|
||||
)
|
||||
|
||||
type fsHandlers struct {
|
||||
fs fs.FS
|
||||
}
|
||||
|
||||
func FS(fs fs.FS) ucl.Module {
|
||||
fsh := fsHandlers{fs: fs}
|
||||
|
||||
return ucl.Module{
|
||||
Name: "fs",
|
||||
Builtins: map[string]ucl.BuiltinHandler{
|
||||
"lines": fsh.lines,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (fh fsHandlers) openFile(name string) (fs.File, error) {
|
||||
if fh.fs == nil {
|
||||
return os.Open(name)
|
||||
}
|
||||
return fh.fs.Open(name)
|
||||
}
|
||||
|
||||
func (fh fsHandlers) lines(ctx context.Context, args ucl.CallArgs) (any, error) {
|
||||
var fname string
|
||||
if err := args.Bind(&fname); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
f, err := fh.openFile(fname)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
lines := make([]string, 0)
|
||||
scanner := bufio.NewScanner(f)
|
||||
for scanner.Scan() {
|
||||
lines = append(lines, scanner.Text())
|
||||
}
|
||||
|
||||
if err := scanner.Err(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return lines, nil
|
||||
}
|
|
@ -0,0 +1,37 @@
|
|||
package builtins_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"testing"
|
||||
"testing/fstest"
|
||||
"ucl.lmika.dev/ucl"
|
||||
"ucl.lmika.dev/ucl/builtins"
|
||||
)
|
||||
|
||||
var testFS = fstest.MapFS{
|
||||
"test.txt": &fstest.MapFile{
|
||||
Data: []byte("these\nare\nlines"),
|
||||
},
|
||||
}
|
||||
|
||||
func TestFS_Cat(t *testing.T) {
|
||||
tests := []struct {
|
||||
descr string
|
||||
eval string
|
||||
want any
|
||||
}{
|
||||
{descr: "read file", eval: `fs:lines "test.txt"`, want: []string{"these", "are", "lines"}},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.descr, func(t *testing.T) {
|
||||
inst := ucl.New(
|
||||
ucl.WithModule(builtins.FS(testFS)),
|
||||
)
|
||||
res, err := inst.Eval(context.Background(), tt.eval)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, tt.want, res)
|
||||
})
|
||||
}
|
||||
}
|
|
@ -0,0 +1,40 @@
|
|||
package builtins
|
||||
|
||||
import (
|
||||
"context"
|
||||
"os"
|
||||
"ucl.lmika.dev/ucl"
|
||||
)
|
||||
|
||||
type osHandlers struct {
|
||||
}
|
||||
|
||||
func OS() ucl.Module {
|
||||
osh := osHandlers{}
|
||||
|
||||
return ucl.Module{
|
||||
Name: "os",
|
||||
Builtins: map[string]ucl.BuiltinHandler{
|
||||
"env": osh.env,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (oh osHandlers) env(ctx context.Context, args ucl.CallArgs) (any, error) {
|
||||
var envName string
|
||||
if err := args.Bind(&envName); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
val, ok := os.LookupEnv(envName)
|
||||
if ok {
|
||||
return val, nil
|
||||
}
|
||||
|
||||
var defValue any
|
||||
if err := args.Bind(&defValue); err == nil {
|
||||
return defValue, nil
|
||||
}
|
||||
|
||||
return "", nil
|
||||
}
|
|
@ -0,0 +1,36 @@
|
|||
package builtins_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"testing"
|
||||
"ucl.lmika.dev/ucl"
|
||||
"ucl.lmika.dev/ucl/builtins"
|
||||
)
|
||||
|
||||
func TestOS_Env(t *testing.T) {
|
||||
tests := []struct {
|
||||
descr string
|
||||
eval string
|
||||
want any
|
||||
}{
|
||||
{descr: "env value", eval: `os:env "MY_ENV"`, want: "my env value"},
|
||||
{descr: "missing env value", eval: `os:env "MISSING_THING"`, want: ""},
|
||||
{descr: "default env value (str)", eval: `os:env "MISSING_THING" "my default"`, want: "my default"},
|
||||
{descr: "default env value (int)", eval: `os:env "MISSING_THING" 1352`, want: 1352},
|
||||
{descr: "default env value (nil)", eval: `os:env "MISSING_THING" ()`, want: nil},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.descr, func(t *testing.T) {
|
||||
t.Setenv("MY_ENV", "my env value")
|
||||
|
||||
inst := ucl.New(
|
||||
ucl.WithModule(builtins.OS()),
|
||||
)
|
||||
res, err := inst.Eval(context.Background(), tt.eval)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, tt.want, res)
|
||||
})
|
||||
}
|
||||
}
|
|
@ -20,6 +20,12 @@ func displayResult(ctx context.Context, inst *Inst, res object) (err error) {
|
|||
if _, err = fmt.Fprintln(inst.out, "(nil)"); err != nil {
|
||||
return err
|
||||
}
|
||||
case listable:
|
||||
for i := 0; i < v.Len(); i++ {
|
||||
if err = displayResult(ctx, inst, v.Index(i)); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
default:
|
||||
if _, err = fmt.Fprintln(inst.out, v.String()); err != nil {
|
||||
return err
|
||||
|
|
13
ucl/inst.go
13
ucl/inst.go
|
@ -29,6 +29,19 @@ func WithMissingBuiltinHandler(handler MissingBuiltinHandler) InstOption {
|
|||
}
|
||||
}
|
||||
|
||||
func WithModule(module Module) InstOption {
|
||||
return func(i *Inst) {
|
||||
for name, builtin := range module.Builtins {
|
||||
i.SetBuiltin(module.Name+":"+name, builtin)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type Module struct {
|
||||
Name string
|
||||
Builtins map[string]BuiltinHandler
|
||||
}
|
||||
|
||||
func New(opts ...InstOption) *Inst {
|
||||
rootEC := &evalCtx{}
|
||||
rootEC.root = rootEC
|
||||
|
|
|
@ -94,6 +94,8 @@ func (u userBuiltin) invoke(ctx context.Context, args invocationArgs) (object, e
|
|||
|
||||
func bindArg(v interface{}, arg object) error {
|
||||
switch t := v.(type) {
|
||||
case *interface{}:
|
||||
*t, _ = toGoValue(arg)
|
||||
case *string:
|
||||
*t = arg.String()
|
||||
case *int:
|
||||
|
|
Loading…
Reference in New Issue