Ctrl K
ComunidadGuías…

engine/jsonfile.go

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
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
	}))
}