Merge remote-tracking branch 'refs/remotes/origin/main'
This commit is contained in:
commit
3bf5b6ec93
|
@ -13,8 +13,10 @@ import (
|
||||||
"github.com/lmika/audax/internal/common/workspaces"
|
"github.com/lmika/audax/internal/common/workspaces"
|
||||||
"github.com/lmika/audax/internal/dynamo-browse/controllers"
|
"github.com/lmika/audax/internal/dynamo-browse/controllers"
|
||||||
"github.com/lmika/audax/internal/dynamo-browse/providers/dynamo"
|
"github.com/lmika/audax/internal/dynamo-browse/providers/dynamo"
|
||||||
|
"github.com/lmika/audax/internal/dynamo-browse/providers/inputhistorystore"
|
||||||
"github.com/lmika/audax/internal/dynamo-browse/providers/settingstore"
|
"github.com/lmika/audax/internal/dynamo-browse/providers/settingstore"
|
||||||
"github.com/lmika/audax/internal/dynamo-browse/providers/workspacestore"
|
"github.com/lmika/audax/internal/dynamo-browse/providers/workspacestore"
|
||||||
|
"github.com/lmika/audax/internal/dynamo-browse/services/inputhistory"
|
||||||
"github.com/lmika/audax/internal/dynamo-browse/services/itemrenderer"
|
"github.com/lmika/audax/internal/dynamo-browse/services/itemrenderer"
|
||||||
"github.com/lmika/audax/internal/dynamo-browse/services/jobs"
|
"github.com/lmika/audax/internal/dynamo-browse/services/jobs"
|
||||||
keybindings_service "github.com/lmika/audax/internal/dynamo-browse/services/keybindings"
|
keybindings_service "github.com/lmika/audax/internal/dynamo-browse/services/keybindings"
|
||||||
|
@ -81,6 +83,7 @@ func main() {
|
||||||
dynamoProvider := dynamo.NewProvider(dynamoClient)
|
dynamoProvider := dynamo.NewProvider(dynamoClient)
|
||||||
resultSetSnapshotStore := workspacestore.NewResultSetSnapshotStore(ws)
|
resultSetSnapshotStore := workspacestore.NewResultSetSnapshotStore(ws)
|
||||||
settingStore := settingstore.New(ws)
|
settingStore := settingstore.New(ws)
|
||||||
|
inputHistoryStore := inputhistorystore.NewInputHistoryStore(ws)
|
||||||
|
|
||||||
if *flagRO {
|
if *flagRO {
|
||||||
if err := settingStore.SetReadOnly(*flagRO); err != nil {
|
if err := settingStore.SetReadOnly(*flagRO); err != nil {
|
||||||
|
@ -98,10 +101,11 @@ func main() {
|
||||||
itemRendererService := itemrenderer.NewService(uiStyles.ItemView.FieldType, uiStyles.ItemView.MetaInfo)
|
itemRendererService := itemrenderer.NewService(uiStyles.ItemView.FieldType, uiStyles.ItemView.MetaInfo)
|
||||||
scriptManagerService := scriptmanager.New()
|
scriptManagerService := scriptmanager.New()
|
||||||
jobsService := jobs.NewService(eventBus)
|
jobsService := jobs.NewService(eventBus)
|
||||||
|
inputHistoryService := inputhistory.New(inputHistoryStore)
|
||||||
|
|
||||||
state := controllers.NewState()
|
state := controllers.NewState()
|
||||||
jobsController := controllers.NewJobsController(jobsService, eventBus, false)
|
jobsController := controllers.NewJobsController(jobsService, eventBus, false)
|
||||||
tableReadController := controllers.NewTableReadController(state, tableService, workspaceService, itemRendererService, jobsController, eventBus, *flagTable)
|
tableReadController := controllers.NewTableReadController(state, tableService, workspaceService, itemRendererService, jobsController, inputHistoryService, eventBus, *flagTable)
|
||||||
tableWriteController := controllers.NewTableWriteController(state, tableService, jobsController, tableReadController, settingStore)
|
tableWriteController := controllers.NewTableWriteController(state, tableService, jobsController, tableReadController, settingStore)
|
||||||
columnsController := controllers.NewColumnsController(eventBus)
|
columnsController := controllers.NewColumnsController(eventBus)
|
||||||
exportController := controllers.NewExportController(state, columnsController)
|
exportController := controllers.NewExportController(state, columnsController)
|
||||||
|
@ -112,7 +116,7 @@ func main() {
|
||||||
keyBindingService := keybindings_service.NewService(keyBindings)
|
keyBindingService := keybindings_service.NewService(keyBindings)
|
||||||
keyBindingController := controllers.NewKeyBindingController(keyBindingService)
|
keyBindingController := controllers.NewKeyBindingController(keyBindingService)
|
||||||
|
|
||||||
commandController := commandctrl.NewCommandController()
|
commandController := commandctrl.NewCommandController(inputHistoryService)
|
||||||
commandController.AddCommandLookupExtension(scriptController)
|
commandController.AddCommandLookupExtension(scriptController)
|
||||||
|
|
||||||
model := ui.NewModel(
|
model := ui.NewModel(
|
||||||
|
|
|
@ -32,7 +32,7 @@ func main() {
|
||||||
|
|
||||||
ctrl := controllers.NewLogFileController(service, flag.Arg(0))
|
ctrl := controllers.NewLogFileController(service, flag.Arg(0))
|
||||||
|
|
||||||
cmdController := commandctrl.NewCommandController()
|
cmdController := commandctrl.NewCommandController(nil)
|
||||||
//cmdController.AddCommands(&commandctrl.CommandList{
|
//cmdController.AddCommands(&commandctrl.CommandList{
|
||||||
// Commands: map[string]commandctrl.Command{
|
// Commands: map[string]commandctrl.Command{
|
||||||
// "cd": func(args []string) tea.Cmd {
|
// "cd": func(args []string) tea.Cmd {
|
||||||
|
|
|
@ -47,7 +47,7 @@ func main() {
|
||||||
|
|
||||||
ctrl := controllers.New(service)
|
ctrl := controllers.New(service)
|
||||||
|
|
||||||
cmdController := commandctrl.NewCommandController()
|
cmdController := commandctrl.NewCommandController(nil)
|
||||||
cmdController.AddCommands(&commandctrl.CommandList{
|
cmdController.AddCommands(&commandctrl.CommandList{
|
||||||
Commands: map[string]commandctrl.Command{
|
Commands: map[string]commandctrl.Command{
|
||||||
"cd": func(ec commandctrl.ExecContext, args []string) tea.Msg {
|
"cd": func(ec commandctrl.ExecContext, args []string) tea.Msg {
|
||||||
|
|
|
@ -3,6 +3,7 @@ package commandctrl
|
||||||
import (
|
import (
|
||||||
"bufio"
|
"bufio"
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"context"
|
||||||
tea "github.com/charmbracelet/bubbletea"
|
tea "github.com/charmbracelet/bubbletea"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
"log"
|
"log"
|
||||||
|
@ -14,13 +15,17 @@ import (
|
||||||
"github.com/lmika/shellwords"
|
"github.com/lmika/shellwords"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const commandsCategory = "commands"
|
||||||
|
|
||||||
type CommandController struct {
|
type CommandController struct {
|
||||||
|
historyProvider IterProvider
|
||||||
commandList *CommandList
|
commandList *CommandList
|
||||||
lookupExtensions []CommandLookupExtension
|
lookupExtensions []CommandLookupExtension
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewCommandController() *CommandController {
|
func NewCommandController(historyProvider IterProvider) *CommandController {
|
||||||
return &CommandController{
|
return &CommandController{
|
||||||
|
historyProvider: historyProvider,
|
||||||
commandList: nil,
|
commandList: nil,
|
||||||
lookupExtensions: nil,
|
lookupExtensions: nil,
|
||||||
}
|
}
|
||||||
|
@ -38,6 +43,7 @@ func (c *CommandController) AddCommandLookupExtension(ext CommandLookupExtension
|
||||||
func (c *CommandController) Prompt() tea.Msg {
|
func (c *CommandController) Prompt() tea.Msg {
|
||||||
return events.PromptForInputMsg{
|
return events.PromptForInputMsg{
|
||||||
Prompt: ":",
|
Prompt: ":",
|
||||||
|
History: c.historyProvider.Iter(context.Background(), commandsCategory),
|
||||||
OnDone: func(value string) tea.Msg {
|
OnDone: func(value string) tea.Msg {
|
||||||
return c.Execute(value)
|
return c.Execute(value)
|
||||||
},
|
},
|
||||||
|
|
|
@ -1,7 +1,9 @@
|
||||||
package commandctrl_test
|
package commandctrl_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"github.com/lmika/audax/internal/common/ui/events"
|
"github.com/lmika/audax/internal/common/ui/events"
|
||||||
|
"github.com/lmika/audax/internal/dynamo-browse/services"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/lmika/audax/internal/common/ui/commandctrl"
|
"github.com/lmika/audax/internal/common/ui/commandctrl"
|
||||||
|
@ -10,7 +12,7 @@ import (
|
||||||
|
|
||||||
func TestCommandController_Prompt(t *testing.T) {
|
func TestCommandController_Prompt(t *testing.T) {
|
||||||
t.Run("prompt user for a command", func(t *testing.T) {
|
t.Run("prompt user for a command", func(t *testing.T) {
|
||||||
cmd := commandctrl.NewCommandController()
|
cmd := commandctrl.NewCommandController(mockIterProvider{})
|
||||||
|
|
||||||
res := cmd.Prompt()
|
res := cmd.Prompt()
|
||||||
|
|
||||||
|
@ -19,3 +21,10 @@ func TestCommandController_Prompt(t *testing.T) {
|
||||||
assert.Equal(t, ":", promptForInputMsg.Prompt)
|
assert.Equal(t, ":", promptForInputMsg.Prompt)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type mockIterProvider struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m mockIterProvider) Iter(ctx context.Context, category string) services.HistoryProvider {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
10
internal/common/ui/commandctrl/iface.go
Normal file
10
internal/common/ui/commandctrl/iface.go
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
package commandctrl
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"github.com/lmika/audax/internal/dynamo-browse/services"
|
||||||
|
)
|
||||||
|
|
||||||
|
type IterProvider interface {
|
||||||
|
Iter(ctx context.Context, category string) services.HistoryProvider
|
||||||
|
}
|
|
@ -2,6 +2,7 @@ package events
|
||||||
|
|
||||||
import (
|
import (
|
||||||
tea "github.com/charmbracelet/bubbletea"
|
tea "github.com/charmbracelet/bubbletea"
|
||||||
|
"github.com/lmika/audax/internal/dynamo-browse/services"
|
||||||
"log"
|
"log"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -22,21 +23,22 @@ func SetTeaMessage(event tea.Msg) tea.Cmd {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func PromptForInput(prompt string, onDone func(value string) tea.Msg) tea.Msg {
|
func PromptForInput(prompt string, history services.HistoryProvider, onDone func(value string) tea.Msg) tea.Msg {
|
||||||
return PromptForInputMsg{
|
return PromptForInputMsg{
|
||||||
Prompt: prompt,
|
Prompt: prompt,
|
||||||
|
History: history,
|
||||||
OnDone: onDone,
|
OnDone: onDone,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func Confirm(prompt string, onResult func(yes bool) tea.Msg) tea.Msg {
|
func Confirm(prompt string, onResult func(yes bool) tea.Msg) tea.Msg {
|
||||||
return PromptForInput(prompt, func(value string) tea.Msg {
|
return PromptForInput(prompt, nil, func(value string) tea.Msg {
|
||||||
return onResult(value == "y")
|
return onResult(value == "y")
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func ConfirmYes(prompt string, onYes func() tea.Msg) tea.Msg {
|
func ConfirmYes(prompt string, onYes func() tea.Msg) tea.Msg {
|
||||||
return PromptForInput(prompt, func(value string) tea.Msg {
|
return PromptForInput(prompt, nil, func(value string) tea.Msg {
|
||||||
if value == "y" {
|
if value == "y" {
|
||||||
return onYes()
|
return onYes()
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,6 +2,7 @@ package events
|
||||||
|
|
||||||
import (
|
import (
|
||||||
tea "github.com/charmbracelet/bubbletea"
|
tea "github.com/charmbracelet/bubbletea"
|
||||||
|
"github.com/lmika/audax/internal/dynamo-browse/services"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Error indicates that an error occurred
|
// Error indicates that an error occurred
|
||||||
|
@ -21,6 +22,7 @@ type ModeMessage string
|
||||||
// PromptForInput indicates that the context is requesting a line of input
|
// PromptForInput indicates that the context is requesting a line of input
|
||||||
type PromptForInputMsg struct {
|
type PromptForInputMsg struct {
|
||||||
Prompt string
|
Prompt string
|
||||||
|
History services.HistoryProvider
|
||||||
OnDone func(value string) tea.Msg
|
OnDone func(value string) tea.Msg
|
||||||
OnCancel func() tea.Msg
|
OnCancel func() tea.Msg
|
||||||
}
|
}
|
||||||
|
|
|
@ -67,7 +67,7 @@ func (cc *ColumnsController) onNewResultSet(rs *models.ResultSet, op resultSetUp
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cc *ColumnsController) AddColumn(afterIndex int) tea.Msg {
|
func (cc *ColumnsController) AddColumn(afterIndex int) tea.Msg {
|
||||||
return events.PromptForInput("column expr: ", func(value string) tea.Msg {
|
return events.PromptForInput("column expr: ", nil, func(value string) tea.Msg {
|
||||||
colExpr, err := queryexpr.Parse(value)
|
colExpr, err := queryexpr.Parse(value)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return events.Error(err)
|
return events.Error(err)
|
||||||
|
|
|
@ -11,6 +11,7 @@ import (
|
||||||
"github.com/lmika/audax/internal/dynamo-browse/models/attrcodec"
|
"github.com/lmika/audax/internal/dynamo-browse/models/attrcodec"
|
||||||
"github.com/lmika/audax/internal/dynamo-browse/models/queryexpr"
|
"github.com/lmika/audax/internal/dynamo-browse/models/queryexpr"
|
||||||
"github.com/lmika/audax/internal/dynamo-browse/models/serialisable"
|
"github.com/lmika/audax/internal/dynamo-browse/models/serialisable"
|
||||||
|
"github.com/lmika/audax/internal/dynamo-browse/services/inputhistory"
|
||||||
"github.com/lmika/audax/internal/dynamo-browse/services/itemrenderer"
|
"github.com/lmika/audax/internal/dynamo-browse/services/itemrenderer"
|
||||||
"github.com/lmika/audax/internal/dynamo-browse/services/viewsnapshot"
|
"github.com/lmika/audax/internal/dynamo-browse/services/viewsnapshot"
|
||||||
bus "github.com/lmika/events"
|
bus "github.com/lmika/events"
|
||||||
|
@ -42,11 +43,17 @@ const (
|
||||||
MarkOpToggle
|
MarkOpToggle
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
queryInputHistoryCategory = "queries"
|
||||||
|
filterInputHistoryCategory = "filters"
|
||||||
|
)
|
||||||
|
|
||||||
type TableReadController struct {
|
type TableReadController struct {
|
||||||
tableService TableReadService
|
tableService TableReadService
|
||||||
workspaceService *viewsnapshot.ViewSnapshotService
|
workspaceService *viewsnapshot.ViewSnapshotService
|
||||||
itemRendererService *itemrenderer.Service
|
itemRendererService *itemrenderer.Service
|
||||||
jobController *JobsController
|
jobController *JobsController
|
||||||
|
inputHistoryService *inputhistory.Service
|
||||||
eventBus *bus.Bus
|
eventBus *bus.Bus
|
||||||
tableName string
|
tableName string
|
||||||
loadFromLastView bool
|
loadFromLastView bool
|
||||||
|
@ -63,6 +70,7 @@ func NewTableReadController(
|
||||||
workspaceService *viewsnapshot.ViewSnapshotService,
|
workspaceService *viewsnapshot.ViewSnapshotService,
|
||||||
itemRendererService *itemrenderer.Service,
|
itemRendererService *itemrenderer.Service,
|
||||||
jobController *JobsController,
|
jobController *JobsController,
|
||||||
|
inputHistoryService *inputhistory.Service,
|
||||||
eventBus *bus.Bus,
|
eventBus *bus.Bus,
|
||||||
tableName string,
|
tableName string,
|
||||||
) *TableReadController {
|
) *TableReadController {
|
||||||
|
@ -72,6 +80,7 @@ func NewTableReadController(
|
||||||
workspaceService: workspaceService,
|
workspaceService: workspaceService,
|
||||||
itemRendererService: itemRendererService,
|
itemRendererService: itemRendererService,
|
||||||
jobController: jobController,
|
jobController: jobController,
|
||||||
|
inputHistoryService: inputHistoryService,
|
||||||
eventBus: eventBus,
|
eventBus: eventBus,
|
||||||
tableName: tableName,
|
tableName: tableName,
|
||||||
mutex: new(sync.Mutex),
|
mutex: new(sync.Mutex),
|
||||||
|
@ -137,6 +146,7 @@ func (c *TableReadController) ScanTable(name string) tea.Msg {
|
||||||
func (c *TableReadController) PromptForQuery() tea.Msg {
|
func (c *TableReadController) PromptForQuery() tea.Msg {
|
||||||
return events.PromptForInputMsg{
|
return events.PromptForInputMsg{
|
||||||
Prompt: "query: ",
|
Prompt: "query: ",
|
||||||
|
History: c.inputHistoryService.Iter(context.Background(), queryInputHistoryCategory),
|
||||||
OnDone: func(value string) tea.Msg {
|
OnDone: func(value string) tea.Msg {
|
||||||
resultSet := c.state.ResultSet()
|
resultSet := c.state.ResultSet()
|
||||||
if resultSet == nil {
|
if resultSet == nil {
|
||||||
|
@ -289,6 +299,7 @@ func (c *TableReadController) Mark(op MarkOp) tea.Msg {
|
||||||
func (c *TableReadController) Filter() tea.Msg {
|
func (c *TableReadController) Filter() tea.Msg {
|
||||||
return events.PromptForInputMsg{
|
return events.PromptForInputMsg{
|
||||||
Prompt: "filter: ",
|
Prompt: "filter: ",
|
||||||
|
History: c.inputHistoryService.Iter(context.Background(), filterInputHistoryCategory),
|
||||||
OnDone: func(value string) tea.Msg {
|
OnDone: func(value string) tea.Msg {
|
||||||
resultSet := c.state.ResultSet()
|
resultSet := c.state.ResultSet()
|
||||||
if resultSet == nil {
|
if resultSet == nil {
|
||||||
|
|
|
@ -8,8 +8,10 @@ import (
|
||||||
"github.com/lmika/audax/internal/dynamo-browse/controllers"
|
"github.com/lmika/audax/internal/dynamo-browse/controllers"
|
||||||
"github.com/lmika/audax/internal/dynamo-browse/models"
|
"github.com/lmika/audax/internal/dynamo-browse/models"
|
||||||
"github.com/lmika/audax/internal/dynamo-browse/providers/dynamo"
|
"github.com/lmika/audax/internal/dynamo-browse/providers/dynamo"
|
||||||
|
"github.com/lmika/audax/internal/dynamo-browse/providers/inputhistorystore"
|
||||||
"github.com/lmika/audax/internal/dynamo-browse/providers/settingstore"
|
"github.com/lmika/audax/internal/dynamo-browse/providers/settingstore"
|
||||||
"github.com/lmika/audax/internal/dynamo-browse/providers/workspacestore"
|
"github.com/lmika/audax/internal/dynamo-browse/providers/workspacestore"
|
||||||
|
"github.com/lmika/audax/internal/dynamo-browse/services/inputhistory"
|
||||||
"github.com/lmika/audax/internal/dynamo-browse/services/itemrenderer"
|
"github.com/lmika/audax/internal/dynamo-browse/services/itemrenderer"
|
||||||
"github.com/lmika/audax/internal/dynamo-browse/services/jobs"
|
"github.com/lmika/audax/internal/dynamo-browse/services/jobs"
|
||||||
"github.com/lmika/audax/internal/dynamo-browse/services/scriptmanager"
|
"github.com/lmika/audax/internal/dynamo-browse/services/scriptmanager"
|
||||||
|
@ -600,9 +602,12 @@ func newService(t *testing.T, cfg serviceConfig) *services {
|
||||||
|
|
||||||
resultSetSnapshotStore := workspacestore.NewResultSetSnapshotStore(ws)
|
resultSetSnapshotStore := workspacestore.NewResultSetSnapshotStore(ws)
|
||||||
settingStore := settingstore.New(ws)
|
settingStore := settingstore.New(ws)
|
||||||
|
inputHistoryStore := inputhistorystore.NewInputHistoryStore(ws)
|
||||||
|
|
||||||
workspaceService := viewsnapshot.NewService(resultSetSnapshotStore)
|
workspaceService := viewsnapshot.NewService(resultSetSnapshotStore)
|
||||||
itemRendererService := itemrenderer.NewService(itemrenderer.PlainTextRenderer(), itemrenderer.PlainTextRenderer())
|
itemRendererService := itemrenderer.NewService(itemrenderer.PlainTextRenderer(), itemrenderer.PlainTextRenderer())
|
||||||
scriptService := scriptmanager.New()
|
scriptService := scriptmanager.New()
|
||||||
|
inputHistoryService := inputhistory.New(inputHistoryStore)
|
||||||
|
|
||||||
client := testdynamo.SetupTestTable(t, testData)
|
client := testdynamo.SetupTestTable(t, testData)
|
||||||
|
|
||||||
|
@ -612,14 +617,14 @@ func newService(t *testing.T, cfg serviceConfig) *services {
|
||||||
|
|
||||||
state := controllers.NewState()
|
state := controllers.NewState()
|
||||||
jobsController := controllers.NewJobsController(jobs.NewService(eventBus), eventBus, true)
|
jobsController := controllers.NewJobsController(jobs.NewService(eventBus), eventBus, true)
|
||||||
readController := controllers.NewTableReadController(state, service, workspaceService, itemRendererService, jobsController, eventBus, cfg.tableName)
|
readController := controllers.NewTableReadController(state, service, workspaceService, itemRendererService, jobsController, inputHistoryService, eventBus, cfg.tableName)
|
||||||
writeController := controllers.NewTableWriteController(state, service, jobsController, readController, settingStore)
|
writeController := controllers.NewTableWriteController(state, service, jobsController, readController, settingStore)
|
||||||
settingsController := controllers.NewSettingsController(settingStore, eventBus)
|
settingsController := controllers.NewSettingsController(settingStore, eventBus)
|
||||||
columnsController := controllers.NewColumnsController(eventBus)
|
columnsController := controllers.NewColumnsController(eventBus)
|
||||||
exportController := controllers.NewExportController(state, columnsController)
|
exportController := controllers.NewExportController(state, columnsController)
|
||||||
scriptController := controllers.NewScriptController(scriptService, readController, settingsController, eventBus)
|
scriptController := controllers.NewScriptController(scriptService, readController, settingsController, eventBus)
|
||||||
|
|
||||||
commandController := commandctrl.NewCommandController()
|
commandController := commandctrl.NewCommandController(inputHistoryService)
|
||||||
commandController.AddCommandLookupExtension(scriptController)
|
commandController.AddCommandLookupExtension(scriptController)
|
||||||
|
|
||||||
if cfg.isReadOnly {
|
if cfg.isReadOnly {
|
||||||
|
|
|
@ -194,7 +194,7 @@ func (a irFieldBeginsWith) canBeExecutedAsQuery(info *models.TableInfo, qci *que
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
if keyName == info.Keys.SortKey {
|
if keyName == info.Keys.SortKey && qci.hasSeenPrimaryKey(info) {
|
||||||
return qci.addKey(info, a.name.keyName())
|
return qci.addKey(info, a.name.keyName())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -143,6 +143,9 @@ func TestModExpr_Query(t *testing.T) {
|
||||||
scanCase("when request sk equals something", `sk="something"`, `#0 = :0`,
|
scanCase("when request sk equals something", `sk="something"`, `#0 = :0`,
|
||||||
exprNameIsString(0, 0, "sk", "something"),
|
exprNameIsString(0, 0, "sk", "something"),
|
||||||
),
|
),
|
||||||
|
scanCase("when request sk starts with something", `sk^="something"`, `begins_with (#0, :0)`,
|
||||||
|
exprNameIsString(0, 0, "sk", "something"),
|
||||||
|
),
|
||||||
scanCase("with not equal", `sk != "something"`, `#0 <> :0`,
|
scanCase("with not equal", `sk != "something"`, `#0 <> :0`,
|
||||||
exprNameIsString(0, 0, "sk", "something"),
|
exprNameIsString(0, 0, "sk", "something"),
|
||||||
),
|
),
|
||||||
|
|
|
@ -69,6 +69,10 @@ func (a *astLiteralValue) String() string {
|
||||||
return *a.StringVal
|
return *a.StringVal
|
||||||
case a.IntVal != nil:
|
case a.IntVal != nil:
|
||||||
return strconv.FormatInt(*a.IntVal, 10)
|
return strconv.FormatInt(*a.IntVal, 10)
|
||||||
|
case a.TrueBoolValue:
|
||||||
|
return "true"
|
||||||
|
case a.FalseBoolValue:
|
||||||
|
return "false"
|
||||||
}
|
}
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
10
internal/dynamo-browse/providers/inputhistorystore/model.go
Normal file
10
internal/dynamo-browse/providers/inputhistorystore/model.go
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
package inputhistorystore
|
||||||
|
|
||||||
|
import "time"
|
||||||
|
|
||||||
|
type inputHistoryItem struct {
|
||||||
|
ID int `storm:"id,increment"`
|
||||||
|
Category string `storm:"index"`
|
||||||
|
Time time.Time
|
||||||
|
Item string
|
||||||
|
}
|
49
internal/dynamo-browse/providers/inputhistorystore/store.go
Normal file
49
internal/dynamo-browse/providers/inputhistorystore/store.go
Normal file
|
@ -0,0 +1,49 @@
|
||||||
|
package inputhistorystore
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"github.com/asdine/storm"
|
||||||
|
"github.com/lmika/audax/internal/common/sliceutils"
|
||||||
|
"github.com/lmika/audax/internal/common/workspaces"
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
"sort"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
const inputHistoryStore = "InputHistoryStore"
|
||||||
|
|
||||||
|
type Store struct {
|
||||||
|
ws storm.Node
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewInputHistoryStore(ws *workspaces.Workspace) *Store {
|
||||||
|
return &Store{
|
||||||
|
ws: ws.DB().From(inputHistoryStore),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Items returns all items from a category ordered by the time
|
||||||
|
func (as *Store) Items(ctx context.Context, category string) ([]string, error) {
|
||||||
|
var items []inputHistoryItem
|
||||||
|
if err := as.ws.Find("Category", category, &items); err != nil {
|
||||||
|
if errors.Is(err, storm.ErrNotFound) {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
sort.Slice(items, func(i, j int) bool {
|
||||||
|
return items[i].Time.Before(items[j].Time)
|
||||||
|
})
|
||||||
|
return sliceutils.Map(items, func(t inputHistoryItem) string {
|
||||||
|
return t.Item
|
||||||
|
}), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (as *Store) PutItem(ctx context.Context, category string, item string) error {
|
||||||
|
return as.ws.Save(&inputHistoryItem{
|
||||||
|
Time: time.Now(),
|
||||||
|
Category: category,
|
||||||
|
Item: item,
|
||||||
|
})
|
||||||
|
}
|
13
internal/dynamo-browse/services/historyprovider.go
Normal file
13
internal/dynamo-browse/services/historyprovider.go
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
package services
|
||||||
|
|
||||||
|
type HistoryProvider interface {
|
||||||
|
// Len returns the number of historical items
|
||||||
|
Len() int
|
||||||
|
|
||||||
|
// Item returns the historical item at index 'idx', where items are chronologically ordered such that the
|
||||||
|
// item at 0 is the oldest item.
|
||||||
|
Item(idx int) string
|
||||||
|
|
||||||
|
// PutItem adds an item to the history
|
||||||
|
PutItem(item string)
|
||||||
|
}
|
8
internal/dynamo-browse/services/inputhistory/iface.go
Normal file
8
internal/dynamo-browse/services/inputhistory/iface.go
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
package inputhistory
|
||||||
|
|
||||||
|
import "context"
|
||||||
|
|
||||||
|
type HistoryItemStore interface {
|
||||||
|
Items(ctx context.Context, category string) ([]string, error)
|
||||||
|
PutItem(ctx context.Context, category string, item string) error
|
||||||
|
}
|
49
internal/dynamo-browse/services/inputhistory/iter.go
Normal file
49
internal/dynamo-browse/services/inputhistory/iter.go
Normal file
|
@ -0,0 +1,49 @@
|
||||||
|
package inputhistory
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"github.com/lmika/audax/internal/dynamo-browse/services"
|
||||||
|
"log"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (svc *Service) Iter(ctx context.Context, category string) services.HistoryProvider {
|
||||||
|
items, err := svc.store.Items(ctx, category)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("warn: cannot get iter for '%v': %v", category, err)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return &Iter{svc, items, category}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (svc *Service) PutItem(ctx context.Context, category string, value string) error {
|
||||||
|
return svc.store.PutItem(ctx, category, value)
|
||||||
|
}
|
||||||
|
|
||||||
|
type Iter struct {
|
||||||
|
svc *Service
|
||||||
|
items []string
|
||||||
|
category string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *Iter) Len() int {
|
||||||
|
return len(i.items)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *Iter) Item(idx int) string {
|
||||||
|
return i.items[idx]
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *Iter) PutItem(item string) {
|
||||||
|
if strings.TrimSpace(item) == "" {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(i.items) > 0 && i.items[len(i.items)-1] == item {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := i.svc.PutItem(context.Background(), i.category, item); err != nil {
|
||||||
|
log.Printf("warn: cannot put input history: category = %v, value = %v, err = %v", i.category, item, err)
|
||||||
|
}
|
||||||
|
}
|
9
internal/dynamo-browse/services/inputhistory/service.go
Normal file
9
internal/dynamo-browse/services/inputhistory/service.go
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
package inputhistory
|
||||||
|
|
||||||
|
type Service struct {
|
||||||
|
store HistoryItemStore
|
||||||
|
}
|
||||||
|
|
||||||
|
func New(store HistoryItemStore) *Service {
|
||||||
|
return &Service{store: store}
|
||||||
|
}
|
|
@ -20,7 +20,7 @@ type StatusAndPrompt struct {
|
||||||
statusMessage string
|
statusMessage string
|
||||||
spinner spinner.Model
|
spinner spinner.Model
|
||||||
spinnerVisible bool
|
spinnerVisible bool
|
||||||
pendingInput *events.PromptForInputMsg
|
pendingInput *pendingInputState
|
||||||
textInput textinput.Model
|
textInput textinput.Model
|
||||||
width, height int
|
width, height int
|
||||||
lastModeLineHeight int
|
lastModeLineHeight int
|
||||||
|
@ -83,15 +83,15 @@ func (s *StatusAndPrompt) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||||
s.textInput.Prompt = msg.Prompt
|
s.textInput.Prompt = msg.Prompt
|
||||||
s.textInput.Focus()
|
s.textInput.Focus()
|
||||||
s.textInput.SetValue("")
|
s.textInput.SetValue("")
|
||||||
s.pendingInput = &msg
|
s.pendingInput = newPendingInputState(msg)
|
||||||
case tea.KeyMsg:
|
case tea.KeyMsg:
|
||||||
if s.pendingInput != nil {
|
if s.pendingInput != nil {
|
||||||
switch msg.Type {
|
switch msg.Type {
|
||||||
case tea.KeyCtrlC, tea.KeyEsc:
|
case tea.KeyCtrlC, tea.KeyEsc:
|
||||||
if s.pendingInput.OnCancel != nil {
|
if s.pendingInput.originalMsg.OnCancel != nil {
|
||||||
pendingInput := s.pendingInput
|
pendingInput := s.pendingInput
|
||||||
cc.Add(func() tea.Msg {
|
cc.Add(func() tea.Msg {
|
||||||
m := pendingInput.OnCancel()
|
m := pendingInput.originalMsg.OnCancel()
|
||||||
return m
|
return m
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -100,18 +100,48 @@ func (s *StatusAndPrompt) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||||
pendingInput := s.pendingInput
|
pendingInput := s.pendingInput
|
||||||
s.pendingInput = nil
|
s.pendingInput = nil
|
||||||
|
|
||||||
return s, func() tea.Msg {
|
m := pendingInput.originalMsg.OnDone(s.textInput.Value())
|
||||||
m := pendingInput.OnDone(s.textInput.Value())
|
|
||||||
return m
|
return s, tea.Batch(
|
||||||
|
events.SetTeaMessage(m),
|
||||||
|
func() tea.Msg {
|
||||||
|
if historyProvider := pendingInput.originalMsg.History; historyProvider != nil {
|
||||||
|
if _, isErrMsg := m.(events.ErrorMsg); !isErrMsg {
|
||||||
|
historyProvider.PutItem(s.textInput.Value())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
)
|
||||||
|
case tea.KeyUp:
|
||||||
|
if historyProvider := s.pendingInput.originalMsg.History; historyProvider != nil && historyProvider.Len() > 0 {
|
||||||
|
if s.pendingInput.historyIdx < 0 {
|
||||||
|
s.pendingInput.historyIdx = historyProvider.Len() - 1
|
||||||
|
} else if s.pendingInput.historyIdx > 0 {
|
||||||
|
s.pendingInput.historyIdx -= 1
|
||||||
|
} else {
|
||||||
|
s.pendingInput.historyIdx = 0
|
||||||
|
}
|
||||||
|
s.textInput.SetValue(historyProvider.Item(s.pendingInput.historyIdx))
|
||||||
|
s.textInput.SetCursor(len(s.textInput.Value()))
|
||||||
|
}
|
||||||
|
case tea.KeyDown:
|
||||||
|
if historyProvider := s.pendingInput.originalMsg.History; historyProvider != nil && historyProvider.Len() > 0 {
|
||||||
|
if s.pendingInput.historyIdx >= 0 && s.pendingInput.historyIdx < historyProvider.Len()-1 {
|
||||||
|
s.pendingInput.historyIdx += 1
|
||||||
|
}
|
||||||
|
s.textInput.SetValue(historyProvider.Item(s.pendingInput.historyIdx))
|
||||||
|
s.textInput.SetCursor(len(s.textInput.Value()))
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
if msg.Type == tea.KeyRunes {
|
if msg.Type == tea.KeyRunes {
|
||||||
msg.Runes = sliceutils.Filter(msg.Runes, func(r rune) bool { return r != '\x0d' && r != '\x0a' })
|
msg.Runes = sliceutils.Filter(msg.Runes, func(r rune) bool { return r != '\x0d' && r != '\x0a' })
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
newTextInput, cmd := s.textInput.Update(msg)
|
newTextInput, cmd := s.textInput.Update(msg)
|
||||||
s.textInput = newTextInput
|
s.textInput = newTextInput
|
||||||
return s, cmd
|
return s, cmd
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
s.statusMessage = ""
|
s.statusMessage = ""
|
||||||
}
|
}
|
||||||
|
|
12
internal/dynamo-browse/ui/teamodels/statusandprompt/types.go
Normal file
12
internal/dynamo-browse/ui/teamodels/statusandprompt/types.go
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
package statusandprompt
|
||||||
|
|
||||||
|
import "github.com/lmika/audax/internal/common/ui/events"
|
||||||
|
|
||||||
|
type pendingInputState struct {
|
||||||
|
originalMsg events.PromptForInputMsg
|
||||||
|
historyIdx int
|
||||||
|
}
|
||||||
|
|
||||||
|
func newPendingInputState(msg events.PromptForInputMsg) *pendingInputState {
|
||||||
|
return &pendingInputState{originalMsg: msg, historyIdx: -1}
|
||||||
|
}
|
|
@ -56,7 +56,7 @@ func (c *SSMController) ChangePrefix(newPrefix string) tea.Msg {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *SSMController) Clone(param models.SSMParameter) tea.Msg {
|
func (c *SSMController) Clone(param models.SSMParameter) tea.Msg {
|
||||||
return events.PromptForInput("New key: ", func(value string) tea.Msg {
|
return events.PromptForInput("New key: ", nil, func(value string) tea.Msg {
|
||||||
return func() tea.Msg {
|
return func() tea.Msg {
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
if err := c.service.Clone(ctx, param, value); err != nil {
|
if err := c.service.Clone(ctx, param, value); err != nil {
|
||||||
|
|
Loading…
Reference in a new issue