Added lists:batch and modified and and or to be useful to handling nil defaults
All checks were successful
Build / build (push) Successful in 2m4s
All checks were successful
Build / build (push) Successful in 2m4s
This commit is contained in:
parent
ad136b3787
commit
8fa2e3efb9
|
@ -11,3 +11,12 @@ lists:add LIST [ARGS...]
|
||||||
|
|
||||||
Adds values to the end of the list. Returns the modified list.
|
Adds values to the end of the list. Returns the modified list.
|
||||||
|
|
||||||
|
### batch
|
||||||
|
|
||||||
|
```
|
||||||
|
lists:batch LIST SIZE
|
||||||
|
```
|
||||||
|
|
||||||
|
Returns a list containing the items of LIST grouped into a sub-list no greater
|
||||||
|
than SIZE. SIZE must be an integer greater than 0. If LIST is empty,
|
||||||
|
then an empty list is returned.
|
|
@ -21,6 +21,7 @@ func main() {
|
||||||
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)),
|
||||||
|
ucl.WithModule(builtins.Lists()),
|
||||||
ucl.WithModule(builtins.OS()),
|
ucl.WithModule(builtins.OS()),
|
||||||
ucl.WithModule(builtins.Strs()),
|
ucl.WithModule(builtins.Strs()),
|
||||||
ucl.WithModule(builtins.Time()),
|
ucl.WithModule(builtins.Time()),
|
||||||
|
|
|
@ -273,7 +273,7 @@ func andBuiltin(ctx context.Context, args invocationArgs) (Object, error) {
|
||||||
|
|
||||||
for _, a := range args.args {
|
for _, a := range args.args {
|
||||||
if a == nil || !a.Truthy() {
|
if a == nil || !a.Truthy() {
|
||||||
return boolObject(false), nil
|
return a, nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return args.args[len(args.args)-1], nil
|
return args.args[len(args.args)-1], nil
|
||||||
|
@ -284,12 +284,12 @@ func orBuiltin(ctx context.Context, args invocationArgs) (Object, error) {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, a := range args.args {
|
for _, a := range args.args[:len(args.args)-1] {
|
||||||
if a != nil && a.Truthy() {
|
if a != nil && a.Truthy() {
|
||||||
return a, nil
|
return a, nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return boolObject(false), nil
|
return args.args[len(args.args)-1], nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func notBuiltin(ctx context.Context, args invocationArgs) (Object, error) {
|
func notBuiltin(ctx context.Context, args invocationArgs) (Object, error) {
|
||||||
|
@ -510,7 +510,7 @@ func keysBuiltin(ctx context.Context, args invocationArgs) (Object, error) {
|
||||||
val := args.args[0]
|
val := args.args[0]
|
||||||
switch v := val.(type) {
|
switch v := val.(type) {
|
||||||
case hashable:
|
case hashable:
|
||||||
keys := make(listObject, 0, v.Len())
|
keys := make(ListObject, 0, v.Len())
|
||||||
if err := v.Each(func(k string, _ Object) error {
|
if err := v.Each(func(k string, _ Object) error {
|
||||||
keys = append(keys, StringObject(k))
|
keys = append(keys, StringObject(k))
|
||||||
return nil
|
return nil
|
||||||
|
@ -536,7 +536,7 @@ func mapBuiltin(ctx context.Context, args invocationArgs) (Object, error) {
|
||||||
switch t := args.args[0].(type) {
|
switch t := args.args[0].(type) {
|
||||||
case Listable:
|
case Listable:
|
||||||
l := t.Len()
|
l := t.Len()
|
||||||
newList := listObject{}
|
newList := ListObject{}
|
||||||
for i := 0; i < l; i++ {
|
for i := 0; i < l; i++ {
|
||||||
v := t.Index(i)
|
v := t.Index(i)
|
||||||
m, err := inv.invoke(ctx, args.fork([]Object{v}))
|
m, err := inv.invoke(ctx, args.fork([]Object{v}))
|
||||||
|
@ -563,7 +563,7 @@ func filterBuiltin(ctx context.Context, args invocationArgs) (Object, error) {
|
||||||
switch t := args.args[0].(type) {
|
switch t := args.args[0].(type) {
|
||||||
case Listable:
|
case Listable:
|
||||||
l := t.Len()
|
l := t.Len()
|
||||||
newList := listObject{}
|
newList := ListObject{}
|
||||||
for i := 0; i < l; i++ {
|
for i := 0; i < l; i++ {
|
||||||
v := t.Index(i)
|
v := t.Index(i)
|
||||||
m, err := inv.invoke(ctx, args.fork([]Object{v}))
|
m, err := inv.invoke(ctx, args.fork([]Object{v}))
|
||||||
|
|
|
@ -2,6 +2,7 @@ package builtins
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"errors"
|
||||||
"ucl.lmika.dev/ucl"
|
"ucl.lmika.dev/ucl"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -10,6 +11,7 @@ func Lists() ucl.Module {
|
||||||
Name: "lists",
|
Name: "lists",
|
||||||
Builtins: map[string]ucl.BuiltinHandler{
|
Builtins: map[string]ucl.BuiltinHandler{
|
||||||
"add": listAdd,
|
"add": listAdd,
|
||||||
|
"batch": listBatch,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -33,3 +35,34 @@ func listAdd(ctx context.Context, args ucl.CallArgs) (any, error) {
|
||||||
|
|
||||||
return target, nil
|
return target, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func listBatch(ctx context.Context, args ucl.CallArgs) (any, error) {
|
||||||
|
var (
|
||||||
|
src ucl.Listable
|
||||||
|
batchSize int
|
||||||
|
)
|
||||||
|
|
||||||
|
if err := args.Bind(&src, &batchSize); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if batchSize < 1 {
|
||||||
|
return nil, errors.New("batch size must be greater than zero")
|
||||||
|
}
|
||||||
|
|
||||||
|
res := ucl.NewListObject()
|
||||||
|
batch := ucl.NewListObject()
|
||||||
|
for i := 0; i < src.Len(); i++ {
|
||||||
|
batch.Append(src.Index(i))
|
||||||
|
if batch.Len() >= batchSize {
|
||||||
|
res.Append(batch)
|
||||||
|
batch = ucl.NewListObject()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if batch.Len() > 0 {
|
||||||
|
res.Append(batch)
|
||||||
|
}
|
||||||
|
|
||||||
|
return res, nil
|
||||||
|
}
|
||||||
|
|
|
@ -35,3 +35,38 @@ func TestLists_Add(t *testing.T) {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestLists_Batch(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
desc string
|
||||||
|
eval string
|
||||||
|
want any
|
||||||
|
wantErr bool
|
||||||
|
}{
|
||||||
|
{desc: "list batch 1", eval: `lists:batch [1 2 3] 2`, want: []any{[]any{1, 2}, []any{3}}},
|
||||||
|
{desc: "list batch 2", eval: `lists:batch [1 2 3 4] 2`, want: []any{[]any{1, 2}, []any{3, 4}}},
|
||||||
|
{desc: "list batch 3", eval: `lists:batch [1 2 3] 3`, want: []any{[]any{1, 2, 3}}},
|
||||||
|
{desc: "list batch 4", eval: `lists:batch [1 2 3] 7`, want: []any{[]any{1, 2, 3}}},
|
||||||
|
{desc: "list batch 5", eval: `lists:batch [1 2 3] 1`, want: []any{[]any{1}, []any{2}, []any{3}}},
|
||||||
|
{desc: "list batch 6", eval: `lists:batch [] 1`, want: []any{}},
|
||||||
|
|
||||||
|
{desc: "err list batch 1", eval: `lists:batch [1 2 3] 0`, wantErr: true},
|
||||||
|
{desc: "err list batch 2", eval: `lists:batch [1 2 3] -3`, wantErr: true},
|
||||||
|
{desc: "err list batch 3", eval: `lists:batch [1 2 3] "waht"`, wantErr: true},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.desc, func(t *testing.T) {
|
||||||
|
inst := ucl.New(
|
||||||
|
ucl.WithModule(builtins.Lists()),
|
||||||
|
)
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
14
ucl/eval.go
14
ucl/eval.go
|
@ -108,9 +108,9 @@ func (e evaluator) evalCmd(ctx context.Context, ec *evalCtx, currentPipe Object,
|
||||||
|
|
||||||
func (e evaluator) evalInvokable(ctx context.Context, ec *evalCtx, currentPipe Object, ast *astCmd, cmd invokable) (Object, error) {
|
func (e evaluator) evalInvokable(ctx context.Context, ec *evalCtx, currentPipe Object, ast *astCmd, cmd invokable) (Object, error) {
|
||||||
var (
|
var (
|
||||||
pargs listObject
|
pargs ListObject
|
||||||
kwargs map[string]*listObject
|
kwargs map[string]*ListObject
|
||||||
argsPtr *listObject
|
argsPtr *ListObject
|
||||||
)
|
)
|
||||||
|
|
||||||
argsPtr = &pargs
|
argsPtr = &pargs
|
||||||
|
@ -121,10 +121,10 @@ func (e evaluator) evalInvokable(ctx context.Context, ec *evalCtx, currentPipe O
|
||||||
if ident := arg.Arg.Ident; len(arg.DotSuffix) == 0 && ident != nil && ident.String()[0] == '-' {
|
if ident := arg.Arg.Ident; len(arg.DotSuffix) == 0 && ident != nil && ident.String()[0] == '-' {
|
||||||
// Arg switch
|
// Arg switch
|
||||||
if kwargs == nil {
|
if kwargs == nil {
|
||||||
kwargs = make(map[string]*listObject)
|
kwargs = make(map[string]*ListObject)
|
||||||
}
|
}
|
||||||
|
|
||||||
argsPtr = &listObject{}
|
argsPtr = &ListObject{}
|
||||||
kwargs[ident.String()[1:]] = argsPtr
|
kwargs[ident.String()[1:]] = argsPtr
|
||||||
} else {
|
} else {
|
||||||
ae, err := e.evalDot(ctx, ec, arg)
|
ae, err := e.evalDot(ctx, ec, arg)
|
||||||
|
@ -203,7 +203,7 @@ func (e evaluator) evalArg(ctx context.Context, ec *evalCtx, n astCmdArg) (Objec
|
||||||
|
|
||||||
func (e evaluator) evalListOrHash(ctx context.Context, ec *evalCtx, loh *astListOrHash) (Object, error) {
|
func (e evaluator) evalListOrHash(ctx context.Context, ec *evalCtx, loh *astListOrHash) (Object, error) {
|
||||||
if loh.EmptyList {
|
if loh.EmptyList {
|
||||||
return &listObject{}, nil
|
return &ListObject{}, nil
|
||||||
} else if loh.EmptyHash {
|
} else if loh.EmptyHash {
|
||||||
return hashObject{}, nil
|
return hashObject{}, nil
|
||||||
}
|
}
|
||||||
|
@ -230,7 +230,7 @@ func (e evaluator) evalListOrHash(ctx context.Context, ec *evalCtx, loh *astList
|
||||||
return h, nil
|
return h, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
l := listObject{}
|
l := ListObject{}
|
||||||
for _, el := range loh.Elements {
|
for _, el := range loh.Elements {
|
||||||
if el.Right != nil {
|
if el.Right != nil {
|
||||||
return nil, errors.New("miss-match of lists and hash")
|
return nil, errors.New("miss-match of lists and hash")
|
||||||
|
|
28
ucl/objs.go
28
ucl/objs.go
|
@ -38,13 +38,17 @@ type hashable interface {
|
||||||
Each(func(k string, v Object) error) error
|
Each(func(k string, v Object) error) error
|
||||||
}
|
}
|
||||||
|
|
||||||
type listObject []Object
|
type ListObject []Object
|
||||||
|
|
||||||
func (lo *listObject) Append(o Object) {
|
func NewListObject() *ListObject {
|
||||||
|
return &ListObject{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (lo *ListObject) Append(o Object) {
|
||||||
*lo = append(*lo, o)
|
*lo = append(*lo, o)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (lo *listObject) Insert(idx int, obj Object) error {
|
func (lo *ListObject) Insert(idx int, obj Object) error {
|
||||||
if idx != -1 {
|
if idx != -1 {
|
||||||
return errors.New("not supported")
|
return errors.New("not supported")
|
||||||
}
|
}
|
||||||
|
@ -52,26 +56,22 @@ func (lo *listObject) Insert(idx int, obj Object) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *listObject) String() string {
|
func (s *ListObject) String() string {
|
||||||
return fmt.Sprintf("%v", []Object(*s))
|
return fmt.Sprintf("%v", []Object(*s))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *listObject) Truthy() bool {
|
func (s *ListObject) Truthy() bool {
|
||||||
return len(*s) > 0
|
return len(*s) > 0
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *listObject) Len() int {
|
func (s *ListObject) Len() int {
|
||||||
return len(*s)
|
return len(*s)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *listObject) Index(i int) Object {
|
func (s *ListObject) Index(i int) Object {
|
||||||
return (*s)[i]
|
return (*s)[i]
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *listObject) Add(o Object) {
|
|
||||||
*s = append(*s, o)
|
|
||||||
}
|
|
||||||
|
|
||||||
type hashObject map[string]Object
|
type hashObject map[string]Object
|
||||||
|
|
||||||
func (s hashObject) String() string {
|
func (s hashObject) String() string {
|
||||||
|
@ -171,7 +171,7 @@ func toGoValue(obj Object) (interface{}, bool) {
|
||||||
return bool(v), true
|
return bool(v), true
|
||||||
case timeObject:
|
case timeObject:
|
||||||
return time.Time(v), true
|
return time.Time(v), true
|
||||||
case *listObject:
|
case *ListObject:
|
||||||
xs := make([]interface{}, 0, len(*v))
|
xs := make([]interface{}, 0, len(*v))
|
||||||
for _, va := range *v {
|
for _, va := range *v {
|
||||||
x, ok := toGoValue(va)
|
x, ok := toGoValue(va)
|
||||||
|
@ -357,7 +357,7 @@ type invocationArgs struct {
|
||||||
inst *Inst
|
inst *Inst
|
||||||
ec *evalCtx
|
ec *evalCtx
|
||||||
args []Object
|
args []Object
|
||||||
kwargs map[string]*listObject
|
kwargs map[string]*ListObject
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ia invocationArgs) expectArgn(x int) error {
|
func (ia invocationArgs) expectArgn(x int) error {
|
||||||
|
@ -415,7 +415,7 @@ func (ia invocationArgs) fork(args []Object) invocationArgs {
|
||||||
inst: ia.inst,
|
inst: ia.inst,
|
||||||
ec: ia.ec,
|
ec: ia.ec,
|
||||||
args: args,
|
args: args,
|
||||||
kwargs: make(map[string]*listObject),
|
kwargs: make(map[string]*ListObject),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -38,7 +38,7 @@ func WithTestBuiltin() InstOption {
|
||||||
}))
|
}))
|
||||||
|
|
||||||
i.rootEC.addCmd("list", invokableFunc(func(ctx context.Context, args invocationArgs) (Object, error) {
|
i.rootEC.addCmd("list", invokableFunc(func(ctx context.Context, args invocationArgs) (Object, error) {
|
||||||
var a listObject = make([]Object, len(args.args))
|
var a ListObject = make([]Object, len(args.args))
|
||||||
copy(a, args.args)
|
copy(a, args.args)
|
||||||
return &a, nil
|
return &a, nil
|
||||||
}))
|
}))
|
||||||
|
@ -1498,9 +1498,11 @@ func TestBuiltins_AndOrNot(t *testing.T) {
|
||||||
{desc: "not 3", expr: `not $false $true`, want: true},
|
{desc: "not 3", expr: `not $false $true`, want: true},
|
||||||
|
|
||||||
{desc: "short circuit and 1", expr: `and "hello" "world"`, want: "world"},
|
{desc: "short circuit and 1", expr: `and "hello" "world"`, want: "world"},
|
||||||
{desc: "short circuit and 2", expr: `and () "world"`, want: false},
|
{desc: "short circuit and 2", expr: `and () "world"`, want: nil},
|
||||||
|
{desc: "short circuit and 3", expr: `and [] "world"`, want: []any{}},
|
||||||
{desc: "short circuit or 1", expr: `or "hello" "world"`, want: "hello"},
|
{desc: "short circuit or 1", expr: `or "hello" "world"`, want: "hello"},
|
||||||
{desc: "short circuit or 2", expr: `or () "world"`, want: "world"},
|
{desc: "short circuit or 2", expr: `or () "world"`, want: "world"},
|
||||||
|
{desc: "short circuit or 3", expr: `or () []`, want: []any{}},
|
||||||
|
|
||||||
{desc: "bad and 1", expr: `and "one"`, wantErr: true},
|
{desc: "bad and 1", expr: `and "one"`, wantErr: true},
|
||||||
{desc: "bad and 2", expr: `and`, wantErr: true},
|
{desc: "bad and 2", expr: `and`, wantErr: true},
|
||||||
|
|
Loading…
Reference in a new issue