Merge remote-tracking branch 'origin/main'
All checks were successful
Build / build (push) Successful in 1m59s
All checks were successful
Build / build (push) Successful in 1m59s
This commit is contained in:
commit
a30c012bcd
8
_site/ucl/repl/index.html
Normal file
8
_site/ucl/repl/index.html
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta name="go-import" content="ucl.lmika.dev git https://lmika.dev/lmika/ucl">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
</body>
|
||||||
|
</html>
|
|
@ -8,6 +8,7 @@ import (
|
||||||
"github.com/alecthomas/participle/v2"
|
"github.com/alecthomas/participle/v2"
|
||||||
"strings"
|
"strings"
|
||||||
"syscall/js"
|
"syscall/js"
|
||||||
|
"ucl.lmika.dev/repl"
|
||||||
"ucl.lmika.dev/ucl"
|
"ucl.lmika.dev/ucl"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -20,7 +21,7 @@ func invokeUCLCallback(name string, args ...any) {
|
||||||
func initJS(ctx context.Context) {
|
func initJS(ctx context.Context) {
|
||||||
uclObj := make(map[string]any)
|
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)
|
invokeUCLCallback("onOutLine", line)
|
||||||
})))
|
})))
|
||||||
|
|
||||||
|
@ -36,7 +37,7 @@ func initJS(ctx context.Context) {
|
||||||
}
|
}
|
||||||
|
|
||||||
wantContinue := args[1].Bool()
|
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
|
var p participle.Error
|
||||||
if errors.As(err, &p) && wantContinue {
|
if errors.As(err, &p) && wantContinue {
|
||||||
invokeUCLCallback("onContinue")
|
invokeUCLCallback("onContinue")
|
||||||
|
@ -50,21 +51,3 @@ func initJS(ctx context.Context) {
|
||||||
})
|
})
|
||||||
js.Global().Set("ucl", uclObj)
|
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
|
|
||||||
//}
|
|
||||||
|
|
70
repl/docs.go
70
repl/docs.go
|
@ -5,7 +5,6 @@ import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/lmika/gopkgs/fp/maps"
|
"github.com/lmika/gopkgs/fp/maps"
|
||||||
"os"
|
|
||||||
"slices"
|
"slices"
|
||||||
"sort"
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
|
@ -31,14 +30,39 @@ func (d Doc) config(cmdName string, r *REPL) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *REPL) helpBuiltin(ctx context.Context, args ucl.CallArgs) (any, error) {
|
func (r *REPL) helpBuiltin(ctx context.Context, args ucl.CallArgs) (any, error) {
|
||||||
switch {
|
if args.NArgs() == 0 {
|
||||||
case args.NArgs() == 0:
|
return NoResults{}, r.listHelpTopics(maps.Keys(r.commandDocs))
|
||||||
names := maps.Keys(r.commandDocs)
|
}
|
||||||
sort.Strings(names)
|
|
||||||
|
|
||||||
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 {
|
found := make([]string, 0)
|
||||||
|
for name := range r.commandDocs {
|
||||||
|
if name == cmdName {
|
||||||
|
return NoResults{}, r.showHelpTopic(name)
|
||||||
|
}
|
||||||
|
|
||||||
|
if strings.Contains(name, cmdName) {
|
||||||
|
found = append(found, name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(found) == 0 {
|
||||||
|
return nil, errors.New("no help found for topic")
|
||||||
|
}
|
||||||
|
|
||||||
|
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]
|
cdoc := r.commandDocs[name]
|
||||||
if cdoc.Brief != "" {
|
if cdoc.Brief != "" {
|
||||||
fmt.Fprintf(tabWriter, "%v\t %v\n", name, r.commandDocs[name].Brief)
|
fmt.Fprintf(tabWriter, "%v\t %v\n", name, r.commandDocs[name].Brief)
|
||||||
|
@ -48,34 +72,33 @@ func (r *REPL) helpBuiltin(ctx context.Context, args ucl.CallArgs) (any, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
tabWriter.Flush()
|
tabWriter.Flush()
|
||||||
default:
|
return nil
|
||||||
var cmdName string
|
|
||||||
if err := args.Bind(&cmdName); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
docs, ok := r.commandDocs[cmdName]
|
func (r *REPL) showHelpTopic(topic string) error {
|
||||||
|
|
||||||
|
docs, ok := r.commandDocs[topic]
|
||||||
if !ok {
|
if !ok {
|
||||||
return nil, errors.New("no help docs found for command")
|
return errors.New("no help docs found for command")
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Printf("%v\n", cmdName)
|
fmt.Fprintf(r.inst.Out(), "%v\n", topic)
|
||||||
fmt.Printf(" %v\n", docs.Brief)
|
fmt.Fprintf(r.inst.Out(), " %v\n", docs.Brief)
|
||||||
|
|
||||||
if docs.Usage != "" {
|
if docs.Usage != "" {
|
||||||
fmt.Println("\nUsage:")
|
fmt.Fprintln(r.inst.Out(), "\nUsage:")
|
||||||
fmt.Printf(" %v %v\n", cmdName, docs.Usage)
|
fmt.Fprintf(r.inst.Out(), " %v %v\n", topic, docs.Usage)
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(docs.Args) > 0 {
|
if len(docs.Args) > 0 {
|
||||||
fmt.Println("\nArguments:")
|
fmt.Fprintln(r.inst.Out(), "\nArguments:")
|
||||||
|
|
||||||
docArgs := slices.Clone(docs.Args)
|
docArgs := slices.Clone(docs.Args)
|
||||||
sort.Slice(docArgs, func(i, j int) bool {
|
sort.Slice(docArgs, func(i, j int) bool {
|
||||||
return strings.TrimPrefix(docs.Args[i].Name, "-") < strings.TrimPrefix(docs.Args[j].Name, "-")
|
return strings.TrimPrefix(docs.Args[i].Name, "-") < strings.TrimPrefix(docs.Args[j].Name, "-")
|
||||||
})
|
})
|
||||||
|
|
||||||
tw := tabwriter.NewWriter(os.Stdout, 0, 0, 1, ' ', 0)
|
tw := tabwriter.NewWriter(r.inst.Out(), 0, 0, 1, ' ', 0)
|
||||||
for _, arg := range docArgs {
|
for _, arg := range docArgs {
|
||||||
fmt.Fprintf(tw, " %v\t %v\n", arg.Name, arg.Brief)
|
fmt.Fprintf(tw, " %v\t %v\n", arg.Name, arg.Brief)
|
||||||
}
|
}
|
||||||
|
@ -83,13 +106,13 @@ func (r *REPL) helpBuiltin(ctx context.Context, args ucl.CallArgs) (any, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
if docs.Detailed != "" {
|
if docs.Detailed != "" {
|
||||||
fmt.Println("\nDetails:")
|
fmt.Fprintln(r.inst.Out(), "\nDetails:")
|
||||||
|
|
||||||
lines := strings.Split(docs.Detailed, "\n")
|
lines := strings.Split(docs.Detailed, "\n")
|
||||||
|
|
||||||
trimLeft := 0
|
trimLeft := 0
|
||||||
if len(lines) == 0 {
|
if len(lines) == 0 {
|
||||||
return nil, nil
|
return nil
|
||||||
} else if len(strings.TrimSpace(lines[0])) == 0 && len(lines) > 1 {
|
} else if len(strings.TrimSpace(lines[0])) == 0 && len(lines) > 1 {
|
||||||
// indicates that the next line should indicate the indentation
|
// indicates that the next line should indicate the indentation
|
||||||
trimLeft = len(lines[1]) - len(strings.TrimLeftFunc(lines[1], unicode.IsSpace))
|
trimLeft = len(lines[1]) - len(strings.TrimLeftFunc(lines[1], unicode.IsSpace))
|
||||||
|
@ -97,11 +120,10 @@ func (r *REPL) helpBuiltin(ctx context.Context, args ucl.CallArgs) (any, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, line := range lines {
|
for _, line := range lines {
|
||||||
fmt.Printf(" %v\n", trimSpaceLeftUpto(line, trimLeft))
|
fmt.Fprintf(r.inst.Out(), " %v\n", trimSpaceLeftUpto(line, trimLeft))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
return nil
|
||||||
return NoResults{}, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func trimSpaceLeftUpto(s string, n int) string {
|
func trimSpaceLeftUpto(s string, n int) string {
|
||||||
|
|
|
@ -18,7 +18,7 @@ func (r *REPL) EvalAndDisplay(ctx context.Context, expr string) error {
|
||||||
return err
|
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) {
|
func (r *REPL) echoPrinter(ctx context.Context, w io.Writer, args []any) (err error) {
|
||||||
|
@ -89,6 +89,7 @@ func (r *REPL) displayResult(ctx context.Context, w io.Writer, res any, concise
|
||||||
fmt.Fprintf(w, "]")
|
fmt.Fprintf(w, "]")
|
||||||
} else {
|
} else {
|
||||||
// In the off-chance that this is actually a slice of printables
|
// In the off-chance that this is actually a slice of printables
|
||||||
|
if len(v) > 0 {
|
||||||
vt := reflect.SliceOf(reflect.TypeOf(v[0]))
|
vt := reflect.SliceOf(reflect.TypeOf(v[0]))
|
||||||
if tp, ok := r.typePrinters[vt]; ok {
|
if tp, ok := r.typePrinters[vt]; ok {
|
||||||
canDisplay := true
|
canDisplay := true
|
||||||
|
@ -108,6 +109,7 @@ func (r *REPL) displayResult(ctx context.Context, w io.Writer, res any, concise
|
||||||
return tp(w, typeSlice.Interface(), concise)
|
return tp(w, typeSlice.Interface(), concise)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
for i := 0; i < len(v); i++ {
|
for i := 0; i < len(v); i++ {
|
||||||
if err = r.displayResult(ctx, w, v[i], true); err != nil {
|
if err = r.displayResult(ctx, w, v[i], true); err != nil {
|
||||||
|
|
10
repl/repl.go
10
repl/repl.go
|
@ -28,17 +28,17 @@ func New(opts ...ucl.InstOption) *REPL {
|
||||||
r.inst = ucl.New(instOpts...)
|
r.inst = ucl.New(instOpts...)
|
||||||
|
|
||||||
r.SetCommand("help", r.helpBuiltin, Doc{
|
r.SetCommand("help", r.helpBuiltin, Doc{
|
||||||
Brief: "displays help about a command",
|
Brief: "displays help about a command or topic",
|
||||||
Usage: "[command]",
|
Usage: "[topic]",
|
||||||
Args: []ArgDoc{
|
Args: []ArgDoc{
|
||||||
{Name: "command", Brief: "command to display detailed help for"},
|
{Name: "topic", Brief: "topic to display"},
|
||||||
},
|
},
|
||||||
Detailed: `
|
Detailed: `
|
||||||
When used without arguments, 'help' will display the list of known commands,
|
When used without arguments, 'help' will display the list of known commands,
|
||||||
along with a brief description on what each one does.
|
along with a brief description on what each one does.
|
||||||
|
|
||||||
When used with an argument, 'help' will display a more detailed explanation
|
If <topic> is a direct match, the contents of <topic> will be displayed.
|
||||||
of what each command does.
|
Otherwise, 'help' will list the topics names that match the given substring.
|
||||||
`,
|
`,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue