Compare commits
5 commits
feature/os
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ebd8c61956 | ||
|
|
05f6816c19 | ||
|
|
1dcfede417 | ||
|
|
dd516feed4 | ||
|
|
7c76e61b08 |
|
|
@ -14,7 +14,7 @@ jobs:
|
||||||
- name: Setup Go
|
- name: Setup Go
|
||||||
uses: actions/setup-go@v2
|
uses: actions/setup-go@v2
|
||||||
with:
|
with:
|
||||||
go-version: 1.24
|
go-version: 1.25
|
||||||
- uses: actions/setup-node@v4
|
- uses: actions/setup-node@v4
|
||||||
with:
|
with:
|
||||||
node-version: 21.1
|
node-version: 21.1
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,7 @@ jobs:
|
||||||
- name: Setup Go
|
- name: Setup Go
|
||||||
uses: actions/setup-go@v2
|
uses: actions/setup-go@v2
|
||||||
with:
|
with:
|
||||||
go-version: 1.22.4
|
go-version: 1.25
|
||||||
- name: Build
|
- name: Build
|
||||||
run: |
|
run: |
|
||||||
make test
|
make test
|
||||||
9
go.mod
9
go.mod
|
|
@ -1,21 +1,20 @@
|
||||||
module ucl.lmika.dev
|
module ucl.lmika.dev
|
||||||
|
|
||||||
go 1.24
|
go 1.25
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/alecthomas/participle/v2 v2.1.1
|
github.com/alecthomas/participle/v2 v2.1.1
|
||||||
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/stretchr/testify v1.10.0
|
github.com/stretchr/testify v1.10.0
|
||||||
lmika.dev/pkg/modash v0.0.0-20250619112300-0be0b6b35b1b
|
github.com/yuin/goldmark v1.7.8
|
||||||
|
go.abhg.dev/goldmark/frontmatter v0.2.0
|
||||||
|
lmika.dev/pkg/modash v0.1.0
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/BurntSushi/toml v1.2.1 // indirect
|
github.com/BurntSushi/toml v1.2.1 // indirect
|
||||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||||
github.com/yuin/goldmark v1.7.8 // 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
|
||||||
)
|
)
|
||||||
|
|
|
||||||
11
go.sum
11
go.sum
|
|
@ -16,24 +16,19 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c
|
||||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM=
|
github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM=
|
||||||
github.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSow5/V2vxeg=
|
github.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSow5/V2vxeg=
|
||||||
github.com/lmika/gopkgs v0.0.0-20240408110817-a02f6fc67d1f h1:tz68Lhc1oR15HVz69IGbtdukdH0x70kBDEvvj5pTXyE=
|
|
||||||
github.com/lmika/gopkgs v0.0.0-20240408110817-a02f6fc67d1f/go.mod h1:zHQvhjGXRro/Xp2C9dbC+ZUpE0gL4GYW75x1lk7hwzI=
|
|
||||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
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/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
|
||||||
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
|
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=
|
||||||
go.abhg.dev/goldmark/frontmatter v0.2.0 h1:P8kPG0YkL12+aYk2yU3xHv4tcXzeVnN+gU0tJ5JnxRw=
|
go.abhg.dev/goldmark/frontmatter v0.2.0 h1:P8kPG0YkL12+aYk2yU3xHv4tcXzeVnN+gU0tJ5JnxRw=
|
||||||
go.abhg.dev/goldmark/frontmatter v0.2.0/go.mod h1:XqrEkZuM57djk7zrlRUB02x8I5J0px76YjkOzhB4YlU=
|
go.abhg.dev/goldmark/frontmatter v0.2.0/go.mod h1:XqrEkZuM57djk7zrlRUB02x8I5J0px76YjkOzhB4YlU=
|
||||||
|
golang.org/x/sys v0.0.0-20220310020820-b874c991c1a5 h1:y/woIyUBFbpQGKS0u1aHF/40WUDnek3fPOyD08H5Vng=
|
||||||
golang.org/x/sys v0.0.0-20220310020820-b874c991c1a5/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220310020820-b874c991c1a5/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.1.0 h1:kunALQeHf1/185U1i0GOB/fy1IPRDDpuoOOqRReG57U=
|
|
||||||
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
lmika.dev/pkg/modash v0.0.0-20250619112300-0be0b6b35b1b h1:Oymcj66pgyJ2CtGk9lPh06P4FOekllE1iPehDwaL0vw=
|
lmika.dev/pkg/modash v0.1.0 h1:fltroSvP0nKj9K0E6G+S9LULvB9Qhj47+SZ2b9v/v/c=
|
||||||
lmika.dev/pkg/modash v0.0.0-20250619112300-0be0b6b35b1b/go.mod h1:8NDl/yR1eCCEhip9FJlVuMNXIeaztQ0Ks/tizExFcTI=
|
lmika.dev/pkg/modash v0.1.0/go.mod h1:8NDl/yR1eCCEhip9FJlVuMNXIeaztQ0Ks/tizExFcTI=
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ import (
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/lmika/gopkgs/fp/maps"
|
"lmika.dev/pkg/modash/momap"
|
||||||
"os"
|
"os"
|
||||||
"slices"
|
"slices"
|
||||||
"sort"
|
"sort"
|
||||||
|
|
@ -33,7 +33,7 @@ func (d Doc) config(cmdName string, r *REPL) {
|
||||||
func (r *REPL) helpBuiltin(ctx context.Context, args ucl.CallArgs) (any, error) {
|
func (r *REPL) helpBuiltin(ctx context.Context, args ucl.CallArgs) (any, error) {
|
||||||
switch {
|
switch {
|
||||||
case args.NArgs() == 0:
|
case args.NArgs() == 0:
|
||||||
names := maps.Keys(r.commandDocs)
|
names := momap.Keys(r.commandDocs)
|
||||||
sort.Strings(names)
|
sort.Strings(names)
|
||||||
|
|
||||||
tabWriter := tabwriter.NewWriter(os.Stdout, 0, 0, 1, ' ', 0)
|
tabWriter := tabwriter.NewWriter(os.Stdout, 0, 0, 1, ' ', 0)
|
||||||
|
|
|
||||||
31
ucl/builtins/fns_test.go
Normal file
31
ucl/builtins/fns_test.go
Normal 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)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -3,6 +3,7 @@ package builtins
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
|
|
||||||
"ucl.lmika.dev/ucl"
|
"ucl.lmika.dev/ucl"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -10,12 +11,46 @@ 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,
|
||||||
|
"sublist": listSublist,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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 +160,83 @@ 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
|
||||||
|
}
|
||||||
|
|
||||||
|
func listSublist(ctx context.Context, args ucl.CallArgs) (any, error) {
|
||||||
|
var (
|
||||||
|
l ucl.Listable
|
||||||
|
from, fromIdx int
|
||||||
|
to, toIdx int
|
||||||
|
)
|
||||||
|
|
||||||
|
if err := args.Bind(&l, &from); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if l == nil {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if args.NArgs() >= 1 {
|
||||||
|
if err := args.Bind(&to); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
fromIdx, toIdx = listPos(l, from), listPos(l, to)
|
||||||
|
} else {
|
||||||
|
if from < 0 {
|
||||||
|
fromIdx, toIdx = listPos(l, from), l.Len()
|
||||||
|
} else {
|
||||||
|
fromIdx, toIdx = 0, listPos(l, from)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if fromIdx > toIdx {
|
||||||
|
return ucl.NewListObject(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
newList := ucl.NewListObjectOfLength(toIdx - fromIdx)
|
||||||
|
for i := fromIdx; i < toIdx; i++ {
|
||||||
|
if err := newList.SetIndex(i-fromIdx, l.Index(i)); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return newList, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func listPos(l ucl.Listable, pos int) int {
|
||||||
|
if pos < 0 {
|
||||||
|
return max(l.Len()+pos, 0)
|
||||||
|
}
|
||||||
|
return min(pos, l.Len())
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,12 +2,51 @@ package builtins_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
"ucl.lmika.dev/ucl"
|
"ucl.lmika.dev/ucl"
|
||||||
"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 +84,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
|
||||||
|
|
@ -76,6 +151,47 @@ func TestLists_Uniq(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestStrs_Sublist(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
desc string
|
||||||
|
eval string
|
||||||
|
want any
|
||||||
|
wantErr bool
|
||||||
|
}{
|
||||||
|
{desc: "sublist 1", eval: `lists:sublist [h e l l o ',' ' ' w o r l d] 5`, want: []any{"h", "e", "l", "l", "o"}},
|
||||||
|
{desc: "sublist 2", eval: `lists:sublist [h e l l o ',' ' ' w o r l d] 5000`, want: []any{"h", "e", "l", "l", "o", ",", " ", "w", "o", "r", "l", "d"}},
|
||||||
|
{desc: "sublist 3", eval: `lists:sublist [h e l l o ',' ' ' w o r l d] -5`, want: []any{"w", "o", "r", "l", "d"}},
|
||||||
|
{desc: "sublist 4", eval: `lists:sublist [h e l l o ',' ' ' w o r l d] -5000`, want: []any{"h", "e", "l", "l", "o", ",", " ", "w", "o", "r", "l", "d"}},
|
||||||
|
{desc: "sublist 5", eval: `lists:sublist [h e l l o ',' ' ' w o r l d] 0 5`, want: []any{"h", "e", "l", "l", "o"}},
|
||||||
|
{desc: "sublist 6", eval: `lists:sublist [h e l l o ',' ' ' w o r l d] 3 10`, want: []any{"l", "o", ",", " ", "w", "o", "r"}},
|
||||||
|
{desc: "sublist 7", eval: `lists:sublist [h e l l o ',' ' ' w o r l d] 3 10000`, want: []any{"l", "o", ",", " ", "w", "o", "r", "l", "d"}},
|
||||||
|
{desc: "sublist 8", eval: `lists:sublist [h e l l o ',' ' ' w o r l d] 3 -5`, want: []any{"l", "o", ",", " "}},
|
||||||
|
{desc: "sublist 9", eval: `lists:sublist [h e l l o ',' ' ' w o r l d] -9 -5`, want: []any{"l", "o", ",", " "}},
|
||||||
|
{desc: "sublist 10", eval: `lists:sublist [h e l l o ',' ' ' w o r l d] 8 5`, want: []any{}},
|
||||||
|
{desc: "sublist 11", eval: `lists:sublist [] 8 5`, want: []any{}},
|
||||||
|
|
||||||
|
{desc: "err 1", eval: `lists:sublist`, wantErr: true},
|
||||||
|
{desc: "err 2", eval: `lists:sublist "asd"`, wantErr: true},
|
||||||
|
{desc: "err 3", eval: `lists:sublist ["asd"]`, wantErr: true},
|
||||||
|
{desc: "err 4", eval: `lists:sublist () 8 5`, wantErr: true},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.desc, func(t *testing.T) {
|
||||||
|
inst := ucl.New(
|
||||||
|
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 uclListOf(args ...any) *ucl.ListObject {
|
func uclListOf(args ...any) *ucl.ListObject {
|
||||||
newList := ucl.NewListObject()
|
newList := ucl.NewListObject()
|
||||||
for _, arg := range args {
|
for _, arg := range args {
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@ import (
|
||||||
"errors"
|
"errors"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"ucl.lmika.dev/ucl"
|
"ucl.lmika.dev/ucl"
|
||||||
)
|
)
|
||||||
|
|
@ -72,6 +73,14 @@ func (oh osHandlers) exec(ctx context.Context, args ucl.CallArgs) (any, error) {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if args.HasSwitch("in") {
|
||||||
|
var inVal string
|
||||||
|
if err := args.BindSwitch("in", &inVal); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
cmd.Stdin = strings.NewReader(inVal)
|
||||||
|
}
|
||||||
|
|
||||||
res, err := cmd.Output()
|
res, err := cmd.Output()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|
|
||||||
|
|
@ -44,7 +44,8 @@ func TestOS_Exec(t *testing.T) {
|
||||||
want any
|
want any
|
||||||
}{
|
}{
|
||||||
{descr: "run command 1", eval: `os:exec "echo" "hello, world"`, want: "hello, world\n"},
|
{descr: "run command 1", eval: `os:exec "echo" "hello, world"`, want: "hello, world\n"},
|
||||||
{descr: "run command 1", eval: `os:exec "date" "+%Y%m%d"`, want: time.Now().Format("20060102") + "\n"},
|
{descr: "run command 2", eval: `os:exec "date" "+%Y%m%d"`, want: time.Now().Format("20060102") + "\n"},
|
||||||
|
{descr: "run command 3", eval: `os:exec "tr" "[a-z]" "[A-Z]" -in "hello"`, want: "HELLO"},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@ import (
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"ucl.lmika.dev/ucl"
|
"ucl.lmika.dev/ucl"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -11,16 +12,74 @@ func Strs() ucl.Module {
|
||||||
return ucl.Module{
|
return ucl.Module{
|
||||||
Name: "strs",
|
Name: "strs",
|
||||||
Builtins: map[string]ucl.BuiltinHandler{
|
Builtins: map[string]ucl.BuiltinHandler{
|
||||||
"to-upper": toUpper,
|
"to-upper": toUpper,
|
||||||
"to-lower": toLower,
|
"to-lower": toLower,
|
||||||
"trim": trim,
|
"trim": trim,
|
||||||
"split": split,
|
"split": split,
|
||||||
"join": join,
|
"join": join,
|
||||||
"has-prefix": hasPrefix,
|
"has-prefix": hasPrefix,
|
||||||
|
"has-suffix": hasSuffix,
|
||||||
|
"trim-prefix": trimPrefix,
|
||||||
|
"trim-suffix": trimSuffix,
|
||||||
|
"substr": strsSubstr,
|
||||||
|
"replace": strsReplace,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func strsReplace(ctx context.Context, args ucl.CallArgs) (any, error) {
|
||||||
|
var (
|
||||||
|
s string
|
||||||
|
from string
|
||||||
|
to string
|
||||||
|
)
|
||||||
|
|
||||||
|
if err := args.Bind(&s, &from, &to); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var count = -1
|
||||||
|
if args.HasSwitch("n") {
|
||||||
|
if err := args.BindSwitch("n", &count); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return strings.Replace(s, from, to, count), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func strsSubstr(ctx context.Context, args ucl.CallArgs) (any, error) {
|
||||||
|
var (
|
||||||
|
s string
|
||||||
|
from int
|
||||||
|
to int
|
||||||
|
)
|
||||||
|
|
||||||
|
if err := args.Bind(&s, &from); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if args.NArgs() >= 1 {
|
||||||
|
if err := args.Bind(&to); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
ffr := strPos(s, from)
|
||||||
|
tfr := strPos(s, to)
|
||||||
|
if ffr > tfr {
|
||||||
|
return "", nil
|
||||||
|
}
|
||||||
|
return s[ffr:tfr], nil
|
||||||
|
|
||||||
|
} else {
|
||||||
|
if from < 0 {
|
||||||
|
return s[strPos(s, from):], nil
|
||||||
|
} else {
|
||||||
|
return s[:strPos(s, from)], nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func toUpper(ctx context.Context, args ucl.CallArgs) (any, error) {
|
func toUpper(ctx context.Context, args ucl.CallArgs) (any, error) {
|
||||||
var s string
|
var s string
|
||||||
if err := args.Bind(&s); err != nil {
|
if err := args.Bind(&s); err != nil {
|
||||||
|
|
@ -57,17 +116,40 @@ func hasPrefix(ctx context.Context, args ucl.CallArgs) (any, error) {
|
||||||
return strings.HasPrefix(s, prefix), nil
|
return strings.HasPrefix(s, prefix), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func split(ctx context.Context, args ucl.CallArgs) (any, error) {
|
func hasSuffix(ctx context.Context, args ucl.CallArgs) (any, error) {
|
||||||
var s string
|
var s, suffix string
|
||||||
if err := args.Bind(&s); err != nil {
|
if err := args.Bind(&s, &suffix); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
sep := ""
|
return strings.HasSuffix(s, suffix), nil
|
||||||
if args.NArgs() > 0 {
|
}
|
||||||
if err := args.Bind(&sep); err != nil {
|
|
||||||
return nil, err
|
func trimPrefix(ctx context.Context, args ucl.CallArgs) (any, error) {
|
||||||
}
|
var s, prefix string
|
||||||
|
if err := args.Bind(&s, &prefix); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return strings.TrimPrefix(s, prefix), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func trimSuffix(ctx context.Context, args ucl.CallArgs) (any, error) {
|
||||||
|
var s, suffix string
|
||||||
|
if err := args.Bind(&s, &suffix); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return strings.TrimSuffix(s, suffix), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func split(ctx context.Context, args ucl.CallArgs) (any, error) {
|
||||||
|
var (
|
||||||
|
s string
|
||||||
|
sep string
|
||||||
|
)
|
||||||
|
if err := args.Bind(&s, &sep); err != nil {
|
||||||
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
n := -1
|
n := -1
|
||||||
|
|
@ -128,3 +210,10 @@ func join(ctx context.Context, args ucl.CallArgs) (any, error) {
|
||||||
|
|
||||||
return nil, errors.New("expected listable or iterable as arg 1")
|
return nil, errors.New("expected listable or iterable as arg 1")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func strPos(s string, pos int) int {
|
||||||
|
if pos < 0 {
|
||||||
|
return max(len(s)+pos, 0)
|
||||||
|
}
|
||||||
|
return min(pos, len(s))
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,8 +2,9 @@ package builtins_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
"ucl.lmika.dev/ucl"
|
"ucl.lmika.dev/ucl"
|
||||||
"ucl.lmika.dev/ucl/builtins"
|
"ucl.lmika.dev/ucl/builtins"
|
||||||
)
|
)
|
||||||
|
|
@ -145,14 +146,15 @@ func TestStrs_Split(t *testing.T) {
|
||||||
{desc: "split 3", eval: `strs:split "" ";"`, want: []string{""}},
|
{desc: "split 3", eval: `strs:split "" ";"`, want: []string{""}},
|
||||||
{desc: "split 4", eval: `strs:split " " ";"`, want: []string{" "}},
|
{desc: "split 4", eval: `strs:split " " ";"`, want: []string{" "}},
|
||||||
|
|
||||||
{desc: "split by char 1", eval: `strs:split "123"`, want: []string{"1", "2", "3"}},
|
{desc: "split by char 1", eval: `strs:split "123" ""`, want: []string{"1", "2", "3"}},
|
||||||
|
|
||||||
{desc: "split max 1", eval: `strs:split "1,2,3" "," -max 2`, want: []string{"1", "2,3"}},
|
{desc: "split max 1", eval: `strs:split "1,2,3" "," -max 2`, want: []string{"1", "2,3"}},
|
||||||
{desc: "split max 2", eval: `strs:split "1,2,3" "," -max 5`, want: []string{"1", "2", "3"}},
|
{desc: "split max 2", eval: `strs:split "1,2,3" "," -max 5`, want: []string{"1", "2", "3"}},
|
||||||
|
|
||||||
{desc: "split by char max 1", eval: `strs:split "12345" -max 3`, want: []string{"1", "2", "345"}},
|
{desc: "split by char max 1", eval: `strs:split "12345" "" -max 3`, want: []string{"1", "2", "345"}},
|
||||||
|
|
||||||
{desc: "err 1", eval: `strs:split "1,2,3" -max []`, wantErr: true},
|
{desc: "err 1", eval: `strs:split "1,2,3" -max []`, wantErr: true},
|
||||||
|
{desc: "err 1", eval: `strs:split "1,2,3"`, wantErr: true},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
|
|
@ -202,3 +204,184 @@ func TestStrs_Join(t *testing.T) {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestStrs_HasSuffix(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
desc string
|
||||||
|
eval string
|
||||||
|
want any
|
||||||
|
wantErr bool
|
||||||
|
}{
|
||||||
|
{desc: "has suffix 1", eval: `strs:has-suffix "hello, world" "world"`, want: true},
|
||||||
|
{desc: "has suffix 2", eval: `strs:has-suffix "hello, world" "hello"`, want: false},
|
||||||
|
{desc: "has suffix 3", eval: `strs:has-suffix "" "world"`, want: false},
|
||||||
|
{desc: "has suffix 4", eval: `strs:has-suffix "hello" ""`, want: true},
|
||||||
|
{desc: "has suffix 5", eval: `strs:has-suffix "test.txt" ".txt"`, want: true},
|
||||||
|
{desc: "has suffix 6", eval: `strs:has-suffix "test.txt" ".pdf"`, want: false},
|
||||||
|
|
||||||
|
{desc: "err 1", eval: `strs:has-suffix`, wantErr: true},
|
||||||
|
{desc: "err 2", eval: `strs:has-suffix "asd"`, wantErr: true},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.desc, func(t *testing.T) {
|
||||||
|
inst := ucl.New(
|
||||||
|
ucl.WithModule(builtins.Strs()),
|
||||||
|
)
|
||||||
|
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 TestStrs_TrimPrefix(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
desc string
|
||||||
|
eval string
|
||||||
|
want any
|
||||||
|
wantErr bool
|
||||||
|
}{
|
||||||
|
{desc: "trim prefix 1", eval: `strs:trim-prefix "hello, world" "hello, "`, want: "world"},
|
||||||
|
{desc: "trim prefix 2", eval: `strs:trim-prefix "goodbye, world" "hello"`, want: "goodbye, world"},
|
||||||
|
{desc: "trim prefix 3", eval: `strs:trim-prefix "" "world"`, want: ""},
|
||||||
|
{desc: "trim prefix 4", eval: `strs:trim-prefix "hello" ""`, want: "hello"},
|
||||||
|
{desc: "trim prefix 5", eval: `strs:trim-prefix "test.txt" "test"`, want: ".txt"},
|
||||||
|
{desc: "trim prefix 6", eval: `strs:trim-prefix "/path/to/file" "/path/"`, want: "to/file"},
|
||||||
|
|
||||||
|
{desc: "err 1", eval: `strs:trim-prefix`, wantErr: true},
|
||||||
|
{desc: "err 2", eval: `strs:trim-prefix "asd"`, wantErr: true},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.desc, func(t *testing.T) {
|
||||||
|
inst := ucl.New(
|
||||||
|
ucl.WithModule(builtins.Strs()),
|
||||||
|
)
|
||||||
|
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 TestStrs_TrimSuffix(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
desc string
|
||||||
|
eval string
|
||||||
|
want any
|
||||||
|
wantErr bool
|
||||||
|
}{
|
||||||
|
{desc: "trim suffix 1", eval: `strs:trim-suffix "hello, world" ", world"`, want: "hello"},
|
||||||
|
{desc: "trim suffix 2", eval: `strs:trim-suffix "hello, world" "goodbye"`, want: "hello, world"},
|
||||||
|
{desc: "trim suffix 3", eval: `strs:trim-suffix "" "world"`, want: ""},
|
||||||
|
{desc: "trim suffix 4", eval: `strs:trim-suffix "hello" ""`, want: "hello"},
|
||||||
|
{desc: "trim suffix 5", eval: `strs:trim-suffix "test.txt" ".txt"`, want: "test"},
|
||||||
|
{desc: "trim suffix 6", eval: `strs:trim-suffix "file.backup" ".backup"`, want: "file"},
|
||||||
|
|
||||||
|
{desc: "err 1", eval: `strs:trim-suffix`, wantErr: true},
|
||||||
|
{desc: "err 2", eval: `strs:trim-suffix "asd"`, wantErr: true},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.desc, func(t *testing.T) {
|
||||||
|
inst := ucl.New(
|
||||||
|
ucl.WithModule(builtins.Strs()),
|
||||||
|
)
|
||||||
|
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 TestStrs_Substr(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
desc string
|
||||||
|
eval string
|
||||||
|
want any
|
||||||
|
wantErr bool
|
||||||
|
}{
|
||||||
|
{desc: "substr 1", eval: `strs:substr "hello, world" 5`, want: "hello"},
|
||||||
|
{desc: "substr 2", eval: `strs:substr "hello, world" 5000`, want: "hello, world"},
|
||||||
|
{desc: "substr 3", eval: `strs:substr "hello, world" -5`, want: "world"},
|
||||||
|
{desc: "substr 4", eval: `strs:substr "hello, world" -5000`, want: "hello, world"},
|
||||||
|
{desc: "substr 5", eval: `strs:substr "hello, world" 0 5`, want: "hello"},
|
||||||
|
{desc: "substr 6", eval: `strs:substr "hello, world" 3 10`, want: "lo, wor"},
|
||||||
|
{desc: "substr 7", eval: `strs:substr "hello, world" 3 10000`, want: "lo, world"},
|
||||||
|
{desc: "substr 8", eval: `strs:substr "hello, world" 3 -5`, want: "lo, "},
|
||||||
|
{desc: "substr 9", eval: `strs:substr "hello, world" -9 -5`, want: "lo, "},
|
||||||
|
{desc: "substr 10", eval: `strs:substr "hello, world" 8 5`, want: ""},
|
||||||
|
{desc: "substr 11", eval: `strs:substr "" 8 5`, want: ""},
|
||||||
|
{desc: "substr 12", eval: `strs:substr () 8 5`, want: ""},
|
||||||
|
|
||||||
|
{desc: "err 1", eval: `strs:substr`, wantErr: true},
|
||||||
|
{desc: "err 2", eval: `strs:substr "asd"`, wantErr: true},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.desc, func(t *testing.T) {
|
||||||
|
inst := ucl.New(
|
||||||
|
ucl.WithModule(builtins.Strs()),
|
||||||
|
)
|
||||||
|
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 TestStrs_Replace(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
desc string
|
||||||
|
eval string
|
||||||
|
want any
|
||||||
|
wantErr bool
|
||||||
|
}{
|
||||||
|
{desc: "replace 1", eval: `strs:replace "hello hello hello" "hello" "world"`, want: "world world world"},
|
||||||
|
{desc: "replace 2", eval: `strs:replace "hello hi hello hi" "hello" "lo"`, want: "lo hi lo hi"},
|
||||||
|
{desc: "replace 3", eval: `strs:replace "hello hello hello" "hello" "world" -n 1`, want: "world hello hello"},
|
||||||
|
{desc: "replace 4", eval: `strs:replace "hello hello hello" "hello" "world" -n 2`, want: "world world hello"},
|
||||||
|
{desc: "replace 5", eval: `strs:replace "hello hello hello" "hello" "world" -n 0`, want: "hello hello hello"},
|
||||||
|
{desc: "replace 6", eval: `strs:replace "each one" "" "|"`, want: "|e|a|c|h| |o|n|e|"},
|
||||||
|
{desc: "replace 7", eval: `strs:replace "hello hello hello" "hello" ""`, want: " "},
|
||||||
|
{desc: "replace 8", eval: `strs:replace "nothing to replace here" "what" "why"`, want: "nothing to replace here"},
|
||||||
|
{desc: "replace 9", eval: `strs:replace "" "what" "why"`, want: ""},
|
||||||
|
{desc: "replace 10", eval: `strs:replace "" "hello" "world" -n 0`, want: ""},
|
||||||
|
|
||||||
|
{desc: "err 1", eval: `strs:replace`, wantErr: true},
|
||||||
|
{desc: "err 2", eval: `strs:replace "asd"`, wantErr: true},
|
||||||
|
{desc: "err 3", eval: `strs:replace "asd" "asd"`, wantErr: true},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.desc, func(t *testing.T) {
|
||||||
|
inst := ucl.New(
|
||||||
|
ucl.WithModule(builtins.Strs()),
|
||||||
|
)
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -82,6 +82,7 @@ func New(opts ...InstOption) *Inst {
|
||||||
rootEC.addCmd("str", invokableFunc(strBuiltin))
|
rootEC.addCmd("str", invokableFunc(strBuiltin))
|
||||||
rootEC.addCmd("int", invokableFunc(intBuiltin))
|
rootEC.addCmd("int", invokableFunc(intBuiltin))
|
||||||
|
|
||||||
|
rootEC.addCmd("nil?", invokableFunc(notNilBuiltin))
|
||||||
rootEC.addCmd("!nil", invokableFunc(notNilBuiltin))
|
rootEC.addCmd("!nil", invokableFunc(notNilBuiltin))
|
||||||
|
|
||||||
rootEC.addCmd("add", invokableFunc(addBuiltin))
|
rootEC.addCmd("add", invokableFunc(addBuiltin))
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,7 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/lmika/gopkgs/fp/slices"
|
"lmika.dev/pkg/modash/moslice"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Object interface {
|
type Object interface {
|
||||||
|
|
@ -62,6 +62,11 @@ func NewListObject() *ListObject {
|
||||||
return &ListObject{}
|
return &ListObject{}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func NewListObjectOfLength(l int) *ListObject {
|
||||||
|
o := make(ListObject, l)
|
||||||
|
return &o
|
||||||
|
}
|
||||||
|
|
||||||
func (lo *ListObject) Append(o Object) {
|
func (lo *ListObject) Append(o Object) {
|
||||||
*lo = append(*lo, o)
|
*lo = append(*lo, o)
|
||||||
}
|
}
|
||||||
|
|
@ -626,7 +631,7 @@ func newStructProxyObject(v reflect.Value, orig reflect.Value) structProxyObject
|
||||||
return structProxyObject{
|
return structProxyObject{
|
||||||
v: v,
|
v: v,
|
||||||
orig: orig,
|
orig: orig,
|
||||||
vf: slices.Filter(reflect.VisibleFields(v.Type()), func(t reflect.StructField) bool { return t.IsExported() }),
|
vf: moslice.Filter(reflect.VisibleFields(v.Type()), func(t reflect.StructField) bool { return t.IsExported() }),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@ import (
|
||||||
"errors"
|
"errors"
|
||||||
"reflect"
|
"reflect"
|
||||||
|
|
||||||
"github.com/lmika/gopkgs/fp/slices"
|
"lmika.dev/pkg/modash/moslice"
|
||||||
)
|
)
|
||||||
|
|
||||||
type BuiltinHandler func(ctx context.Context, args CallArgs) (any, error)
|
type BuiltinHandler func(ctx context.Context, args CallArgs) (any, error)
|
||||||
|
|
@ -291,7 +291,7 @@ func (i Invokable) Invoke(ctx context.Context, args ...any) (any, error) {
|
||||||
inst: i.inst,
|
inst: i.inst,
|
||||||
}
|
}
|
||||||
|
|
||||||
invArgs.args, err = slices.MapWithError(args, func(a any) (Object, error) {
|
invArgs.args, err = moslice.MapWithError(args, func(a any) (Object, error) {
|
||||||
return fromGoValue(a)
|
return fromGoValue(a)
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue