@@ -4,20 +4,23 @@ import (
4
4
"errors"
5
5
"fmt"
6
6
"io"
7
+ "os"
7
8
"sort"
8
9
"strings"
9
10
"text/template"
10
11
11
- "github.com/ipfs/go-ipfs-cmds"
12
+ cmds "github.com/ipfs/go-ipfs-cmds"
13
+ "golang.org/x/crypto/ssh/terminal"
12
14
)
13
15
14
16
const (
15
- requiredArg = "<%v>"
16
- optionalArg = "[<%v>]"
17
- variadicArg = "%v..."
18
- shortFlag = "-%v"
19
- longFlag = "--%v"
20
- optionType = "(%v)"
17
+ defaultTerminalWidth = 80
18
+ requiredArg = "<%v>"
19
+ optionalArg = "[<%v>]"
20
+ variadicArg = "%v..."
21
+ shortFlag = "-%v"
22
+ longFlag = "--%v"
23
+ optionType = "(%v)"
21
24
22
25
whitespace = "\r \n \t "
23
26
@@ -28,7 +31,6 @@ type helpFields struct {
28
31
Indent string
29
32
Usage string
30
33
Path string
31
- ArgUsage string
32
34
Tagline string
33
35
Arguments string
34
36
Options string
@@ -48,7 +50,7 @@ type helpFields struct {
48
50
// `
49
51
func (f * helpFields ) TrimNewlines () {
50
52
f .Path = strings .Trim (f .Path , "\n " )
51
- f .ArgUsage = strings .Trim (f .ArgUsage , "\n " )
53
+ f .Usage = strings .Trim (f .Usage , "\n " )
52
54
f .Tagline = strings .Trim (f .Tagline , "\n " )
53
55
f .Arguments = strings .Trim (f .Arguments , "\n " )
54
56
f .Options = strings .Trim (f .Options , "\n " )
@@ -66,17 +68,16 @@ func (f *helpFields) IndentAll() {
66
68
return indentString (s , indentStr )
67
69
}
68
70
71
+ f .Usage = indent (f .Usage )
69
72
f .Arguments = indent (f .Arguments )
70
73
f .Options = indent (f .Options )
71
74
f .Synopsis = indent (f .Synopsis )
72
75
f .Subcommands = indent (f .Subcommands )
73
76
f .Description = indent (f .Description )
74
77
}
75
78
76
- const usageFormat = "{{if .Usage}}{{.Usage}}{{else}}{{.Path}}{{if .ArgUsage}} {{.ArgUsage}}{{end}} - {{.Tagline}}{{end}}"
77
-
78
79
const longHelpFormat = `USAGE
79
- {{.Indent}}{{template "usage" . }}
80
+ {{.Usage }}
80
81
81
82
{{if .Synopsis}}SYNOPSIS
82
83
{{.Synopsis}}
@@ -96,11 +97,12 @@ const longHelpFormat = `USAGE
96
97
{{end}}{{if .Subcommands}}SUBCOMMANDS
97
98
{{.Subcommands}}
98
99
99
- {{.Indent}}Use '{{.Path}} <subcmd> --help' for more information about each command.
100
+ {{.Indent}}For more information about each command, use:
101
+ {{.Indent}}'{{.Path}} <subcmd> --help'
100
102
{{end}}
101
103
`
102
104
const shortHelpFormat = `USAGE
103
- {{.Indent}}{{template "usage" . }}
105
+ {{.Usage }}
104
106
{{if .Synopsis}}
105
107
{{.Synopsis}}
106
108
{{end}}{{if .Description}}
@@ -109,18 +111,30 @@ const shortHelpFormat = `USAGE
109
111
SUBCOMMANDS
110
112
{{.Subcommands}}
111
113
{{end}}{{if .MoreHelp}}
112
- Use '{{.Path}} --help' for more information about this command.
114
+ {{.Indent}}For more information about each command, use:
115
+ {{.Indent}}'{{.Path}} <subcmd> --help'
113
116
{{end}}
114
117
`
115
118
116
- var usageTemplate * template.Template
117
119
var longHelpTemplate * template.Template
118
120
var shortHelpTemplate * template.Template
119
121
122
+ func getTerminalWidth (out io.Writer ) int {
123
+ file , ok := out .(* os.File )
124
+ if ok {
125
+ if terminal .IsTerminal (int (file .Fd ())) {
126
+ width , _ , err := terminal .GetSize (int (file .Fd ()))
127
+ if err == nil {
128
+ return width
129
+ }
130
+ }
131
+ }
132
+ return defaultTerminalWidth
133
+ }
134
+
120
135
func init () {
121
- usageTemplate = template .Must (template .New ("usage" ).Parse (usageFormat ))
122
- longHelpTemplate = template .Must (usageTemplate .New ("longHelp" ).Parse (longHelpFormat ))
123
- shortHelpTemplate = template .Must (usageTemplate .New ("shortHelp" ).Parse (shortHelpFormat ))
136
+ longHelpTemplate = template .Must (template .New ("longHelp" ).Parse (longHelpFormat ))
137
+ shortHelpTemplate = template .Must (template .New ("shortHelp" ).Parse (shortHelpFormat ))
124
138
}
125
139
126
140
var ErrNoHelpRequested = errors .New ("no help requested" )
@@ -154,7 +168,6 @@ func LongHelp(rootName string, root *cmds.Command, path []string, out io.Writer)
154
168
fields := helpFields {
155
169
Indent : indentStr ,
156
170
Path : pathStr ,
157
- ArgUsage : usageText (cmd ),
158
171
Tagline : cmd .Helptext .Tagline ,
159
172
Arguments : cmd .Helptext .Arguments ,
160
173
Options : cmd .Helptext .Options ,
@@ -165,22 +178,29 @@ func LongHelp(rootName string, root *cmds.Command, path []string, out io.Writer)
165
178
MoreHelp : (cmd != root ),
166
179
}
167
180
181
+ width := getTerminalWidth (out ) - len (indentStr )
182
+
168
183
if len (cmd .Helptext .LongDescription ) > 0 {
169
184
fields .Description = cmd .Helptext .LongDescription
170
185
}
171
186
172
187
// autogen fields that are empty
188
+ if len (cmd .Helptext .Usage ) > 0 {
189
+ fields .Usage = cmd .Helptext .Usage
190
+ } else {
191
+ fields .Usage = commandUsageText (width , cmd , rootName , path )
192
+ }
173
193
if len (fields .Arguments ) == 0 {
174
- fields .Arguments = strings .Join (argumentText (cmd ), "\n " )
194
+ fields .Arguments = strings .Join (argumentText (width , cmd ), "\n " )
175
195
}
176
196
if len (fields .Options ) == 0 {
177
- fields .Options = strings .Join (optionText (cmd ), "\n " )
197
+ fields .Options = strings .Join (optionText (width , cmd ), "\n " )
178
198
}
179
199
if len (fields .Subcommands ) == 0 {
180
- fields .Subcommands = strings .Join (subcommandText (cmd , rootName , path ), "\n " )
200
+ fields .Subcommands = strings .Join (subcommandText (width , cmd , rootName , path ), "\n " )
181
201
}
182
202
if len (fields .Synopsis ) == 0 {
183
- fields .Synopsis = generateSynopsis (cmd , pathStr )
203
+ fields .Synopsis = generateSynopsis (width , cmd , pathStr )
184
204
}
185
205
186
206
// trim the extra newlines (see TrimNewlines doc)
@@ -212,21 +232,26 @@ func ShortHelp(rootName string, root *cmds.Command, path []string, out io.Writer
212
232
fields := helpFields {
213
233
Indent : indentStr ,
214
234
Path : pathStr ,
215
- ArgUsage : usageText (cmd ),
216
235
Tagline : cmd .Helptext .Tagline ,
217
236
Synopsis : cmd .Helptext .Synopsis ,
218
237
Description : cmd .Helptext .ShortDescription ,
219
238
Subcommands : cmd .Helptext .Subcommands ,
220
- Usage : cmd .Helptext .Usage ,
221
239
MoreHelp : (cmd != root ),
222
240
}
223
241
242
+ width := getTerminalWidth (out ) - len (indentStr )
243
+
224
244
// autogen fields that are empty
245
+ if len (cmd .Helptext .Usage ) > 0 {
246
+ fields .Usage = cmd .Helptext .Usage
247
+ } else {
248
+ fields .Usage = commandUsageText (width , cmd , rootName , path )
249
+ }
225
250
if len (fields .Subcommands ) == 0 {
226
- fields .Subcommands = strings .Join (subcommandText (cmd , rootName , path ), "\n " )
251
+ fields .Subcommands = strings .Join (subcommandText (width , cmd , rootName , path ), "\n " )
227
252
}
228
253
if len (fields .Synopsis ) == 0 {
229
- fields .Synopsis = generateSynopsis (cmd , pathStr )
254
+ fields .Synopsis = generateSynopsis (width , cmd , pathStr )
230
255
}
231
256
232
257
// trim the extra newlines (see TrimNewlines doc)
@@ -238,8 +263,17 @@ func ShortHelp(rootName string, root *cmds.Command, path []string, out io.Writer
238
263
return shortHelpTemplate .Execute (out , fields )
239
264
}
240
265
241
- func generateSynopsis (cmd * cmds.Command , path string ) string {
266
+ func generateSynopsis (width int , cmd * cmds.Command , path string ) string {
242
267
res := path
268
+ currentLineLength := len (res )
269
+ appendText := func (text string ) {
270
+ if currentLineLength + len (text )+ 1 > width {
271
+ res += "\n " + strings .Repeat (" " , len (path ))
272
+ currentLineLength = len (path )
273
+ }
274
+ currentLineLength += len (text ) + 1
275
+ res += " " + text
276
+ }
243
277
for _ , opt := range cmd .Options {
244
278
valopt , ok := cmd .Helptext .SynopsisOptionsValues [opt .Name ()]
245
279
if ! ok {
@@ -267,10 +301,10 @@ func generateSynopsis(cmd *cmds.Command, path string) string {
267
301
}
268
302
}
269
303
}
270
- res = fmt . Sprintf ( "%s [%s]" , res , sopt )
304
+ appendText ( "[" + sopt + "]" )
271
305
}
272
306
if len (cmd .Arguments ) > 0 {
273
- res = fmt . Sprintf ( "%s [--]", res )
307
+ appendText ( " [--]" )
274
308
}
275
309
for _ , arg := range cmd .Arguments {
276
310
sarg := fmt .Sprintf ("<%s>" , arg .Name )
@@ -281,25 +315,52 @@ func generateSynopsis(cmd *cmds.Command, path string) string {
281
315
if ! arg .Required {
282
316
sarg = fmt .Sprintf ("[%s]" , sarg )
283
317
}
284
- res = fmt . Sprintf ( "%s %s" , res , sarg )
318
+ appendText ( sarg )
285
319
}
286
320
return strings .Trim (res , " " )
287
321
}
288
322
289
- func argumentText (cmd * cmds.Command ) []string {
323
+ func argumentText (width int , cmd * cmds.Command ) []string {
290
324
lines := make ([]string , len (cmd .Arguments ))
291
325
292
326
for i , arg := range cmd .Arguments {
293
327
lines [i ] = argUsageText (arg )
294
328
}
295
329
lines = align (lines )
296
330
for i , arg := range cmd .Arguments {
297
- lines [i ] += " - " + arg .Description
331
+ lines [i ] += " - "
332
+ lines [i ] = appendWrapped (lines [i ], arg .Description , width )
298
333
}
299
334
300
335
return lines
301
336
}
302
337
338
+ func appendWrapped (prefix , text string , width int ) string {
339
+ offset := len (prefix )
340
+ bWidth := width - offset
341
+
342
+ text = strings .Trim (text , whitespace )
343
+ // Minimum help-text width is 30 characters.
344
+ if bWidth < 30 {
345
+ prefix += text
346
+ return prefix
347
+ }
348
+
349
+ for len (text ) > bWidth {
350
+ idx := strings .LastIndexAny (text [:bWidth ], whitespace )
351
+ if idx < 0 {
352
+ idx = strings .IndexAny (text , whitespace )
353
+ }
354
+ if idx < 0 {
355
+ break
356
+ }
357
+ prefix += text [:idx ] + "\n " + strings .Repeat (" " , offset )
358
+ text = strings .TrimLeft (text [idx :], whitespace )
359
+ }
360
+ prefix += text
361
+ return prefix
362
+ }
363
+
303
364
func optionFlag (flag string ) string {
304
365
if len (flag ) == 1 {
305
366
return fmt .Sprintf (shortFlag , flag )
@@ -308,7 +369,7 @@ func optionFlag(flag string) string {
308
369
}
309
370
}
310
371
311
- func optionText (cmd ... * cmds.Command ) []string {
372
+ func optionText (width int , cmd ... * cmds.Command ) []string {
312
373
// get a slice of the options we want to list out
313
374
options := make ([]cmds.Option , 0 )
314
375
for _ , c := range cmd {
@@ -317,53 +378,33 @@ func optionText(cmd ...*cmds.Command) []string {
317
378
}
318
379
}
319
380
320
- // add option names to output (with each name aligned)
321
- lines := make ([]string , 0 )
322
- j := 0
323
- for {
324
- done := true
325
- i := 0
326
- for _ , opt := range options {
327
- if len (lines ) < i + 1 {
328
- lines = append (lines , "" )
329
- }
330
-
331
- names := sortByLength (opt .Names ())
332
- if len (names ) >= j + 1 {
333
- lines [i ] += optionFlag (names [j ])
334
- }
335
- if len (names ) > j + 1 {
336
- lines [i ] += ", "
337
- done = false
338
- }
339
-
340
- i ++
341
- }
342
-
343
- if done {
344
- break
381
+ // add option names to output
382
+ lines := make ([]string , len (options ))
383
+ for i , opt := range options {
384
+ flags := sortByLength (opt .Names ())
385
+ for j , f := range flags {
386
+ flags [j ] = optionFlag (f )
345
387
}
346
-
347
- lines = align (lines )
348
- j ++
388
+ lines [i ] = strings .Join (flags , ", " )
349
389
}
350
390
lines = align (lines )
351
391
352
392
// add option types to output
353
393
for i , opt := range options {
354
- lines [i ] += " " + fmt .Sprintf ("%v" , opt .Type ())
394
+ lines [i ] += " " + fmt .Sprintf ("%v" , opt .Type ())
355
395
}
356
396
lines = align (lines )
357
397
358
398
// add option descriptions to output
359
399
for i , opt := range options {
360
- lines [i ] += " - " + opt .Description ()
400
+ lines [i ] += " - "
401
+ lines [i ] = appendWrapped (lines [i ], opt .Description (), width )
361
402
}
362
403
363
404
return lines
364
405
}
365
406
366
- func subcommandText (cmd * cmds.Command , rootName string , path []string ) []string {
407
+ func subcommandText (width int , cmd * cmds.Command , rootName string , path []string ) []string {
367
408
prefix := fmt .Sprintf ("%v %v" , rootName , strings .Join (path , " " ))
368
409
if len (path ) > 0 {
369
410
prefix += " "
@@ -392,12 +433,24 @@ func subcommandText(cmd *cmds.Command, rootName string, path []string) []string
392
433
393
434
lines = align (lines )
394
435
for i , sub := range subcmds {
395
- lines [i ] += " - " + sub .Helptext .Tagline
436
+ lines [i ] += " - "
437
+ lines [i ] = appendWrapped (lines [i ], sub .Helptext .Tagline , width )
396
438
}
397
439
398
440
return lines
399
441
}
400
442
443
+ func commandUsageText (width int , cmd * cmds.Command , rootName string , path []string ) string {
444
+ text := fmt .Sprintf ("%v %v" , rootName , strings .Join (path , " " ))
445
+ argUsage := usageText (cmd )
446
+ if len (argUsage ) > 0 {
447
+ text += " " + argUsage
448
+ }
449
+ text += " - "
450
+ text = appendWrapped (text , cmd .Helptext .Tagline , width )
451
+ return text
452
+ }
453
+
401
454
func usageText (cmd * cmds.Command ) string {
402
455
s := ""
403
456
for i , arg := range cmd .Arguments {
0 commit comments