mirror of
https://github.com/rwxrob/bonzai
synced 2024-11-12 07:10:26 +00:00
Fix help and mark, ready for 1.0
This commit is contained in:
parent
40b96f4174
commit
e1eceeee5f
@ -337,6 +337,11 @@ want the specific reasons.
|
||||
Every Bonzai binary is its own local web server which Go's rich
|
||||
standard library to draw on.
|
||||
|
||||
* **[]Section instead of map[string]string for Other**
|
||||
|
||||
This allows composition notation and ensures the author has control of
|
||||
how the Other sections appear.
|
||||
|
||||
## Style Guidelines
|
||||
|
||||
* Everything through `go fmt` or equiv, no exceptions
|
||||
|
@ -17,7 +17,7 @@ type Command interface {
|
||||
GetCommands() []string
|
||||
GetHidden() []string
|
||||
GetParams() []string
|
||||
GetOther() map[string]string
|
||||
GetOther() []string
|
||||
GetCompleter() Completer
|
||||
GetCaller() Command
|
||||
}
|
||||
|
97
help/help.go
97
help/help.go
@ -4,10 +4,14 @@
|
||||
package help
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"strings"
|
||||
|
||||
"github.com/rwxrob/bonzai/comp"
|
||||
Z "github.com/rwxrob/bonzai/z"
|
||||
"github.com/rwxrob/fn/filt"
|
||||
"github.com/rwxrob/fn/maps"
|
||||
"github.com/rwxrob/to"
|
||||
)
|
||||
|
||||
// Cmd provides help documentation for the caller allowing the specific
|
||||
@ -33,7 +37,7 @@ var Cmd = &Z.Cmd{
|
||||
if len(args) == 0 {
|
||||
args = append(args, "all")
|
||||
}
|
||||
// ForTerminal(x.Caller, args[0])
|
||||
ForTerminal(x.Caller, args[0])
|
||||
return nil
|
||||
},
|
||||
}
|
||||
@ -54,7 +58,7 @@ func helpCompleter(x comp.Command, args ...string) []string {
|
||||
if caller != nil {
|
||||
other := caller.GetOther()
|
||||
if other != nil {
|
||||
list = append(list, maps.Keys(other)...)
|
||||
list = append(list, other...)
|
||||
}
|
||||
}
|
||||
|
||||
@ -65,8 +69,6 @@ func helpCompleter(x comp.Command, args ...string) []string {
|
||||
return filt.HasPrefix(list, args[0])
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
// printIfHave takes any thing with a named field and a value, converts
|
||||
// everything to string values (with to.String) and prints it with
|
||||
// Print after passing it through Format. If the value is an empty
|
||||
@ -76,7 +78,7 @@ func printIfHave(thing, name, value any) {
|
||||
log.Printf("%v has no %v\n", to.String(thing), to.String(name))
|
||||
return
|
||||
}
|
||||
fmt.Print(Format(to.String(value)))
|
||||
Z.PrintMark(to.String(value))
|
||||
}
|
||||
|
||||
// ForTerminal converts the collective help documentation of the given
|
||||
@ -88,83 +90,110 @@ func printIfHave(thing, name, value any) {
|
||||
// terminal is not interactive (see Z.Emph).
|
||||
func ForTerminal(x *Z.Cmd, section string) {
|
||||
switch section {
|
||||
|
||||
case "name":
|
||||
printIfHave("command", "name", x.Name)
|
||||
|
||||
case "title":
|
||||
printIfHave(x.Name, "title", x.Title())
|
||||
|
||||
case "summary":
|
||||
printIfHave(x.Name, "summary", x.Summary)
|
||||
|
||||
case "params":
|
||||
printIfHave(x.Name, "params", x.UsageParams())
|
||||
|
||||
case "commands":
|
||||
printIfHave(x.Name, "commands", x.UsageCmdTitles())
|
||||
|
||||
case "description", "desc":
|
||||
printIfHave(x.Name, "description", Z.Mark(x.Description))
|
||||
|
||||
case "examples":
|
||||
log.Printf("examples are planned but not yet implemented")
|
||||
|
||||
case "legal":
|
||||
printIfHave(x.Name, "legal", x.Legal())
|
||||
|
||||
case "copyright":
|
||||
printIfHave(x.Name, "copyright", x.Copyright)
|
||||
|
||||
case "license":
|
||||
printIfHave(x.Name, "license", x.License)
|
||||
|
||||
case "version":
|
||||
printIfHave(x.Name, "version", x.Version)
|
||||
|
||||
case "all":
|
||||
Z.Println("**NAME**")
|
||||
Z.PrintlnInWrap(x.Title() + "\n")
|
||||
|
||||
Z.PrintEmph("**NAME**\n")
|
||||
Z.PrintMark(x.Title() + "\n\n")
|
||||
|
||||
// always print a synopsis so we can communicate with command
|
||||
// developers about invalid field combinations through ERRORs
|
||||
Z.Println("**SYNOPSIS**")
|
||||
|
||||
Z.PrintEmph("**SYNOPSIS**\n")
|
||||
|
||||
switch {
|
||||
|
||||
case x.Usage != "":
|
||||
Z.PrintMarkf("%v %v", x.Name, x.Usage)
|
||||
|
||||
case x.Call == nil && x.Params != nil:
|
||||
// FIXME: replace with string var from lang.go
|
||||
Z.PrintlnInWrap(
|
||||
"{ERROR: Params without Call: "+
|
||||
strings.Join(x.Params, ", ")+"}" + "\n"
|
||||
Z.PrintMarkf(
|
||||
"{ERROR: Params without Call: %v}\n\n",
|
||||
strings.Join(x.Params, ", "),
|
||||
)
|
||||
|
||||
case len(x.Commands) == 0 && x.Call == nil:
|
||||
// FIXME: replace with string var from lang.go
|
||||
Z.PrintlnInWrap("{ERROR: neither Call nor Commands defined}")+"\n"
|
||||
Z.PrintMark("{ERROR: neither Call nor Commands defined}")
|
||||
|
||||
case len(x.Commands) > 0 && x.Call == nil:
|
||||
Z.PrintMarkf("%v COMMAND", x.Name)
|
||||
|
||||
case len(x.Commands) > 0 && x.Call != nil && len(x.Params) > 0:
|
||||
Z.PrintfInWrap("**%v** (COMMAND|%v)",x.Name,x.UsageParams())
|
||||
Z.PrintMarkf("%v (COMMAND|%v)", x.Name, x.UsageParams())
|
||||
|
||||
case len(x.Commands) == 0 && x.Call != nil && len(x.Params) > 0:
|
||||
Z.PrintfInWrap( "**%v** %v", x.Name,x.UsageParams())
|
||||
Z.PrintMarkf("%v %v", x.Name, x.UsageParams())
|
||||
|
||||
case len(x.Commands) == 0 && x.Call != nil:
|
||||
fmt.Println(to.IndentWrapped(Z.Emphasize("**"+x.Name+"**"), 7, 80))
|
||||
Z.PrintMarkf(`%v`, x.Name)
|
||||
|
||||
default:
|
||||
Z.PrintMark("{ERROR: unknown synopsis combination}")
|
||||
}
|
||||
fmt.Println()
|
||||
|
||||
if len(x.Commands) > 0 {
|
||||
fmt.Println(Z.Format("**COMMANDS**"))
|
||||
fmt.Println(to.IndentWrapped(x.UsageCmdTitles(), 7, 80) + "\n")
|
||||
Z.PrintEmph("**COMMANDS**\n")
|
||||
Z.PrintIndent(x.UsageCmdTitles())
|
||||
fmt.Println()
|
||||
}
|
||||
|
||||
if len(x.Description) > 0 {
|
||||
fmt.Println(Z.Format("**DESCRIPTION**"))
|
||||
body := strings.TrimSpace(to.Dedented(x.Description))
|
||||
fmt.Println(to.IndentWrapped(Z.Format(body), 7, 80) + "\n")
|
||||
Z.PrintEmph("**DESCRIPTION**\n")
|
||||
Z.PrintMark(x.Description)
|
||||
}
|
||||
|
||||
legal := x.Legal()
|
||||
if len(legal) > 0 {
|
||||
fmt.Println(Z.Format("**LEGAL**"))
|
||||
fmt.Println(to.IndentWrapped(legal, 7, 80) + "\n")
|
||||
Z.PrintEmph("**LEGAL**\n")
|
||||
Z.PrintIndent(legal)
|
||||
fmt.Println()
|
||||
}
|
||||
|
||||
if len(x.Other) > 0 {
|
||||
for section, text := range x.Other {
|
||||
fmt.Println(Z.Format("**" + strings.ToUpper(section) + "**"))
|
||||
fmt.Println(to.IndentWrapped(text, 7, 80) + "\n")
|
||||
for _, s := range x.Other {
|
||||
Z.PrintEmphf("**%v**\n", strings.ToUpper(s.Title))
|
||||
Z.PrintMark(s.Body)
|
||||
}
|
||||
}
|
||||
|
||||
default:
|
||||
v, has := x.Other[section]
|
||||
if !has {
|
||||
printIfHave(x.Name, section, "")
|
||||
for _, s := range x.Other {
|
||||
if s.Title == section {
|
||||
Z.PrintMark(s.Body)
|
||||
}
|
||||
}
|
||||
printIfHave(x.Name, section, Z.Format(v))
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
@ -29,7 +29,7 @@ func ExampleCmd_summary() {
|
||||
func ExampleCmd_other() {
|
||||
term.AttrOff()
|
||||
x := &Z.Cmd{
|
||||
Other: map[string]string{"foo": `some foo text`},
|
||||
Other: []Z.Section{{"foo", `some foo text`}},
|
||||
}
|
||||
help.ForTerminal(x, "foo")
|
||||
// Output:
|
||||
@ -49,6 +49,7 @@ func ExampleCmd_all_Error_No_Call_No_Commands() {
|
||||
//
|
||||
// SYNOPSIS
|
||||
// {ERROR: neither Call nor Commands defined}
|
||||
|
||||
}
|
||||
|
||||
func ExampleCmd_all_Error_Params_No_Call() {
|
||||
@ -128,7 +129,7 @@ func ExampleCmd_all_Call_With_Optional_Params_and_Commands() {
|
||||
//
|
||||
// COMMANDS
|
||||
// f|foo - foo the things
|
||||
// bar - bar the things
|
||||
// bar - bar the things
|
||||
// nosum
|
||||
}
|
||||
|
||||
@ -198,9 +199,9 @@ func ExampleCmd_all_Other() {
|
||||
x := &Z.Cmd{
|
||||
Name: `cmd`,
|
||||
Call: func(_ *Z.Cmd, _ ...string) error { return nil },
|
||||
Other: map[string]string{
|
||||
"foo": "A whole section dedicated to foo.",
|
||||
"bar": "WTF is a bar anyway",
|
||||
Other: []Z.Section{
|
||||
{"FOO", "A whole section dedicated to foo."},
|
||||
{"bar", "WTF is a bar anyway"},
|
||||
},
|
||||
}
|
||||
help.ForTerminal(x, "all")
|
||||
|
60
z/cmd.go
60
z/cmd.go
@ -19,22 +19,21 @@ import (
|
||||
)
|
||||
|
||||
type Cmd struct {
|
||||
Name string `json:"name,omitempty"`
|
||||
Aliases []string `json:"aliases,omitempty"`
|
||||
Summary string `json:"summary,omitempty"`
|
||||
Usage string `json:"usage,omitempty"`
|
||||
Version string `json:"version,omitempty"`
|
||||
Copyright string `json:"copyright,omitempty"`
|
||||
License string `json:"license,omitempty"`
|
||||
Description string `json:"description,omitempty"`
|
||||
Site string `json:"site,omitempty"`
|
||||
Source string `json:"source,omitempty"`
|
||||
Issues string `json:"issues,omitempty"`
|
||||
Commands []*Cmd `json:"commands,omitempty"`
|
||||
Params []string `json:"params,omitempty"`
|
||||
Hidden []string `json:"hidden,omitempty"`
|
||||
|
||||
Other map[string]string `json:"other,omitempty"`
|
||||
Name string `json:"name,omitempty"`
|
||||
Aliases []string `json:"aliases,omitempty"`
|
||||
Summary string `json:"summary,omitempty"`
|
||||
Usage string `json:"usage,omitempty"`
|
||||
Version string `json:"version,omitempty"`
|
||||
Copyright string `json:"copyright,omitempty"`
|
||||
License string `json:"license,omitempty"`
|
||||
Description string `json:"description,omitempty"`
|
||||
Site string `json:"site,omitempty"`
|
||||
Source string `json:"source,omitempty"`
|
||||
Issues string `json:"issues,omitempty"`
|
||||
Commands []*Cmd `json:"commands,omitempty"`
|
||||
Params []string `json:"params,omitempty"`
|
||||
Hidden []string `json:"hidden,omitempty"`
|
||||
Other []Section `json:"other,omitempty"`
|
||||
|
||||
Completer comp.Completer `json:"-"`
|
||||
Conf conf.Configurer `json:"-"`
|
||||
@ -47,7 +46,16 @@ type Cmd struct {
|
||||
MinParm int `json:"-"` // minimum number of params required
|
||||
MaxParm int `json:"-"` // maximum number of params required
|
||||
|
||||
_aliases map[string]*Cmd
|
||||
_aliases map[string]*Cmd // see cacheAliases called from Run
|
||||
_sections map[string]string // see cacheSections called from Run
|
||||
}
|
||||
|
||||
// Section contains the Other sections of a command. Composition
|
||||
// notation (without Title and Body) is not only supported but
|
||||
// encouraged for clarity when reading the source for documentation.
|
||||
type Section struct {
|
||||
Title string
|
||||
Body string
|
||||
}
|
||||
|
||||
// Names returns the Name and any Aliases grouped such that the Name is
|
||||
@ -86,8 +94,6 @@ func (x *Cmd) Title() string {
|
||||
return "{ERROR: Name is empty}"
|
||||
}
|
||||
switch {
|
||||
case len(x.Summary) > 0 && len(x.Version) > 0:
|
||||
return x.Name + " (" + x.Version + ")" + " - " + x.Summary
|
||||
case len(x.Summary) > 0:
|
||||
return x.Name + " - " + x.Summary
|
||||
default:
|
||||
@ -118,6 +124,9 @@ func (x *Cmd) Legal() string {
|
||||
}
|
||||
}
|
||||
|
||||
// OtherTitles returns just the ordered titles from Other.
|
||||
func (x *Cmd) OtherTitles() []string { return maps.Keys(x._sections) }
|
||||
|
||||
func (x *Cmd) cacheAliases() {
|
||||
x._aliases = map[string]*Cmd{}
|
||||
if x.Commands == nil {
|
||||
@ -133,6 +142,16 @@ func (x *Cmd) cacheAliases() {
|
||||
}
|
||||
}
|
||||
|
||||
func (x *Cmd) cacheSections() {
|
||||
x._sections = map[string]string{}
|
||||
if len(x.Other) == 0 {
|
||||
return
|
||||
}
|
||||
for _, s := range x.Other {
|
||||
x._sections[s.Title] = s.Body
|
||||
}
|
||||
}
|
||||
|
||||
// Run is for running a command within a specific runtime (shell) and
|
||||
// performs completion if completion context is detected. Otherwise, it
|
||||
// executes the leaf Cmd returned from Seek calling its Method, and then
|
||||
@ -146,6 +165,7 @@ func (x *Cmd) cacheAliases() {
|
||||
func (x *Cmd) Run() {
|
||||
|
||||
x.cacheAliases()
|
||||
x.cacheSections()
|
||||
|
||||
// resolve Z.Aliases (if completion didn't replace them)
|
||||
if len(os.Args) > 1 {
|
||||
@ -389,7 +409,7 @@ func (x *Cmd) GetHidden() []string { return x.Hidden }
|
||||
func (x *Cmd) GetParams() []string { return x.Params }
|
||||
|
||||
// GetOther fulfills the comp.Command interface.
|
||||
func (x *Cmd) GetOther() map[string]string { return x.Other }
|
||||
func (x *Cmd) GetOther() []string { return x.OtherTitles() }
|
||||
|
||||
// GetCompleter fulfills the Command interface.
|
||||
func (x *Cmd) GetCompleter() comp.Completer { return x.Completer }
|
||||
|
15
z/mark.go
15
z/mark.go
@ -17,9 +17,11 @@ import (
|
||||
var IndentBy = 7
|
||||
|
||||
// Columns is the number of bytes (not runes) at which Wrap will wrap.
|
||||
// Default is 80. Bonzai command tree creator can change this for every
|
||||
// composite command imported their application in this one place.
|
||||
var Columns = 80
|
||||
// By default detects the terminal width (if possible) otherwise keeps
|
||||
// 80 standard (see rwxrob/term.WinSize). Bonzai command tree creator
|
||||
// can change this for every composite command imported their
|
||||
// application in this one place.
|
||||
var Columns = int(term.WinSize.Col)
|
||||
|
||||
// Lines returns the string converted into a slice of lines.
|
||||
func Lines(in string) []string { return to.Lines(in) }
|
||||
@ -145,6 +147,7 @@ MAIN:
|
||||
for s.Scan() {
|
||||
|
||||
if s.Peek("\n\n") {
|
||||
block = append(block, []byte(string(s.Rune))...)
|
||||
blocks = append(blocks, &Block{Paragraph, block})
|
||||
s.Scan()
|
||||
s.Scan()
|
||||
@ -294,11 +297,11 @@ func Mark(in string) string {
|
||||
for _, block := range blocks {
|
||||
switch block.T {
|
||||
case Paragraph:
|
||||
out += InWrap(Emph(string(block.V))) + "\n"
|
||||
out += Emph(InWrap(string(block.V))) + "\n"
|
||||
case Bulleted:
|
||||
out += Indent(Emph(string(block.V))) + "\n"
|
||||
out += Emph(Indent(string(block.V))) + "\n"
|
||||
case Numbered:
|
||||
out += Indent(Emph(string(block.V))) + "\n"
|
||||
out += Emph(Indent(string(block.V))) + "\n"
|
||||
case Verbatim:
|
||||
out += to.Indented(Indent(string(block.V)), 4) + "\n"
|
||||
default:
|
||||
|
@ -66,16 +66,18 @@ func ExampleBlocks_paragraph() {
|
||||
in := `
|
||||
Simple paragraph
|
||||
here on multiple
|
||||
lines
|
||||
lines.
|
||||
|
||||
And another one here
|
||||
with just a bit more.
|
||||
|
||||
`
|
||||
|
||||
fmt.Printf("%q\n", Z.Blocks(in)[0])
|
||||
fmt.Printf("%q\n", Z.Blocks(in)[1])
|
||||
|
||||
// Output:
|
||||
// "Simple paragraph here on multiple lines."
|
||||
// "And another one here with just a bit more."
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user