From 59d5c96c248cb8fdeadb52e2daee8dd222547ace Mon Sep 17 00:00:00 2001 From: Chris Lane Date: Thu, 25 Jun 2020 18:21:51 -0400 Subject: [PATCH 1/3] feat(pagination): implement paginated output Implement a `pager` config option. If configured, `cheat` will automatically pipe output through the configured pager (where appropriate). --- cmd/cheat/cmd_directories.go | 7 +++++-- cmd/cheat/cmd_list.go | 10 ++++++++-- cmd/cheat/cmd_search.go | 9 ++++++--- cmd/cheat/cmd_tags.go | 9 +++++++-- cmd/cheat/cmd_view.go | 3 ++- cmd/cheat/str_config.go | 3 +++ configs/conf.yml | 3 +++ internal/config/config.go | 7 +++++++ internal/display/display.go | 37 ++++++++++++++++++++++++++++++++++++ 9 files changed, 78 insertions(+), 10 deletions(-) create mode 100644 internal/display/display.go diff --git a/cmd/cheat/cmd_directories.go b/cmd/cheat/cmd_directories.go index 34246d8..7b20e4c 100644 --- a/cmd/cheat/cmd_directories.go +++ b/cmd/cheat/cmd_directories.go @@ -1,18 +1,20 @@ package main import ( + "bytes" "fmt" - "os" "text/tabwriter" "github.com/cheat/cheat/internal/config" + "github.com/cheat/cheat/internal/display" ) // cmdDirectories lists the configured cheatpaths. func cmdDirectories(opts map[string]interface{}, conf config.Config) { // initialize a tabwriter to produce cleanly columnized output - w := tabwriter.NewWriter(os.Stdout, 0, 0, 1, ' ', 0) + var out bytes.Buffer + w := tabwriter.NewWriter(&out, 0, 0, 1, ' ', 0) // generate sorted, columnized output for _, path := range conf.Cheatpaths { @@ -25,4 +27,5 @@ func cmdDirectories(opts map[string]interface{}, conf config.Config) { // write columnized output to stdout w.Flush() + display.Display(out.String(), conf) } diff --git a/cmd/cheat/cmd_list.go b/cmd/cheat/cmd_list.go index 6f03318..2e41a25 100644 --- a/cmd/cheat/cmd_list.go +++ b/cmd/cheat/cmd_list.go @@ -1,6 +1,7 @@ package main import ( + "bytes" "fmt" "os" "regexp" @@ -9,6 +10,7 @@ import ( "text/tabwriter" "github.com/cheat/cheat/internal/config" + "github.com/cheat/cheat/internal/display" "github.com/cheat/cheat/internal/sheet" "github.com/cheat/cheat/internal/sheets" ) @@ -85,10 +87,13 @@ func cmdList(opts map[string]interface{}, conf config.Config) { } // initialize a tabwriter to produce cleanly columnized output - w := tabwriter.NewWriter(os.Stdout, 0, 0, 1, ' ', 0) + var out bytes.Buffer + w := tabwriter.NewWriter(&out, 0, 0, 1, ' ', 0) + + // write a header row + fmt.Fprintln(w, "title:\tfile:\ttags:") // generate sorted, columnized output - fmt.Fprintln(w, "title:\tfile:\ttags:") for _, sheet := range flattened { fmt.Fprintln(w, fmt.Sprintf( "%s\t%s\t%s", @@ -100,4 +105,5 @@ func cmdList(opts map[string]interface{}, conf config.Config) { // write columnized output to stdout w.Flush() + display.Display(out.String(), conf) } diff --git a/cmd/cheat/cmd_search.go b/cmd/cheat/cmd_search.go index edc9b29..f497831 100644 --- a/cmd/cheat/cmd_search.go +++ b/cmd/cheat/cmd_search.go @@ -7,6 +7,7 @@ import ( "strings" "github.com/cheat/cheat/internal/config" + "github.com/cheat/cheat/internal/display" "github.com/cheat/cheat/internal/sheet" "github.com/cheat/cheat/internal/sheets" ) @@ -87,12 +88,14 @@ func cmdSearch(opts map[string]interface{}, conf config.Config) { } // output the cheatsheet title - fmt.Printf("%s:\n", sheet.Title) + out := fmt.Sprintf("%s:\n", sheet.Title) // indent each line of content with two spaces for _, line := range strings.Split(sheet.Text, "\n") { - fmt.Printf(" %s\n", line) + out += fmt.Sprintf(" %s\n", line) } - fmt.Println("") + + // display the output + display.Display(out, conf) } } diff --git a/cmd/cheat/cmd_tags.go b/cmd/cheat/cmd_tags.go index a17c87c..3358bdd 100644 --- a/cmd/cheat/cmd_tags.go +++ b/cmd/cheat/cmd_tags.go @@ -5,6 +5,7 @@ import ( "os" "github.com/cheat/cheat/internal/config" + "github.com/cheat/cheat/internal/display" "github.com/cheat/cheat/internal/sheets" ) @@ -18,8 +19,12 @@ func cmdTags(opts map[string]interface{}, conf config.Config) { os.Exit(1) } - // write sheet tags to stdout + // assemble the output + out := "" for _, tag := range sheets.Tags(cheatsheets) { - fmt.Println(tag) + out += fmt.Sprintln(tag) } + + // display the output + display.Display(out, conf) } diff --git a/cmd/cheat/cmd_view.go b/cmd/cheat/cmd_view.go index 0636dc7..3e0aade 100644 --- a/cmd/cheat/cmd_view.go +++ b/cmd/cheat/cmd_view.go @@ -6,6 +6,7 @@ import ( "strings" "github.com/cheat/cheat/internal/config" + "github.com/cheat/cheat/internal/display" "github.com/cheat/cheat/internal/sheets" ) @@ -47,5 +48,5 @@ func cmdView(opts map[string]interface{}, conf config.Config) { } // display the cheatsheet - fmt.Print(sheet.Text) + display.Display(sheet.Text, conf) } diff --git a/cmd/cheat/str_config.go b/cmd/cheat/str_config.go index 6cfcd2a..f903eb6 100644 --- a/cmd/cheat/str_config.go +++ b/cmd/cheat/str_config.go @@ -23,6 +23,9 @@ style: monokai # One of: "terminal", "terminal256", "terminal16m" formatter: terminal16m +# Through which pager should output be piped? (Unset this key for no pager.) +pager: less -FRX + # The paths at which cheatsheets are available. Tags associated with a cheatpath # are automatically attached to all cheatsheets residing on that path. # diff --git a/configs/conf.yml b/configs/conf.yml index a622d3e..8b9bfca 100644 --- a/configs/conf.yml +++ b/configs/conf.yml @@ -14,6 +14,9 @@ style: monokai # One of: "terminal", "terminal256", "terminal16m" formatter: terminal16m +# Through which pager should output be piped? (Unset this key for no pager.) +pager: less -FRX + # The paths at which cheatsheets are available. Tags associated with a cheatpath # are automatically attached to all cheatsheets residing on that path. # diff --git a/internal/config/config.go b/internal/config/config.go index 040f645..7ae0901 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -5,6 +5,7 @@ import ( "io/ioutil" "os" "path/filepath" + "strings" cp "github.com/cheat/cheat/internal/cheatpath" @@ -19,6 +20,7 @@ type Config struct { Cheatpaths []cp.Cheatpath `yaml:"cheatpaths"` Style string `yaml:"style"` Formatter string `yaml:"formatter"` + Pager string `yaml:"pager"` } // New returns a new Config struct @@ -111,5 +113,10 @@ func New(opts map[string]interface{}, confPath string, resolve bool) (Config, er conf.Formatter = "terminal16m" } + // if a pager was not provided, set a default + if strings.TrimSpace(conf.Pager) == "" { + conf.Pager = "" + } + return conf, nil } diff --git a/internal/display/display.go b/internal/display/display.go new file mode 100644 index 0000000..d0930cd --- /dev/null +++ b/internal/display/display.go @@ -0,0 +1,37 @@ +package display + +import ( + "fmt" + "os" + "os/exec" + "strings" + + "github.com/cheat/cheat/internal/config" +) + +// Display writes output either directly to stdout, or through a pager, +// depending upon configuration. +func Display(out string, conf config.Config) { + // if no pager was configured, print the output to stdout and exit + if conf.Pager == "" { + fmt.Print(out) + os.Exit(0) + } + + // otherwise, pipe output through the pager + parts := strings.Split(conf.Pager, " ") + pager := parts[0] + args := parts[1:] + + // run the pager + cmd := exec.Command(pager, args...) + cmd.Stdin = strings.NewReader(out) + cmd.Stdout = os.Stdout + + // handle errors + err := cmd.Run() + if err != nil { + fmt.Fprintln(os.Stderr, fmt.Sprintf("failed to write to pager: %v", err)) + os.Exit(1) + } +} From 49afd7c16b4b083ba7b16313b8a6d8981af65298 Mon Sep 17 00:00:00 2001 From: Chris Lane Date: Thu, 25 Jun 2020 18:38:03 -0400 Subject: [PATCH 2/3] feat: modify return codes Modify exit codes. `cheat` now returns an exit code value `2` on errors pertaining to a cheatsheet not being found. BREAKING CHANGE --- cmd/cheat/cmd_list.go | 4 ++-- cmd/cheat/cmd_remove.go | 4 ++-- cmd/cheat/cmd_search.go | 2 +- cmd/cheat/cmd_view.go | 2 +- doc/cheat.1 | 7 +++++++ doc/cheat.1.md | 9 +++++++++ 6 files changed, 22 insertions(+), 6 deletions(-) diff --git a/cmd/cheat/cmd_list.go b/cmd/cheat/cmd_list.go index 2e41a25..502fa80 100644 --- a/cmd/cheat/cmd_list.go +++ b/cmd/cheat/cmd_list.go @@ -81,9 +81,9 @@ func cmdList(opts map[string]interface{}, conf config.Config) { flattened = filtered } - // exit early if no cheatsheets are available + // return exit code 2 if no cheatsheets are available if len(flattened) == 0 { - os.Exit(0) + os.Exit(2) } // initialize a tabwriter to produce cleanly columnized output diff --git a/cmd/cheat/cmd_remove.go b/cmd/cheat/cmd_remove.go index caaa09a..072e525 100644 --- a/cmd/cheat/cmd_remove.go +++ b/cmd/cheat/cmd_remove.go @@ -37,8 +37,8 @@ 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)) - os.Exit(1) + fmt.Fprintln(os.Stderr, fmt.Sprintf("No cheatsheet found for '%s'.\n", cheatsheet)) + os.Exit(2) } // fail early if the sheet is read-only diff --git a/cmd/cheat/cmd_search.go b/cmd/cheat/cmd_search.go index f497831..3606b88 100644 --- a/cmd/cheat/cmd_search.go +++ b/cmd/cheat/cmd_search.go @@ -46,7 +46,7 @@ func cmdSearch(opts map[string]interface{}, conf config.Config) { s, ok := consolidated[cheatsheet] if !ok { fmt.Printf("No cheatsheet found for '%s'.\n", cheatsheet) - os.Exit(0) + os.Exit(2) } consolidated = map[string]sheet.Sheet{ diff --git a/cmd/cheat/cmd_view.go b/cmd/cheat/cmd_view.go index 3e0aade..2f4c550 100644 --- a/cmd/cheat/cmd_view.go +++ b/cmd/cheat/cmd_view.go @@ -39,7 +39,7 @@ func cmdView(opts map[string]interface{}, conf config.Config) { sheet, ok := consolidated[cheatsheet] if !ok { fmt.Printf("No cheatsheet found for '%s'.\n", cheatsheet) - os.Exit(0) + os.Exit(2) } // apply colorization if requested diff --git a/doc/cheat.1 b/doc/cheat.1 index 5b2c932..cc19064 100644 --- a/doc/cheat.1 +++ b/doc/cheat.1 @@ -203,6 +203,13 @@ If set, autocompletion scripts will attempt to integrate with \f[B]fzf\f[]. .RS .RE +.SH RETURN VALUES +.IP "0." 3 +Successful termination +.IP "1." 3 +Application error +.IP "2." 3 +Cheatsheet(s) not found .SH BUGS .PP See GitHub issues: diff --git a/doc/cheat.1.md b/doc/cheat.1.md index f587195..eb9c1ae 100644 --- a/doc/cheat.1.md +++ b/doc/cheat.1.md @@ -163,6 +163,15 @@ set, all other config paths will be ignored. : If set, autocompletion scripts will attempt to integrate with **fzf**. +RETURN VALUES +============= + +0. Successful termination + +1. Application error + +2. Cheatsheet(s) not found + BUGS ==== From fa5eb44be8d50c394cacc258084f70cbafdba37a Mon Sep 17 00:00:00 2001 From: Chris Lane Date: Thu, 25 Jun 2020 18:53:27 -0400 Subject: [PATCH 3/3] chore: bump version to 4.0.0 --- cmd/cheat/main.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/cheat/main.go b/cmd/cheat/main.go index f9c724e..966b7ac 100755 --- a/cmd/cheat/main.go +++ b/cmd/cheat/main.go @@ -17,7 +17,7 @@ import ( "github.com/cheat/cheat/internal/installer" ) -const version = "3.10.1" +const version = "4.0.0" func main() {