jsonfile.go 6.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268
  1. package engine
  2. import (
  3. "os"
  4. "path/filepath"
  5. "strings"
  6. log "github.com/sirupsen/logrus"
  7. "github.com/xyproto/algernon/lua/jnode"
  8. "github.com/xyproto/datablock"
  9. lua "github.com/xyproto/gopher-lua"
  10. "github.com/xyproto/jpath"
  11. )
  12. // For dealing with JSON documents and strings
  13. const (
  14. // Identifier for the JFile class in Lua
  15. lJFileClass = "JFile"
  16. )
  17. // Get the first argument, "self", and cast it from userdata to a library (which is really a hash map).
  18. func checkJFile(L *lua.LState) *jpath.JFile {
  19. ud := L.CheckUserData(1)
  20. if jfile, ok := ud.Value.(*jpath.JFile); ok {
  21. return jfile
  22. }
  23. L.ArgError(1, "JSON file expected")
  24. return nil
  25. }
  26. // Takes a JFile, a JSON path (optional) and JSON data.
  27. // Stores the JSON data. Returns true if successful.
  28. func jfileAdd(L *lua.LState) int {
  29. jfile := checkJFile(L) // arg 1
  30. top := L.GetTop()
  31. jsonpath := "x"
  32. jsondata := ""
  33. if top == 2 {
  34. jsondata = L.ToString(2)
  35. if jsondata == "" {
  36. L.ArgError(2, "JSON data expected")
  37. }
  38. } else if top == 3 {
  39. jsonpath = L.ToString(2)
  40. // Check for { to help avoid allowing JSON data as a JSON path
  41. if jsonpath == "" || strings.Contains(jsonpath, "{") {
  42. L.ArgError(2, "JSON path expected")
  43. }
  44. jsondata = L.ToString(3)
  45. if jsondata == "" {
  46. L.ArgError(3, "JSON data expected")
  47. }
  48. }
  49. err := jfile.AddJSON(jsonpath, []byte(jsondata))
  50. if err != nil {
  51. if top == 2 || strings.HasPrefix(err.Error(), "invalid character") {
  52. log.Error("JSON data: ", err)
  53. } else {
  54. log.Error(err)
  55. }
  56. }
  57. L.Push(lua.LBool(err == nil))
  58. return 1 // number of results
  59. }
  60. // Takes a JFile and a JSON path.
  61. // Returns a string value or an empty string.
  62. func jfileGetString(L *lua.LState) int {
  63. jfile := checkJFile(L) // arg 1
  64. jsonpath := L.ToString(2)
  65. if jsonpath == "" {
  66. L.ArgError(2, "JSON path expected")
  67. }
  68. val, err := jfile.GetString(jsonpath)
  69. if err != nil {
  70. log.Error(err)
  71. val = ""
  72. }
  73. L.Push(lua.LString(val))
  74. return 1 // number of results
  75. }
  76. // Takes a JFile and a JSON path.
  77. // Returns a JNode or nil.
  78. func jfileGetNode(L *lua.LState) int {
  79. jfile := checkJFile(L) // arg 1
  80. jsonpath := L.ToString(2)
  81. if jsonpath == "" {
  82. L.ArgError(2, "JSON path expected")
  83. }
  84. node, err := jfile.GetNode(jsonpath)
  85. if err != nil {
  86. L.Push(lua.LNil)
  87. return 1 // number of results
  88. }
  89. // Return the JNode
  90. ud := L.NewUserData()
  91. ud.Value = node
  92. L.SetMetatable(ud, L.GetTypeMetatable(jnode.Class))
  93. L.Push(ud)
  94. return 1 // number of results
  95. }
  96. // Takes a JFile and a JSON path.
  97. // Returns a value or nil.
  98. func jfileGet(L *lua.LState) int {
  99. jfile := checkJFile(L) // arg 1
  100. jsonpath := L.ToString(2)
  101. if jsonpath == "" {
  102. L.ArgError(2, "JSON path expected")
  103. }
  104. // Will handle nil nodes below, so the error value can be ignored
  105. node, _ := jfile.GetNode(jsonpath)
  106. // Convert the JSON node to a Lua value, if possible
  107. var retval lua.LValue
  108. if node == jpath.NilNode {
  109. retval = lua.LNil
  110. } else if _, ok := node.CheckMap(); ok {
  111. // Return the JNode instead of converting the map
  112. log.Info("Returning a JSON node instead of a Lua map")
  113. ud := L.NewUserData()
  114. ud.Value = node
  115. L.SetMetatable(ud, L.GetTypeMetatable(jnode.Class))
  116. retval = ud
  117. // buf.WriteString(fmt.Sprintf("Map with %d elements.", len(m)))
  118. } else if _, ok := node.CheckList(); ok {
  119. log.Info("Returning a JSON node instead of a Lua map")
  120. // Return the JNode instead of converting the list
  121. ud := L.NewUserData()
  122. ud.Value = node
  123. L.SetMetatable(ud, L.GetTypeMetatable(jnode.Class))
  124. retval = ud
  125. // buf.WriteString(fmt.Sprintf("List with %d elements.", len(l)))
  126. } else if s, ok := node.CheckString(); ok {
  127. retval = lua.LString(s)
  128. } else if s, ok := node.CheckInt(); ok {
  129. retval = lua.LNumber(s)
  130. } else if b, ok := node.CheckBool(); ok {
  131. retval = lua.LBool(b)
  132. } else if i, ok := node.CheckInt64(); ok {
  133. retval = lua.LNumber(i)
  134. } else if u, ok := node.CheckUint64(); ok {
  135. retval = lua.LNumber(u)
  136. } else if f, ok := node.CheckFloat64(); ok {
  137. retval = lua.LNumber(f)
  138. } else {
  139. log.Error("Unknown JSON node type")
  140. return 0
  141. }
  142. // Return the LValue
  143. L.Push(retval)
  144. return 1 // number of results
  145. }
  146. // Take a JFile, a JSON path and a string.
  147. // Returns a value or an empty string.
  148. func jfileSet(L *lua.LState) int {
  149. jfile := checkJFile(L) // arg 1
  150. jsonpath := L.ToString(2)
  151. if jsonpath == "" {
  152. L.ArgError(2, "JSON path expected")
  153. }
  154. sval := L.ToString(3)
  155. if sval == "" {
  156. L.ArgError(3, "String value expected")
  157. }
  158. err := jfile.SetString(jsonpath, sval)
  159. if err != nil {
  160. log.Error(err)
  161. }
  162. L.Push(lua.LBool(err == nil))
  163. return 1 // number of results
  164. }
  165. // Take a JFile and a JSON path.
  166. // Remove a key from a map. Return true if successful.
  167. func jfileDelKey(L *lua.LState) int {
  168. jfile := checkJFile(L) // arg 1
  169. jsonpath := L.ToString(2)
  170. if jsonpath == "" {
  171. L.ArgError(2, "JSON path expected")
  172. }
  173. err := jfile.DelKey(jsonpath)
  174. if err != nil {
  175. log.Error(err)
  176. }
  177. L.Push(lua.LBool(nil == err))
  178. return 1 // number of results
  179. }
  180. // Given a JFile, return the JSON document.
  181. // May return an empty string.
  182. func jfileJSON(L *lua.LState) int {
  183. jfile := checkJFile(L) // arg 1
  184. data, err := jfile.JSON()
  185. retval := ""
  186. if err == nil { // ok
  187. retval = string(data)
  188. }
  189. L.Push(lua.LString(retval))
  190. return 1 // number of results
  191. }
  192. // Create a new JSON file
  193. func constructJFile(L *lua.LState, filename string, fperm os.FileMode, fs *datablock.FileStat) (*lua.LUserData, error) {
  194. fullFilename := filename
  195. // Check if the file exists
  196. if !fs.Exists(fullFilename) {
  197. // Create an empty JSON document/file
  198. if err := os.WriteFile(fullFilename, []byte("[]\n"), fperm); err != nil {
  199. return nil, err
  200. }
  201. }
  202. // Create a new JFile
  203. jfile, err := jpath.NewFile(fullFilename)
  204. if err != nil {
  205. log.Error(err)
  206. return nil, err
  207. }
  208. // Create a new userdata struct
  209. ud := L.NewUserData()
  210. ud.Value = jfile
  211. L.SetMetatable(ud, L.GetTypeMetatable(lJFileClass))
  212. return ud, nil
  213. }
  214. // The hash map methods that are to be registered
  215. var jfileMethods = map[string]lua.LGFunction{
  216. "__tostring": jfileJSON,
  217. "add": jfileAdd,
  218. "getstring": jfileGetString,
  219. "getnode": jfileGetNode,
  220. "get": jfileGet,
  221. "set": jfileSet,
  222. "delkey": jfileDelKey,
  223. "string": jfileJSON, // undocumented
  224. }
  225. // LoadJFile makes functions related to building a library of Lua code available
  226. func (ac *Config) LoadJFile(L *lua.LState, scriptdir string) {
  227. // Register the JFile class and the methods that belongs with it.
  228. mt := L.NewTypeMetatable(lJFileClass)
  229. mt.RawSetH(lua.LString("__index"), mt)
  230. L.SetFuncs(mt, jfileMethods)
  231. // The constructor for new Libraries takes only an optional id
  232. L.SetGlobal("JFile", L.NewFunction(func(L *lua.LState) int {
  233. // Get the filename and schema
  234. filename := L.ToString(1)
  235. // Construct a new JFile
  236. userdata, err := constructJFile(L, filepath.Join(scriptdir, filename), ac.defaultPermissions, ac.fs)
  237. if err != nil {
  238. log.Error(err)
  239. L.Push(lua.LString(err.Error()))
  240. return 1 // Number of returned values
  241. }
  242. // Return the Lua JFile object
  243. L.Push(userdata)
  244. return 1 // number of results
  245. }))
  246. }