Added help command
This commit is contained in:
parent
8c5cb299c2
commit
23be2593f3
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
87
repl/docs.go
87
repl/docs.go
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
35
repl/repl.go
35
repl/repl.go
|
@ -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
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue