ucl: Have started adding some of the psudo variables
This commit is contained in:
parent
18ffe85a56
commit
40f8dd76e2
2
go.mod
2
go.mod
|
@ -117,5 +117,5 @@ require (
|
||||||
golang.org/x/text v0.9.0 // indirect
|
golang.org/x/text v0.9.0 // indirect
|
||||||
google.golang.org/appengine v1.6.7 // indirect
|
google.golang.org/appengine v1.6.7 // indirect
|
||||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||||
ucl.lmika.dev v0.0.0-20250517115116-0f1ceba0902e // indirect
|
ucl.lmika.dev v0.0.0-20250518033831-f79e91e26d78 // indirect
|
||||||
)
|
)
|
||||||
|
|
10
go.sum
10
go.sum
|
@ -444,3 +444,13 @@ ucl.lmika.dev v0.0.0-20250517003439-109be33d1495 h1:r46r+7T59Drm+in7TEWKCZfFYIM0
|
||||||
ucl.lmika.dev v0.0.0-20250517003439-109be33d1495/go.mod h1:/MMZKm6mOMtnY4I8TYEot4Pc8dKEy+/IAQo1VdpA5EY=
|
ucl.lmika.dev v0.0.0-20250517003439-109be33d1495/go.mod h1:/MMZKm6mOMtnY4I8TYEot4Pc8dKEy+/IAQo1VdpA5EY=
|
||||||
ucl.lmika.dev v0.0.0-20250517115116-0f1ceba0902e h1:CQ+qPqI5lYiiEM0tNAr4jS0iMz16bFqOui5mU3AHsCU=
|
ucl.lmika.dev v0.0.0-20250517115116-0f1ceba0902e h1:CQ+qPqI5lYiiEM0tNAr4jS0iMz16bFqOui5mU3AHsCU=
|
||||||
ucl.lmika.dev v0.0.0-20250517115116-0f1ceba0902e/go.mod h1:/MMZKm6mOMtnY4I8TYEot4Pc8dKEy+/IAQo1VdpA5EY=
|
ucl.lmika.dev v0.0.0-20250517115116-0f1ceba0902e/go.mod h1:/MMZKm6mOMtnY4I8TYEot4Pc8dKEy+/IAQo1VdpA5EY=
|
||||||
|
ucl.lmika.dev v0.0.0-20250517212052-51e35aa9a675 h1:kGKh3zj6lMzOrGAquFW7ROgx9/6nwJ8DXiSLtceRiak=
|
||||||
|
ucl.lmika.dev v0.0.0-20250517212052-51e35aa9a675/go.mod h1:/MMZKm6mOMtnY4I8TYEot4Pc8dKEy+/IAQo1VdpA5EY=
|
||||||
|
ucl.lmika.dev v0.0.0-20250517212757-33d04ba18db4 h1:rnietWu2B+NXLqKfo7jgf6r+srMwxFa5eizywkq4LFk=
|
||||||
|
ucl.lmika.dev v0.0.0-20250517212757-33d04ba18db4/go.mod h1:/MMZKm6mOMtnY4I8TYEot4Pc8dKEy+/IAQo1VdpA5EY=
|
||||||
|
ucl.lmika.dev v0.0.0-20250517213937-94aad417121d h1:CMcA8aQV6iiPK75EbHvoIVZhZmSggfrWNhK9BFm2aIg=
|
||||||
|
ucl.lmika.dev v0.0.0-20250517213937-94aad417121d/go.mod h1:/MMZKm6mOMtnY4I8TYEot4Pc8dKEy+/IAQo1VdpA5EY=
|
||||||
|
ucl.lmika.dev v0.0.0-20250518024533-f4be44fcbc94 h1:x3IRtT1jbedblimi2hesKGBihg243+wNOSvagCPR0KU=
|
||||||
|
ucl.lmika.dev v0.0.0-20250518024533-f4be44fcbc94/go.mod h1:/MMZKm6mOMtnY4I8TYEot4Pc8dKEy+/IAQo1VdpA5EY=
|
||||||
|
ucl.lmika.dev v0.0.0-20250518033831-f79e91e26d78 h1:lbOZUb6whYMLI4win5QL+eLSgqc3N9TtTgT8hTipNl8=
|
||||||
|
ucl.lmika.dev v0.0.0-20250518033831-f79e91e26d78/go.mod h1:/MMZKm6mOMtnY4I8TYEot4Pc8dKEy+/IAQo1VdpA5EY=
|
||||||
|
|
|
@ -45,12 +45,10 @@ func (rs *rsModule) rsNew(ctx context.Context, args ucl.CallArgs) (_ any, err er
|
||||||
return nil, errors.New("no table specified")
|
return nil, errors.New("no table specified")
|
||||||
}
|
}
|
||||||
|
|
||||||
return ResultSetProxy{
|
return newResultSetProxy(&models.ResultSet{
|
||||||
RS: &models.ResultSet{
|
TableInfo: tableInfo,
|
||||||
TableInfo: tableInfo,
|
Created: time.Now(),
|
||||||
Created: time.Now(),
|
}), nil
|
||||||
},
|
|
||||||
}, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var rsQueryDoc = repl.Doc{
|
var rsQueryDoc = repl.Doc{
|
||||||
|
@ -128,9 +126,7 @@ func (rs *rsModule) rsQuery(ctx context.Context, args ucl.CallArgs) (any, error)
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return ResultSetProxy{
|
return newResultSetProxy(newResultSet), nil
|
||||||
RS: newResultSet,
|
|
||||||
}, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func moduleRS(tableService *tables.Service, state *controllers.State) ucl.Module {
|
func moduleRS(tableService *tables.Service, state *controllers.State) ucl.Module {
|
||||||
|
|
|
@ -1,7 +1,180 @@
|
||||||
package cmdpacks
|
package cmdpacks
|
||||||
|
|
||||||
import "github.com/lmika/dynamo-browse/internal/dynamo-browse/models"
|
import (
|
||||||
|
"fmt"
|
||||||
|
"github.com/aws/aws-sdk-go-v2/service/dynamodb/types"
|
||||||
|
"github.com/lmika/dynamo-browse/internal/dynamo-browse/models"
|
||||||
|
"maps"
|
||||||
|
"strconv"
|
||||||
|
"ucl.lmika.dev/ucl"
|
||||||
|
)
|
||||||
|
|
||||||
type ResultSetProxy struct {
|
type proxyFields[T any] map[string]func(t T) ucl.Object
|
||||||
RS *models.ResultSet
|
|
||||||
|
type simpleProxy[T comparable] struct {
|
||||||
|
value T
|
||||||
|
fields proxyFields[T]
|
||||||
|
}
|
||||||
|
|
||||||
|
func (tp simpleProxy[T]) String() string {
|
||||||
|
return fmt.Sprint(tp.value)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (tp simpleProxy[T]) Truthy() bool {
|
||||||
|
var zeroT T
|
||||||
|
return tp.value != zeroT
|
||||||
|
}
|
||||||
|
|
||||||
|
func (tp simpleProxy[T]) Len() int {
|
||||||
|
return len(tp.fields)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (tp simpleProxy[T]) Value(k string) ucl.Object {
|
||||||
|
f, ok := tp.fields[k]
|
||||||
|
if !ok {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return f(tp.value)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (tp simpleProxy[T]) Each(fn func(k string, v ucl.Object) error) error {
|
||||||
|
for key := range maps.Keys(tp.fields) {
|
||||||
|
if err := fn(key, tp.Value(key)); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type simpleProxyList[T comparable] struct {
|
||||||
|
values []T
|
||||||
|
converter func(T) ucl.Object
|
||||||
|
}
|
||||||
|
|
||||||
|
func newSimpleProxyList[T comparable](values []T, converter func(T) ucl.Object) simpleProxyList[T] {
|
||||||
|
return simpleProxyList[T]{values: values, converter: converter}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (tp simpleProxyList[T]) String() string {
|
||||||
|
return fmt.Sprint(tp.values)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (tp simpleProxyList[T]) Truthy() bool {
|
||||||
|
return len(tp.values) > 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (tp simpleProxyList[T]) Len() int {
|
||||||
|
return len(tp.values)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (tp simpleProxyList[T]) Index(k int) ucl.Object {
|
||||||
|
return tp.converter(tp.values[k])
|
||||||
|
}
|
||||||
|
|
||||||
|
func newResultSetProxy(rs *models.ResultSet) ucl.Object {
|
||||||
|
return simpleProxy[*models.ResultSet]{value: rs, fields: resultSetProxyFields}
|
||||||
|
}
|
||||||
|
|
||||||
|
var resultSetProxyFields = proxyFields[*models.ResultSet]{
|
||||||
|
"Table": func(t *models.ResultSet) ucl.Object { return newTableProxy(t.TableInfo) },
|
||||||
|
"Items": func(t *models.ResultSet) ucl.Object { return resultSetItemsProxy{t} },
|
||||||
|
}
|
||||||
|
|
||||||
|
func newTableProxy(table *models.TableInfo) ucl.Object {
|
||||||
|
return simpleProxy[*models.TableInfo]{value: table, fields: tableProxyFields}
|
||||||
|
}
|
||||||
|
|
||||||
|
var tableProxyFields = proxyFields[*models.TableInfo]{
|
||||||
|
"Name": func(t *models.TableInfo) ucl.Object { return ucl.StringObject(t.Name) },
|
||||||
|
"Keys": func(t *models.TableInfo) ucl.Object { return newKeyAttributeProxy(t.Keys) },
|
||||||
|
"DefinedAttributes": func(t *models.TableInfo) ucl.Object { return ucl.StringListObject(t.DefinedAttributes) },
|
||||||
|
"GSIs": func(t *models.TableInfo) ucl.Object { return newSimpleProxyList(t.GSIs, newGSIProxy) },
|
||||||
|
}
|
||||||
|
|
||||||
|
func newKeyAttributeProxy(keyAttrs models.KeyAttribute) ucl.Object {
|
||||||
|
return simpleProxy[models.KeyAttribute]{value: keyAttrs, fields: keyAttributeProxyFields}
|
||||||
|
}
|
||||||
|
|
||||||
|
var keyAttributeProxyFields = proxyFields[models.KeyAttribute]{
|
||||||
|
"PartitionKey": func(t models.KeyAttribute) ucl.Object { return ucl.StringObject(t.PartitionKey) },
|
||||||
|
"SortKey": func(t models.KeyAttribute) ucl.Object { return ucl.StringObject(t.SortKey) },
|
||||||
|
}
|
||||||
|
|
||||||
|
func newGSIProxy(gsi models.TableGSI) ucl.Object {
|
||||||
|
return simpleProxy[models.TableGSI]{value: gsi, fields: gsiProxyFields}
|
||||||
|
}
|
||||||
|
|
||||||
|
var gsiProxyFields = proxyFields[models.TableGSI]{
|
||||||
|
"Name": func(t models.TableGSI) ucl.Object { return ucl.StringObject(t.Name) },
|
||||||
|
"Keys": func(t models.TableGSI) ucl.Object { return newKeyAttributeProxy(t.Keys) },
|
||||||
|
}
|
||||||
|
|
||||||
|
type resultSetItemsProxy struct {
|
||||||
|
resultSet *models.ResultSet
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ip resultSetItemsProxy) String() string {
|
||||||
|
return "items"
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ip resultSetItemsProxy) Truthy() bool {
|
||||||
|
return len(ip.resultSet.Items()) > 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (tp resultSetItemsProxy) Len() int {
|
||||||
|
return len(tp.resultSet.Items())
|
||||||
|
}
|
||||||
|
|
||||||
|
func (tp resultSetItemsProxy) Index(k int) ucl.Object {
|
||||||
|
return itemProxy{resultSet: tp.resultSet, idx: k, item: tp.resultSet.Items()[k]}
|
||||||
|
}
|
||||||
|
|
||||||
|
type itemProxy struct {
|
||||||
|
resultSet *models.ResultSet
|
||||||
|
idx int
|
||||||
|
item models.Item
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ip itemProxy) String() string {
|
||||||
|
return "item"
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ip itemProxy) Truthy() bool {
|
||||||
|
return len(ip.item) > 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (tp itemProxy) Len() int {
|
||||||
|
return len(tp.item)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (tp itemProxy) Value(k string) ucl.Object {
|
||||||
|
f, ok := tp.item[k]
|
||||||
|
if !ok {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return convertAttributeValueToUCLObject(f)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (tp itemProxy) Each(fn func(k string, v ucl.Object) error) error {
|
||||||
|
for key := range maps.Keys(tp.item) {
|
||||||
|
if err := fn(key, tp.Value(key)); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func convertAttributeValueToUCLObject(attrValue types.AttributeValue) ucl.Object {
|
||||||
|
switch t := attrValue.(type) {
|
||||||
|
case *types.AttributeValueMemberS:
|
||||||
|
return ucl.StringObject(t.Value)
|
||||||
|
case *types.AttributeValueMemberN:
|
||||||
|
i, err := strconv.ParseInt(t.Value, 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return ucl.IntObject(i)
|
||||||
|
}
|
||||||
|
// TODO: the rest
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,28 +4,59 @@ import (
|
||||||
"context"
|
"context"
|
||||||
"github.com/lmika/dynamo-browse/internal/common/ui/commandctrl"
|
"github.com/lmika/dynamo-browse/internal/common/ui/commandctrl"
|
||||||
"github.com/lmika/dynamo-browse/internal/dynamo-browse/controllers"
|
"github.com/lmika/dynamo-browse/internal/dynamo-browse/controllers"
|
||||||
|
"github.com/lmika/dynamo-browse/internal/dynamo-browse/models"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
"log"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type tablePVar struct {
|
||||||
|
state *controllers.State
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rs tablePVar) Get(ctx context.Context) (any, error) {
|
||||||
|
return newTableProxy(rs.state.ResultSet().TableInfo), nil
|
||||||
|
}
|
||||||
|
|
||||||
type resultSetPVar struct {
|
type resultSetPVar struct {
|
||||||
state *controllers.State
|
state *controllers.State
|
||||||
readController *controllers.TableReadController
|
readController *controllers.TableReadController
|
||||||
}
|
}
|
||||||
|
|
||||||
func (rs resultSetPVar) Get(ctx context.Context) (any, error) {
|
func (rs resultSetPVar) Get(ctx context.Context) (any, error) {
|
||||||
return ResultSetProxy{rs.state.ResultSet()}, nil
|
return newResultSetProxy(rs.state.ResultSet()), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (rs resultSetPVar) Set(ctx context.Context, value any) error {
|
func (rs resultSetPVar) Set(ctx context.Context, value any) error {
|
||||||
rsVal, ok := value.(ResultSetProxy)
|
rsVal, ok := value.(simpleProxy[*models.ResultSet])
|
||||||
if !ok {
|
if !ok {
|
||||||
return errors.New("new value to @resultset is not a result set")
|
return errors.New("new value to @resultset is not a result set")
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Printf("type = %T", rsVal.RS)
|
msg := rs.readController.SetResultSet(rsVal.value)
|
||||||
|
|
||||||
msg := rs.readController.SetResultSet(rsVal.RS)
|
|
||||||
commandctrl.PostMsg(ctx, msg)
|
commandctrl.PostMsg(ctx, msg)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type itemPVar struct {
|
||||||
|
state *controllers.State
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rs itemPVar) Get(ctx context.Context) (any, error) {
|
||||||
|
selItem, ok := commandctrl.SelectedItemIndex(ctx)
|
||||||
|
if !ok {
|
||||||
|
return nil, errors.New("no item selected")
|
||||||
|
}
|
||||||
|
return itemProxy{rs.state.ResultSet(), selItem, rs.state.ResultSet().Items()[selItem]}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rs itemPVar) Set(ctx context.Context, value any) error {
|
||||||
|
rsVal, ok := value.(itemProxy)
|
||||||
|
if !ok {
|
||||||
|
return errors.New("new value to @item is not an item")
|
||||||
|
}
|
||||||
|
|
||||||
|
if msg := commandctrl.SetSelectedItemIndex(ctx, rsVal.idx); msg != nil {
|
||||||
|
commandctrl.PostMsg(ctx, msg)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
|
@ -386,4 +386,6 @@ func (sc StandardCommands) ConfigureUCL(ucl *ucl.Inst) {
|
||||||
// set-opt --> alias to opts:set
|
// set-opt --> alias to opts:set
|
||||||
|
|
||||||
ucl.SetPseudoVar("resultset", resultSetPVar{sc.State, sc.ReadController})
|
ucl.SetPseudoVar("resultset", resultSetPVar{sc.State, sc.ReadController})
|
||||||
|
ucl.SetPseudoVar("table", tablePVar{sc.State})
|
||||||
|
ucl.SetPseudoVar("item", itemPVar{sc.State})
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,3 +24,12 @@ func SelectedItemIndex(ctx context.Context) (int, bool) {
|
||||||
|
|
||||||
return cmdCtl.uiStateProvider.SelectedItemIndex(), true
|
return cmdCtl.uiStateProvider.SelectedItemIndex(), true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func SetSelectedItemIndex(ctx context.Context, newIdx int) tea.Msg {
|
||||||
|
cmdCtl, ok := ctx.Value(commandCtlKey).(*CommandController)
|
||||||
|
if !ok {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return cmdCtl.uiStateProvider.SetSelectedItemIndex(newIdx)
|
||||||
|
}
|
||||||
|
|
|
@ -2,6 +2,7 @@ package commandctrl
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
tea "github.com/charmbracelet/bubbletea"
|
||||||
"github.com/lmika/dynamo-browse/internal/dynamo-browse/services"
|
"github.com/lmika/dynamo-browse/internal/dynamo-browse/services"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -11,4 +12,5 @@ type IterProvider interface {
|
||||||
|
|
||||||
type UIStateProvider interface {
|
type UIStateProvider interface {
|
||||||
SelectedItemIndex() int
|
SelectedItemIndex() int
|
||||||
|
SetSelectedItemIndex(newIdx int) tea.Msg
|
||||||
}
|
}
|
||||||
|
|
|
@ -397,3 +397,7 @@ func (m *Model) promptToQuit() tea.Msg {
|
||||||
func (m *Model) SelectedItemIndex() int {
|
func (m *Model) SelectedItemIndex() int {
|
||||||
return m.tableView.SelectedItemIndex()
|
return m.tableView.SelectedItemIndex()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (m *Model) SetSelectedItemIndex(newIdx int) tea.Msg {
|
||||||
|
return m.tableView.SetSelectedItemIndex(newIdx)
|
||||||
|
}
|
||||||
|
|
|
@ -208,6 +208,27 @@ func (m *Model) SelectedItemIndex() int {
|
||||||
return selectedItem.itemIndex
|
return selectedItem.itemIndex
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (m *Model) SetSelectedItemIndex(newIdx int) tea.Msg {
|
||||||
|
cursor := m.table.Cursor()
|
||||||
|
switch {
|
||||||
|
case newIdx <= 0:
|
||||||
|
m.table.GoTop()
|
||||||
|
case newIdx >= len(m.rows)-1:
|
||||||
|
m.table.GoBottom()
|
||||||
|
case newIdx < cursor:
|
||||||
|
delta := cursor - newIdx
|
||||||
|
for d := 0; d < delta; d++ {
|
||||||
|
m.table.GoUp()
|
||||||
|
}
|
||||||
|
case newIdx > cursor:
|
||||||
|
delta := newIdx - cursor
|
||||||
|
for d := 0; d < delta; d++ {
|
||||||
|
m.table.GoDown()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return m.postSelectedItemChanged()
|
||||||
|
}
|
||||||
|
|
||||||
func (m *Model) selectedItem() (itemTableRow, bool) {
|
func (m *Model) selectedItem() (itemTableRow, bool) {
|
||||||
resultSet := m.resultSet
|
resultSet := m.resultSet
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue