Add basic support for plugins
This commit is contained in:
parent
a176409933
commit
4f74722c14
6 changed files with 115 additions and 1 deletions
|
@ -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
|
`public/*.html`. Assets in `public/themes/<name>/assets/*` are served by the
|
||||||
HTTP server at `themes/<name>/assets/*`.
|
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
|
## License
|
||||||
|
|
||||||
MIT
|
MIT
|
||||||
|
|
2
go.mod
2
go.mod
|
@ -12,7 +12,9 @@ require (
|
||||||
github.com/mattn/go-colorable v0.1.4 // indirect
|
github.com/mattn/go-colorable v0.1.4 // indirect
|
||||||
github.com/mattn/go-isatty v0.0.10 // indirect
|
github.com/mattn/go-isatty v0.0.10 // indirect
|
||||||
github.com/valyala/fasttemplate v1.1.0 // 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/crypto v0.0.0-20191202143827-86a70503ff7e // indirect
|
||||||
golang.org/x/net v0.0.0-20191126235420-ef20fe5d7933 // indirect
|
golang.org/x/net v0.0.0-20191126235420-ef20fe5d7933 // indirect
|
||||||
golang.org/x/sys v0.0.0-20191128015809-6d18c012aee9 // indirect
|
golang.org/x/sys v0.0.0-20191128015809-6d18c012aee9 // indirect
|
||||||
|
layeh.com/gopher-luar v1.0.7
|
||||||
)
|
)
|
||||||
|
|
9
go.sum
9
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.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 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
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.0.1/go.mod h1:UQGH1tvbgY+Nz5t2n7tXsz52dQxojPUpymEIMZ47gx8=
|
||||||
github.com/valyala/fasttemplate v1.1.0 h1:RZqt0yGBsps8NGvLSGW804QQqCUYYLsaOjTVHy1Ocw4=
|
github.com/valyala/fasttemplate v1.1.0 h1:RZqt0yGBsps8NGvLSGW804QQqCUYYLsaOjTVHy1Ocw4=
|
||||||
github.com/valyala/fasttemplate v1.1.0/go.mod h1:UQGH1tvbgY+Nz5t2n7tXsz52dQxojPUpymEIMZ47gx8=
|
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-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 h1:HuIa8hRrWRSrqYzx1qI49NNxhdi2PrY7gxVSq1JjLDc=
|
||||||
golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
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-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 h1:e6HwijUxhDe+hPNjZQQn9bA5PW3vNmnN64U2ZW759Lk=
|
||||||
golang.org/x/net v0.0.0-20191126235420-ef20fe5d7933/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
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-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-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
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/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 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
|
||||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
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=
|
||||||
|
|
78
plugin.go
Normal file
78
plugin.go
Normal file
|
@ -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
|
||||||
|
}
|
|
@ -28,6 +28,8 @@ type Server struct {
|
||||||
tls bool
|
tls bool
|
||||||
insecure bool
|
insecure bool
|
||||||
}
|
}
|
||||||
|
|
||||||
|
plugins []Plugin
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Server) parseIMAPURL(imapURL string) error {
|
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)
|
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) {
|
e.HTTPErrorHandler = func(err error, c echo.Context) {
|
||||||
code := http.StatusInternalServerError
|
code := http.StatusInternalServerError
|
||||||
if he, ok := err.(*echo.HTTPError); ok {
|
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 {
|
e.Use(func(next echo.HandlerFunc) echo.HandlerFunc {
|
||||||
return func(ectx echo.Context) error {
|
return func(ectx echo.Context) error {
|
||||||
ctx := &context{Context: ectx, server: s}
|
ctx := &context{Context: ectx, server: s}
|
||||||
|
ctx.Set("context", ctx)
|
||||||
|
|
||||||
cookie, err := ctx.Cookie(cookieName)
|
cookie, err := ctx.Cookie(cookieName)
|
||||||
if err == http.ErrNoCookie {
|
if err == http.ErrNoCookie {
|
||||||
|
|
12
template.go
12
template.go
|
@ -1,6 +1,7 @@
|
||||||
package koushin
|
package koushin
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"html/template"
|
"html/template"
|
||||||
"io"
|
"io"
|
||||||
"net/url"
|
"net/url"
|
||||||
|
@ -13,7 +14,16 @@ type tmpl struct {
|
||||||
t *template.Template
|
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)
|
return t.t.ExecuteTemplate(w, name, data)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue