mirror of
				https://github.com/quitesimpleorg/hs9001.git
				synced 2025-11-04 02:29:29 +01:00 
			
		
		
		
	Compare commits
	
		
			5 Commits
		
	
	
		
			master
			...
			97648433c0
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 
						 | 
					97648433c0 | ||
| 
						 | 
					2450accc96 | ||
| 
						 | 
					e5c1beb937 | ||
| 
						 | 
					a2c4c99af4 | ||
| e292b4ce74 | 
							
								
								
									
										56
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										56
									
								
								README.md
									
									
									
									
									
								
							@@ -2,34 +2,15 @@
 | 
				
			|||||||
hs9001 (history search 9001) is an easy, quite simple bash history enhancement. It simply writes all
 | 
					hs9001 (history search 9001) is an easy, quite simple bash history enhancement. It simply writes all
 | 
				
			||||||
your bash commands into an sqlite database. You can then search this database.
 | 
					your bash commands into an sqlite database. You can then search this database.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
It improves over bash's built-in history mechanism as it'll aggregate the shell history of all open shells,
 | 
					 | 
				
			||||||
timestamp them and also record additional information such as the directory a command was executed in.
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
## Usage / Examples
 | 
					 | 
				
			||||||
### Search
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
```
 | 
					 | 
				
			||||||
hs [search terms]
 | 
					 | 
				
			||||||
```
 | 
					 | 
				
			||||||
You can further filter with options like `-cwd`, `-after` and so on...
 | 
					 | 
				
			||||||
For a full list, see `-help`.
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
```
 | 
					 | 
				
			||||||
hs -cwd . 
 | 
					 | 
				
			||||||
``` 
 | 
					 | 
				
			||||||
Lists all commands ever entered in this directory
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
```
 | 
					 | 
				
			||||||
hs -today -cwd . git
 | 
					 | 
				
			||||||
``` 
 | 
					 | 
				
			||||||
Lists all git commands in the current directory which have been entered today.
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
Also, it (by default) replaces bash's built-in CTRL-R mechanism, so hs9001's database will be used instead of bash's limited history files.
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
When in reverse-search mode, you can only search the history of the current directory by pressing CTRL+A and then "w".
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
## Install
 | 
					## Install
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### From source
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					go build
 | 
				
			||||||
 | 
					#move hs9001 to a PATH location
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
### Debian / Ubuntu
 | 
					### Debian / Ubuntu
 | 
				
			||||||
Latest release can be installed using apt
 | 
					Latest release can be installed using apt
 | 
				
			||||||
```
 | 
					```
 | 
				
			||||||
@@ -47,22 +28,31 @@ apk update
 | 
				
			|||||||
apk add hs9001
 | 
					apk add hs9001
 | 
				
			||||||
```
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
### From source
 | 
					
 | 
				
			||||||
```
 | 
					### Setup / Config
 | 
				
			||||||
go build
 | 
					
 | 
				
			||||||
#move hs9001 to a PATH location
 | 
					 | 
				
			||||||
```
 | 
					 | 
				
			||||||
Add this to .bashrc
 | 
					Add this to .bashrc
 | 
				
			||||||
 | 
					
 | 
				
			||||||
```
 | 
					```
 | 
				
			||||||
eval "$(hs9001 bash-enable)"
 | 
					eval "$(hs9001 bash-enable)"
 | 
				
			||||||
```
 | 
					```
 | 
				
			||||||
 | 
					By default, every system user gets his own database. You can override this by setting the environment variable
 | 
				
			||||||
 | 
					for all users that should write to your unified database.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
This will also create a `hs`alias so you have to type less in everyday usage.
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
By default, every system user gets his own database. You can override this by setting the environment variable for all users that should write to your unified database.
 | 
					 | 
				
			||||||
```
 | 
					```
 | 
				
			||||||
export HS9001_DB_PATH="/home/db/history.sqlite"
 | 
					export HS9001_DB_PATH="/home/db/history.sqlite"
 | 
				
			||||||
```
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## Usage
 | 
				
			||||||
 | 
					### Search
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					hs9001 search [search terms]
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					It is recommended to create an alias for search to make life easier, e. g.:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					alias searchh='hs9001 search'
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										45
									
								
								history.go
									
									
									
									
									
								
							
							
						
						
									
										45
									
								
								history.go
									
									
									
									
									
								
							@@ -2,10 +2,8 @@ package main
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
import (
 | 
					import (
 | 
				
			||||||
	"database/sql"
 | 
						"database/sql"
 | 
				
			||||||
	"hs9001/liner"
 | 
					 | 
				
			||||||
	"io"
 | 
						"io"
 | 
				
			||||||
	"log"
 | 
						"log"
 | 
				
			||||||
	"path/filepath"
 | 
					 | 
				
			||||||
	"strings"
 | 
						"strings"
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -13,34 +11,13 @@ type history struct {
 | 
				
			|||||||
	conn *sql.DB
 | 
						conn *sql.DB
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func createSearchOpts(query string, mode int) searchopts {
 | 
					func (h *history) GetHistoryByPrefix(prefix string) (ph []string) {
 | 
				
			||||||
	opts := searchopts{}
 | 
						opts := searchopts{}
 | 
				
			||||||
	o := "DESC"
 | 
						opts.order = "DESC"
 | 
				
			||||||
	opts.order = &o
 | 
						cmdqry := prefix + "%"
 | 
				
			||||||
	lim := 100
 | 
						opts.command = &cmdqry
 | 
				
			||||||
	opts.limit = &lim
 | 
					 | 
				
			||||||
	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)
 | 
						results := search(h.conn, opts)
 | 
				
			||||||
	for e := results.Back(); e != nil; e = e.Prev() {
 | 
						for e := results.Front(); e != nil; e = e.Next() {
 | 
				
			||||||
		entry, ok := e.Value.(*HistoryEntry)
 | 
							entry, ok := e.Value.(*HistoryEntry)
 | 
				
			||||||
		if !ok {
 | 
							if !ok {
 | 
				
			||||||
			log.Panic("Failed to retrieve entries")
 | 
								log.Panic("Failed to retrieve entries")
 | 
				
			||||||
@@ -49,13 +26,13 @@ func (h *history) GetHistoryByPrefix(prefix string, mode int) (ph []string) {
 | 
				
			|||||||
	}
 | 
						}
 | 
				
			||||||
	return
 | 
						return
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					func (h *history) GetHistoryByPattern(pattern string) (ph []string, pos []int) {
 | 
				
			||||||
func (h *history) GetHistoryByPattern(pattern string, mode int) (ph []string, pos []int) {
 | 
						opts := searchopts{}
 | 
				
			||||||
	cmdquery := "%" + pattern + "%"
 | 
						opts.order = "DESC"
 | 
				
			||||||
	opts := createSearchOpts(cmdquery, mode)
 | 
						cmdqry := "%" + pattern + "%"
 | 
				
			||||||
 | 
						opts.command = &cmdqry
 | 
				
			||||||
	results := search(h.conn, opts)
 | 
						results := search(h.conn, opts)
 | 
				
			||||||
	for e := results.Back(); e != nil; e = e.Prev() {
 | 
						for e := results.Front(); e != nil; e = e.Next() {
 | 
				
			||||||
		entry, ok := e.Value.(*HistoryEntry)
 | 
							entry, ok := e.Value.(*HistoryEntry)
 | 
				
			||||||
		if !ok {
 | 
							if !ok {
 | 
				
			||||||
			log.Panic("Failed to retrieve entries")
 | 
								log.Panic("Failed to retrieve entries")
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -37,8 +37,8 @@ type HistoryProvider interface {
 | 
				
			|||||||
	WriteHistory(w io.Writer) (num int, err error)
 | 
						WriteHistory(w io.Writer) (num int, err error)
 | 
				
			||||||
	AppendHistory(item string)
 | 
						AppendHistory(item string)
 | 
				
			||||||
	ClearHistory()
 | 
						ClearHistory()
 | 
				
			||||||
	GetHistoryByPrefix(prefix string, mode int) (ph []string)
 | 
						GetHistoryByPrefix(prefix string) (ph []string)
 | 
				
			||||||
	GetHistoryByPattern(pattern string, mode int) (ph []string, pos []int)
 | 
						GetHistoryByPattern(pattern string) (ph []string, pos []int)
 | 
				
			||||||
	RLock()
 | 
						RLock()
 | 
				
			||||||
	RUnlock()
 | 
						RUnlock()
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@@ -95,11 +95,11 @@ func (s *State) AppendHistory(item string) {
 | 
				
			|||||||
func (s *State) ClearHistory() {
 | 
					func (s *State) ClearHistory() {
 | 
				
			||||||
	s.historyProvider.ClearHistory()
 | 
						s.historyProvider.ClearHistory()
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
func (s *State) getHistoryByPrefix(prefix string, mode int) (ph []string) {
 | 
					func (s *State) getHistoryByPrefix(prefix string) (ph []string) {
 | 
				
			||||||
	return s.historyProvider.GetHistoryByPrefix(prefix, mode)
 | 
						return s.historyProvider.GetHistoryByPrefix(prefix)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
func (s *State) getHistoryByPattern(pattern string, mode int) (ph []string, pos []int) {
 | 
					func (s *State) getHistoryByPattern(pattern string) (ph []string, pos []int) {
 | 
				
			||||||
	return s.historyProvider.GetHistoryByPattern(pattern, mode)
 | 
						return s.historyProvider.GetHistoryByPattern(pattern)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// SetHistoryProvider allows you to set a custom provider
 | 
					// SetHistoryProvider allows you to set a custom provider
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -90,11 +90,6 @@ const (
 | 
				
			|||||||
	tabReverse
 | 
						tabReverse
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const (
 | 
					 | 
				
			||||||
	ModeGlobal = iota
 | 
					 | 
				
			||||||
	ModeWorkdir
 | 
					 | 
				
			||||||
)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
func (s *State) refresh(prompt []rune, buf []rune, pos int) error {
 | 
					func (s *State) refresh(prompt []rune, buf []rune, pos int) error {
 | 
				
			||||||
	if s.columns == 0 {
 | 
						if s.columns == 0 {
 | 
				
			||||||
		return ErrInternal
 | 
							return ErrInternal
 | 
				
			||||||
@@ -416,26 +411,8 @@ func (s *State) tabComplete(p []rune, line []rune, pos int) ([]rune, int, interf
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
// reverse intelligent search, implements a bash-like history search.
 | 
					// reverse intelligent search, implements a bash-like history search.
 | 
				
			||||||
func (s *State) reverseISearch(origLine []rune, origPos int) ([]rune, int, interface{}, error) {
 | 
					func (s *State) reverseISearch(origLine []rune, origPos int) ([]rune, int, interface{}, error) {
 | 
				
			||||||
	modeSelect := false
 | 
						p := "(reverse-i-search)`': "
 | 
				
			||||||
	currentMode := ModeGlobal
 | 
						err := s.refresh([]rune(p), origLine, origPos)
 | 
				
			||||||
 | 
					 | 
				
			||||||
	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 {
 | 
						if err != nil {
 | 
				
			||||||
		return origLine, origPos, rune(esc), err
 | 
							return origLine, origPos, rune(esc), err
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
@@ -449,10 +426,11 @@ func (s *State) reverseISearch(origLine []rune, origPos int) ([]rune, int, inter
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
	getLine := func() ([]rune, []rune, int) {
 | 
						getLine := func() ([]rune, []rune, int) {
 | 
				
			||||||
		search := string(line)
 | 
							search := string(line)
 | 
				
			||||||
		return []rune(getPrompt(search)), []rune(foundLine), foundPos
 | 
							prompt := "(reverse-i-search)`%s': "
 | 
				
			||||||
 | 
							return []rune(fmt.Sprintf(prompt, search)), []rune(foundLine), foundPos
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	history, positions := s.getHistoryByPattern(string(line), currentMode)
 | 
						history, positions := s.getHistoryByPattern(string(line))
 | 
				
			||||||
	historyPos := len(history) - 1
 | 
						historyPos := len(history) - 1
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	for {
 | 
						for {
 | 
				
			||||||
@@ -472,8 +450,6 @@ func (s *State) reverseISearch(origLine []rune, origPos int) ([]rune, int, inter
 | 
				
			|||||||
				} else {
 | 
									} else {
 | 
				
			||||||
					s.doBeep()
 | 
										s.doBeep()
 | 
				
			||||||
				}
 | 
									}
 | 
				
			||||||
			case ctrlA:
 | 
					 | 
				
			||||||
				modeSelect = true
 | 
					 | 
				
			||||||
			case ctrlS: // Search forward
 | 
								case ctrlS: // Search forward
 | 
				
			||||||
				if historyPos < len(history)-1 && historyPos >= 0 {
 | 
									if historyPos < len(history)-1 && historyPos >= 0 {
 | 
				
			||||||
					historyPos++
 | 
										historyPos++
 | 
				
			||||||
@@ -491,7 +467,7 @@ func (s *State) reverseISearch(origLine []rune, origPos int) ([]rune, int, inter
 | 
				
			|||||||
					pos -= n
 | 
										pos -= n
 | 
				
			||||||
 | 
					
 | 
				
			||||||
					// For each char deleted, display the last matching line of history
 | 
										// For each char deleted, display the last matching line of history
 | 
				
			||||||
					history, positions := s.getHistoryByPattern(string(line), currentMode)
 | 
										history, positions := s.getHistoryByPattern(string(line))
 | 
				
			||||||
					historyPos = len(history) - 1
 | 
										historyPos = len(history) - 1
 | 
				
			||||||
					if len(history) > 0 {
 | 
										if len(history) > 0 {
 | 
				
			||||||
						foundLine = history[historyPos]
 | 
											foundLine = history[historyPos]
 | 
				
			||||||
@@ -504,28 +480,17 @@ func (s *State) reverseISearch(origLine []rune, origPos int) ([]rune, int, inter
 | 
				
			|||||||
			case ctrlG: // Cancel
 | 
								case ctrlG: // Cancel
 | 
				
			||||||
				return origLine, origPos, rune(esc), err
 | 
									return origLine, origPos, rune(esc), err
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			case tab, cr, lf, ctrlB, ctrlD, ctrlE, ctrlF, ctrlK,
 | 
								case tab, cr, lf, ctrlA, ctrlB, ctrlD, ctrlE, ctrlF, ctrlK,
 | 
				
			||||||
				ctrlL, ctrlN, ctrlO, ctrlP, ctrlQ, ctrlT, ctrlU, ctrlV, ctrlW, ctrlX, ctrlY, ctrlZ:
 | 
									ctrlL, ctrlN, ctrlO, ctrlP, ctrlQ, ctrlT, ctrlU, ctrlV, ctrlW, ctrlX, ctrlY, ctrlZ:
 | 
				
			||||||
				fallthrough
 | 
									fallthrough
 | 
				
			||||||
			case 0, ctrlC, esc, 28, 29, 30, 31:
 | 
								case 0, ctrlC, esc, 28, 29, 30, 31:
 | 
				
			||||||
				return []rune(foundLine), foundPos, next, err
 | 
									return []rune(foundLine), foundPos, next, err
 | 
				
			||||||
			default:
 | 
								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:]...)...)
 | 
									line = append(line[:pos], append([]rune{v}, line[pos:]...)...)
 | 
				
			||||||
				pos++
 | 
									pos++
 | 
				
			||||||
 | 
					
 | 
				
			||||||
				// For each keystroke typed, display the last matching line of history
 | 
									// For each keystroke typed, display the last matching line of history
 | 
				
			||||||
				history, positions = s.getHistoryByPattern(string(line), currentMode)
 | 
									history, positions = s.getHistoryByPattern(string(line))
 | 
				
			||||||
				historyPos = len(history) - 1
 | 
									historyPos = len(history) - 1
 | 
				
			||||||
				if len(history) > 0 {
 | 
									if len(history) > 0 {
 | 
				
			||||||
					foundLine = history[historyPos]
 | 
										foundLine = history[historyPos]
 | 
				
			||||||
@@ -761,7 +726,7 @@ mainLoop:
 | 
				
			|||||||
			case ctrlP: // up
 | 
								case ctrlP: // up
 | 
				
			||||||
				historyAction = true
 | 
									historyAction = true
 | 
				
			||||||
				if historyStale {
 | 
									if historyStale {
 | 
				
			||||||
					historyPrefix = s.getHistoryByPrefix(string(line), ModeGlobal)
 | 
										historyPrefix = s.getHistoryByPrefix(string(line))
 | 
				
			||||||
					historyPos = len(historyPrefix)
 | 
										historyPos = len(historyPrefix)
 | 
				
			||||||
					historyStale = false
 | 
										historyStale = false
 | 
				
			||||||
				}
 | 
									}
 | 
				
			||||||
@@ -779,7 +744,7 @@ mainLoop:
 | 
				
			|||||||
			case ctrlN: // down
 | 
								case ctrlN: // down
 | 
				
			||||||
				historyAction = true
 | 
									historyAction = true
 | 
				
			||||||
				if historyStale {
 | 
									if historyStale {
 | 
				
			||||||
					historyPrefix = s.getHistoryByPrefix(string(line), ModeGlobal)
 | 
										historyPrefix = s.getHistoryByPrefix(string(line))
 | 
				
			||||||
					historyPos = len(historyPrefix)
 | 
										historyPos = len(historyPrefix)
 | 
				
			||||||
					historyStale = false
 | 
										historyStale = false
 | 
				
			||||||
				}
 | 
									}
 | 
				
			||||||
@@ -947,7 +912,7 @@ mainLoop:
 | 
				
			|||||||
			case up:
 | 
								case up:
 | 
				
			||||||
				historyAction = true
 | 
									historyAction = true
 | 
				
			||||||
				if historyStale {
 | 
									if historyStale {
 | 
				
			||||||
					historyPrefix = s.getHistoryByPrefix(string(line), ModeGlobal)
 | 
										historyPrefix = s.getHistoryByPrefix(string(line))
 | 
				
			||||||
					historyPos = len(historyPrefix)
 | 
										historyPos = len(historyPrefix)
 | 
				
			||||||
					historyStale = false
 | 
										historyStale = false
 | 
				
			||||||
				}
 | 
									}
 | 
				
			||||||
@@ -964,7 +929,7 @@ mainLoop:
 | 
				
			|||||||
			case down:
 | 
								case down:
 | 
				
			||||||
				historyAction = true
 | 
									historyAction = true
 | 
				
			||||||
				if historyStale {
 | 
									if historyStale {
 | 
				
			||||||
					historyPrefix = s.getHistoryByPrefix(string(line), ModeGlobal)
 | 
										historyPrefix = s.getHistoryByPrefix(string(line))
 | 
				
			||||||
					historyPos = len(historyPrefix)
 | 
										historyPos = len(historyPrefix)
 | 
				
			||||||
					historyStale = false
 | 
										historyStale = false
 | 
				
			||||||
				}
 | 
									}
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										67
									
								
								main.go
									
									
									
									
									
								
							
							
						
						
									
										67
									
								
								main.go
									
									
									
									
									
								
							@@ -66,11 +66,6 @@ func migrateDatabase(conn *sql.DB, currentVersion int) {
 | 
				
			|||||||
	migrations := []string{
 | 
						migrations := []string{
 | 
				
			||||||
		"ALTER TABLE history ADD COLUMN workdir varchar(4096) DEFAULT ''",
 | 
							"ALTER TABLE history ADD COLUMN workdir varchar(4096) DEFAULT ''",
 | 
				
			||||||
		"ALTER TABLE history ADD COLUMN retval integer DEFAULT -9001",
 | 
							"ALTER TABLE history ADD COLUMN retval integer DEFAULT -9001",
 | 
				
			||||||
		"ALTER TABLE history ADD COLUMN unix_tmp integer",
 | 
					 | 
				
			||||||
		"UPDATE history SET unix_tmp = strftime('%s', timestamp)",
 | 
					 | 
				
			||||||
		"DROP VIEW count_by_date",
 | 
					 | 
				
			||||||
		"ALTER TABLE history DROP COLUMN timestamp",
 | 
					 | 
				
			||||||
		"ALTER TABLE history RENAME COLUMN unix_tmp TO timestamp",
 | 
					 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if !(len(migrations) > currentVersion) {
 | 
						if !(len(migrations) > currentVersion) {
 | 
				
			||||||
@@ -162,14 +157,13 @@ type searchopts struct {
 | 
				
			|||||||
	after   *time.Time
 | 
						after   *time.Time
 | 
				
			||||||
	before  *time.Time
 | 
						before  *time.Time
 | 
				
			||||||
	retval  *int
 | 
						retval  *int
 | 
				
			||||||
	order   *string
 | 
						order   string
 | 
				
			||||||
	limit   *int
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func search(conn *sql.DB, opts searchopts) list.List {
 | 
					func search(conn *sql.DB, opts searchopts) list.List {
 | 
				
			||||||
	args := make([]interface{}, 0)
 | 
						args := make([]interface{}, 0)
 | 
				
			||||||
	var sb strings.Builder
 | 
						var sb strings.Builder
 | 
				
			||||||
	sb.WriteString("SELECT id, command, workdir, user, hostname, retval, timestamp ")
 | 
						sb.WriteString("SELECT id, command, workdir, user, hostname, retval ")
 | 
				
			||||||
	sb.WriteString("FROM history ")
 | 
						sb.WriteString("FROM history ")
 | 
				
			||||||
	sb.WriteString("WHERE 1=1 ") //1=1 so we can append as many AND foo as we want, or none
 | 
						sb.WriteString("WHERE 1=1 ") //1=1 so we can append as many AND foo as we want, or none
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -182,11 +176,11 @@ func search(conn *sql.DB, opts searchopts) list.List {
 | 
				
			|||||||
		args = append(args, opts.workdir)
 | 
							args = append(args, opts.workdir)
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	if opts.after != nil {
 | 
						if opts.after != nil {
 | 
				
			||||||
		sb.WriteString("AND timestamp > ? ")
 | 
							sb.WriteString("AND timestamp > datetime(?, 'unixepoch') ")
 | 
				
			||||||
		args = append(args, opts.after.Unix())
 | 
							args = append(args, opts.after.Unix())
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	if opts.before != nil {
 | 
						if opts.before != nil {
 | 
				
			||||||
		sb.WriteString("AND timestamp < ? ")
 | 
							sb.WriteString("AND timestamp < datetime(?, 'unixepoch') ")
 | 
				
			||||||
		args = append(args, opts.before.Unix())
 | 
							args = append(args, opts.before.Unix())
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	if opts.retval != nil {
 | 
						if opts.retval != nil {
 | 
				
			||||||
@@ -194,18 +188,7 @@ func search(conn *sql.DB, opts searchopts) list.List {
 | 
				
			|||||||
		args = append(args, opts.retval)
 | 
							args = append(args, opts.retval)
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	sb.WriteString("ORDER BY timestamp ")
 | 
						sb.WriteString("ORDER BY timestamp ")
 | 
				
			||||||
	if opts.order != nil {
 | 
					 | 
				
			||||||
		sb.WriteString(*opts.order)
 | 
					 | 
				
			||||||
		sb.WriteRune(' ')
 | 
					 | 
				
			||||||
	} else {
 | 
					 | 
				
			||||||
	sb.WriteString("ASC ")
 | 
						sb.WriteString("ASC ")
 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	if opts.limit != nil {
 | 
					 | 
				
			||||||
		sb.WriteString("LIMIT ")
 | 
					 | 
				
			||||||
		sb.WriteString(strconv.Itoa(*opts.limit))
 | 
					 | 
				
			||||||
		sb.WriteRune(' ')
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
	queryStmt := sb.String()
 | 
						queryStmt := sb.String()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -218,12 +201,10 @@ func search(conn *sql.DB, opts searchopts) list.List {
 | 
				
			|||||||
	defer rows.Close()
 | 
						defer rows.Close()
 | 
				
			||||||
	for rows.Next() {
 | 
						for rows.Next() {
 | 
				
			||||||
		var entry HistoryEntry
 | 
							var entry HistoryEntry
 | 
				
			||||||
		var timestamp int64
 | 
							err = rows.Scan(&entry.id, &entry.cmd, &entry.cwd, &entry.user, &entry.hostname, &entry.retval)
 | 
				
			||||||
		err = rows.Scan(&entry.id, &entry.cmd, &entry.cwd, &entry.user, &entry.hostname, &entry.retval, ×tamp)
 | 
					 | 
				
			||||||
		if err != nil {
 | 
							if err != nil {
 | 
				
			||||||
			log.Panic(err)
 | 
								log.Panic(err)
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
		entry.timestamp = time.Unix(timestamp, 0)
 | 
					 | 
				
			||||||
		result.PushBack(&entry)
 | 
							result.PushBack(&entry)
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	return result
 | 
						return result
 | 
				
			||||||
@@ -239,7 +220,7 @@ func delete(conn *sql.DB, entryId uint32) {
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func add(conn *sql.DB, entry HistoryEntry) {
 | 
					func add(conn *sql.DB, entry HistoryEntry) {
 | 
				
			||||||
	stmt, err := conn.Prepare("INSERT INTO history (user, command, hostname, workdir, timestamp, retval) VALUES (?, ?, ?, ?, ?,?)")
 | 
						stmt, err := conn.Prepare("INSERT INTO history (user, command, hostname, workdir, timestamp, retval) VALUES (?, ?, ?, ?, datetime(?, 'unixepoch'),?)")
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		log.Panic(err)
 | 
							log.Panic(err)
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
@@ -328,7 +309,6 @@ func main() {
 | 
				
			|||||||
				PROMPT_COMMAND='hs9001 add -ret $? "$(history 1)"'
 | 
									PROMPT_COMMAND='hs9001 add -ret $? "$(history 1)"'
 | 
				
			||||||
				bind -x '"\C-r": " READLINE_LINE=$(hs9001 bash-ctrlr 3>&1 1>&2 2>&3) READLINE_POINT=0"'
 | 
									bind -x '"\C-r": " READLINE_LINE=$(hs9001 bash-ctrlr 3>&1 1>&2 2>&3) READLINE_POINT=0"'
 | 
				
			||||||
			fi
 | 
								fi
 | 
				
			||||||
			alias hs='hs9001 search'
 | 
					 | 
				
			||||||
		`)
 | 
							`)
 | 
				
			||||||
	case "bash-disable":
 | 
						case "bash-disable":
 | 
				
			||||||
		fmt.Printf("unset PROMPT_COMMAND\n")
 | 
							fmt.Printf("unset PROMPT_COMMAND\n")
 | 
				
			||||||
@@ -358,14 +338,13 @@ func main() {
 | 
				
			|||||||
		var afterTime string
 | 
							var afterTime string
 | 
				
			||||||
		var beforeTime string
 | 
							var beforeTime string
 | 
				
			||||||
		var distinct bool = true
 | 
							var distinct bool = true
 | 
				
			||||||
		var today bool = false
 | 
					 | 
				
			||||||
		var retVal int
 | 
							var retVal int
 | 
				
			||||||
		searchCmd.StringVar(&workDir, "cwd", "", "Search only within this workdir")
 | 
							searchCmd.StringVar(&workDir, "cwd", "", "Search only within this workdir")
 | 
				
			||||||
		searchCmd.StringVar(&afterTime, "after", "", "Start searching from this timeframe")
 | 
							searchCmd.StringVar(&afterTime, "after", "", "Start searching from this timeframe")
 | 
				
			||||||
		searchCmd.StringVar(&beforeTime, "before", "", "End 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(&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.IntVar(&retVal, "ret", -9001, "Only query commands that returned with this exit code. -9001=all (default)")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		searchCmd.Parse(globalargs)
 | 
							searchCmd.Parse(globalargs)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		args := searchCmd.Args()
 | 
							args := searchCmd.Args()
 | 
				
			||||||
@@ -373,8 +352,7 @@ func main() {
 | 
				
			|||||||
		q := strings.Join(args, " ")
 | 
							q := strings.Join(args, " ")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		opts := searchopts{}
 | 
							opts := searchopts{}
 | 
				
			||||||
		o := "ASC"
 | 
							opts.order = "ASC"
 | 
				
			||||||
		opts.order = &o
 | 
					 | 
				
			||||||
		if q != "" {
 | 
							if q != "" {
 | 
				
			||||||
			cmd := "%" + q + "%"
 | 
								cmd := "%" + q + "%"
 | 
				
			||||||
			opts.command = &cmd
 | 
								opts.command = &cmd
 | 
				
			||||||
@@ -386,11 +364,6 @@ func main() {
 | 
				
			|||||||
			}
 | 
								}
 | 
				
			||||||
			opts.workdir = &wd
 | 
								opts.workdir = &wd
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					 | 
				
			||||||
		if today {
 | 
					 | 
				
			||||||
			afterTime = "today"
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		if afterTime != "" {
 | 
							if afterTime != "" {
 | 
				
			||||||
			afterTimestamp, err := naturaldate.Parse(afterTime, time.Now())
 | 
								afterTimestamp, err := naturaldate.Parse(afterTime, time.Now())
 | 
				
			||||||
			if err != nil {
 | 
								if err != nil {
 | 
				
			||||||
@@ -411,35 +384,15 @@ func main() {
 | 
				
			|||||||
		results := search(conn, opts)
 | 
							results := search(conn, opts)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		previousCmd := ""
 | 
							previousCmd := ""
 | 
				
			||||||
		previousReturn := -1
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		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() {
 | 
							for e := results.Front(); e != nil; e = e.Next() {
 | 
				
			||||||
			entry, ok := e.Value.(*HistoryEntry)
 | 
								entry, ok := e.Value.(*HistoryEntry)
 | 
				
			||||||
			if !ok {
 | 
								if !ok {
 | 
				
			||||||
				log.Panic("Failed to retrieve entries")
 | 
									log.Panic("Failed to retrieve entries")
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
			if !distinct || !(previousCmd == entry.cmd && previousReturn == entry.retval)  {
 | 
								if !distinct || previousCmd != entry.cmd {
 | 
				
			||||||
				prefix := ""
 | 
									fmt.Printf("%s\n", entry.cmd)
 | 
				
			||||||
				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
 | 
								previousCmd = entry.cmd
 | 
				
			||||||
			previousReturn = entry.retval
 | 
					 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		if cmd == "delete" {
 | 
							if cmd == "delete" {
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user