diff --git a/cmd/cmsh/main.go b/cmd/cmsh/main.go index 21c0fa9..bfcf4cd 100644 --- a/cmd/cmsh/main.go +++ b/cmd/cmsh/main.go @@ -19,7 +19,6 @@ func main() { defer rl.Close() instRepl := repl.New( - ucl.WithModule(builtins.Bytes()), ucl.WithModule(builtins.CSV(nil)), ucl.WithModule(builtins.FS(nil)), ucl.WithModule(builtins.Log(nil)), diff --git a/repl/evaldisplay.go b/repl/evaldisplay.go index e4e8c60..4fc841d 100644 --- a/repl/evaldisplay.go +++ b/repl/evaldisplay.go @@ -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 { 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: if concise { fmt.Fprintf(w, "[") diff --git a/ucl/builtins/bytes.go b/ucl/builtins/bytes.go index 27fd8b0..98d1a3c 100644 --- a/ucl/builtins/bytes.go +++ b/ucl/builtins/bytes.go @@ -1,15 +1,12 @@ package builtins import ( - "bytes" "context" "crypto/md5" "crypto/sha1" "crypto/sha256" "encoding/base64" - "encoding/hex" "errors" - "fmt" "ucl.lmika.dev/ucl" ) @@ -21,15 +18,11 @@ type bytesHandlers struct{} // Perform operations over byte slices. func Bytes() ucl.Module { - bh := bytesHandlers{} - return ucl.Module{ Name: "bytes", Builtins: map[string]ucl.BuiltinHandler{ - "from": bh.from, - "hash": bh.hash, - "base64": bh.base64, - "hex": bh.hex, + "hash": bytesHandlers{}.hash, + "base64": bytesHandlers{}.base64, }, } } @@ -89,50 +82,6 @@ func (c bytesHandlers) base64(ctx context.Context, args ucl.CallArgs) (any, erro 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) { if objs := args.RestAsObjects(); len(objs) > 0 { 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 } - -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 -} diff --git a/ucl/builtins/bytes_test.go b/ucl/builtins/bytes_test.go index bb91d26..1c5554e 100644 --- a/ucl/builtins/bytes_test.go +++ b/ucl/builtins/bytes_test.go @@ -10,76 +10,6 @@ import ( "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) { tests := []struct { 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) - } - }) - } -} diff --git a/ucl/builtins/os.go b/ucl/builtins/os.go index 0f3da1d..e19c19f 100644 --- a/ucl/builtins/os.go +++ b/ucl/builtins/os.go @@ -13,7 +13,6 @@ import ( type OSProvider interface { LookupEnv(string) (string, bool) Exec(ctx context.Context, cmd string, args ...string) (*exec.Cmd, error) - ExecBang(ctx context.Context, cmd string) (*exec.Cmd, error) } type osHandlers struct { @@ -30,7 +29,6 @@ func OS() ucl.Module { Builtins: map[string]ucl.BuiltinHandler{ "env": osh.env, "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 } -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{} -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) { return os.LookupEnv(key) } diff --git a/ucl/builtins/os_test.go b/ucl/builtins/os_test.go index ed30ba5..78cee7f 100644 --- a/ucl/builtins/os_test.go +++ b/ucl/builtins/os_test.go @@ -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) - }) - } -} diff --git a/ucl/objs.go b/ucl/objs.go index f5e694c..df0872d 100644 --- a/ucl/objs.go +++ b/ucl/objs.go @@ -121,17 +121,7 @@ func (ss StringListObject) Index(i int) Object { type BytesObject []byte func (bs BytesObject) String() string { - var sb strings.Builder - - sb.WriteString("bytes[") - for i, b := range bs { - if i > 0 { - sb.WriteString(" ") - } - sb.WriteString(fmt.Sprintf("%02x", b)) - } - sb.WriteString("]") - return sb.String() + return fmt.Sprintf("[%v]bytes", len(bs)) } func (bs BytesObject) Truthy() bool {