PageRenderTime 34ms CodeModel.GetById 1ms app.highlight 27ms RepoModel.GetById 2ms app.codeStats 0ms

/man.go

https://code.google.com/p/mango-doc/
Go | 310 lines | 282 code | 19 blank | 9 comment | 72 complexity | 344f6628416776b74d9933c845123ba4 MD5 | raw file
  1package main
  2
  3import (
  4	"bytes"
  5	"fmt"
  6	"go/ast"
  7	"go/doc"
  8	"go/token"
  9	"sort"
 10	"strconv"
 11	"strings"
 12	"time"
 13)
 14
 15//BUG(jmf): Quotation marks are all wrong in postscript output.
 16
 17func ovr_map(in []*section) map[string][]interface{} {
 18	out := map[string][]interface{}{}
 19	for _, s := range in {
 20		out[s.name] = s.paras
 21	}
 22	return out
 23}
 24
 25type M struct {
 26	*F
 27	name, version, sec   string
 28	descr                []byte //short description
 29	sections, overd, end []*section
 30	overm                map[string][]interface{}
 31	refs                 []string
 32	pkg                  *ast.Package
 33	docs                 *doc.Package
 34}
 35
 36func NewManPage(pkg *ast.Package, docs *doc.Package, overd []*section) *M {
 37	//break up the package document, extract a short description
 38	dvec := unstring([]byte(docs.Doc))
 39	var fs []byte //first sentence.
 40	if dvec != nil && len(dvec) > 0 {
 41		if p, ok := dvec[0].([][]byte); ok && len(p) > 0 {
 42			fs = p[0]
 43			//if the first paragraph is one sentence, only use it in description
 44			//otherwise we leave it where it is to repeat.
 45			if len(p) == 1 {
 46				dvec = dvec[1:]
 47			}
 48		}
 49	}
 50	m := &M{
 51		F:        Formatter(),
 52		name:     *name,
 53		version:  grep_version(pkg),
 54		descr:    fs,
 55		sections: sections(dvec),
 56		overd:    overd,
 57		overm:    ovr_map(overd),
 58		pkg:      pkg,
 59		docs:     docs,
 60	}
 61	h := -1
 62	for i, sec := range m.sections {
 63		if sec.name == "HISTORY" {
 64			h = i
 65			break
 66		}
 67	}
 68	if hs := get_section(m, "HISTORY", h); hs != nil {
 69		m.end = []*section{&section{"HISTORY", hs}}
 70	}
 71	m.WriteString(".\\\"    Automatically generated by mango(1)")
 72	return m
 73}
 74
 75func lit(x interface{}) []byte {
 76	if b, ok := x.(*ast.BasicLit); ok {
 77		v := b.Value
 78		switch b.Kind {
 79		case token.CHAR, token.STRING:
 80			v, _ = strconv.Unquote(v)
 81		}
 82		return []byte(v)
 83	}
 84	return nil
 85}
 86
 87func grep_version(pkg *ast.Package) string {
 88	if *version != "" {
 89		return *version
 90	}
 91	for _, file := range pkg.Files {
 92		for _, decl := range file.Decls {
 93			if g, ok := decl.(*ast.GenDecl); ok {
 94				if g.Tok == token.CONST || g.Tok == token.VAR {
 95					for _, s := range g.Specs {
 96						if v, ok := s.(*ast.ValueSpec); ok {
 97							for i, n := range v.Names {
 98								if n.Name == "Version" {
 99									t := v.Values[i]
100									if b, ok := t.(*ast.BasicLit); ok {
101										return string(lit(b))
102									}
103								}
104							}
105						}
106					}
107				}
108			}
109		}
110	}
111	return ""
112}
113
114func flatten(docs *doc.Package, extras []string) <-chan string {
115	out := make(chan string)
116	var sub func(interface{})
117	sub = func(x interface{}) {
118		switch t := x.(type) {
119		case []*doc.Value:
120			for _, v := range t {
121				out <- v.Doc
122			}
123		case []*doc.Func:
124			for _, v := range t {
125				out <- v.Doc
126			}
127		case []*doc.Type:
128			for _, v := range t {
129				out <- v.Doc
130				sub(v.Consts)
131				sub(v.Vars)
132				sub(v.Funcs)
133				sub(v.Methods)
134			}
135		}
136	}
137	go func() {
138		for _, x := range extras {
139			out <- x
140		}
141		for _, bug := range docs.Bugs {
142			out <- bug
143		}
144		out <- docs.Doc
145		sub(docs.Consts)
146		sub(docs.Types)
147		sub(docs.Vars)
148		sub(docs.Funcs)
149		close(out)
150	}()
151	return out
152}
153
154func (m *M) find_refs(extras []string) {
155	var acc []string
156	seen := map[string]bool{}
157	seen[m.name+"("+m.sec+")"] = true //don't want recursive references
158	for str := range flatten(m.docs, extras) {
159		for _, word := range strings.Fields(str) {
160			if !refrx.MatchString(word) {
161				continue
162			}
163			switch word[strings.Index(word, "(")+1] { //check the part in the ()
164			case '1', '2', '3', '4', '5', '6', '7', '8', '9', '0',
165				'n', 'o', 'l', 'x', 'p':
166				//okay, even though most of these are unlikely
167				//and some deprecated
168			default:
169				//not a man page
170				continue
171			}
172			if !seen[word] {
173				seen[word] = true
174				acc = append(acc, word)
175			}
176		}
177	}
178	sort.Strings(acc)
179	m.refs = acc
180}
181
182func (m *M) do_header(kind string) {
183	tm := time.Now().Format("2006-01-02")
184	version := m.version
185	if version == "" {
186		version = tm
187	}
188	if *manual != "" {
189		kind = *manual
190	}
191	m.WriteString(
192		fmt.Sprintf("\n.TH \"%s\" %s \"%s\" \"version %s\" \"%s\"",
193			m.name,
194			m.sec,
195			tm,
196			version,
197			kind,
198		))
199}
200
201func (m *M) do_name() {
202	m.section("NAME")
203	m.WriteString(m.name)
204	s := bytes.TrimSpace(m.descr)
205	if len(s) > 0 {
206		m.WriteString(" \\- ")
207		m.Write(s) //first sentence
208	}
209}
210
211func get_section(m *M, nm string, i int) (ps []interface{}) {
212	ok := false
213	if ps, ok = m.overm[nm]; ok {
214		delete(m.overm, nm)
215	} else if i != -1 {
216		ps = m.sections[i].paras
217	}
218	//regardless of where it comes from, remove from m.sections given valid i
219	switch {
220	case i == -1:
221		return
222	case i == 0:
223		m.sections = m.sections[1:]
224	case i == len(m.sections)-1:
225		m.sections = m.sections[:len(m.sections)-1]
226	default:
227		copy(m.sections[i:], m.sections[i+1:])
228		m.sections = m.sections[:len(m.sections)-1]
229	}
230	return
231}
232
233func (m *M) do_description() {
234	i := -1
235	if len(m.sections) > 0 {
236		i = 0
237	}
238	ps := get_section(m, "", i)
239	if ps != nil && len(ps) > 0 {
240		m.section("DESCRIPTION")
241		m.paras(ps)
242	}
243}
244
245func (m *M) user_sections(sx ...string) {
246	for _, req := range sx {
247		for i, sc := range m.sections {
248			if sc.name != req {
249				i = -1
250			}
251			if ps := get_section(m, req, i); ps != nil {
252				m.section(req)
253				m.paras(ps)
254			}
255		}
256	}
257}
258
259func (m *M) remaining_user_sections() {
260	for _, sec := range m.sections {
261		m.section(sec.name)
262		m.paras(sec.paras)
263	}
264	//this is horrible but beats deleting the overd sections as we go
265	for _, s := range m.overd {
266		if _, ok := m.overm[s.name]; ok {
267			m.section(s.name)
268			m.paras(s.paras)
269		}
270	}
271}
272
273func (m *M) do_endmatter() {
274	for _, sec := range m.end {
275		m.section(sec.name)
276		m.paras(sec.paras)
277	}
278}
279
280func (m *M) do_bugs() {
281	bs := m.docs.Bugs
282	if len(bs) > 0 {
283		m.section("BUGS")
284		m.text(bytes.TrimSpace([]byte(bs[0])))
285		for _, b := range bs[1:] {
286			m.PP()
287			m.text(bytes.TrimSpace([]byte(b)))
288		}
289	}
290}
291
292func (m *M) _seealso1(s string) {
293	m.WriteString(".BR ")
294	piv := strings.Index(s, "(")
295	m.Write(escape([]byte(s[:piv])))
296	m.WriteByte(' ')
297	m.WriteString(s[piv:])
298}
299
300func (m *M) do_see_also() {
301	if len(m.refs) > 0 {
302		m.section("SEE ALSO")
303		last := len(m.refs) - 1
304		for _, s := range m.refs[:last] {
305			m._seealso1(s)
306			m.WriteString(",\n")
307		}
308		m._seealso1(m.refs[last])
309	}
310}