@@ 1,4 1,5 @@
bin/**
+libexec/**
# Created by https://www.toptal.com/developers/gitignore/api/go,emacs,vim,vscode
# Edit at https://www.toptal.com/developers/gitignore?templates=go,emacs,vim,vscode
@@ 104,4 105,4 @@ tags
!.vscode/extensions.json
*.code-workspace
-# End of https://www.toptal.com/developers/gitignore/api/go,emacs,vim,vscode>
\ No newline at end of file
+# End of https://www.toptal.com/developers/gitignore/api/go,emacs,vim,vscode
@@ 3,8 3,6 @@ github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03
github.com/NYTimes/gziphandler v0.0.0-20170623195520-56545f4a5d46/go.mod h1:3wb06e3pkSAbeQ52E9H9iFoQsEEwGN64994WTCIhntQ=
github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0=
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE=
-github.com/Shopify/go-lua v0.0.0-20221004153744-91867de107cf h1:VSUCKpFV0AfYGslgQSRdBI9HCVp1TCXJmScWD0VXL5g=
-github.com/Shopify/go-lua v0.0.0-20221004153744-91867de107cf/go.mod h1:UCCi6u6YA8oXfJ15vEMiMGIaWWfLiOacinBPHxLXtEU=
github.com/agext/levenshtein v1.2.1 h1:QmvMAjj2aEICytGiWzmxoE0x2KZvE0fvmqMOfy2tjT8=
github.com/agext/levenshtein v1.2.1/go.mod h1:JEDfjyjHDjOF/1e4FlBE/PkbqA9OfWu2ki2W0IB5558=
github.com/apparentlymart/go-dump v0.0.0-20180507223929-23540a00eaa3/go.mod h1:oL81AME2rN47vu18xqj1S1jPIPuN7afo62yKTNn3XMM=
@@ 127,6 125,8 @@ github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/
github.com/vmihailenco/msgpack v3.3.3+incompatible/go.mod h1:fy3FlTQTDXWkZ7Bh6AcGMlsjHatGryHQYUTf1ShIgkk=
github.com/vmihailenco/msgpack/v4 v4.3.12/go.mod h1:gborTTJjAo/GWTqqRjrLCn9pgNN+NXzzngzBKDPIqw4=
github.com/vmihailenco/tagparser v0.1.1/go.mod h1:OeAg3pn3UbLjkWt+rN9oFYB6u/cQgqMEUPoW2WPyhdI=
+github.com/yuin/gopher-lua v1.1.0 h1:BojcDhfyDWgU2f2TOzYK/g5p2gxMrku8oupLDqlnSqE=
+github.com/yuin/gopher-lua v1.1.0/go.mod h1:GBR0iDaNXjAgGg9zfCvksxSRnQx76gclCIb7kdAd1Pw=
github.com/zclconf/go-cty v1.2.0/go.mod h1:hOPWgoHbaTUnI5k4D2ld+GRpFJSCe6bCM7m1q/N4PQ8=
github.com/zclconf/go-cty v1.8.0 h1:s4AvqaeQzJIu3ndv4gVIhplVD0krU+bgrcLSVUnaWuA=
github.com/zclconf/go-cty v1.8.0/go.mod h1:vVKLxnk3puL4qRAv72AO+W99LUD4da90g3uUAzyuvAk=
@@ 166,10 166,10 @@ golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201112073958-5cba982894dd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.3.0 h1:w8ZOecv6NaNa/zC8944JTU3vz4u6Lagfk4RPQxv92NQ=
-golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/term v0.3.0 h1:qoo4akIqOcDME5bhc/NgxUdovd6BSS2uMsVjB56q1xI=
-golang.org/x/term v0.3.0/go.mod h1:q750SLmJuPmVoN1blW3UFBPREJfb1KmY3vwxfr+nFDA=
+golang.org/x/sys v0.6.0 h1:MVltZSvRTcU2ljQOhs94SXPftV6DCNnZViHeQps87pQ=
+golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/term v0.6.0 h1:clScbb1cHjoCkyRbWwBEUZ5H/tIFu5TAXIqaZD0Gcjw=
+golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
@@ 1,74 1,90 @@
package main
import (
- "bufio"
"errors"
"fmt"
"io"
"os"
"strings"
- "github.com/Shopify/go-lua"
"github.com/nesv/cmndr"
+ lua "github.com/yuin/gopher-lua"
+ "golang.org/x/term"
"git.sr.ht/~nesv/govern/internal/facts"
)
+const luaShellBanner = `
+ ____ _____ _ _____ _________
+ / __ / __ \ | / / _ \/ ___/ __ \
+ / /_/ / /_/ / |/ / __/ / / / / /
+ \__, /\____/|___/\___/_/ /_/ /_/
+/____/`
+
func (c *command) runLuaShell(cmd *cmndr.Cmd, args []string) error {
if err := c.init(); err != nil {
return err
}
- state, err := c.initializeLuaRuntime()
+ ff, err := c.collectFacts()
if err != nil {
- return fmt.Errorf("initialize lua runtime: %w", err)
+ return fmt.Errorf("collect facts: %w", err)
}
- r := bufio.NewReader(os.Stdin)
- for {
- fmt.Fprint(os.Stderr, ":: ")
+ vm := lua.NewState()
+ defer vm.Close()
+
+ vm.PreloadModule("govern", func(state *lua.LState) int {
+ module := state.SetFuncs(state.NewTable(), map[string]lua.LGFunction{
+ "GetFact": getFact(ff),
+ "AvailableFacts": availableFacts(ff),
+ })
- line, err := r.ReadString('\n')
+ state.SetField(module, "name", lua.LString("govern"))
+
+ state.Push(module)
+ return 1
+ })
+
+ fmt.Fprintln(os.Stderr, luaShellBanner)
+
+ oldTermState, err := term.MakeRaw(int(os.Stdin.Fd()))
+ if err != nil {
+ return fmt.Errorf("make raw terminal: %w", err)
+ }
+ defer term.Restore(int(os.Stdin.Fd()), oldTermState)
+
+ terminal := term.NewTerminal(&readwriter{
+ r: os.Stdin,
+ w: os.Stdout,
+ }, "govern-> ")
+
+ for {
+ line, err := terminal.ReadLine()
if errors.Is(err, io.EOF) {
+ fmt.Fprintln(terminal, "arrivederci!")
return nil
} else if err != nil {
fmt.Fprintln(os.Stderr, "!!", err)
continue
}
- if err := lua.DoString(state, strings.TrimSpace(line)); err != nil {
- fmt.Fprintln(os.Stderr, err)
- continue
+ switch cmd := strings.TrimSpace(line); cmd {
+ case "exit", "quit":
+ fmt.Fprintln(terminal, "g'bye!")
+ return nil
}
- }
-}
-func (c *command) initializeLuaRuntime() (*lua.State, error) {
- state := lua.NewState()
- lua.OpenLibraries(state)
+ // fmt.Fprintf(os.Stderr, "debug: command = %q\r\n", strings.TrimSpace(line))
- // Load all facts and push them into the Lua runtime's global state.
- ff, err := c.collectFacts()
- if err != nil {
- return nil, fmt.Errorf("collect facts: %w", err)
- }
-
- // TODO(nesv): Register custom functions.
- lua.Require(state, "govern", func(state *lua.State) int {
- lua.NewLibrary(state, []lua.RegistryFunction{
- {
- Name: "GetFact",
- Function: getFact(ff),
- },
- })
- return 1
- }, false)
-
- return state, nil
-}
+ if err := vm.DoString(strings.TrimSpace(line)); err != nil {
+ errMsg := strings.ReplaceAll(err.Error(), "\n", "\r\n")
+ fmt.Fprintf(os.Stderr, "%s\r\n", errMsg)
+ continue
+ }
-func (c *command) loadBuiltinLuaRunners(state *lua.State) error {
- return nil // TODO
+ fmt.Fprint(terminal, "\r")
+ }
}
func stdio() io.ReadWriter {
@@ 98,19 114,31 @@ func (rw *readwriter) Write(p []byte) (int, error) { return rw.w.Write(p) }
// Retrieve a fact.
// getFact is a function that gets loaded into the Lua runtime.
-func getFact(ff *facts.Facts) lua.Function {
- return func(state *lua.State) int {
- name := lua.CheckString(state, 1)
- fv, err := ff.Get(name)
+func getFact(ff *facts.Facts) lua.LGFunction {
+ return func(state *lua.LState) int {
+ name := state.ToString(1)
+ if name == "" {
+ panic("empty string")
+ }
+
+ factValue, err := ff.Get(name)
if err != nil {
- lua.ArgumentError(state, 1, err.Error())
+ panic(fmt.Sprintf("cannot find fact %q: %s", name, err))
}
- if name == "" {
- lua.ArgumentError(state, 1, "no fact name specified")
+ state.Push(lua.LString(factValue))
+ return 1
+ }
+}
+
+func availableFacts(ff *facts.Facts) lua.LGFunction {
+ return func(ls *lua.LState) int {
+ names := ls.NewTable()
+ for _, name := range ff.Names() {
+ names.Append(lua.LString(name))
}
- state.PushString(fv)
+ ls.Push(names)
return 1
}
}