From e8f001c78b0914df211a4aa3b56629db0ed6da44 Mon Sep 17 00:00:00 2001 From: David Dworken Date: Sun, 12 Jun 2022 21:28:19 -0700 Subject: [PATCH] Add basic support for stripping out HISTTIMEFORMAT prefixes --- client/client_test.go | 61 ++++++++++++++++++++++++++++++++++++++++++ client/lib/lib.go | 19 ++++++++++++- client/lib/lib_test.go | 33 +++++++++++++++++++++++ shared/testutils.go | 7 +++++ 4 files changed, 119 insertions(+), 1 deletion(-) diff --git a/client/client_test.go b/client/client_test.go index 5c1fb08..fed261e 100644 --- a/client/client_test.go +++ b/client/client_test.go @@ -125,6 +125,7 @@ func TestParameterized(t *testing.T) { t.Run("testInstallViaPythonScript/"+tester.ShellName(), func(t *testing.T) { testInstallViaPythonScript(t, tester) }) t.Run("testExportWithQuery/"+tester.ShellName(), func(t *testing.T) { testExportWithQuery(t, tester) }) t.Run("testHelpCommand/"+tester.ShellName(), func(t *testing.T) { testHelpCommand(t, tester) }) + t.Run("testStripBashTimePrefix/"+tester.ShellName(), func(t *testing.T) { testStripBashTimePrefix(t, tester) }) } } @@ -1142,4 +1143,64 @@ func testHelpCommand(t *testing.T, tester shellTester) { } } +func testStripBashTimePrefix(t *testing.T, tester shellTester) { + if tester.ShellName() != "bash" { + t.Skip() + } + + // Setup + defer shared.BackupAndRestore(t)() + installHishtory(t, tester, "") + + // Add a HISTTIMEFORMAT to the bashrc + homedir, err := os.UserHomeDir() + if err != nil { + t.Fatal(err) + } + f, err := os.OpenFile(path.Join(homedir, ".hishtory", "config.sh"), + os.O_APPEND|os.O_WRONLY, 0644) + if err != nil { + t.Fatal(err) + } + defer f.Close() + _, err = f.WriteString("\nexport HISTTIMEFORMAT='%F %T '\n") + if err != nil { + t.Fatal(err) + } + + // Record a command + tester.RunInteractiveShell(t, `ls -Slah`) + + // Check it shows up correctly + out := tester.RunInteractiveShell(t, "hishtory export ls") + if out != "ls -Slah\n" { + t.Fatalf("hishtory had unexpected output=%#v", out) + } + + // Update it to another complex one + homedir, err = os.UserHomeDir() + if err != nil { + t.Fatal(err) + } + f, err = os.OpenFile(path.Join(homedir, ".hishtory", "config.sh"), + os.O_APPEND|os.O_WRONLY, 0644) + if err != nil { + t.Fatal(err) + } + defer f.Close() + _, err = f.WriteString("\nexport HISTTIMEFORMAT='[%c] '\n") + if err != nil { + t.Fatal(err) + } + + // Record a command + tester.RunInteractiveShell(t, `echo foo`) + + // Check it shows up correctly + out = tester.RunInteractiveShell(t, "hishtory export echo") + if out != "echo foo\n" { + t.Fatalf("hishtory had unexpected output=%#v", out) + } +} + // TODO: write a test that runs hishtroy export | grep -v pipefail and then see if that shows up in query/export, I think there is weird behavior here diff --git a/client/lib/lib.go b/client/lib/lib.go index 2c7aec7..00bd13b 100644 --- a/client/lib/lib.go +++ b/client/lib/lib.go @@ -121,7 +121,7 @@ func BuildHistoryEntry(args []string) (*data.HistoryEntry, error) { // Don't save commands that start with a space return nil, nil } - entry.Command = cmd + entry.Command = maybeSkipBashHistTimePrefix(cmd) } else if shell == "zsh" { cmd := strings.TrimSuffix(strings.TrimSuffix(args[4], "\n"), " ") if strings.HasPrefix(cmd, " ") { @@ -150,6 +150,23 @@ func BuildHistoryEntry(args []string) (*data.HistoryEntry, error) { return &entry, nil } +func maybeSkipBashHistTimePrefix(cmdLine string) string { + format := os.Getenv("HISTTIMEFORMAT") + if format == "" { + return cmdLine + } + if !strings.HasSuffix(format, " ") { + GetLogger().Printf("bash has HISTTIMEFORMAT set, but it doesn't end in a space so we can't strip the timestamp") + return cmdLine + } + // TODO: could we handle things like `export HISTTIMEFORMAT='%c:'`? + numSpaces := strings.Count(format, " ") + numC := strings.Count(format, "%c") + numSpaces += (4 * numC) + split := strings.SplitN(cmdLine, " ", numSpaces+1) + return split[len(split)-1] +} + func parseCrossPlatformInt(data string) (int64, error) { data = strings.TrimSuffix(data, "N") return strconv.ParseInt(data, 10, 64) diff --git a/client/lib/lib_test.go b/client/lib/lib_test.go index ddfba98..a6086b4 100644 --- a/client/lib/lib_test.go +++ b/client/lib/lib_test.go @@ -190,3 +190,36 @@ func TestParseCrossPlatformInt(t *testing.T) { t.Fatalf("failed to parse cross platform int %d", res) } } + +func TestMaybeSkipBashHistTimePrefix(t *testing.T) { + defer shared.BackupAndRestoreEnv("HISTTIMEFORMAT")() + + testcases := []struct { + env, cmdLine, expected string + }{ + {"%F %T ", "2019-07-12 13:02:31 sudo apt update", "sudo apt update"}, + {"%F %T ", "2019-07-12 13:02:31 ls a b", "ls a b"}, + {"%F %T ", "2019-07-12 13:02:31 ls a ", "ls a "}, + {"%F %T ", "2019-07-12 13:02:31 ls a", "ls a"}, + {"%F %T ", "2019-07-12 13:02:31 ls", "ls"}, + {"%F %T ", "2019-07-12 13:02:31 ls -Slah", "ls -Slah"}, + {"%F ", "2019-07-12 ls -Slah", "ls -Slah"}, + {"%F ", "2019-07-12 ls -Slah", "ls -Slah"}, + {"", "ls -Slah", "ls -Slah"}, + {"[%F %T] ", "[2019-07-12 13:02:31] sudo apt update", "sudo apt update"}, + {"[%F a %T] ", "[2019-07-12 a 13:02:31] sudo apt update", "sudo apt update"}, + {"aaa ", "aaa sudo apt update", "sudo apt update"}, + {"%c ", "Sun Aug 19 02:56:02 2012 sudo apt update", "sudo apt update"}, + {"%c ", "Sun Aug 19 02:56:02 2012 ls", "ls"}, + {"[%c] ", "[Sun Aug 19 02:56:02 2012] ls", "ls"}, + {"[%c %t] ", "[Sun Aug 19 02:56:02 2012 aaaa] ls", "ls"}, + {"[%c %t] ", "[Sun Aug 19 02:56:02 2012 aaaa] ls -Slah", "ls -Slah"}, + } + + for _, tc := range testcases { + os.Setenv("HISTTIMEFORMAT", tc.env) + if stripped := maybeSkipBashHistTimePrefix(tc.cmdLine); stripped != tc.expected { + t.Fatalf("skipping the time prefix returned %#v (expected=%#v for %#v)", stripped, tc.expected, tc.cmdLine) + } + } +} diff --git a/shared/testutils.go b/shared/testutils.go index 5106f4d..c07390d 100644 --- a/shared/testutils.go +++ b/shared/testutils.go @@ -61,6 +61,13 @@ func BackupAndRestoreWithId(t *testing.T, id string) func() { } } +func BackupAndRestoreEnv(k string) func() { + origValue := os.Getenv(k) + return func() { + os.Setenv(k, origValue) + } +} + func checkError(err error) { if err != nil && os.Getenv("GITHUB_ACTION") == "" { _, filename, line, _ := runtime.Caller(1)