From 4f74722c14d18e78e172825961d7a65d25b41d3f Mon Sep 17 00:00:00 2001 From: Simon Ser Date: Mon, 9 Dec 2019 16:02:12 +0100 Subject: [PATCH] Add basic support for plugins --- README.md | 7 ++++++ go.mod | 2 ++ go.sum | 9 +++++++ plugin.go | 78 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ server.go | 8 +++++++ template.go | 12 +++++++++- 6 files changed, 115 insertions(+), 1 deletion(-) create mode 100644 plugin.go 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//*.html` override default templates in `public/*.html`. Assets in `public/themes//assets/*` are served by the HTTP server at `themes//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) }