Files
plainleaf/cli/repl.go
T
Zef Hemel 338c78f806 Iterating on the CLI
* Renamed 'lua' to 'eval'
* Renamed 'luascript' to 'script'
* Added 'describe' to describe LIQ and lists tags that have defined
  schemas

Tests for all of this
2026-04-16 10:58:20 +02:00

160 lines
2.9 KiB
Go

package cli
import (
"fmt"
"io"
"os"
"strings"
"time"
"github.com/chzyer/readline"
"github.com/spf13/cobra"
)
func ReplCommand() *cobra.Command {
return &cobra.Command{
Use: "repl",
Short: "Interactive Lua REPL",
RunE: func(cmd *cobra.Command, args []string) error {
conn, err := connFromFlags(cmd)
if err != nil {
return err
}
return startRepl(conn)
},
}
}
func isIncomplete(code string) bool {
depth := 0
for _, word := range strings.Fields(code) {
switch word {
case "do", "function", "if", "repeat":
depth++
case "end", "until":
depth--
}
}
for _, ch := range code {
switch ch {
case '(', '[', '{':
depth++
case ')', ']', '}':
depth--
}
}
return depth > 0
}
func startRepl(conn *SpaceConnection) error {
rl, err := readline.NewEx(&readline.Config{
Prompt: "lua> ",
InterruptPrompt: "^C",
EOFPrompt: "",
})
if err != nil {
return err
}
defer rl.Close()
fmt.Println("SilverBullet Lua REPL. Type .exit or Ctrl-D to quit.")
var scriptMode bool
var scriptBuffer string
var multiLineBuffer string
for {
line, err := rl.Readline()
if err == readline.ErrInterrupt {
if multiLineBuffer != "" || scriptMode {
multiLineBuffer = ""
scriptMode = false
scriptBuffer = ""
rl.SetPrompt("lua> ")
continue
}
break
}
if err == io.EOF {
break
}
trimmed := strings.TrimSpace(line)
if trimmed == ".exit" {
break
}
if trimmed == ".script" {
scriptMode = true
scriptBuffer = ""
fmt.Println("Entering script mode. Type .end to execute.")
rl.SetPrompt("...> ")
continue
}
if scriptMode {
if trimmed == ".end" {
scriptMode = false
rl.SetPrompt("lua> ")
if strings.TrimSpace(scriptBuffer) != "" {
result, err := conn.EvalLuaScript(scriptBuffer)
if err != nil {
fmt.Fprintf(rl.Stderr(), "Error: %s\n", err)
} else {
FormatOutput(os.Stdout, result, OutputText)
}
}
continue
}
scriptBuffer += line + "\n"
continue
}
if strings.HasPrefix(trimmed, ".timeout ") {
var val int
if _, err := fmt.Sscanf(trimmed, ".timeout %d", &val); err != nil || val <= 0 {
fmt.Fprintln(rl.Stderr(), "Invalid timeout value")
} else {
conn.Timeout = time.Duration(val) * time.Second
fmt.Printf("Timeout set to %ds\n", val)
}
continue
}
if multiLineBuffer != "" {
multiLineBuffer += "\n" + line
} else {
multiLineBuffer = line
}
if isIncomplete(multiLineBuffer) {
rl.SetPrompt("...> ")
continue
}
code := multiLineBuffer
multiLineBuffer = ""
rl.SetPrompt("lua> ")
if strings.TrimSpace(code) == "" {
continue
}
isMultiLine := strings.Contains(code, "\n")
var result any
if isMultiLine {
result, err = conn.EvalLuaScript(code)
} else {
result, err = conn.EvalLua(code)
}
if err != nil {
fmt.Fprintf(rl.Stderr(), "Error: %s\n", err)
} else {
FormatOutput(os.Stdout, result, OutputText)
}
}
fmt.Println()
return nil
}