Added back the core interactive commands
This commit is contained in:
parent
17381f3d0b
commit
cb908ec4eb
|
@ -162,7 +162,9 @@ func main() {
|
||||||
|
|
||||||
commandController := commandctrl.NewCommandController(inputHistoryService,
|
commandController := commandctrl.NewCommandController(inputHistoryService,
|
||||||
cmdpacks.StandardCommands{
|
cmdpacks.StandardCommands{
|
||||||
ReadController: tableReadController,
|
ReadController: tableReadController,
|
||||||
|
WriteController: tableWriteController,
|
||||||
|
ExportController: exportController,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
commandController.AddCommandLookupExtension(scriptController)
|
commandController.AddCommandLookupExtension(scriptController)
|
||||||
|
@ -183,6 +185,7 @@ func main() {
|
||||||
pasteboardProvider,
|
pasteboardProvider,
|
||||||
keyBindings,
|
keyBindings,
|
||||||
)
|
)
|
||||||
|
commandController.SetUIStateProvider(&model)
|
||||||
|
|
||||||
// Pre-determine if layout has dark background. This prevents calls for creating a list to hang.
|
// Pre-determine if layout has dark background. This prevents calls for creating a list to hang.
|
||||||
osstyle.DetectCurrentScheme()
|
osstyle.DetectCurrentScheme()
|
||||||
|
|
|
@ -5,17 +5,21 @@ import (
|
||||||
tea "github.com/charmbracelet/bubbletea"
|
tea "github.com/charmbracelet/bubbletea"
|
||||||
"github.com/lmika/dynamo-browse/internal/common/ui/commandctrl"
|
"github.com/lmika/dynamo-browse/internal/common/ui/commandctrl"
|
||||||
"github.com/lmika/dynamo-browse/internal/dynamo-browse/controllers"
|
"github.com/lmika/dynamo-browse/internal/dynamo-browse/controllers"
|
||||||
|
"github.com/lmika/dynamo-browse/internal/dynamo-browse/models"
|
||||||
|
"github.com/pkg/errors"
|
||||||
"ucl.lmika.dev/repl"
|
"ucl.lmika.dev/repl"
|
||||||
"ucl.lmika.dev/ucl"
|
"ucl.lmika.dev/ucl"
|
||||||
)
|
)
|
||||||
|
|
||||||
type StandardCommands struct {
|
type StandardCommands struct {
|
||||||
ReadController *controllers.TableReadController
|
ReadController *controllers.TableReadController
|
||||||
|
WriteController *controllers.TableWriteController
|
||||||
|
ExportController *controllers.ExportController
|
||||||
|
KeyBindingController *controllers.KeyBindingController
|
||||||
}
|
}
|
||||||
|
|
||||||
var cmdQuitDoc = repl.Doc{
|
var cmdQuitDoc = repl.Doc{
|
||||||
Brief: "Quits dynamo-browse",
|
Brief: "Quits dynamo-browse",
|
||||||
Usage: "quit",
|
|
||||||
Detailed: `
|
Detailed: `
|
||||||
This will quit dynamo-browse immediately, without prompting to apply
|
This will quit dynamo-browse immediately, without prompting to apply
|
||||||
any changes.
|
any changes.
|
||||||
|
@ -29,7 +33,7 @@ func (sc StandardCommands) cmdQuit(ctx context.Context, args ucl.CallArgs) (any,
|
||||||
|
|
||||||
var cmdTableDoc = repl.Doc{
|
var cmdTableDoc = repl.Doc{
|
||||||
Brief: "Prompt for table to scan",
|
Brief: "Prompt for table to scan",
|
||||||
Usage: "table [NAME]",
|
Usage: "[NAME]",
|
||||||
Args: []repl.ArgDoc{
|
Args: []repl.ArgDoc{
|
||||||
{Name: "name", Brief: "Name of the table to scan"},
|
{Name: "name", Brief: "Name of the table to scan"},
|
||||||
},
|
},
|
||||||
|
@ -54,7 +58,321 @@ func (sc StandardCommands) cmdTable(ctx context.Context, args ucl.CallArgs) (any
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var cmdExportDoc = repl.Doc{
|
||||||
|
Brief: "Exports a result-set as CSV",
|
||||||
|
Usage: "FILENAME [-all]",
|
||||||
|
Args: []repl.ArgDoc{
|
||||||
|
{Name: "filename", Brief: "Filename to export to"},
|
||||||
|
{Name: "-all", Brief: "Export all results from the table"},
|
||||||
|
},
|
||||||
|
Detailed: `
|
||||||
|
The fields of the current table view will be treated as the header of the
|
||||||
|
exported table.
|
||||||
|
|
||||||
|
This command is intended only for interactive sessions and is not suitable
|
||||||
|
for scripting. The export will run asynchronously.
|
||||||
|
`,
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sc StandardCommands) cmdExport(ctx context.Context, args ucl.CallArgs) (any, error) {
|
||||||
|
var filename string
|
||||||
|
if err := args.Bind(&filename); err != nil {
|
||||||
|
return nil, errors.New("expected filename")
|
||||||
|
}
|
||||||
|
|
||||||
|
opts := controllers.ExportOptions{
|
||||||
|
AllResults: args.HasSwitch("all"),
|
||||||
|
}
|
||||||
|
|
||||||
|
commandctrl.PostMsg(ctx, sc.ExportController.ExportCSV(filename, opts))
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var cmdMarkDoc = repl.Doc{
|
||||||
|
Brief: "Set the marked items of the current result-set",
|
||||||
|
Usage: "[WHAT] [-where EXPR]",
|
||||||
|
Args: []repl.ArgDoc{
|
||||||
|
{Name: "what", Brief: "Items to mark. Defaults to 'all'"},
|
||||||
|
{Name: "-where", Brief: "Filter expression select items to mark"},
|
||||||
|
},
|
||||||
|
Detailed: `
|
||||||
|
WHAT can be one of:
|
||||||
|
|
||||||
|
- all: Mark all items in the current result-set
|
||||||
|
- none: Unmark all items in the current result-set
|
||||||
|
- toggle: Toggle the marked state of all items in the current result-set
|
||||||
|
`,
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sc StandardCommands) cmdMark(ctx context.Context, args ucl.CallArgs) (any, error) {
|
||||||
|
var markOp = controllers.MarkOpMark
|
||||||
|
|
||||||
|
if args.NArgs() > 0 {
|
||||||
|
var markOpStr string
|
||||||
|
if err := args.Bind(&markOpStr); err == nil {
|
||||||
|
switch markOpStr {
|
||||||
|
case "all":
|
||||||
|
markOp = controllers.MarkOpMark
|
||||||
|
case "none":
|
||||||
|
markOp = controllers.MarkOpUnmark
|
||||||
|
case "toggle":
|
||||||
|
markOp = controllers.MarkOpToggle
|
||||||
|
default:
|
||||||
|
return nil, errors.New("unrecognised mark operation")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var whereExpr = ""
|
||||||
|
if args.HasSwitch("where") {
|
||||||
|
if err := args.BindSwitch("where", &whereExpr); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
commandctrl.PostMsg(ctx, sc.ReadController.Mark(markOp, whereExpr))
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var cmdNextPageDoc = repl.Doc{
|
||||||
|
Brief: "Retrieve and display the next page of the current result-set",
|
||||||
|
Detailed: `
|
||||||
|
This command is intended only for interactive sessions and is not suitable
|
||||||
|
for scripting. Fetching the next page will run asynchronously.
|
||||||
|
`,
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sc StandardCommands) cmdNextPage(ctx context.Context, args ucl.CallArgs) (any, error) {
|
||||||
|
commandctrl.PostMsg(ctx, sc.ReadController.NextPage())
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var cmdDeleteDoc = repl.Doc{
|
||||||
|
Brief: "Delete the marked items of the current result-set",
|
||||||
|
Detailed: `
|
||||||
|
The user will be prompted to confirm the deletion. If approved, the
|
||||||
|
items will be deleted immediately.
|
||||||
|
|
||||||
|
This command is intended only for interactive sessions and is not suitable
|
||||||
|
for scripting.
|
||||||
|
`,
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sc StandardCommands) cmdDelete(ctx context.Context, args ucl.CallArgs) (any, error) {
|
||||||
|
commandctrl.PostMsg(ctx, sc.WriteController.DeleteMarked())
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var cmdNewItemDoc = repl.Doc{
|
||||||
|
Brief: "Adds a new item to the current result-set",
|
||||||
|
Detailed: `
|
||||||
|
The user will be prompted to enter the values for each required attribute,
|
||||||
|
such as the partition and sort key. The new item will be commited to the database
|
||||||
|
upon the next write.
|
||||||
|
|
||||||
|
This command is intended only for interactive sessions and is not suitable
|
||||||
|
for scripting.
|
||||||
|
`,
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sc StandardCommands) cmdNewItem(ctx context.Context, args ucl.CallArgs) (any, error) {
|
||||||
|
commandctrl.PostMsg(ctx, sc.WriteController.NewItem())
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var cmdCloneDoc = repl.Doc{
|
||||||
|
Brief: "Adds a copy of the selected item as a new item to the current result-set",
|
||||||
|
Detailed: `
|
||||||
|
The user will be prompted to enter the partition and sort key. All other
|
||||||
|
attributes will be cloned from the selected item. The new item will be
|
||||||
|
commited to the database upon the next write.
|
||||||
|
|
||||||
|
This command is intended only for interactive sessions and is not suitable
|
||||||
|
for scripting.
|
||||||
|
`,
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sc StandardCommands) cmdClone(ctx context.Context, args ucl.CallArgs) (any, error) {
|
||||||
|
selectedItemIndex, ok := commandctrl.SelectedItemIndex(ctx)
|
||||||
|
if !ok {
|
||||||
|
return nil, errors.New("no item selected")
|
||||||
|
}
|
||||||
|
|
||||||
|
commandctrl.PostMsg(ctx, sc.WriteController.CloneItem(selectedItemIndex))
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var cmdSetAttrDoc = repl.Doc{
|
||||||
|
Brief: "Modify a field value of the selected or marked items",
|
||||||
|
Usage: "ATTR [TYPE]",
|
||||||
|
Args: []repl.ArgDoc{
|
||||||
|
{Name: "attr", Brief: "Attribute to modify"},
|
||||||
|
{Name: "-S", Brief: "Set attribute to a string"},
|
||||||
|
{Name: "-N", Brief: "Set attribute to a number"},
|
||||||
|
{Name: "-BOOL", Brief: "Set attribute to a boolean"},
|
||||||
|
{Name: "-NULL", Brief: "Set attribute to a null"},
|
||||||
|
{Name: "-TO", Brief: "Set attribute to the result of an expression"},
|
||||||
|
},
|
||||||
|
Detailed: `
|
||||||
|
The user will be prompted to enter the new value for the attribute.
|
||||||
|
If the attribute type is not known, then a type will need to be specified.
|
||||||
|
Otherwise, the type will be unchanged. The modified item will be
|
||||||
|
commited to the database upon the next write.
|
||||||
|
`,
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sc StandardCommands) cmdSetAttr(ctx context.Context, args ucl.CallArgs) (any, error) {
|
||||||
|
var fieldName string
|
||||||
|
if err := args.Bind(&fieldName); err != nil {
|
||||||
|
return nil, errors.New("expected field name")
|
||||||
|
}
|
||||||
|
|
||||||
|
selectedItemIndex, ok := commandctrl.SelectedItemIndex(ctx)
|
||||||
|
if !ok {
|
||||||
|
return nil, errors.New("no item selected")
|
||||||
|
}
|
||||||
|
|
||||||
|
var itemType = models.UnsetItemType
|
||||||
|
switch {
|
||||||
|
case args.HasSwitch("S"):
|
||||||
|
itemType = models.StringItemType
|
||||||
|
case args.HasSwitch("N"):
|
||||||
|
itemType = models.NumberItemType
|
||||||
|
case args.HasSwitch("BOOL"):
|
||||||
|
itemType = models.BoolItemType
|
||||||
|
case args.HasSwitch("NULL"):
|
||||||
|
itemType = models.NullItemType
|
||||||
|
case args.HasSwitch("TO"):
|
||||||
|
itemType = models.ExprValueItemType
|
||||||
|
}
|
||||||
|
|
||||||
|
commandctrl.PostMsg(ctx, sc.WriteController.SetAttributeValue(selectedItemIndex, itemType, fieldName))
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var cmdDelAttrDoc = repl.Doc{
|
||||||
|
Brief: "Remove the field of the selected or marked items",
|
||||||
|
Usage: "ATTR",
|
||||||
|
Args: []repl.ArgDoc{
|
||||||
|
{Name: "attr", Brief: "Attribute to remove"},
|
||||||
|
},
|
||||||
|
Detailed: `
|
||||||
|
The modified item will be commited to the database upon the next write.
|
||||||
|
`,
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sc StandardCommands) cmdDelAttr(ctx context.Context, args ucl.CallArgs) (any, error) {
|
||||||
|
var fieldName string
|
||||||
|
if err := args.Bind(&fieldName); err != nil {
|
||||||
|
return nil, errors.New("expected field name")
|
||||||
|
}
|
||||||
|
|
||||||
|
selectedItemIndex, ok := commandctrl.SelectedItemIndex(ctx)
|
||||||
|
if !ok {
|
||||||
|
return nil, errors.New("no item selected")
|
||||||
|
}
|
||||||
|
|
||||||
|
commandctrl.PostMsg(ctx, sc.WriteController.DeleteAttribute(selectedItemIndex, fieldName))
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var cmdPutDoc = repl.Doc{
|
||||||
|
Brief: "Commit changes to the table",
|
||||||
|
Detailed: `
|
||||||
|
This will put all new and modified items.
|
||||||
|
|
||||||
|
This command is intended only for interactive sessions and is not suitable
|
||||||
|
for scripting. The user will be prompted to confirm the changes.
|
||||||
|
`,
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sc StandardCommands) cmdPut(ctx context.Context, args ucl.CallArgs) (any, error) {
|
||||||
|
commandctrl.PostMsg(ctx, sc.WriteController.PutItems())
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var cmdTouchDoc = repl.Doc{
|
||||||
|
Brief: "Put the currently selected item",
|
||||||
|
Detailed: `
|
||||||
|
This will put the currently selected item, regardless of whether it has been
|
||||||
|
modified.
|
||||||
|
|
||||||
|
This command is intended only for interactive sessions and is not suitable
|
||||||
|
for scripting. The user will be prompted to confirm the touch.
|
||||||
|
`,
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sc StandardCommands) cmdTouch(ctx context.Context, args ucl.CallArgs) (any, error) {
|
||||||
|
selectedItemIndex, ok := commandctrl.SelectedItemIndex(ctx)
|
||||||
|
if !ok {
|
||||||
|
return nil, errors.New("no item selected")
|
||||||
|
}
|
||||||
|
|
||||||
|
commandctrl.PostMsg(ctx, sc.WriteController.TouchItem(selectedItemIndex))
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var cmdNoisyTouchDoc = repl.Doc{
|
||||||
|
Brief: "Put the currently selected item by deleting it first",
|
||||||
|
Detailed: `
|
||||||
|
This will put the currently selected item, regardless of whether it has been
|
||||||
|
modified. It does so by removing the item from the table, then adding it back again.
|
||||||
|
|
||||||
|
This command is intended only for interactive sessions and is not suitable
|
||||||
|
for scripting. The user will be prompted to confirm the touch.
|
||||||
|
`,
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sc StandardCommands) cmdNoisyTouch(ctx context.Context, args ucl.CallArgs) (any, error) {
|
||||||
|
selectedItemIndex, ok := commandctrl.SelectedItemIndex(ctx)
|
||||||
|
if !ok {
|
||||||
|
return nil, errors.New("no item selected")
|
||||||
|
}
|
||||||
|
|
||||||
|
commandctrl.PostMsg(ctx, sc.WriteController.NoisyTouchItem(selectedItemIndex))
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var cmdRebindDoc = repl.Doc{
|
||||||
|
Brief: "Binds a new key to an action",
|
||||||
|
Usage: "ACTION KEY",
|
||||||
|
Args: []repl.ArgDoc{
|
||||||
|
{Name: "action", Brief: "Action to bind"},
|
||||||
|
{Name: "key", Brief: "Key to bind"},
|
||||||
|
},
|
||||||
|
Detailed: `
|
||||||
|
If the key is already bound to an action, it will be replaced.
|
||||||
|
The set of actions this command accepts is well-defined. For binding
|
||||||
|
to arbitrary actions, use the ui:bind command.
|
||||||
|
`,
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sc StandardCommands) cmdRebind(ctx context.Context, args ucl.CallArgs) (any, error) {
|
||||||
|
var bindingName, newKey string
|
||||||
|
if err := args.Bind(&bindingName, &newKey); err != nil {
|
||||||
|
return nil, errors.New("expected: bindingName newKey")
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: should only force if not interactive
|
||||||
|
commandctrl.PostMsg(ctx, sc.KeyBindingController.Rebind(bindingName, newKey, false))
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
func (sc StandardCommands) ConfigureUCL(ucl *ucl.Inst) {
|
func (sc StandardCommands) ConfigureUCL(ucl *ucl.Inst) {
|
||||||
ucl.SetBuiltin("quit", sc.cmdQuit)
|
ucl.SetBuiltin("quit", sc.cmdQuit)
|
||||||
ucl.SetBuiltin("table", sc.cmdTable)
|
ucl.SetBuiltin("table", sc.cmdTable)
|
||||||
|
ucl.SetBuiltin("export", sc.cmdExport)
|
||||||
|
ucl.SetBuiltin("mark", sc.cmdMark)
|
||||||
|
// unmark --> alias for { mark none }
|
||||||
|
ucl.SetBuiltin("next-page", sc.cmdNextPage)
|
||||||
|
ucl.SetBuiltin("delete", sc.cmdDelete)
|
||||||
|
ucl.SetBuiltin("new-item", sc.cmdNewItem)
|
||||||
|
ucl.SetBuiltin("clone", sc.cmdClone)
|
||||||
|
ucl.SetBuiltin("set-attr", sc.cmdSetAttr)
|
||||||
|
ucl.SetBuiltin("del-attr", sc.cmdDelAttr)
|
||||||
|
ucl.SetBuiltin("put", sc.cmdPut)
|
||||||
|
ucl.SetBuiltin("touch", sc.cmdTouch)
|
||||||
|
ucl.SetBuiltin("noisy-touch", sc.cmdNoisyTouch)
|
||||||
|
ucl.SetBuiltin("rebind", sc.cmdRebind)
|
||||||
|
// set-opt --> alias to opts:set
|
||||||
}
|
}
|
||||||
|
|
|
@ -28,6 +28,7 @@ type CommandController struct {
|
||||||
commandList *CommandList
|
commandList *CommandList
|
||||||
lookupExtensions []CommandLookupExtension
|
lookupExtensions []CommandLookupExtension
|
||||||
completionProvider CommandCompletionProvider
|
completionProvider CommandCompletionProvider
|
||||||
|
uiStateProvider UIStateProvider
|
||||||
cmdChan chan cmdMessage
|
cmdChan chan cmdMessage
|
||||||
msgChan chan tea.Msg
|
msgChan chan tea.Msg
|
||||||
interactive bool
|
interactive bool
|
||||||
|
@ -68,6 +69,10 @@ func (c *CommandController) StartMessageSender(msgSender func(tea.Msg)) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *CommandController) SetUIStateProvider(provider UIStateProvider) {
|
||||||
|
c.uiStateProvider = provider
|
||||||
|
}
|
||||||
|
|
||||||
func (c *CommandController) AddCommandLookupExtension(ext CommandLookupExtension) {
|
func (c *CommandController) AddCommandLookupExtension(ext CommandLookupExtension) {
|
||||||
c.lookupExtensions = append(c.lookupExtensions, ext)
|
c.lookupExtensions = append(c.lookupExtensions, ext)
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,3 +15,12 @@ func PostMsg(ctx context.Context, msg tea.Msg) {
|
||||||
cmdCtl.postMessage(msg)
|
cmdCtl.postMessage(msg)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func SelectedItemIndex(ctx context.Context) (int, bool) {
|
||||||
|
cmdCtl, ok := ctx.Value(commandCtlKey).(*CommandController)
|
||||||
|
if !ok {
|
||||||
|
return 0, false
|
||||||
|
}
|
||||||
|
|
||||||
|
return cmdCtl.uiStateProvider.SelectedItemIndex(), true
|
||||||
|
}
|
||||||
|
|
|
@ -8,3 +8,7 @@ import (
|
||||||
type IterProvider interface {
|
type IterProvider interface {
|
||||||
Iter(ctx context.Context, category string) services.HistoryProvider
|
Iter(ctx context.Context, category string) services.HistoryProvider
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type UIStateProvider interface {
|
||||||
|
SelectedItemIndex() int
|
||||||
|
}
|
||||||
|
|
|
@ -393,3 +393,7 @@ func (m *Model) promptToQuit() tea.Msg {
|
||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (m *Model) SelectedItemIndex() int {
|
||||||
|
return m.tableView.SelectedItemIndex()
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in a new issue