about summary refs log tree commit diff
path: root/pkgs/profpatsch/nman/nman.go
blob: b62479a00168911a14df360d941020cb3e2b0b17 (plain) (blame)
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)

}