-
Notifications
You must be signed in to change notification settings - Fork 3
Expand file tree
/
Copy pathsection.go
More file actions
193 lines (165 loc) · 5.75 KB
/
section.go
File metadata and controls
193 lines (165 loc) · 5.75 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
package warg
import (
"log"
"sort"
)
// SectionOpt customizes a Section on creation
type SectionOpt func(*Section)
// NewSection creates a standalone [Section]. All section options are in the [go.bbkane.com/warg/section] package
func NewSection(helpShort string, opts ...SectionOpt) Section {
section := Section{
HelpShort: helpShort,
Sections: make(SectionMap),
Cmds: make(CmdMap),
HelpLong: "",
Footer: "",
}
for _, opt := range opts {
opt(§ion)
}
return section
}
// SubSection adds an existing SubSection as a child of this SubSection. Panics if a SubSection with the same name already exists
func SubSection(name string, value Section) SectionOpt {
return func(app *Section) {
if _, alreadyThere := app.Sections[name]; !alreadyThere {
app.Sections[name] = value
} else {
log.Panicf("section already exists: %#v\n", name)
}
}
}
// SubSectionMap adds existing Sections as a child of this Section. Panics if a Section with the same name already exists
func SubSectionMap(sections SectionMap) SectionOpt {
return func(app *Section) {
for name, value := range sections {
SubSection(name, value)(app)
}
}
}
// SubCmd adds an existing SubCmd as a child of this Section. Panics if a SubCmd with the same name already exists
func SubCmd(name string, value Cmd) SectionOpt {
return func(app *Section) {
if _, alreadyThere := app.Cmds[name]; !alreadyThere {
app.Cmds[name] = value
} else {
log.Panicf("command already exists: %#v\n", name)
}
}
}
// SubCmdMap adds existing Commands as a child of this Section. Panics if a Command with the same name already exists
func SubCmdMap(commands CmdMap) SectionOpt {
return func(app *Section) {
for name, value := range commands {
SubCmd(name, value)(app)
}
}
}
// NewSubSection creates a new Section as a child of this Section. Panics if a NewSubSection with the same name already exists
func NewSubSection(name string, helpShort string, opts ...SectionOpt) SectionOpt {
return SubSection(name, NewSection(helpShort, opts...))
}
// NewSubCmd creates a new Command as a child of this Section. Panics if a NewSubCmd with the same name already exists
func NewSubCmd(name string, helpShort string, action Action, opts ...CmdOpt) SectionOpt {
return SubCmd(name, NewCmd(helpShort, action, opts...))
}
// SectionFooter adds an optional help string to this Section
func SectionFooter(footer string) SectionOpt {
return func(cat *Section) {
cat.Footer = footer
}
}
// SectionHelpLong adds an optional help string to this Section
func SectionHelpLong(helpLong string) SectionOpt {
return func(cat *Section) {
cat.HelpLong = helpLong
}
}
// SectionMap holds Sections - used by other Sections
type SectionMap map[string]Section
func (fm SectionMap) Empty() bool {
return len(fm) == 0
}
func (fm SectionMap) SortedNames() []string {
keys := make([]string, 0, len(fm))
for k := range fm {
keys = append(keys, k)
}
sort.Slice(keys, func(i, j int) bool {
return string(keys[i]) < string(keys[j])
})
return keys
}
// Sections are like "folders" for Commmands.
// They should usually have noun names.
// Sections should not be be created directly, but with the APIs in [go.bbkane.com/warg/section].
type Section struct {
// Cmds holds the Cmds under this Section
Cmds CmdMap
// Sections holds the Sections under this Section
Sections SectionMap
// HelpShort is a required one-line descripiton of this section
HelpShort string
// HelpLong is an optional longer description of this section
HelpLong string
// Footer is yet another optional longer description.
Footer string
}
// flatSection represents a section and relevant parent information
type flatSection struct {
// Path to this section
Path []string
// Sec is this section
Sec Section
}
// Breadthfirst returns a SectionIterator that yields sections sorted alphabetically breadth-first by path.
// Yielded sections should never be modified - they can share references to the same inherited flags
// SectionIterator's Next() method panics if two sections in the path have flags with the same name.
// Breadthfirst is used by app.Validate and help.AllCommandCommandHelp/help.AllCommandSectionHelp
func (sec *Section) breadthFirst(path []string) sectionIterator {
queue := make([]flatSection, 0, 1)
queue = append(queue, flatSection{
Path: path,
Sec: *sec,
})
return sectionIterator{
queue: queue,
}
}
// depthFirstSections returns all sections in depth-first pre-order: the current section first,
// then each child section (sorted alphabetically) and its descendants recursively.
// This ensures a section's own commands appear before its siblings' commands in help output.
// See https://github.com/bbkane/warg/issues/74
func depthFirstSections(sec Section, path []string) []flatSection {
result := []flatSection{{Path: path, Sec: sec}}
for _, childName := range sec.Sections.SortedNames() {
childPath := append(append([]string(nil), path...), childName)
result = append(result, depthFirstSections(sec.Sections[childName], childPath)...)
}
return result
}
// sectionIterator is used in BreadthFirst. See BreadthFirst docs
type sectionIterator struct {
queue []flatSection
}
// HasNext is used in BreadthFirst. See BreadthFirst docs
func (s *sectionIterator) Next() flatSection {
current := s.queue[0]
s.queue = s.queue[1:]
// Add child sections to queue
for _, childName := range current.Sec.Sections.SortedNames() {
// child.Path = current.Path + child.name
childPath := make([]string, len(current.Path)+1)
copy(childPath, current.Path)
childPath[len(childPath)-1] = childName
s.queue = append(s.queue, flatSection{
Path: childPath,
Sec: current.Sec.Sections[childName],
})
}
return current
}
// HasNext is used in BreadthFirst. See BreadthFirst docs
func (s *sectionIterator) HasNext() bool {
return len(s.queue) > 0
}