mango.go

  1package mango
  2
  3import (
  4	"errors"
  5	"fmt"
  6	"sort"
  7	"strings"
  8	"time"
  9)
 10
 11// ManPage represents a man page generator.
 12type ManPage struct {
 13	Root            Command
 14	sections        []Section
 15	section         uint
 16	description     string
 17	longDescription string
 18}
 19
 20// Command represents a command.
 21type Command struct {
 22	Name     string
 23	Short    string
 24	Usage    string
 25	Flags    map[string]Flag
 26	Commands map[string]*Command
 27}
 28
 29// Flag represents a flag.
 30type Flag struct {
 31	Name  string
 32	Short string
 33	Usage string
 34	PFlag bool
 35}
 36
 37// Section represents a section.
 38type Section struct {
 39	Name string
 40	Text string
 41}
 42
 43// NewManPage returns a new ManPage generator instance.
 44func NewManPage(section uint, title string, description string) *ManPage {
 45	root := NewCommand(title, "", "")
 46	return &ManPage{
 47		Root:        *root,
 48		section:     section,
 49		description: description,
 50	}
 51}
 52
 53// NewCommand returns a new Command.
 54func NewCommand(name string, short string, usage string) *Command {
 55	return &Command{
 56		Name:     name,
 57		Short:    short,
 58		Usage:    usage,
 59		Flags:    make(map[string]Flag),
 60		Commands: make(map[string]*Command),
 61	}
 62}
 63
 64// WithLongDescription sets the long description.
 65func (m *ManPage) WithLongDescription(desc string) *ManPage {
 66	m.longDescription = desc
 67	return m
 68}
 69
 70// WithSection adds a section to the man page.
 71func (m *ManPage) WithSection(section string, text string) *ManPage {
 72	m.sections = append(m.sections, Section{
 73		Name: section,
 74		Text: text,
 75	})
 76	return m
 77}
 78
 79// AddFlag adds a flag to the command.
 80func (m *Command) AddFlag(f Flag) error {
 81	if _, found := m.Flags[f.Name]; found {
 82		return errors.New("duplicate flag: " + f.Name)
 83	}
 84
 85	m.Flags[f.Name] = f
 86	return nil
 87}
 88
 89// AddCommand adds a sub-command.
 90func (m *Command) AddCommand(c *Command) error {
 91	if _, found := m.Commands[c.Name]; found {
 92		return errors.New("duplicate command: " + c.Name)
 93	}
 94
 95	m.Commands[c.Name] = c
 96	return nil
 97}
 98
 99func (m ManPage) buildCommand(w Builder, c Command) {
100	if len(c.Flags) > 0 {
101		if c.Name == m.Root.Name {
102			w.Section("Options")
103			w.TaggedParagraph(-1)
104		} else {
105			w.TaggedParagraph(-1)
106			w.TextBold("OPTIONS")
107			w.Indent(4)
108		}
109		keys := make([]string, 0, len(c.Flags))
110		for k := range c.Flags {
111			keys = append(keys, k)
112		}
113		sort.Strings(keys)
114
115		for i, k := range keys {
116			opt := c.Flags[k]
117			if i > 0 {
118				w.TaggedParagraph(-1)
119			}
120
121			prefix := "-"
122			if opt.PFlag {
123				prefix = "--"
124			}
125
126			if opt.Short != "" {
127				w.TextBold(fmt.Sprintf("%[1]s%[2]s %[1]s%[3]s", prefix, opt.Short, opt.Name))
128			} else {
129				w.TextBold(prefix + opt.Name)
130			}
131			w.EndSection()
132
133			w.Text(strings.ReplaceAll(opt.Usage, "\n", " "))
134		}
135
136		if c.Name == m.Root.Name {
137		} else {
138			w.IndentEnd()
139		}
140	}
141
142	if len(c.Commands) > 0 {
143		if c.Name == m.Root.Name {
144			w.Section("Commands")
145			w.TaggedParagraph(-1)
146		} else {
147			w.TaggedParagraph(-1)
148			w.TextBold("COMMANDS")
149			w.Indent(4)
150		}
151		keys := make([]string, 0, len(c.Commands))
152		for k := range c.Commands {
153			keys = append(keys, k)
154		}
155		sort.Strings(keys)
156
157		for i, k := range keys {
158			opt := c.Commands[k]
159			if i > 0 {
160				w.TaggedParagraph(-1)
161			}
162
163			w.TextBold(opt.Name)
164			if opt.Usage != "" {
165				w.Text(strings.TrimPrefix(opt.Usage, opt.Name))
166			}
167			w.Indent(4)
168			w.Text(strings.ReplaceAll(opt.Short, "\n", " "))
169			w.IndentEnd()
170
171			m.buildCommand(w, *opt)
172		}
173
174		if c.Name == m.Root.Name {
175		} else {
176			w.IndentEnd()
177		}
178	}
179}
180
181// Build generates the man page.
182func (m ManPage) Build(w Builder) string {
183	/*
184	   Common order:
185	   - name
186	   - synopsis
187	   - description
188	   - options
189	   - exit status
190	   - usage
191	   - notes
192	   - authors
193	   - copyright
194	   - see also
195	*/
196
197	w.Heading(m.section, m.Root.Name, m.description, time.Now())
198
199	w.Section("Name")
200	w.Text(m.Root.Name + " - " + m.description)
201
202	w.Section("Synopsis")
203	w.TextBold(m.Root.Name)
204	w.Text(" [")
205	w.TextItalic("options...")
206	w.Text("] [")
207	w.TextItalic("argument...")
208	w.Text("]")
209
210	w.Section("Description")
211	w.Text(m.longDescription)
212
213	m.buildCommand(w, m.Root)
214
215	for _, v := range m.sections {
216		w.Section(v.Name)
217		w.Text(v.Text)
218	}
219
220	return w.String()
221}