Merge pull request #72 from ddworken/hscroll

Implement horizontal scrolling
This commit is contained in:
David Dworken 2023-02-13 21:27:22 -08:00 committed by GitHub
commit 031bb5446c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 877 additions and 1041 deletions

View File

@ -32,8 +32,8 @@ jobs:
sudo hostname ghaction-runner-hostname || true # Set a consistent hostname so we can run tests that depend on it
sudo scutil --set HostName ghaction-runner-hostname || true
make test
# - name: Setup tmate session
# if: ${{ failure() }}
# uses: mxschmitt/action-tmate@v3
# with:
# limit-access-to-actor: true
- name: Setup tmate session
if: ${{ failure() }}
uses: mxschmitt/action-tmate@v3
with:
limit-access-to-actor: true

View File

@ -493,6 +493,11 @@ func OpenDB() (*gorm.DB, error) {
if err != nil {
return nil, fmt.Errorf("failed to connect to the DB: %v", err)
}
underlyingDb, err := db.DB()
if err != nil {
return nil, fmt.Errorf("failed to access underlying DB: %v", err)
}
underlyingDb.SetMaxOpenConns(1)
db.Exec("PRAGMA journal_mode = WAL")
AddDatabaseTables(db)
return db, nil

View File

@ -30,8 +30,6 @@ func skipSlowTests() bool {
return os.Getenv("FAST") != ""
}
var initialWd string
func TestMain(m *testing.M) {
defer testutils.BackupAndRestoreEnv("HISHTORY_TEST")()
os.Setenv("HISHTORY_TEST", "1")
@ -45,11 +43,6 @@ func TestMain(m *testing.M) {
if err != nil {
panic(fmt.Sprintf("failed to build client: %v", err))
}
cwd, err := os.Getwd()
if err != nil {
panic(fmt.Sprintf("failed to os.Getwd(): %v", err))
}
initialWd = cwd
m.Run()
}
@ -296,12 +289,12 @@ yes | hishtory init `+userSecret)
// And test the export for each shell without anything filtered out
out = tester.RunInteractiveShell(t, `hishtory export -pipefail | grep -v 'hishtory init '`)
compareGoldens(t, out, "testIntegrationWithNewDevice-"+tester.ShellName())
testutils.CompareGoldens(t, out, "testIntegrationWithNewDevice-"+tester.ShellName())
// And test the table but with a subset of columns that is static
tester.RunInteractiveShell(t, `hishtory config-set displayed-columns Hostname 'Exit Code' Command`)
out = tester.RunInteractiveShell(t, `hishtory query -pipefail | grep -v 'hishtory init ' | grep -v 'ls /'`)
compareGoldens(t, out, "testIntegrationWithNewDevice-table"+tester.ShellName())
testutils.CompareGoldens(t, out, "testIntegrationWithNewDevice-table"+tester.ShellName())
// Assert there are no leaked connections
assertNoLeakedConnections(t)
@ -1062,30 +1055,30 @@ func testDisplayTable(t *testing.T, tester shellTester) {
// Query and check the table
tester.RunInteractiveShell(t, ` hishtory disable`)
out := hishtoryQuery(t, tester, "table")
compareGoldens(t, out, "testDisplayTable-defaultColumns")
testutils.CompareGoldens(t, out, "testDisplayTable-defaultColumns")
// Adjust the columns that should be displayed
tester.RunInteractiveShell(t, `hishtory config-set displayed-columns Hostname Command`)
// And check the table again
out = hishtoryQuery(t, tester, "table")
compareGoldens(t, out, "testDisplayTable-customColumns")
testutils.CompareGoldens(t, out, "testDisplayTable-customColumns")
// And again
tester.RunInteractiveShell(t, `hishtory config-set displayed-columns Hostname 'Exit Code' Command`)
out = hishtoryQuery(t, tester, "table")
compareGoldens(t, out, "testDisplayTable-customColumns-2")
testutils.CompareGoldens(t, out, "testDisplayTable-customColumns-2")
// And again
tester.RunInteractiveShell(t, `hishtory config-add displayed-columns CWD`)
out = hishtoryQuery(t, tester, "table")
compareGoldens(t, out, "testDisplayTable-customColumns-3")
testutils.CompareGoldens(t, out, "testDisplayTable-customColumns-3")
// Test displaying a command with multiple lines
entry3 := testutils.MakeFakeHistoryEntry("while :\ndo\nls /table/\ndone")
manuallySubmitHistoryEntry(t, userSecret, entry3)
out = hishtoryQuery(t, tester, "table")
compareGoldens(t, out, "testDisplayTable-customColumns-multiLineCommand")
testutils.CompareGoldens(t, out, "testDisplayTable-customColumns-multiLineCommand")
// Add a custom column
tester.RunInteractiveShell(t, `hishtory config-add custom-columns foo "echo aaaaaaaaaaaaa"`)
@ -1099,7 +1092,7 @@ func testDisplayTable(t *testing.T, tester shellTester) {
// And run a query and confirm it is displayed
out = hishtoryQuery(t, tester, "table")
compareGoldens(t, out, "testDisplayTable-customColumns-trulyCustom")
testutils.CompareGoldens(t, out, "testDisplayTable-customColumns-trulyCustom")
}
func testRequestAndReceiveDbDump(t *testing.T, tester shellTester) {
@ -1779,36 +1772,7 @@ func TestFish(t *testing.T) {
// Check a table to see some other metadata
tester.RunInteractiveShell(t, `hishtory config-set displayed-columns CWD Hostname 'Exit Code' Command`)
out = hishtoryQuery(t, tester, "-pipefail")
compareGoldens(t, out, "TestFish-table")
}
func normalizeHostnames(data string) string {
hostnames := []string{"Davids-MacBook-Air.local", "ghaction-runner-hostname"}
for _, hostname := range hostnames {
data = strings.ReplaceAll(data, hostname, "ghaction-runner-hostname")
}
return data
}
func compareGoldens(t *testing.T, out, goldenName string) {
out = normalizeHostnames(out)
goldenPath := path.Join(initialWd, "client/lib/goldens/", goldenName)
expected, err := os.ReadFile(goldenPath)
if err != nil {
if os.IsNotExist(err) {
expected = []byte("ERR_FILE_NOT_FOUND")
} else {
testutils.Check(t, err)
}
}
if diff := cmp.Diff(string(expected), out); diff != "" {
if os.Getenv("HISHTORY_UPDATE_GOLDENS") == "" {
_, filename, line, _ := runtime.Caller(1)
t.Fatalf("hishtory golden mismatch for %s at %s:%d (-expected +got):\n%s\nactual=\n%s", goldenName, filename, line, diff, out)
} else {
testutils.Check(t, os.WriteFile(goldenPath, []byte(out), 0644))
}
}
testutils.CompareGoldens(t, out, "TestFish-table")
}
func TestTui(t *testing.T) {
@ -1831,7 +1795,7 @@ func TestTui(t *testing.T) {
t.Fatalf("failed to split out=%#v", out)
}
out = strings.TrimSpace(strings.Split(out, "hishtory tquery")[1])
compareGoldens(t, out, "TestTui-Initial")
testutils.CompareGoldens(t, out, "TestTui-Initial")
// Check the output when there is a search
out = captureTerminalOutput(t, tester, []string{
@ -1839,7 +1803,7 @@ func TestTui(t *testing.T) {
"ls",
})
out = strings.TrimSpace(strings.Split(out, "hishtory tquery")[1])
compareGoldens(t, out, "TestTui-Search")
testutils.CompareGoldens(t, out, "TestTui-Search")
// Check the output when there is a selected result
out = captureTerminalOutput(t, tester, []string{
@ -1858,7 +1822,7 @@ func TestTui(t *testing.T) {
"ls",
})
out = strings.TrimSpace(strings.Split(out, "hishtory tquery")[1])
compareGoldens(t, out, "TestTui-InitialInvalidSearch")
testutils.CompareGoldens(t, out, "TestTui-InitialInvalidSearch")
// Check the output when the initial search is invalid
out = captureTerminalOutput(t, tester, []string{
@ -1866,14 +1830,14 @@ func TestTui(t *testing.T) {
"ls:",
})
out = strings.TrimSpace(strings.Split(out, "hishtory tquery")[1])
compareGoldens(t, out, "TestTui-InvalidSearch")
testutils.CompareGoldens(t, out, "TestTui-InvalidSearch")
// Check the output when the size is smaller
out = captureTerminalOutputWithShellNameAndDimensions(t, tester, tester.ShellName(), 100, 20, []TmuxCommand{
{Keys: "hishtory SPACE tquery ENTER"},
})
out = strings.TrimSpace(strings.Split(out, "hishtory tquery")[1])
compareGoldens(t, out, "TestTui-SmallTerminal")
testutils.CompareGoldens(t, out, "TestTui-SmallTerminal")
// Check that we can use left arrow keys to scroll
out = captureTerminalOutput(t, tester, []string{
@ -1883,7 +1847,7 @@ func TestTui(t *testing.T) {
"l",
})
out = strings.TrimSpace(strings.Split(out, "hishtory tquery")[1])
compareGoldens(t, out, "TestTui-LeftScroll")
testutils.CompareGoldens(t, out, "TestTui-LeftScroll")
// Check that we can exit the TUI via pressing esc
out = captureTerminalOutput(t, tester, []string{
@ -1894,7 +1858,7 @@ func TestTui(t *testing.T) {
t.Fatalf("unexpected out=\n%s", out)
}
if !testutils.IsGithubAction() {
compareGoldens(t, out, "TestTui-Exit")
testutils.CompareGoldens(t, out, "TestTui-Exit")
}
// Check that it resizes after the terminal size is adjusted
@ -1904,7 +1868,7 @@ func TestTui(t *testing.T) {
{ResizeX: 300, ResizeY: 100},
})
out = strings.TrimSpace(strings.Split(out, "hishtory tquery")[1])
compareGoldens(t, out, "TestTui-Resize")
testutils.CompareGoldens(t, out, "TestTui-Resize")
// Check that we can delete an entry
out = captureTerminalOutput(t, tester, []string{
@ -1913,14 +1877,14 @@ func TestTui(t *testing.T) {
"C-K",
})
out = strings.TrimSpace(strings.Split(out, "hishtory tquery")[1])
compareGoldens(t, out, "TestTui-Delete")
testutils.CompareGoldens(t, out, "TestTui-Delete")
// And that it stays deleted
out = captureTerminalOutput(t, tester, []string{
"hishtory SPACE tquery ENTER",
})
out = strings.TrimSpace(strings.Split(out, "hishtory tquery")[1])
compareGoldens(t, out, "TestTui-DeleteStill")
testutils.CompareGoldens(t, out, "TestTui-DeleteStill")
// And that we can then delete another entry
out = captureTerminalOutput(t, tester, []string{
@ -1928,14 +1892,31 @@ func TestTui(t *testing.T) {
"C-K",
})
out = strings.TrimSpace(strings.Split(out, "hishtory tquery")[1])
compareGoldens(t, out, "TestTui-DeleteAgain")
testutils.CompareGoldens(t, out, "TestTui-DeleteAgain")
// And that it stays deleted
out = captureTerminalOutput(t, tester, []string{
"hishtory SPACE tquery ENTER",
})
out = strings.TrimSpace(strings.Split(out, "hishtory tquery")[1])
compareGoldens(t, out, "TestTui-DeleteAgainStill")
testutils.CompareGoldens(t, out, "TestTui-DeleteAgainStill")
// Test horizontal scrolling by one to the right
testutils.Check(t, db.Create(testutils.MakeFakeHistoryEntry("echo '1234567890qwertyuiopasdfghjklzxxcvbnm0987654321_0_1234567890qwertyuiopasdfghjklzxxcvbnm0987654321_1_1234567890qwertyuiopasdfghjklzxxcvbnm0987654321_2_1234567890qwertyuiopasdfghjklzxxcvbnm0987654321'")).Error)
out = captureTerminalOutput(t, tester, []string{
"hishtory SPACE tquery ENTER",
"S-Left S-Right S-Right S-Left",
})
out = strings.TrimSpace(strings.Split(out, "hishtory tquery")[1])
testutils.CompareGoldens(t, out, "TestTui-RightScroll")
// Test horizontal scrolling by two
out = captureTerminalOutput(t, tester, []string{
"hishtory SPACE tquery ENTER",
"S-Right S-Right",
})
out = strings.TrimSpace(strings.Split(out, "hishtory tquery")[1])
testutils.CompareGoldens(t, out, "TestTui-RightScrollTwo")
// Assert there are no leaked connections
assertNoLeakedConnections(t)
@ -2035,7 +2016,7 @@ func testControlR(t *testing.T, tester shellTester, shellName string, onlineStat
t.Fatalf("failed to find separator in %#v", out)
}
out = strings.TrimSpace(strings.Split(out, "\n\n\n")[1])
compareGoldens(t, out, "testControlR-Initial")
testutils.CompareGoldens(t, out, "testControlR-Initial")
// And check that we can scroll down and select an option
out = captureTerminalOutputWithShellName(t, tester, shellName, []string{"C-R", "Down Down", "Enter"})
@ -2072,14 +2053,14 @@ func testControlR(t *testing.T, tester shellTester, shellName string, onlineStat
if strings.Contains(out, "\n\n\n") {
out = strings.TrimSpace(strings.Split(out, "\n\n\n")[1])
}
compareGoldens(t, out, "testControlR-Search")
testutils.CompareGoldens(t, out, "testControlR-Search")
// An advanced search and check that the table is updated
out = captureTerminalOutputWithShellName(t, tester, shellName, []string{"C-R", "cwd:/tmp/ SPACE ls"})
if strings.Contains(out, "\n\n\n") {
out = strings.TrimSpace(strings.Split(out, "\n\n\n")[1])
}
compareGoldens(t, out, "testControlR-AdvancedSearch")
testutils.CompareGoldens(t, out, "testControlR-AdvancedSearch")
// Set some different columns to be displayed and check that the table displays those
tester.RunInteractiveShell(t, `hishtory config-set displayed-columns Hostname 'Exit Code' Command`)
@ -2087,7 +2068,7 @@ func testControlR(t *testing.T, tester shellTester, shellName string, onlineStat
if strings.Contains(out, "\n\n\n") {
out = strings.TrimSpace(strings.Split(out, "\n\n\n")[1])
}
compareGoldens(t, out, "testControlR-displayedColumns")
testutils.CompareGoldens(t, out, "testControlR-displayedColumns")
// Add a custom column
tester.RunInteractiveShell(t, `hishtory config-add custom-columns foo "echo foo"`)
@ -2102,35 +2083,35 @@ func testControlR(t *testing.T, tester shellTester, shellName string, onlineStat
if tester.ShellName() == "bash" {
out = strings.TrimSpace(strings.Split(out, "\n\n\n")[1])
}
compareGoldens(t, out, "testControlR-customColumn")
testutils.CompareGoldens(t, out, "testControlR-customColumn")
// Start with a search query, and then press control-r and it shows results for that query
out = captureTerminalOutputWithShellName(t, tester, shellName, []string{"ls", "C-R"})
if strings.Contains(out, "\n\n\n") {
out = strings.TrimSpace(strings.Split(out, "\n\n\n")[1])
}
compareGoldens(t, out, "testControlR-InitialSearch")
testutils.CompareGoldens(t, out, "testControlR-InitialSearch")
// Start with a search query, and then press control-r, then make the query more specific
out = captureTerminalOutputWithShellName(t, tester, shellName, []string{"e", "C-R", "cho"})
if strings.Contains(out, "\n\n\n") {
out = strings.TrimSpace(strings.Split(out, "\n\n\n")[1])
}
compareGoldens(t, out, "testControlR-InitialSearchExpanded")
testutils.CompareGoldens(t, out, "testControlR-InitialSearchExpanded")
// Start with a search query for which there are no results
out = captureTerminalOutputWithShellName(t, tester, shellName, []string{"asdf", "C-R"})
if strings.Contains(out, "\n\n\n") {
out = strings.TrimSpace(strings.Split(out, "\n\n\n")[1])
}
compareGoldens(t, out, "testControlR-InitialSearchNoResults")
testutils.CompareGoldens(t, out, "testControlR-InitialSearchNoResults")
// Start with a search query for which there are no results
out = captureTerminalOutputWithShellName(t, tester, shellName, []string{"asdf", "C-R", "BSpace BSpace BSpace BSpace echo"})
if strings.Contains(out, "\n\n\n") {
out = strings.TrimSpace(strings.Split(out, "\n\n\n")[1])
}
compareGoldens(t, out, "testControlR-InitialSearchNoResultsThenFoundResults")
testutils.CompareGoldens(t, out, "testControlR-InitialSearchNoResultsThenFoundResults")
// Search, hit control-c, and the table should be cleared
out = captureTerminalOutputWithShellName(t, tester, shellName, []string{"echo", "C-R", "c", "C-C"})
@ -2142,7 +2123,7 @@ func testControlR(t *testing.T, tester shellTester, shellName string, onlineStat
}
if !testutils.IsGithubAction() {
// This bit is broken on actions since actions run as a different user
compareGoldens(t, out, "testControlR-ControlC-"+shellName)
testutils.CompareGoldens(t, out, "testControlR-ControlC-"+shellName)
}
// Disable control-r
@ -2154,7 +2135,7 @@ func testControlR(t *testing.T, tester shellTester, shellName string, onlineStat
}
if !testutils.IsGithubAction() {
// This bit is broken on actions since actions run as a different user
compareGoldens(t, out, "testControlR-"+shellName+"-Disabled")
testutils.CompareGoldens(t, out, "testControlR-"+shellName+"-Disabled")
}
// Re-enable control-r
@ -2166,7 +2147,7 @@ func testControlR(t *testing.T, tester shellTester, shellName string, onlineStat
if strings.Contains(out, "\n\n\n") {
out = strings.TrimSpace(strings.Split(out, "\n\n\n")[1])
}
compareGoldens(t, out, "testControlR-Final")
testutils.CompareGoldens(t, out, "testControlR-Final")
// Record a multi-line command
tester.RunInteractiveShell(t, ` hishtory enable`)
@ -2180,7 +2161,7 @@ func testControlR(t *testing.T, tester shellTester, shellName string, onlineStat
if strings.Contains(out, "\n\n\n") {
out = strings.TrimSpace(strings.Split(out, "\n\n\n")[1])
}
compareGoldens(t, out, "testControlR-DisplayMultiline-"+shellName)
testutils.CompareGoldens(t, out, "testControlR-DisplayMultiline-"+shellName)
// Check that we can select it correctly
out = captureTerminalOutputWithShellName(t, tester, shellName, []string{"C-R", "Slah", "Enter"})
@ -2191,7 +2172,7 @@ func testControlR(t *testing.T, tester shellTester, shellName string, onlineStat
t.Fatalf("out has unexpected output missing the selected row: \n%s", out)
}
if !testutils.IsGithubAction() {
compareGoldens(t, out, "testControlR-SelectMultiline-"+shellName)
testutils.CompareGoldens(t, out, "testControlR-SelectMultiline-"+shellName)
}
// Assert there are no leaked connections
@ -2214,7 +2195,7 @@ echo baz`)
// Check that the hishtory is saved correctly
out = tester.RunInteractiveShell(t, `hishtory export | grep -v pipefail`)
compareGoldens(t, out, "testCustomColumns-initHistory")
testutils.CompareGoldens(t, out, "testCustomColumns-initHistory")
// Configure a custom column
tester.RunInteractiveShell(t, `hishtory config-add custom-columns git_remote '(git remote -v 2>/dev/null | grep origin 1>/dev/null ) && git remote get-url origin || true'`)
@ -2230,14 +2211,14 @@ echo bar`)
// And check that it is all recorded correctly
tester.RunInteractiveShell(t, `hishtory config-set displayed-columns 'Exit Code' git_remote Command `)
out = tester.RunInteractiveShell(t, `hishtory query -pipefail`)
compareGoldens(t, out, fmt.Sprintf("testCustomColumns-query-isAction=%v", testutils.IsGithubAction()))
testutils.CompareGoldens(t, out, fmt.Sprintf("testCustomColumns-query-isAction=%v", testutils.IsGithubAction()))
out = captureTerminalOutput(t, tester, []string{"hishtory SPACE tquery SPACE -pipefail ENTER"})
testName := "testCustomColumns-tquery-" + tester.ShellName()
if testutils.IsGithubAction() {
testName += "-isAction"
testName += "-" + runtime.GOOS
}
compareGoldens(t, out, testName)
testutils.CompareGoldens(t, out, testName)
}
func testUninstall(t *testing.T, tester shellTester) {
@ -2249,19 +2230,19 @@ func testUninstall(t *testing.T, tester shellTester) {
tester.RunInteractiveShell(t, `echo foo
echo baz`)
out := tester.RunInteractiveShell(t, `hishtory export -pipefail`)
compareGoldens(t, out, "testUninstall-recorded")
testutils.CompareGoldens(t, out, "testUninstall-recorded")
// And then uninstall
out, err := tester.RunInteractiveShellRelaxed(t, `yes | hishtory uninstall`)
testutils.Check(t, err)
compareGoldens(t, out, "testUninstall-uninstall")
testutils.CompareGoldens(t, out, "testUninstall-uninstall")
// And check that hishtory has been uninstalled
out, err = tester.RunInteractiveShellRelaxed(t, `echo foo
hishtory
echo bar`)
testutils.Check(t, err)
compareGoldens(t, out, "testUninstall-post-uninstall")
testutils.CompareGoldens(t, out, "testUninstall-post-uninstall")
// And check again, but in a way that shows the full terminal output
if !testutils.IsGithubAction() {
@ -2270,7 +2251,7 @@ echo bar`)
"hishtory ENTER",
"echo SPACE bar ENTER",
})
compareGoldens(t, out, "testUninstall-post-uninstall-"+tester.ShellName())
testutils.CompareGoldens(t, out, "testUninstall-post-uninstall-"+tester.ShellName())
}
}
@ -2304,14 +2285,14 @@ func TestTimestampFormat(t *testing.T) {
// And check that it is displayed in both the tui and the classic view
out := hishtoryQuery(t, tester, "-pipefail -tablesizing")
compareGoldens(t, out, "TestTimestampFormat-query")
testutils.CompareGoldens(t, out, "TestTimestampFormat-query")
out = captureTerminalOutput(t, tester, []string{"hishtory SPACE tquery SPACE -pipefail SPACE -tablesizing ENTER"})
out = strings.TrimSpace(strings.Split(out, "hishtory tquery")[1])
goldenName := "TestTimestampFormat-tquery"
if testutils.IsGithubAction() {
goldenName += "-isAction"
}
compareGoldens(t, out, goldenName)
testutils.CompareGoldens(t, out, goldenName)
}
func TestZDotDir(t *testing.T) {
@ -2363,23 +2344,23 @@ echo baz
echo baz
echo foo`)
out := tester.RunInteractiveShell(t, `hishtory export -pipefail`)
compareGoldens(t, out, "testRemoveDuplicateRows-export")
testutils.CompareGoldens(t, out, "testRemoveDuplicateRows-export")
tester.RunInteractiveShell(t, `hishtory config-set displayed-columns 'Exit Code' Command`)
out = tester.RunInteractiveShell(t, `hishtory query -pipefail`)
compareGoldens(t, out, "testRemoveDuplicateRows-query")
testutils.CompareGoldens(t, out, "testRemoveDuplicateRows-query")
out = captureTerminalOutput(t, tester, []string{"hishtory SPACE tquery SPACE -pipefail ENTER"})
out = strings.TrimSpace(strings.Split(out, "hishtory tquery")[1])
compareGoldens(t, out, "testRemoveDuplicateRows-tquery")
testutils.CompareGoldens(t, out, "testRemoveDuplicateRows-tquery")
// And change the config to filter out duplicate rows
tester.RunInteractiveShell(t, `hishtory config-set filter-duplicate-commands true`)
out = tester.RunInteractiveShell(t, `hishtory export -pipefail`)
compareGoldens(t, out, "testRemoveDuplicateRows-enabled-export")
testutils.CompareGoldens(t, out, "testRemoveDuplicateRows-enabled-export")
out = tester.RunInteractiveShell(t, `hishtory query -pipefail`)
compareGoldens(t, out, "testRemoveDuplicateRows-enabled-query")
testutils.CompareGoldens(t, out, "testRemoveDuplicateRows-enabled-query")
out = captureTerminalOutput(t, tester, []string{"hishtory SPACE tquery SPACE -pipefail ENTER"})
out = strings.TrimSpace(strings.Split(out, "hishtory tquery")[1])
compareGoldens(t, out, "testRemoveDuplicateRows-enabled-tquery")
testutils.CompareGoldens(t, out, "testRemoveDuplicateRows-enabled-tquery")
}
func TestSetConfigNoCorruption(t *testing.T) {

View File

@ -0,0 +1,26 @@
Search Query: > ls
┌────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
│ Hostname CWD Timestamp Runtime Exit Code Command │
│────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────│
│ localhost /tmp/ Oct 17 2022 21:43:31 PDT 3s 2 …567890qwertyuiopasdfghjklzxxcvbnm0987654321_0_1234567890qwertyuiopasdfghjklzxxcvbnm0987654321_1_1234567890qwertyuiopasdfghjk… │
│ localhost /tmp/ Oct 17 2022 21:43:16 PDT 3s 2 … │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
└────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘

View File

@ -0,0 +1,26 @@
Search Query: > ls
┌────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
│ Hostname CWD Timestamp Runtime Exit Code Command │
│────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────│
│ localhost /tmp/ Oct 17 2022 21:43:31 PDT 3s 2 …tyuiopasdfghjklzxxcvbnm0987654321_0_1234567890qwertyuiopasdfghjklzxxcvbnm0987654321_1_1234567890qwertyuiopasdfghjklzxxcvbnm0… │
│ localhost /tmp/ Oct 17 2022 21:43:16 PDT 3s 2 … │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
└────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘

View File

@ -0,0 +1,21 @@
Column1  Column2 
 a1 a2345 
b1 b23
c1 c1234567890abcdefgh…

View File

@ -0,0 +1,21 @@
Column1  Column2 
 a1 …2345 
b1 …23
c1 …1234567890abcdefgh…

View File

@ -0,0 +1,21 @@
Column1  Column2 
 a1 …345 
b1 …3
c1 …234567890abcdefghi…

View File

@ -0,0 +1,21 @@
Column1  Column2 
 a1 …45 
b1 …
c1 …34567890abcdefghij…

View File

@ -11,7 +11,7 @@ import (
"github.com/charmbracelet/bubbles/key"
"github.com/charmbracelet/bubbles/spinner"
"github.com/charmbracelet/bubbles/table"
"github.com/ddworken/hishtory/client/table"
"github.com/charmbracelet/bubbles/textinput"
tea "github.com/charmbracelet/bubbletea"
"github.com/charmbracelet/lipgloss"
@ -372,6 +372,14 @@ func makeTable(ctx *context.Context, rows []table.Row) (table.Model, error) {
key.WithKeys("end"),
key.WithHelp("end", "go to end"),
),
MoveLeft: key.NewBinding(
key.WithKeys("shift+left"),
key.WithHelp("Shift+←", "move left"),
),
MoveRight: key.NewBinding(
key.WithKeys("shift+right"),
key.WithHelp("Shift+→", "move right"),
),
}
_, terminalHeight, err := getTerminalSize()
if err != nil {

487
client/table/table.go Normal file
View File

@ -0,0 +1,487 @@
// Forked from https://github.com/charmbracelet/bubbles/blob/master/table/table.go to add horizontal scrolling
package table
import (
"strings"
"github.com/charmbracelet/bubbles/key"
"github.com/charmbracelet/bubbles/viewport"
tea "github.com/charmbracelet/bubbletea"
"github.com/charmbracelet/lipgloss"
"github.com/mattn/go-runewidth"
)
// Model defines a state for the table widget.
type Model struct {
KeyMap KeyMap
cols []Column
rows []Row
cursor int
focus bool
styles Styles
viewport viewport.Model
start int
end int
hcol int
hstep int
hcursor int
}
// Row represents one line in the table.
type Row []string
// Column defines the table structure.
type Column struct {
Title string
Width int
}
// KeyMap defines keybindings. It satisfies to the help.KeyMap interface, which
// is used to render the menu menu.
type KeyMap struct {
LineUp key.Binding
LineDown key.Binding
PageUp key.Binding
PageDown key.Binding
HalfPageUp key.Binding
HalfPageDown key.Binding
GotoTop key.Binding
GotoBottom key.Binding
MoveLeft key.Binding
MoveRight key.Binding
}
// DefaultKeyMap returns a default set of keybindings.
func DefaultKeyMap() KeyMap {
const spacebar = " "
return KeyMap{
LineUp: key.NewBinding(
key.WithKeys("up", "k"),
key.WithHelp("↑/k", "up"),
),
LineDown: key.NewBinding(
key.WithKeys("down", "j"),
key.WithHelp("↓/j", "down"),
),
PageUp: key.NewBinding(
key.WithKeys("b", "pgup"),
key.WithHelp("b/pgup", "page up"),
),
PageDown: key.NewBinding(
key.WithKeys("f", "pgdown", spacebar),
key.WithHelp("f/pgdn", "page down"),
),
HalfPageUp: key.NewBinding(
key.WithKeys("u", "ctrl+u"),
key.WithHelp("u", "½ page up"),
),
HalfPageDown: key.NewBinding(
key.WithKeys("d", "ctrl+d"),
key.WithHelp("d", "½ page down"),
),
GotoTop: key.NewBinding(
key.WithKeys("home", "g"),
key.WithHelp("g/home", "go to start"),
),
GotoBottom: key.NewBinding(
key.WithKeys("end", "G"),
key.WithHelp("G/end", "go to end"),
),
MoveLeft: key.NewBinding(
key.WithKeys("shift+left"),
key.WithHelp("Shift+←", "move left"),
),
MoveRight: key.NewBinding(
key.WithKeys("shift+right"),
key.WithHelp("Shift+→", "move right"),
),
}
}
// Styles contains style definitions for this list component. By default, these
// values are generated by DefaultStyles.
type Styles struct {
Header lipgloss.Style
Cell lipgloss.Style
Selected lipgloss.Style
}
// DefaultStyles returns a set of default style definitions for this table.
func DefaultStyles() Styles {
return Styles{
Selected: lipgloss.NewStyle().Bold(true).Foreground(lipgloss.Color("212")),
Header: lipgloss.NewStyle().Bold(true).Padding(0, 1),
Cell: lipgloss.NewStyle().Padding(0, 1),
}
}
// SetStyles sets the table styles.
func (m *Model) SetStyles(s Styles) {
m.styles = s
m.UpdateViewport()
}
// Option is used to set options in New. For example:
//
// table := New(WithColumns([]Column{{Title: "ID", Width: 10}}))
type Option func(*Model)
// New creates a new model for the table widget.
func New(opts ...Option) Model {
m := Model{
cursor: 0,
viewport: viewport.New(0, 20),
KeyMap: DefaultKeyMap(),
styles: DefaultStyles(),
hcol: -1,
hstep: 10,
hcursor: 0,
}
for _, opt := range opts {
opt(&m)
}
m.UpdateViewport()
return m
}
// WithColumns sets the table columns (headers).
func WithColumns(cols []Column) Option {
return func(m *Model) {
m.cols = cols
}
}
// WithRows sets the table rows (data).
func WithRows(rows []Row) Option {
return func(m *Model) {
m.rows = rows
}
}
// WithHeight sets the height of the table.
func WithHeight(h int) Option {
return func(m *Model) {
m.viewport.Height = h
}
}
// WithWidth sets the width of the table.
func WithWidth(w int) Option {
return func(m *Model) {
m.viewport.Width = w
}
}
// WithFocused sets the focus state of the table.
func WithFocused(f bool) Option {
return func(m *Model) {
m.focus = f
}
}
// WithStyles sets the table styles.
func WithStyles(s Styles) Option {
return func(m *Model) {
m.styles = s
}
}
// WithKeyMap sets the key map.
func WithKeyMap(km KeyMap) Option {
return func(m *Model) {
m.KeyMap = km
}
}
// Update is the Bubble Tea update loop.
func (m Model) Update(msg tea.Msg) (Model, tea.Cmd) {
if !m.focus {
return m, nil
}
var cmds []tea.Cmd
switch msg := msg.(type) {
case tea.KeyMsg:
switch {
case key.Matches(msg, m.KeyMap.LineUp):
m.MoveUp(1)
case key.Matches(msg, m.KeyMap.LineDown):
m.MoveDown(1)
case key.Matches(msg, m.KeyMap.PageUp):
m.MoveUp(m.viewport.Height)
case key.Matches(msg, m.KeyMap.PageDown):
m.MoveDown(m.viewport.Height)
case key.Matches(msg, m.KeyMap.HalfPageUp):
m.MoveUp(m.viewport.Height / 2)
case key.Matches(msg, m.KeyMap.HalfPageDown):
m.MoveDown(m.viewport.Height / 2)
case key.Matches(msg, m.KeyMap.LineDown):
m.MoveDown(1)
case key.Matches(msg, m.KeyMap.GotoTop):
m.GotoTop()
case key.Matches(msg, m.KeyMap.GotoBottom):
m.GotoBottom()
case key.Matches(msg, m.KeyMap.MoveLeft):
m.MoveLeft(m.hstep)
case key.Matches(msg, m.KeyMap.MoveRight):
m.MoveRight(m.hstep)
}
}
return m, tea.Batch(cmds...)
}
// Focused returns the focus state of the table.
func (m Model) Focused() bool {
return m.focus
}
// Focus focusses the table, allowing the user to move around the rows and
// interact.
func (m *Model) Focus() {
m.focus = true
m.UpdateViewport()
}
// Blur blurs the table, preventing selection or movement.
func (m *Model) Blur() {
m.focus = false
m.UpdateViewport()
}
// View renders the component.
func (m Model) View() string {
return m.headersView() + "\n" + m.viewport.View()
}
// UpdateViewport updates the list content based on the previously defined
// columns and rows.
func (m *Model) UpdateViewport() {
renderedRows := make([]string, 0, len(m.rows))
// Render only rows from: m.cursor-m.viewport.Height to: m.cursor+m.viewport.Height
// Constant runtime, independent of number of rows in a table.
// Limits the number of renderedRows to a maximum of 2*m.viewport.Height
if m.cursor >= 0 {
m.start = clamp(m.cursor-m.viewport.Height, 0, m.cursor)
} else {
m.start = 0
}
m.end = clamp(m.cursor+m.viewport.Height, m.cursor, len(m.rows))
for i := m.start; i < m.end; i++ {
renderedRows = append(renderedRows, m.renderRow(i))
}
m.viewport.SetContent(
lipgloss.JoinVertical(lipgloss.Left, renderedRows...),
)
}
// SelectedRow returns the selected row.
// You can cast it to your own implementation.
func (m Model) SelectedRow() Row {
return m.rows[m.cursor]
}
// Rows returns the current rows.
func (m Model) Rows() []Row {
return m.rows
}
// SetRows set a new rows state.
func (m *Model) SetRows(r []Row) {
m.rows = r
m.UpdateViewport()
}
// SetColumns set a new columns state.
func (m *Model) SetColumns(c []Column) {
m.cols = c
m.UpdateViewport()
}
// ColIndex gets the index of a column n, where if n is positive it returns n clamped, and if n is negative it reutrns the column index counting from the right
func (m *Model) ColIndex(n int) int {
if n < 0 {
return clamp(len(m.cols)-n, 0, len(m.cols)-1)
} else {
return clamp(n, 0, len(m.cols)-1)
}
}
// Gets the maximum useful horizontal scroll
func (m *Model) MaxHScroll() int {
maxWidth := 0
index := m.ColIndex(m.hcol)
for _, row := range m.rows {
if len(row) > index {
maxWidth = max(len(row[index]), maxWidth)
}
}
return max(maxWidth-m.cols[index].Width+1, 0)
}
// SetWidth sets the width of the viewport of the table.
func (m *Model) SetWidth(w int) {
m.viewport.Width = w
m.UpdateViewport()
}
// SetHeight sets the height of the viewport of the table.
func (m *Model) SetHeight(h int) {
m.viewport.Height = h
m.UpdateViewport()
}
// Height returns the viewport height of the table.
func (m Model) Height() int {
return m.viewport.Height
}
// Width returns the viewport width of the table.
func (m Model) Width() int {
return m.viewport.Width
}
// Cursor returns the index of the selected row.
func (m Model) Cursor() int {
return m.cursor
}
// SetCursor sets the cursor position in the table.
func (m *Model) SetCursor(n int) {
m.cursor = clamp(n, 0, len(m.rows)-1)
m.UpdateViewport()
}
// MoveUp moves the selection up by any number of row.
// It can not go above the first row.
func (m *Model) MoveUp(n int) {
m.cursor = clamp(m.cursor-n, 0, len(m.rows)-1)
switch {
case m.start == 0:
m.viewport.SetYOffset(clamp(m.viewport.YOffset, 0, m.cursor))
case m.start < m.viewport.Height:
m.viewport.SetYOffset(clamp(m.viewport.YOffset+n, 0, m.cursor))
case m.viewport.YOffset >= 1:
m.viewport.YOffset = clamp(m.viewport.YOffset+n, 1, m.viewport.Height)
}
m.UpdateViewport()
}
// MoveDown moves the selection down by any number of row.
// It can not go below the last row.
func (m *Model) MoveDown(n int) {
m.cursor = clamp(m.cursor+n, 0, len(m.rows)-1)
m.UpdateViewport()
switch {
case m.end == len(m.rows):
m.viewport.SetYOffset(clamp(m.viewport.YOffset-n, 1, m.viewport.Height))
case m.cursor > (m.end-m.start)/2:
m.viewport.SetYOffset(clamp(m.viewport.YOffset-n, 1, m.cursor))
case m.viewport.YOffset > 1:
case m.cursor > m.viewport.YOffset+m.viewport.Height-1:
m.viewport.SetYOffset(clamp(m.viewport.YOffset+1, 0, 1))
}
}
// GotoTop moves the selection to the first row.
func (m *Model) GotoTop() {
m.MoveUp(m.cursor)
}
// GotoBottom moves the selection to the last row.
func (m *Model) GotoBottom() {
m.MoveDown(len(m.rows))
}
// MoveLeft scrolls left
func (m *Model) MoveLeft(n int) {
m.hcursor = clamp(m.hcursor-n, 0, m.MaxHScroll())
m.UpdateViewport()
}
// MoveRight scrolls right
func (m *Model) MoveRight(n int) {
m.hcursor = clamp(m.hcursor+n, 0, m.MaxHScroll())
m.UpdateViewport()
}
// FromValues create the table rows from a simple string. It uses `\n` by
// default for getting all the rows and the given separator for the fields on
// each row.
func (m *Model) FromValues(value, separator string) {
rows := []Row{}
for _, line := range strings.Split(value, "\n") {
r := Row{}
for _, field := range strings.Split(line, separator) {
r = append(r, field)
}
rows = append(rows, r)
}
m.SetRows(rows)
}
func (m Model) headersView() string {
var s = make([]string, 0, len(m.cols))
for _, col := range m.cols {
style := lipgloss.NewStyle().Width(col.Width).MaxWidth(col.Width).Inline(true)
renderedCell := style.Render(runewidth.Truncate(col.Title, col.Width, "…"))
s = append(s, m.styles.Header.Render(renderedCell))
}
return lipgloss.JoinHorizontal(lipgloss.Left, s...)
}
func (m *Model) renderRow(rowID int) string {
var s = make([]string, 0, len(m.cols))
for i, value := range m.rows[rowID] {
style := lipgloss.NewStyle().Width(m.cols[i].Width).MaxWidth(m.cols[i].Width).Inline(true)
var renderedCell string
if i == m.ColIndex(m.hcol) && m.hcursor > 0 {
renderedCell = m.styles.Cell.Render(style.Render(runewidth.Truncate(runewidth.TruncateLeft(value, m.hcursor, "…"), m.cols[i].Width, "…")))
} else {
renderedCell = m.styles.Cell.Render(style.Render(runewidth.Truncate(value, m.cols[i].Width, "…")))
}
s = append(s, renderedCell)
}
row := lipgloss.JoinHorizontal(lipgloss.Left, s...)
if rowID == m.cursor {
return m.styles.Selected.Render(row)
}
return row
}
func max(a, b int) int {
if a > b {
return a
}
return b
}
func min(a, b int) int {
if a < b {
return a
}
return b
}
func clamp(v, low, high int) int {
return min(max(v, low), high)
}

View File

@ -0,0 +1,80 @@
// Forked from https://github.com/charmbracelet/bubbles/blob/master/table/table_test.go to add horizontal scrolling
package table
import (
"testing"
"github.com/ddworken/hishtory/shared/testutils"
)
func TestFromValues(t *testing.T) {
input := "foo1,bar1\nfoo2,bar2\nfoo3,bar3"
table := New(WithColumns([]Column{{Title: "Foo"}, {Title: "Bar"}}))
table.FromValues(input, ",")
if len(table.rows) != 3 {
t.Fatalf("expect table to have 3 rows but it has %d", len(table.rows))
}
expect := []Row{
{"foo1", "bar1"},
{"foo2", "bar2"},
{"foo3", "bar3"},
}
if !deepEqual(table.rows, expect) {
t.Fatal("table rows is not equals to the input")
}
}
func TestFromValuesWithTabSeparator(t *testing.T) {
input := "foo1.\tbar1\nfoo,bar,baz\tbar,2"
table := New(WithColumns([]Column{{Title: "Foo"}, {Title: "Bar"}}))
table.FromValues(input, "\t")
if len(table.rows) != 2 {
t.Fatalf("expect table to have 2 rows but it has %d", len(table.rows))
}
expect := []Row{
{"foo1.", "bar1"},
{"foo,bar,baz", "bar,2"},
}
if !deepEqual(table.rows, expect) {
t.Fatal("table rows is not equals to the input")
}
}
func TestHScoll(t *testing.T) {
table := New(
WithColumns([]Column{{Title: "Column1", Width: 10}, {Title: "Column2", Width: 20}}),
WithRows([]Row{
{"a1", "a2345"},
{"b1", "b23"},
{"c1", "c1234567890abcdefghijklmnopqrstuvwxyz"},
}),
)
testutils.CompareGoldens(t, table.View(), "unittestTable-truncatedTable")
table.MoveRight(1)
testutils.CompareGoldens(t, table.View(), "unittestTable-truncatedTable-right1")
table.MoveRight(1)
testutils.CompareGoldens(t, table.View(), "unittestTable-truncatedTable-right2")
table.MoveRight(1)
testutils.CompareGoldens(t, table.View(), "unittestTable-truncatedTable-right3")
table.MoveLeft(1)
testutils.CompareGoldens(t, table.View(), "unittestTable-truncatedTable-right2")
}
func deepEqual(a, b []Row) bool {
if len(a) != len(b) {
return false
}
for i, r := range a {
for j, f := range r {
if f != b[i][j] {
return false
}
}
}
return true
}

24
go.mod
View File

@ -3,20 +3,27 @@ module github.com/ddworken/hishtory
go 1.18
require (
github.com/DataDog/datadog-go v4.8.3+incompatible
github.com/araddon/dateparse v0.0.0-20210429162001-6b43995a97de
github.com/charmbracelet/bubbles v0.14.1-0.20221006154229-d1775121146a
github.com/charmbracelet/bubbletea v0.23.0
github.com/charmbracelet/bubbles v0.15.0
github.com/charmbracelet/bubbletea v0.23.1
github.com/charmbracelet/lipgloss v0.6.0
github.com/fatih/color v1.13.0
github.com/glebarez/sqlite v1.4.7
github.com/go-test/deep v1.0.8
github.com/google/go-cmp v0.5.9
github.com/google/uuid v1.3.0
github.com/jackc/pgx/v4 v4.14.1
github.com/lib/pq v1.10.4
github.com/mattn/go-runewidth v0.0.14
github.com/muesli/termenv v0.13.0
github.com/rodaine/table v1.0.1
github.com/sirupsen/logrus v1.9.0
github.com/slsa-framework/slsa-verifier v1.3.2
github.com/spf13/cobra v1.6.1
golang.org/x/term v0.2.0
gopkg.in/DataDog/dd-trace-go.v1 v1.43.1
gopkg.in/natefinch/lumberjack.v2 v2.0.0
gorm.io/driver/postgres v1.3.1
gorm.io/driver/sqlite v1.3.6
gorm.io/gorm v1.23.8
@ -36,15 +43,11 @@ require (
github.com/Azure/go-autorest/logger v0.2.1 // indirect
github.com/Azure/go-autorest/tracing v0.6.0 // indirect
github.com/DataDog/datadog-agent/pkg/obfuscate v0.40.1 // indirect
github.com/DataDog/datadog-go v4.8.3+incompatible // indirect
github.com/DataDog/datadog-go/v5 v5.1.1 // indirect
github.com/DataDog/gostackparse v0.5.0 // indirect
github.com/DataDog/sketches-go v1.4.1 // indirect
github.com/Microsoft/go-winio v0.6.0 // indirect
github.com/PaesslerAG/gval v1.0.0 // indirect
github.com/PaesslerAG/jsonpath v0.1.1 // indirect
github.com/ThalesIgnite/crypto11 v1.2.5 // indirect
github.com/alecthomas/kong v0.7.1 // indirect
github.com/alibabacloud-go/alibabacloud-gateway-spi v0.0.4 // indirect
github.com/alibabacloud-go/cr-20160607 v1.0.1 // indirect
github.com/alibabacloud-go/cr-20181201 v1.0.10 // indirect
@ -104,7 +107,6 @@ require (
github.com/dustin/go-humanize v1.0.0 // indirect
github.com/envoyproxy/go-control-plane v0.10.2-0.20220325020618-49ff273808a1 // indirect
github.com/envoyproxy/protoc-gen-validate v0.6.2 // indirect
github.com/felixge/fgprof v0.9.3 // indirect
github.com/fsnotify/fsnotify v1.5.4 // indirect
github.com/fullstorydev/grpcurl v1.8.7 // indirect
github.com/ghodss/yaml v1.0.0 // indirect
@ -149,7 +151,6 @@ require (
github.com/grpc-ecosystem/grpc-gateway/v2 v2.11.3 // indirect
github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
github.com/hashicorp/go-retryablehttp v0.7.1 // indirect
github.com/hashicorp/golang-lru v0.5.4 // indirect
github.com/hashicorp/hcl v1.0.0 // indirect
github.com/imdario/mergo v0.3.12 // indirect
github.com/in-toto/in-toto-golang v0.3.4-0.20220709202702-fa494aaa0add // indirect
@ -161,7 +162,6 @@ require (
github.com/jackc/pgproto3/v2 v2.2.0 // indirect
github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b // indirect
github.com/jackc/pgtype v1.9.1 // indirect
github.com/jackc/pgx/v4 v4.14.1 // indirect
github.com/jedisct1/go-minisign v0.0.0-20211028175153-1c139d1cc84b // indirect
github.com/jhump/protoreflect v1.13.0 // indirect
github.com/jinzhu/inflection v1.0.0 // indirect
@ -179,7 +179,6 @@ require (
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.16 // indirect
github.com/mattn/go-localereader v0.0.1 // indirect
github.com/mattn/go-runewidth v0.0.14 // indirect
github.com/mattn/go-sqlite3 v1.14.15 // indirect
github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369 // indirect
github.com/miekg/pkcs11 v1.1.1 // indirect
@ -200,7 +199,6 @@ require (
github.com/pelletier/go-toml/v2 v2.0.5 // indirect
github.com/philhofer/fwd v1.1.1 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/pkg/profile v1.7.0 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/prometheus/client_golang v1.13.0 // indirect
github.com/prometheus/client_model v0.2.0 // indirect
@ -217,13 +215,11 @@ require (
github.com/sigstore/fulcio v0.6.0 // indirect
github.com/sigstore/rekor v1.0.0 // indirect
github.com/sigstore/sigstore v1.4.5 // indirect
github.com/sirupsen/logrus v1.9.0 // indirect
github.com/skratchdot/open-golang v0.0.0-20200116055534-eef842397966 // indirect
github.com/slsa-framework/slsa-github-generator v1.2.0 // indirect
github.com/soheilhy/cmux v0.1.5 // indirect
github.com/spf13/afero v1.8.2 // indirect
github.com/spf13/cast v1.5.0 // indirect
github.com/spf13/cobra v1.6.1 // indirect
github.com/spf13/jwalterweatherman v1.1.0 // indirect
github.com/spf13/pflag v1.0.5 // indirect
github.com/spf13/viper v1.13.0 // indirect
@ -283,11 +279,9 @@ require (
google.golang.org/genproto v0.0.0-20221014213838-99cd37c6964a // indirect
google.golang.org/grpc v1.50.1 // indirect
google.golang.org/protobuf v1.28.1 // indirect
gopkg.in/DataDog/dd-trace-go.v1 v1.43.1 // indirect
gopkg.in/cheggaaa/pb.v1 v1.0.28 // indirect
gopkg.in/inf.v0 v0.9.1 // indirect
gopkg.in/ini.v1 v1.67.0 // indirect
gopkg.in/natefinch/lumberjack.v2 v2.0.0 // indirect
gopkg.in/square/go-jose.v2 v2.6.0 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect

961
go.sum

File diff suppressed because it is too large Load Diff

View File

@ -16,6 +16,7 @@ import (
"time"
"github.com/ddworken/hishtory/client/data"
"github.com/google/go-cmp/cmp"
)
const (
@ -23,6 +24,28 @@ const (
DB_SHM_PATH = data.DB_PATH + "-shm"
)
var initialWd string
func init() {
initialWd = getInitialWd()
}
func getInitialWd() string {
cwd, err := os.Getwd()
if err != nil {
panic(err)
}
if !strings.Contains(cwd, "/hishtory/") {
return cwd
}
components := strings.Split(cwd, "/hishtory/")
dir := components[0] + "/hishtory"
if IsGithubAction() {
dir += "/hishtory"
}
return dir
}
func ResetLocalState(t *testing.T) {
homedir, err := os.UserHomeDir()
Check(t, err)
@ -321,3 +344,32 @@ func persistLog() {
_, err = f.WriteString("\n")
checkError(err)
}
func CompareGoldens(t *testing.T, out, goldenName string) {
out = normalizeHostnames(out)
goldenPath := path.Join(initialWd, "client/lib/goldens/", goldenName)
expected, err := os.ReadFile(goldenPath)
if err != nil {
if os.IsNotExist(err) {
expected = []byte("ERR_FILE_NOT_FOUND:" + goldenPath)
} else {
Check(t, err)
}
}
if diff := cmp.Diff(string(expected), out); diff != "" {
if os.Getenv("HISHTORY_UPDATE_GOLDENS") == "" {
_, filename, line, _ := runtime.Caller(1)
t.Fatalf("hishtory golden mismatch for %s at %s:%d (-expected +got):\n%s\nactual=\n%s", goldenName, filename, line, diff, out)
} else {
Check(t, os.WriteFile(goldenPath, []byte(out), 0644))
}
}
}
func normalizeHostnames(data string) string {
hostnames := []string{"Davids-MacBook-Air.local", "ghaction-runner-hostname"}
for _, hostname := range hostnames {
data = strings.ReplaceAll(data, hostname, "ghaction-runner-hostname")
}
return data
}