123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268 |
- package engine
- import (
- "os"
- "path/filepath"
- "strings"
- log "github.com/sirupsen/logrus"
- "github.com/xyproto/algernon/lua/jnode"
- "github.com/xyproto/datablock"
- lua "github.com/xyproto/gopher-lua"
- "github.com/xyproto/jpath"
- )
- // For dealing with JSON documents and strings
- const (
- // Identifier for the JFile class in Lua
- lJFileClass = "JFile"
- )
- // Get the first argument, "self", and cast it from userdata to a library (which is really a hash map).
- func checkJFile(L *lua.LState) *jpath.JFile {
- ud := L.CheckUserData(1)
- if jfile, ok := ud.Value.(*jpath.JFile); ok {
- return jfile
- }
- L.ArgError(1, "JSON file expected")
- return nil
- }
- // Takes a JFile, a JSON path (optional) and JSON data.
- // Stores the JSON data. Returns true if successful.
- func jfileAdd(L *lua.LState) int {
- jfile := checkJFile(L) // arg 1
- top := L.GetTop()
- jsonpath := "x"
- jsondata := ""
- if top == 2 {
- jsondata = L.ToString(2)
- if jsondata == "" {
- L.ArgError(2, "JSON data expected")
- }
- } else if top == 3 {
- jsonpath = L.ToString(2)
- // Check for { to help avoid allowing JSON data as a JSON path
- if jsonpath == "" || strings.Contains(jsonpath, "{") {
- L.ArgError(2, "JSON path expected")
- }
- jsondata = L.ToString(3)
- if jsondata == "" {
- L.ArgError(3, "JSON data expected")
- }
- }
- err := jfile.AddJSON(jsonpath, []byte(jsondata))
- if err != nil {
- if top == 2 || strings.HasPrefix(err.Error(), "invalid character") {
- log.Error("JSON data: ", err)
- } else {
- log.Error(err)
- }
- }
- L.Push(lua.LBool(err == nil))
- return 1 // number of results
- }
- // Takes a JFile and a JSON path.
- // Returns a string value or an empty string.
- func jfileGetString(L *lua.LState) int {
- jfile := checkJFile(L) // arg 1
- jsonpath := L.ToString(2)
- if jsonpath == "" {
- L.ArgError(2, "JSON path expected")
- }
- val, err := jfile.GetString(jsonpath)
- if err != nil {
- log.Error(err)
- val = ""
- }
- L.Push(lua.LString(val))
- return 1 // number of results
- }
- // Takes a JFile and a JSON path.
- // Returns a JNode or nil.
- func jfileGetNode(L *lua.LState) int {
- jfile := checkJFile(L) // arg 1
- jsonpath := L.ToString(2)
- if jsonpath == "" {
- L.ArgError(2, "JSON path expected")
- }
- node, err := jfile.GetNode(jsonpath)
- if err != nil {
- L.Push(lua.LNil)
- return 1 // number of results
- }
- // Return the JNode
- ud := L.NewUserData()
- ud.Value = node
- L.SetMetatable(ud, L.GetTypeMetatable(jnode.Class))
- L.Push(ud)
- return 1 // number of results
- }
- // Takes a JFile and a JSON path.
- // Returns a value or nil.
- func jfileGet(L *lua.LState) int {
- jfile := checkJFile(L) // arg 1
- jsonpath := L.ToString(2)
- if jsonpath == "" {
- L.ArgError(2, "JSON path expected")
- }
- // Will handle nil nodes below, so the error value can be ignored
- node, _ := jfile.GetNode(jsonpath)
- // Convert the JSON node to a Lua value, if possible
- var retval lua.LValue
- if node == jpath.NilNode {
- retval = lua.LNil
- } else if _, ok := node.CheckMap(); ok {
- // Return the JNode instead of converting the map
- log.Info("Returning a JSON node instead of a Lua map")
- ud := L.NewUserData()
- ud.Value = node
- L.SetMetatable(ud, L.GetTypeMetatable(jnode.Class))
- retval = ud
- // buf.WriteString(fmt.Sprintf("Map with %d elements.", len(m)))
- } else if _, ok := node.CheckList(); ok {
- log.Info("Returning a JSON node instead of a Lua map")
- // Return the JNode instead of converting the list
- ud := L.NewUserData()
- ud.Value = node
- L.SetMetatable(ud, L.GetTypeMetatable(jnode.Class))
- retval = ud
- // buf.WriteString(fmt.Sprintf("List with %d elements.", len(l)))
- } else if s, ok := node.CheckString(); ok {
- retval = lua.LString(s)
- } else if s, ok := node.CheckInt(); ok {
- retval = lua.LNumber(s)
- } else if b, ok := node.CheckBool(); ok {
- retval = lua.LBool(b)
- } else if i, ok := node.CheckInt64(); ok {
- retval = lua.LNumber(i)
- } else if u, ok := node.CheckUint64(); ok {
- retval = lua.LNumber(u)
- } else if f, ok := node.CheckFloat64(); ok {
- retval = lua.LNumber(f)
- } else {
- log.Error("Unknown JSON node type")
- return 0
- }
- // Return the LValue
- L.Push(retval)
- return 1 // number of results
- }
- // Take a JFile, a JSON path and a string.
- // Returns a value or an empty string.
- func jfileSet(L *lua.LState) int {
- jfile := checkJFile(L) // arg 1
- jsonpath := L.ToString(2)
- if jsonpath == "" {
- L.ArgError(2, "JSON path expected")
- }
- sval := L.ToString(3)
- if sval == "" {
- L.ArgError(3, "String value expected")
- }
- err := jfile.SetString(jsonpath, sval)
- if err != nil {
- log.Error(err)
- }
- L.Push(lua.LBool(err == nil))
- return 1 // number of results
- }
- // Take a JFile and a JSON path.
- // Remove a key from a map. Return true if successful.
- func jfileDelKey(L *lua.LState) int {
- jfile := checkJFile(L) // arg 1
- jsonpath := L.ToString(2)
- if jsonpath == "" {
- L.ArgError(2, "JSON path expected")
- }
- err := jfile.DelKey(jsonpath)
- if err != nil {
- log.Error(err)
- }
- L.Push(lua.LBool(nil == err))
- return 1 // number of results
- }
- // Given a JFile, return the JSON document.
- // May return an empty string.
- func jfileJSON(L *lua.LState) int {
- jfile := checkJFile(L) // arg 1
- data, err := jfile.JSON()
- retval := ""
- if err == nil { // ok
- retval = string(data)
- }
- L.Push(lua.LString(retval))
- return 1 // number of results
- }
- // Create a new JSON file
- func constructJFile(L *lua.LState, filename string, fperm os.FileMode, fs *datablock.FileStat) (*lua.LUserData, error) {
- fullFilename := filename
- // Check if the file exists
- if !fs.Exists(fullFilename) {
- // Create an empty JSON document/file
- if err := os.WriteFile(fullFilename, []byte("[]\n"), fperm); err != nil {
- return nil, err
- }
- }
- // Create a new JFile
- jfile, err := jpath.NewFile(fullFilename)
- if err != nil {
- log.Error(err)
- return nil, err
- }
- // Create a new userdata struct
- ud := L.NewUserData()
- ud.Value = jfile
- L.SetMetatable(ud, L.GetTypeMetatable(lJFileClass))
- return ud, nil
- }
- // The hash map methods that are to be registered
- var jfileMethods = map[string]lua.LGFunction{
- "__tostring": jfileJSON,
- "add": jfileAdd,
- "getstring": jfileGetString,
- "getnode": jfileGetNode,
- "get": jfileGet,
- "set": jfileSet,
- "delkey": jfileDelKey,
- "string": jfileJSON, // undocumented
- }
- // LoadJFile makes functions related to building a library of Lua code available
- func (ac *Config) LoadJFile(L *lua.LState, scriptdir string) {
- // Register the JFile class and the methods that belongs with it.
- mt := L.NewTypeMetatable(lJFileClass)
- mt.RawSetH(lua.LString("__index"), mt)
- L.SetFuncs(mt, jfileMethods)
- // The constructor for new Libraries takes only an optional id
- L.SetGlobal("JFile", L.NewFunction(func(L *lua.LState) int {
- // Get the filename and schema
- filename := L.ToString(1)
- // Construct a new JFile
- userdata, err := constructJFile(L, filepath.Join(scriptdir, filename), ac.defaultPermissions, ac.fs)
- if err != nil {
- log.Error(err)
- L.Push(lua.LString(err.Error()))
- return 1 // Number of returned values
- }
- // Return the Lua JFile object
- L.Push(userdata)
- return 1 // number of results
- }))
- }
|