mirror of
https://github.com/ddworken/hishtory.git
synced 2025-06-27 07:22:29 +02:00
Add negative conditions to search queries + tests + better error messages by including filename:line in error messages
This commit is contained in:
parent
970e5d75db
commit
5325fc75ae
@ -8,6 +8,7 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"regexp"
|
"regexp"
|
||||||
|
"runtime"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
@ -18,7 +19,8 @@ import (
|
|||||||
func RunInteractiveBashCommands(t *testing.T, script string) string {
|
func RunInteractiveBashCommands(t *testing.T, script string) string {
|
||||||
out, err := RunInteractiveBashCommandsWithoutStrictMode(t, "set -emo pipefail\n"+script)
|
out, err := RunInteractiveBashCommandsWithoutStrictMode(t, "set -emo pipefail\n"+script)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("error when running command: %v", err)
|
_, filename, line, _ := runtime.Caller(1)
|
||||||
|
t.Fatalf("error when running command at %s:%d: %v", filename, line, err)
|
||||||
}
|
}
|
||||||
return out
|
return out
|
||||||
}
|
}
|
||||||
@ -262,6 +264,7 @@ func TestAdvancedQuery(t *testing.T) {
|
|||||||
notacommand
|
notacommand
|
||||||
cd /tmp/
|
cd /tmp/
|
||||||
echo querybydir
|
echo querybydir
|
||||||
|
hishtory disable
|
||||||
`)
|
`)
|
||||||
|
|
||||||
// Query based on cwd
|
// Query based on cwd
|
||||||
@ -367,6 +370,48 @@ func TestAdvancedQuery(t *testing.T) {
|
|||||||
if strings.Count(out, "\n") != 2 {
|
if strings.Count(out, "\n") != 2 {
|
||||||
t.Fatalf("hishtory query has the wrong number of lines=%d, out=%#v", strings.Count(out, "\n"), out)
|
t.Fatalf("hishtory query has the wrong number of lines=%d, out=%#v", strings.Count(out, "\n"), out)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Test filtering out a search item
|
||||||
|
out = RunInteractiveBashCommands(t, `hishtory query`)
|
||||||
|
if !strings.Contains(out, "cmd_with_diff_hostname_and_username") {
|
||||||
|
t.Fatalf("hishtory query doesn't contain expected result, out=%#v", out)
|
||||||
|
}
|
||||||
|
out = RunInteractiveBashCommands(t, `hishtory query -cmd_with_diff_hostname_and_username`)
|
||||||
|
if strings.Contains(out, "cmd_with_diff_hostname_and_username") {
|
||||||
|
t.Fatalf("hishtory query contains unexpected result, out=%#v", out)
|
||||||
|
}
|
||||||
|
out = RunInteractiveBashCommands(t, `hishtory query -echo`)
|
||||||
|
if strings.Contains(out, "echo") {
|
||||||
|
t.Fatalf("hishtory query contains unexpected result, out=%#v", out)
|
||||||
|
}
|
||||||
|
if strings.Count(out, "\n") != 7 {
|
||||||
|
t.Fatalf("hishtory query has the wrong number of lines=%d, out=%#v", strings.Count(out, "\n"), out)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test filtering out with an atom
|
||||||
|
out = RunInteractiveBashCommands(t, `hishtory query -hostname:otherhostname`)
|
||||||
|
if strings.Contains(out, "cmd_with_diff_hostname_and_username") {
|
||||||
|
t.Fatalf("hishtory query contains unexpected result, out=%#v", out)
|
||||||
|
}
|
||||||
|
out = RunInteractiveBashCommands(t, `hishtory query -user:otheruser`)
|
||||||
|
if strings.Contains(out, "cmd_with_diff_hostname_and_username") {
|
||||||
|
t.Fatalf("hishtory query contains unexpected result, out=%#v", out)
|
||||||
|
}
|
||||||
|
out = RunInteractiveBashCommands(t, `hishtory query -exit_code:0`)
|
||||||
|
if strings.Count(out, "\n") != 3 {
|
||||||
|
t.Fatalf("hishtory query has the wrong number of lines=%d, out=%#v", strings.Count(out, "\n"), out)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test filtering out a search item that also looks like it could be a search for a flag
|
||||||
|
entry = data.MakeFakeHistoryEntry("foo -echo")
|
||||||
|
manuallySubmitHistoryEntry(t, userSecret, entry)
|
||||||
|
out = RunInteractiveBashCommands(t, `hishtory query -echo`)
|
||||||
|
if strings.Contains(out, "echo") {
|
||||||
|
t.Fatalf("hishtory query contains unexpected result, out=%#v", out)
|
||||||
|
}
|
||||||
|
if strings.Count(out, "\n") != 7 {
|
||||||
|
t.Fatalf("hishtory query has the wrong number of lines=%d, out=%#v", strings.Count(out, "\n"), out)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestUpdate(t *testing.T) {
|
func TestUpdate(t *testing.T) {
|
||||||
|
@ -157,40 +157,32 @@ func Search(db *gorm.DB, query string, limit int) ([]*HistoryEntry, error) {
|
|||||||
}
|
}
|
||||||
tx := db.Where("true")
|
tx := db.Where("true")
|
||||||
for _, token := range tokens {
|
for _, token := range tokens {
|
||||||
|
if strings.HasPrefix(token, "-") {
|
||||||
if strings.Contains(token, ":") {
|
if strings.Contains(token, ":") {
|
||||||
splitToken := strings.SplitN(token, ":", 2)
|
query, val, err := parseAtomizedToken(token[1:])
|
||||||
field := splitToken[0]
|
|
||||||
val := splitToken[1]
|
|
||||||
switch field {
|
|
||||||
case "user":
|
|
||||||
tx = tx.Where("local_username = ?", val)
|
|
||||||
case "hostname":
|
|
||||||
tx = tx.Where("instr(hostname, ?) > 0", val)
|
|
||||||
case "cwd":
|
|
||||||
// TODO: Can I make this support querying via ~/ too?
|
|
||||||
tx = tx.Where("instr(current_working_directory, ?) > 0", val)
|
|
||||||
case "exit_code":
|
|
||||||
tx = tx.Where("exit_code = ?", val)
|
|
||||||
case "before":
|
|
||||||
t, err := parseTimeGenerously(val)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to parse before:%s as a timestamp: %v", val, err)
|
return nil, err
|
||||||
}
|
}
|
||||||
tx = tx.Where("CAST(strftime(\"%s\",start_time) AS INTEGER) < ?", t.Unix())
|
tx = tx.Where("NOT "+query, val)
|
||||||
case "after":
|
|
||||||
t, err := parseTimeGenerously(val)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to parse after:%s as a timestamp: %v", val, err)
|
|
||||||
}
|
|
||||||
tx = tx.Where("CAST(strftime(\"%s\",start_time) AS INTEGER) > ?", t.Unix())
|
|
||||||
default:
|
|
||||||
panic("TODO: probably return an error?")
|
|
||||||
}
|
|
||||||
} else if strings.HasPrefix(token, "-") {
|
|
||||||
panic("TODO(ddworken): Implement -foo as filtering out foo")
|
|
||||||
} else {
|
} else {
|
||||||
wildcardedToken := "%" + token + "%"
|
query, v1, v2, v3, err := parseNonAtomizedToken(token[1:])
|
||||||
tx = tx.Where("(command LIKE ? OR hostname LIKE ? OR current_working_directory LIKE ?)", wildcardedToken, wildcardedToken, wildcardedToken)
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
tx = tx.Where("NOT "+query, v1, v2, v3)
|
||||||
|
}
|
||||||
|
} else if strings.Contains(token, ":") {
|
||||||
|
query, val, err := parseAtomizedToken(token)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
tx = tx.Where(query, val)
|
||||||
|
} else {
|
||||||
|
query, v1, v2, v3, err := parseNonAtomizedToken(token)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
tx = tx.Where(query, v1, v2, v3)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
tx = tx.Order("end_time DESC")
|
tx = tx.Order("end_time DESC")
|
||||||
@ -205,6 +197,42 @@ func Search(db *gorm.DB, query string, limit int) ([]*HistoryEntry, error) {
|
|||||||
return historyEntries, nil
|
return historyEntries, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func parseNonAtomizedToken(token string) (string, interface{}, interface{}, interface{}, error) {
|
||||||
|
wildcardedToken := "%" + token + "%"
|
||||||
|
return "(command LIKE ? OR hostname LIKE ? OR current_working_directory LIKE ?)", wildcardedToken, wildcardedToken, wildcardedToken, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseAtomizedToken(token string) (string, interface{}, error) {
|
||||||
|
splitToken := strings.SplitN(token, ":", 2)
|
||||||
|
field := splitToken[0]
|
||||||
|
val := splitToken[1]
|
||||||
|
switch field {
|
||||||
|
case "user":
|
||||||
|
return "(local_username = ?)", val, nil
|
||||||
|
case "hostname":
|
||||||
|
return "(instr(hostname, ?) > 0)", val, nil
|
||||||
|
case "cwd":
|
||||||
|
// TODO: Can I make this support querying via ~/ too?
|
||||||
|
return "(instr(current_working_directory, ?) > 0)", val, nil
|
||||||
|
case "exit_code":
|
||||||
|
return "(exit_code = ?)", val, nil
|
||||||
|
case "before":
|
||||||
|
t, err := parseTimeGenerously(val)
|
||||||
|
if err != nil {
|
||||||
|
return "", nil, fmt.Errorf("failed to parse before:%s as a timestamp: %v", val, err)
|
||||||
|
}
|
||||||
|
return "(CAST(strftime(\"%s\",start_time) AS INTEGER) < ?)", t.Unix(), nil
|
||||||
|
case "after":
|
||||||
|
t, err := parseTimeGenerously(val)
|
||||||
|
if err != nil {
|
||||||
|
return "", nil, fmt.Errorf("failed to parse after:%s as a timestamp: %v", val, err)
|
||||||
|
}
|
||||||
|
return "(CAST(strftime(\"%s\",start_time) AS INTEGER) > ?)", t.Unix(), nil
|
||||||
|
default:
|
||||||
|
return "", nil, fmt.Errorf("search query contains unknown search atom %s", field)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func tokenize(query string) ([]string, error) {
|
func tokenize(query string) ([]string, error) {
|
||||||
if query == "" {
|
if query == "" {
|
||||||
return []string{}, nil
|
return []string{}, nil
|
||||||
|
@ -2,7 +2,6 @@ package lib
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
_ "embed" // for embedding config.sh
|
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
@ -14,11 +13,14 @@ import (
|
|||||||
"os/user"
|
"os/user"
|
||||||
"path"
|
"path"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"runtime"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"syscall"
|
"syscall"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
_ "embed" // for embedding config.sh
|
||||||
|
|
||||||
"gorm.io/driver/sqlite"
|
"gorm.io/driver/sqlite"
|
||||||
"gorm.io/gorm"
|
"gorm.io/gorm"
|
||||||
|
|
||||||
@ -276,7 +278,8 @@ func Disable() error {
|
|||||||
|
|
||||||
func CheckFatalError(err error) {
|
func CheckFatalError(err error) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("hishtory fatal error: %v", err)
|
_, filename, line, _ := runtime.Caller(1)
|
||||||
|
log.Fatalf("hishtory fatal error at %s:%d: %v", filename, line, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -6,6 +6,7 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"path"
|
"path"
|
||||||
|
"runtime"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
@ -98,12 +99,14 @@ func RunTestServer(t *testing.T) func() {
|
|||||||
|
|
||||||
func Check(t *testing.T, err error) {
|
func Check(t *testing.T, err error) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Unexpected error: %v", err)
|
_, filename, line, _ := runtime.Caller(1)
|
||||||
|
t.Fatalf("Unexpected error at %s:%d: %v", filename, line, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func CheckWithInfo(t *testing.T, err error, additionalInfo string) {
|
func CheckWithInfo(t *testing.T, err error, additionalInfo string) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Unexpected error: %v! Additional info: %v", err, additionalInfo)
|
_, filename, line, _ := runtime.Caller(1)
|
||||||
|
t.Fatalf("Unexpected error: %v at %s:%d! Additional info: %v", err, filename, line, additionalInfo)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user