1
0
spogulis no https://github.com/quitesimpleorg/hs9001.git synced 2025-07-17 14:34:56 +02:00

8 Revīzijas

3 mainīti faili ar 162 papildinājumiem un 59 dzēšanām

5
go.mod
Parādīt failu

@ -2,4 +2,7 @@ module hs9001
go 1.16 go 1.16
require modernc.org/sqlite v1.10.0 require (
github.com/tj/go-naturaldate v1.3.0
modernc.org/sqlite v1.10.0
)

15
go.sum
Parādīt failu

@ -1,3 +1,5 @@
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo= github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo=
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
github.com/google/go-cmp v0.5.3 h1:x95R7cp+rSeeqAMI2knLtQ0DKlaBhv2NrtrOvafPHRo= github.com/google/go-cmp v0.5.3 h1:x95R7cp+rSeeqAMI2knLtQ0DKlaBhv2NrtrOvafPHRo=
@ -8,8 +10,17 @@ github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHX
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
github.com/mattn/go-sqlite3 v1.14.6 h1:dNPt6NO46WmLVt2DLNpwczCmdV5boIZ6g/tlDrlRUbg= github.com/mattn/go-sqlite3 v1.14.6 h1:dNPt6NO46WmLVt2DLNpwczCmdV5boIZ6g/tlDrlRUbg=
github.com/mattn/go-sqlite3 v1.14.6/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU= github.com/mattn/go-sqlite3 v1.14.6/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0 h1:OdAsTTz6OkFY5QxjkYwrChwuRruF69c169dPK26NUlk= github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0 h1:OdAsTTz6OkFY5QxjkYwrChwuRruF69c169dPK26NUlk=
github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/tj/assert v0.0.0-20190920132354-ee03d75cd160 h1:NSWpaDaurcAJY7PkL8Xt0PhZE7qpvbZl5ljd8r6U0bI=
github.com/tj/assert v0.0.0-20190920132354-ee03d75cd160/go.mod h1:mZ9/Rh9oLWpLLDRpvE+3b7gP/C2YyLFYxNmcLnPTMe0=
github.com/tj/go-naturaldate v1.3.0 h1:OgJIPkR/Jk4bFMBLbxZ8w+QUxwjqSvzd9x+yXocY4RI=
github.com/tj/go-naturaldate v1.3.0/go.mod h1:rpUbjivDKiS1BlfMGc2qUKNZ/yxgthOfmytQs8d8hKk=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
@ -39,6 +50,10 @@ golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8T
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
modernc.org/cc/v3 v3.31.5-0.20210308123301-7a3e9dab9009 h1:u0oCo5b9wyLr++HF3AN9JicGhkUxJhMz51+8TIZH9N0= modernc.org/cc/v3 v3.31.5-0.20210308123301-7a3e9dab9009 h1:u0oCo5b9wyLr++HF3AN9JicGhkUxJhMz51+8TIZH9N0=
modernc.org/cc/v3 v3.31.5-0.20210308123301-7a3e9dab9009/go.mod h1:0R6jl1aZlIl2avnYfbfHBS1QB6/f+16mihBObaBC878= modernc.org/cc/v3 v3.31.5-0.20210308123301-7a3e9dab9009/go.mod h1:0R6jl1aZlIl2avnYfbfHBS1QB6/f+16mihBObaBC878=
modernc.org/ccgo/v3 v3.9.0 h1:JbcEIqjw4Agf+0g3Tc85YvfYqkkFOv6xBwS4zkfqSoA= modernc.org/ccgo/v3 v3.9.0 h1:JbcEIqjw4Agf+0g3Tc85YvfYqkkFOv6xBwS4zkfqSoA=

201
main.go
Parādīt failu

@ -2,6 +2,7 @@ package main
import ( import (
"bufio" "bufio"
"container/list"
"database/sql" "database/sql"
"flag" "flag"
"fmt" "fmt"
@ -10,10 +11,22 @@ import (
"path/filepath" "path/filepath"
"regexp" "regexp"
"strings" "strings"
"time"
"github.com/tj/go-naturaldate"
_ "modernc.org/sqlite" _ "modernc.org/sqlite"
) )
type HistoryEntry struct {
id uint32
cmd string
cwd string
hostname string
user string
retval int
timestamp time.Time
}
func databaseLocation() string { func databaseLocation() string {
envOverride := os.Getenv("HS9001_DB_PATH") envOverride := os.Getenv("HS9001_DB_PATH")
if envOverride != "" { if envOverride != "" {
@ -45,7 +58,8 @@ func initDatabase(conn *sql.DB) {
func migrateDatabase(conn *sql.DB, currentVersion int) { func migrateDatabase(conn *sql.DB, currentVersion int) {
migrations := []string{ migrations := []string{
"ALTER TABLE history add column workdir varchar(4096)", "ALTER TABLE history ADD COLUMN workdir varchar(4096) DEFAULT ''",
"ALTER TABLE history ADD COLUMN retval integer DEFAULT -9001",
} }
if !(len(migrations) > currentVersion) { if !(len(migrations) > currentVersion) {
@ -91,6 +105,25 @@ func setDBVersion(conn *sql.DB, ver int) {
} }
} }
func NewHistoryEntry(cmd string, retval int) HistoryEntry {
wd, err := os.Getwd()
if err != nil {
log.Panic(err)
}
hostname, err := os.Hostname()
if err != nil {
log.Panic(err)
}
return HistoryEntry{
user: os.Getenv("USER"),
hostname: hostname,
cmd: cmd,
cwd: wd,
timestamp: time.Now(),
retval: retval,
}
}
func importFromStdin(conn *sql.DB) { func importFromStdin(conn *sql.DB) {
scanner := bufio.NewScanner(os.Stdin) scanner := bufio.NewScanner(os.Stdin)
@ -100,7 +133,10 @@ func importFromStdin(conn *sql.DB) {
} }
for scanner.Scan() { for scanner.Scan() {
add(conn, scanner.Text(), "") entry := NewHistoryEntry(scanner.Text(), -9001)
entry.cwd = ""
entry.timestamp = time.Unix(0, 0)
add(conn, entry)
} }
_, err = conn.Exec("END;") _, err = conn.Exec("END;")
@ -109,53 +145,62 @@ func importFromStdin(conn *sql.DB) {
} }
} }
func search(conn *sql.DB, q string) { func search(conn *sql.DB, q string, workdir string, beginTime time.Time, endTime time.Time, retval int) list.List {
queryStmt := "SELECT command FROM history WHERE command LIKE ? ORDER BY timestamp ASC" var sb strings.Builder
sb.WriteString("SELECT id, command, workdir, user, hostname, retval ")
sb.WriteString("FROM history ")
sb.WriteString("WHERE timestamp BETWEEN datetime(?, 'unixepoch') ")
sb.WriteString("AND datetime(?, 'unixepoch') ")
sb.WriteString("AND command LIKE ? ")
sb.WriteString("AND workdir LIKE ? ")
if retval != -9001 {
sb.WriteString("AND retval = ? ")
}
sb.WriteString("ORDER BY timestamp ASC ")
rows, err := conn.Query(queryStmt, "%"+q+"%") queryStmt := sb.String()
args := make([]interface{}, 0)
args = append(args, beginTime.Unix())
args = append(args, endTime.Unix())
args = append(args, q)
args = append(args, workdir)
if retval != -9001 {
args = append(args, retval)
}
rows, err := conn.Query(queryStmt, args...)
if err != nil { if err != nil {
log.Panic(err) log.Panic(err)
} }
var result list.List
defer rows.Close() defer rows.Close()
for rows.Next() { for rows.Next() {
var command string var entry HistoryEntry
err = rows.Scan(&command) err = rows.Scan(&entry.id, &entry.cmd, &entry.cwd, &entry.user, &entry.hostname, &entry.retval)
if err != nil { if err != nil {
log.Panic(err) log.Panic(err)
} }
fmt.Printf("%s\n", command) result.PushBack(&entry)
}
return result
}
func delete(conn *sql.DB, entryId uint32) {
queryStmt := "DELETE FROM history WHERE id = ?"
_, err := conn.Exec(queryStmt, entryId)
if err != nil {
log.Panic(err)
} }
} }
func delete(conn *sql.DB, q string) { func add(conn *sql.DB, entry HistoryEntry) {
queryStmt := "DELETE FROM history WHERE command LIKE ?" stmt, err := conn.Prepare("INSERT INTO history (user, command, hostname, workdir, timestamp, retval) VALUES (?, ?, ?, ?, datetime(?, 'unixepoch'),?)")
_, err := conn.Exec(queryStmt, "%"+q+"%")
if err != nil { if err != nil {
log.Panic(err) log.Panic(err)
} }
_, err = conn.Exec("VACUUM") _, err = stmt.Exec(entry.user, entry.cmd, entry.hostname, entry.cwd, entry.timestamp.Unix(), entry.retval)
if err != nil {
log.Panic(err)
}
}
func add(conn *sql.DB, cmd string, cwd string) {
user := os.Getenv("USER")
hostname, err := os.Hostname()
if err != nil {
log.Panic(err)
}
stmt, err := conn.Prepare("INSERT INTO history (user, command, hostname, workdir) VALUES (?, ?, ?, ?)")
if err != nil {
log.Panic(err)
}
_, err = stmt.Exec(user, cmd, hostname, cwd)
if err != nil { if err != nil {
log.Panic(err) log.Panic(err)
} }
@ -192,7 +237,6 @@ func printUsage() {
func main() { func main() {
addCmd := flag.NewFlagSet("add", flag.ExitOnError) addCmd := flag.NewFlagSet("add", flag.ExitOnError)
searchCmd := flag.NewFlagSet("search", flag.ExitOnError) searchCmd := flag.NewFlagSet("search", flag.ExitOnError)
deleteCmd := flag.NewFlagSet("delete", flag.ExitOnError)
if len(os.Args) < 2 { if len(os.Args) < 2 {
printUsage() printUsage()
@ -236,37 +280,78 @@ func main() {
var rgx = regexp.MustCompile("\\s+\\d+\\s+(.*)") var rgx = regexp.MustCompile("\\s+\\d+\\s+(.*)")
rs := rgx.FindStringSubmatch(historycmd) rs := rgx.FindStringSubmatch(historycmd)
if len(rs) == 2 { if len(rs) == 2 {
wd, err := os.Getwd() add(conn, NewHistoryEntry(rs[1], ret))
}
case "search":
fallthrough
case "delete":
var workDir string
var beginTime string
var endTime string
var distinct bool = true
var retVal int
searchCmd.StringVar(&workDir, "workdir", "%", "Search only within this workdir")
searchCmd.StringVar(&beginTime, "begin", "50 years ago", "Start searching from this timeframe")
searchCmd.StringVar(&endTime, "end", "now", "End searching from this timeframe")
searchCmd.BoolVar(&distinct, "distinct", true, "Remove consecutive duplicate commands from output")
searchCmd.IntVar(&retVal, "ret", -9001, "Only query commands that returned with this exit code. -9001=all (default)")
searchCmd.Parse(globalargs)
args := searchCmd.Args()
beginTimestamp, err := naturaldate.Parse(beginTime, time.Now())
if err != nil {
fmt.Fprintf(os.Stderr, "Failed to convert time string: %s\n", err.Error())
}
endTimeStamp, err := naturaldate.Parse(endTime, time.Now())
if err != nil {
fmt.Fprintf(os.Stderr, "Failed to convert time string: %s\n", err.Error())
}
q := strings.Join(args, " ")
results := search(conn, "%"+q+"%", workDir, beginTimestamp, endTimeStamp, retVal)
previousCmd := ""
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)
}
previousCmd = entry.cmd
}
if cmd == "delete" {
_, err := conn.Exec("BEGIN;")
if err != nil { if err != nil {
log.Panic(err) log.Panic(err)
} }
add(conn, rs[1], wd)
}
case "search":
searchCmd.Parse(globalargs)
args := searchCmd.Args()
if len(args) < 1 { for e := results.Front(); e != nil; e = e.Next() {
fmt.Fprint(os.Stderr, "Please provide the search query\n") entry, ok := e.Value.(*HistoryEntry)
os.Exit(1) if !ok {
} log.Panic("Failed to retrieve entries")
}
delete(conn, entry.id)
}
q := strings.Join(args, " ") _, err = conn.Exec("END;")
search(conn, q) if err != nil {
log.Panic(err)
}
_, err = conn.Exec("VACUUM")
if err != nil {
log.Panic(err)
}
}
os.Exit(23) os.Exit(23)
case "delete":
deleteCmd.Parse(globalargs)
args := deleteCmd.Args()
if len(args) < 1 {
fmt.Fprint(os.Stderr, "Error: You need to provide a search query for records to delete")
}
q := strings.Join(args, " ")
delete(conn, q)
//we do not want to leak what we just deleted :^)
os.Exit(23)
case "import": case "import":
importFromStdin(conn) importFromStdin(conn)
default: default: