1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
|
package main
import (
"fmt"
"os"
"strconv"
"log"
"bytes"
"os/exec"
"io/ioutil"
"syscall"
)
var usage = `nman drvAttr [section|page] [page]
Open man pages in a temporary nix-shell.
1 If one argument is given, the drvAttr & page have the same name.
2 If two arguments are given and the second arg is
a <number>) like 1, but in the man section <number>
a <page> ) like 1, but open the page <page>
3 If three arguments are given, the order is <drvAttr> <sect> <page>
`
func lookupManPage(manpath string, manSection int64, manPage string) (int, error) {
if manSection < -1 {
panic("manSection must not be < -1")
}
// holy debug printf
// fmt.Printf("attr: %s, sect: %d, page: %s\n", drvAttr, manSection, manPage)
manBin, err := exec.LookPath("man");
if err != nil {
return 0, fmt.Errorf("man executable not in PATH")
}
var manArgs []string
if (manSection == -1) {
manArgs = []string{manPage}
} else {
manArgs = []string{strconv.FormatInt(manSection, 10), manPage}
}
man := exec.Command(manBin, manArgs...)
man.Env = append(
os.Environ(),
fmt.Sprintf("MANPATH=%s", manpath))
man.Stderr = os.Stderr
man.Stdout = os.Stdout
err = man.Run()
if exiterr, ok := err.(*exec.ExitError); ok {
ws := exiterr.Sys().(syscall.WaitStatus)
return ws.ExitStatus(), nil
} else {
return 0, err
}
}
func buildManOutput(drvAttr string) (string, error) {
nixExpr := fmt.Sprintf(
`with import <nixpkgs> {}; %s.man or %s.doc or %s.out or %s`,
drvAttr, drvAttr, drvAttr, drvAttr)
nixBuild := exec.Command("nix-build", "--no-out-link", "-E", nixExpr)
nixBuild.Stderr = os.Stderr
pipe, err := nixBuild.StdoutPipe()
if err != nil { return "", fmt.Errorf("could not access stdout of nix-build: %s", err) }
err = nixBuild.Start()
if err != nil { return "", fmt.Errorf("could not start nix-build: %s", err) }
out, err := ioutil.ReadAll(pipe)
if err != nil { return "", fmt.Errorf("could not read from nix-build: %s", err) }
lines := bytes.Split(bytes.TrimSpace(out), []byte("\n"))
err = nixBuild.Wait()
if err != nil { return "", fmt.Errorf("nix-build process died: %s", err) }
// last line ouf output looks like: /nix/store/abc…-foobar.drv!output
drvPath := bytes.Split(lines[len(lines)-1], []byte("!"))[0]
if _, err := os.Stat(string(drvPath)); err != nil {
return "", fmt.Errorf("%s doesn’t look like an output path: %s", drvPath, err)
}
return string(drvPath), nil
}
func main() {
var manPage string
var drvAttr string
args := os.Args
// man section or -1 if no man section
var manSection int64 = -1
if (len(args) >= 3) {
i, err := strconv.ParseUint(args[2], 10, 64)
if err == nil && i >= 0 {
manSection = int64(i)
}
}
// the first argument is always a derivation attribute
switch len(args) {
case 2:
// arg is package and drv attr
manPage = args[1]
case 3:
if (manSection == -1) {
// like 2, but arg 2 is package
manPage = args[2]
} else {
// man section given, page is arg 1
manPage = args[1]
}
case 4:
// arg 2 is manSection, arg 3 is package
manPage = args[3]
default:
fmt.Print(usage)
os.Exit(-1)
}
drvAttr = args[1]
drvPath, err := buildManOutput(drvAttr)
if err != nil {
log.Fatal(err)
}
exitStatus, err := lookupManPage(drvPath + "/share/man", manSection, manPage)
if err != nil {
log.Fatal(err)
}
os.Exit(exitStatus)
}
|