Compare commits
No commits in common. "main" and "v0.1.4" have entirely different histories.
7 changed files with 3 additions and 300 deletions
|
|
@ -19,7 +19,6 @@ func main() {
|
||||||
defer rl.Close()
|
defer rl.Close()
|
||||||
|
|
||||||
instRepl := repl.New(
|
instRepl := repl.New(
|
||||||
ucl.WithModule(builtins.Bytes()),
|
|
||||||
ucl.WithModule(builtins.CSV(nil)),
|
ucl.WithModule(builtins.CSV(nil)),
|
||||||
ucl.WithModule(builtins.FS(nil)),
|
ucl.WithModule(builtins.FS(nil)),
|
||||||
ucl.WithModule(builtins.Log(nil)),
|
ucl.WithModule(builtins.Log(nil)),
|
||||||
|
|
|
||||||
|
|
@ -60,28 +60,6 @@ func (r *REPL) displayResult(ctx context.Context, w io.Writer, res any, concise
|
||||||
if _, err = fmt.Fprintln(w, "(nil)"); err != nil {
|
if _, err = fmt.Fprintln(w, "(nil)"); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
case []byte:
|
|
||||||
if _, err = fmt.Fprint(w, "bytes["); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
for i, x := range v {
|
|
||||||
if i > 0 {
|
|
||||||
if _, err = fmt.Fprint(w, " "); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if _, err = fmt.Fprintf(w, "%02x", x); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if _, err = fmt.Fprint(w, "]"); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if !concise {
|
|
||||||
if _, err = fmt.Fprintln(w); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
case ucl.Listable:
|
case ucl.Listable:
|
||||||
if concise {
|
if concise {
|
||||||
fmt.Fprintf(w, "[")
|
fmt.Fprintf(w, "[")
|
||||||
|
|
|
||||||
|
|
@ -1,15 +1,12 @@
|
||||||
package builtins
|
package builtins
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
|
||||||
"context"
|
"context"
|
||||||
"crypto/md5"
|
"crypto/md5"
|
||||||
"crypto/sha1"
|
"crypto/sha1"
|
||||||
"crypto/sha256"
|
"crypto/sha256"
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"encoding/hex"
|
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"ucl.lmika.dev/ucl"
|
"ucl.lmika.dev/ucl"
|
||||||
)
|
)
|
||||||
|
|
@ -21,15 +18,11 @@ type bytesHandlers struct{}
|
||||||
// Perform operations over byte slices.
|
// Perform operations over byte slices.
|
||||||
|
|
||||||
func Bytes() ucl.Module {
|
func Bytes() ucl.Module {
|
||||||
bh := bytesHandlers{}
|
|
||||||
|
|
||||||
return ucl.Module{
|
return ucl.Module{
|
||||||
Name: "bytes",
|
Name: "bytes",
|
||||||
Builtins: map[string]ucl.BuiltinHandler{
|
Builtins: map[string]ucl.BuiltinHandler{
|
||||||
"from": bh.from,
|
"hash": bytesHandlers{}.hash,
|
||||||
"hash": bh.hash,
|
"base64": bytesHandlers{}.base64,
|
||||||
"base64": bh.base64,
|
|
||||||
"hex": bh.hex,
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -89,50 +82,6 @@ func (c bytesHandlers) base64(ctx context.Context, args ucl.CallArgs) (any, erro
|
||||||
return res, nil
|
return res, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// :fn from
|
|
||||||
//
|
|
||||||
// :syntax INPUT ...
|
|
||||||
//
|
|
||||||
// Returns a byte slice from a given input. Input can be one of the following:
|
|
||||||
//
|
|
||||||
// - Nil, which will produce an empty byte slice
|
|
||||||
// - A string, which would produce a byte slice containing the UTF-8 encoded string
|
|
||||||
// - A number, which would produce a single byte slice containing the number as a byte
|
|
||||||
// - A byte slice, which would produce a copy of the byte slice
|
|
||||||
//
|
|
||||||
// A list or iter will consume the elements and apply the byte slice conversion recursively, combining
|
|
||||||
// the results into a single byte slice.
|
|
||||||
func (c bytesHandlers) from(ctx context.Context, args ucl.CallArgs) (any, error) {
|
|
||||||
var bfr bytes.Buffer
|
|
||||||
|
|
||||||
var o ucl.Object
|
|
||||||
for args.NArgs() > 0 {
|
|
||||||
if err := args.Bind(&o); err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to bind object: %w", err)
|
|
||||||
}
|
|
||||||
if err := writeObjToBytesBuffer(ctx, &bfr, o); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return bfr.Bytes(), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// :fn hex
|
|
||||||
//
|
|
||||||
// :syntax BYTES
|
|
||||||
//
|
|
||||||
// Returns a string encoding the bytes slice as a hex string
|
|
||||||
func (c bytesHandlers) hex(ctx context.Context, args ucl.CallArgs) (any, error) {
|
|
||||||
inputBts, args, err := consumeStringOrBytes(args)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
res := hex.EncodeToString(inputBts)
|
|
||||||
return res, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func consumeStringOrBytes(args ucl.CallArgs) (bs ucl.BytesObject, _ ucl.CallArgs, _ error) {
|
func consumeStringOrBytes(args ucl.CallArgs) (bs ucl.BytesObject, _ ucl.CallArgs, _ error) {
|
||||||
if objs := args.RestAsObjects(); len(objs) > 0 {
|
if objs := args.RestAsObjects(); len(objs) > 0 {
|
||||||
if b, ok := objs[0].(ucl.BytesObject); ok {
|
if b, ok := objs[0].(ucl.BytesObject); ok {
|
||||||
|
|
@ -147,45 +96,3 @@ func consumeStringOrBytes(args ucl.CallArgs) (bs ucl.BytesObject, _ ucl.CallArgs
|
||||||
}
|
}
|
||||||
return ucl.BytesObject(s), args, nil
|
return ucl.BytesObject(s), args, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func writeObjToBytesBuffer(ctx context.Context, bfr *bytes.Buffer, o ucl.Object) error {
|
|
||||||
if o == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
switch v := o.(type) {
|
|
||||||
case ucl.StringObject:
|
|
||||||
bfr.Write([]byte(v))
|
|
||||||
case ucl.IntObject:
|
|
||||||
bfr.WriteByte(byte(v))
|
|
||||||
case ucl.StringListObject:
|
|
||||||
for _, el := range v {
|
|
||||||
bfr.Write([]byte(el))
|
|
||||||
}
|
|
||||||
case ucl.BytesObject:
|
|
||||||
bfr.Write(v)
|
|
||||||
case ucl.Listable:
|
|
||||||
l := v.Len()
|
|
||||||
for i := 0; i < l; i++ {
|
|
||||||
if err := writeObjToBytesBuffer(ctx, bfr, v.Index(i)); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
case ucl.Iterable:
|
|
||||||
for v.HasNext() {
|
|
||||||
n, err := v.Next(ctx)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if n == nil {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
if err := writeObjToBytesBuffer(ctx, bfr, n); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
return fmt.Errorf("unsupported type %T", o)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -10,76 +10,6 @@ import (
|
||||||
"ucl.lmika.dev/ucl/builtins"
|
"ucl.lmika.dev/ucl/builtins"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestBytes_From(t *testing.T) {
|
|
||||||
tests := []struct {
|
|
||||||
desc string
|
|
||||||
eval string
|
|
||||||
want any
|
|
||||||
wantErr bool
|
|
||||||
}{
|
|
||||||
{desc: "no args", eval: `bytes:from`},
|
|
||||||
{desc: "nil", eval: `bytes:from ()`},
|
|
||||||
|
|
||||||
{desc: "string 1", eval: `bytes:from "hello"`, want: []byte("hello")},
|
|
||||||
{desc: "string 2", eval: `bytes:from ""`},
|
|
||||||
{desc: "string utf-8", eval: `bytes:from "héllo"`, want: []byte("héllo")},
|
|
||||||
|
|
||||||
{desc: "single int", eval: `bytes:from 104`, want: []byte{104}},
|
|
||||||
{desc: "zero int", eval: `bytes:from 0`, want: []byte{0x00}},
|
|
||||||
{desc: "int wraps to byte", eval: `bytes:from 260`, want: []byte{4}},
|
|
||||||
|
|
||||||
{desc: "bytes input", eval: `bytes:from (bytes:from "hello")`, want: []byte("hello")},
|
|
||||||
|
|
||||||
{desc: "multiple args", eval: `bytes:from "he" 108 "lo"`, want: []byte("hello")},
|
|
||||||
|
|
||||||
{desc: "list of ints", eval: `bytes:from [104 101 108 108 111]`, want: []byte("hello")},
|
|
||||||
{desc: "empty list", eval: `bytes:from []`},
|
|
||||||
{desc: "list of strings", eval: `bytes:from ["he" "llo"]`, want: []byte("hello")},
|
|
||||||
{desc: "list with nil", eval: `bytes:from ["he" () "llo"]`, want: []byte("hello")},
|
|
||||||
{desc: "nested lists", eval: `bytes:from [["he"] [108 "lo"]]`, want: []byte("hello")},
|
|
||||||
{desc: "list of bytes", eval: `bytes:from [(bytes:from "he") (bytes:from "llo")]`, want: []byte("hello")},
|
|
||||||
|
|
||||||
{desc: "iterator of ints", eval: `bytes:from (itrs:from [104 105])`, want: []byte("hi")},
|
|
||||||
{desc: "iterator from seq", eval: `seq 4 | map { |x| add $x 97 } | bytes:from`, want: []byte("abcd")},
|
|
||||||
|
|
||||||
{desc: "round trip through str", eval: `str (bytes:from "hello")`, want: "hello"},
|
|
||||||
|
|
||||||
{desc: "err unsupported bool", eval: `bytes:from $true`, wantErr: true},
|
|
||||||
{desc: "err unsupported map", eval: `bytes:from [a: 1]`, wantErr: true},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, tt := range tests {
|
|
||||||
t.Run(tt.desc, func(t *testing.T) {
|
|
||||||
inst := ucl.New(
|
|
||||||
ucl.WithModule(builtins.Bytes()),
|
|
||||||
ucl.WithModule(builtins.Itrs()),
|
|
||||||
)
|
|
||||||
inst.SetVar("true", true)
|
|
||||||
|
|
||||||
res, err := inst.EvalString(context.Background(), tt.eval)
|
|
||||||
if tt.wantErr {
|
|
||||||
assert.Error(t, err)
|
|
||||||
} else {
|
|
||||||
assert.NoError(t, err)
|
|
||||||
|
|
||||||
if wantStr, ok := tt.want.(string); ok {
|
|
||||||
assert.Equal(t, wantStr, res)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
resBts, ok := res.([]byte)
|
|
||||||
if assert.True(t, ok, "expected []byte result, got %T", res) {
|
|
||||||
if tt.want == nil {
|
|
||||||
assert.Empty(t, resBts)
|
|
||||||
} else {
|
|
||||||
assert.Equal(t, tt.want, resBts)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestBytes_Hash(t *testing.T) {
|
func TestBytes_Hash(t *testing.T) {
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
desc string
|
desc string
|
||||||
|
|
@ -153,36 +83,3 @@ func TestBytes_Base64(t *testing.T) {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestBytes_Hex(t *testing.T) {
|
|
||||||
tests := []struct {
|
|
||||||
desc string
|
|
||||||
eval string
|
|
||||||
want string
|
|
||||||
wantErr bool
|
|
||||||
}{
|
|
||||||
{desc: "encode string", eval: `bytes:hex "hello"`, want: "68656c6c6f"},
|
|
||||||
{desc: "encode empty string", eval: `bytes:hex ""`, want: ""},
|
|
||||||
{desc: "encode string with utf-8", eval: `bytes:hex "héllo"`, want: "68c3a96c6c6f"},
|
|
||||||
|
|
||||||
{desc: "encode bytes input", eval: `bytes:hex (bytes:from "hello")`, want: "68656c6c6f"},
|
|
||||||
{desc: "encode hash result", eval: `bytes:hex (bytes:hash "hello" md5)`, want: "5d41402abc4b2a76b9719d911017c592"},
|
|
||||||
|
|
||||||
{desc: "err missing args", eval: `bytes:hex`, wantErr: true},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, tt := range tests {
|
|
||||||
t.Run(tt.desc, func(t *testing.T) {
|
|
||||||
inst := ucl.New(
|
|
||||||
ucl.WithModule(builtins.Bytes()),
|
|
||||||
)
|
|
||||||
res, err := inst.EvalString(context.Background(), tt.eval)
|
|
||||||
if tt.wantErr {
|
|
||||||
assert.Error(t, err)
|
|
||||||
} else {
|
|
||||||
assert.NoError(t, err)
|
|
||||||
assert.Equal(t, tt.want, res)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,6 @@ import (
|
||||||
type OSProvider interface {
|
type OSProvider interface {
|
||||||
LookupEnv(string) (string, bool)
|
LookupEnv(string) (string, bool)
|
||||||
Exec(ctx context.Context, cmd string, args ...string) (*exec.Cmd, error)
|
Exec(ctx context.Context, cmd string, args ...string) (*exec.Cmd, error)
|
||||||
ExecBang(ctx context.Context, cmd string) (*exec.Cmd, error)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type osHandlers struct {
|
type osHandlers struct {
|
||||||
|
|
@ -30,7 +29,6 @@ func OS() ucl.Module {
|
||||||
Builtins: map[string]ucl.BuiltinHandler{
|
Builtins: map[string]ucl.BuiltinHandler{
|
||||||
"env": osh.env,
|
"env": osh.env,
|
||||||
"exec": osh.exec,
|
"exec": osh.exec,
|
||||||
"!": osh.bang,
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -91,47 +89,8 @@ func (oh osHandlers) exec(ctx context.Context, args ucl.CallArgs) (any, error) {
|
||||||
return string(res), nil
|
return string(res), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (oh osHandlers) bang(ctx context.Context, args ucl.CallArgs) (any, error) {
|
|
||||||
var (
|
|
||||||
cmdStr string
|
|
||||||
stdIn string
|
|
||||||
hasStdin bool
|
|
||||||
)
|
|
||||||
|
|
||||||
if args.NArgs() == 1 {
|
|
||||||
if err := args.Bind(&cmdStr); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if err := args.Bind(&stdIn, &cmdStr); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
hasStdin = true
|
|
||||||
}
|
|
||||||
|
|
||||||
cmd, err := oh.provider.ExecBang(ctx, cmdStr)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if hasStdin {
|
|
||||||
cmd.Stdin = strings.NewReader(stdIn)
|
|
||||||
}
|
|
||||||
|
|
||||||
res, err := cmd.Output()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return string(res), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type builtinOSProvider struct{}
|
type builtinOSProvider struct{}
|
||||||
|
|
||||||
func (p builtinOSProvider) ExecBang(ctx context.Context, cmd string) (*exec.Cmd, error) {
|
|
||||||
return exec.CommandContext(ctx, "bash", "-c", cmd), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (builtinOSProvider) LookupEnv(key string) (string, bool) {
|
func (builtinOSProvider) LookupEnv(key string) (string, bool) {
|
||||||
return os.LookupEnv(key)
|
return os.LookupEnv(key)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -60,30 +60,3 @@ func TestOS_Exec(t *testing.T) {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestOS_Bang(t *testing.T) {
|
|
||||||
tests := []struct {
|
|
||||||
descr string
|
|
||||||
eval string
|
|
||||||
want any
|
|
||||||
}{
|
|
||||||
{descr: "run command 1", eval: `os:! "echo 'hello, world'"`, want: "hello, world\n"},
|
|
||||||
{descr: "run command 2", eval: `os:! "date +%Y%m%d"`, want: time.Now().Format("20060102") + "\n"},
|
|
||||||
{descr: "run command 3", eval: `os:! "hello" "tr [a-z] [A-Z]"`, want: "HELLO"},
|
|
||||||
{descr: "run command 4", eval: `os:! "hello" "tr -d e" "hello"`, want: "hllo"},
|
|
||||||
{descr: "run command 5", eval: `"hello" | os:! "tr [a-z] [A-Z]"`, want: "HELLO"},
|
|
||||||
{descr: "run command 6", eval: `"hello" | os:! "tr -d e"`, want: "hllo"},
|
|
||||||
{descr: "run command 7", eval: `"hello" | os:! "tr -d e" | os:! "tr [a-z] [A-Z]"`, want: "HLLO"},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, tt := range tests {
|
|
||||||
t.Run(tt.descr, func(t *testing.T) {
|
|
||||||
inst := ucl.New(
|
|
||||||
ucl.WithModule(builtins.OS()),
|
|
||||||
)
|
|
||||||
res, err := inst.EvalString(context.Background(), tt.eval)
|
|
||||||
assert.NoError(t, err)
|
|
||||||
assert.Equal(t, tt.want, res)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
||||||
12
ucl/objs.go
12
ucl/objs.go
|
|
@ -121,17 +121,7 @@ func (ss StringListObject) Index(i int) Object {
|
||||||
type BytesObject []byte
|
type BytesObject []byte
|
||||||
|
|
||||||
func (bs BytesObject) String() string {
|
func (bs BytesObject) String() string {
|
||||||
var sb strings.Builder
|
return fmt.Sprintf("[%v]bytes", len(bs))
|
||||||
|
|
||||||
sb.WriteString("bytes[")
|
|
||||||
for i, b := range bs {
|
|
||||||
if i > 0 {
|
|
||||||
sb.WriteString(" ")
|
|
||||||
}
|
|
||||||
sb.WriteString(fmt.Sprintf("%02x", b))
|
|
||||||
}
|
|
||||||
sb.WriteString("]")
|
|
||||||
return sb.String()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (bs BytesObject) Truthy() bool {
|
func (bs BytesObject) Truthy() bool {
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue