diff --git a/go.mod b/go.mod index 0b87074..ccf156c 100644 --- a/go.mod +++ b/go.mod @@ -29,7 +29,7 @@ require ( 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-20250528113931-3a88c0c777d8 + ucl.lmika.dev v0.1.0 ) require ( @@ -77,5 +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 + lmika.dev/pkg/modash v0.0.0-20250619112300-0be0b6b35b1b // indirect ) diff --git a/go.sum b/go.sum index d443ccb..431cfe1 100644 --- a/go.sum +++ b/go.sum @@ -251,6 +251,8 @@ 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= +lmika.dev/pkg/modash v0.0.0-20250619112300-0be0b6b35b1b h1:Oymcj66pgyJ2CtGk9lPh06P4FOekllE1iPehDwaL0vw= +lmika.dev/pkg/modash v0.0.0-20250619112300-0be0b6b35b1b/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= @@ -261,3 +263,7 @@ ucl.lmika.dev v0.0.0-20250527114213-41b4fdb00382 h1:rDJtNrcKVmEqLep1l2YrodPjCfq+ ucl.lmika.dev v0.0.0-20250527114213-41b4fdb00382/go.mod h1:/MMZKm6mOMtnY4I8TYEot4Pc8dKEy+/IAQo1VdpA5EY= ucl.lmika.dev v0.0.0-20250528113931-3a88c0c777d8 h1:kC312X0SvM9YHtuS1r6Js+CgmSS+kSAMLj8cYFuI0+4= ucl.lmika.dev v0.0.0-20250528113931-3a88c0c777d8/go.mod h1:/MMZKm6mOMtnY4I8TYEot4Pc8dKEy+/IAQo1VdpA5EY= +ucl.lmika.dev v0.0.0-20250718121358-7c76e61b08e4 h1:4HF6Av2/cOXBmRfHBthHn2iHJhk9GvHAFg6Tu6LVUTA= +ucl.lmika.dev v0.0.0-20250718121358-7c76e61b08e4/go.mod h1:+HB5VAi0cI28mr3LbclJvv5lb/HclJ3R60x6cbjgt4c= +ucl.lmika.dev v0.1.0 h1:gIZvLjruY1buIH25cm1hcIOvZ/+BvsZ+f84xrhcS6pY= +ucl.lmika.dev v0.1.0/go.mod h1:+HB5VAi0cI28mr3LbclJvv5lb/HclJ3R60x6cbjgt4c= diff --git a/internal/common/ui/commandctrl/cmdpacks/modpb_test.go b/internal/common/ui/commandctrl/cmdpacks/modpb_test.go index 91aeb02..37f091e 100644 --- a/internal/common/ui/commandctrl/cmdpacks/modpb_test.go +++ b/internal/common/ui/commandctrl/cmdpacks/modpb_test.go @@ -1,8 +1,9 @@ package cmdpacks_test import ( - "github.com/stretchr/testify/assert" "testing" + + "github.com/stretchr/testify/assert" ) func TestModPB_Copy(t *testing.T) { @@ -10,8 +11,8 @@ func TestModPB_Copy(t *testing.T) { svc := newService(t) _, err := svc.CommandController.ExecuteAndWait(t.Context(), ` - $items = @resultset.Items - $skItems = $items | map { |i| $i.sk } | lists:uniq + items = @resultset.Items + skItems = $items | map { |i| $i.sk } | lists:uniq pb:copy ($skItems | strs:join "\n") `) assert.NoError(t, err) @@ -23,8 +24,8 @@ func TestModPB_Copy(t *testing.T) { svc := newService(t) _, err := svc.CommandController.ExecuteAndWait(t.Context(), ` - $items = @resultset.Items - $skItems = $items | map { |i| $i.alpha } | filter !nil | lists:uniq + items = @resultset.Items + skItems = $items | map { |i| $i.alpha } | filter !nil | lists:uniq pb:copy ($skItems | strs:join "\n") `) assert.NoError(t, err) diff --git a/internal/common/ui/commandctrl/cmdpacks/modrs_test.go b/internal/common/ui/commandctrl/cmdpacks/modrs_test.go index e9a7ac8..d4ce9a2 100644 --- a/internal/common/ui/commandctrl/cmdpacks/modrs_test.go +++ b/internal/common/ui/commandctrl/cmdpacks/modrs_test.go @@ -2,10 +2,11 @@ package cmdpacks_test import ( "fmt" + "testing" + + "github.com/stretchr/testify/assert" "lmika.dev/cmd/dynamo-browse/internal/common/ui/commandctrl/cmdpacks" "lmika.dev/cmd/dynamo-browse/internal/dynamo-browse/models" - "github.com/stretchr/testify/assert" - "testing" ) func TestModRS_New(t *testing.T) { @@ -70,7 +71,7 @@ func TestModRS_Union(t *testing.T) { svc := newService(t, withDefaultLimit(2)) rsProxy, err := svc.CommandController.ExecuteAndWait(t.Context(), ` - $mr = rs:union @resultset (rs:next-page @resultset) + mr = rs:union @resultset (rs:next-page @resultset) assert (eq (len $mr.Items) 3) "expected len == 3" assert (eq $mr.Items.(0).pk "abc") "expected 0.pk" @@ -152,3 +153,41 @@ func TestModRS_Query(t *testing.T) { }) } } + +func TestModRS_First(t *testing.T) { + tests := []struct { + descr string + cmd string + }{ + { + descr: "returns the first item in sorted order", + cmd: ` + rs = rs:query 'pk="abc"' -table service-test-data + assert (eq $rs.First.pk "abc") "expected First.pk == abc" + assert (eq $rs.First.sk "111") "expected First.sk == 111" + `, + }, { + descr: "returns the first item in single item", + cmd: ` + rs = rs:query 'pk="abc" and sk="222"' -table service-test-data + assert (eq $rs.First.pk "abc") "expected First.pk == abc" + assert (eq $rs.First.sk "222") "expected First.sk == 222" + assert (eq $rs.First.beta 1231) "expected First.beta == 1231" + `, + }, { + descr: "returns the first item in empty result", + cmd: ` + rs = rs:query 'pk="zzz"' -table service-test-data + assert (eq $rs.First ()) "expected First to be nil" + `, + }, + } + for _, tt := range tests { + t.Run(tt.descr, func(t *testing.T) { + svc := newService(t) + + _, err := svc.CommandController.ExecuteAndWait(t.Context(), tt.cmd) + assert.NoError(t, err) + }) + } +} diff --git a/internal/common/ui/commandctrl/cmdpacks/proxy.go b/internal/common/ui/commandctrl/cmdpacks/proxy.go index 4a72a5d..8461517 100644 --- a/internal/common/ui/commandctrl/cmdpacks/proxy.go +++ b/internal/common/ui/commandctrl/cmdpacks/proxy.go @@ -2,11 +2,12 @@ package cmdpacks import ( "fmt" + "maps" + "strconv" + "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" "ucl.lmika.dev/ucl" ) @@ -99,6 +100,13 @@ var resultSetProxyFields = &proxyInfo[*models.ResultSet]{ "Table": func(t *models.ResultSet) ucl.Object { return newTableProxy(t.TableInfo) }, "Items": func(t *models.ResultSet) ucl.Object { return resultSetItemsProxy{t} }, "HasNextPage": func(t *models.ResultSet) ucl.Object { return ucl.BoolObject(t.HasNextPage()) }, + "First": func(t *models.ResultSet) ucl.Object { + items := t.Items() + if len(items) == 0 { + return nil + } + return itemProxy{resultSet: t, idx: 0, item: items[0]} + }, }, }