Compare commits
No commits in common. "08a3c162a2b9a3ecfe05951073d6c73a93f088e8" and "c8b65f6b0a54f371b56154212fe8b2982d4e39ea" have entirely different histories.
08a3c162a2
...
c8b65f6b0a
|
|
@ -177,7 +177,6 @@ func main() {
|
||||||
keyBindingController,
|
keyBindingController,
|
||||||
pasteboardProvider,
|
pasteboardProvider,
|
||||||
settingsController,
|
settingsController,
|
||||||
itemRendererService,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
commandController, err := commandctrl.NewCommandController(inputHistoryService, stdCommands)
|
commandController, err := commandctrl.NewCommandController(inputHistoryService, stdCommands)
|
||||||
|
|
|
||||||
6
go.mod
6
go.mod
|
|
@ -25,7 +25,7 @@ require (
|
||||||
github.com/muesli/ansi v0.0.0-20211031195517-c9f0611b6c70
|
github.com/muesli/ansi v0.0.0-20211031195517-c9f0611b6c70
|
||||||
github.com/muesli/reflow v0.3.0
|
github.com/muesli/reflow v0.3.0
|
||||||
github.com/pkg/errors v0.9.1
|
github.com/pkg/errors v0.9.1
|
||||||
github.com/stretchr/testify v1.11.1
|
github.com/stretchr/testify v1.10.0
|
||||||
golang.design/x/clipboard v0.6.2
|
golang.design/x/clipboard v0.6.2
|
||||||
golang.org/x/exp v0.0.0-20230108222341-4b8118a2686a
|
golang.org/x/exp v0.0.0-20230108222341-4b8118a2686a
|
||||||
ucl.lmika.dev v0.1.2
|
ucl.lmika.dev v0.1.2
|
||||||
|
|
@ -50,12 +50,9 @@ require (
|
||||||
github.com/aymanbagabas/go-osc52 v1.0.3 // indirect
|
github.com/aymanbagabas/go-osc52 v1.0.3 // indirect
|
||||||
github.com/containerd/console v1.0.3 // indirect
|
github.com/containerd/console v1.0.3 // indirect
|
||||||
github.com/davecgh/go-spew v1.1.1 // 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/protobuf v1.5.2 // indirect
|
||||||
github.com/golang/snappy v0.0.4 // 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/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/juju/ansiterm v0.0.0-20210929141451-8b71cc96ebdc // indirect
|
||||||
github.com/kr/pretty v0.3.1 // indirect
|
github.com/kr/pretty v0.3.1 // indirect
|
||||||
github.com/lucasb-eyer/go-colorful v1.2.0 // indirect
|
github.com/lucasb-eyer/go-colorful v1.2.0 // indirect
|
||||||
|
|
@ -66,7 +63,6 @@ require (
|
||||||
github.com/muesli/termenv v0.13.0 // indirect
|
github.com/muesli/termenv v0.13.0 // indirect
|
||||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||||
github.com/rivo/uniseg v0.4.2 // 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/rogpeppe/go-internal v1.10.0 // indirect
|
||||||
github.com/sahilm/fuzzy v0.1.0 // indirect
|
github.com/sahilm/fuzzy v0.1.0 // indirect
|
||||||
github.com/vmihailenco/msgpack v4.0.4+incompatible // indirect
|
github.com/vmihailenco/msgpack v4.0.4+incompatible // indirect
|
||||||
|
|
|
||||||
10
go.sum
10
go.sum
|
|
@ -75,8 +75,6 @@ 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.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 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
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.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.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
|
||||||
github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw=
|
github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw=
|
||||||
|
|
@ -86,16 +84,12 @@ 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.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 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg=
|
||||||
github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
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 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM=
|
||||||
github.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSow5/V2vxeg=
|
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 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg=
|
||||||
github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo=
|
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 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8=
|
||||||
github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U=
|
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 h1:ZQrgZFsLzkw7o3CoDzsfBhx0bf/1rVBXrLy8dXKRe8o=
|
||||||
github.com/juju/ansiterm v0.0.0-20210929141451-8b71cc96ebdc/go.mod h1:PyXUpnI3olx3bsPcHt98FGPX/KCFZ1Fi+hw1XLI6384=
|
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=
|
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
|
||||||
|
|
@ -152,8 +146,6 @@ 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.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
||||||
github.com/rivo/uniseg v0.4.2 h1:YwD0ulJSJytLpiaWua0sBDusfsCZohxjxzVTYjwxfV8=
|
github.com/rivo/uniseg v0.4.2 h1:YwD0ulJSJytLpiaWua0sBDusfsCZohxjxzVTYjwxfV8=
|
||||||
github.com/rivo/uniseg v0.4.2/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
|
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.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 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ=
|
||||||
github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog=
|
github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog=
|
||||||
|
|
@ -162,8 +154,6 @@ 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/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 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
|
||||||
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
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 h1:dSLoQfGFAo3F6OoNhwUmLwVgaUXK79GlxNBwueZn0xI=
|
||||||
github.com/vmihailenco/msgpack v4.0.4+incompatible/go.mod h1:fy3FlTQTDXWkZ7Bh6AcGMlsjHatGryHQYUTf1ShIgkk=
|
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=
|
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
|
||||||
|
|
|
||||||
|
|
@ -1,96 +0,0 @@
|
||||||
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,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -2,7 +2,6 @@ package cmdpacks
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
|
||||||
"github.com/aws/aws-sdk-go-v2/service/dynamodb/types"
|
"github.com/aws/aws-sdk-go-v2/service/dynamodb/types"
|
||||||
"ucl.lmika.dev/ucl"
|
"ucl.lmika.dev/ucl"
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -2,8 +2,6 @@ package cmdpacks
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/aws/aws-sdk-go-v2/service/dynamodb/types"
|
"github.com/aws/aws-sdk-go-v2/service/dynamodb/types"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
"lmika.dev/cmd/dynamo-browse/internal/common/ui/commandctrl"
|
"lmika.dev/cmd/dynamo-browse/internal/common/ui/commandctrl"
|
||||||
|
|
@ -11,6 +9,7 @@ import (
|
||||||
"lmika.dev/cmd/dynamo-browse/internal/dynamo-browse/models"
|
"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/models/queryexpr"
|
||||||
"lmika.dev/cmd/dynamo-browse/internal/dynamo-browse/services/tables"
|
"lmika.dev/cmd/dynamo-browse/internal/dynamo-browse/services/tables"
|
||||||
|
"time"
|
||||||
"ucl.lmika.dev/repl"
|
"ucl.lmika.dev/repl"
|
||||||
"ucl.lmika.dev/ucl"
|
"ucl.lmika.dev/ucl"
|
||||||
)
|
)
|
||||||
|
|
@ -72,22 +71,21 @@ func parseQuery(
|
||||||
args ucl.CallArgs,
|
args ucl.CallArgs,
|
||||||
currentRS *models.ResultSet,
|
currentRS *models.ResultSet,
|
||||||
tablesService *tables.Service,
|
tablesService *tables.Service,
|
||||||
extraArgs int,
|
) (*queryexpr.QueryExpr, *models.TableInfo, error) {
|
||||||
) (ucl.CallArgs, *queryexpr.QueryExpr, *models.TableInfo, error) {
|
|
||||||
var expr string
|
var expr string
|
||||||
if err := args.Bind(&expr); err != nil {
|
if err := args.Bind(&expr); err != nil {
|
||||||
return args, nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
q, err := queryexpr.Parse(expr)
|
q, err := queryexpr.Parse(expr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return args, nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if args.NArgs() > extraArgs {
|
if args.NArgs() > 0 {
|
||||||
var queryArgs ucl.Hashable
|
var queryArgs ucl.Hashable
|
||||||
if err := args.Bind(&queryArgs); err != nil {
|
if err := args.Bind(&queryArgs); err != nil {
|
||||||
return args, nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
queryNames := map[string]string{}
|
queryNames := map[string]string{}
|
||||||
|
|
@ -99,15 +97,12 @@ func parseQuery(
|
||||||
|
|
||||||
queryNames[k] = v.String()
|
queryNames[k] = v.String()
|
||||||
|
|
||||||
switch t := v.(type) {
|
switch v.(type) {
|
||||||
case ucl.StringObject:
|
case ucl.StringObject:
|
||||||
queryValues[k] = &types.AttributeValueMemberS{Value: v.String()}
|
queryValues[k] = &types.AttributeValueMemberS{Value: v.String()}
|
||||||
case ucl.IntObject:
|
case ucl.IntObject:
|
||||||
queryValues[k] = &types.AttributeValueMemberN{Value: v.String()}
|
queryValues[k] = &types.AttributeValueMemberN{Value: v.String()}
|
||||||
case ucl.BoolObject:
|
// TODO: other types
|
||||||
queryValues[k] = &types.AttributeValueMemberBOOL{Value: t.Truthy()}
|
|
||||||
case attributeValueProxy:
|
|
||||||
queryValues[k] = t.value
|
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
|
|
@ -119,24 +114,24 @@ func parseQuery(
|
||||||
if args.HasSwitch("table") {
|
if args.HasSwitch("table") {
|
||||||
var tblName string
|
var tblName string
|
||||||
if err := args.BindSwitch("table", &tblName); err != nil {
|
if err := args.BindSwitch("table", &tblName); err != nil {
|
||||||
return args, nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
tableInfo, err = tablesService.Describe(ctx, tblName)
|
tableInfo, err = tablesService.Describe(ctx, tblName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return args, nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
} else if currentRS != nil && currentRS.TableInfo != nil {
|
} else if currentRS != nil && currentRS.TableInfo != nil {
|
||||||
tableInfo = currentRS.TableInfo
|
tableInfo = currentRS.TableInfo
|
||||||
} else {
|
} else {
|
||||||
return args, nil, nil, errors.New("no table specified")
|
return nil, nil, errors.New("no table specified")
|
||||||
}
|
}
|
||||||
|
|
||||||
return args, q, tableInfo, nil
|
return q, tableInfo, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (rs *rsModule) rsQuery(ctx context.Context, args ucl.CallArgs) (any, error) {
|
func (rs *rsModule) rsQuery(ctx context.Context, args ucl.CallArgs) (any, error) {
|
||||||
_, q, tableInfo, err := parseQuery(ctx, args, rs.state.ResultSet(), rs.tableService, 0)
|
q, tableInfo, err := parseQuery(ctx, args, rs.state.ResultSet(), rs.tableService)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -214,14 +214,6 @@ func TestModRS_First(t *testing.T) {
|
||||||
rs = rs:query 'pk="zzz"' -table service-test-data
|
rs = rs:query 'pk="zzz"' -table service-test-data
|
||||||
assert (eq $rs.First ()) "expected First to be nil"
|
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 {
|
for _, tt := range tests {
|
||||||
|
|
|
||||||
|
|
@ -2,14 +2,11 @@ package cmdpacks
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
|
||||||
|
|
||||||
tea "github.com/charmbracelet/bubbletea"
|
tea "github.com/charmbracelet/bubbletea"
|
||||||
"lmika.dev/cmd/dynamo-browse/internal/common/ui/commandctrl"
|
"lmika.dev/cmd/dynamo-browse/internal/common/ui/commandctrl"
|
||||||
"lmika.dev/cmd/dynamo-browse/internal/common/ui/events"
|
"lmika.dev/cmd/dynamo-browse/internal/common/ui/events"
|
||||||
"lmika.dev/cmd/dynamo-browse/internal/dynamo-browse/controllers"
|
"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/itemrenderer"
|
|
||||||
"lmika.dev/cmd/dynamo-browse/internal/dynamo-browse/services/tables"
|
"lmika.dev/cmd/dynamo-browse/internal/dynamo-browse/services/tables"
|
||||||
"ucl.lmika.dev/ucl"
|
"ucl.lmika.dev/ucl"
|
||||||
)
|
)
|
||||||
|
|
@ -19,7 +16,6 @@ type uiModule struct {
|
||||||
state *controllers.State
|
state *controllers.State
|
||||||
ckb *customKeyBinding
|
ckb *customKeyBinding
|
||||||
readController *controllers.TableReadController
|
readController *controllers.TableReadController
|
||||||
itemRenderer *itemrenderer.Service
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *uiModule) uiCommand(ctx context.Context, args ucl.CallArgs) (any, error) {
|
func (m *uiModule) uiCommand(ctx context.Context, args ucl.CallArgs) (any, error) {
|
||||||
|
|
@ -175,7 +171,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) {
|
func (m *uiModule) uiQuery(ctx context.Context, args ucl.CallArgs) (any, error) {
|
||||||
_, q, tableInfo, err := parseQuery(ctx, args, m.state.ResultSet(), m.tableService, 0)
|
q, tableInfo, err := parseQuery(ctx, args, m.state.ResultSet(), m.tableService)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
@ -195,36 +191,15 @@ func (m *uiModule) uiFilter(ctx context.Context, args ucl.CallArgs) (any, error)
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *uiModule) uiSetItemAnnotator(ctx context.Context, args ucl.CallArgs) (any, error) {
|
|
||||||
var inv ucl.Invokable
|
|
||||||
if err := args.Bind(&inv); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
m.itemRenderer.SetAnnotation(itemrenderer.AnnotationFunc(func(rs *models.ResultSet, item models.Item, path models.AttrPathNode) string {
|
|
||||||
v, err := inv.Invoke(ctx, newResultSetProxy(rs), itemProxy{rs, 0, item}, attrPathProxy{attrPath: &path})
|
|
||||||
if err != nil {
|
|
||||||
return ""
|
|
||||||
} else if v == nil {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
return fmt.Sprint(v)
|
|
||||||
}))
|
|
||||||
commandctrl.QueueRefresh(ctx)
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func moduleUI(
|
func moduleUI(
|
||||||
tableService *tables.Service,
|
tableService *tables.Service,
|
||||||
state *controllers.State,
|
state *controllers.State,
|
||||||
readController *controllers.TableReadController,
|
readController *controllers.TableReadController,
|
||||||
itemRenderer *itemrenderer.Service,
|
|
||||||
) (ucl.Module, controllers.CustomKeyBindingSource) {
|
) (ucl.Module, controllers.CustomKeyBindingSource) {
|
||||||
m := &uiModule{
|
m := &uiModule{
|
||||||
tableService: tableService,
|
tableService: tableService,
|
||||||
state: state,
|
state: state,
|
||||||
readController: readController,
|
readController: readController,
|
||||||
itemRenderer: itemRenderer,
|
|
||||||
ckb: &customKeyBinding{
|
ckb: &customKeyBinding{
|
||||||
bindings: map[string]tea.Cmd{},
|
bindings: map[string]tea.Cmd{},
|
||||||
keyBindings: map[string]string{},
|
keyBindings: map[string]string{},
|
||||||
|
|
@ -234,15 +209,14 @@ func moduleUI(
|
||||||
return ucl.Module{
|
return ucl.Module{
|
||||||
Name: "ui",
|
Name: "ui",
|
||||||
Builtins: map[string]ucl.BuiltinHandler{
|
Builtins: map[string]ucl.BuiltinHandler{
|
||||||
"command": m.uiCommand,
|
"command": m.uiCommand,
|
||||||
"prompt": m.uiPrompt,
|
"prompt": m.uiPrompt,
|
||||||
"prompt-table": m.uiPromptTable,
|
"prompt-table": m.uiPromptTable,
|
||||||
"prompt-keypress": m.uiInKey,
|
"prompt-keypress": m.uiInKey,
|
||||||
"confirm": m.uiConfirm,
|
"confirm": m.uiConfirm,
|
||||||
"query": m.uiQuery,
|
"query": m.uiQuery,
|
||||||
"filter": m.uiFilter,
|
"filter": m.uiFilter,
|
||||||
"bind": m.uiBind,
|
"bind": m.uiBind,
|
||||||
"set-item-annotator": m.uiSetItemAnnotator,
|
|
||||||
},
|
},
|
||||||
}, m.ckb
|
}, m.ckb
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -234,49 +234,6 @@ func (tp itemProxy) Each(fn func(k string, v ucl.Object) error) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
type attrPathProxy struct {
|
|
||||||
attrPath *models.AttrPathNode
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ap attrPathProxy) String() string {
|
|
||||||
return "RSItem()"
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ap attrPathProxy) Truthy() bool {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ap attrPathProxy) Len() (l int) {
|
|
||||||
for p := ap.attrPath; p != nil; p = p.Parent {
|
|
||||||
l++
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ap attrPathProxy) Index(k int) ucl.Object {
|
|
||||||
if k == -1 {
|
|
||||||
return ucl.StringObject(ap.attrPath.Key)
|
|
||||||
}
|
|
||||||
|
|
||||||
if k >= 0 {
|
|
||||||
k = ap.Len() - k - 1
|
|
||||||
} else {
|
|
||||||
k = -k - 1
|
|
||||||
}
|
|
||||||
|
|
||||||
if k < 0 {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
for p := ap.attrPath; p != nil; p = p.Parent {
|
|
||||||
if k <= 0 {
|
|
||||||
return ucl.StringObject(p.Key)
|
|
||||||
}
|
|
||||||
k -= 1
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type attributeValueProxy struct {
|
type attributeValueProxy struct {
|
||||||
value types.AttributeValue
|
value types.AttributeValue
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,101 +0,0 @@
|
||||||
package cmdpacks
|
|
||||||
|
|
||||||
import (
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
"lmika.dev/cmd/dynamo-browse/internal/dynamo-browse/models"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestAttrPathProxy_Index(t *testing.T) {
|
|
||||||
tests := []struct {
|
|
||||||
descr string
|
|
||||||
attrPath models.AttrPathNode
|
|
||||||
index int
|
|
||||||
want string
|
|
||||||
wantNil bool
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
descr: "return leaf 1",
|
|
||||||
attrPath: models.AttrPathNode{Key: "leaf"},
|
|
||||||
index: -1,
|
|
||||||
want: "leaf",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
descr: "return leaf 2",
|
|
||||||
attrPath: models.AttrPathNode{Key: "leaf", Parent: &models.AttrPathNode{Key: "parent"}},
|
|
||||||
index: -1,
|
|
||||||
want: "leaf",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
descr: "return parent 1",
|
|
||||||
attrPath: models.AttrPathNode{Key: "leaf", Parent: &models.AttrPathNode{Key: "parent"}},
|
|
||||||
index: -2,
|
|
||||||
want: "parent",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
descr: "return parent 1",
|
|
||||||
attrPath: models.AttrPathNode{Key: "leaf", Parent: &models.AttrPathNode{Key: "parent", Parent: &models.AttrPathNode{Key: "grandparent"}}},
|
|
||||||
index: -2,
|
|
||||||
want: "parent",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
descr: "return parent 3",
|
|
||||||
attrPath: models.AttrPathNode{Key: "leaf", Parent: &models.AttrPathNode{Key: "parent", Parent: &models.AttrPathNode{Key: "grandparent"}}},
|
|
||||||
index: -3,
|
|
||||||
want: "grandparent",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
descr: "return root 1",
|
|
||||||
attrPath: models.AttrPathNode{Key: "leaf", Parent: &models.AttrPathNode{Key: "parent", Parent: &models.AttrPathNode{Key: "grandparent"}}},
|
|
||||||
index: 0,
|
|
||||||
want: "grandparent",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
descr: "return root 2",
|
|
||||||
attrPath: models.AttrPathNode{Key: "leaf", Parent: &models.AttrPathNode{Key: "parent"}},
|
|
||||||
index: 0,
|
|
||||||
want: "parent",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
descr: "return root 3",
|
|
||||||
attrPath: models.AttrPathNode{Key: "leaf"},
|
|
||||||
index: 0,
|
|
||||||
want: "leaf",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
descr: "return first child 1",
|
|
||||||
attrPath: models.AttrPathNode{Key: "leaf", Parent: &models.AttrPathNode{Key: "parent", Parent: &models.AttrPathNode{Key: "grandparent"}}},
|
|
||||||
index: 1,
|
|
||||||
want: "parent",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
descr: "return first child 2",
|
|
||||||
attrPath: models.AttrPathNode{Key: "leaf", Parent: &models.AttrPathNode{Key: "parent"}},
|
|
||||||
index: 1,
|
|
||||||
want: "leaf",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
descr: "return nil 1",
|
|
||||||
attrPath: models.AttrPathNode{Key: "leaf", Parent: &models.AttrPathNode{Key: "parent", Parent: &models.AttrPathNode{Key: "grandparent"}}},
|
|
||||||
index: -5,
|
|
||||||
wantNil: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
descr: "return nil 2",
|
|
||||||
attrPath: models.AttrPathNode{Key: "leaf", Parent: &models.AttrPathNode{Key: "parent"}},
|
|
||||||
index: 56,
|
|
||||||
wantNil: true,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
for _, tt := range tests {
|
|
||||||
t.Run(tt.descr, func(t *testing.T) {
|
|
||||||
proxy := attrPathProxy{&tt.attrPath}
|
|
||||||
if tt.wantNil {
|
|
||||||
assert.Nil(t, proxy.Index(tt.index))
|
|
||||||
} else {
|
|
||||||
assert.Equal(t, tt.want, proxy.Index(tt.index).String())
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -9,7 +9,6 @@ import (
|
||||||
"lmika.dev/cmd/dynamo-browse/internal/dynamo-browse/controllers"
|
"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"
|
||||||
"lmika.dev/cmd/dynamo-browse/internal/dynamo-browse/services"
|
"lmika.dev/cmd/dynamo-browse/internal/dynamo-browse/services"
|
||||||
"lmika.dev/cmd/dynamo-browse/internal/dynamo-browse/services/itemrenderer"
|
|
||||||
"lmika.dev/cmd/dynamo-browse/internal/dynamo-browse/services/tables"
|
"lmika.dev/cmd/dynamo-browse/internal/dynamo-browse/services/tables"
|
||||||
"ucl.lmika.dev/repl"
|
"ucl.lmika.dev/repl"
|
||||||
"ucl.lmika.dev/ucl"
|
"ucl.lmika.dev/ucl"
|
||||||
|
|
@ -24,7 +23,6 @@ type StandardCommands struct {
|
||||||
KeyBindingController *controllers.KeyBindingController
|
KeyBindingController *controllers.KeyBindingController
|
||||||
PBProvider services.PasteboardProvider
|
PBProvider services.PasteboardProvider
|
||||||
SettingsController *controllers.SettingsController
|
SettingsController *controllers.SettingsController
|
||||||
ItemRenderer *itemrenderer.Service
|
|
||||||
|
|
||||||
modUI ucl.Module
|
modUI ucl.Module
|
||||||
}
|
}
|
||||||
|
|
@ -38,9 +36,8 @@ func NewStandardCommands(
|
||||||
keyBindingController *controllers.KeyBindingController,
|
keyBindingController *controllers.KeyBindingController,
|
||||||
pbProvider services.PasteboardProvider,
|
pbProvider services.PasteboardProvider,
|
||||||
settingsController *controllers.SettingsController,
|
settingsController *controllers.SettingsController,
|
||||||
itemRenderer *itemrenderer.Service,
|
|
||||||
) StandardCommands {
|
) StandardCommands {
|
||||||
modUI, ckbs := moduleUI(tableService, state, readController, itemRenderer)
|
modUI, ckbs := moduleUI(tableService, state, readController)
|
||||||
keyBindingController.SetCustomKeyBindingSource(ckbs)
|
keyBindingController.SetCustomKeyBindingSource(ckbs)
|
||||||
|
|
||||||
return StandardCommands{
|
return StandardCommands{
|
||||||
|
|
@ -52,7 +49,6 @@ func NewStandardCommands(
|
||||||
KeyBindingController: keyBindingController,
|
KeyBindingController: keyBindingController,
|
||||||
PBProvider: pbProvider,
|
PBProvider: pbProvider,
|
||||||
SettingsController: settingsController,
|
SettingsController: settingsController,
|
||||||
ItemRenderer: itemRenderer,
|
|
||||||
modUI: modUI,
|
modUI: modUI,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -404,7 +400,6 @@ func (sc StandardCommands) InstOptions() []ucl.InstOption {
|
||||||
ucl.WithModule(modulePB(sc.PBProvider)),
|
ucl.WithModule(modulePB(sc.PBProvider)),
|
||||||
ucl.WithModule(moduleOpt(sc.SettingsController)),
|
ucl.WithModule(moduleOpt(sc.SettingsController)),
|
||||||
ucl.WithModule(moduleAttrValue()),
|
ucl.WithModule(moduleAttrValue()),
|
||||||
ucl.WithModule(moduleAsync(sc.TableService, sc.State)),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -454,30 +449,4 @@ ui:bind "view.toggle-marked-items" "M" {
|
||||||
mark all
|
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
|
|
||||||
`
|
`
|
||||||
|
|
|
||||||
|
|
@ -2,8 +2,6 @@ package cmdpacks_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"testing"
|
|
||||||
|
|
||||||
tea "github.com/charmbracelet/bubbletea"
|
tea "github.com/charmbracelet/bubbletea"
|
||||||
bus "github.com/lmika/events"
|
bus "github.com/lmika/events"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
|
|
@ -23,6 +21,7 @@ import (
|
||||||
"lmika.dev/cmd/dynamo-browse/internal/dynamo-browse/ui/keybindings"
|
"lmika.dev/cmd/dynamo-browse/internal/dynamo-browse/ui/keybindings"
|
||||||
"lmika.dev/cmd/dynamo-browse/test/testdynamo"
|
"lmika.dev/cmd/dynamo-browse/test/testdynamo"
|
||||||
"lmika.dev/cmd/dynamo-browse/test/testworkspace"
|
"lmika.dev/cmd/dynamo-browse/test/testworkspace"
|
||||||
|
"testing"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestStdCmds_Mark(t *testing.T) {
|
func TestStdCmds_Mark(t *testing.T) {
|
||||||
|
|
@ -163,7 +162,6 @@ func newService(t *testing.T, opts ...serviceOpt) *services {
|
||||||
keyBindingController,
|
keyBindingController,
|
||||||
testPB,
|
testPB,
|
||||||
settingsController,
|
settingsController,
|
||||||
itemRendererService,
|
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -4,14 +4,12 @@ import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
tea "github.com/charmbracelet/bubbletea"
|
||||||
|
"github.com/pkg/errors"
|
||||||
"log"
|
"log"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"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"
|
||||||
"ucl.lmika.dev/ucl/builtins"
|
"ucl.lmika.dev/ucl/builtins"
|
||||||
|
|
||||||
|
|
@ -19,22 +17,12 @@ import (
|
||||||
"lmika.dev/cmd/dynamo-browse/internal/common/ui/events"
|
"lmika.dev/cmd/dynamo-browse/internal/common/ui/events"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const commandsCategory = "commands"
|
||||||
commandsCategory = "commands"
|
|
||||||
pendingTaskBuffer = 50
|
|
||||||
pendingAuxTaskBuffer = 50
|
|
||||||
auxWorkers = 4
|
|
||||||
)
|
|
||||||
|
|
||||||
type cmdMessage struct {
|
type cmdMessage struct {
|
||||||
cmd string
|
cmd string
|
||||||
}
|
}
|
||||||
|
|
||||||
type pendingTask struct {
|
|
||||||
descr string
|
|
||||||
task func(ctx context.Context) error
|
|
||||||
}
|
|
||||||
|
|
||||||
type CommandController struct {
|
type CommandController struct {
|
||||||
uclInst *ucl.Inst
|
uclInst *ucl.Inst
|
||||||
historyProvider IterProvider
|
historyProvider IterProvider
|
||||||
|
|
@ -42,29 +30,19 @@ type CommandController struct {
|
||||||
lookupExtensions []CommandLookupExtension
|
lookupExtensions []CommandLookupExtension
|
||||||
completionProvider CommandCompletionProvider
|
completionProvider CommandCompletionProvider
|
||||||
uiStateProvider UIStateProvider
|
uiStateProvider UIStateProvider
|
||||||
cronScheduler gocron.Scheduler
|
|
||||||
cmdChan chan cmdMessage
|
cmdChan chan cmdMessage
|
||||||
pendingTaskChan chan pendingTask
|
|
||||||
pendingAuxTaskChan chan pendingTask
|
|
||||||
msgChan chan tea.Msg
|
msgChan chan tea.Msg
|
||||||
interactive bool
|
interactive bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewCommandController(historyProvider IterProvider, pkgs ...CommandPack) (*CommandController, error) {
|
func NewCommandController(historyProvider IterProvider, pkgs ...CommandPack) (*CommandController, error) {
|
||||||
sched, err := gocron.NewScheduler()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
cc := &CommandController{
|
cc := &CommandController{
|
||||||
historyProvider: historyProvider,
|
historyProvider: historyProvider,
|
||||||
commandList: nil,
|
commandList: nil,
|
||||||
lookupExtensions: nil,
|
lookupExtensions: nil,
|
||||||
cronScheduler: sched,
|
cmdChan: make(chan cmdMessage),
|
||||||
cmdChan: make(chan cmdMessage),
|
msgChan: make(chan tea.Msg),
|
||||||
pendingTaskChan: make(chan pendingTask, pendingTaskBuffer),
|
interactive: true,
|
||||||
pendingAuxTaskChan: make(chan pendingTask, pendingAuxTaskBuffer),
|
|
||||||
msgChan: make(chan tea.Msg),
|
|
||||||
interactive: true,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
options := []ucl.InstOption{
|
options := []ucl.InstOption{
|
||||||
|
|
@ -97,8 +75,6 @@ func NewCommandController(historyProvider IterProvider, pkgs ...CommandPack) (*C
|
||||||
}
|
}
|
||||||
|
|
||||||
go cc.cmdLooper()
|
go cc.cmdLooper()
|
||||||
go cc.auxCmdLooper()
|
|
||||||
sched.Start()
|
|
||||||
|
|
||||||
return cc, nil
|
return cc, nil
|
||||||
}
|
}
|
||||||
|
|
@ -196,13 +172,12 @@ func (c *CommandController) Invoke(invokable ucl.Invokable, args []any) (msg tea
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *CommandController) cmdLooper() {
|
func (c *CommandController) cmdLooper() {
|
||||||
ctx := context.Background()
|
execCtx := execContext{ctrl: c}
|
||||||
|
ctx := context.WithValue(context.Background(), commandCtlKey, &execCtx)
|
||||||
|
|
||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
case cmdChan := <-c.cmdChan:
|
case cmdChan := <-c.cmdChan:
|
||||||
execCtx := execContext{ctrl: c}
|
|
||||||
ctx := context.WithValue(ctx, commandCtlKey, &execCtx)
|
|
||||||
|
|
||||||
res, err := c.ExecuteAndWait(ctx, cmdChan.cmd)
|
res, err := c.ExecuteAndWait(ctx, cmdChan.cmd)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.postMessage(events.Error(err))
|
c.postMessage(events.Error(err))
|
||||||
|
|
@ -212,16 +187,6 @@ func (c *CommandController) cmdLooper() {
|
||||||
if execCtx.requestRefresh {
|
if execCtx.requestRefresh {
|
||||||
c.postMessage(events.ResultSetUpdated{})
|
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{})
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -340,13 +305,15 @@ func (c *CommandController) cmdInvoker(ctx context.Context, name string, args uc
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *CommandController) printLine(s string) {
|
func (c *CommandController) printLine(s string) {
|
||||||
log.Println(s)
|
|
||||||
if c.msgChan == nil || !c.interactive {
|
if c.msgChan == nil || !c.interactive {
|
||||||
|
log.Println(s)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
select {
|
select {
|
||||||
case c.msgChan <- events.StatusMsg(s):
|
case c.msgChan <- events.StatusMsg(s):
|
||||||
|
default:
|
||||||
|
log.Println(s)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -358,21 +325,6 @@ func (c *CommandController) postMessage(msg tea.Msg) {
|
||||||
c.msgChan <- 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 {
|
type teaMsgWrapper struct {
|
||||||
msg tea.Msg
|
msg tea.Msg
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,10 +2,7 @@ package commandctrl
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
|
||||||
tea "github.com/charmbracelet/bubbletea"
|
tea "github.com/charmbracelet/bubbletea"
|
||||||
"github.com/go-co-op/gocron/v2"
|
|
||||||
"github.com/pkg/errors"
|
|
||||||
"ucl.lmika.dev/ucl"
|
"ucl.lmika.dev/ucl"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -60,40 +57,6 @@ func QueueRefresh(ctx context.Context) {
|
||||||
cmdCtl.requestRefresh = true
|
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 {
|
type Invoker interface {
|
||||||
Invoke(invokable ucl.Invokable, args []any) tea.Msg
|
Invoke(invokable ucl.Invokable, args []any) tea.Msg
|
||||||
Inst() *ucl.Inst
|
Inst() *ucl.Inst
|
||||||
|
|
|
||||||
|
|
@ -1,6 +0,0 @@
|
||||||
package models
|
|
||||||
|
|
||||||
type AttrPathNode struct {
|
|
||||||
Key string
|
|
||||||
Parent *AttrPathNode
|
|
||||||
}
|
|
||||||
|
|
@ -2,30 +2,18 @@ package itemrenderer
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
|
||||||
"text/tabwriter"
|
|
||||||
|
|
||||||
"lmika.dev/cmd/dynamo-browse/internal/dynamo-browse/models"
|
"lmika.dev/cmd/dynamo-browse/internal/dynamo-browse/models"
|
||||||
"lmika.dev/cmd/dynamo-browse/internal/dynamo-browse/models/itemrender"
|
"lmika.dev/cmd/dynamo-browse/internal/dynamo-browse/models/itemrender"
|
||||||
|
"io"
|
||||||
|
"text/tabwriter"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Service struct {
|
type Service struct {
|
||||||
annotation Annotation
|
styles styleRenderer
|
||||||
styles styleRenderer
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewService(
|
func NewService(fileTypeStyle StyleRenderer, metaInfoStyle StyleRenderer) *Service {
|
||||||
fileTypeStyle StyleRenderer,
|
|
||||||
metaInfoStyle StyleRenderer,
|
|
||||||
) *Service {
|
|
||||||
if fileTypeStyle == nil {
|
|
||||||
fileTypeStyle = plainTextStyleRenderer{}
|
|
||||||
}
|
|
||||||
if metaInfoStyle == nil {
|
|
||||||
metaInfoStyle = plainTextStyleRenderer{}
|
|
||||||
}
|
|
||||||
return &Service{
|
return &Service{
|
||||||
annotation: nil,
|
|
||||||
styles: styleRenderer{
|
styles: styleRenderer{
|
||||||
fileTypeRenderer: fileTypeStyle,
|
fileTypeRenderer: fileTypeStyle,
|
||||||
metaInfoRenderer: metaInfoStyle,
|
metaInfoRenderer: metaInfoStyle,
|
||||||
|
|
@ -33,10 +21,6 @@ func NewService(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Service) SetAnnotation(a Annotation) {
|
|
||||||
s.annotation = a
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Service) RenderItem(w io.Writer, item models.Item, resultSet *models.ResultSet, plainText bool) {
|
func (s *Service) RenderItem(w io.Writer, item models.Item, resultSet *models.ResultSet, plainText bool) {
|
||||||
styles := s.styles
|
styles := s.styles
|
||||||
if plainText {
|
if plainText {
|
||||||
|
|
@ -49,47 +33,25 @@ func (s *Service) RenderItem(w io.Writer, item models.Item, resultSet *models.Re
|
||||||
for _, colName := range resultSet.Columns() {
|
for _, colName := range resultSet.Columns() {
|
||||||
seenColumns[colName] = struct{}{}
|
seenColumns[colName] = struct{}{}
|
||||||
if r := itemrender.ToRenderer(item[colName]); r != nil {
|
if r := itemrender.ToRenderer(item[colName]); r != nil {
|
||||||
p := models.AttrPathNode{Key: colName}
|
s.renderItem(tabWriter, "", colName, r, styles)
|
||||||
s.renderItem(tabWriter, resultSet, item, p, "", r, styles)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for k, _ := range item {
|
for k, _ := range item {
|
||||||
if _, seen := seenColumns[k]; !seen {
|
if _, seen := seenColumns[k]; !seen {
|
||||||
if r := itemrender.ToRenderer(item[k]); r != nil {
|
if r := itemrender.ToRenderer(item[k]); r != nil {
|
||||||
p := models.AttrPathNode{Key: k}
|
s.renderItem(tabWriter, "", k, r, styles)
|
||||||
s.renderItem(tabWriter, resultSet, item, p, "", r, styles)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
tabWriter.Flush()
|
tabWriter.Flush()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *Service) renderItem(
|
func (m *Service) renderItem(w io.Writer, prefix string, name string, r itemrender.Renderer, sr styleRenderer) {
|
||||||
w io.Writer,
|
fmt.Fprintf(w, "%s%v\t%s\t%s%s\n",
|
||||||
resultSet *models.ResultSet,
|
prefix, name, sr.fileTypeRenderer.Render(r.TypeName()), r.StringValue(), sr.metaInfoRenderer.Render(r.MetaInfo()))
|
||||||
item models.Item,
|
|
||||||
path models.AttrPathNode,
|
|
||||||
prefix string,
|
|
||||||
r itemrender.Renderer,
|
|
||||||
sr styleRenderer,
|
|
||||||
) {
|
|
||||||
fmt.Fprint(w, prefix)
|
|
||||||
fmt.Fprint(w, path.Key)
|
|
||||||
fmt.Fprint(w, "\t")
|
|
||||||
fmt.Fprint(w, sr.fileTypeRenderer.Render(r.TypeName()))
|
|
||||||
fmt.Fprint(w, "\t")
|
|
||||||
fmt.Fprint(w, r.StringValue())
|
|
||||||
fmt.Fprint(w, sr.metaInfoRenderer.Render(r.MetaInfo()))
|
|
||||||
if m.annotation != nil {
|
|
||||||
fmt.Fprint(w, " ")
|
|
||||||
fmt.Fprint(w, sr.metaInfoRenderer.Render(m.annotation.AnnotateAttribute(resultSet, item, path)))
|
|
||||||
}
|
|
||||||
fmt.Fprint(w, "\n")
|
|
||||||
|
|
||||||
if subitems := r.SubItems(); len(subitems) > 0 {
|
if subitems := r.SubItems(); len(subitems) > 0 {
|
||||||
for _, si := range subitems {
|
for _, si := range subitems {
|
||||||
p := models.AttrPathNode{Key: si.Key, Parent: &path}
|
m.renderItem(w, prefix+" ", si.Key, si.Value, sr)
|
||||||
m.renderItem(w, resultSet, item, p, prefix+" ", si.Value, sr)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -98,13 +60,3 @@ type styleRenderer struct {
|
||||||
fileTypeRenderer StyleRenderer
|
fileTypeRenderer StyleRenderer
|
||||||
metaInfoRenderer StyleRenderer
|
metaInfoRenderer StyleRenderer
|
||||||
}
|
}
|
||||||
|
|
||||||
type Annotation interface {
|
|
||||||
AnnotateAttribute(rs *models.ResultSet, item models.Item, path models.AttrPathNode) string
|
|
||||||
}
|
|
||||||
|
|
||||||
type AnnotationFunc func(rs *models.ResultSet, item models.Item, path models.AttrPathNode) string
|
|
||||||
|
|
||||||
func (af AnnotationFunc) AnnotateAttribute(rs *models.ResultSet, item models.Item, path models.AttrPathNode) string {
|
|
||||||
return af(rs, item, path)
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,6 @@
|
||||||
package dynamoitemview
|
package dynamoitemview
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/charmbracelet/bubbles/viewport"
|
"github.com/charmbracelet/bubbles/viewport"
|
||||||
tea "github.com/charmbracelet/bubbletea"
|
tea "github.com/charmbracelet/bubbletea"
|
||||||
"github.com/charmbracelet/lipgloss"
|
"github.com/charmbracelet/lipgloss"
|
||||||
|
|
@ -11,6 +9,7 @@ import (
|
||||||
"lmika.dev/cmd/dynamo-browse/internal/dynamo-browse/ui/teamodels/frame"
|
"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/layout"
|
||||||
"lmika.dev/cmd/dynamo-browse/internal/dynamo-browse/ui/teamodels/styles"
|
"lmika.dev/cmd/dynamo-browse/internal/dynamo-browse/ui/teamodels/styles"
|
||||||
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Model struct {
|
type Model struct {
|
||||||
|
|
|
||||||
|
|
@ -20,7 +20,7 @@ type ItemViewStyle struct {
|
||||||
var DefaultStyles = Styles{
|
var DefaultStyles = Styles{
|
||||||
ItemView: ItemViewStyle{
|
ItemView: ItemViewStyle{
|
||||||
FieldType: lipgloss.NewStyle().Foreground(lipgloss.AdaptiveColor{Light: "#2B800C", Dark: "#73C653"}),
|
FieldType: lipgloss.NewStyle().Foreground(lipgloss.AdaptiveColor{Light: "#2B800C", Dark: "#73C653"}),
|
||||||
MetaInfo: lipgloss.NewStyle().Foreground(lipgloss.Color("#707070")),
|
MetaInfo: lipgloss.NewStyle().Foreground(lipgloss.Color("#888888")),
|
||||||
},
|
},
|
||||||
Frames: frame.Style{
|
Frames: frame.Style{
|
||||||
ActiveTitle: lipgloss.NewStyle().
|
ActiveTitle: lipgloss.NewStyle().
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue