diff --git a/go.mod b/go.mod index dd7d9d6..3f5b6d1 100644 --- a/go.mod +++ b/go.mod @@ -25,7 +25,7 @@ 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.10.0 + github.com/stretchr/testify v1.11.1 golang.design/x/clipboard v0.6.2 golang.org/x/exp v0.0.0-20230108222341-4b8118a2686a ucl.lmika.dev v0.1.2 @@ -50,9 +50,12 @@ require ( github.com/aymanbagabas/go-osc52 v1.0.3 // indirect github.com/containerd/console v1.0.3 // indirect github.com/davecgh/go-spew v1.1.1 // indirect + github.com/go-co-op/gocron/v2 v2.17.0 // indirect github.com/golang/protobuf v1.5.2 // indirect github.com/golang/snappy v0.0.4 // indirect + github.com/google/uuid v1.6.0 // indirect github.com/jmespath/go-jmespath v0.4.0 // indirect + github.com/jonboulle/clockwork v0.5.0 // indirect github.com/juju/ansiterm v0.0.0-20210929141451-8b71cc96ebdc // indirect github.com/kr/pretty v0.3.1 // indirect github.com/lucasb-eyer/go-colorful v1.2.0 // indirect @@ -63,6 +66,7 @@ require ( github.com/muesli/termenv v0.13.0 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/rivo/uniseg v0.4.2 // indirect + github.com/robfig/cron/v3 v3.0.1 // indirect github.com/rogpeppe/go-internal v1.10.0 // indirect github.com/sahilm/fuzzy v0.1.0 // indirect github.com/vmihailenco/msgpack v4.0.4+incompatible // indirect diff --git a/go.sum b/go.sum index 3adfbdd..cf34a68 100644 --- a/go.sum +++ b/go.sum @@ -75,6 +75,8 @@ github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ3 github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 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/go-co-op/gocron/v2 v2.17.0 h1:e/oj6fcAM8vOOKZxv2Cgfmjo+s8AXC46po5ZPtaSea4= +github.com/go-co-op/gocron/v2 v2.17.0/go.mod h1:Zii6he+Zfgy5W9B+JKk/KwejFOW0kZTFvHtwIpR4aBI= github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw= @@ -84,12 +86,16 @@ github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEW github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg= github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 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/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg= github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8= github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U= +github.com/jonboulle/clockwork v0.5.0 h1:Hyh9A8u51kptdkR+cqRpT1EebBwTn1oK9YfGYbdFz6I= +github.com/jonboulle/clockwork v0.5.0/go.mod h1:3mZlmanh0g2NDKO5TWZVJAfofYk64M7XN3SzBPjZF60= github.com/juju/ansiterm v0.0.0-20210929141451-8b71cc96ebdc h1:ZQrgZFsLzkw7o3CoDzsfBhx0bf/1rVBXrLy8dXKRe8o= github.com/juju/ansiterm v0.0.0-20210929141451-8b71cc96ebdc/go.mod h1:PyXUpnI3olx3bsPcHt98FGPX/KCFZ1Fi+hw1XLI6384= github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= @@ -146,6 +152,8 @@ github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJ github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rivo/uniseg v0.4.2 h1:YwD0ulJSJytLpiaWua0sBDusfsCZohxjxzVTYjwxfV8= github.com/rivo/uniseg v0.4.2/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= +github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs= +github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro= github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ= github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog= @@ -154,6 +162,8 @@ 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.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= +github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= 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= diff --git a/internal/common/ui/commandctrl/cmdpacks/modasync.go b/internal/common/ui/commandctrl/cmdpacks/modasync.go new file mode 100644 index 0000000..9526e03 --- /dev/null +++ b/internal/common/ui/commandctrl/cmdpacks/modasync.go @@ -0,0 +1,96 @@ +package cmdpacks + +import ( + "context" + "time" + + "github.com/go-co-op/gocron/v2" + "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/services/tables" + "ucl.lmika.dev/ucl" +) + +type asyncModule struct { + tableService *tables.Service + state *controllers.State +} + +func (m asyncModule) asyncDo(ctx context.Context, args ucl.CallArgs) (any, error) { + var block ucl.Invokable + if err := args.Bind(&block); err != nil { + return nil, err + } + + return nil, commandctrl.ScheduleTask(ctx, func(ctx context.Context) error { + _, err := block.Invoke(ctx) + return err + }) +} + +func (m asyncModule) asyncIn(ctx context.Context, args ucl.CallArgs) (any, error) { + var ( + duration int + block ucl.Invokable + ) + if err := args.Bind(&duration, &block); err != nil { + return nil, err + } + + _, err := commandctrl.CronScheduler(ctx).NewJob( + gocron.OneTimeJob( + gocron.OneTimeJobStartDateTime(time.Now().Add(time.Duration(duration)*time.Second)), + ), + gocron.NewTask(func(ctx context.Context) { + commandctrl.ScheduleTask(ctx, func(ctx context.Context) error { + _, err := block.Invoke(ctx) + return err + }) + }), + gocron.WithContext(ctx), + ) + return nil, err +} + +func (m asyncModule) asyncQuery(ctx context.Context, args ucl.CallArgs) (any, error) { + var ( + block ucl.Invokable + ) + + args, q, tableInfo, err := parseQuery(ctx, args, m.state.ResultSet(), m.tableService, 1) + if err != nil { + return nil, err + } + + if err := args.Bind(&block); err != nil { + return nil, err + } + + return nil, commandctrl.ScheduleAuxTask(ctx, "query: "+q.String(), func(ctx context.Context) error { + newResultSet, err := m.tableService.ScanOrQuery(context.Background(), tableInfo, q, nil) + if err != nil { + return err + } + + return commandctrl.ScheduleTask(ctx, func(ctx context.Context) error { + _, err := block.Invoke(ctx, newResultSetProxy(newResultSet)) + return err + }) + }) +} + +func moduleAsync(tableService *tables.Service, state *controllers.State) ucl.Module { + m := asyncModule{ + state: state, + tableService: tableService, + } + + return ucl.Module{ + Name: "async", + Builtins: map[string]ucl.BuiltinHandler{ + "do": m.asyncDo, + "in": m.asyncIn, + "query": m.asyncQuery, + }, + } +} diff --git a/internal/common/ui/commandctrl/cmdpacks/modav.go b/internal/common/ui/commandctrl/cmdpacks/modav.go index 977a7c1..6c850ac 100644 --- a/internal/common/ui/commandctrl/cmdpacks/modav.go +++ b/internal/common/ui/commandctrl/cmdpacks/modav.go @@ -2,6 +2,7 @@ package cmdpacks import ( "context" + "github.com/aws/aws-sdk-go-v2/service/dynamodb/types" "ucl.lmika.dev/ucl" ) diff --git a/internal/common/ui/commandctrl/cmdpacks/modrs.go b/internal/common/ui/commandctrl/cmdpacks/modrs.go index addcaeb..56c5548 100644 --- a/internal/common/ui/commandctrl/cmdpacks/modrs.go +++ b/internal/common/ui/commandctrl/cmdpacks/modrs.go @@ -2,6 +2,8 @@ package cmdpacks import ( "context" + "time" + "github.com/aws/aws-sdk-go-v2/service/dynamodb/types" "github.com/pkg/errors" "lmika.dev/cmd/dynamo-browse/internal/common/ui/commandctrl" @@ -9,7 +11,6 @@ import ( "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" - "time" "ucl.lmika.dev/repl" "ucl.lmika.dev/ucl" ) @@ -71,21 +72,22 @@ func parseQuery( args ucl.CallArgs, currentRS *models.ResultSet, tablesService *tables.Service, -) (*queryexpr.QueryExpr, *models.TableInfo, error) { + extraArgs int, +) (ucl.CallArgs, *queryexpr.QueryExpr, *models.TableInfo, error) { var expr string if err := args.Bind(&expr); err != nil { - return nil, nil, err + return args, nil, nil, err } q, err := queryexpr.Parse(expr) if err != nil { - return nil, nil, err + return args, nil, nil, err } - if args.NArgs() > 0 { + if args.NArgs() > extraArgs { var queryArgs ucl.Hashable if err := args.Bind(&queryArgs); err != nil { - return nil, nil, err + return args, nil, nil, err } queryNames := map[string]string{} @@ -97,12 +99,15 @@ func parseQuery( queryNames[k] = v.String() - switch v.(type) { + switch t := v.(type) { case ucl.StringObject: queryValues[k] = &types.AttributeValueMemberS{Value: v.String()} case ucl.IntObject: queryValues[k] = &types.AttributeValueMemberN{Value: v.String()} - // TODO: other types + case ucl.BoolObject: + queryValues[k] = &types.AttributeValueMemberBOOL{Value: t.Truthy()} + case attributeValueProxy: + queryValues[k] = t.value } return nil }) @@ -114,24 +119,24 @@ func parseQuery( if args.HasSwitch("table") { var tblName string if err := args.BindSwitch("table", &tblName); err != nil { - return nil, nil, err + return args, nil, nil, err } tableInfo, err = tablesService.Describe(ctx, tblName) if err != nil { - return nil, nil, err + return args, nil, nil, err } } else if currentRS != nil && currentRS.TableInfo != nil { tableInfo = currentRS.TableInfo } else { - return nil, nil, errors.New("no table specified") + return args, nil, nil, errors.New("no table specified") } - return q, tableInfo, nil + return args, q, tableInfo, nil } func (rs *rsModule) rsQuery(ctx context.Context, args ucl.CallArgs) (any, error) { - q, tableInfo, err := parseQuery(ctx, args, rs.state.ResultSet(), rs.tableService) + _, q, tableInfo, err := parseQuery(ctx, args, rs.state.ResultSet(), rs.tableService, 0) if err != nil { return nil, err } diff --git a/internal/common/ui/commandctrl/cmdpacks/modrs_test.go b/internal/common/ui/commandctrl/cmdpacks/modrs_test.go index b3f6f82..13a2dd7 100644 --- a/internal/common/ui/commandctrl/cmdpacks/modrs_test.go +++ b/internal/common/ui/commandctrl/cmdpacks/modrs_test.go @@ -214,6 +214,14 @@ func TestModRS_First(t *testing.T) { rs = rs:query 'pk="zzz"' -table service-test-data assert (eq $rs.First ()) "expected First to be nil" `, + }, { + descr: "returns the first item using placeholders", + cmd: ` + rs = rs:query 'pk=$v and sk=$u' [v:"abc" u:"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" + `, }, } for _, tt := range tests { diff --git a/internal/common/ui/commandctrl/cmdpacks/modui.go b/internal/common/ui/commandctrl/cmdpacks/modui.go index 993d02e..e27822a 100644 --- a/internal/common/ui/commandctrl/cmdpacks/modui.go +++ b/internal/common/ui/commandctrl/cmdpacks/modui.go @@ -175,7 +175,7 @@ func (m *uiModule) uiBind(ctx context.Context, args ucl.CallArgs) (any, error) { } func (m *uiModule) uiQuery(ctx context.Context, args ucl.CallArgs) (any, error) { - q, tableInfo, err := parseQuery(ctx, args, m.state.ResultSet(), m.tableService) + _, q, tableInfo, err := parseQuery(ctx, args, m.state.ResultSet(), m.tableService, 0) if err != nil { return nil, err } @@ -210,6 +210,7 @@ func (m *uiModule) uiSetItemAnnotator(ctx context.Context, args ucl.CallArgs) (a } return fmt.Sprint(v) })) + commandctrl.QueueRefresh(ctx) return nil, nil } diff --git a/internal/common/ui/commandctrl/cmdpacks/stdcmds.go b/internal/common/ui/commandctrl/cmdpacks/stdcmds.go index 5231506..0d5c494 100644 --- a/internal/common/ui/commandctrl/cmdpacks/stdcmds.go +++ b/internal/common/ui/commandctrl/cmdpacks/stdcmds.go @@ -404,6 +404,7 @@ func (sc StandardCommands) InstOptions() []ucl.InstOption { ucl.WithModule(modulePB(sc.PBProvider)), ucl.WithModule(moduleOpt(sc.SettingsController)), ucl.WithModule(moduleAttrValue()), + ucl.WithModule(moduleAsync(sc.TableService, sc.State)), } } @@ -453,4 +454,30 @@ ui:bind "view.toggle-marked-items" "M" { mark all } } + +proc _prep_officeCount { + openedOffices = 0 + closedOffices = 0 + + async:query 'officeOpened=$v' [v:(av:true)] { |rs| + openedOffices = len $rs + async:query 'officeOpened=$u' [u:(av:false)] { |rs| + closedOffices = len $rs + + ui:set-item-annotator { |rs item path| + if (eq $path.(-1) "officeOpened") { + if $item.officeOpened { + "Count = ${openedOffices}" + } else { + "Count = ${closedOffices}" + } + } else { + "" + } + } + } -table business-addresses + } -table business-addresses +} + +_prep_officeCount ` diff --git a/internal/common/ui/commandctrl/commandctrl.go b/internal/common/ui/commandctrl/commandctrl.go index 5e0c330..7dc6ba4 100644 --- a/internal/common/ui/commandctrl/commandctrl.go +++ b/internal/common/ui/commandctrl/commandctrl.go @@ -4,12 +4,14 @@ import ( "bytes" "context" "fmt" - tea "github.com/charmbracelet/bubbletea" - "github.com/pkg/errors" "log" "os" "path/filepath" "strings" + + tea "github.com/charmbracelet/bubbletea" + "github.com/go-co-op/gocron/v2" + "github.com/pkg/errors" "ucl.lmika.dev/ucl" "ucl.lmika.dev/ucl/builtins" @@ -17,12 +19,22 @@ import ( "lmika.dev/cmd/dynamo-browse/internal/common/ui/events" ) -const commandsCategory = "commands" +const ( + commandsCategory = "commands" + pendingTaskBuffer = 50 + pendingAuxTaskBuffer = 50 + auxWorkers = 4 +) type cmdMessage struct { cmd string } +type pendingTask struct { + descr string + task func(ctx context.Context) error +} + type CommandController struct { uclInst *ucl.Inst historyProvider IterProvider @@ -30,19 +42,29 @@ type CommandController struct { lookupExtensions []CommandLookupExtension completionProvider CommandCompletionProvider uiStateProvider UIStateProvider + cronScheduler gocron.Scheduler cmdChan chan cmdMessage + pendingTaskChan chan pendingTask + pendingAuxTaskChan chan pendingTask msgChan chan tea.Msg interactive bool } func NewCommandController(historyProvider IterProvider, pkgs ...CommandPack) (*CommandController, error) { + sched, err := gocron.NewScheduler() + if err != nil { + return nil, err + } cc := &CommandController{ - historyProvider: historyProvider, - commandList: nil, - lookupExtensions: nil, - cmdChan: make(chan cmdMessage), - msgChan: make(chan tea.Msg), - interactive: true, + historyProvider: historyProvider, + commandList: nil, + lookupExtensions: nil, + cronScheduler: sched, + cmdChan: make(chan cmdMessage), + pendingTaskChan: make(chan pendingTask, pendingTaskBuffer), + pendingAuxTaskChan: make(chan pendingTask, pendingAuxTaskBuffer), + msgChan: make(chan tea.Msg), + interactive: true, } options := []ucl.InstOption{ @@ -75,6 +97,8 @@ func NewCommandController(historyProvider IterProvider, pkgs ...CommandPack) (*C } go cc.cmdLooper() + go cc.auxCmdLooper() + sched.Start() return cc, nil } @@ -172,12 +196,13 @@ func (c *CommandController) Invoke(invokable ucl.Invokable, args []any) (msg tea } func (c *CommandController) cmdLooper() { - execCtx := execContext{ctrl: c} - ctx := context.WithValue(context.Background(), commandCtlKey, &execCtx) - + ctx := context.Background() for { select { case cmdChan := <-c.cmdChan: + execCtx := execContext{ctrl: c} + ctx := context.WithValue(ctx, commandCtlKey, &execCtx) + res, err := c.ExecuteAndWait(ctx, cmdChan.cmd) if err != nil { c.postMessage(events.Error(err)) @@ -187,6 +212,16 @@ func (c *CommandController) cmdLooper() { if execCtx.requestRefresh { c.postMessage(events.ResultSetUpdated{}) } + case task := <-c.pendingTaskChan: + execCtx := execContext{ctrl: c} + ctx := context.WithValue(ctx, commandCtlKey, &execCtx) + + if err := task.task(ctx); err != nil { + c.postMessage(events.Error(err)) + } + if execCtx.requestRefresh { + c.postMessage(events.ResultSetUpdated{}) + } } } } @@ -305,15 +340,13 @@ func (c *CommandController) cmdInvoker(ctx context.Context, name string, args uc } func (c *CommandController) printLine(s string) { + log.Println(s) if c.msgChan == nil || !c.interactive { - log.Println(s) return } select { case c.msgChan <- events.StatusMsg(s): - default: - log.Println(s) } } @@ -325,6 +358,21 @@ func (c *CommandController) postMessage(msg tea.Msg) { c.msgChan <- msg } +func (c *CommandController) auxCmdLooper() { + ctx := context.WithValue(context.Background(), commandCtlKey, &execContext{ctrl: c}) + + for i := 0; i < auxWorkers; i++ { + go func() { + for auxTask := range c.pendingAuxTaskChan { + log.Printf("running aux task: %v", auxTask.descr) + if err := auxTask.task(ctx); err != nil { + log.Printf("aux task error: %v", err) + } + } + }() + } +} + type teaMsgWrapper struct { msg tea.Msg } diff --git a/internal/common/ui/commandctrl/ctx.go b/internal/common/ui/commandctrl/ctx.go index dffff70..3f5c7db 100644 --- a/internal/common/ui/commandctrl/ctx.go +++ b/internal/common/ui/commandctrl/ctx.go @@ -2,7 +2,10 @@ package commandctrl import ( "context" + tea "github.com/charmbracelet/bubbletea" + "github.com/go-co-op/gocron/v2" + "github.com/pkg/errors" "ucl.lmika.dev/ucl" ) @@ -57,6 +60,40 @@ func QueueRefresh(ctx context.Context) { cmdCtl.requestRefresh = true } +func CronScheduler(ctx context.Context) gocron.Scheduler { + cmdCtl, ok := ctx.Value(commandCtlKey).(*execContext) + if !ok { + return nil + } + return cmdCtl.ctrl.cronScheduler +} + +func ScheduleTask(ctx context.Context, task func(ctx context.Context) error) error { + cmdCtl, ok := ctx.Value(commandCtlKey).(*execContext) + if !ok { + return errors.New("no command controller") + } + select { + case cmdCtl.ctrl.pendingTaskChan <- pendingTask{task: task}: + return nil + default: + return errors.New("task queue is full") + } +} + +func ScheduleAuxTask(ctx context.Context, descr string, task func(ctx context.Context) error) error { + cmdCtl, ok := ctx.Value(commandCtlKey).(*execContext) + if !ok { + return errors.New("no command controller") + } + select { + case cmdCtl.ctrl.pendingAuxTaskChan <- pendingTask{descr: descr, task: task}: + return nil + default: + return errors.New("aux task queue is full") + } +} + type Invoker interface { Invoke(invokable ucl.Invokable, args []any) tea.Msg Inst() *ucl.Inst diff --git a/internal/dynamo-browse/ui/teamodels/dynamoitemview/model.go b/internal/dynamo-browse/ui/teamodels/dynamoitemview/model.go index 9bd770c..dc95276 100644 --- a/internal/dynamo-browse/ui/teamodels/dynamoitemview/model.go +++ b/internal/dynamo-browse/ui/teamodels/dynamoitemview/model.go @@ -1,6 +1,8 @@ package dynamoitemview import ( + "strings" + "github.com/charmbracelet/bubbles/viewport" tea "github.com/charmbracelet/bubbletea" "github.com/charmbracelet/lipgloss" @@ -9,7 +11,6 @@ import ( "lmika.dev/cmd/dynamo-browse/internal/dynamo-browse/ui/teamodels/frame" "lmika.dev/cmd/dynamo-browse/internal/dynamo-browse/ui/teamodels/layout" "lmika.dev/cmd/dynamo-browse/internal/dynamo-browse/ui/teamodels/styles" - "strings" ) type Model struct { diff --git a/internal/dynamo-browse/ui/teamodels/styles/styles.go b/internal/dynamo-browse/ui/teamodels/styles/styles.go index c288077..504bc41 100644 --- a/internal/dynamo-browse/ui/teamodels/styles/styles.go +++ b/internal/dynamo-browse/ui/teamodels/styles/styles.go @@ -20,7 +20,7 @@ type ItemViewStyle struct { var DefaultStyles = Styles{ ItemView: ItemViewStyle{ FieldType: lipgloss.NewStyle().Foreground(lipgloss.AdaptiveColor{Light: "#2B800C", Dark: "#73C653"}), - MetaInfo: lipgloss.NewStyle().Foreground(lipgloss.Color("#888888")), + MetaInfo: lipgloss.NewStyle().Foreground(lipgloss.Color("#707070")), }, Frames: frame.Style{ ActiveTitle: lipgloss.NewStyle().