From 484b44739153495d5cb748c1c17b27234edbd052 Mon Sep 17 00:00:00 2001 From: Chris Allen Lane Date: Thu, 4 Aug 2022 19:26:07 -0400 Subject: [PATCH 1/4] perf(Sheets): do not walk hidden directories Modify `Sheets.Load` to not walk hidden directories like `.git`. This optimization can potentially prevent thousands of system calls from being made, because `.git` directories can contain many files. --- internal/sheets/load.go | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/internal/sheets/load.go b/internal/sheets/load.go index 409f505..0d88c24 100644 --- a/internal/sheets/load.go +++ b/internal/sheets/load.go @@ -2,6 +2,7 @@ package sheets import ( "fmt" + "io/fs" "os" "path/filepath" "strings" @@ -55,7 +56,11 @@ func Load(cheatpaths []cp.Cheatpath) ([]map[string]sheet.Sheet, error) { // contained within hidden directories in the middle of a path, though // that should not realistically occur. if strings.HasPrefix(title, ".") || strings.HasPrefix(info.Name(), ".") { - return nil + // Do not walk hidden directories. This is important, + // because it's common for cheatsheets to be stored in + // version-control, and a `.git` directory can easily + // contain thousands of files. + return fs.SkipDir } // parse the cheatsheet file into a `sheet` struct From 85f5ae8ec76c06497bcec71265173a2bc2b01d31 Mon Sep 17 00:00:00 2001 From: Chris Allen Lane Date: Thu, 4 Aug 2022 20:38:49 -0400 Subject: [PATCH 2/4] chore: various lint corrections Make various lint corrections in order to appease `staticcheck`. --- cmd/cheat/cmd_directories.go | 6 +----- cmd/cheat/cmd_edit.go | 2 +- cmd/cheat/cmd_list.go | 14 +++----------- cmd/cheat/cmd_remove.go | 8 ++++---- cmd/cheat/cmd_search.go | 4 ++-- cmd/cheat/cmd_tags.go | 2 +- cmd/cheat/cmd_view.go | 2 +- internal/cheatpath/filter_test.go | 2 +- internal/cheatpath/writeable.go | 4 +--- internal/config/config.go | 3 +-- internal/config/init.go | 3 +-- internal/config/init_test.go | 5 ++--- internal/config/path_test.go | 3 +-- internal/display/faint.go | 2 +- internal/display/underline.go | 2 +- internal/display/write.go | 2 +- internal/installer/prompt.go | 2 +- internal/sheet/copy_test.go | 9 ++++----- internal/sheet/sheet.go | 4 ++-- 19 files changed, 30 insertions(+), 49 deletions(-) diff --git a/cmd/cheat/cmd_directories.go b/cmd/cheat/cmd_directories.go index eeadd36..de8b353 100644 --- a/cmd/cheat/cmd_directories.go +++ b/cmd/cheat/cmd_directories.go @@ -18,11 +18,7 @@ func cmdDirectories(opts map[string]interface{}, conf config.Config) { // generate sorted, columnized output for _, path := range conf.Cheatpaths { - fmt.Fprintln(w, fmt.Sprintf( - "%s:\t%s", - path.Name, - path.Path, - )) + fmt.Fprintf(w, "%s:\t%s\n", path.Name, path.Path) } // write columnized output to stdout diff --git a/cmd/cheat/cmd_edit.go b/cmd/cheat/cmd_edit.go index ee17399..6c5d2ff 100644 --- a/cmd/cheat/cmd_edit.go +++ b/cmd/cheat/cmd_edit.go @@ -20,7 +20,7 @@ func cmdEdit(opts map[string]interface{}, conf config.Config) { // load the cheatsheets cheatsheets, err := sheets.Load(conf.Cheatpaths) if err != nil { - fmt.Fprintln(os.Stderr, fmt.Sprintf("failed to list cheatsheets: %v", err)) + fmt.Fprintf(os.Stderr, "failed to list cheatsheets: %v\n", err) os.Exit(1) } diff --git a/cmd/cheat/cmd_list.go b/cmd/cheat/cmd_list.go index 9ef5106..28bb442 100644 --- a/cmd/cheat/cmd_list.go +++ b/cmd/cheat/cmd_list.go @@ -21,7 +21,7 @@ func cmdList(opts map[string]interface{}, conf config.Config) { // load the cheatsheets cheatsheets, err := sheets.Load(conf.Cheatpaths) if err != nil { - fmt.Fprintln(os.Stderr, fmt.Sprintf("failed to list cheatsheets: %v", err)) + fmt.Fprintf(os.Stderr, "failed to list cheatsheets: %v\n", err) os.Exit(1) } @@ -63,10 +63,7 @@ func cmdList(opts map[string]interface{}, conf config.Config) { // compile the regex reg, err := regexp.Compile(pattern) if err != nil { - fmt.Fprintln( - os.Stderr, - fmt.Sprintf("failed to compile regexp: %s, %v", pattern, err), - ) + fmt.Fprintf(os.Stderr, "failed to compile regexp: %s, %v\n", pattern, err) os.Exit(1) } @@ -95,12 +92,7 @@ func cmdList(opts map[string]interface{}, conf config.Config) { // generate sorted, columnized output for _, sheet := range flattened { - fmt.Fprintln(w, fmt.Sprintf( - "%s\t%s\t%s", - sheet.Title, - sheet.Path, - strings.Join(sheet.Tags, ","), - )) + fmt.Fprintf(w, "%s\t%s\t%s\n", sheet.Title, sheet.Path, strings.Join(sheet.Tags, ",")) } // write columnized output to stdout diff --git a/cmd/cheat/cmd_remove.go b/cmd/cheat/cmd_remove.go index 072e525..ae6053d 100644 --- a/cmd/cheat/cmd_remove.go +++ b/cmd/cheat/cmd_remove.go @@ -17,7 +17,7 @@ func cmdRemove(opts map[string]interface{}, conf config.Config) { // load the cheatsheets cheatsheets, err := sheets.Load(conf.Cheatpaths) if err != nil { - fmt.Fprintln(os.Stderr, fmt.Sprintf("failed to list cheatsheets: %v", err)) + fmt.Fprintf(os.Stderr, "failed to list cheatsheets: %v\n", err) os.Exit(1) } @@ -37,19 +37,19 @@ func cmdRemove(opts map[string]interface{}, conf config.Config) { // fail early if the requested cheatsheet does not exist sheet, ok := consolidated[cheatsheet] if !ok { - fmt.Fprintln(os.Stderr, fmt.Sprintf("No cheatsheet found for '%s'.\n", cheatsheet)) + fmt.Fprintf(os.Stderr, "No cheatsheet found for '%s'.\n", cheatsheet) os.Exit(2) } // fail early if the sheet is read-only if sheet.ReadOnly { - fmt.Fprintln(os.Stderr, fmt.Sprintf("cheatsheet '%s' is read-only.", cheatsheet)) + fmt.Fprintf(os.Stderr, "cheatsheet '%s' is read-only.\n", cheatsheet) os.Exit(1) } // otherwise, attempt to delete the sheet if err := os.Remove(sheet.Path); err != nil { - fmt.Fprintln(os.Stderr, fmt.Sprintf("failed to delete sheet: %s, %v", sheet.Title, err)) + fmt.Fprintf(os.Stderr, "failed to delete sheet: %s, %v\n", sheet.Title, err) os.Exit(1) } } diff --git a/cmd/cheat/cmd_search.go b/cmd/cheat/cmd_search.go index ed11954..f9da0c2 100644 --- a/cmd/cheat/cmd_search.go +++ b/cmd/cheat/cmd_search.go @@ -19,7 +19,7 @@ func cmdSearch(opts map[string]interface{}, conf config.Config) { // load the cheatsheets cheatsheets, err := sheets.Load(conf.Cheatpaths) if err != nil { - fmt.Fprintln(os.Stderr, fmt.Sprintf("failed to list cheatsheets: %v", err)) + fmt.Fprintf(os.Stderr, "failed to list cheatsheets: %v\n", err) os.Exit(1) } @@ -55,7 +55,7 @@ func cmdSearch(opts map[string]interface{}, conf config.Config) { // compile the regex reg, err := regexp.Compile(pattern) if err != nil { - fmt.Fprintln(os.Stderr, fmt.Sprintf("failed to compile regexp: %s, %v", pattern, err)) + fmt.Fprintf(os.Stderr, "failed to compile regexp: %s, %v\n", pattern, err) os.Exit(1) } diff --git a/cmd/cheat/cmd_tags.go b/cmd/cheat/cmd_tags.go index 6187d88..8709c3e 100644 --- a/cmd/cheat/cmd_tags.go +++ b/cmd/cheat/cmd_tags.go @@ -15,7 +15,7 @@ func cmdTags(opts map[string]interface{}, conf config.Config) { // load the cheatsheets cheatsheets, err := sheets.Load(conf.Cheatpaths) if err != nil { - fmt.Fprintln(os.Stderr, fmt.Sprintf("failed to list cheatsheets: %v", err)) + fmt.Fprintf(os.Stderr, "failed to list cheatsheets: %v\n", err) os.Exit(1) } diff --git a/cmd/cheat/cmd_view.go b/cmd/cheat/cmd_view.go index 13b3c93..b745674 100644 --- a/cmd/cheat/cmd_view.go +++ b/cmd/cheat/cmd_view.go @@ -18,7 +18,7 @@ func cmdView(opts map[string]interface{}, conf config.Config) { // load the cheatsheets cheatsheets, err := sheets.Load(conf.Cheatpaths) if err != nil { - fmt.Fprintln(os.Stderr, fmt.Sprintf("failed to list cheatsheets: %v", err)) + fmt.Fprintf(os.Stderr, "failed to list cheatsheets: %v\n", err) os.Exit(1) } diff --git a/internal/cheatpath/filter_test.go b/internal/cheatpath/filter_test.go index 8587047..92c5025 100644 --- a/internal/cheatpath/filter_test.go +++ b/internal/cheatpath/filter_test.go @@ -46,7 +46,7 @@ func TestFilterFailure(t *testing.T) { } // filter the paths - paths, err := Filter(paths, "qux") + _, err := Filter(paths, "qux") if err == nil { t.Errorf("failed to return an error on non-existent cheatpath") } diff --git a/internal/cheatpath/writeable.go b/internal/cheatpath/writeable.go index b45482c..1f7c522 100644 --- a/internal/cheatpath/writeable.go +++ b/internal/cheatpath/writeable.go @@ -11,12 +11,10 @@ func Writeable(cheatpaths []Cheatpath) (Cheatpath, error) { // NB: we're going backwards because we assume that the most "local" // cheatpath will be specified last in the configs for i := len(cheatpaths) - 1; i >= 0; i-- { - // if the cheatpath is not read-only, it is writeable, and thus returned - if cheatpaths[i].ReadOnly == false { + if !cheatpaths[i].ReadOnly { return cheatpaths[i], nil } - } // otherwise, return an error diff --git a/internal/config/config.go b/internal/config/config.go index 4870a54..71002d9 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -2,7 +2,6 @@ package config import ( "fmt" - "io/ioutil" "os" "os/exec" "path/filepath" @@ -29,7 +28,7 @@ type Config struct { func New(opts map[string]interface{}, confPath string, resolve bool) (Config, error) { // read the config file - buf, err := ioutil.ReadFile(confPath) + buf, err := os.ReadFile(confPath) if err != nil { return Config{}, fmt.Errorf("could not read config file: %v", err) } diff --git a/internal/config/init.go b/internal/config/init.go index 91fa6d6..ada5e9d 100644 --- a/internal/config/init.go +++ b/internal/config/init.go @@ -2,7 +2,6 @@ package config import ( "fmt" - "io/ioutil" "os" "path/filepath" ) @@ -16,7 +15,7 @@ func Init(confpath string, configs string) error { } // write the config file - if err := ioutil.WriteFile(confpath, []byte(configs), 0644); err != nil { + if err := os.WriteFile(confpath, []byte(configs), 0644); err != nil { return fmt.Errorf("failed to create file: %v", err) } diff --git a/internal/config/init_test.go b/internal/config/init_test.go index 4d17bb2..b40ba2a 100644 --- a/internal/config/init_test.go +++ b/internal/config/init_test.go @@ -1,7 +1,6 @@ package config import ( - "io/ioutil" "os" "testing" ) @@ -10,7 +9,7 @@ import ( func TestInit(t *testing.T) { // initialize a temporary config file - confFile, err := ioutil.TempFile("", "cheat-test") + confFile, err := os.CreateTemp("", "cheat-test") if err != nil { t.Errorf("failed to create temp file: %v", err) } @@ -25,7 +24,7 @@ func TestInit(t *testing.T) { } // read back the config file contents - bytes, err := ioutil.ReadFile(confFile.Name()) + bytes, err := os.ReadFile(confFile.Name()) if err != nil { t.Errorf("failed to read config file: %v", err) } diff --git a/internal/config/path_test.go b/internal/config/path_test.go index 2121ff9..51d8f65 100644 --- a/internal/config/path_test.go +++ b/internal/config/path_test.go @@ -1,7 +1,6 @@ package config import ( - "io/ioutil" "os" "testing" ) @@ -24,7 +23,7 @@ func TestPathConfigNotExists(t *testing.T) { func TestPathConfigExists(t *testing.T) { // initialize a temporary config file - confFile, err := ioutil.TempFile("", "cheat-test") + confFile, err := os.CreateTemp("", "cheat-test") if err != nil { t.Errorf("failed to create temp file: %v", err) } diff --git a/internal/display/faint.go b/internal/display/faint.go index aafdd27..45a7343 100644 --- a/internal/display/faint.go +++ b/internal/display/faint.go @@ -10,7 +10,7 @@ import ( func Faint(str string, conf config.Config) string { // make `str` faint only if colorization has been requested if conf.Colorize { - return fmt.Sprintf(fmt.Sprintf("\033[2m%s\033[0m", str)) + return fmt.Sprintf("\033[2m%s\033[0m", str) } // otherwise, return the string unmodified diff --git a/internal/display/underline.go b/internal/display/underline.go index 4ac914c..e9e5907 100644 --- a/internal/display/underline.go +++ b/internal/display/underline.go @@ -4,5 +4,5 @@ import "fmt" // Underline returns an underlined string func Underline(str string) string { - return fmt.Sprintf(fmt.Sprintf("\033[4m%s\033[0m", str)) + return fmt.Sprintf("\033[4m%s\033[0m", str) } diff --git a/internal/display/write.go b/internal/display/write.go index c1bd2b4..3736262 100644 --- a/internal/display/write.go +++ b/internal/display/write.go @@ -31,7 +31,7 @@ func Write(out string, conf config.Config) { // handle errors err := cmd.Run() if err != nil { - fmt.Fprintln(os.Stderr, fmt.Sprintf("failed to write to pager: %v", err)) + fmt.Fprintf(os.Stderr, "failed to write to pager: %v\n", err) os.Exit(1) } } diff --git a/internal/installer/prompt.go b/internal/installer/prompt.go index 7090d1f..3f2d90e 100644 --- a/internal/installer/prompt.go +++ b/internal/installer/prompt.go @@ -14,7 +14,7 @@ func Prompt(prompt string, def bool) (bool, error) { reader := bufio.NewReader(os.Stdin) // display the prompt - fmt.Print(fmt.Sprintf("%s: ", prompt)) + fmt.Printf("%s: ", prompt) // read the answer ans, err := reader.ReadString('\n') diff --git a/internal/sheet/copy_test.go b/internal/sheet/copy_test.go index 1cd868b..b7ef29c 100644 --- a/internal/sheet/copy_test.go +++ b/internal/sheet/copy_test.go @@ -1,7 +1,6 @@ package sheet import ( - "io/ioutil" "os" "path" "testing" @@ -13,7 +12,7 @@ func TestCopyFlat(t *testing.T) { // mock a cheatsheet file text := "this is the cheatsheet text" - src, err := ioutil.TempFile("", "foo-src") + src, err := os.CreateTemp("", "foo-src") if err != nil { t.Errorf("failed to mock cheatsheet: %v", err) } @@ -41,7 +40,7 @@ func TestCopyFlat(t *testing.T) { } // assert that the destination file contains the correct text - got, err := ioutil.ReadFile(outpath) + got, err := os.ReadFile(outpath) if err != nil { t.Errorf("failed to read destination file: %v", err) } @@ -60,7 +59,7 @@ func TestCopyDeep(t *testing.T) { // mock a cheatsheet file text := "this is the cheatsheet text" - src, err := ioutil.TempFile("", "foo-src") + src, err := os.CreateTemp("", "foo-src") if err != nil { t.Errorf("failed to mock cheatsheet: %v", err) } @@ -94,7 +93,7 @@ func TestCopyDeep(t *testing.T) { } // assert that the destination file contains the correct text - got, err := ioutil.ReadFile(outpath) + got, err := os.ReadFile(outpath) if err != nil { t.Errorf("failed to read destination file: %v", err) } diff --git a/internal/sheet/sheet.go b/internal/sheet/sheet.go index 4471401..aaf9d92 100644 --- a/internal/sheet/sheet.go +++ b/internal/sheet/sheet.go @@ -2,7 +2,7 @@ package sheet import ( "fmt" - "io/ioutil" + "os" "sort" "github.com/cheat/cheat/internal/frontmatter" @@ -29,7 +29,7 @@ func New( ) (Sheet, error) { // read the cheatsheet file - markdown, err := ioutil.ReadFile(path) + markdown, err := os.ReadFile(path) if err != nil { return Sheet{}, fmt.Errorf("failed to read file: %s, %v", path, err) } From f4e6c76e5897330e71dedb38c174a21ace42857a Mon Sep 17 00:00:00 2001 From: Christopher Allen Lane Date: Fri, 5 Aug 2022 06:38:08 -0400 Subject: [PATCH 3/4] fix: escape sequences in search output (#687) Fix an issue whereby ANSI escape characters could appear in search output when a pager was not configured. The root cause of the problem was code that was overzealously applying an underlying effect to search terms. This commit simply rips out underlying entirely, both as means of resolving this problem, and also simply for removing needless visual noise from search output. --- cmd/cheat/cmd_search.go | 24 +++++++++++++----------- cmd/cheat/cmd_view.go | 2 +- internal/display/underline.go | 8 -------- internal/display/underline_test.go | 14 -------------- internal/display/write.go | 7 +++---- 5 files changed, 17 insertions(+), 38 deletions(-) delete mode 100644 internal/display/underline.go delete mode 100644 internal/display/underline_test.go diff --git a/cmd/cheat/cmd_search.go b/cmd/cheat/cmd_search.go index f9da0c2..88a35e4 100644 --- a/cmd/cheat/cmd_search.go +++ b/cmd/cheat/cmd_search.go @@ -59,9 +59,9 @@ func cmdSearch(opts map[string]interface{}, conf config.Config) { os.Exit(1) } - // `Search` will return text entries that match the search terms. We're - // using it here to overwrite the prior cheatsheet Text, filtering it to - // only what is relevant + // `Search` will return text entries that match the search terms. + // We're using it here to overwrite the prior cheatsheet Text, + // filtering it to only what is relevant. sheet.Text = sheet.Search(reg) // if the sheet did not match the search, ignore it and move on @@ -74,14 +74,16 @@ func cmdSearch(opts map[string]interface{}, conf config.Config) { sheet.Colorize(conf) } - // display the cheatsheet title and path - out += fmt.Sprintf("%s %s\n", - display.Underline(sheet.Title), + // display the cheatsheet body + out += fmt.Sprintf( + "%s %s\n%s\n", + // append the cheatsheet title + sheet.Title, + // append the cheatsheet path display.Faint(fmt.Sprintf("(%s)", sheet.CheatPath), conf), + // indent each line of content + display.Indent(sheet.Text), ) - - // indent each line of content - out += display.Indent(sheet.Text) + "\n" } } @@ -89,7 +91,7 @@ func cmdSearch(opts map[string]interface{}, conf config.Config) { out = strings.TrimSpace(out) // display the output - // NB: resist the temptation to call `display.Display` multiple times in - // the loop above. That will not play nicely with the paginator. + // NB: resist the temptation to call `display.Write` multiple times in the + // loop above. That will not play nicely with the paginator. display.Write(out, conf) } diff --git a/cmd/cheat/cmd_view.go b/cmd/cheat/cmd_view.go index b745674..41a47a6 100644 --- a/cmd/cheat/cmd_view.go +++ b/cmd/cheat/cmd_view.go @@ -41,7 +41,7 @@ func cmdView(opts map[string]interface{}, conf config.Config) { // identify the matching cheatsheet out += fmt.Sprintf("%s %s\n", - display.Underline(sheet.Title), + sheet.Title, display.Faint(fmt.Sprintf("(%s)", sheet.CheatPath), conf), ) diff --git a/internal/display/underline.go b/internal/display/underline.go deleted file mode 100644 index e9e5907..0000000 --- a/internal/display/underline.go +++ /dev/null @@ -1,8 +0,0 @@ -package display - -import "fmt" - -// Underline returns an underlined string -func Underline(str string) string { - return fmt.Sprintf("\033[4m%s\033[0m", str) -} diff --git a/internal/display/underline_test.go b/internal/display/underline_test.go deleted file mode 100644 index e9743ee..0000000 --- a/internal/display/underline_test.go +++ /dev/null @@ -1,14 +0,0 @@ -package display - -import ( - "testing" -) - -// TestUnderline asserts that Underline applies underline formatting -func TestUnderline(t *testing.T) { - want := "\033[4mfoo\033[0m" - got := Underline("foo") - if want != got { - t.Errorf("failed to underline: want: %s, got: %s", want, got) - } -} diff --git a/internal/display/write.go b/internal/display/write.go index 3736262..d4dcfc5 100644 --- a/internal/display/write.go +++ b/internal/display/write.go @@ -23,14 +23,13 @@ func Write(out string, conf config.Config) { pager := parts[0] args := parts[1:] - // run the pager + // configure the pager cmd := exec.Command(pager, args...) cmd.Stdin = strings.NewReader(out) cmd.Stdout = os.Stdout - // handle errors - err := cmd.Run() - if err != nil { + // run the pager and handle errors + if err := cmd.Run(); err != nil { fmt.Fprintf(os.Stderr, "failed to write to pager: %v\n", err) os.Exit(1) } From 8130b2f3bdb1307713e94670a7f16718db03a21b Mon Sep 17 00:00:00 2001 From: Chris Allen Lane Date: Thu, 4 Aug 2022 20:40:21 -0400 Subject: [PATCH 4/4] chore: bump version to `4.2.7` --- INSTALLING.md | 4 ++-- cmd/cheat/main.go | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/INSTALLING.md b/INSTALLING.md index 7f85bf4..9f67d91 100644 --- a/INSTALLING.md +++ b/INSTALLING.md @@ -9,13 +9,13 @@ On Unix-like systems, you may simply paste the following snippet into your termi ```sh cd /tmp \ - && wget https://github.com/cheat/cheat/releases/download/4.2.5/cheat-linux-amd64.gz \ + && wget https://github.com/cheat/cheat/releases/download/4.2.7/cheat-linux-amd64.gz \ && gunzip cheat-linux-amd64.gz \ && chmod +x cheat-linux-amd64 \ && sudo mv cheat-linux-amd64 /usr/local/bin/cheat ``` -You may need to need to change the version number (`4.2.5`) and the archive +You may need to need to change the version number (`4.2.7`) and the archive (`cheat-linux-amd64.gz`) depending on your platform. See the [releases page][releases] for a list of supported platforms. diff --git a/cmd/cheat/main.go b/cmd/cheat/main.go index f2790ac..c40e138 100755 --- a/cmd/cheat/main.go +++ b/cmd/cheat/main.go @@ -16,7 +16,7 @@ import ( "github.com/cheat/cheat/internal/installer" ) -const version = "4.2.6" +const version = "4.2.7" func main() {