-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathprint-help.go
227 lines (201 loc) · 5.47 KB
/
print-help.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
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
// Copyright (C) 2023 Takayuki Sato. All Rights Reserved.
// This program is free software under MIT License.
// See the file LICENSE in this distribution for more details.
package cliargs
import (
"fmt"
"golang.org/x/term"
"os"
"strings"
)
// MarginsAndIndentExceedLineWidth is an error which indicates that the sum of
// left margin, right margin, and indent is greater than line width.
// It means that there are no width to print texts.
type MarginsAndIndentExceedLineWidth struct {
LineWidth, MarginLeft, MarginRight, Indent int
}
func (e MarginsAndIndentExceedLineWidth) Error() string {
return "MarginsAndIndentExceedLineWidth"
}
// WrapOpts is a struct type which holds options for wrapping texts.
// This struct type has the following field for wrap options: MarginLeft,
// MarginRight, and Indent.
// MarginLeft and MarginRight is space widths on both sides, and these are
// applied to all lines.
// Indent is a space width on left side, and this is applied to second line or
// later of each option.
type WrapOpts struct {
MarginLeft int
MarginRight int
Indent int
}
// HelpIter is a struct type to iterate lines of a help text.
type HelpIter struct {
texts []string
index int
indent int
margin string
lineIter lineIter
}
func newHelpIter(texts []string, lineWidth, indent, margin int) HelpIter {
if len(texts) == 0 {
return HelpIter{}
}
return HelpIter{
texts: texts,
indent: indent,
margin: strings.Repeat(" ", margin),
lineIter: newLineIter(texts[0], lineWidth),
}
}
// Next is a method which returns a line of a help text and a status which
// indicates this HelpIter has more texts or not.
// If there are more lines, the returned IterStatus value is ITER_HAS_MORE,
// otherwise the value is ITER_NO_MORE.
func (iter *HelpIter) Next() (string, IterStatus) {
if len(iter.texts) == 0 {
return "", ITER_NO_MORE
}
line, status := iter.lineIter.Next()
if len(line) > 0 {
line = iter.margin + line
}
if status == ITER_NO_MORE {
iter.index++
if iter.index >= len(iter.texts) {
return line, status
}
iter.lineIter.resetText(iter.texts[iter.index])
iter.lineIter.setIndent(0)
return line, ITER_HAS_MORE
}
if iter.index > 0 {
iter.lineIter.setIndent(iter.indent)
}
return line, status
}
// MakeHelp is a function to make a line iterator of a help text.
// This function makes a help text from a usage text, option configurations
// ([]OptCfg), and wrap options (WrapOpts).
//
// A help text consists of an usage section and options section, and options
// section consists of title parts and description parts.
// On a title part, a option name, aliases, and a .AtParam field of OptCfg are
// enumerated.
// On a description part, a .Desc field of OptCfg is put and it follows a title
// part with an indent, specified in WrapOpts,
//
// On the both sides of a help text, left margin and right margin of which size
// are specified in WrapOpts can be put.
// These margins are applied to all lines of a help text.
//
// The sum of left margin, right margin, and indent have to be less than the
// line width, because if not, there is no width to output texts.
// The line width is obtained from the terminal width.
func MakeHelp(
usage string, optCfgs []OptCfg, wrapOpts WrapOpts,
) (HelpIter, error) {
lineWidth, _, err := term.GetSize(int(os.Stdout.Fd()))
if err != nil {
lineWidth = 80
}
tw := make([]textAndWidth, len(optCfgs))
indent := 0
i := 0
for _, cfg := range optCfgs {
if cfg.Name == anyOption {
continue
}
tw[i] = makeOptTitle(cfg)
if indent < tw[i].width {
indent = tw[i].width
}
i++
}
if i != len(tw) {
tw = tw[0:i]
}
indent += 2
if wrapOpts.Indent > 0 {
indent = wrapOpts.Indent
}
if (wrapOpts.MarginLeft + wrapOpts.MarginRight + indent) >= lineWidth {
return HelpIter{}, MarginsAndIndentExceedLineWidth{
LineWidth: lineWidth,
MarginLeft: wrapOpts.MarginLeft,
MarginRight: wrapOpts.MarginRight,
Indent: indent,
}
}
lineWidth -= wrapOpts.MarginLeft + wrapOpts.MarginRight
texts := make([]string, 1+len(tw))
texts[0] = usage
i = 0
for _, cfg := range optCfgs {
if cfg.Name == anyOption {
continue
}
texts[i+1] = makeOptHelp(tw[i], cfg, indent)
i++
}
return newHelpIter(texts, lineWidth, indent, wrapOpts.MarginLeft), nil
}
type textAndWidth struct {
text string
width int
}
func makeOptTitle(cfg OptCfg) textAndWidth {
title := cfg.Name
switch len(title) {
case 0:
case 1:
title = "-" + title
default:
title = "--" + title
}
for _, alias := range cfg.Aliases {
switch len(alias) {
case 0:
case 1:
title += ", -" + alias
default:
title += ", --" + alias
}
}
if cfg.HasParam && len(cfg.AtParam) > 0 {
title += " " + cfg.AtParam
}
w := textWidth(title)
return textAndWidth{text: title, width: w}
}
func textWidth(text string) int {
w := 0
for _, r := range text {
w += runeWidth(r)
}
return w
}
func makeOptHelp(tw textAndWidth, cfg OptCfg, indent int) string {
w := tw.width
if w+2 > indent {
return tw.text + "\n" + strings.Repeat(" ", indent) + cfg.Desc
} else {
return tw.text + strings.Repeat(" ", indent-w) + cfg.Desc
}
}
// PrintHelp is a function which output a help text to stdout.
// This function calls MakeHelp function to make a help text inside itself.
func PrintHelp(usage string, optCfgs []OptCfg, wrapOpts WrapOpts) error {
iter, err := MakeHelp(usage, optCfgs, wrapOpts)
if err != nil {
return err
}
for {
line, status := iter.Next()
fmt.Println(line)
if status == ITER_NO_MORE {
break
}
}
return nil
}