Skip to content

Instantly share code, notes, and snippets.

@dchapes

dchapes/pager.go Secret

Created February 24, 2015 22:52
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save dchapes/1d0c538ce07902b76c75 to your computer and use it in GitHub Desktop.
Save dchapes/1d0c538ce07902b76c75 to your computer and use it in GitHub Desktop.
Simple pager Go package
// The pager package allows the program to easily pipe it's
// standard output through a pager program
// (like how the man command does).
package pager
import (
"errors"
"os"
"os/exec"
"strings"
)
var pager struct {
cmd *exec.Cmd
file *os.File
}
// The environment variables to check for the name of (and arguments to)
// the pager to run.
var PagerEnvVariables = []string{"PAGER"}
// The command names in $PATH to look for if none of the environment
// variables are set.
// Cannot include arguments.
var PagerCommands = []string{"less", "more"}
func pagerExecPath() (path string, args []string, err error) {
for _, testVar := range PagerEnvVariables {
path = os.Getenv(testVar)
if path != "" {
// BUG: does not handle multiple spaces, e.g.: "less -s -R"
args = strings.Split(path, " ")
return args[0], args[1:], nil
}
}
// This default only gets used if PagerCommands is empty.
err = exec.ErrNotFound
for _, testPath := range PagerCommands {
path, err = exec.LookPath(testPath)
if err == nil {
return path, nil, nil
}
}
return "", nil, err
}
// New returns a new os.File connected to a pager.
// The returned file can be used as a replacement to os.Stdout,
// everything written to it is piped to a pager.
// To determine what pager to run, the environment variables listed
// in PagerEnvVariables are checked.
// If all are empty/unset then the commands listed in PagerCommands
// are looked for in $PATH.
func New() (*os.File, error) {
if pager.cmd != nil {
return nil, errors.New("pager: already exists")
}
path, args, err := pagerExecPath()
if err != nil {
return nil, err
}
pager.cmd = exec.Command(path, args...)
pager.cmd.Stdout = os.Stdout
pager.cmd.Stderr = os.Stderr
w, err := pager.cmd.StdinPipe()
if err != nil {
return nil, err
}
f, ok := w.(*os.File)
if !ok {
return nil, errors.New("pager: exec.Command.StdinPipe did not return type *os.File")
}
pager.file = f
err = pager.cmd.Start()
if err != nil {
return nil, err
}
return pager.file, nil
}
// Stdout sets the global variable os.Stdout to the result of New()
// and returns the old os.Stdout value.
func Stdout() (oldStdout *os.File, err error) {
oldStdout = os.Stdout
output, err := New()
if err != nil {
return
}
os.Stdout = output
return
}
// Wait closes the pipe to the pager setup with New() or Stdout() and waits
// for it to exit.
//
// This should normally be called before the program exists,
// typically via a defer call in main().
func Wait() {
if pager.cmd == nil {
return
}
pager.file.Close()
pager.cmd.Wait()
}
@sean-
Copy link

sean- commented Dec 14, 2017

PSA: On line 32 replace strings.Split() with strings.Fields() to fix the mentioned bug.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment