Added list:append
All checks were successful
Build / build (push) Successful in 2m20s

This commit is contained in:
Leon Mika 2025-07-18 22:13:58 +10:00
parent 7a2b012833
commit 7c76e61b08
5 changed files with 173 additions and 3 deletions

2
go.mod
View file

@ -7,6 +7,7 @@ require (
github.com/chzyer/readline v1.5.1 github.com/chzyer/readline v1.5.1
github.com/lmika/gopkgs v0.0.0-20240408110817-a02f6fc67d1f github.com/lmika/gopkgs v0.0.0-20240408110817-a02f6fc67d1f
github.com/stretchr/testify v1.10.0 github.com/stretchr/testify v1.10.0
lmika.dev/pkg/modash v0.0.0-20250619112300-0be0b6b35b1b
) )
require ( require (
@ -17,5 +18,4 @@ require (
go.abhg.dev/goldmark/frontmatter v0.2.0 // indirect go.abhg.dev/goldmark/frontmatter v0.2.0 // indirect
golang.org/x/sys v0.0.0-20220310020820-b874c991c1a5 // indirect golang.org/x/sys v0.0.0-20220310020820-b874c991c1a5 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect
lmika.dev/pkg/modash v0.0.0-20250619112300-0be0b6b35b1b // indirect
) )

1
go.sum
View file

@ -22,6 +22,7 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/yuin/goldmark v1.7.8 h1:iERMLn0/QJeHFhxSt3p6PeN9mGnvIKSpG9YYorDMnic= github.com/yuin/goldmark v1.7.8 h1:iERMLn0/QJeHFhxSt3p6PeN9mGnvIKSpG9YYorDMnic=
github.com/yuin/goldmark v1.7.8/go.mod h1:uzxRWxtg69N339t3louHJ7+O03ezfj6PlliRlaOzY1E= github.com/yuin/goldmark v1.7.8/go.mod h1:uzxRWxtg69N339t3louHJ7+O03ezfj6PlliRlaOzY1E=

31
ucl/builtins/fns_test.go Normal file
View file

@ -0,0 +1,31 @@
package builtins_test
import (
"context"
"github.com/stretchr/testify/assert"
"testing"
"ucl.lmika.dev/ucl"
"ucl.lmika.dev/ucl/builtins"
)
func TestFns_Uni(t *testing.T) {
tests := []struct {
descr string
eval string
want any
}{
{descr: "uni 1", eval: `s = fns:uni add 2 ; $s 3`, want: 5},
{descr: "uni 2", eval: `s = fns:uni (proc { |x| add $x 1 }) ; $s 3`, want: 4},
}
for _, tt := range tests {
t.Run(tt.descr, func(t *testing.T) {
inst := ucl.New(
ucl.WithModule(builtins.Fns()),
)
res, err := inst.EvalString(context.Background(), tt.eval)
assert.NoError(t, err)
assert.Equal(t, tt.want, res)
})
}
}

View file

@ -10,12 +10,45 @@ func Lists() ucl.Module {
return ucl.Module{ return ucl.Module{
Name: "lists", Name: "lists",
Builtins: map[string]ucl.BuiltinHandler{ Builtins: map[string]ucl.BuiltinHandler{
"first": listFirst, "append": listAppend,
"uniq": listUniq, "first": listFirst,
"batch": listBatch,
"uniq": listUniq,
}, },
} }
} }
func listAppend(ctx context.Context, args ucl.CallArgs) (any, error) {
var (
what ucl.Object
item ucl.Object
)
if err := args.Bind(&what); err != nil {
return nil, err
}
if what == nil {
what = ucl.NewListObject()
}
t, ok := what.(ucl.ModListable)
if !ok {
return nil, errors.New("expected mutable list")
}
for args.NArgs() > 0 {
if err := args.Bind(&item); err != nil {
return nil, err
}
if err := t.Insert(-1, item); err != nil {
return nil, err
}
}
return t, nil
}
func listFirst(ctx context.Context, args ucl.CallArgs) (any, error) { func listFirst(ctx context.Context, args ucl.CallArgs) (any, error) {
var ( var (
what ucl.Object what ucl.Object
@ -125,3 +158,34 @@ func listUniq(ctx context.Context, args ucl.CallArgs) (any, error) {
return found, nil return found, nil
} }
func listBatch(ctx context.Context, args ucl.CallArgs) (any, error) {
var (
what ucl.Object
groupSize int
)
if err := args.Bind(&what, &groupSize); err != nil {
return nil, err
}
if groupSize <= 0 {
return nil, errors.New("group size must be > 0")
}
groups := ucl.NewListObject()
var thisGroup *ucl.ListObject
if err := eachListOrIterItem(ctx, what, func(idx int, v ucl.Object) error {
if thisGroup == nil || thisGroup.Len() == groupSize {
thisGroup = ucl.NewListObject()
groups.Append(thisGroup)
}
thisGroup.Append(v)
return nil
}); err != nil {
return nil, err
}
return groups, nil
}

View file

@ -8,6 +8,44 @@ import (
"ucl.lmika.dev/ucl/builtins" "ucl.lmika.dev/ucl/builtins"
) )
func TestLists_Append(t *testing.T) {
tests := []struct {
desc string
eval string
want any
wantErr bool
}{
{desc: "append 1", eval: `lists:append [1 2 3] 4`, want: []any{1, 2, 3, 4}},
{desc: "append 2", eval: `lists:append [1 2 3] 4 5 6`, want: []any{1, 2, 3, 4, 5, 6}},
{desc: "append 3", eval: `lists:append [] 1 2 3`, want: []any{1, 2, 3}},
{desc: "append 4", eval: `lists:append () 1 2 3`, want: []any{1, 2, 3}},
{desc: "append 5", eval: `lists:append [1 2 3]`, want: []any{1, 2, 3}},
{desc: "append 6", eval: `l = [] ; lists:append $l 1 2 3 ; $l`, want: []any{1, 2, 3}},
{desc: "append 7", eval: `l = [1 2 3] ; lists:append $l [4 5 6] ; $l`, want: []any{1, 2, 3, []any{4, 5, 6}}},
{desc: "append 8", eval: `lists:append (seq 3 | itrs:from | itrs:to-list) 4 5 6`, want: []any{0, 1, 2, 4, 5, 6}},
{desc: "err 1", eval: `lists:append "asa" 1`, wantErr: true},
{desc: "err 2", eval: `lists:append 123 1`, wantErr: true},
{desc: "err 3", eval: `lists:append [:] 1`, wantErr: true},
}
for _, tt := range tests {
t.Run(tt.desc, func(t *testing.T) {
inst := ucl.New(
ucl.WithModule(builtins.Itrs()),
ucl.WithModule(builtins.Lists()),
)
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)
}
})
}
}
func TestLists_First(t *testing.T) { func TestLists_First(t *testing.T) {
tests := []struct { tests := []struct {
desc string desc string
@ -45,6 +83,42 @@ func TestLists_First(t *testing.T) {
} }
} }
func TestLists_Batch(t *testing.T) {
tests := []struct {
desc string
eval string
want any
wantErr bool
}{
{desc: "batch 1", eval: `lists:batch [1 2 3 4 5] 2`, want: []any{[]any{1, 2}, []any{3, 4}, []any{5}}},
{desc: "batch 2", eval: `lists:batch [1 2 3 4] 2`, want: []any{[]any{1, 2}, []any{3, 4}}},
{desc: "batch 3", eval: `lists:batch [1 2 3 4 5] 3`, want: []any{[]any{1, 2, 3}, []any{4, 5}}},
{desc: "batch 4", eval: `lists:batch [1 2 3 4 5] 12`, want: []any{[]any{1, 2, 3, 4, 5}}},
{desc: "batch 5", eval: `lists:batch [1 2 3 4 5] 1`, want: []any{[]any{1}, []any{2}, []any{3}, []any{4}, []any{5}}},
{desc: "batch 6", eval: `lists:batch [1] 12`, want: []any{[]any{1}}},
{desc: "batch 7", eval: `lists:batch [] 12`, want: []any{}},
{desc: "err 1", eval: `lists:batch [1 2 3] -3`, wantErr: true},
{desc: "err 2", eval: `lists:batch [1 2 3] 0`, wantErr: true},
}
for _, tt := range tests {
t.Run(tt.desc, func(t *testing.T) {
inst := ucl.New(
ucl.WithModule(builtins.Itrs()),
ucl.WithModule(builtins.Lists()),
)
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)
}
})
}
}
func TestLists_Uniq(t *testing.T) { func TestLists_Uniq(t *testing.T) {
tests := []struct { tests := []struct {
desc string desc string