diff --git a/_site/ucl/repl/index.html b/_site/ucl/repl/index.html new file mode 100644 index 0000000..a1529ea --- /dev/null +++ b/_site/ucl/repl/index.html @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/cmd/playwasm/jsiter.go b/cmd/playwasm/jsiter.go index dca6b12..1e62e6d 100644 --- a/cmd/playwasm/jsiter.go +++ b/cmd/playwasm/jsiter.go @@ -8,6 +8,7 @@ import ( "github.com/alecthomas/participle/v2" "strings" "syscall/js" + "ucl.lmika.dev/repl" "ucl.lmika.dev/ucl" ) @@ -20,7 +21,7 @@ func invokeUCLCallback(name string, args ...any) { func initJS(ctx context.Context) { uclObj := make(map[string]any) - inst := ucl.New(ucl.WithOut(ucl.LineHandler(func(line string) { + replInst := repl.New(ucl.WithOut(ucl.LineHandler(func(line string) { invokeUCLCallback("onOutLine", line) }))) @@ -36,7 +37,7 @@ func initJS(ctx context.Context) { } wantContinue := args[1].Bool() - if err := ucl.EvalAndDisplay(ctx, inst, cmdLine); err != nil { + if err := replInst.EvalAndDisplay(ctx, cmdLine); err != nil { var p participle.Error if errors.As(err, &p) && wantContinue { invokeUCLCallback("onContinue") @@ -50,21 +51,3 @@ func initJS(ctx context.Context) { }) js.Global().Set("ucl", uclObj) } - -// -//type uclOut struct { -// lineBuffer *bytes.Buffer -// writeLine func(line string) -//} -// -//func (uo *uclOut) Write(p []byte) (n int, err error) { -// for _, b := range p { -// if b == '\n' { -// uo.writeLine(uo.lineBuffer.String()) -// uo.lineBuffer.Reset() -// } else { -// uo.lineBuffer.WriteByte(b) -// } -// } -// return len(p), nil -//} diff --git a/repl/docs.go b/repl/docs.go index 40e3485..632b822 100644 --- a/repl/docs.go +++ b/repl/docs.go @@ -5,7 +5,6 @@ import ( "errors" "fmt" "github.com/lmika/gopkgs/fp/maps" - "os" "slices" "sort" "strings" @@ -31,77 +30,98 @@ func (d Doc) config(cmdName string, r *REPL) { } 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) + if args.NArgs() == 0 { + return NoResults{}, r.listHelpTopics(maps.Keys(r.commandDocs)) + } - tabWriter := tabwriter.NewWriter(os.Stdout, 0, 0, 1, ' ', 0) + var cmdName string + if err := args.Bind(&cmdName); err != nil { + return nil, err + } - 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.Usage != "" { - fmt.Println("\nUsage:") - fmt.Printf(" %v %v\n", cmdName, docs.Usage) - } - - if len(docs.Args) > 0 { - fmt.Println("\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, "-") - }) - - tw := tabwriter.NewWriter(os.Stdout, 0, 0, 1, ' ', 0) - for _, arg := range docArgs { - fmt.Fprintf(tw, " %v\t %v\n", arg.Name, arg.Brief) - } - tw.Flush() - } - - if docs.Detailed != "" { - fmt.Println("\nDetails:") - - 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)) - } + found := make([]string, 0) + for name := range r.commandDocs { + if strings.Contains(name, cmdName) { + found = append(found, name) } } - return NoResults{}, nil + + if len(found) == 0 { + return nil, errors.New("no help found for topic") + } else if len(found) == 1 { + return NoResults{}, r.showHelpTopic(found[0]) + } + + 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) + } + } + + 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) + + 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, "-") + }) + + 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) + } + tw.Flush() + } + + if docs.Detailed != "" { + fmt.Fprintln(r.inst.Out(), "\nDetails:") + + lines := strings.Split(docs.Detailed, "\n") + + 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:] + } + + for _, line := range lines { + fmt.Fprintf(r.inst.Out(), " %v\n", trimSpaceLeftUpto(line, trimLeft)) + } + } + return nil } func trimSpaceLeftUpto(s string, n int) string { diff --git a/repl/evaldisplay.go b/repl/evaldisplay.go index d062778..f32ae5b 100644 --- a/repl/evaldisplay.go +++ b/repl/evaldisplay.go @@ -18,7 +18,7 @@ func (r *REPL) EvalAndDisplay(ctx context.Context, expr string) error { return err } - return r.displayResult(ctx, os.Stdout, res, false) + return r.displayResult(ctx, r.inst.Out(), res, false) } func (r *REPL) echoPrinter(ctx context.Context, w io.Writer, args []any) (err error) {