working tracking for start time

This commit is contained in:
David Dworken 2022-01-09 16:39:13 -08:00
parent 11962b26c9
commit 3cc9118a69
9 changed files with 83 additions and 32 deletions

View File

@ -1,5 +0,0 @@
Installation:
```
export PROMPT_COMMAND='~/.hishtory-client saveHistoryEntry $? "`history 1`"'
```

View File

@ -22,7 +22,6 @@ func main() {
shared.CheckFatalError(shared.Setup(os.Args)) shared.CheckFatalError(shared.Setup(os.Args))
case "install": case "install":
shared.CheckFatalError(shared.Install()) shared.CheckFatalError(shared.Install())
shared.CheckFatalError(shared.Setup(os.Args))
case "enable": case "enable":
shared.CheckFatalError(shared.Enable()) shared.CheckFatalError(shared.Enable())
case "disable": case "disable":

View File

@ -25,7 +25,6 @@ func main() {
case "init": case "init":
shared.CheckFatalError(shared.Setup(os.Args)) shared.CheckFatalError(shared.Setup(os.Args))
case "install": case "install":
shared.CheckFatalError(shared.Setup(os.Args))
shared.CheckFatalError(shared.Install()) shared.CheckFatalError(shared.Install())
case "enable": case "enable":
shared.CheckFatalError(shared.Enable()) shared.CheckFatalError(shared.Enable())

View File

@ -17,7 +17,7 @@ func TestSubmitThenQuery(t *testing.T) {
shared.Check(t, shared.Setup([]string{})) shared.Check(t, shared.Setup([]string{}))
// Submit an entry // Submit an entry
entry, err := shared.BuildHistoryEntry([]string{"unused", "saveHistoryEntry", "120", " 123 ls / "}) entry, err := shared.BuildHistoryEntry([]string{"unused", "saveHistoryEntry", "120", " 123 ls / ", "1641774958326745663"})
shared.Check(t, err) shared.Check(t, err)
reqBody, err := json.Marshal(entry) reqBody, err := json.Marshal(entry)
shared.Check(t, err) shared.Check(t, err)
@ -63,7 +63,7 @@ func TestNoUserSecretGivesNoResults(t *testing.T) {
shared.Check(t, shared.Setup([]string{})) shared.Check(t, shared.Setup([]string{}))
// Submit an entry // Submit an entry
entry, err := shared.BuildHistoryEntry([]string{"unused", "saveHistoryEntry", "120", " 123 ls / "}) entry, err := shared.BuildHistoryEntry([]string{"unused", "saveHistoryEntry", "120", " 123 ls / ", "1641774958326745663"})
shared.Check(t, err) shared.Check(t, err)
reqBody, err := json.Marshal(entry) reqBody, err := json.Marshal(entry)
shared.Check(t, err) shared.Check(t, err)
@ -91,14 +91,14 @@ func TestSearchQuery(t *testing.T) {
shared.Check(t, shared.Setup([]string{})) shared.Check(t, shared.Setup([]string{}))
// Submit an entry that we'll match // Submit an entry that we'll match
entry, err := shared.BuildHistoryEntry([]string{"unused", "saveHistoryEntry", "120", " 123 ls /bar "}) entry, err := shared.BuildHistoryEntry([]string{"unused", "saveHistoryEntry", "120", " 123 ls /bar ", "1641774958326745663"})
shared.Check(t, err) shared.Check(t, err)
reqBody, err := json.Marshal(entry) reqBody, err := json.Marshal(entry)
shared.Check(t, err) shared.Check(t, err)
submitReq := httptest.NewRequest(http.MethodPost, "/", bytes.NewReader(reqBody)) submitReq := httptest.NewRequest(http.MethodPost, "/", bytes.NewReader(reqBody))
apiSubmitHandler(nil, submitReq) apiSubmitHandler(nil, submitReq)
// Submit an entry that we won't match // Submit an entry that we won't match
entry, err = shared.BuildHistoryEntry([]string{"unused", "saveHistoryEntry", "120", " 123 ls /foo "}) entry, err = shared.BuildHistoryEntry([]string{"unused", "saveHistoryEntry", "120", " 123 ls /foo ", "1641774958326745663"})
shared.Check(t, err) shared.Check(t, err)
reqBody, err = json.Marshal(entry) reqBody, err = json.Marshal(entry)
shared.Check(t, err) shared.Check(t, err)

View File

@ -1,6 +1,8 @@
package shared package shared
import ( import (
_ "embed"
"encoding/json" "encoding/json"
"fmt" "fmt"
"io" "io"
@ -24,6 +26,11 @@ const (
CONFIG_PATH = ".hishtory.config" CONFIG_PATH = ".hishtory.config"
) )
var (
//go:embed config.sh
CONFIG_SH_CONTENTS string
)
func getCwd() (string, error) { func getCwd() (string, error) {
cwd, err := os.Getwd() cwd, err := os.Getwd()
if err != nil { if err != nil {
@ -63,7 +70,12 @@ func BuildHistoryEntry(args []string) (*HistoryEntry, error) {
} }
entry.CurrentWorkingDirectory = cwd entry.CurrentWorkingDirectory = cwd
// TODO(ddworken): start time // start time
nanos, err := strconv.ParseInt(args[4], 10, 64)
if err != nil {
return nil, fmt.Errorf("failed to parse start time %s as int: %v", args[4], err)
}
entry.StartTime = time.Unix(0, nanos)
// end time // end time
entry.EndTime = time.Now() entry.EndTime = time.Now()
@ -119,17 +131,19 @@ func Setup(args []string) error {
func DisplayResults(results []*HistoryEntry, displayHostname bool) { func DisplayResults(results []*HistoryEntry, displayHostname bool) {
headerFmt := color.New(color.FgGreen, color.Underline).SprintfFunc() headerFmt := color.New(color.FgGreen, color.Underline).SprintfFunc()
tbl := table.New("CWD", "Timestamp", "Exit Code", "Command") tbl := table.New("CWD", "Timestamp", "Runtime", "Exit Code", "Command")
if displayHostname { if displayHostname {
tbl = table.New("Hostname", "CWD", "Timestamp", "Exit Code", "Command") tbl = table.New("Hostname", "CWD", "Timestamp", "Runtime", "Exit Code", "Command")
} }
tbl.WithHeaderFormatter(headerFmt) tbl.WithHeaderFormatter(headerFmt)
for _, result := range results { for _, result := range results {
timestamp := result.StartTime.Format("Jan 2 2006 15:04:05 MST")
duration := result.EndTime.Sub(result.StartTime).Round(time.Millisecond).String()
if displayHostname { if displayHostname {
tbl.AddRow(result.Hostname, result.CurrentWorkingDirectory, result.EndTime.Format("Jan 2 2006 15:04:05 MST"), result.ExitCode, result.Command) tbl.AddRow(result.Hostname, result.CurrentWorkingDirectory, timestamp, duration, result.ExitCode, result.Command)
} else { } else {
tbl.AddRow(result.CurrentWorkingDirectory, result.EndTime.Format("Jan 2 2006 15:04:05 MST"), result.ExitCode, result.Command) tbl.AddRow(result.CurrentWorkingDirectory, timestamp, duration, result.ExitCode, result.Command)
} }
} }
@ -206,10 +220,6 @@ func CheckFatalError(err error) {
} }
} }
const (
PROMPT_COMMAND = "export PROMPT_COMMAND='%s saveHistoryEntry $? \"`history 1`\"'"
)
func Install() error { func Install() error {
homedir, err := os.UserHomeDir() homedir, err := os.UserHomeDir()
if err != nil { if err != nil {
@ -219,24 +229,41 @@ func Install() error {
if err != nil { if err != nil {
return err return err
} }
return configureBashrc(homedir, path) err = configureBashrc(homedir, path)
if err != nil {
return err
}
_, err = GetConfig()
if err != nil {
// No config, so set up a new installation
return Setup(os.Args)
}
return nil
} }
func configureBashrc(homedir, binaryPath string) error { func configureBashrc(homedir, binaryPath string) error {
promptCommand := fmt.Sprintf(PROMPT_COMMAND, binaryPath) // Check if we need to configure the bashrc
bashrc, err := ioutil.ReadFile(path.Join(homedir, ".bashrc")) bashrc, err := ioutil.ReadFile(path.Join(homedir, ".bashrc"))
if err != nil { if err != nil {
return fmt.Errorf("failed to read bashrc: %v", err) return fmt.Errorf("failed to read bashrc: %v", err)
} }
if strings.Contains(string(bashrc), promptCommand) { if strings.Contains(string(bashrc), "# Hishtory Config:") {
return nil return nil
} }
// Create the file we're going to source in our bashrc
bashConfigPath := path.Join(filepath.Dir(binaryPath), "config.sh")
err = ioutil.WriteFile(bashConfigPath, []byte(CONFIG_SH_CONTENTS), 0644)
if err != nil {
return fmt.Errorf("failed to write config.sh file: %v", err)
}
// Add to bashrc
f, err := os.OpenFile(path.Join(homedir, ".bashrc"), os.O_APPEND|os.O_WRONLY, 0644) f, err := os.OpenFile(path.Join(homedir, ".bashrc"), os.O_APPEND|os.O_WRONLY, 0644)
if err != nil { if err != nil {
return fmt.Errorf("failed to append to bashrc: %v", err) return fmt.Errorf("failed to append to bashrc: %v", err)
} }
defer f.Close() defer f.Close()
_, err = f.WriteString(string(bashrc) + "\n# Hishtory Config:\nexport PATH=\"$PATH:" + filepath.Dir(binaryPath) + "\"\n" + promptCommand + "\n") _, err = f.WriteString("\n# Hishtory Config:\nexport PATH=\"$PATH:" + filepath.Dir(binaryPath) + "\"\nsource " + bashConfigPath + "\n")
if err != nil { if err != nil {
return fmt.Errorf("failed to append to bashrc: %v", err) return fmt.Errorf("failed to append to bashrc: %v", err)
} }

View File

@ -5,6 +5,7 @@ import (
"path" "path"
"strings" "strings"
"testing" "testing"
"time"
) )
func TestSetup(t *testing.T) { func TestSetup(t *testing.T) {
@ -28,7 +29,7 @@ func TestSetup(t *testing.T) {
func TestBuildHistoryEntry(t *testing.T) { func TestBuildHistoryEntry(t *testing.T) {
defer BackupAndRestore(t) defer BackupAndRestore(t)
Check(t, Setup([]string{})) Check(t, Setup([]string{}))
entry, err := BuildHistoryEntry([]string{"unused", "saveHistoryEntry", "120", " 123 ls / "}) entry, err := BuildHistoryEntry([]string{"unused", "saveHistoryEntry", "120", " 123 ls / ", "1641774958326745663"})
Check(t, err) Check(t, err)
if entry.UserSecret == "" || len(entry.UserSecret) < 10 || strings.TrimSpace(entry.UserSecret) != entry.UserSecret { if entry.UserSecret == "" || len(entry.UserSecret) < 10 || strings.TrimSpace(entry.UserSecret) != entry.UserSecret {
t.Fatalf("history entry has unexpected user secret: %v", entry.UserSecret) t.Fatalf("history entry has unexpected user secret: %v", entry.UserSecret)
@ -45,6 +46,9 @@ func TestBuildHistoryEntry(t *testing.T) {
if entry.Command != "ls /" { if entry.Command != "ls /" {
t.Fatalf("history entry has unexpected command: %v", entry.Command) t.Fatalf("history entry has unexpected command: %v", entry.Command)
} }
if entry.StartTime.Format(time.RFC3339) != "2022-01-09T16:35:58-08:00" {
t.Fatalf("history entry has incorrect start time: %v", entry.StartTime.Format(time.RFC3339))
}
} }
func TestGetUserSecret(t *testing.T) { func TestGetUserSecret(t *testing.T) {

27
shared/config.sh Normal file
View File

@ -0,0 +1,27 @@
# This script should be sourced inside of .bashrc to integrate bash with hishtory
# Implementation of PreCommand and PostCommand based on https://jichu4n.com/posts/debug-trap-and-prompt_command-in-bash/
function PreCommand() {
if [ -z "$HISHTORY_AT_PROMPT" ]; then
return
fi
unset HISHTORY_AT_PROMPT
# Run before every command
HISHTORY_START_TIME=`date +%s%N`
}
trap "PreCommand" DEBUG
HISHTORY_FIRST_PROMPT=1
function PostCommand() {
HISHTORY_AT_PROMPT=1
if [ -n "$HISHTORY_FIRST_PROMPT" ]; then
unset HISHTORY_FIRST_PROMPT
return
fi
# Run after every prompt
hishtory saveHistoryEntry $? "`history 1`" $HISHTORY_START_TIME
}
PROMPT_COMMAND="PostCommand"

View File

@ -2,7 +2,6 @@ package shared
import ( import (
"fmt" "fmt"
"log"
"os" "os"
"path" "path"
"strings" "strings"
@ -28,7 +27,6 @@ const (
) )
func Persist(entry HistoryEntry) error { func Persist(entry HistoryEntry) error {
log.Printf("Saving %#v to the DB\n", entry)
db, err := OpenDB() db, err := OpenDB()
if err != nil { if err != nil {
return err return err
@ -46,7 +44,7 @@ func OpenDB() (*gorm.DB, error) {
} }
db, err := gorm.Open(sqlite.Open(path.Join(homedir, DB_PATH)), &gorm.Config{}) db, err := gorm.Open(sqlite.Open(path.Join(homedir, DB_PATH)), &gorm.Config{})
if err != nil { if err != nil {
panic("failed to connect database") return nil, fmt.Errorf("failed to connect to the DB: %v", err)
} }
db.AutoMigrate(&HistoryEntry{}) db.AutoMigrate(&HistoryEntry{})
return db, nil return db, nil
@ -65,6 +63,8 @@ func Search(db *gorm.DB, userSecret, query string, limit int) ([]*HistoryEntry,
val := splitToken[1] val := splitToken[1]
// tx = tx.Where() // tx = tx.Where()
panic("TODO(ddworken): Use " + field + val) panic("TODO(ddworken): Use " + field + val)
} else if strings.HasPrefix(token, "-") {
panic("TODO(ddworken): Implement -foo as filtering out foo")
} else { } else {
wildcardedToken := "%" + token + "%" wildcardedToken := "%" + token + "%"
tx = tx.Where("(command LIKE ? OR hostname LIKE ? OR current_working_directory LIKE ?)", wildcardedToken, wildcardedToken, wildcardedToken) tx = tx.Where("(command LIKE ? OR hostname LIKE ? OR current_working_directory LIKE ?)", wildcardedToken, wildcardedToken, wildcardedToken)

View File

@ -7,7 +7,7 @@ import (
func TestPersist(t *testing.T) { func TestPersist(t *testing.T) {
defer BackupAndRestore(t) defer BackupAndRestore(t)
Check(t, Setup([]string{})) Check(t, Setup([]string{}))
entry, err := BuildHistoryEntry([]string{"unused", "saveHistoryEntry", "120", " 123 ls / "}) entry, err := BuildHistoryEntry([]string{"unused", "saveHistoryEntry", "120", " 123 ls / ", "1641774958326745663"})
Check(t, err) Check(t, err)
Check(t, Persist(*entry)) Check(t, Persist(*entry))
@ -30,13 +30,13 @@ func TestSearch(t *testing.T) {
Check(t, Setup([]string{})) Check(t, Setup([]string{}))
// Insert data // Insert data
entry1, err := BuildHistoryEntry([]string{"unused", "saveHistoryEntry", "120", " 123 ls / "}) entry1, err := BuildHistoryEntry([]string{"unused", "saveHistoryEntry", "120", " 123 ls / ", "1641774958326745663"})
Check(t, err) Check(t, err)
Check(t, Persist(*entry1)) Check(t, Persist(*entry1))
entry2, err := BuildHistoryEntry([]string{"unused", "saveHistoryEntry", "120", " 123 ls /foo "}) entry2, err := BuildHistoryEntry([]string{"unused", "saveHistoryEntry", "120", " 123 ls /foo ", "1641774958326745663"})
Check(t, err) Check(t, err)
Check(t, Persist(*entry2)) Check(t, Persist(*entry2))
entry3, err := BuildHistoryEntry([]string{"unused", "saveHistoryEntry", "120", " 123 echo hi "}) entry3, err := BuildHistoryEntry([]string{"unused", "saveHistoryEntry", "120", " 123 echo hi ", "1641774958326745663"})
Check(t, err) Check(t, err)
Check(t, Persist(*entry3)) Check(t, Persist(*entry3))