/man.go
https://code.google.com/p/mango-doc/ · Go · 310 lines · 282 code · 19 blank · 9 comment · 73 complexity · 344f6628416776b74d9933c845123ba4 MD5 · raw file
- package main
- import (
- "bytes"
- "fmt"
- "go/ast"
- "go/doc"
- "go/token"
- "sort"
- "strconv"
- "strings"
- "time"
- )
- //BUG(jmf): Quotation marks are all wrong in postscript output.
- func ovr_map(in []*section) map[string][]interface{} {
- out := map[string][]interface{}{}
- for _, s := range in {
- out[s.name] = s.paras
- }
- return out
- }
- type M struct {
- *F
- name, version, sec string
- descr []byte //short description
- sections, overd, end []*section
- overm map[string][]interface{}
- refs []string
- pkg *ast.Package
- docs *doc.Package
- }
- func NewManPage(pkg *ast.Package, docs *doc.Package, overd []*section) *M {
- //break up the package document, extract a short description
- dvec := unstring([]byte(docs.Doc))
- var fs []byte //first sentence.
- if dvec != nil && len(dvec) > 0 {
- if p, ok := dvec[0].([][]byte); ok && len(p) > 0 {
- fs = p[0]
- //if the first paragraph is one sentence, only use it in description
- //otherwise we leave it where it is to repeat.
- if len(p) == 1 {
- dvec = dvec[1:]
- }
- }
- }
- m := &M{
- F: Formatter(),
- name: *name,
- version: grep_version(pkg),
- descr: fs,
- sections: sections(dvec),
- overd: overd,
- overm: ovr_map(overd),
- pkg: pkg,
- docs: docs,
- }
- h := -1
- for i, sec := range m.sections {
- if sec.name == "HISTORY" {
- h = i
- break
- }
- }
- if hs := get_section(m, "HISTORY", h); hs != nil {
- m.end = []*section{§ion{"HISTORY", hs}}
- }
- m.WriteString(".\\\" Automatically generated by mango(1)")
- return m
- }
- func lit(x interface{}) []byte {
- if b, ok := x.(*ast.BasicLit); ok {
- v := b.Value
- switch b.Kind {
- case token.CHAR, token.STRING:
- v, _ = strconv.Unquote(v)
- }
- return []byte(v)
- }
- return nil
- }
- func grep_version(pkg *ast.Package) string {
- if *version != "" {
- return *version
- }
- for _, file := range pkg.Files {
- for _, decl := range file.Decls {
- if g, ok := decl.(*ast.GenDecl); ok {
- if g.Tok == token.CONST || g.Tok == token.VAR {
- for _, s := range g.Specs {
- if v, ok := s.(*ast.ValueSpec); ok {
- for i, n := range v.Names {
- if n.Name == "Version" {
- t := v.Values[i]
- if b, ok := t.(*ast.BasicLit); ok {
- return string(lit(b))
- }
- }
- }
- }
- }
- }
- }
- }
- }
- return ""
- }
- func flatten(docs *doc.Package, extras []string) <-chan string {
- out := make(chan string)
- var sub func(interface{})
- sub = func(x interface{}) {
- switch t := x.(type) {
- case []*doc.Value:
- for _, v := range t {
- out <- v.Doc
- }
- case []*doc.Func:
- for _, v := range t {
- out <- v.Doc
- }
- case []*doc.Type:
- for _, v := range t {
- out <- v.Doc
- sub(v.Consts)
- sub(v.Vars)
- sub(v.Funcs)
- sub(v.Methods)
- }
- }
- }
- go func() {
- for _, x := range extras {
- out <- x
- }
- for _, bug := range docs.Bugs {
- out <- bug
- }
- out <- docs.Doc
- sub(docs.Consts)
- sub(docs.Types)
- sub(docs.Vars)
- sub(docs.Funcs)
- close(out)
- }()
- return out
- }
- func (m *M) find_refs(extras []string) {
- var acc []string
- seen := map[string]bool{}
- seen[m.name+"("+m.sec+")"] = true //don't want recursive references
- for str := range flatten(m.docs, extras) {
- for _, word := range strings.Fields(str) {
- if !refrx.MatchString(word) {
- continue
- }
- switch word[strings.Index(word, "(")+1] { //check the part in the ()
- case '1', '2', '3', '4', '5', '6', '7', '8', '9', '0',
- 'n', 'o', 'l', 'x', 'p':
- //okay, even though most of these are unlikely
- //and some deprecated
- default:
- //not a man page
- continue
- }
- if !seen[word] {
- seen[word] = true
- acc = append(acc, word)
- }
- }
- }
- sort.Strings(acc)
- m.refs = acc
- }
- func (m *M) do_header(kind string) {
- tm := time.Now().Format("2006-01-02")
- version := m.version
- if version == "" {
- version = tm
- }
- if *manual != "" {
- kind = *manual
- }
- m.WriteString(
- fmt.Sprintf("\n.TH \"%s\" %s \"%s\" \"version %s\" \"%s\"",
- m.name,
- m.sec,
- tm,
- version,
- kind,
- ))
- }
- func (m *M) do_name() {
- m.section("NAME")
- m.WriteString(m.name)
- s := bytes.TrimSpace(m.descr)
- if len(s) > 0 {
- m.WriteString(" \\- ")
- m.Write(s) //first sentence
- }
- }
- func get_section(m *M, nm string, i int) (ps []interface{}) {
- ok := false
- if ps, ok = m.overm[nm]; ok {
- delete(m.overm, nm)
- } else if i != -1 {
- ps = m.sections[i].paras
- }
- //regardless of where it comes from, remove from m.sections given valid i
- switch {
- case i == -1:
- return
- case i == 0:
- m.sections = m.sections[1:]
- case i == len(m.sections)-1:
- m.sections = m.sections[:len(m.sections)-1]
- default:
- copy(m.sections[i:], m.sections[i+1:])
- m.sections = m.sections[:len(m.sections)-1]
- }
- return
- }
- func (m *M) do_description() {
- i := -1
- if len(m.sections) > 0 {
- i = 0
- }
- ps := get_section(m, "", i)
- if ps != nil && len(ps) > 0 {
- m.section("DESCRIPTION")
- m.paras(ps)
- }
- }
- func (m *M) user_sections(sx ...string) {
- for _, req := range sx {
- for i, sc := range m.sections {
- if sc.name != req {
- i = -1
- }
- if ps := get_section(m, req, i); ps != nil {
- m.section(req)
- m.paras(ps)
- }
- }
- }
- }
- func (m *M) remaining_user_sections() {
- for _, sec := range m.sections {
- m.section(sec.name)
- m.paras(sec.paras)
- }
- //this is horrible but beats deleting the overd sections as we go
- for _, s := range m.overd {
- if _, ok := m.overm[s.name]; ok {
- m.section(s.name)
- m.paras(s.paras)
- }
- }
- }
- func (m *M) do_endmatter() {
- for _, sec := range m.end {
- m.section(sec.name)
- m.paras(sec.paras)
- }
- }
- func (m *M) do_bugs() {
- bs := m.docs.Bugs
- if len(bs) > 0 {
- m.section("BUGS")
- m.text(bytes.TrimSpace([]byte(bs[0])))
- for _, b := range bs[1:] {
- m.PP()
- m.text(bytes.TrimSpace([]byte(b)))
- }
- }
- }
- func (m *M) _seealso1(s string) {
- m.WriteString(".BR ")
- piv := strings.Index(s, "(")
- m.Write(escape([]byte(s[:piv])))
- m.WriteByte(' ')
- m.WriteString(s[piv:])
- }
- func (m *M) do_see_also() {
- if len(m.refs) > 0 {
- m.section("SEE ALSO")
- last := len(m.refs) - 1
- for _, s := range m.refs[:last] {
- m._seealso1(s)
- m.WriteString(",\n")
- }
- m._seealso1(m.refs[last])
- }
- }