servelua.go 4.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140
  1. package engine
  2. import (
  3. "fmt"
  4. "net/http"
  5. "net/http/httptest"
  6. "path/filepath"
  7. "strings"
  8. "github.com/xyproto/algernon/lua/convert"
  9. "github.com/xyproto/algernon/utils"
  10. "github.com/xyproto/gluamapper"
  11. lua "github.com/xyproto/gopher-lua"
  12. "github.com/xyproto/pongo2"
  13. log "github.com/sirupsen/logrus"
  14. )
  15. // LoadServeFile exposes functions for serving other files to Lua
  16. func (ac *Config) LoadServeFile(w http.ResponseWriter, req *http.Request, L *lua.LState, filename string) {
  17. // Serve a file in the scriptdir
  18. L.SetGlobal("serve", L.NewFunction(func(L *lua.LState) int {
  19. scriptdir := filepath.Dir(filename)
  20. serveFilename := filepath.Join(scriptdir, L.ToString(1))
  21. dataFilename := filepath.Join(scriptdir, ac.defaultLuaDataFilename)
  22. if L.GetTop() >= 2 {
  23. // Optional argument for using a different file than "data.lua"
  24. dataFilename = filepath.Join(scriptdir, L.ToString(2))
  25. }
  26. if !ac.fs.Exists(serveFilename) {
  27. log.Error("Could not serve " + serveFilename + ". File not found.")
  28. return 0 // Number of results
  29. }
  30. if ac.fs.IsDir(serveFilename) {
  31. log.Error("Could not serve " + serveFilename + ". Not a file.")
  32. return 0 // Number of results
  33. }
  34. ac.FilePage(w, req, serveFilename, dataFilename)
  35. return 0 // Number of results
  36. }))
  37. // Output text as rendered Pongo2, using a po2 file and an optional table
  38. L.SetGlobal("serve2", L.NewFunction(func(L *lua.LState) int {
  39. scriptdir := filepath.Dir(filename)
  40. // Use the first argument as the template and the second argument as the data map
  41. templateFilename := filepath.Join(scriptdir, L.CheckString(1))
  42. ext := filepath.Ext(strings.ToLower(templateFilename))
  43. templateData, err := ac.cache.Read(templateFilename, ac.shouldCache(ext))
  44. if err != nil {
  45. if ac.debugMode {
  46. fmt.Fprintf(w, "Unable to read %s: %s", templateFilename, err)
  47. } else {
  48. log.Errorf("Unable to read %s: %s", templateFilename, err)
  49. }
  50. return 0 // number of restuls
  51. }
  52. templateString := templateData.String()
  53. // If a table is given as the second argument, fill pongoMap with keys and values
  54. pongoMap := make(pongo2.Context)
  55. if L.GetTop() == 2 {
  56. luaTable := L.CheckTable(2)
  57. goMap := gluamapper.ToGoValue(luaTable, gluamapper.Option{
  58. NameFunc: func(s string) string {
  59. return s
  60. },
  61. })
  62. if interfaceMap, ok := goMap.(map[any]any); ok {
  63. // Try to convert from map[any]any to map[string]any
  64. convertedMap := make(map[string]any)
  65. for k, v := range interfaceMap {
  66. convertedMap[k.(string)] = v
  67. }
  68. pongoMap = pongo2.Context(convertedMap)
  69. } else if m, ok := goMap.(map[string]any); ok {
  70. pongoMap = pongo2.Context(m)
  71. }
  72. // fmt.Println("PONGOMAP", pongoMap, "LUA TABLE", luaTable)
  73. } else if L.GetTop() > 2 {
  74. log.Error("Too many arguments given to the serve2 function")
  75. return 0 // number of restuls
  76. }
  77. // Retrieve all the function arguments as a bytes.Buffer
  78. buf := convert.Arguments2buffer(L, true)
  79. // Use the buffer as a template.
  80. // Options are "Pretty printing, but without line numbers."
  81. tpl, err := pongo2.FromString(templateString)
  82. if err != nil {
  83. if ac.debugMode {
  84. fmt.Fprint(w, "Could not compile Pongo2 template:\n\t"+err.Error()+"\n\n"+buf.String())
  85. } else {
  86. log.Errorf("Could not compile Pongo2 template:\n%s\n%s", err, buf.String())
  87. }
  88. return 0 // number of results
  89. }
  90. // nil is the template context (variables etc in a map)
  91. if err := tpl.ExecuteWriter(pongoMap, w); err != nil {
  92. if ac.debugMode {
  93. fmt.Fprint(w, "Could not compile Pongo2:\n\t"+err.Error()+"\n\n"+buf.String())
  94. } else {
  95. log.Errorf("Could not compile Pongo2:\n%s\n%s", err, buf.String())
  96. }
  97. }
  98. return 0 // number of results
  99. }))
  100. // Get the rendered contents of a file in the scriptdir. Discards HTTP headers.
  101. L.SetGlobal("render", L.NewFunction(func(L *lua.LState) int {
  102. scriptdir := filepath.Dir(filename)
  103. serveFilename := filepath.Join(scriptdir, L.ToString(1))
  104. dataFilename := filepath.Join(scriptdir, ac.defaultLuaDataFilename)
  105. if L.GetTop() >= 2 {
  106. // Optional argument for using a different file than "data.lua"
  107. dataFilename = filepath.Join(scriptdir, L.ToString(2))
  108. }
  109. if !ac.fs.Exists(serveFilename) {
  110. log.Error("Could not render " + serveFilename + ". File not found.")
  111. return 0 // Number of results
  112. }
  113. if ac.fs.IsDir(serveFilename) {
  114. log.Error("Could not render " + serveFilename + ". Not a file.")
  115. return 0 // Number of results
  116. }
  117. // Render the filename to a httptest.Recorder
  118. recorder := httptest.NewRecorder()
  119. ac.FilePage(recorder, req, serveFilename, dataFilename)
  120. // Return the recorder as a string
  121. L.Push(lua.LString(utils.RecorderToString(recorder)))
  122. return 1 // Number of results
  123. }))
  124. }