|
|
|
@ -5,17 +5,21 @@ import (
|
|
|
|
|
tea "github.com/charmbracelet/bubbletea"
|
|
|
|
|
"github.com/lmika/dynamo-browse/internal/common/ui/commandctrl"
|
|
|
|
|
"github.com/lmika/dynamo-browse/internal/dynamo-browse/controllers"
|
|
|
|
|
"github.com/lmika/dynamo-browse/internal/dynamo-browse/models"
|
|
|
|
|
"github.com/pkg/errors"
|
|
|
|
|
"ucl.lmika.dev/repl"
|
|
|
|
|
"ucl.lmika.dev/ucl"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
type StandardCommands struct {
|
|
|
|
|
ReadController *controllers.TableReadController
|
|
|
|
|
ReadController *controllers.TableReadController
|
|
|
|
|
WriteController *controllers.TableWriteController
|
|
|
|
|
ExportController *controllers.ExportController
|
|
|
|
|
KeyBindingController *controllers.KeyBindingController
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var cmdQuitDoc = repl.Doc{
|
|
|
|
|
Brief: "Quits dynamo-browse",
|
|
|
|
|
Usage: "quit",
|
|
|
|
|
Detailed: `
|
|
|
|
|
This will quit dynamo-browse immediately, without prompting to apply
|
|
|
|
|
any changes.
|
|
|
|
@ -29,7 +33,7 @@ func (sc StandardCommands) cmdQuit(ctx context.Context, args ucl.CallArgs) (any,
|
|
|
|
|
|
|
|
|
|
var cmdTableDoc = repl.Doc{
|
|
|
|
|
Brief: "Prompt for table to scan",
|
|
|
|
|
Usage: "table [NAME]",
|
|
|
|
|
Usage: "[NAME]",
|
|
|
|
|
Args: []repl.ArgDoc{
|
|
|
|
|
{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
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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) {
|
|
|
|
|
ucl.SetBuiltin("quit", sc.cmdQuit)
|
|
|
|
|
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
|
|
|
|
|
}
|
|
|
|
|