aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--README.md7
-rw-r--r--go.mod2
-rw-r--r--go.sum9
-rw-r--r--plugin.go78
-rw-r--r--server.go8
-rw-r--r--template.go12
6 files changed, 115 insertions, 1 deletions
diff --git a/README.md b/README.md
index 33288c8..82cb933 100644
--- a/README.md
+++ b/README.md
@@ -14,6 +14,13 @@ Templates in `public/themes/<name>/*.html` override default templates in
`public/*.html`. Assets in `public/themes/<name>/assets/*` are served by the
HTTP server at `themes/<name>/assets/*`.
+## Plugins
+
+Lua plugins are supported. They can be dropped in `plugins/*.lua`.
+
+For now only a single hook is supported: `render(name, data)`. If defined, this
+Lua function will be called prior to rendering a template.
+
## License
MIT
diff --git a/go.mod b/go.mod
index 6107fbe..229f677 100644
--- a/go.mod
+++ b/go.mod
@@ -12,7 +12,9 @@ require (
github.com/mattn/go-colorable v0.1.4 // indirect
github.com/mattn/go-isatty v0.0.10 // indirect
github.com/valyala/fasttemplate v1.1.0 // indirect
+ github.com/yuin/gopher-lua v0.0.0-20191128022950-c6266f4fe8d7
golang.org/x/crypto v0.0.0-20191202143827-86a70503ff7e // indirect
golang.org/x/net v0.0.0-20191126235420-ef20fe5d7933 // indirect
golang.org/x/sys v0.0.0-20191128015809-6d18c012aee9 // indirect
+ layeh.com/gopher-luar v1.0.7
)
diff --git a/go.sum b/go.sum
index 46cc5b5..869a1ff 100644
--- a/go.sum
+++ b/go.sum
@@ -1,3 +1,6 @@
+github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
+github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
+github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
@@ -50,6 +53,9 @@ github.com/valyala/fasttemplate v1.0.1 h1:tY9CJiPnMXf1ERmG2EyK7gNUd+c6RKGD0IfU8W
github.com/valyala/fasttemplate v1.0.1/go.mod h1:UQGH1tvbgY+Nz5t2n7tXsz52dQxojPUpymEIMZ47gx8=
github.com/valyala/fasttemplate v1.1.0 h1:RZqt0yGBsps8NGvLSGW804QQqCUYYLsaOjTVHy1Ocw4=
github.com/valyala/fasttemplate v1.1.0/go.mod h1:UQGH1tvbgY+Nz5t2n7tXsz52dQxojPUpymEIMZ47gx8=
+github.com/yuin/gopher-lua v0.0.0-20190206043414-8bfc7677f583/go.mod h1:gqRgreBUhTSL0GeU64rtZ3Uq3wtjOa/TB2YfrtkCbVQ=
+github.com/yuin/gopher-lua v0.0.0-20191128022950-c6266f4fe8d7 h1:Y17pEjKgx2X0A69WQPGa8hx/Myzu+4NdUxlkZpbAYio=
+github.com/yuin/gopher-lua v0.0.0-20191128022950-c6266f4fe8d7/go.mod h1:gqRgreBUhTSL0GeU64rtZ3Uq3wtjOa/TB2YfrtkCbVQ=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4 h1:HuIa8hRrWRSrqYzx1qI49NNxhdi2PrY7gxVSq1JjLDc=
golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
@@ -59,6 +65,7 @@ golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3 h1:0GoQqolDA55aaLxZyTzK/Y2eP
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20191126235420-ef20fe5d7933 h1:e6HwijUxhDe+hPNjZQQn9bA5PW3vNmnN64U2ZW759Lk=
golang.org/x/net v0.0.0-20191126235420-ef20fe5d7933/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/sys v0.0.0-20190204203706-41f3e6584952/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@@ -76,3 +83,5 @@ gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+layeh.com/gopher-luar v1.0.7 h1:53iv6CCkRs5wyofZ+qVXcyAYQOIG52s6pt4xkqZdq7k=
+layeh.com/gopher-luar v1.0.7/go.mod h1:TPnIVCZ2RJBndm7ohXyaqfhzjlZ+OA2SZR/YwL8tECk=
diff --git a/plugin.go b/plugin.go
new file mode 100644
index 0000000..b94c330
--- /dev/null
+++ b/plugin.go
@@ -0,0 +1,78 @@
+package koushin
+
+import (
+ "fmt"
+ "path/filepath"
+
+ "github.com/labstack/echo/v4"
+ "github.com/yuin/gopher-lua"
+ "layeh.com/gopher-luar"
+)
+
+type Plugin interface {
+ Name() string
+ Render(name string, data interface{}) error
+ Close() error
+}
+
+type luaPlugin struct {
+ filename string
+ state *lua.LState
+}
+
+func (p *luaPlugin) Name() string {
+ return p.filename
+}
+
+func (p *luaPlugin) Render(name string, data interface{}) error {
+ global := p.state.GetGlobal("render")
+ if global == nil {
+ return nil
+ }
+
+ if err := p.state.CallByParam(lua.P{
+ Fn: global,
+ NRet: 0,
+ Protect: true,
+ }, lua.LString(name), luar.New(p.state, data)); err != nil {
+ return err
+ }
+
+ return nil
+}
+
+func (p *luaPlugin) Close() error {
+ p.state.Close()
+ return nil
+}
+
+func loadLuaPlugin(filename string) (*luaPlugin, error) {
+ l := lua.NewState()
+ if err := l.DoFile(filename); err != nil {
+ return nil, err
+ }
+
+ return &luaPlugin{filename, l}, nil
+}
+
+func loadAllLuaPlugins(log echo.Logger) ([]Plugin, error) {
+ filenames, err := filepath.Glob("plugins/*.lua")
+ if err != nil {
+ return nil, fmt.Errorf("filepath.Glob failed: %v", err)
+ }
+
+ plugins := make([]Plugin, 0, len(filenames))
+ for _, filename := range filenames {
+ log.Printf("Loading Lua plugin '%v'", filename)
+ p, err := loadLuaPlugin(filename)
+ if err != nil {
+ for _, p := range plugins {
+ p.Close()
+ }
+ return nil, fmt.Errorf("failed to load Lua plugin '%v': %v", filename, err)
+ }
+ plugins = append(plugins, p)
+ }
+
+ return plugins, nil
+}
diff --git a/server.go b/server.go
index d8aeca7..0845628 100644
--- a/server.go
+++ b/server.go
@@ -28,6 +28,8 @@ type Server struct {
tls bool
insecure bool
}
+
+ plugins []Plugin
}
func (s *Server) parseIMAPURL(imapURL string) error {
@@ -131,6 +133,11 @@ func New(e *echo.Echo, options *Options) error {
return fmt.Errorf("failed to load templates: %v", err)
}
+ s.plugins, err = loadAllLuaPlugins(e.Logger)
+ if err != nil {
+ return fmt.Errorf("failed to load plugins: %v", err)
+ }
+
e.HTTPErrorHandler = func(err error, c echo.Context) {
code := http.StatusInternalServerError
if he, ok := err.(*echo.HTTPError); ok {
@@ -145,6 +152,7 @@ func New(e *echo.Echo, options *Options) error {
e.Use(func(next echo.HandlerFunc) echo.HandlerFunc {
return func(ectx echo.Context) error {
ctx := &context{Context: ectx, server: s}
+ ctx.Set("context", ctx)
cookie, err := ctx.Cookie(cookieName)
if err == http.ErrNoCookie {
diff --git a/template.go b/template.go
index f12e2ec..a4c3ee0 100644
--- a/template.go
+++ b/template.go
@@ -1,6 +1,7 @@
package koushin
import (
+ "fmt"
"html/template"
"io"
"net/url"
@@ -13,7 +14,16 @@ type tmpl struct {
t *template.Template
}
-func (t *tmpl) Render(w io.Writer, name string, data interface{}, c echo.Context) error {
+func (t *tmpl) Render(w io.Writer, name string, data interface{}, ectx echo.Context) error {
+ // ectx is the raw *echo.context, not our own *context
+ ctx := ectx.Get("context").(*context)
+
+ for _, plugin := range ctx.server.plugins {
+ if err := plugin.Render(name, data); err != nil {
+ return fmt.Errorf("failed to run plugin '%v': %v", plugin.Name(), err)
+ }
+ }
+
return t.t.ExecuteTemplate(w, name, data)
}