shell-history/internal/search/search.go

99 lines
2.2 KiB
Go

package search
import (
"database/sql"
"flag"
"fmt"
"strings"
"time"
)
const (
dim = "\033[2m"
reset = "\033[0m"
)
func Run(d *sql.DB, args []string) error {
fs := flag.NewFlagSet("search", flag.ExitOnError)
host := fs.String("host", "", "filter by hostname")
dir := fs.String("dir", "", "filter by working directory")
after := fs.String("after", "", "filter entries after timestamp")
before := fs.String("before", "", "filter entries before timestamp")
limit := fs.Int("limit", 0, "max results (0 = unlimited)")
reverse := fs.Bool("reverse", false, "newest first")
compact := fs.Bool("compact", false, "compact output with color")
fs.Parse(args)
query := strings.Join(fs.Args(), " ")
var where []string
var params []any
if query != "" {
where = append(where, "command LIKE ?")
params = append(params, "%"+query+"%")
}
if *host != "" {
where = append(where, "hostname = ?")
params = append(params, *host)
}
if *dir != "" {
where = append(where, "working_dir = ?")
params = append(params, *dir)
}
if *after != "" {
where = append(where, "timestamp > ?")
params = append(params, *after)
}
if *before != "" {
where = append(where, "timestamp < ?")
params = append(params, *before)
}
inner := "SELECT timestamp, hostname, working_dir, command FROM history"
if len(where) > 0 {
inner += " WHERE " + strings.Join(where, " AND ")
}
inner += " ORDER BY timestamp DESC"
if *limit > 0 {
inner += " LIMIT ?"
params = append(params, *limit)
}
outerOrder := "ASC"
if *reverse {
outerOrder = "DESC"
}
q := "SELECT * FROM (" + inner + ") ORDER BY timestamp " + outerOrder
rows, err := d.Query(q, params...)
if err != nil {
return err
}
defer rows.Close()
for rows.Next() {
var ts, h, wd, cmd string
if err := rows.Scan(&ts, &h, &wd, &cmd); err != nil {
return err
}
if t, err := time.Parse(time.RFC3339, ts); err == nil {
ts = t.Format("2006-01-02 15:04")
}
if *compact {
fmt.Printf("%s%s %s %s%s %s\n", dim, ts, h, shortenDir(wd), reset, cmd)
} else {
fmt.Printf("%s %s %s %s\n", ts, h, wd, cmd)
}
}
return rows.Err()
}
func shortenDir(dir string) string {
parts := strings.Split(dir, "/")
if len(parts) <= 3 {
return dir
}
return "../" + parts[len(parts)-2] + "/" + parts[len(parts)-1]
}