ucl/repl/docs.go

141 lines
2.9 KiB
Go
Raw Normal View History

2024-12-11 09:47:05 +00:00
package repl
2024-12-11 10:16:08 +00:00
import (
"context"
"errors"
"fmt"
"github.com/lmika/gopkgs/fp/maps"
2024-12-11 10:23:53 +00:00
"slices"
2024-12-11 10:16:08 +00:00
"sort"
"strings"
"text/tabwriter"
"ucl.lmika.dev/ucl"
"unicode"
)
2024-12-11 10:23:53 +00:00
type ArgDoc struct {
Name string
Brief string
}
2024-12-11 09:47:05 +00:00
type Doc struct {
Brief string
2024-12-11 10:23:53 +00:00
Usage string
Args []ArgDoc
2024-12-11 09:47:05 +00:00
Detailed string
}
2024-12-11 10:16:08 +00:00
func (d Doc) config(cmdName string, r *REPL) {
r.commandDocs[cmdName] = d
}
func (r *REPL) helpBuiltin(ctx context.Context, args ucl.CallArgs) (any, error) {
2024-12-11 20:47:52 +00:00
if args.NArgs() == 0 {
return NoResults{}, r.listHelpTopics(maps.Keys(r.commandDocs))
}
2024-12-11 10:16:08 +00:00
2024-12-11 20:47:52 +00:00
var cmdName string
if err := args.Bind(&cmdName); err != nil {
return nil, err
}
2024-12-11 10:16:08 +00:00
2024-12-11 20:47:52 +00:00
found := make([]string, 0)
for name := range r.commandDocs {
if strings.Contains(name, cmdName) {
found = append(found, name)
2024-12-11 10:16:08 +00:00
}
2024-12-11 20:47:52 +00:00
}
2024-12-11 10:16:08 +00:00
2024-12-11 20:47:52 +00:00
if len(found) == 0 {
return nil, errors.New("no help found for topic")
} else if len(found) == 1 {
return NoResults{}, r.showHelpTopic(found[0])
}
2024-12-11 10:16:08 +00:00
2024-12-11 20:47:52 +00:00
return NoResults{}, r.listHelpTopics(found)
}
func (r *REPL) listHelpTopics(topics []string) error {
sort.Strings(topics)
tabWriter := tabwriter.NewWriter(r.inst.Out(), 0, 0, 1, ' ', 0)
for _, name := range topics {
cdoc := r.commandDocs[name]
if cdoc.Brief != "" {
fmt.Fprintf(tabWriter, "%v\t %v\n", name, r.commandDocs[name].Brief)
} else {
fmt.Fprintf(tabWriter, "%v\n", name)
2024-12-11 10:23:53 +00:00
}
2024-12-11 20:47:52 +00:00
}
2024-12-11 10:23:53 +00:00
2024-12-11 20:47:52 +00:00
tabWriter.Flush()
return nil
}
func (r *REPL) showHelpTopic(topic string) error {
docs, ok := r.commandDocs[topic]
if !ok {
return errors.New("no help docs found for command")
}
fmt.Fprintf(r.inst.Out(), "%v\n", topic)
fmt.Fprintf(r.inst.Out(), " %v\n", docs.Brief)
2024-12-11 10:23:53 +00:00
2024-12-11 20:47:52 +00:00
if docs.Usage != "" {
fmt.Fprintln(r.inst.Out(), "\nUsage:")
fmt.Fprintf(r.inst.Out(), " %v %v\n", topic, docs.Usage)
}
if len(docs.Args) > 0 {
fmt.Fprintln(r.inst.Out(), "\nArguments:")
docArgs := slices.Clone(docs.Args)
sort.Slice(docArgs, func(i, j int) bool {
return strings.TrimPrefix(docs.Args[i].Name, "-") < strings.TrimPrefix(docs.Args[j].Name, "-")
})
2024-12-11 10:23:53 +00:00
2024-12-11 20:47:52 +00:00
tw := tabwriter.NewWriter(r.inst.Out(), 0, 0, 1, ' ', 0)
for _, arg := range docArgs {
fmt.Fprintf(tw, " %v\t %v\n", arg.Name, arg.Brief)
2024-12-11 10:23:53 +00:00
}
2024-12-11 20:47:52 +00:00
tw.Flush()
}
2024-12-11 10:23:53 +00:00
2024-12-11 20:47:52 +00:00
if docs.Detailed != "" {
fmt.Fprintln(r.inst.Out(), "\nDetails:")
2024-12-11 10:16:08 +00:00
2024-12-11 20:47:52 +00:00
lines := strings.Split(docs.Detailed, "\n")
2024-12-11 10:16:08 +00:00
2024-12-11 20:47:52 +00:00
trimLeft := 0
if len(lines) == 0 {
return nil
} else if len(strings.TrimSpace(lines[0])) == 0 && len(lines) > 1 {
// indicates that the next line should indicate the indentation
trimLeft = len(lines[1]) - len(strings.TrimLeftFunc(lines[1], unicode.IsSpace))
lines = lines[1:]
}
2024-12-11 10:16:08 +00:00
2024-12-11 20:47:52 +00:00
for _, line := range lines {
fmt.Fprintf(r.inst.Out(), " %v\n", trimSpaceLeftUpto(line, trimLeft))
2024-12-11 10:16:08 +00:00
}
}
2024-12-11 20:47:52 +00:00
return nil
2024-12-11 10:16:08 +00:00
}
func trimSpaceLeftUpto(s string, n int) string {
if n == 0 {
return s
}
for i, c := range s {
if i >= n {
return s[i:]
} else if !unicode.IsSpace(c) {
return s[i:]
}
}
return s
}