master xplshn/aruu / scripts / mkman / main.go
  1package main
  2
  3import (
  4	"bufio"
  5	"flag"
  6	"fmt"
  7	"os"
  8	"path/filepath"
  9	"strings"
 10	"time"
 11)
 12
 13// parse config.mk variables and values
 14type Config map[string]string
 15
 16func parseConfigMk(path string) (Config, error) {
 17	f, err := os.Open(path)
 18	if err != nil {
 19		return nil, err
 20	}
 21	defer f.Close()
 22
 23	raw := make(Config)
 24	sc := bufio.NewScanner(f)
 25	for sc.Scan() {
 26		line := strings.TrimSpace(sc.Text())
 27		if line == "" || strings.HasPrefix(line, "#") {
 28			continue
 29		}
 30		eq := strings.IndexByte(line, '=')
 31		if eq < 0 {
 32			continue
 33		}
 34		k := strings.TrimSpace(line[:eq])
 35		v := strings.TrimSpace(line[eq+1:])
 36		raw[k] = v
 37	}
 38	if err := sc.Err(); err != nil {
 39		return nil, err
 40	}
 41
 42	cfg := make(Config, len(raw))
 43	for k, v := range raw {
 44		cfg[k] = expandVars(v, raw)
 45	}
 46	return cfg, nil
 47}
 48
 49func expandVars(s string, env Config) string {
 50	for {
 51		start := strings.Index(s, "$(")
 52		if start < 0 {
 53			break
 54		}
 55		end := strings.Index(s[start:], ")")
 56		if end < 0 {
 57			break
 58		}
 59		end += start
 60		varname := s[start+2 : end]
 61		replacement := ""
 62		if v, ok := env[varname]; ok {
 63			replacement = v
 64		}
 65		s = s[:start] + replacement + s[end+1:]
 66	}
 67	return s
 68}
 69
 70func (cfg Config) isEnabled(key string) bool {
 71	return cfg[key] == "1"
 72}
 73
 74// preprocessor condition stack for nested feature blocks
 75type ifFrame struct {
 76	active bool
 77	seen   bool
 78}
 79
 80type ifStack struct {
 81	frames []ifFrame
 82}
 83
 84func (s *ifStack) push(active, seen bool) {
 85	s.frames = append(s.frames, ifFrame{active: active, seen: seen})
 86}
 87
 88func (s *ifStack) pop() {
 89	if len(s.frames) > 0 {
 90		s.frames = s.frames[:len(s.frames)-1]
 91	}
 92}
 93
 94func (s *ifStack) parentActive() bool {
 95	for i := 0; i < len(s.frames)-1; i++ {
 96		if !s.frames[i].active {
 97			return false
 98		}
 99	}
100	return true
101}
102
103func (s *ifStack) globallyActive() bool {
104	for _, f := range s.frames {
105		if !f.active {
106			return false
107		}
108	}
109	return true
110}
111
112func (s *ifStack) top() *ifFrame {
113	if len(s.frames) == 0 {
114		return nil
115	}
116	return &s.frames[len(s.frames)-1]
117}
118
119// parse and evaluate preprocessor feature conditions
120func evalCondition(expr string, cfg Config) bool {
121	expr = strings.TrimSpace(expr)
122	if strings.HasPrefix(expr, "defined(") && strings.HasSuffix(expr, ")") {
123		key := expr[8 : len(expr)-1]
124		_, ok := cfg[key]
125		return ok
126	}
127	if strings.HasPrefix(expr, "!defined(") && strings.HasSuffix(expr, ")") {
128		key := expr[9 : len(expr)-1]
129		_, ok := cfg[key]
130		return !ok
131	}
132	if idx := strings.Index(expr, "=="); idx >= 0 {
133		lhs := strings.TrimSpace(expr[:idx])
134		rhs := strings.TrimSpace(expr[idx+2:])
135		return cfg[lhs] == rhs
136	}
137	if idx := strings.Index(expr, "!="); idx >= 0 {
138		lhs := strings.TrimSpace(expr[:idx])
139		rhs := strings.TrimSpace(expr[idx+2:])
140		return cfg[lhs] != rhs
141	}
142	if strings.HasPrefix(expr, "!") {
143		key := strings.TrimSpace(expr[1:])
144		return cfg[key] != "1"
145	}
146	return cfg[expr] == "1"
147}
148
149// section inference from file path
150func inferSection(path string) int {
151	clean := filepath.ToSlash(path)
152	switch {
153	case strings.Contains(clean, "/linux/"),
154		strings.Contains(clean, "/net/"),
155		strings.Contains(clean, "/xsi/"):
156		return 8
157	default:
158		return 1
159	}
160}
161
162// a Renderer turns a parsed Page into one output format on os.Stdout
163type Renderer interface {
164	Render() error
165}
166
167func newRenderer(format string, page *Page) (Renderer, error) {
168	switch strings.ToLower(format) {
169	case "", "mdoc":
170		return NewMdocRenderer(page, os.Stdout), nil
171	case "txt":
172		return NewTxtRenderer(page, os.Stdout), nil
173	default:
174		return nil, fmt.Errorf("unknown format %q (want mdoc or txt)", format)
175	}
176}
177
178// main entry point and flags definition
179func main() {
180	configPath := flag.String("config", "config.mk", "path to config.mk")
181	sectionFlag := flag.Int("section", 0, "man section override (0 = infer from path)")
182	dateFlag := flag.String("date", "", "date string for TH line (default: current month/year)")
183	formatFlag := flag.String("fmt", "mdoc", "output format: mdoc or txt")
184	flag.Parse()
185
186	if flag.NArg() < 1 {
187		fmt.Fprintf(os.Stderr, "usage: mkman [-config config.mk] [-section N] [-fmt mdoc|txt] file.c\n")
188		os.Exit(1)
189	}
190	cfile := flag.Arg(0)
191
192	cfg, err := parseConfigMk(*configPath)
193	if err != nil {
194		fmt.Fprintf(os.Stderr, "mkman: config: %v\n", err)
195		os.Exit(1)
196	}
197
198	section := *sectionFlag
199	if section == 0 {
200		section = inferSection(cfile)
201	}
202
203	date := *dateFlag
204	if date == "" {
205		date = time.Now().Format("January 2, 2006")
206	}
207
208	page, err := ParsePage(cfile, cfg, section, date)
209	if err != nil {
210		fmt.Fprintf(os.Stderr, "mkman: scan: %v\n", err)
211		os.Exit(1)
212	}
213
214	if page == nil {
215		fmt.Fprintf(os.Stderr, "mkman: %s: no ?man comments found\n", cfile)
216		os.Exit(0)
217	}
218
219	r, err := newRenderer(*formatFlag, page)
220	if err != nil {
221		fmt.Fprintf(os.Stderr, "mkman: %v\n", err)
222		os.Exit(1)
223	}
224	if err := r.Render(); err != nil {
225		fmt.Fprintf(os.Stderr, "mkman: render: %v\n", err)
226		os.Exit(1)
227	}
228}