Added help command

This commit is contained in:
Leon Mika 2024-12-11 21:16:08 +11:00
parent 8c5cb299c2
commit 23be2593f3
4 changed files with 120 additions and 28 deletions

View file

@ -2,6 +2,7 @@ package main
import (
"context"
"fmt"
"github.com/chzyer/readline"
"log"
"ucl.lmika.dev/repl"
@ -22,13 +23,24 @@ func main() {
)
ctx := context.Background()
instRepl.SetCommand("hello", func(ctx context.Context, args ucl.CallArgs) (any, error) {
fmt.Println("hello")
return nil, nil
}, repl.Doc{
Brief: "displays hello",
Detailed: `
This displays the message 'hello' to the terminal.
It then terminates.
`,
})
for {
line, err := rl.Readline()
if err != nil { // io.EOF
break
}
if err := ucl.EvalAndDisplay(ctx, inst, line); err != nil {
if err := instRepl.EvalAndDisplay(ctx, line); err != nil {
log.Printf("%T: %v", err, err)
}
}

View file

@ -1,6 +1,93 @@
package repl
import (
"context"
"errors"
"fmt"
"github.com/lmika/gopkgs/fp/maps"
"os"
"sort"
"strings"
"text/tabwriter"
"ucl.lmika.dev/ucl"
"unicode"
)
type Doc struct {
Brief string
Detailed string
}
func (d Doc) config(cmdName string, r *REPL) {
r.commandDocs[cmdName] = d
}
func (r *REPL) helpBuiltin(ctx context.Context, args ucl.CallArgs) (any, error) {
switch {
case args.NArgs() == 0:
names := maps.Keys(r.commandDocs)
sort.Strings(names)
tabWriter := tabwriter.NewWriter(os.Stdout, 0, 0, 1, ' ', 0)
for _, name := range names {
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)
}
}
tabWriter.Flush()
default:
var cmdName string
if err := args.Bind(&cmdName); err != nil {
return nil, err
}
docs, ok := r.commandDocs[cmdName]
if !ok {
return nil, errors.New("no help docs found for command")
}
fmt.Printf("%v\n", cmdName)
fmt.Printf(" %v\n", docs.Brief)
if docs.Detailed != "" {
fmt.Println("")
fmt.Println("Details:")
lines := strings.Split(docs.Detailed, "\n")
trimLeft := 0
if len(lines) == 0 {
return nil, 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:]
}
for _, line := range lines {
fmt.Printf(" %v\n", trimSpaceLeftUpto(line, trimLeft))
}
}
}
return NoResults{}, nil
}
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
}

View file

@ -7,6 +7,8 @@ import (
"ucl.lmika.dev/ucl"
)
type NoResults struct{}
func (r *REPL) EvalAndDisplay(ctx context.Context, expr string) error {
res, err := r.inst.Eval(ctx, expr)
if err != nil {
@ -18,20 +20,26 @@ func (r *REPL) EvalAndDisplay(ctx context.Context, expr string) error {
func displayResult(ctx context.Context, inst *ucl.Inst, res any) (err error) {
switch v := res.(type) {
case NoResults:
return nil
case nil:
if _, err = fmt.Fprintln(os.Stdout, "(nil)"); err != nil {
return err
}
case listable:
case ucl.Listable:
for i := 0; i < v.Len(); i++ {
if err = displayResult(ctx, inst, v.Index(i)); err != nil {
return err
}
}
default:
case ucl.Object:
if _, err = fmt.Fprintln(os.Stdout, v.String()); err != nil {
return err
}
default:
if _, err = fmt.Fprintln(os.Stdout, v); err != nil {
return err
}
}
return nil
}

View file

@ -1,10 +1,6 @@
package repl
import (
"context"
"fmt"
"github.com/lmika/gopkgs/fp/maps"
"sort"
"ucl.lmika.dev/ucl"
)
@ -25,7 +21,16 @@ func New(opts ...ucl.InstOption) *REPL {
inst: inst,
commandDocs: make(map[string]Doc),
}
inst.SetBuiltin("help", r.helpBuiltin)
r.SetCommand("help", r.helpBuiltin, Doc{
Brief: "displays help about a command",
Detailed: `
When used without arguments, 'help' will display the list of known commands,
along with a brief description on what each one does.
When used with an argument, 'help' will display a more detailed explanation
of what each command does.
`,
})
return r
}
@ -42,23 +47,3 @@ func (r *REPL) SetCommand(name string, fn ucl.BuiltinHandler, opts ...CommandOpt
r.inst.SetBuiltin(name, fn)
}
func (r *REPL) helpBuiltin(ctx context.Context, args ucl.CallArgs) (any, error) {
switch {
case args.NArgs() == 0:
// TEMP
names := maps.Keys(r.commandDocs)
sort.Strings(names)
for _, name := range names {
cdoc := r.commandDocs[name]
if cdoc.Brief != "" {
fmt.Println("%v\t%v", name, r.commandDocs[name])
} else {
fmt.Println("%v", name)
}
}
// END TEMP
}
return nil, nil
}