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}