|
0 |
// The Pager package allows the program to easily pipe it's
|
|
1 |
// standard output through a Pager program
|
|
2 |
// (like how the man command does).
|
|
3 |
//
|
|
4 |
// Borrowed from: https://gist.github.com/dchapes/1d0c538ce07902b76c75 and
|
|
5 |
// reworked slightly.
|
|
6 |
|
|
7 |
package pager
|
|
8 |
|
|
9 |
import (
|
|
10 |
"errors"
|
|
11 |
"io"
|
|
12 |
"os"
|
|
13 |
"os/exec"
|
|
14 |
"path"
|
|
15 |
"strings"
|
|
16 |
)
|
|
17 |
|
|
18 |
type Pager struct {
|
|
19 |
cmd *exec.Cmd
|
|
20 |
file io.WriteCloser
|
|
21 |
}
|
|
22 |
|
|
23 |
var pager Pager
|
|
24 |
|
|
25 |
// The environment variables to check for the name of (and arguments to)
|
|
26 |
// the Pager to run.
|
|
27 |
var PagerEnvVariables = []string{"PAGER"}
|
|
28 |
|
|
29 |
// The command names in $PATH to look for if none of the environment
|
|
30 |
// variables are set.
|
|
31 |
// Cannot include arguments.
|
|
32 |
var PagerCommands = []string{"less", "more"}
|
|
33 |
|
|
34 |
func pagerExecPath() (pagerPath string, args []string, err error) {
|
|
35 |
for _, testVar := range PagerEnvVariables {
|
|
36 |
pagerPath = os.Getenv(testVar)
|
|
37 |
if pagerPath != "" {
|
|
38 |
args = strings.Fields(pagerPath)
|
|
39 |
if len(args) > 1 {
|
|
40 |
return args[0], args[1:], nil
|
|
41 |
}
|
|
42 |
}
|
|
43 |
}
|
|
44 |
|
|
45 |
// This default only gets used if PagerCommands is empty.
|
|
46 |
err = exec.ErrNotFound
|
|
47 |
for _, testPath := range PagerCommands {
|
|
48 |
pagerPath, err = exec.LookPath(testPath)
|
|
49 |
if err == nil {
|
|
50 |
switch {
|
|
51 |
case path.Base(pagerPath) == "less":
|
|
52 |
// TODO(seanc@): Make the buffer size conditional
|
|
53 |
args := []string{"-X", "-F", "-R", "--buffers=65535"}
|
|
54 |
return pagerPath, args, nil
|
|
55 |
default:
|
|
56 |
return pagerPath, nil, nil
|
|
57 |
}
|
|
58 |
}
|
|
59 |
}
|
|
60 |
return "", nil, err
|
|
61 |
}
|
|
62 |
|
|
63 |
// New returns a new io.WriteCloser connected to a Pager.
|
|
64 |
// The returned WriteCloser can be used as a replacement to os.Stdout,
|
|
65 |
// everything written to it is piped to a Pager.
|
|
66 |
// To determine what Pager to run, the environment variables listed
|
|
67 |
// in PagerEnvVariables are checked.
|
|
68 |
// If all are empty/unset then the commands listed in PagerCommands
|
|
69 |
// are looked for in $PATH.
|
|
70 |
func New() (*Pager, error) {
|
|
71 |
p := &Pager{}
|
|
72 |
if p.cmd != nil {
|
|
73 |
return nil, errors.New("Pager: already exists")
|
|
74 |
}
|
|
75 |
pagerPath, args, err := pagerExecPath()
|
|
76 |
if err != nil {
|
|
77 |
return nil, err
|
|
78 |
}
|
|
79 |
|
|
80 |
// If the Pager is less(1), set some useful options
|
|
81 |
switch {
|
|
82 |
case path.Base(pagerPath) == "less":
|
|
83 |
os.Setenv("LESSSECURE", "1")
|
|
84 |
}
|
|
85 |
|
|
86 |
p.cmd = exec.Command(pagerPath, args...)
|
|
87 |
p.cmd.Stdout = os.Stdout
|
|
88 |
p.cmd.Stderr = os.Stderr
|
|
89 |
w, err := p.cmd.StdinPipe()
|
|
90 |
if err != nil {
|
|
91 |
return nil, err
|
|
92 |
}
|
|
93 |
f, ok := w.(io.WriteCloser)
|
|
94 |
if !ok {
|
|
95 |
return nil, errors.New("Pager: exec.Command.StdinPipe did not return type io.WriteCloser")
|
|
96 |
}
|
|
97 |
p.file = f
|
|
98 |
err = p.cmd.Start()
|
|
99 |
if err != nil {
|
|
100 |
return nil, err
|
|
101 |
}
|
|
102 |
|
|
103 |
return p, nil
|
|
104 |
}
|
|
105 |
|
|
106 |
// Wait closes the pipe to the Pager setup with New() or Stdout() and waits
|
|
107 |
// for it to exit.
|
|
108 |
//
|
|
109 |
// This should normally be called before the program exists,
|
|
110 |
// typically via a defer call in main().
|
|
111 |
func (p *Pager) Wait() error {
|
|
112 |
if p.cmd == nil {
|
|
113 |
return nil
|
|
114 |
}
|
|
115 |
p.file.Close()
|
|
116 |
return p.cmd.Wait()
|
|
117 |
}
|
|
118 |
|
|
119 |
func (p *Pager) Close() error {
|
|
120 |
return nil
|
|
121 |
}
|
|
122 |
|
|
123 |
func (p *Pager) Write(data []byte) (n int, err error) {
|
|
124 |
return p.file.Write(data)
|
|
125 |
}
|