-
Notifications
You must be signed in to change notification settings - Fork 3
/
usage.go
191 lines (182 loc) · 5.58 KB
/
usage.go
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
package brigodier
import (
"bytes"
"context"
)
// AllUsage gets all possible executable commands following the given node.
//
// You may use Dispatcher.Root as a target to get all usage data for the entire command tree.
//
// The returned syntax will be in "simple" form: <param> and literal.
// "Optional" nodes will be listed as multiple entries: the parent node, and the child nodes.
// For example, a required literal "foo" followed by an optional param "int" will be two nodes:
// foo
// foo <int>
//
// The path to the specified node will NOT be prepended to the output, as there can theoretically be many
// ways to reach a given node. It will only give you paths relative to the specified node, not absolute from root.
func (d *Dispatcher) AllUsage(ctx context.Context, node CommandNode, restricted bool) []string {
return d.allUsage(ctx, node, nil, "", restricted)
}
func (d *Dispatcher) allUsage(ctx context.Context, node CommandNode, result []string, prefix string, restricted bool) []string {
if restricted && !node.CanUse(ctx) {
return result
}
if node.Command() != nil {
result = append(result, prefix)
}
b := new(bytes.Buffer)
if node.Redirect() != nil {
if prefix == "" {
b.WriteString(node.UsageText())
} else {
b.WriteString(prefix)
}
b.WriteRune(ArgumentSeparator)
if node.Redirect() == &d.Root {
b.WriteString("...")
} else {
b.WriteString("-> ")
b.WriteString(node.Redirect().UsageText())
}
result = append(result, b.String())
} else { // if len(node.Children()) != 0
node.ChildrenOrdered().Range(func(_ string, child CommandNode) bool {
b.Reset()
if prefix != "" {
b.WriteString(prefix)
b.WriteRune(ArgumentSeparator)
}
b.WriteString(child.UsageText())
result = d.allUsage(ctx, child, result, b.String(), restricted)
return true
})
}
return result
}
const (
// UsageOptionalOpen is the open rune for an optional argument.
UsageOptionalOpen rune = '['
// UsageOptionalClose is the close rune for an optional argument.
UsageOptionalClose rune = ']'
// UsageRequiredOpen is the open rune for a required argument.
UsageRequiredOpen rune = '('
// UsageRequiredClose is the close rune for a required argument.
UsageRequiredClose rune = ')'
// UsageOr is the or rune splitting multiple argument options.
UsageOr rune = '|'
)
// SmartUsage gets the possible executable commands from a specified node.
//
// You may use Dispatcher.Root as a target to get usage data for the entire command tree.
//
// The returned syntax will be in "smart" form: <param>, literal, [optional] and (either|or).
// These forms may be mixed and matched to provide as much information about the child nodes as it can, without being too verbose.
// For example, a required literal "foo" followed by an optional param "int" can be compressed into one string:
// foo [<int>]
//
// The path to the specified node will NOT be prepended to the output, as there can theoretically be many
// ways to reach a given node. It will only give you paths relative to the specified node, not absolute from root.
//
// The returned usage will be restricted to only commands that the provided context.Context can use.
func (d *Dispatcher) SmartUsage(ctx context.Context, node CommandNode) CommandNodeStringMap {
result := NewCommandNodeStringMap()
optional := node.Command() != nil
node.ChildrenOrdered().Range(func(_ string, child CommandNode) bool {
usage := d.smartUsage(ctx, child, optional, false)
if usage != "" {
result.Put(child, usage)
}
return true
})
return result
}
func (d *Dispatcher) smartUsage(ctx context.Context, node CommandNode, optional bool, deep bool) string {
if !node.CanUse(ctx) {
return ""
}
b := new(bytes.Buffer) // self
if optional {
b.WriteRune(UsageOptionalOpen)
b.WriteString(node.UsageText())
b.WriteRune(UsageOptionalClose)
} else {
b.WriteString(node.UsageText())
}
if deep {
return b.String()
}
var openChar, closeChar rune
childOptional := node.Command() != nil
if childOptional {
openChar = UsageOptionalOpen
closeChar = UsageOptionalClose
} else {
openChar = UsageRequiredOpen
closeChar = UsageRequiredClose
}
if node.Redirect() != nil {
b.WriteRune(ArgumentSeparator)
if node.Redirect() == &d.Root {
b.WriteString("...")
} else {
b.WriteString("-> ")
b.WriteString(node.Redirect().UsageText())
}
return b.String()
}
var children []CommandNode
node.ChildrenOrdered().Range(func(_ string, child CommandNode) bool {
if child.CanUse(ctx) {
children = append(children, child)
}
return true
})
if len(children) == 1 {
usage := d.smartUsage(ctx, children[0], childOptional, childOptional)
if usage != "" {
b.WriteRune(ArgumentSeparator)
b.WriteString(usage)
return b.String()
}
} else if len(children) > 1 {
var (
childUsage []string
deduplicate = map[string]struct{}{}
)
for _, child := range children {
usage := d.smartUsage(ctx, child, optional, true)
if usage != "" {
if _, ok := deduplicate[usage]; !ok {
childUsage = append(childUsage, usage)
deduplicate[usage] = struct{}{}
}
}
}
if len(childUsage) == 1 {
b.WriteRune(ArgumentSeparator)
if childOptional {
b.WriteRune(UsageOptionalOpen)
b.WriteString(childUsage[0])
b.WriteRune(UsageOptionalClose)
} else {
b.WriteString(childUsage[0])
}
return b.String()
} else if len(children) > 1 {
for i, child := range children {
if i == 0 {
b.WriteRune(ArgumentSeparator)
b.WriteRune(openChar)
} else {
b.WriteRune(UsageOr)
}
b.WriteString(child.UsageText())
if i == len(children)-1 {
b.WriteRune(closeChar)
}
}
}
}
return b.String()
}