ucl/ucl/builtins/bytes.go
Leon Mika d178c05b63
Some checks failed
Build / build (push) Failing after 2m47s
Added additional byte builtins
2026-09-08 21:57:15 +10:00

191 lines
4.1 KiB
Go

package builtins
import (
"bytes"
"context"
"crypto/md5"
"crypto/sha1"
"crypto/sha256"
"encoding/base64"
"encoding/hex"
"errors"
"fmt"
"ucl.lmika.dev/ucl"
)
type bytesHandlers struct{}
/// :module bytes
//
// 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,
},
}
}
// :fn hash
//
// :syntax INPUT ALGORITHM
//
// Returns a byte slice containing the result of hashing the input with the given algorithm.
//
// The input can either be a byte slice or a string. The supported algorithms are as follows:
//
// - md5
// - sha1
// - sha256
func (b bytesHandlers) hash(ctx context.Context, args ucl.CallArgs) (any, error) {
inputBts, args, err := consumeStringOrBytes(args)
if err != nil {
return nil, err
}
var algor string
if err := args.Bind(&algor); err != nil {
return nil, err
}
switch algor {
case "md5":
res := md5.Sum(inputBts)
return ucl.BytesObject(res[:]), nil
case "sha1":
res := sha1.Sum(inputBts)
return ucl.BytesObject(res[:]), nil
case "sha256":
res := sha256.Sum256(inputBts)
return ucl.BytesObject(res[:]), nil
}
return nil, errors.New("unsupported algorithm")
}
// :fn base64
//
// :syntax INPUT
//
// Returns a string containing the base64 encoding of the input.
//
// The input can either be a byte slice or a string. The result will be a base64 encoded string
// using standard encoding.
func (c bytesHandlers) base64(ctx context.Context, args ucl.CallArgs) (any, error) {
inputBts, args, err := consumeStringOrBytes(args)
if err != nil {
return nil, err
}
res := base64.StdEncoding.EncodeToString(inputBts)
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 {
args.Shift(1)
return b, args, nil
}
}
var s string
if err := args.Bind(&s); err != nil {
return nil, ucl.CallArgs{}, err
}
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
}