diff --git a/go.mod b/go.mod index 6cd9c64..d8f76e7 100644 --- a/go.mod +++ b/go.mod @@ -26,10 +26,10 @@ require ( github.com/muesli/ansi v0.0.0-20211031195517-c9f0611b6c70 github.com/muesli/reflow v0.3.0 github.com/pkg/errors v0.9.1 - github.com/stretchr/testify v1.9.0 + github.com/stretchr/testify v1.10.0 golang.design/x/clipboard v0.6.2 golang.org/x/exp v0.0.0-20230108222341-4b8118a2686a - ucl.lmika.dev v0.0.0-20250525023717-3076897eb73e + ucl.lmika.dev v0.0.0-20250527114213-41b4fdb00382 ) require ( @@ -77,4 +77,5 @@ require ( golang.org/x/text v0.9.0 // indirect google.golang.org/appengine v1.6.7 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect + lmika.dev/pkg/modash v0.0.0-20250216001243-c73e50a0913d // indirect ) diff --git a/go.sum b/go.sum index 462dd4c..2bb8119 100644 --- a/go.sum +++ b/go.sum @@ -156,6 +156,7 @@ github.com/sahilm/fuzzy v0.1.0/go.mod h1:VFvziUEIMCrT6A6tw2RFIXPXXmzXbOsSHF0DOI8 github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 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/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/vmihailenco/msgpack v4.0.4+incompatible h1:dSLoQfGFAo3F6OoNhwUmLwVgaUXK79GlxNBwueZn0xI= github.com/vmihailenco/msgpack v4.0.4+incompatible/go.mod h1:fy3FlTQTDXWkZ7Bh6AcGMlsjHatGryHQYUTf1ShIgkk= github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= @@ -247,5 +248,13 @@ gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +lmika.dev/pkg/modash v0.0.0-20250216001243-c73e50a0913d h1:x5aMBOkCr4cjJyFmq+qJVUsByfffD9k56HYDx1yZSR4= +lmika.dev/pkg/modash v0.0.0-20250216001243-c73e50a0913d/go.mod h1:8NDl/yR1eCCEhip9FJlVuMNXIeaztQ0Ks/tizExFcTI= ucl.lmika.dev v0.0.0-20250525023717-3076897eb73e h1:N+HzQUunDUvdjAzbSDtHQZVZ1k+XHbVgbNwmc+EKmlQ= ucl.lmika.dev v0.0.0-20250525023717-3076897eb73e/go.mod h1:/MMZKm6mOMtnY4I8TYEot4Pc8dKEy+/IAQo1VdpA5EY= +ucl.lmika.dev v0.0.0-20250527110948-e869e6c9bd4d h1:SlmmY92u7nvPW6xa66n2ZPfCOx90uNp1KkJZ1IDF6K0= +ucl.lmika.dev v0.0.0-20250527110948-e869e6c9bd4d/go.mod h1:/MMZKm6mOMtnY4I8TYEot4Pc8dKEy+/IAQo1VdpA5EY= +ucl.lmika.dev v0.0.0-20250527112110-03e6878524a1 h1:e++1/TfwVKdWi1TmO+kfCdO2+lCTKCrh1m4ps0p7UUM= +ucl.lmika.dev v0.0.0-20250527112110-03e6878524a1/go.mod h1:/MMZKm6mOMtnY4I8TYEot4Pc8dKEy+/IAQo1VdpA5EY= +ucl.lmika.dev v0.0.0-20250527114213-41b4fdb00382 h1:rDJtNrcKVmEqLep1l2YrodPjCfq+/yl7p8EZUrKW7Aw= +ucl.lmika.dev v0.0.0-20250527114213-41b4fdb00382/go.mod h1:/MMZKm6mOMtnY4I8TYEot4Pc8dKEy+/IAQo1VdpA5EY= diff --git a/internal/common/ui/commandctrl/cmdpacks/modav.go b/internal/common/ui/commandctrl/cmdpacks/modav.go new file mode 100644 index 0000000..977a7c1 --- /dev/null +++ b/internal/common/ui/commandctrl/cmdpacks/modav.go @@ -0,0 +1,52 @@ +package cmdpacks + +import ( + "context" + "github.com/aws/aws-sdk-go-v2/service/dynamodb/types" + "ucl.lmika.dev/ucl" +) + +type avModule struct { +} + +func (avModule) avTrue(ctx context.Context, args ucl.CallArgs) (_ any, err error) { + return attributeValueProxy{value: &types.AttributeValueMemberBOOL{Value: true}}, nil +} + +func (avModule) avFalse(ctx context.Context, args ucl.CallArgs) (_ any, err error) { + return attributeValueProxy{value: &types.AttributeValueMemberBOOL{Value: false}}, nil +} + +func (avModule) avNull(ctx context.Context, args ucl.CallArgs) (_ any, err error) { + return attributeValueProxy{value: &types.AttributeValueMemberNULL{Value: true}}, nil +} + +func (avModule) avStringSet(ctx context.Context, args ucl.CallArgs) (_ any, err error) { + var listable ucl.Listable + + if err := args.Bind(&listable); err != nil { + return nil, err + } + + ss := make([]string, listable.Len()) + for i := 0; i < listable.Len(); i++ { + item := listable.Index(i) + ss[i] = item.String() + } + + return attributeValueProxy{value: &types.AttributeValueMemberSS{Value: ss}}, nil +} + +func moduleAttrValue() ucl.Module { + m := avModule{} + + return ucl.Module{ + Name: "av", + Builtins: map[string]ucl.BuiltinHandler{ + "true": m.avTrue, + "false": m.avFalse, + "null": m.avNull, + "string-set": m.avStringSet, + }, + } +} diff --git a/internal/common/ui/commandctrl/cmdpacks/modrs.go b/internal/common/ui/commandctrl/cmdpacks/modrs.go index dc84692..addcaeb 100644 --- a/internal/common/ui/commandctrl/cmdpacks/modrs.go +++ b/internal/common/ui/commandctrl/cmdpacks/modrs.go @@ -3,12 +3,12 @@ package cmdpacks import ( "context" "github.com/aws/aws-sdk-go-v2/service/dynamodb/types" + "github.com/pkg/errors" "lmika.dev/cmd/dynamo-browse/internal/common/ui/commandctrl" "lmika.dev/cmd/dynamo-browse/internal/dynamo-browse/controllers" "lmika.dev/cmd/dynamo-browse/internal/dynamo-browse/models" "lmika.dev/cmd/dynamo-browse/internal/dynamo-browse/models/queryexpr" "lmika.dev/cmd/dynamo-browse/internal/dynamo-browse/services/tables" - "github.com/pkg/errors" "time" "ucl.lmika.dev/repl" "ucl.lmika.dev/ucl" @@ -252,8 +252,12 @@ func (rs *rsModule) rsSet(ctx context.Context, args ucl.CallArgs) (_ any, err er return nil, err } - // TEMP: attribute is always S - if err := q.SetEvalItem(item.item, &types.AttributeValueMemberS{Value: val.String()}); err != nil { + vs, err := mapUCLObjectToAttributeType(val) + if err != nil { + return nil, err + } + + if err := q.SetEvalItem(item.item, vs); err != nil { return nil, err } item.resultSet.SetDirty(item.idx, true) diff --git a/internal/common/ui/commandctrl/cmdpacks/proxy.go b/internal/common/ui/commandctrl/cmdpacks/proxy.go index 0e61a23..4a72a5d 100644 --- a/internal/common/ui/commandctrl/cmdpacks/proxy.go +++ b/internal/common/ui/commandctrl/cmdpacks/proxy.go @@ -3,6 +3,7 @@ package cmdpacks import ( "fmt" "github.com/aws/aws-sdk-go-v2/service/dynamodb/types" + "github.com/pkg/errors" "lmika.dev/cmd/dynamo-browse/internal/dynamo-browse/models" "maps" "strconv" @@ -200,6 +201,18 @@ func (tp itemProxy) Each(fn func(k string, v ucl.Object) error) error { return nil } +type attributeValueProxy struct { + value types.AttributeValue +} + +func (ip attributeValueProxy) String() string { + return "attributeValueProxy()" +} + +func (ip attributeValueProxy) Truthy() bool { + return ip.value != nil +} + func convertAttributeValueToUCLObject(attrValue types.AttributeValue) ucl.Object { switch t := attrValue.(type) { case *types.AttributeValueMemberS: @@ -210,7 +223,58 @@ func convertAttributeValueToUCLObject(attrValue types.AttributeValue) ucl.Object return nil } return ucl.IntObject(i) + case *types.AttributeValueMemberBOOL: + return ucl.BoolObject(t.Value) + case *types.AttributeValueMemberL: + vs := make(ucl.ListObject, len(t.Value)) + for i, v := range t.Value { + vs[i] = convertAttributeValueToUCLObject(v) + } + return &vs + case *types.AttributeValueMemberM: + hs := make(ucl.HashObject) + for k, v := range t.Value { + hs[k] = convertAttributeValueToUCLObject(v) + } + return hs } - // TODO: the rest - return nil + + return attributeValueProxy{value: attrValue} +} + +func mapUCLObjectToAttributeType(obj ucl.Object) (types.AttributeValue, error) { + switch t := obj.(type) { + case ucl.StringObject: + return &types.AttributeValueMemberS{Value: t.String()}, nil + case ucl.IntObject: + return &types.AttributeValueMemberN{Value: t.String()}, nil + case ucl.BoolObject: + return &types.AttributeValueMemberBOOL{Value: t.Truthy()}, nil + case ucl.Listable: + vals := make([]types.AttributeValue, t.Len()) + for i := 0; i < t.Len(); i++ { + v, err := mapUCLObjectToAttributeType(t.Index(i)) + if err != nil { + return nil, err + } + vals[i] = v + } + return &types.AttributeValueMemberL{Value: vals}, nil + case ucl.Hashable: + vals := make(map[string]types.AttributeValue) + if err := t.Each(func(k string, v ucl.Object) error { + vv, err := mapUCLObjectToAttributeType(v) + if err != nil { + return err + } + vals[k] = vv + return nil + }); err != nil { + return nil, err + } + return &types.AttributeValueMemberM{Value: vals}, nil + case attributeValueProxy: + return t.value, nil + } + return nil, errors.New("unsupported attribute type") } diff --git a/internal/common/ui/commandctrl/cmdpacks/stdcmds.go b/internal/common/ui/commandctrl/cmdpacks/stdcmds.go index 0bdc0e7..fce0b96 100644 --- a/internal/common/ui/commandctrl/cmdpacks/stdcmds.go +++ b/internal/common/ui/commandctrl/cmdpacks/stdcmds.go @@ -3,12 +3,12 @@ package cmdpacks import ( "context" tea "github.com/charmbracelet/bubbletea" + "github.com/pkg/errors" "lmika.dev/cmd/dynamo-browse/internal/common/ui/commandctrl" "lmika.dev/cmd/dynamo-browse/internal/dynamo-browse/controllers" "lmika.dev/cmd/dynamo-browse/internal/dynamo-browse/models" "lmika.dev/cmd/dynamo-browse/internal/dynamo-browse/services" "lmika.dev/cmd/dynamo-browse/internal/dynamo-browse/services/tables" - "github.com/pkg/errors" "ucl.lmika.dev/repl" "ucl.lmika.dev/ucl" ) @@ -398,6 +398,7 @@ func (sc StandardCommands) InstOptions() []ucl.InstOption { ucl.WithModule(sc.modUI), ucl.WithModule(modulePB(sc.PBProvider)), ucl.WithModule(moduleOpt(sc.SettingsController)), + ucl.WithModule(moduleAttrValue()), } }