Added help command
This commit is contained in:
parent
8c5cb299c2
commit
23be2593f3
|
@ -2,6 +2,7 @@ package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"fmt"
|
||||||
"github.com/chzyer/readline"
|
"github.com/chzyer/readline"
|
||||||
"log"
|
"log"
|
||||||
"ucl.lmika.dev/repl"
|
"ucl.lmika.dev/repl"
|
||||||
|
@ -22,13 +23,24 @@ func main() {
|
||||||
)
|
)
|
||||||
ctx := context.Background()
|
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 {
|
for {
|
||||||
line, err := rl.Readline()
|
line, err := rl.Readline()
|
||||||
if err != nil { // io.EOF
|
if err != nil { // io.EOF
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := ucl.EvalAndDisplay(ctx, inst, line); err != nil {
|
if err := instRepl.EvalAndDisplay(ctx, line); err != nil {
|
||||||
log.Printf("%T: %v", err, err)
|
log.Printf("%T: %v", err, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
87
repl/docs.go
87
repl/docs.go
|
@ -1,6 +1,93 @@
|
||||||
package repl
|
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 {
|
type Doc struct {
|
||||||
Brief string
|
Brief string
|
||||||
Detailed 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
|
||||||
|
}
|
||||||
|
|
|
@ -7,6 +7,8 @@ import (
|
||||||
"ucl.lmika.dev/ucl"
|
"ucl.lmika.dev/ucl"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type NoResults struct{}
|
||||||
|
|
||||||
func (r *REPL) EvalAndDisplay(ctx context.Context, expr string) error {
|
func (r *REPL) EvalAndDisplay(ctx context.Context, expr string) error {
|
||||||
res, err := r.inst.Eval(ctx, expr)
|
res, err := r.inst.Eval(ctx, expr)
|
||||||
if err != nil {
|
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) {
|
func displayResult(ctx context.Context, inst *ucl.Inst, res any) (err error) {
|
||||||
switch v := res.(type) {
|
switch v := res.(type) {
|
||||||
|
case NoResults:
|
||||||
|
return nil
|
||||||
case nil:
|
case nil:
|
||||||
if _, err = fmt.Fprintln(os.Stdout, "(nil)"); err != nil {
|
if _, err = fmt.Fprintln(os.Stdout, "(nil)"); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
case listable:
|
case ucl.Listable:
|
||||||
for i := 0; i < v.Len(); i++ {
|
for i := 0; i < v.Len(); i++ {
|
||||||
if err = displayResult(ctx, inst, v.Index(i)); err != nil {
|
if err = displayResult(ctx, inst, v.Index(i)); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
default:
|
case ucl.Object:
|
||||||
if _, err = fmt.Fprintln(os.Stdout, v.String()); err != nil {
|
if _, err = fmt.Fprintln(os.Stdout, v.String()); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
default:
|
||||||
|
if _, err = fmt.Fprintln(os.Stdout, v); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
35
repl/repl.go
35
repl/repl.go
|
@ -1,10 +1,6 @@
|
||||||
package repl
|
package repl
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
"github.com/lmika/gopkgs/fp/maps"
|
|
||||||
"sort"
|
|
||||||
"ucl.lmika.dev/ucl"
|
"ucl.lmika.dev/ucl"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -25,7 +21,16 @@ func New(opts ...ucl.InstOption) *REPL {
|
||||||
inst: inst,
|
inst: inst,
|
||||||
commandDocs: make(map[string]Doc),
|
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
|
return r
|
||||||
}
|
}
|
||||||
|
@ -42,23 +47,3 @@ func (r *REPL) SetCommand(name string, fn ucl.BuiltinHandler, opts ...CommandOpt
|
||||||
|
|
||||||
r.inst.SetBuiltin(name, fn)
|
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
|
|
||||||
}
|
|
||||||
|
|
Loading…
Reference in a new issue