Added custom printers
This commit is contained in:
parent
b9f349d1d7
commit
ee2e9464a7
57
cmd/cmsh/fancy.go
Normal file
57
cmd/cmsh/fancy.go
Normal file
|
@ -0,0 +1,57 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"ucl.lmika.dev/repl"
|
||||||
|
"ucl.lmika.dev/ucl"
|
||||||
|
)
|
||||||
|
|
||||||
|
type FancyType struct {
|
||||||
|
Foo string
|
||||||
|
Bar string
|
||||||
|
}
|
||||||
|
|
||||||
|
var newFancyDoc = repl.Doc{
|
||||||
|
Brief: "returns something fancy",
|
||||||
|
Detailed: `
|
||||||
|
This will create and return something fancy.
|
||||||
|
`,
|
||||||
|
}
|
||||||
|
|
||||||
|
func newFancy(ctx context.Context, args ucl.CallArgs) (any, error) {
|
||||||
|
return FancyType{Foo: "is foo", Bar: "is bar"}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func manyFancies(ctx context.Context, args ucl.CallArgs) (any, error) {
|
||||||
|
return []FancyType{
|
||||||
|
{Foo: "foo 1", Bar: "bar 1"},
|
||||||
|
{Foo: "foo 2", Bar: "bar 2"},
|
||||||
|
{Foo: "foo 3", Bar: "bar 3"},
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func displayFancy(w io.Writer, f FancyType, concise bool) error {
|
||||||
|
if concise {
|
||||||
|
_, err := fmt.Fprintf(w, "%s:%s", f.Foo, f.Bar)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Fprintf(w, "Foo.. %s\n", f.Foo)
|
||||||
|
fmt.Fprintf(w, "Bar.. %s\n", f.Bar)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func displayFancies(w io.Writer, fs []FancyType, concise bool) error {
|
||||||
|
if concise {
|
||||||
|
_, err := fmt.Fprintf(w, "%d fancies", len(fs))
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Fprintln(w, "FOO\tBAR")
|
||||||
|
for _, f := range fs {
|
||||||
|
fmt.Fprintf(w, "%v\t%v\n", f.Foo, f.Bar)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -33,6 +33,11 @@ func main() {
|
||||||
It then terminates.
|
It then terminates.
|
||||||
`,
|
`,
|
||||||
})
|
})
|
||||||
|
instRepl.SetCommand("new-fancy", newFancy, newFancyDoc)
|
||||||
|
instRepl.SetCommand("many-fancies", manyFancies)
|
||||||
|
|
||||||
|
repl.AddTypePrinter(instRepl, displayFancy)
|
||||||
|
repl.AddTypePrinter(instRepl, displayFancies)
|
||||||
|
|
||||||
for {
|
for {
|
||||||
line, err := rl.Readline()
|
line, err := rl.Readline()
|
||||||
|
|
|
@ -3,7 +3,10 @@ package repl
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io"
|
||||||
"os"
|
"os"
|
||||||
|
"reflect"
|
||||||
|
"strings"
|
||||||
"ucl.lmika.dev/ucl"
|
"ucl.lmika.dev/ucl"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -15,31 +18,118 @@ func (r *REPL) EvalAndDisplay(ctx context.Context, expr string) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
return displayResult(ctx, r.inst, res)
|
return r.displayResult(ctx, os.Stdout, res, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
func displayResult(ctx context.Context, inst *ucl.Inst, res any) (err error) {
|
func (r *REPL) echoPrinter(ctx context.Context, w io.Writer, args []any) (err error) {
|
||||||
|
if len(args) == 0 {
|
||||||
|
_, err := fmt.Fprintln(w)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
var line strings.Builder
|
||||||
|
for _, arg := range args {
|
||||||
|
if err := r.displayResult(ctx, &line, arg, len(args) > 1); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
res := line.String()
|
||||||
|
if strings.HasSuffix(res, "\n") {
|
||||||
|
res = res[:len(res)-1]
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = fmt.Fprintln(w, res)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *REPL) displayResult(ctx context.Context, w io.Writer, res any, concise bool) (err error) {
|
||||||
|
// Check type printers
|
||||||
|
tp, ok := r.typePrinters[reflect.TypeOf(res)]
|
||||||
|
if ok {
|
||||||
|
return tp(w, res, concise)
|
||||||
|
}
|
||||||
|
|
||||||
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 ucl.Listable:
|
case ucl.Listable:
|
||||||
for i := 0; i < v.Len(); i++ {
|
if concise {
|
||||||
if err = displayResult(ctx, inst, v.Index(i)); err != nil {
|
fmt.Fprintf(w, "[")
|
||||||
return err
|
for i := 0; i < v.Len(); i++ {
|
||||||
|
if i > 0 {
|
||||||
|
fmt.Fprintf(w, " ")
|
||||||
|
}
|
||||||
|
if err = r.displayResult(ctx, w, v.Index(i), true); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fmt.Fprintf(w, "]")
|
||||||
|
} else {
|
||||||
|
for i := 0; i < v.Len(); i++ {
|
||||||
|
if err = r.displayResult(ctx, w, v.Index(i), true); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
fmt.Fprintf(w, "\n")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case []interface{}:
|
||||||
|
if concise {
|
||||||
|
fmt.Fprintf(w, "[")
|
||||||
|
for i := 0; i < len(v); i++ {
|
||||||
|
if i > 0 {
|
||||||
|
fmt.Fprintf(w, " ")
|
||||||
|
}
|
||||||
|
if err = r.displayResult(ctx, w, v[i], true); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fmt.Fprintf(w, "]")
|
||||||
|
} else {
|
||||||
|
// In the off-chance that this is actually a slice of printables
|
||||||
|
vt := reflect.SliceOf(reflect.TypeOf(v[0]))
|
||||||
|
if tp, ok := r.typePrinters[vt]; ok {
|
||||||
|
canDisplay := true
|
||||||
|
|
||||||
|
typeSlice := reflect.MakeSlice(vt, len(v), len(v))
|
||||||
|
for i := 0; i < len(v); i++ {
|
||||||
|
vv := reflect.ValueOf(v[i])
|
||||||
|
if vv.CanConvert(vt.Elem()) {
|
||||||
|
typeSlice.Index(i).Set(vv)
|
||||||
|
} else {
|
||||||
|
canDisplay = false
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if canDisplay {
|
||||||
|
return tp(w, typeSlice.Interface(), concise)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := 0; i < len(v); i++ {
|
||||||
|
if err = r.displayResult(ctx, w, v[i], true); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
fmt.Fprintf(w, "\n")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
case ucl.Object:
|
case ucl.Object:
|
||||||
if _, err = fmt.Fprintln(os.Stdout, v.String()); err != nil {
|
if _, err = fmt.Fprint(w, v.String()); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
if !concise {
|
||||||
|
fmt.Fprintln(w)
|
||||||
|
}
|
||||||
default:
|
default:
|
||||||
if _, err = fmt.Fprintln(os.Stdout, v); err != nil {
|
if _, err = fmt.Fprint(w, v); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
if !concise {
|
||||||
|
fmt.Fprintln(w)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
18
repl/repl.go
18
repl/repl.go
|
@ -1,6 +1,9 @@
|
||||||
package repl
|
package repl
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"io"
|
||||||
|
"reflect"
|
||||||
|
"slices"
|
||||||
"ucl.lmika.dev/ucl"
|
"ucl.lmika.dev/ucl"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -11,16 +14,19 @@ type CommandOpt interface {
|
||||||
type REPL struct {
|
type REPL struct {
|
||||||
inst *ucl.Inst
|
inst *ucl.Inst
|
||||||
|
|
||||||
commandDocs map[string]Doc
|
commandDocs map[string]Doc
|
||||||
|
typePrinters map[reflect.Type]func(w io.Writer, v any, brief bool) error
|
||||||
}
|
}
|
||||||
|
|
||||||
func New(opts ...ucl.InstOption) *REPL {
|
func New(opts ...ucl.InstOption) *REPL {
|
||||||
inst := ucl.New(opts...)
|
|
||||||
|
|
||||||
r := &REPL{
|
r := &REPL{
|
||||||
inst: inst,
|
commandDocs: make(map[string]Doc),
|
||||||
commandDocs: make(map[string]Doc),
|
typePrinters: make(map[reflect.Type]func(w io.Writer, v any, brief bool) error),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
instOpts := append(slices.Clone(opts), ucl.WithCustomEchoPrinter(r.echoPrinter))
|
||||||
|
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",
|
||||||
Usage: "[command]",
|
Usage: "[command]",
|
||||||
|
@ -36,6 +42,8 @@ func New(opts ...ucl.InstOption) *REPL {
|
||||||
`,
|
`,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
AddTypePrinter(r, func(w io.Writer, t NoResults, concise bool) error { return nil })
|
||||||
|
|
||||||
return r
|
return r
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
20
repl/typeprinter.go
Normal file
20
repl/typeprinter.go
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
package repl
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"reflect"
|
||||||
|
)
|
||||||
|
|
||||||
|
func AddTypePrinter[T any](r *REPL, p func(w io.Writer, t T, concise bool) error) {
|
||||||
|
var t T
|
||||||
|
|
||||||
|
tt := reflect.TypeOf(t)
|
||||||
|
r.typePrinters[tt] = func(w io.Writer, v any, concise bool) error {
|
||||||
|
vt, ok := v.(T)
|
||||||
|
if !ok {
|
||||||
|
return fmt.Errorf("cannot convert %v to T", v)
|
||||||
|
}
|
||||||
|
return p(w, vt, concise)
|
||||||
|
}
|
||||||
|
}
|
|
@ -9,6 +9,19 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
func echoBuiltin(ctx context.Context, args invocationArgs) (Object, error) {
|
func echoBuiltin(ctx context.Context, args invocationArgs) (Object, error) {
|
||||||
|
echoPrinter := args.inst.echoPrinter
|
||||||
|
if echoPrinter != nil {
|
||||||
|
convertedArgs := make([]interface{}, len(args.args))
|
||||||
|
for i, arg := range args.args {
|
||||||
|
if convArg, ok := toGoValue(arg); ok {
|
||||||
|
convertedArgs[i] = convArg
|
||||||
|
} else {
|
||||||
|
convertedArgs[i] = arg
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil, echoPrinter(ctx, args.inst.out, convertedArgs)
|
||||||
|
}
|
||||||
|
|
||||||
if len(args.args) == 0 {
|
if len(args.args) == 0 {
|
||||||
if _, err := fmt.Fprintln(args.inst.Out()); err != nil {
|
if _, err := fmt.Fprintln(args.inst.Out()); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|
|
@ -11,6 +11,7 @@ import (
|
||||||
type Inst struct {
|
type Inst struct {
|
||||||
out io.Writer
|
out io.Writer
|
||||||
missingBuiltinHandler MissingBuiltinHandler
|
missingBuiltinHandler MissingBuiltinHandler
|
||||||
|
echoPrinter EchoPrinter
|
||||||
|
|
||||||
rootEC *evalCtx
|
rootEC *evalCtx
|
||||||
}
|
}
|
||||||
|
@ -37,6 +38,14 @@ func WithModule(module Module) InstOption {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type EchoPrinter func(ctx context.Context, w io.Writer, args []any) error
|
||||||
|
|
||||||
|
func WithCustomEchoPrinter(echoPrinter EchoPrinter) InstOption {
|
||||||
|
return func(i *Inst) {
|
||||||
|
i.echoPrinter = echoPrinter
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
type Module struct {
|
type Module struct {
|
||||||
Name string
|
Name string
|
||||||
Builtins map[string]BuiltinHandler
|
Builtins map[string]BuiltinHandler
|
||||||
|
|
Loading…
Reference in a new issue