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 | ||||
| 	google.golang.org/appengine v1.6.7 // 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-20250517115116-0f1ceba0902e h1:CQ+qPqI5lYiiEM0tNAr4jS0iMz16bFqOui5mU3AHsCU= | ||||
| 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 ResultSetProxy{ | ||||
| 		RS: &models.ResultSet{ | ||||
| 			TableInfo: tableInfo, | ||||
| 			Created:   time.Now(), | ||||
| 		}, | ||||
| 	}, nil | ||||
| 	return newResultSetProxy(&models.ResultSet{ | ||||
| 		TableInfo: tableInfo, | ||||
| 		Created:   time.Now(), | ||||
| 	}), nil | ||||
| } | ||||
| 
 | ||||
| var rsQueryDoc = repl.Doc{ | ||||
|  | @ -128,9 +126,7 @@ func (rs *rsModule) rsQuery(ctx context.Context, args ucl.CallArgs) (any, error) | |||
| 		return nil, err | ||||
| 	} | ||||
| 
 | ||||
| 	return ResultSetProxy{ | ||||
| 		RS: newResultSet, | ||||
| 	}, nil | ||||
| 	return newResultSetProxy(newResultSet), nil | ||||
| } | ||||
| 
 | ||||
| func moduleRS(tableService *tables.Service, state *controllers.State) ucl.Module { | ||||
|  |  | |||
|  | @ -1,7 +1,180 @@ | |||
| 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 { | ||||
| 	RS *models.ResultSet | ||||
| type proxyFields[T any] map[string]func(t T) ucl.Object | ||||
| 
 | ||||
| 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" | ||||
| 	"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/models" | ||||
| 	"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 { | ||||
| 	state          *controllers.State | ||||
| 	readController *controllers.TableReadController | ||||
| } | ||||
| 
 | ||||
| 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 { | ||||
| 	rsVal, ok := value.(ResultSetProxy) | ||||
| 	rsVal, ok := value.(simpleProxy[*models.ResultSet]) | ||||
| 	if !ok { | ||||
| 		return errors.New("new value to @resultset is not a result set") | ||||
| 	} | ||||
| 
 | ||||
| 	log.Printf("type = %T", rsVal.RS) | ||||
| 
 | ||||
| 	msg := rs.readController.SetResultSet(rsVal.RS) | ||||
| 	msg := rs.readController.SetResultSet(rsVal.value) | ||||
| 	commandctrl.PostMsg(ctx, msg) | ||||
| 	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
 | ||||
| 
 | ||||
| 	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 | ||||
| } | ||||
| 
 | ||||
| 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 ( | ||||
| 	"context" | ||||
| 	tea "github.com/charmbracelet/bubbletea" | ||||
| 	"github.com/lmika/dynamo-browse/internal/dynamo-browse/services" | ||||
| ) | ||||
| 
 | ||||
|  | @ -11,4 +12,5 @@ type IterProvider interface { | |||
| 
 | ||||
| type UIStateProvider interface { | ||||
| 	SelectedItemIndex() int | ||||
| 	SetSelectedItemIndex(newIdx int) tea.Msg | ||||
| } | ||||
|  |  | |||
|  | @ -397,3 +397,7 @@ func (m *Model) promptToQuit() tea.Msg { | |||
| func (m *Model) SelectedItemIndex() int { | ||||
| 	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 | ||||
| } | ||||
| 
 | ||||
| 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) { | ||||
| 	resultSet := m.resultSet | ||||
| 
 | ||||
|  |  | |||
		Loading…
	
		Reference in a new issue