Codebase list golang-github-sean--pager / 6be7e00f-b1d8-493b-971a-f92b7145cc8a/main pager.go
6be7e00f-b1d8-493b-971a-f92b7145cc8a/main

Tree @6be7e00f-b1d8-493b-971a-f92b7145cc8a/main (Download .tar.gz)

pager.go @6be7e00f-b1d8-493b-971a-f92b7145cc8a/mainraw · history · blame

// The Pager package allows the program to easily pipe it's
// standard output through a Pager program
// (like how the man command does).
//
// Borrowed from: https://gist.github.com/dchapes/1d0c538ce07902b76c75 and
// reworked slightly.

package pager

import (
	"errors"
	"io"
	"os"
	"os/exec"
	"path"
	"strings"
)

type Pager struct {
	cmd  *exec.Cmd
	file io.WriteCloser
}

var pager Pager

// 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() (pagerPath string, args []string, err error) {
	for _, testVar := range PagerEnvVariables {
		pagerPath = os.Getenv(testVar)
		if pagerPath != "" {
			args = strings.Fields(pagerPath)
			if len(args) > 1 {
				return args[0], args[1:], nil
			}
		}
	}

	// This default only gets used if PagerCommands is empty.
	err = exec.ErrNotFound
	for _, testPath := range PagerCommands {
		pagerPath, err = exec.LookPath(testPath)
		if err == nil {
			switch {
			case path.Base(pagerPath) == "less":
				// TODO(seanc@): Make the buffer size conditional
				args := []string{"-X", "-F", "-R", "--buffers=65535"}
				return pagerPath, args, nil
			default:
				return pagerPath, nil, nil
			}
		}
	}
	return "", nil, err
}

// New returns a new io.WriteCloser connected to a Pager.
// The returned WriteCloser 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() (*Pager, error) {
	p := &Pager{}
	if p.cmd != nil {
		return nil, errors.New("Pager: already exists")
	}
	pagerPath, args, err := pagerExecPath()
	if err != nil {
		return nil, err
	}

	// If the Pager is less(1), set some useful options
	switch {
	case path.Base(pagerPath) == "less":
		os.Setenv("LESSSECURE", "1")
	}

	p.cmd = exec.Command(pagerPath, args...)
	p.cmd.Stdout = os.Stdout
	p.cmd.Stderr = os.Stderr
	w, err := p.cmd.StdinPipe()
	if err != nil {
		return nil, err
	}
	f, ok := w.(io.WriteCloser)
	if !ok {
		return nil, errors.New("Pager: exec.Command.StdinPipe did not return type io.WriteCloser")
	}
	p.file = f
	err = p.cmd.Start()
	if err != nil {
		return nil, err
	}

	return p, nil
}

// 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 (p *Pager) Wait() error {
	if p.cmd == nil {
		return nil
	}
	p.file.Close()
	return p.cmd.Wait()
}

func (p *Pager) Close() error {
	return nil
}

func (p *Pager) Write(data []byte) (n int, err error) {
	return p.file.Write(data)
}