github.com/keltia/go-ipfs@v0.3.8-0.20150909044612-210793031c63/commands/cli/helptext.go (about) 1 package cli 2 3 import ( 4 "fmt" 5 "io" 6 "sort" 7 "strings" 8 "text/template" 9 10 cmds "github.com/ipfs/go-ipfs/commands" 11 ) 12 13 const ( 14 requiredArg = "<%v>" 15 optionalArg = "[<%v>]" 16 variadicArg = "%v..." 17 shortFlag = "-%v" 18 longFlag = "--%v" 19 optionType = "(%v)" 20 21 whitespace = "\r\n\t " 22 23 indentStr = " " 24 ) 25 26 type helpFields struct { 27 Indent string 28 Usage string 29 Path string 30 ArgUsage string 31 Tagline string 32 Arguments string 33 Options string 34 Synopsis string 35 Subcommands string 36 Description string 37 MoreHelp bool 38 } 39 40 // TrimNewlines removes extra newlines from fields. This makes aligning 41 // commands easier. Below, the leading + tralining newlines are removed: 42 // Synopsis: ` 43 // ipfs config <key> - Get value of <key> 44 // ipfs config <key> <value> - Set value of <key> to <value> 45 // ipfs config --show - Show config file 46 // ipfs config --edit - Edit config file in $EDITOR 47 // ` 48 func (f *helpFields) TrimNewlines() { 49 f.Path = strings.Trim(f.Path, "\n") 50 f.ArgUsage = strings.Trim(f.ArgUsage, "\n") 51 f.Tagline = strings.Trim(f.Tagline, "\n") 52 f.Arguments = strings.Trim(f.Arguments, "\n") 53 f.Options = strings.Trim(f.Options, "\n") 54 f.Synopsis = strings.Trim(f.Synopsis, "\n") 55 f.Subcommands = strings.Trim(f.Subcommands, "\n") 56 f.Description = strings.Trim(f.Description, "\n") 57 } 58 59 // Indent adds whitespace the lines of fields. 60 func (f *helpFields) IndentAll() { 61 indent := func(s string) string { 62 if s == "" { 63 return s 64 } 65 return indentString(s, indentStr) 66 } 67 68 f.Arguments = indent(f.Arguments) 69 f.Options = indent(f.Options) 70 f.Synopsis = indent(f.Synopsis) 71 f.Subcommands = indent(f.Subcommands) 72 f.Description = indent(f.Description) 73 } 74 75 const usageFormat = "{{if .Usage}}{{.Usage}}{{else}}{{.Path}}{{if .ArgUsage}} {{.ArgUsage}}{{end}} - {{.Tagline}}{{end}}" 76 77 const longHelpFormat = ` 78 {{.Indent}}{{template "usage" .}} 79 80 {{if .Arguments}}ARGUMENTS: 81 82 {{.Arguments}} 83 84 {{end}}{{if .Options}}OPTIONS: 85 86 {{.Options}} 87 88 {{end}}{{if .Subcommands}}SUBCOMMANDS: 89 90 {{.Subcommands}} 91 92 {{.Indent}}Use '{{.Path}} <subcmd> --help' for more information about each command. 93 94 {{end}}{{if .Description}}DESCRIPTION: 95 96 {{.Description}} 97 98 {{end}} 99 ` 100 const shortHelpFormat = `USAGE: 101 102 {{.Indent}}{{template "usage" .}} 103 {{if .Synopsis}} 104 {{.Synopsis}} 105 {{end}}{{if .Description}} 106 {{.Description}} 107 {{end}} 108 {{if .MoreHelp}}Use '{{.Path}} --help' for more information about this command. 109 {{end}} 110 ` 111 112 var usageTemplate *template.Template 113 var longHelpTemplate *template.Template 114 var shortHelpTemplate *template.Template 115 116 func init() { 117 usageTemplate = template.Must(template.New("usage").Parse(usageFormat)) 118 longHelpTemplate = template.Must(usageTemplate.New("longHelp").Parse(longHelpFormat)) 119 shortHelpTemplate = template.Must(usageTemplate.New("shortHelp").Parse(shortHelpFormat)) 120 } 121 122 // LongHelp returns a formatted CLI helptext string, generated for the given command 123 func LongHelp(rootName string, root *cmds.Command, path []string, out io.Writer) error { 124 cmd, err := root.Get(path) 125 if err != nil { 126 return err 127 } 128 129 pathStr := rootName 130 if len(path) > 0 { 131 pathStr += " " + strings.Join(path, " ") 132 } 133 134 fields := helpFields{ 135 Indent: indentStr, 136 Path: pathStr, 137 ArgUsage: usageText(cmd), 138 Tagline: cmd.Helptext.Tagline, 139 Arguments: cmd.Helptext.Arguments, 140 Options: cmd.Helptext.Options, 141 Synopsis: cmd.Helptext.Synopsis, 142 Subcommands: cmd.Helptext.Subcommands, 143 Description: cmd.Helptext.ShortDescription, 144 Usage: cmd.Helptext.Usage, 145 MoreHelp: (cmd != root), 146 } 147 148 if len(cmd.Helptext.LongDescription) > 0 { 149 fields.Description = cmd.Helptext.LongDescription 150 } 151 152 // autogen fields that are empty 153 if len(fields.Arguments) == 0 { 154 fields.Arguments = strings.Join(argumentText(cmd), "\n") 155 } 156 if len(fields.Options) == 0 { 157 fields.Options = strings.Join(optionText(cmd), "\n") 158 } 159 if len(fields.Subcommands) == 0 { 160 fields.Subcommands = strings.Join(subcommandText(cmd, rootName, path), "\n") 161 } 162 163 // trim the extra newlines (see TrimNewlines doc) 164 fields.TrimNewlines() 165 166 // indent all fields that have been set 167 fields.IndentAll() 168 169 return longHelpTemplate.Execute(out, fields) 170 } 171 172 // ShortHelp returns a formatted CLI helptext string, generated for the given command 173 func ShortHelp(rootName string, root *cmds.Command, path []string, out io.Writer) error { 174 cmd, err := root.Get(path) 175 if err != nil { 176 return err 177 } 178 179 // default cmd to root if there is no path 180 if path == nil && cmd == nil { 181 cmd = root 182 } 183 184 pathStr := rootName 185 if len(path) > 0 { 186 pathStr += " " + strings.Join(path, " ") 187 } 188 189 fields := helpFields{ 190 Indent: indentStr, 191 Path: pathStr, 192 ArgUsage: usageText(cmd), 193 Tagline: cmd.Helptext.Tagline, 194 Synopsis: cmd.Helptext.Synopsis, 195 Description: cmd.Helptext.ShortDescription, 196 Usage: cmd.Helptext.Usage, 197 MoreHelp: (cmd != root), 198 } 199 200 // trim the extra newlines (see TrimNewlines doc) 201 fields.TrimNewlines() 202 203 // indent all fields that have been set 204 fields.IndentAll() 205 206 return shortHelpTemplate.Execute(out, fields) 207 } 208 209 func argumentText(cmd *cmds.Command) []string { 210 lines := make([]string, len(cmd.Arguments)) 211 212 for i, arg := range cmd.Arguments { 213 lines[i] = argUsageText(arg) 214 } 215 lines = align(lines) 216 for i, arg := range cmd.Arguments { 217 lines[i] += " - " + arg.Description 218 } 219 220 return lines 221 } 222 223 func optionFlag(flag string) string { 224 if len(flag) == 1 { 225 return fmt.Sprintf(shortFlag, flag) 226 } else { 227 return fmt.Sprintf(longFlag, flag) 228 } 229 } 230 231 func optionText(cmd ...*cmds.Command) []string { 232 // get a slice of the options we want to list out 233 options := make([]cmds.Option, 0) 234 for _, c := range cmd { 235 for _, opt := range c.Options { 236 options = append(options, opt) 237 } 238 } 239 240 // add option names to output (with each name aligned) 241 lines := make([]string, 0) 242 j := 0 243 for { 244 done := true 245 i := 0 246 for _, opt := range options { 247 if len(lines) < i+1 { 248 lines = append(lines, "") 249 } 250 251 names := sortByLength(opt.Names()) 252 if len(names) >= j+1 { 253 lines[i] += optionFlag(names[j]) 254 } 255 if len(names) > j+1 { 256 lines[i] += ", " 257 done = false 258 } 259 260 i++ 261 } 262 263 if done { 264 break 265 } 266 267 lines = align(lines) 268 j++ 269 } 270 lines = align(lines) 271 272 // add option types to output 273 for i, opt := range options { 274 lines[i] += " " + fmt.Sprintf("%v", opt.Type()) 275 } 276 lines = align(lines) 277 278 // add option descriptions to output 279 for i, opt := range options { 280 lines[i] += " - " + opt.Description() 281 } 282 283 return lines 284 } 285 286 func subcommandText(cmd *cmds.Command, rootName string, path []string) []string { 287 prefix := fmt.Sprintf("%v %v", rootName, strings.Join(path, " ")) 288 if len(path) > 0 { 289 prefix += " " 290 } 291 subcmds := make([]*cmds.Command, len(cmd.Subcommands)) 292 lines := make([]string, len(cmd.Subcommands)) 293 294 i := 0 295 for name, sub := range cmd.Subcommands { 296 usage := usageText(sub) 297 if len(usage) > 0 { 298 usage = " " + usage 299 } 300 lines[i] = prefix + name + usage 301 subcmds[i] = sub 302 i++ 303 } 304 305 lines = align(lines) 306 for i, sub := range subcmds { 307 lines[i] += " - " + sub.Helptext.Tagline 308 } 309 310 return lines 311 } 312 313 func usageText(cmd *cmds.Command) string { 314 s := "" 315 for i, arg := range cmd.Arguments { 316 if i != 0 { 317 s += " " 318 } 319 s += argUsageText(arg) 320 } 321 322 return s 323 } 324 325 func argUsageText(arg cmds.Argument) string { 326 s := arg.Name 327 328 if arg.Required { 329 s = fmt.Sprintf(requiredArg, s) 330 } else { 331 s = fmt.Sprintf(optionalArg, s) 332 } 333 334 if arg.Variadic { 335 s = fmt.Sprintf(variadicArg, s) 336 } 337 338 return s 339 } 340 341 func align(lines []string) []string { 342 longest := 0 343 for _, line := range lines { 344 length := len(line) 345 if length > longest { 346 longest = length 347 } 348 } 349 350 for i, line := range lines { 351 length := len(line) 352 if length > 0 { 353 lines[i] += strings.Repeat(" ", longest-length) 354 } 355 } 356 357 return lines 358 } 359 360 func indent(lines []string, prefix string) []string { 361 for i, line := range lines { 362 lines[i] = prefix + indentString(line, prefix) 363 } 364 return lines 365 } 366 367 func indentString(line string, prefix string) string { 368 return prefix + strings.Replace(line, "\n", "\n"+prefix, -1) 369 } 370 371 type lengthSlice []string 372 373 func (ls lengthSlice) Len() int { 374 return len(ls) 375 } 376 func (ls lengthSlice) Swap(a, b int) { 377 ls[a], ls[b] = ls[b], ls[a] 378 } 379 func (ls lengthSlice) Less(a, b int) bool { 380 return len(ls[a]) < len(ls[b]) 381 } 382 383 func sortByLength(slice []string) []string { 384 output := make(lengthSlice, len(slice)) 385 for i, val := range slice { 386 output[i] = val 387 } 388 sort.Sort(output) 389 return []string(output) 390 }