réplica de
https://github.com/quitesimpleorg/hs9001.git
synced 2025-07-08 16:34:55 +02:00
Comparar commits
3 Commits
3a6a1b2aa9
...
v0.5
Autor | SHA1 | Fecha | |
---|---|---|---|
6d4e7a96dc | |||
ebcdfa5ff4 | |||
0949ee422a |
39
history.go
39
history.go
@ -2,8 +2,10 @@ package main
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"hs9001/liner"
|
||||
"io"
|
||||
"log"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
)
|
||||
|
||||
@ -11,14 +13,32 @@ type history struct {
|
||||
conn *sql.DB
|
||||
}
|
||||
|
||||
func (h *history) GetHistoryByPrefix(prefix string) (ph []string) {
|
||||
func createSearchOpts(query string, mode int) searchopts {
|
||||
opts := searchopts{}
|
||||
o := "DESC"
|
||||
opts.order = &o
|
||||
lim := 100
|
||||
opts.limit = &lim
|
||||
cmdqry := prefix + "%"
|
||||
opts.command = &cmdqry
|
||||
opts.command = &query
|
||||
|
||||
switch mode {
|
||||
case liner.ModeGlobal:
|
||||
break
|
||||
case liner.ModeWorkdir:
|
||||
workdir, err := filepath.Abs(".")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
opts.workdir = &workdir
|
||||
default:
|
||||
panic("Invalid mode supplied")
|
||||
}
|
||||
return opts
|
||||
}
|
||||
|
||||
func (h *history) GetHistoryByPrefix(prefix string, mode int) (ph []string) {
|
||||
cmdquery := prefix + "%"
|
||||
opts := createSearchOpts(cmdquery, mode)
|
||||
results := search(h.conn, opts)
|
||||
for e := results.Back(); e != nil; e = e.Prev() {
|
||||
entry, ok := e.Value.(*HistoryEntry)
|
||||
@ -29,14 +49,11 @@ func (h *history) GetHistoryByPrefix(prefix string) (ph []string) {
|
||||
}
|
||||
return
|
||||
}
|
||||
func (h *history) GetHistoryByPattern(pattern string) (ph []string, pos []int) {
|
||||
opts := searchopts{}
|
||||
o := "DESC"
|
||||
opts.order = &o
|
||||
lim := 100
|
||||
opts.limit = &lim
|
||||
cmdqry := "%" + pattern + "%"
|
||||
opts.command = &cmdqry
|
||||
|
||||
func (h *history) GetHistoryByPattern(pattern string, mode int) (ph []string, pos []int) {
|
||||
cmdquery := "%" + pattern + "%"
|
||||
opts := createSearchOpts(cmdquery, mode)
|
||||
|
||||
results := search(h.conn, opts)
|
||||
for e := results.Back(); e != nil; e = e.Prev() {
|
||||
entry, ok := e.Value.(*HistoryEntry)
|
||||
|
@ -37,8 +37,8 @@ type HistoryProvider interface {
|
||||
WriteHistory(w io.Writer) (num int, err error)
|
||||
AppendHistory(item string)
|
||||
ClearHistory()
|
||||
GetHistoryByPrefix(prefix string) (ph []string)
|
||||
GetHistoryByPattern(pattern string) (ph []string, pos []int)
|
||||
GetHistoryByPrefix(prefix string, mode int) (ph []string)
|
||||
GetHistoryByPattern(pattern string, mode int) (ph []string, pos []int)
|
||||
RLock()
|
||||
RUnlock()
|
||||
}
|
||||
@ -95,11 +95,11 @@ func (s *State) AppendHistory(item string) {
|
||||
func (s *State) ClearHistory() {
|
||||
s.historyProvider.ClearHistory()
|
||||
}
|
||||
func (s *State) getHistoryByPrefix(prefix string) (ph []string) {
|
||||
return s.historyProvider.GetHistoryByPrefix(prefix)
|
||||
func (s *State) getHistoryByPrefix(prefix string, mode int) (ph []string) {
|
||||
return s.historyProvider.GetHistoryByPrefix(prefix, mode)
|
||||
}
|
||||
func (s *State) getHistoryByPattern(pattern string) (ph []string, pos []int) {
|
||||
return s.historyProvider.GetHistoryByPattern(pattern)
|
||||
func (s *State) getHistoryByPattern(pattern string, mode int) (ph []string, pos []int) {
|
||||
return s.historyProvider.GetHistoryByPattern(pattern, mode)
|
||||
}
|
||||
|
||||
// SetHistoryProvider allows you to set a custom provider
|
||||
|
@ -90,6 +90,11 @@ const (
|
||||
tabReverse
|
||||
)
|
||||
|
||||
const (
|
||||
ModeGlobal = iota
|
||||
ModeWorkdir
|
||||
)
|
||||
|
||||
func (s *State) refresh(prompt []rune, buf []rune, pos int) error {
|
||||
if s.columns == 0 {
|
||||
return ErrInternal
|
||||
@ -411,8 +416,26 @@ func (s *State) tabComplete(p []rune, line []rune, pos int) ([]rune, int, interf
|
||||
|
||||
// reverse intelligent search, implements a bash-like history search.
|
||||
func (s *State) reverseISearch(origLine []rune, origPos int) ([]rune, int, interface{}, error) {
|
||||
p := "(reverse-i-search)`': "
|
||||
err := s.refresh([]rune(p), origLine, origPos)
|
||||
modeSelect := false
|
||||
currentMode := ModeGlobal
|
||||
|
||||
getPrompt := func(arg string) string {
|
||||
prompt := ""
|
||||
switch currentMode {
|
||||
case ModeWorkdir:
|
||||
prompt = "(reverse:cwd)`%s': "
|
||||
case ModeGlobal:
|
||||
prompt = "(reverse:global)`%s': "
|
||||
default:
|
||||
panic("Invalid mode")
|
||||
}
|
||||
if modeSelect {
|
||||
prompt = "(select mode)`%s': "
|
||||
}
|
||||
return fmt.Sprintf(prompt, arg)
|
||||
}
|
||||
|
||||
err := s.refresh([]rune(getPrompt("")), origLine, origPos)
|
||||
if err != nil {
|
||||
return origLine, origPos, rune(esc), err
|
||||
}
|
||||
@ -426,11 +449,10 @@ func (s *State) reverseISearch(origLine []rune, origPos int) ([]rune, int, inter
|
||||
|
||||
getLine := func() ([]rune, []rune, int) {
|
||||
search := string(line)
|
||||
prompt := "(reverse-i-search)`%s': "
|
||||
return []rune(fmt.Sprintf(prompt, search)), []rune(foundLine), foundPos
|
||||
return []rune(getPrompt(search)), []rune(foundLine), foundPos
|
||||
}
|
||||
|
||||
history, positions := s.getHistoryByPattern(string(line))
|
||||
history, positions := s.getHistoryByPattern(string(line), currentMode)
|
||||
historyPos := len(history) - 1
|
||||
|
||||
for {
|
||||
@ -450,6 +472,8 @@ func (s *State) reverseISearch(origLine []rune, origPos int) ([]rune, int, inter
|
||||
} else {
|
||||
s.doBeep()
|
||||
}
|
||||
case ctrlA:
|
||||
modeSelect = true
|
||||
case ctrlS: // Search forward
|
||||
if historyPos < len(history)-1 && historyPos >= 0 {
|
||||
historyPos++
|
||||
@ -467,7 +491,7 @@ func (s *State) reverseISearch(origLine []rune, origPos int) ([]rune, int, inter
|
||||
pos -= n
|
||||
|
||||
// For each char deleted, display the last matching line of history
|
||||
history, positions := s.getHistoryByPattern(string(line))
|
||||
history, positions := s.getHistoryByPattern(string(line), currentMode)
|
||||
historyPos = len(history) - 1
|
||||
if len(history) > 0 {
|
||||
foundLine = history[historyPos]
|
||||
@ -480,17 +504,28 @@ func (s *State) reverseISearch(origLine []rune, origPos int) ([]rune, int, inter
|
||||
case ctrlG: // Cancel
|
||||
return origLine, origPos, rune(esc), err
|
||||
|
||||
case tab, cr, lf, ctrlA, ctrlB, ctrlD, ctrlE, ctrlF, ctrlK,
|
||||
case tab, cr, lf, ctrlB, ctrlD, ctrlE, ctrlF, ctrlK,
|
||||
ctrlL, ctrlN, ctrlO, ctrlP, ctrlQ, ctrlT, ctrlU, ctrlV, ctrlW, ctrlX, ctrlY, ctrlZ:
|
||||
fallthrough
|
||||
case 0, ctrlC, esc, 28, 29, 30, 31:
|
||||
return []rune(foundLine), foundPos, next, err
|
||||
default:
|
||||
if modeSelect {
|
||||
switch v {
|
||||
case 'g':
|
||||
currentMode = ModeGlobal
|
||||
case 'w':
|
||||
currentMode = ModeWorkdir
|
||||
}
|
||||
modeSelect = false
|
||||
break
|
||||
}
|
||||
|
||||
line = append(line[:pos], append([]rune{v}, line[pos:]...)...)
|
||||
pos++
|
||||
|
||||
// For each keystroke typed, display the last matching line of history
|
||||
history, positions = s.getHistoryByPattern(string(line))
|
||||
history, positions = s.getHistoryByPattern(string(line), currentMode)
|
||||
historyPos = len(history) - 1
|
||||
if len(history) > 0 {
|
||||
foundLine = history[historyPos]
|
||||
@ -726,7 +761,7 @@ mainLoop:
|
||||
case ctrlP: // up
|
||||
historyAction = true
|
||||
if historyStale {
|
||||
historyPrefix = s.getHistoryByPrefix(string(line))
|
||||
historyPrefix = s.getHistoryByPrefix(string(line), ModeGlobal)
|
||||
historyPos = len(historyPrefix)
|
||||
historyStale = false
|
||||
}
|
||||
@ -744,7 +779,7 @@ mainLoop:
|
||||
case ctrlN: // down
|
||||
historyAction = true
|
||||
if historyStale {
|
||||
historyPrefix = s.getHistoryByPrefix(string(line))
|
||||
historyPrefix = s.getHistoryByPrefix(string(line), ModeGlobal)
|
||||
historyPos = len(historyPrefix)
|
||||
historyStale = false
|
||||
}
|
||||
@ -912,7 +947,7 @@ mainLoop:
|
||||
case up:
|
||||
historyAction = true
|
||||
if historyStale {
|
||||
historyPrefix = s.getHistoryByPrefix(string(line))
|
||||
historyPrefix = s.getHistoryByPrefix(string(line), ModeGlobal)
|
||||
historyPos = len(historyPrefix)
|
||||
historyStale = false
|
||||
}
|
||||
@ -929,7 +964,7 @@ mainLoop:
|
||||
case down:
|
||||
historyAction = true
|
||||
if historyStale {
|
||||
historyPrefix = s.getHistoryByPrefix(string(line))
|
||||
historyPrefix = s.getHistoryByPrefix(string(line), ModeGlobal)
|
||||
historyPos = len(historyPrefix)
|
||||
historyStale = false
|
||||
}
|
||||
|
34
main.go
34
main.go
@ -164,7 +164,7 @@ type searchopts struct {
|
||||
func search(conn *sql.DB, opts searchopts) list.List {
|
||||
args := make([]interface{}, 0)
|
||||
var sb strings.Builder
|
||||
sb.WriteString("SELECT id, command, workdir, user, hostname, retval ")
|
||||
sb.WriteString("SELECT id, command, workdir, user, hostname, retval, strftime(\"%s\", timestamp) ")
|
||||
sb.WriteString("FROM history ")
|
||||
sb.WriteString("WHERE 1=1 ") //1=1 so we can append as many AND foo as we want, or none
|
||||
|
||||
@ -213,10 +213,12 @@ func search(conn *sql.DB, opts searchopts) list.List {
|
||||
defer rows.Close()
|
||||
for rows.Next() {
|
||||
var entry HistoryEntry
|
||||
err = rows.Scan(&entry.id, &entry.cmd, &entry.cwd, &entry.user, &entry.hostname, &entry.retval)
|
||||
var timestamp int64
|
||||
err = rows.Scan(&entry.id, &entry.cmd, &entry.cwd, &entry.user, &entry.hostname, &entry.retval, ×tamp)
|
||||
if err != nil {
|
||||
log.Panic(err)
|
||||
}
|
||||
entry.timestamp = time.Unix(timestamp, 0)
|
||||
result.PushBack(&entry)
|
||||
}
|
||||
return result
|
||||
@ -351,13 +353,14 @@ func main() {
|
||||
var afterTime string
|
||||
var beforeTime string
|
||||
var distinct bool = true
|
||||
var today bool = false
|
||||
var retVal int
|
||||
searchCmd.StringVar(&workDir, "cwd", "", "Search only within this workdir")
|
||||
searchCmd.StringVar(&afterTime, "after", "", "Start searching from this timeframe")
|
||||
searchCmd.StringVar(&beforeTime, "before", "", "End searching from this timeframe")
|
||||
searchCmd.BoolVar(&distinct, "distinct", true, "Remove consecutive duplicate commands from output")
|
||||
searchCmd.BoolVar(&today, "today", false, "Search only today's entries. Overrides --after")
|
||||
searchCmd.IntVar(&retVal, "ret", -9001, "Only query commands that returned with this exit code. -9001=all (default)")
|
||||
|
||||
searchCmd.Parse(globalargs)
|
||||
|
||||
args := searchCmd.Args()
|
||||
@ -378,6 +381,11 @@ func main() {
|
||||
}
|
||||
opts.workdir = &wd
|
||||
}
|
||||
|
||||
if today {
|
||||
afterTime = "today"
|
||||
}
|
||||
|
||||
if afterTime != "" {
|
||||
afterTimestamp, err := naturaldate.Parse(afterTime, time.Now())
|
||||
if err != nil {
|
||||
@ -398,13 +406,31 @@ func main() {
|
||||
results := search(conn, opts)
|
||||
|
||||
previousCmd := ""
|
||||
|
||||
fi, err := os.Stdout.Stat()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
//Don't print colors if output is piped
|
||||
printColors := true
|
||||
if (fi.Mode() & os.ModeCharDevice) == 0 {
|
||||
printColors = false
|
||||
}
|
||||
|
||||
for e := results.Front(); e != nil; e = e.Next() {
|
||||
entry, ok := e.Value.(*HistoryEntry)
|
||||
if !ok {
|
||||
log.Panic("Failed to retrieve entries")
|
||||
}
|
||||
if !distinct || previousCmd != entry.cmd {
|
||||
fmt.Printf("%s\n", entry.cmd)
|
||||
prefix := ""
|
||||
postfix := ""
|
||||
if printColors && entry.retval != 0 {
|
||||
prefix = "\033[38;5;88m"
|
||||
postfix = "\033[0m"
|
||||
}
|
||||
fmt.Printf("%s%s%s\n", prefix, entry.cmd, postfix)
|
||||
}
|
||||
previousCmd = entry.cmd
|
||||
}
|
||||
|
Referencia en una nueva incidencia
Block a user