From 434965ca6701aa9939a56b1ba8563a786461d6db Mon Sep 17 00:00:00 2001 From: David Dworken Date: Mon, 11 Dec 2023 21:07:09 -0800 Subject: [PATCH] Add support for quoted searchs for exact matches, for #135 --- README.md | 3 +-- client/lib/lib.go | 8 ++++++-- client/lib/lib_test.go | 22 +++++++++++++++------- 3 files changed, 22 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index 4afcba3d..d7e827a0 100644 --- a/README.md +++ b/README.md @@ -38,7 +38,7 @@ Both support the same query format, see the below annotated queries: |---|---| | `psql` | Find all commands containing `psql` | | `psql db.example.com` | Find all commands containing `psql` and `db.example.com` | -| `docker hostname:my-server` | Find all commands containing `docker` that were run on the computer with hostname `my-server` | +| `"docker run" hostname:my-server` | Find all commands containing `docker run` that were run on the computer with hostname `my-server` | | `nano user:root` | Find all commands containing `nano` that were run as `root` | | `exit_code:127` | Find all commands that exited with code `127` | | `service before:2022-02-01` | Find all commands containing `service` run before February 1st 2022 | @@ -74,7 +74,6 @@ If you would like to: -
TUI key bindings The TUI (opened via `Control+R`) supports a number of key bindings: diff --git a/client/lib/lib.go b/client/lib/lib.go index ebbb0773..8a680f17 100644 --- a/client/lib/lib.go +++ b/client/lib/lib.go @@ -978,19 +978,23 @@ func tokenize(query string) []string { return splitEscaped(query, ' ', -1) } +// TODO: Maybe add support for searching for the backslash character itself? func splitEscaped(query string, separator rune, maxSplit int) []string { var token []rune var tokens []string splits := 1 runeQuery := []rune(query) + isInQuotedString := false for i := 0; i < len(runeQuery); i++ { - if (maxSplit < 0 || splits < maxSplit) && runeQuery[i] == separator { + if (maxSplit < 0 || splits < maxSplit) && runeQuery[i] == separator && !isInQuotedString { tokens = append(tokens, string(token)) token = token[:0] splits++ } else if runeQuery[i] == '\\' && i+1 < len(runeQuery) { - token = append(token, runeQuery[i], runeQuery[i+1]) i++ + token = append(token, runeQuery[i]) + } else if runeQuery[i] == '"' { + isInQuotedString = !isInQuotedString } else { token = append(token, runeQuery[i]) } diff --git a/client/lib/lib_test.go b/client/lib/lib_test.go index 61a60d26..60639d03 100644 --- a/client/lib/lib_test.go +++ b/client/lib/lib_test.go @@ -257,7 +257,7 @@ func TestContainsUnescaped(t *testing.T) { for _, tc := range testcases { actual := containsUnescaped(tc.input, tc.token) if !reflect.DeepEqual(actual, tc.expected) { - t.Fatalf("containsUnescaped failure for containsUnescaped(%#v, %#v), actual=%#v", tc.input, tc.token, actual) + t.Fatalf("failure for containsUnescaped(%#v, %#v), actual=%#v", tc.input, tc.token, actual) } } } @@ -274,16 +274,24 @@ func TestSplitEscaped(t *testing.T) { {"foo bar baz", ' ', 3, []string{"foo", "bar", "baz"}}, {"foo bar baz", ' ', 1, []string{"foo bar baz"}}, {"foo bar baz", ' ', -1, []string{"foo", "bar", "baz"}}, - {"foo\\ bar baz", ' ', -1, []string{"foo\\ bar", "baz"}}, - {"foo\\bar baz", ' ', -1, []string{"foo\\bar", "baz"}}, - {"foo\\bar baz foob", ' ', 2, []string{"foo\\bar", "baz foob"}}, - {"foo\\ bar\\ baz", ' ', -1, []string{"foo\\ bar\\ baz"}}, - {"foo\\ bar\\ baz", ' ', -1, []string{"foo\\ bar\\ ", "baz"}}, + {"foo\\ bar baz", ' ', -1, []string{"foo bar", "baz"}}, + {"foo\\bar baz", ' ', -1, []string{"foobar", "baz"}}, + {"foo\\bar baz foob", ' ', 2, []string{"foobar", "baz foob"}}, + {"foo\\ bar\\ baz", ' ', -1, []string{"foo bar baz"}}, + {"foo\\ bar\\ baz", ' ', -1, []string{"foo bar ", "baz"}}, + {"\"foo bar\"", ' ', -1, []string{"foo bar"}}, + {"\"foo bar\" \" \"", ' ', -1, []string{"foo bar", " "}}, + {"\"foo bar baz\" and", ' ', -1, []string{"foo bar baz", "and"}}, + {"\"foo bar baz\" and", ' ', -1, []string{"foo bar baz", "and"}}, + {"\"foo bar baz", ' ', -1, []string{"foo bar baz"}}, + {"\"foo bar baz\\\"\"", ' ', -1, []string{"foo bar baz\""}}, + {"cwd:\"foo bar :baz\\\"\"", ':', -1, []string{"cwd", "foo bar :baz\""}}, + {"cwd:\"foo bar :baz\\\"\"", ' ', -1, []string{"cwd:foo bar :baz\""}}, } for _, tc := range testcases { actual := splitEscaped(tc.input, tc.char, tc.limit) if !reflect.DeepEqual(actual, tc.expected) { - t.Fatalf("containsUnescaped failure for splitEscaped(%#v, %#v, %#v), actual=%#v", tc.input, string(tc.char), tc.limit, actual) + t.Fatalf("failure for splitEscaped(%#v, %#v, %#v), actual=%#v", tc.input, string(tc.char), tc.limit, actual) } } }