engine/jsonfile.go
Rama main · réplica de lectura del repositorio original.
package engine
import (
"os"
"path/filepath"
"strings"
"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") {
logrus.Error("JSON data: ", err)
} else {
logrus.Error(err)
}
}
L.Push(lua.LBool(err == nil))
return 1 // number of results
}
// Takes a JFile and a JSON path.
// Returns the value as a string, 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")
}
node, err := jfile.GetNode(jsonpath)
if err != nil {
logrus.Error(err)
L.Push(lua.LString(""))
return 1 // number of results
}
L.Push(lua.LString(node.StringValue()))
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
logrus.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 {
logrus.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 {
logrus.Error("Unknown JSON node type")
retval = lua.LNil
}
// 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")
}
// CheckString errors if arg 3 is missing or not a string-like value,
// but accepts "" — an empty string is a valid JSON value.
sval := L.CheckString(3)
err := jfile.SetString(jsonpath, sval)
if err != nil {
logrus.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 {
logrus.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 {
logrus.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 {
logrus.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
}))
}