package bonfire import ( "strconv" "strings" ) // helpUsage is the usage line of a command in help and the JSON listing: // the name, its arguments and [options]; passthrough commands end with // [--] because everything after the name reaches them unparsed. func helpUsage(c Command) string { u := commandUsage(c) + " [options]" if c.Passthrough { u += " [--] " } return u } // optionLabel is the left column of a flag: "-x, --name=NAME" for value // flags, "--name" for bare flags, indented four spaces without shorthand. func optionLabel(f Flag) string { label := " " if f.Shorthand != "" { label = "-" + f.Shorthand + ", " } label += "--" + f.Name if !f.Bare { label += "=" + strings.ToUpper(strings.ReplaceAll(f.Name, "-", "_")) } return label } // optionDetail is a flag's description followed by its default and the // repeatable marker. func optionDetail(f Flag) string { parts := []string{} if f.Description != "" { parts = append(parts, f.Description) } if f.Default != "" && !f.Bare { if _, err := strconv.Atoi(f.Default); err == nil { parts = append(parts, "[default: "+f.Default+"]") } else { parts = append(parts, "[default: "+strconv.Quote(f.Default)+"]") } } if f.Repeatable { parts = append(parts, "(multiple values allowed)") } return strings.Join(parts, " ") } // renderHelp writes a command's help in the Artisan layout: Description, // Usage, Arguments, Options with defaults, Global options, Help (the long // text) and Examples, each section present only when it has content. func (st *rootState) renderHelp(c Command, g Globals) error { p := DefaultPalette() var b strings.Builder section := func(title string) { if b.Len() > 0 { b.WriteString("\n") } b.WriteString(p.Header.Render(title+":") + "\n") } if c.Description != "" { section("Description") b.WriteString(" " + c.Description + "\n") } section("Usage") b.WriteString(" " + helpUsage(c) + "\n") width := 0 for _, a := range c.Args { width = max(width, len(a.Name)) } for _, f := range c.Flags { width = max(width, len(optionLabel(f))) } row := func(label, detail string) { line := " " + p.Success.Render(label) if detail != "" { line += strings.Repeat(" ", width-len(label)+2) + detail } b.WriteString(line + "\n") } if len(c.Args) > 0 { section("Arguments") for _, a := range c.Args { row(a.Name, a.Description) } } if len(c.Flags) > 0 { section("Options") for _, f := range c.Flags { row(optionLabel(f), optionDetail(f)) } } section("Global options") writeGlobalOptions(&b, p) if long := strings.TrimSpace(c.Long); long != "" { section("Help") for _, line := range strings.Split(long, "\n") { if strings.TrimSpace(line) == "" { b.WriteString("\n") continue } b.WriteString(" " + line + "\n") } } if len(c.Examples) > 0 { section("Examples") bin := usageName(st.name) for i, e := range c.Examples { if i > 0 { b.WriteString("\n") } if e.Description != "" { b.WriteString(" " + p.Muted.Render("# "+e.Description) + "\n") } b.WriteString(" " + bin + " " + e.Command + "\n") } } return writeStyled(st.out, g, b.String()) }