@@ -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 |
@@ -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 | |||
) |
@@ -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= |
@@ -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 | |||
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 { | |||
@@ -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) | |||
} | |||