From cbbda23e014e1684788268bebbb4050ea361650e Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Sun, 18 Sep 2022 19:31:24 +0530 Subject: [PATCH] Utility code to find longest common prefix/suffix and to quote strings for various shells --- tools/utils/longest-common.go | 79 ++++++++++++++++++++++++++++++ tools/utils/longest-common_test.go | 21 ++++++++ tools/utils/shell.go | 26 ++++++++++ 3 files changed, 126 insertions(+) create mode 100644 tools/utils/longest-common.go create mode 100644 tools/utils/longest-common_test.go create mode 100644 tools/utils/shell.go diff --git a/tools/utils/longest-common.go b/tools/utils/longest-common.go new file mode 100644 index 000000000..1551395b0 --- /dev/null +++ b/tools/utils/longest-common.go @@ -0,0 +1,79 @@ +// License: GPLv3 Copyright: 2022, Kovid Goyal, + +package utils + +import ( + "fmt" + "math" +) + +var _ = fmt.Print + +func slice_iter(strs []string) func() (string, bool) { + i := 0 + limit := len(strs) + return func() (string, bool) { + if i < limit { + i++ + return strs[i-1], false + } + return "", true + } +} + +// Prefix returns the longest common prefix of the provided strings +func Prefix(strs []string) string { + return LongestCommon(slice_iter(strs), true) +} + +// Suffix returns the longest common suffix of the provided strings +func Suffix(strs []string) string { + return LongestCommon(slice_iter(strs), false) +} + +func min(a ...int) int { + ans := math.MaxInt + for _, x := range a { + if x < ans { + ans = x + } + } + return ans +} + +func LongestCommon(next func() (string, bool), prefix bool) string { + xfix, done := next() + if xfix == "" || done { + return "" + } + for { + q, done := next() + if done { + break + } + q_len := len(q) + xfix_len := len(xfix) + max_len := min(q_len, xfix_len) + if max_len == 0 { + return "" + } + if prefix { + for i := 0; i < max_len; i++ { + if xfix[i] != q[i] { + xfix = xfix[:i] + break + } + } + } else { + for i := 0; i < max_len; i++ { + xi := xfix_len - i - 1 + si := q_len - i - 1 + if xfix[xi] != q[si] { + xfix = xfix[xi+1:] + break + } + } + } + } + return xfix +} diff --git a/tools/utils/longest-common_test.go b/tools/utils/longest-common_test.go new file mode 100644 index 000000000..5f8d656cf --- /dev/null +++ b/tools/utils/longest-common_test.go @@ -0,0 +1,21 @@ +// License: GPLv3 Copyright: 2022, Kovid Goyal, + +package utils + +import ( + "fmt" + "testing" +) + +var _ = fmt.Print + +func TestLongestCommon(t *testing.T) { + p := func(expected string, items ...string) { + actual := Prefix(items) + if actual != expected { + t.Fatalf("Failed with %#v\nExpected: %#v\nActual: %#v", items, expected, actual) + } + } + p("abc", "abc", "abcd") + p("", "abc", "xy") +} diff --git a/tools/utils/shell.go b/tools/utils/shell.go new file mode 100644 index 000000000..a415897cb --- /dev/null +++ b/tools/utils/shell.go @@ -0,0 +1,26 @@ +// License: GPLv3 Copyright: 2022, Kovid Goyal, + +package utils + +import ( + "fmt" + "strings" +) + +var _ = fmt.Print + +// Quotes arbitrary strings for bash, dash and zsh +func QuoteStringForSH(x string) string { + parts := strings.Split(x, "'") + for i, p := range parts { + parts[i] = "'" + p + "'" + } + return strings.Join(parts, "\"'\"") +} + +// Quotes arbitrary strings for fish +func QuoteStringForFish(x string) string { + x = strings.ReplaceAll(x, "\\", "\\\\") + x = strings.ReplaceAll(x, "'", "\\'") + return "'" + x + "'" +}