A webmail client. Forked from https://git.sr.ht/~migadu/alps
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 

185 lines
3.8 KiB

  1. package alpslua
  2. import (
  3. "fmt"
  4. "html/template"
  5. "path/filepath"
  6. "git.sr.ht/~migadu/alps"
  7. "github.com/labstack/echo/v4"
  8. "github.com/yuin/gopher-lua"
  9. "layeh.com/gopher-luar"
  10. )
  11. type luaRoute struct {
  12. method string
  13. path string
  14. f *lua.LFunction
  15. }
  16. type luaPlugin struct {
  17. filename string
  18. state *lua.LState
  19. renderCallbacks map[string]*lua.LFunction
  20. filters template.FuncMap
  21. routes []luaRoute
  22. }
  23. func (p *luaPlugin) Name() string {
  24. return p.filename
  25. }
  26. func (p *luaPlugin) onRender(l *lua.LState) int {
  27. name := l.CheckString(1)
  28. f := l.CheckFunction(2)
  29. p.renderCallbacks[name] = f
  30. return 0
  31. }
  32. func (p *luaPlugin) setFilter(l *lua.LState) int {
  33. name := l.CheckString(1)
  34. f := l.CheckFunction(2)
  35. p.filters[name] = func(args ...interface{}) string {
  36. luaArgs := make([]lua.LValue, len(args))
  37. for i, v := range args {
  38. luaArgs[i] = luar.New(l, v)
  39. }
  40. err := l.CallByParam(lua.P{
  41. Fn: f,
  42. NRet: 1,
  43. Protect: true,
  44. }, luaArgs...)
  45. if err != nil {
  46. panic(err) // TODO: better error handling?
  47. }
  48. ret := l.CheckString(-1)
  49. l.Pop(1)
  50. return ret
  51. }
  52. return 0
  53. }
  54. func (p *luaPlugin) setRoute(l *lua.LState) int {
  55. method := l.CheckString(1)
  56. path := l.CheckString(2)
  57. f := l.CheckFunction(3)
  58. p.routes = append(p.routes, luaRoute{method, path, f})
  59. return 0
  60. }
  61. func (p *luaPlugin) inject(name string, data alps.RenderData) error {
  62. f, ok := p.renderCallbacks[name]
  63. if !ok {
  64. return nil
  65. }
  66. err := p.state.CallByParam(lua.P{
  67. Fn: f,
  68. NRet: 0,
  69. Protect: true,
  70. }, luar.New(p.state, data))
  71. if err != nil {
  72. return err
  73. }
  74. return nil
  75. }
  76. func (p *luaPlugin) Inject(ctx *alps.Context, name string, data alps.RenderData) error {
  77. if err := p.inject("*", data); err != nil {
  78. return err
  79. }
  80. return p.inject(name, data)
  81. }
  82. func (p *luaPlugin) LoadTemplate(t *template.Template) error {
  83. t.Funcs(p.filters)
  84. paths, err := filepath.Glob(filepath.Dir(p.filename) + "/public/*.html")
  85. if err != nil {
  86. return err
  87. }
  88. if len(paths) > 0 {
  89. if _, err := t.ParseFiles(paths...); err != nil {
  90. return err
  91. }
  92. }
  93. return nil
  94. }
  95. func (p *luaPlugin) SetRoutes(group *echo.Group) {
  96. for _, r := range p.routes {
  97. group.Match([]string{r.method}, r.path, func(ctx echo.Context) error {
  98. err := p.state.CallByParam(lua.P{
  99. Fn: r.f,
  100. NRet: 0,
  101. Protect: true,
  102. }, luar.New(p.state, ctx))
  103. if err != nil {
  104. return fmt.Errorf("Lua plugin error: %v", err)
  105. }
  106. return nil
  107. })
  108. }
  109. _, name := filepath.Split(filepath.Dir(p.filename))
  110. group.Static("/plugins/"+name+"/assets", filepath.Dir(p.filename)+"/public/assets")
  111. }
  112. func (p *luaPlugin) Close() error {
  113. p.state.Close()
  114. return nil
  115. }
  116. func loadLuaPlugin(filename string) (*luaPlugin, error) {
  117. l := lua.NewState()
  118. p := &luaPlugin{
  119. filename: filename,
  120. state: l,
  121. renderCallbacks: make(map[string]*lua.LFunction),
  122. filters: make(template.FuncMap),
  123. }
  124. mt := l.NewTypeMetatable("alps")
  125. l.SetGlobal("alps", mt)
  126. l.SetField(mt, "on_render", l.NewFunction(p.onRender))
  127. l.SetField(mt, "set_filter", l.NewFunction(p.setFilter))
  128. l.SetField(mt, "set_route", l.NewFunction(p.setRoute))
  129. if err := l.DoFile(filename); err != nil {
  130. l.Close()
  131. return nil, err
  132. }
  133. return p, nil
  134. }
  135. func loadAllLuaPlugins(s *alps.Server) ([]alps.Plugin, error) {
  136. log := s.Logger()
  137. filenames, err := filepath.Glob(alps.PluginDir + "/*/main.lua")
  138. if err != nil {
  139. return nil, fmt.Errorf("filepath.Glob failed: %v", err)
  140. }
  141. plugins := make([]alps.Plugin, 0, len(filenames))
  142. for _, filename := range filenames {
  143. log.Printf("Loading Lua plugin %q", filename)
  144. p, err := loadLuaPlugin(filename)
  145. if err != nil {
  146. for _, p := range plugins {
  147. p.Close()
  148. }
  149. return nil, fmt.Errorf("failed to load Lua plugin %q: %v", filename, err)
  150. }
  151. plugins = append(plugins, p)
  152. }
  153. return plugins, nil
  154. }