From 59d5c96c248cb8fdeadb52e2daee8dd222547ace Mon Sep 17 00:00:00 2001 From: Chris Lane Date: Thu, 25 Jun 2020 18:21:51 -0400 Subject: [PATCH] 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) + } +}