From cec75c0d5b2cdb67a7b499c3a6e1a6e2e2da168a Mon Sep 17 00:00:00 2001 From: sawka Date: Mon, 20 Mar 2023 12:19:48 -0700 Subject: [PATCH] add simple history expansion --- db/schema.sql | 22 +++++----- pkg/cmdrunner/cmdrunner.go | 83 ++++++++++++++++++++++++++++++++++++++ pkg/sstore/dbops.go | 16 ++++++++ 3 files changed, 110 insertions(+), 11 deletions(-) diff --git a/db/schema.sql b/db/schema.sql index 059edcc6..68a47ba9 100644 --- a/db/schema.sql +++ b/db/schema.sql @@ -95,7 +95,7 @@ CREATE TABLE cmd ( runout json NOT NULL, rtnstate boolean NOT NULL, rtnbasehash varchar(36) NOT NULL, - rtndiffhasharr json NOT NULL, + rtndiffhasharr json NOT NULL, screenid varchar(36) NOT NULL DEFAULT '', PRIMARY KEY (sessionid, cmdid) ); CREATE TABLE history ( @@ -113,7 +113,16 @@ CREATE TABLE history ( cmdstr text NOT NULL, ismetacmd boolean, incognito boolean -); +, linenum int NOT NULL DEFAULT 0); +CREATE TABLE activity ( + day varchar(20) PRIMARY KEY, + uploaded boolean NOT NULL, + tdata json NOT NULL, + tzname varchar(50) NOT NULL, + tzoffset int NOT NULL, + clientversion varchar(20) NOT NULL, + clientarch varchar(20) NOT NULL +, buildtime varchar(20) NOT NULL DEFAULT '-', osrelease varchar(20) NOT NULL DEFAULT '-'); CREATE TABLE bookmark ( bookmarkid varchar(36) PRIMARY KEY, createdts bigint NOT NULL, @@ -134,15 +143,6 @@ CREATE TABLE bookmark_cmd ( cmdid varchar(36) NOT NULL, PRIMARY KEY (bookmarkid, sessionid, cmdid) ); -CREATE TABLE activity ( - day varchar(20) PRIMARY KEY, - uploaded boolean NOT NULL, - tdata json NOT NULL, - tzname varchar(50) NOT NULL, - tzoffset int NOT NULL, - clientversion varchar(50) NOT NULL, - clientarch varchar(50) NOT NULL -, buildtime varchar(20) NOT NULL DEFAULT '-', osrelease varchar(20) NOT NULL DEFAULT '-'); CREATE TABLE playbook ( playbookid varchar(36) PRIMARY KEY, playbookname varchar(100) NOT NULL, diff --git a/pkg/cmdrunner/cmdrunner.go b/pkg/cmdrunner/cmdrunner.go index 6cfb69c1..e976be13 100644 --- a/pkg/cmdrunner/cmdrunner.go +++ b/pkg/cmdrunner/cmdrunner.go @@ -47,6 +47,7 @@ const DefaultPTERM = "MxM" const MaxCommandLen = 4096 const MaxSignalLen = 12 const MaxSignalNum = 64 +const MaxEvalDepth = 5 var ColorNames = []string{"black", "red", "green", "yellow", "blue", "magenta", "cyan", "white", "orange"} var RemoteColorNames = []string{"red", "green", "yellow", "blue", "magenta", "cyan", "white", "orange"} @@ -87,6 +88,7 @@ var sigNameRe = regexp.MustCompile("^((SIG[A-Z0-9]+)|(\\d+))$") type contextType string var historyContextKey = contextType("history") +var depthContextKey = contextType("depth") type SetVarScope struct { ScopeName string @@ -323,6 +325,68 @@ func resolveNonNegInt(arg string, def int) (int, error) { return ival, nil } +var histExpansionRe = regexp.MustCompile(`^!(\d+)$`) + +func doCmdHistoryExpansion(ctx context.Context, ids resolvedIds, cmdStr string) (string, error) { + if !strings.HasPrefix(cmdStr, "!") { + return "", nil + } + if strings.HasPrefix(cmdStr, "! ") { + return "", nil + } + if cmdStr == "!!" { + return doHistoryExpansion(ctx, ids, -1) + } + if strings.HasPrefix(cmdStr, "!-") { + return "", fmt.Errorf("prompt does not support negative history offsets, use a stable positive history offset instead: '![linenum]'") + } + m := histExpansionRe.FindStringSubmatch(cmdStr) + if m == nil { + return "", fmt.Errorf("unsupported history substitution, can use '!!' or '![linenum]'") + } + ival, err := strconv.Atoi(m[1]) + if err != nil { + return "", fmt.Errorf("invalid history expansion") + } + return doHistoryExpansion(ctx, ids, ival) +} + +func doHistoryExpansion(ctx context.Context, ids resolvedIds, hnum int) (string, error) { + if hnum == 0 { + return "", fmt.Errorf("invalid history expansion, cannot expand line number '0'") + } + if hnum < -1 { + return "", fmt.Errorf("invalid history expansion, cannot expand negative history offsets") + } + foundHistoryNum := hnum + if hnum == -1 { + var err error + foundHistoryNum, err = sstore.GetLastHistoryLineNum(ctx, ids.ScreenId) + if err != nil { + return "", fmt.Errorf("cannot expand history, error finding last history item: %v", err) + } + if foundHistoryNum == 0 { + return "", fmt.Errorf("cannot expand history, no last history item") + } + } + hitem, err := sstore.GetHistoryItemByLineNum(ctx, ids.ScreenId, foundHistoryNum) + if err != nil { + return "", fmt.Errorf("cannot get history item '%d': %v", foundHistoryNum, err) + } + if hitem == nil { + return "", fmt.Errorf("cannot expand history, history item '%d' not found", foundHistoryNum) + } + return hitem.CmdStr, nil +} + +func getEvalDepth(ctx context.Context) int { + depthVal := ctx.Value(depthContextKey) + if depthVal == nil { + return 0 + } + return depthVal.(int) +} + func RunCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (sstore.UpdatePacket, error) { ids, err := resolveUiIds(ctx, pk, R_Session|R_Screen|R_RemoteConnected) if err != nil { @@ -333,6 +397,22 @@ func RunCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (sstore.U } renderer := pk.Kwargs["renderer"] cmdStr := firstArg(pk) + expandedCmdStr, err := doCmdHistoryExpansion(ctx, ids, cmdStr) + if err != nil { + return nil, err + } + if expandedCmdStr != "" { + newPk := scpacket.MakeFeCommandPacket() + newPk.MetaCmd = "eval" + newPk.Args = []string{expandedCmdStr} + newPk.Kwargs = pk.Kwargs + newPk.RawStr = pk.RawStr + newPk.UIContext = pk.UIContext + newPk.Interactive = pk.Interactive + evalDepth := getEvalDepth(ctx) + ctxWithDepth := context.WithValue(ctx, depthContextKey, evalDepth+1) + return EvalCommand(ctxWithDepth, newPk) + } isRtnStateCmd := IsReturnStateCommand(cmdStr) // runPacket.State is set in remote.RunCommand() runPacket := packet.MakeRunPacket() @@ -410,6 +490,9 @@ func EvalCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (sstore. // fall through (non-fatal error) } } + if getEvalDepth(ctx) > MaxEvalDepth { + return nil, fmt.Errorf("alias/history expansion max-depth exceeded") + } var historyContext historyContextType ctxWithHistory := context.WithValue(ctx, historyContextKey, &historyContext) var update sstore.UpdatePacket diff --git a/pkg/sstore/dbops.go b/pkg/sstore/dbops.go index e10f6571..3af2c051 100644 --- a/pkg/sstore/dbops.go +++ b/pkg/sstore/dbops.go @@ -341,6 +341,22 @@ func GetHistoryItems(ctx context.Context, opts HistoryQueryOpts) (*HistoryQueryR return rtn, nil } +func GetHistoryItemByLineNum(ctx context.Context, screenId string, lineNum int) (*HistoryItemType, error) { + return WithTxRtn(ctx, func(tx *TxWrap) (*HistoryItemType, error) { + query := `SELECT * FROM history WHERE screenid = ? AND linenum = ?` + hitem := GetMapGen[*HistoryItemType](tx, query, screenId, lineNum) + return hitem, nil + }) +} + +func GetLastHistoryLineNum(ctx context.Context, screenId string) (int, error) { + return WithTxRtn(ctx, func(tx *TxWrap) (int, error) { + query := `SELECT COALESCE(max(linenum), 0) FROM history WHERE screenid = ?` + maxLineNum := tx.GetInt(query, screenId) + return maxLineNum, nil + }) +} + // includes archived sessions func GetBareSessions(ctx context.Context) ([]*SessionType, error) { var rtn []*SessionType