8 Commit

Autore SHA1 Messaggio Data
776dcebb04 Log cwd 2021-04-05 11:54:09 +02:00
1809905992 Add DB migrations 2021-04-05 11:44:43 +02:00
305e4300cc Fix off-by-one in CLI 2021-04-05 10:43:54 +02:00
b02911c9b4 Add 'delete' subcommand to delete matching records from log 2021-04-05 10:29:50 +02:00
8477ba5bfe CLI: Properly parse cli flags for subcommands
Previously we parsed cli flags globally instead of per subcommand, which was weird.
2021-04-05 10:18:35 +02:00
54697be895 Search: Properly exit on error 2021-04-05 09:53:25 +02:00
da945dce2d add LICENSE 2021-03-23 14:35:50 +01:00
ee7a0868a8 Update README.md: Mention repos, minor improvements 2021-03-23 14:35:50 +01:00
5 ha cambiato i file con 181 aggiunte e 47 eliminazioni

6
LICENSE Normal file
Vedi File

@ -0,0 +1,6 @@
Copyright 2021 lawl (github.com/lawl)
Copyright 2021 Albert S. <hs9001 at quitesimple org>
Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.

Vedi File

@ -1,24 +1,44 @@
# hs9001
hs90001 (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.
## Setup
## Install
### From source
```
go build
#move hs9001 to a PATH location
# Initialize database
hs9001 init
````
```
### Debian / Ubuntu
Latest release can be installed using apt
```
curl -s https://repo.quitesimple.org/repo.quitesimple.org.asc | sudo apt-key add -
echo "deb https://repo.quitesimple.org/debian/ default main" | sudo tee /etc/apt/sources.list.d/quitesimple.list
sudo apt-get update
sudo apt-get install hs9001
```
### Alpine
```
wget https://repo.quitesimple.org/repo%40quitesimple.org-5f3d101.rsa.pub -O /etc/apk/repo@quitesimple.org-5f3d101.rsa.pub
echo "https://repo.quitesimple.org/alpine/quitesimple/" >> /etc/apk/repositories
apk update
apk add hs9001
```
### Setup / Config
Add this to .bashrc
```
if [ -n "$PS1" ] ; then
PROMPT_COMMAND='hs9001 -ret $? add "$(history 1)"'
PROMPT_COMMAND='hs9001 add -ret $? "$(history 1)"'
fi
```
By default, every system user gets his own database. You can override this by overriding the environment variable
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.
```
@ -29,7 +49,7 @@ export HS9001_DB_PATH="/home/db/history.sqlite"
### Search
```
hs9001 search "term"
hs9001 search [search terms]
```
It is recommended to create an alias for search to make life easier, e. g.:

2
go.mod
Vedi File

@ -2,4 +2,4 @@ module hs9001
go 1.16
require modernc.org/sqlite v1.10.0 // indirect
require modernc.org/sqlite v1.10.0

6
go.sum
Vedi File

@ -1,9 +1,12 @@
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/google/go-cmp v0.5.3 h1:x95R7cp+rSeeqAMI2knLtQ0DKlaBhv2NrtrOvafPHRo=
github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs=
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8=
github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY=
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/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU=
github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0 h1:OdAsTTz6OkFY5QxjkYwrChwuRruF69c169dPK26NUlk=
github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
@ -40,6 +43,7 @@ modernc.org/cc/v3 v3.31.5-0.20210308123301-7a3e9dab9009 h1:u0oCo5b9wyLr++HF3AN9J
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/go.mod h1:nQbgkn8mwzPdp4mm6BT6+p85ugQ7FrGgIcYaE7nSrpY=
modernc.org/httpfs v1.0.6 h1:AAgIpFZRXuYnkjftxTAZwMIiwEqAfk8aVB2/oA6nAeM=
modernc.org/httpfs v1.0.6/go.mod h1:7dosgurJGp0sPaRanU53W4xZYKh14wfzX420oZADeHM=
modernc.org/libc v1.7.13-0.20210308123627-12f642a52bb8/go.mod h1:U1eq8YWr/Kc1RWCMFUWEdkTg8OTcfLw2kY8EDwl039w=
modernc.org/libc v1.8.0 h1:Pp4uv9g0csgBMpGPABKtkieF6O5MGhfGo6ZiOdlYfR8=
@ -55,8 +59,10 @@ modernc.org/sqlite v1.10.0 h1:0QNqx4EzfZzNEG13sFbS/L+egh0X5WXSckHrxHkySX8=
modernc.org/sqlite v1.10.0/go.mod h1:PGzq6qlhyYjL6uVbSgS6WoF7ZopTW/sI7+7p+mb4ZVU=
modernc.org/strutil v1.1.0 h1:+1/yCzZxY2pZwwrsbH+4T7BQMoLQ9QiBshRC9eicYsc=
modernc.org/strutil v1.1.0/go.mod h1:lstksw84oURvj9y3tn8lGvRxyRC1S2+g5uuIzNfIOBs=
modernc.org/tcl v1.5.0 h1:euZSUNfE0Fd4W8VqXI1Ly1v7fqDJoBuAV88Ea+SnaSs=
modernc.org/tcl v1.5.0/go.mod h1:gb57hj4pO8fRrK54zveIfFXBaMHK3SKJNWcmRw1cRzc=
modernc.org/token v1.0.0 h1:a0jaWiNMDhDUtqOj09wvjWWAqd3q7WpBulmL9H2egsk=
modernc.org/token v1.0.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM=
modernc.org/z v1.0.1-0.20210308123920-1f282aa71362/go.mod h1:8/SRk5C/HgiQWCgXdfpb+1RvhORdkz5sw72d3jjtyqA=
modernc.org/z v1.0.1 h1:WyIDpEpAIx4Hel6q/Pcgj/VhaQV5XPJ2I6ryIYbjnpc=
modernc.org/z v1.0.1/go.mod h1:8/SRk5C/HgiQWCgXdfpb+1RvhORdkz5sw72d3jjtyqA=

178
main.go
Vedi File

@ -28,6 +28,7 @@ func createConnection() *sql.DB {
if err != nil {
log.Panic(err)
}
return db
}
@ -41,6 +42,55 @@ func initDatabase(conn *sql.DB) {
}
}
func migrateDatabase(conn *sql.DB, currentVersion int) {
migrations := []string{
"ALTER TABLE history add column workdir varchar(4096)",
}
if !(len(migrations) > currentVersion) {
return
}
_, err := conn.Exec("BEGIN;")
if err != nil {
log.Panic(err)
}
for _, m := range migrations[currentVersion:] {
_, err := conn.Exec(m)
if err != nil {
log.Panic(err)
}
}
setDBVersion(conn, len(migrations))
_, err = conn.Exec("END;")
if err != nil {
log.Panic(err)
}
}
func fetchDBVersion(conn *sql.DB) int {
rows, err := conn.Query("PRAGMA user_version;")
if err != nil {
log.Panic(err)
}
defer rows.Close()
rows.Next()
var res int
rows.Scan(&res)
return res
}
func setDBVersion(conn *sql.DB, ver int) {
_, err := conn.Exec(fmt.Sprintf("PRAGMA user_version=%d", ver))
if err != nil {
log.Panic(err)
}
}
func importFromStdin(conn *sql.DB) {
scanner := bufio.NewScanner(os.Stdin)
@ -50,7 +100,7 @@ func importFromStdin(conn *sql.DB) {
}
for scanner.Scan() {
add(conn, scanner.Text())
add(conn, scanner.Text(), "")
}
_, err = conn.Exec("END;")
@ -67,6 +117,7 @@ func search(conn *sql.DB, q string) {
log.Panic(err)
}
defer rows.Close()
for rows.Next() {
var command string
err = rows.Scan(&command)
@ -77,19 +128,34 @@ func search(conn *sql.DB, q string) {
}
}
func add(conn *sql.DB, cmd string) {
func delete(conn *sql.DB, q string) {
queryStmt := "DELETE FROM history WHERE command LIKE ?"
_, err := conn.Exec(queryStmt, "%"+q+"%")
if err != nil {
log.Panic(err)
}
_, err = conn.Exec("VACUUM")
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) VALUES (?, ?, ?)")
stmt, err := conn.Prepare("INSERT INTO history (user, command, hostname, workdir) VALUES (?, ?, ?, ?)")
if err != nil {
log.Panic(err)
}
_, err = stmt.Exec(user, cmd, hostname)
_, err = stmt.Exec(user, cmd, hostname, cwd)
if err != nil {
log.Panic(err)
}
@ -120,57 +186,93 @@ func exists(path string) (bool, error) {
}
func printUsage() {
fmt.Fprintf(os.Stderr, "Usage: ./hs9001 <add/search/init/import>\n")
fmt.Fprintf(os.Stderr, "Usage: ./hs9001 <add/search/import>\n")
}
func main() {
var ret int
flag.IntVar(&ret, "ret", 0, "Return value of the command to add")
flag.Parse()
args := flag.Args()
argslen := len(args)
addCmd := flag.NewFlagSet("add", flag.ExitOnError)
searchCmd := flag.NewFlagSet("search", flag.ExitOnError)
deleteCmd := flag.NewFlagSet("delete", flag.ExitOnError)
if argslen < 1 {
if len(os.Args) < 2 {
printUsage()
return
}
cmd := args[0]
cmd := os.Args[1]
globalargs := os.Args[2:]
conn := createConnection()
var conn *sql.DB
ok, _ := exists(databaseLocation())
if cmd == "add" {
if ret == 23 { // 23 is our secret do not log status code
return
}
if argslen < 2 {
fmt.Fprint(os.Stderr, "Error: You need to provide the command to be added")
}
historycmd := args[1]
var rgx = regexp.MustCompile("\\s+\\d+\\s+(.*)")
rs := rgx.FindStringSubmatch(historycmd)
if len(rs) == 2 {
add(conn, rs[1])
}
} else if cmd == "search" {
if argslen < 2 {
fmt.Fprint(os.Stderr, "Please provide the search query\n")
}
q := strings.Join(args[1:], " ")
search(conn, q)
os.Exit(23)
} else if cmd == "init" {
if !ok {
err := os.MkdirAll(filepath.Dir(databaseLocation()), 0755)
if err != nil {
log.Panic(err)
}
conn = createConnection()
initDatabase(conn)
} else if cmd == "import" {
importFromStdin(conn)
} else {
fmt.Fprint(os.Stderr, "Error: Unknown command supplied\n\n")
conn = createConnection()
}
migrateDatabase(conn, fetchDBVersion(conn))
switch cmd {
case "add":
var ret int
addCmd.IntVar(&ret, "ret", 0, "Return value of the command to add")
addCmd.Parse(globalargs)
args := addCmd.Args()
if ret == 23 { // 23 is our secret do not log status code
return
}
if len(args) < 1 {
fmt.Fprint(os.Stderr, "Error: You need to provide the command to be added")
}
historycmd := args[0]
var rgx = regexp.MustCompile("\\s+\\d+\\s+(.*)")
rs := rgx.FindStringSubmatch(historycmd)
if len(rs) == 2 {
wd, err := os.Getwd()
if err != nil {
log.Panic(err)
}
add(conn, rs[1], wd)
}
case "search":
searchCmd.Parse(globalargs)
args := searchCmd.Args()
if len(args) < 1 {
fmt.Fprint(os.Stderr, "Please provide the search query\n")
os.Exit(1)
}
q := strings.Join(args, " ")
search(conn, q)
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":
importFromStdin(conn)
default:
fmt.Fprintf(os.Stderr, "Error: Unknown subcommand '%s' supplied\n\n", cmd)
printUsage()
return
}
}