diff --git a/LICENSE b/LICENSE index 0219d81..4816ed8 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2020 - 2022 Jan-Lukas Else +Copyright (c) 2020 - 2023 Jan-Lukas Else Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/config.go b/config.go index 8c2dbe3..839168a 100644 --- a/config.go +++ b/config.go @@ -350,7 +350,6 @@ type configPprof struct { type configPlugin struct { Path string `mapstructure:"path"` - Type string `mapstructure:"type"` Import string `mapstructure:"import"` Config map[string]any `mapstructure:"config"` } diff --git a/go.mod b/go.mod index 68b9b88..18fc911 100644 --- a/go.mod +++ b/go.mod @@ -36,7 +36,7 @@ require ( github.com/jlelse/feeds v1.2.1-0.20210704161900-189f94254ad4 github.com/justinas/alice v1.2.0 github.com/kaorimatz/go-opml v0.0.0-20210201121027-bc8e2852d7f9 - github.com/klauspost/compress v1.15.14 + github.com/klauspost/compress v1.15.15 github.com/lestrrat-go/file-rotatelogs v2.4.0+incompatible github.com/lopezator/migrator v0.3.1 github.com/mattn/go-sqlite3 v1.14.16 @@ -50,7 +50,7 @@ require ( github.com/schollz/sqlite3dump v1.3.1 github.com/snabb/sitemap v1.0.0 github.com/spf13/cast v1.5.0 - github.com/spf13/viper v1.14.0 + github.com/spf13/viper v1.15.0 github.com/stretchr/testify v1.8.1 github.com/tdewolff/minify/v2 v2.12.4 // master @@ -67,7 +67,7 @@ require ( golang.org/x/sync v0.1.0 golang.org/x/text v0.6.0 gopkg.in/yaml.v3 v3.0.1 - maunium.net/go/mautrix v0.12.4 + maunium.net/go/mautrix v0.13.0 nhooyr.io/websocket v1.8.7 // main willnorris.com/go/microformats v1.1.2-0.20221115043057-ffbbdaef989e @@ -98,24 +98,23 @@ require ( github.com/jonboulle/clockwork v0.3.0 // indirect github.com/json-iterator/go v1.1.12 // indirect github.com/lestrrat-go/strftime v1.0.6 // indirect - github.com/magiconair/properties v1.8.6 // indirect + github.com/magiconair/properties v1.8.7 // indirect github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-isatty v0.0.16 // indirect github.com/mitchellh/mapstructure v1.5.0 // indirect github.com/mmcdole/goxpp v0.0.0-20181012175147-0068e33feabf // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect - github.com/pelletier/go-toml v1.9.5 // indirect - github.com/pelletier/go-toml/v2 v2.0.5 // indirect + github.com/pelletier/go-toml/v2 v2.0.6 // indirect github.com/pkg/errors v0.9.1 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/rs/xid v1.4.0 // indirect github.com/rs/zerolog v1.28.0 // indirect github.com/snabb/diagio v1.0.0 // indirect - github.com/spf13/afero v1.9.2 // indirect + github.com/spf13/afero v1.9.3 // indirect github.com/spf13/jwalterweatherman v1.1.0 // indirect github.com/spf13/pflag v1.0.5 // indirect - github.com/subosito/gotenv v1.4.1 // indirect + github.com/subosito/gotenv v1.4.2 // indirect github.com/tdewolff/parse/v2 v2.6.4 // indirect github.com/tidwall/gjson v1.14.4 // indirect github.com/tidwall/match v1.1.1 // indirect diff --git a/go.sum b/go.sum index 71fec58..dc2302b 100644 --- a/go.sum +++ b/go.sum @@ -278,8 +278,8 @@ github.com/kaorimatz/go-opml v0.0.0-20210201121027-bc8e2852d7f9 h1:+9REu9CK9D1AQ github.com/kaorimatz/go-opml v0.0.0-20210201121027-bc8e2852d7f9/go.mod h1:OvY5ZBrAC9kOvM2PZs9Lw0BH+5K7tjrT6T7SFhn27OA= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/klauspost/compress v1.10.3/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs= -github.com/klauspost/compress v1.15.14 h1:i7WCKDToww0wA+9qrUZ1xOjp218vfFo3nTU6UHp+gOc= -github.com/klauspost/compress v1.15.14/go.mod h1:QPwzmACJjUTFsnSHH934V6woptycfrDDJnH7hvFVbGM= +github.com/klauspost/compress v1.15.15 h1:EF27CXIuDsYJ6mmvtBRlEuB2UVOqHG1tAXgZ7yIO+lw= +github.com/klauspost/compress v1.15.15/go.mod h1:ZcK2JAFqKOpnBlxcLsJzYfrS9X1akm9fHZNnD9+Vo/4= github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= @@ -299,8 +299,8 @@ github.com/lestrrat-go/strftime v1.0.6 h1:CFGsDEt1pOpFNU+TJB0nhz9jl+K0hZSLE205Ah github.com/lestrrat-go/strftime v1.0.6/go.mod h1:f7jQKgV5nnJpYgdEasS+/y7EsTb8ykN2z68n3TtcTaw= github.com/lopezator/migrator v0.3.1 h1:ZFPT6aC7+nGWkqhleynABZ6ftycSf6hmHHLOaryq1Og= github.com/lopezator/migrator v0.3.1/go.mod h1:X+lHDMZ9Ci3/KdbypJcQYFFwipVrJsX4fRCQ4QLauYk= -github.com/magiconair/properties v1.8.6 h1:5ibWZ6iY0NctNGWo87LalDlEZ6R41TqbbDamhfG/Qzo= -github.com/magiconair/properties v1.8.6/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60= +github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY= +github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= github.com/matryer/try v0.0.0-20161228173917-9ac251b645a2/go.mod h1:0KeJpeMD6o+O4hW7qJOT7vyQPKrWmj26uf5wMc/IiIs= github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4= github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= @@ -332,10 +332,8 @@ github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9G github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= github.com/paulmach/go.geojson v1.4.0 h1:5x5moCkCtDo5x8af62P9IOAYGQcYHtxz2QJ3x1DoCgY= github.com/paulmach/go.geojson v1.4.0/go.mod h1:YaKx1hKpWF+T2oj2lFJPsW/t1Q5e1jQI61eoQSTwpIs= -github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3ve8= -github.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= -github.com/pelletier/go-toml/v2 v2.0.5 h1:ipoSadvV8oGUjnUbMub59IDPPwfxF694nG/jwbMiyQg= -github.com/pelletier/go-toml/v2 v2.0.5/go.mod h1:OMHamSCAODeSsVrwwvcJOaoN0LIUIaFVNZzmWyNfXas= +github.com/pelletier/go-toml/v2 v2.0.6 h1:nrzqCb7j9cDFj2coyLNLaZuJTLjWjlaz6nvTvIwycIU= +github.com/pelletier/go-toml/v2 v2.0.6/go.mod h1:eumQOmlWiOPt5WriQQqoM5y18pDHwha2N+QD+EUNTek= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/sftp v1.13.1/go.mod h1:3HaPG6Dq1ILlpPZRO0HVMrsydcdLt6HRDccSgb87qRg= @@ -364,16 +362,16 @@ github.com/snabb/diagio v1.0.0 h1:kovhQ1rDXoEbmpf/T5N2sUp2iOdxEg+TcqzbYVHV2V0= github.com/snabb/diagio v1.0.0/go.mod h1:ZyGaWFhfBVqstGUw6laYetzeTwZ2xxVPqTALx1QQa1w= github.com/snabb/sitemap v1.0.0 h1:7vJeNPAaaj7fQSRS3WYuJHzUjdnhLdSLLpvVtnhbzC0= github.com/snabb/sitemap v1.0.0/go.mod h1:Id8uz1+WYdiNmSjEi4BIvL5UwNPYLsTHzRbjmDwNDzA= -github.com/spf13/afero v1.9.2 h1:j49Hj62F0n+DaZ1dDCvhABaPNSGNkt32oRFxI33IEMw= -github.com/spf13/afero v1.9.2/go.mod h1:iUV7ddyEEZPO5gA3zD4fJt6iStLlL+Lg4m2cihcDf8Y= +github.com/spf13/afero v1.9.3 h1:41FoI0fD7OR7mGcKE/aOiLkGreyf8ifIOQmJANWogMk= +github.com/spf13/afero v1.9.3/go.mod h1:iUV7ddyEEZPO5gA3zD4fJt6iStLlL+Lg4m2cihcDf8Y= github.com/spf13/cast v1.5.0 h1:rj3WzYc11XZaIZMPKmwP96zkFEnnAmV8s6XbB2aY32w= github.com/spf13/cast v1.5.0/go.mod h1:SpXXQ5YoyJw6s3/6cMTQuxvgRl3PCJiyaX9p6b155UU= github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk= github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= -github.com/spf13/viper v1.14.0 h1:Rg7d3Lo706X9tHsJMUjdiwMpHB7W8WnSVOssIY+JElU= -github.com/spf13/viper v1.14.0/go.mod h1:WT//axPky3FdvXHzGw33dNdXXXfFQqmEalje+egj8As= +github.com/spf13/viper v1.15.0 h1:js3yy885G8xwJa6iOISGFwd+qlUo5AvyXb7CiihdtiU= +github.com/spf13/viper v1.15.0/go.mod h1:fFcTBJxvhhzSJiZy8n+PeW6t8l+KeT/uTARa0jHOQLA= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= @@ -386,8 +384,8 @@ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/ github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= -github.com/subosito/gotenv v1.4.1 h1:jyEFiXpy21Wm81FBN71l9VoMMV8H8jG+qIK3GCpY6Qs= -github.com/subosito/gotenv v1.4.1/go.mod h1:ayKnFf/c6rvx/2iiLrJUk1e6plDbT3edrFNGqEflhK0= +github.com/subosito/gotenv v1.4.2 h1:X1TuBLAMDFbaTAChgCBLu3DU3UPyELpnF2jjJ2cz/S8= +github.com/subosito/gotenv v1.4.2/go.mod h1:ayKnFf/c6rvx/2iiLrJUk1e6plDbT3edrFNGqEflhK0= github.com/tdewolff/minify/v2 v2.12.4 h1:kejsHQMM17n6/gwdw53qsi6lg0TGddZADVyQOz1KMdE= github.com/tdewolff/minify/v2 v2.12.4/go.mod h1:h+SRvSIX3kwgwTFOpSckvSxgax3uy8kZTSF1Ojrr3bk= github.com/tdewolff/parse/v2 v2.6.4 h1:KCkDvNUMof10e3QExio9OPZJT8SbdKojLBumw8YZycQ= @@ -773,8 +771,8 @@ honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWh honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= -maunium.net/go/mautrix v0.12.4 h1:TAg+qkgZLlD2wvshFEuGCv7kADnAbQ6NZmTPu7wsXZI= -maunium.net/go/mautrix v0.12.4/go.mod h1:NBN7/dch8xMnt4VEV9nucVOkzbP4PHr3agXJrFpM5AE= +maunium.net/go/mautrix v0.13.0 h1:CRdpMFc1kDSNnCZMcqahR9/pkDy/vgRbd+fHnSCl6Yg= +maunium.net/go/mautrix v0.13.0/go.mod h1:gYMQPsZ9lQpyKlVp+DGwOuc9LIcE/c8GZW2CvKHISgM= nhooyr.io/websocket v1.8.7 h1:usjR2uOr/zjjkVMy0lW+PPohFok7PCow5sDjLgX4P4g= nhooyr.io/websocket v1.8.7/go.mod h1:B70DZP8IakI65RVQ51MsWP/8jndNma26DVA/nFSCgW0= rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= diff --git a/http.go b/http.go index 45a1928..9fad56b 100644 --- a/http.go +++ b/http.go @@ -16,6 +16,7 @@ import ( "github.com/go-chi/chi/v5/middleware" "github.com/justinas/alice" "github.com/klauspost/compress/flate" + "github.com/samber/lo" "go.goblog.app/app/pkgs/httpcompress" "go.goblog.app/app/pkgs/maprouter" "go.goblog.app/app/pkgs/plugintypes" @@ -46,7 +47,7 @@ func (a *goBlog) startServer() (err error) { h = h.Append(a.securityHeaders) } // Add plugin middlewares - middlewarePlugins := getPluginsForType[plugintypes.Middleware](a, middlewarePlugin) + middlewarePlugins := lo.Map(a.getPlugins(pluginMiddlewareType), func(item any, index int) plugintypes.Middleware { return item.(plugintypes.Middleware) }) sort.Slice(middlewarePlugins, func(i, j int) bool { // Sort with descending prio return middlewarePlugins[i].Prio() > middlewarePlugins[j].Prio() diff --git a/pkgs/plugins/plugin.go b/pkgs/plugins/plugin.go deleted file mode 100644 index 5e13993..0000000 --- a/pkgs/plugins/plugin.go +++ /dev/null @@ -1,59 +0,0 @@ -package plugins - -import ( - "fmt" - "path/filepath" - "reflect" - - "github.com/traefik/yaegi/interp" - "github.com/traefik/yaegi/stdlib" -) - -type plugin struct { - Config *PluginConfig - plugin reflect.Value -} - -// PluginConfig is the configuration of the plugin. -type PluginConfig struct { - // Path is the storage path of the plugin. - Path string - // ImportPath is the module path i.e. "github.com/user/module". - ImportPath string - // PluginType is the type of plugin, this plugin is checked against that type. - // The available types are specified by the implementor of this package. - PluginType string -} - -func (p *plugin) initPlugin(host *PluginHost) error { - const errText = "initPlugin: %w" - - interpreter := interp.New(interp.Options{ - GoPath: p.Config.Path, - }) - - if err := interpreter.Use(stdlib.Symbols); err != nil { - return fmt.Errorf(errText, err) - } - - if err := interpreter.Use(host.Symbols); err != nil { - return fmt.Errorf(errText, err) - } - - if _, err := interpreter.Eval(fmt.Sprintf(`import "%s"`, p.Config.ImportPath)); err != nil { - return fmt.Errorf(errText, err) - } - - v, err := interpreter.Eval(filepath.Base(p.Config.ImportPath) + ".GetPlugin") - if err != nil { - return fmt.Errorf(errText, err) - } - - result := v.Call([]reflect.Value{}) - if len(result) > 1 { - return fmt.Errorf(errText+": function GetPlugin has more than one return value", ErrValidatingPlugin) - } - p.plugin = result[0] - - return nil -} diff --git a/pkgs/plugins/plugins.go b/pkgs/plugins/plugins.go index 89b7660..30c414e 100644 --- a/pkgs/plugins/plugins.go +++ b/pkgs/plugins/plugins.go @@ -1,80 +1,124 @@ package plugins import ( + "errors" "fmt" + "io/fs" + "path/filepath" "reflect" + "strings" "github.com/traefik/yaegi/interp" + "github.com/traefik/yaegi/stdlib" +) + +// PluginHost manages the plugins. +type PluginHost struct { + plugins map[string][]*plugin + pluginTypes map[string]reflect.Type + symbols interp.Exports + embeddedPlugins fs.FS +} + +// PluginConfig is the configuration of the plugin. +type PluginConfig struct { + // Path is the storage path of the plugin. + Path string + // ImportPath is the module path i.e. "github.com/user/module". + ImportPath string +} + +type plugin struct { + Config *PluginConfig + plugin any +} + +var ( + // ErrValidatingPlugin is returned when the plugin fails to fully implement the interface of the plugin type. + ErrValidatingPlugin = errors.New("plugin does not implement type") + // ErrInitFunc is returned when the plugin has a faulty initialization function. + ErrInitFunc = errors.New("bad plugin init function") +) + +const ( + embeddedPrefix = "embedded:" ) // NewPluginHost initializes a PluginHost. -func NewPluginHost(symbols interp.Exports) *PluginHost { +func NewPluginHost(pluginTypes map[string]reflect.Type, symbols interp.Exports, embeddedPlugins fs.FS) *PluginHost { return &PluginHost{ - Plugins: []*plugin{}, - PluginTypes: map[string]reflect.Type{}, - Symbols: symbols, + plugins: map[string][]*plugin{}, + pluginTypes: pluginTypes, + symbols: symbols, + embeddedPlugins: embeddedPlugins, } } -// AddPluginType adds a plugin type to the list. -// The interface for the pluginType parameter should be a nil of the plugin type interface: -// -// (*PluginInterface)(nil) -func (h *PluginHost) AddPluginType(name string, pluginType interface{}) { - h.PluginTypes[name] = reflect.TypeOf(pluginType).Elem() -} - // LoadPlugin loads a new plugin to the host. -func (h *PluginHost) LoadPlugin(config *PluginConfig) (any, error) { +func (h *PluginHost) LoadPlugin(config *PluginConfig) (map[string]any, error) { p := &plugin{ Config: config, } - err := p.initPlugin(h) + plugins, err := p.initPlugin(h) if err != nil { return nil, err } - err = h.validatePlugin(p) - if err != nil { - return nil, err - } - h.Plugins = append(h.Plugins, p) - return p.plugin.Interface(), nil -} - -func (h *PluginHost) validatePlugin(p *plugin) error { - pType := reflect.TypeOf(p.plugin.Interface()) - - if _, ok := h.PluginTypes[p.Config.PluginType]; !ok { - return fmt.Errorf("validatePlugin: %v: %w", p.Config.PluginType, ErrInvalidType) - } - - if !pType.Implements(h.PluginTypes[p.Config.PluginType]) { - return fmt.Errorf("validatePlugin:%v: %w %v", p, ErrValidatingPlugin, p.Config.PluginType) - } - - return nil + return plugins, nil } // GetPlugins returns a list of all plugins. -func (h *PluginHost) GetPlugins() (list []any) { - for _, p := range h.Plugins { - list = append(list, p.plugin.Interface()) +func (h *PluginHost) GetPlugins(typ string) (list []any) { + for _, p := range h.plugins[typ] { + list = append(list, p.plugin) } return } -// GetPluginsForType returns all the plugins that are of type pluginType or empty if the pluginType doesn't exist. -func GetPluginsForType[T any](h *PluginHost, pluginType string) (list []T) { - if _, ok := h.PluginTypes[pluginType]; !ok { - return +func (p *plugin) initPlugin(host *PluginHost) (plugins map[string]any, err error) { + const errText = "initPlugin: %w" + + plugins = map[string]any{} + + var filesystem fs.FS + if strings.HasPrefix(p.Config.Path, embeddedPrefix) { + filesystem = host.embeddedPlugins } - for _, p := range h.Plugins { - if p.Config.PluginType != pluginType { - continue + + interpreter := interp.New(interp.Options{ + GoPath: strings.TrimPrefix(p.Config.Path, embeddedPrefix), + SourcecodeFilesystem: filesystem, + }) + + if err := interpreter.Use(stdlib.Symbols); err != nil { + return nil, fmt.Errorf(errText, err) + } + + if err := interpreter.Use(host.symbols); err != nil { + return nil, fmt.Errorf(errText, err) + } + + if _, err := interpreter.Eval(fmt.Sprintf(`import "%s"`, p.Config.ImportPath)); err != nil { + return nil, fmt.Errorf(errText, err) + } + + v, err := interpreter.Eval(filepath.Base(p.Config.ImportPath) + ".GetPlugin") + if err != nil { + return nil, fmt.Errorf(errText, err) + } + + resultArray := v.Call([]reflect.Value{}) + for _, result := range resultArray { + newPlugin := &plugin{ + Config: p.Config, + plugin: result.Interface(), } - if t, ok := p.plugin.Interface().(T); ok { - list = append(list, t) + for name, reflectType := range host.pluginTypes { + if result.Type().Implements(reflectType) { + host.plugins[name] = append(host.plugins[name], newPlugin) + plugins[name] = newPlugin.plugin + } } } + return } diff --git a/pkgs/plugins/types.go b/pkgs/plugins/types.go deleted file mode 100644 index f1b310f..0000000 --- a/pkgs/plugins/types.go +++ /dev/null @@ -1,25 +0,0 @@ -package plugins - -import ( - "errors" - "reflect" - - "github.com/traefik/yaegi/interp" -) - -// PluginHost manages the plugins. -type PluginHost struct { - // Plugins contains a list of the plugins. - Plugins []*plugin - // PluginTypes is a list of plugins types that plugins have to use at least one of. - PluginTypes map[string]reflect.Type - // Symbols is the map of symbols generated by yaegi extract. - Symbols interp.Exports -} - -var ( - // ErrInvalidType is returned when the plugin type specified by the plugin is invalid. - ErrInvalidType = errors.New("invalid plugin type") - // ErrValidatingPlugin is returned when the plugin fails to fully implement the interface of the plugin type. - ErrValidatingPlugin = errors.New("plugin does not implement type") -) diff --git a/pkgs/plugintypes/goblog.go b/pkgs/plugintypes/goblog.go index 79f8d02..17bcbe9 100644 --- a/pkgs/plugintypes/goblog.go +++ b/pkgs/plugintypes/goblog.go @@ -3,13 +3,12 @@ package plugintypes import ( "context" "database/sql" - - "go.goblog.app/app/pkgs/htmlbuilder" ) // App is used to access GoBlog's app instance. type App interface { GetDatabase() Database + GetPost(path string) (Post, error) } // Database is used to provide access to GoBlog's database. @@ -27,36 +26,8 @@ type Post interface { GetParameters() map[string][]string } -// Blog -type Blog interface { +// RenderContext +type RenderContext interface { + GetPath() string GetBlog() string } - -// RenderType -type RenderType string - -// RenderData -type RenderData interface { - // Empty -} - -// RenderNextFunc -type RenderNextFunc func(*htmlbuilder.HtmlBuilder) - -// Render main element content on post page, data = PostRenderData -const PostMainElementRenderType RenderType = "post-main-content" - -// PostRenderData is RenderData containing a Post -type PostRenderData interface { - RenderData - GetPost() Post -} - -// Render footer element on every blog page, data = BlogRenderData -const BlogFooterRenderType RenderType = "blog-footer" - -// BlogRenderData is RenderData containing a Blog -type BlogRenderData interface { - RenderData - GetBlog() Blog -} diff --git a/pkgs/plugintypes/plugins.go b/pkgs/plugintypes/plugins.go index 32740f2..280e2eb 100644 --- a/pkgs/plugintypes/plugins.go +++ b/pkgs/plugintypes/plugins.go @@ -1,38 +1,29 @@ package plugintypes import ( + "io" "net/http" - - "go.goblog.app/app/pkgs/htmlbuilder" ) -// SetApp is used in all plugin types to allow -// GoBlog set it's app instance to be accessible by the plugin. +// SetApp is used to allow GoBlog set its app instance to be accessible by the plugin. type SetApp interface { - SetApp(App) + SetApp(app App) } -// SetConfig is used in all plugin types to allow -// GoBlog set plugin configuration. +// SetConfig is used in all plugin types to allow GoBlog set the plugin configuration. type SetConfig interface { - SetConfig(map[string]any) + SetConfig(config map[string]any) } type Exec interface { - SetApp - SetConfig Exec() } type Middleware interface { - SetApp - SetConfig - Handler(http.Handler) http.Handler + Handler(next http.Handler) http.Handler Prio() int } type UI interface { - SetApp - SetConfig - Render(*htmlbuilder.HtmlBuilder, RenderType, RenderData, RenderNextFunc) + Render(renderContext RenderContext, rendered io.Reader, modified io.Writer) } diff --git a/pkgs/yaegiwrappers/github_com-PuerkitoBio-goquery.go b/pkgs/yaegiwrappers/github_com-PuerkitoBio-goquery.go new file mode 100644 index 0000000..bebcb31 --- /dev/null +++ b/pkgs/yaegiwrappers/github_com-PuerkitoBio-goquery.go @@ -0,0 +1,74 @@ +// Code generated by 'yaegi extract github.com/PuerkitoBio/goquery'. DO NOT EDIT. + +// MIT License +// +// Copyright (c) 2020 - 2023 Jan-Lukas Else +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +package yaegiwrappers + +import ( + "github.com/PuerkitoBio/goquery" + "golang.org/x/net/html" + "reflect" +) + +func init() { + Symbols["github.com/PuerkitoBio/goquery/goquery"] = map[string]reflect.Value{ + // function, constant and variable definitions + "CloneDocument": reflect.ValueOf(goquery.CloneDocument), + "NewDocument": reflect.ValueOf(goquery.NewDocument), + "NewDocumentFromNode": reflect.ValueOf(goquery.NewDocumentFromNode), + "NewDocumentFromReader": reflect.ValueOf(goquery.NewDocumentFromReader), + "NewDocumentFromResponse": reflect.ValueOf(goquery.NewDocumentFromResponse), + "NodeName": reflect.ValueOf(goquery.NodeName), + "OuterHtml": reflect.ValueOf(goquery.OuterHtml), + "Render": reflect.ValueOf(goquery.Render), + "Single": reflect.ValueOf(goquery.Single), + "SingleMatcher": reflect.ValueOf(goquery.SingleMatcher), + "ToEnd": reflect.ValueOf(goquery.ToEnd), + + // type definitions + "Document": reflect.ValueOf((*goquery.Document)(nil)), + "Matcher": reflect.ValueOf((*goquery.Matcher)(nil)), + "Selection": reflect.ValueOf((*goquery.Selection)(nil)), + + // interface wrapper definitions + "_Matcher": reflect.ValueOf((*_github_com_PuerkitoBio_goquery_Matcher)(nil)), + } +} + +// _github_com_PuerkitoBio_goquery_Matcher is an interface wrapper for Matcher type +type _github_com_PuerkitoBio_goquery_Matcher struct { + IValue interface{} + WFilter func(a0 []*html.Node) []*html.Node + WMatch func(a0 *html.Node) bool + WMatchAll func(a0 *html.Node) []*html.Node +} + +func (W _github_com_PuerkitoBio_goquery_Matcher) Filter(a0 []*html.Node) []*html.Node { + return W.WFilter(a0) +} +func (W _github_com_PuerkitoBio_goquery_Matcher) Match(a0 *html.Node) bool { + return W.WMatch(a0) +} +func (W _github_com_PuerkitoBio_goquery_Matcher) MatchAll(a0 *html.Node) []*html.Node { + return W.WMatchAll(a0) +} diff --git a/pkgs/yaegiwrappers/go_goblog_app-app-pkgs-bufferpool.go b/pkgs/yaegiwrappers/go_goblog_app-app-pkgs-bufferpool.go new file mode 100644 index 0000000..8610034 --- /dev/null +++ b/pkgs/yaegiwrappers/go_goblog_app-app-pkgs-bufferpool.go @@ -0,0 +1,38 @@ +// Code generated by 'yaegi extract go.goblog.app/app/pkgs/bufferpool'. DO NOT EDIT. + +// MIT License +// +// Copyright (c) 2020 - 2023 Jan-Lukas Else +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +package yaegiwrappers + +import ( + "go.goblog.app/app/pkgs/bufferpool" + "reflect" +) + +func init() { + Symbols["go.goblog.app/app/pkgs/bufferpool/bufferpool"] = map[string]reflect.Value{ + // function, constant and variable definitions + "Get": reflect.ValueOf(bufferpool.Get), + "Put": reflect.ValueOf(bufferpool.Put), + } +} diff --git a/pkgs/yaegiwrappers/go_goblog_app-app-pkgs-htmlbuilder.go b/pkgs/yaegiwrappers/go_goblog_app-app-pkgs-htmlbuilder.go index f13e699..4baa3ee 100644 --- a/pkgs/yaegiwrappers/go_goblog_app-app-pkgs-htmlbuilder.go +++ b/pkgs/yaegiwrappers/go_goblog_app-app-pkgs-htmlbuilder.go @@ -2,7 +2,7 @@ // MIT License // -// Copyright (c) 2020 - 2022 Jan-Lukas Else +// Copyright (c) 2020 - 2023 Jan-Lukas Else // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal diff --git a/pkgs/yaegiwrappers/go_goblog_app-app-pkgs-plugintypes.go b/pkgs/yaegiwrappers/go_goblog_app-app-pkgs-plugintypes.go index 4b0ba44..e2b6a7f 100644 --- a/pkgs/yaegiwrappers/go_goblog_app-app-pkgs-plugintypes.go +++ b/pkgs/yaegiwrappers/go_goblog_app-app-pkgs-plugintypes.go @@ -2,7 +2,7 @@ // MIT License // -// Copyright (c) 2020 - 2022 Jan-Lukas Else +// Copyright (c) 2020 - 2023 Jan-Lukas Else // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal @@ -27,47 +27,35 @@ package yaegiwrappers import ( "context" "database/sql" - "go.goblog.app/app/pkgs/htmlbuilder" "go.goblog.app/app/pkgs/plugintypes" + "io" "net/http" "reflect" ) func init() { Symbols["go.goblog.app/app/pkgs/plugintypes/plugintypes"] = map[string]reflect.Value{ - // function, constant and variable definitions - "BlogFooterRenderType": reflect.ValueOf(plugintypes.BlogFooterRenderType), - "PostMainElementRenderType": reflect.ValueOf(plugintypes.PostMainElementRenderType), - // type definitions - "App": reflect.ValueOf((*plugintypes.App)(nil)), - "Blog": reflect.ValueOf((*plugintypes.Blog)(nil)), - "BlogRenderData": reflect.ValueOf((*plugintypes.BlogRenderData)(nil)), - "Database": reflect.ValueOf((*plugintypes.Database)(nil)), - "Exec": reflect.ValueOf((*plugintypes.Exec)(nil)), - "Middleware": reflect.ValueOf((*plugintypes.Middleware)(nil)), - "Post": reflect.ValueOf((*plugintypes.Post)(nil)), - "PostRenderData": reflect.ValueOf((*plugintypes.PostRenderData)(nil)), - "RenderData": reflect.ValueOf((*plugintypes.RenderData)(nil)), - "RenderNextFunc": reflect.ValueOf((*plugintypes.RenderNextFunc)(nil)), - "RenderType": reflect.ValueOf((*plugintypes.RenderType)(nil)), - "SetApp": reflect.ValueOf((*plugintypes.SetApp)(nil)), - "SetConfig": reflect.ValueOf((*plugintypes.SetConfig)(nil)), - "UI": reflect.ValueOf((*plugintypes.UI)(nil)), + "App": reflect.ValueOf((*plugintypes.App)(nil)), + "Database": reflect.ValueOf((*plugintypes.Database)(nil)), + "Exec": reflect.ValueOf((*plugintypes.Exec)(nil)), + "Middleware": reflect.ValueOf((*plugintypes.Middleware)(nil)), + "Post": reflect.ValueOf((*plugintypes.Post)(nil)), + "RenderContext": reflect.ValueOf((*plugintypes.RenderContext)(nil)), + "SetApp": reflect.ValueOf((*plugintypes.SetApp)(nil)), + "SetConfig": reflect.ValueOf((*plugintypes.SetConfig)(nil)), + "UI": reflect.ValueOf((*plugintypes.UI)(nil)), // interface wrapper definitions - "_App": reflect.ValueOf((*_go_goblog_app_app_pkgs_plugintypes_App)(nil)), - "_Blog": reflect.ValueOf((*_go_goblog_app_app_pkgs_plugintypes_Blog)(nil)), - "_BlogRenderData": reflect.ValueOf((*_go_goblog_app_app_pkgs_plugintypes_BlogRenderData)(nil)), - "_Database": reflect.ValueOf((*_go_goblog_app_app_pkgs_plugintypes_Database)(nil)), - "_Exec": reflect.ValueOf((*_go_goblog_app_app_pkgs_plugintypes_Exec)(nil)), - "_Middleware": reflect.ValueOf((*_go_goblog_app_app_pkgs_plugintypes_Middleware)(nil)), - "_Post": reflect.ValueOf((*_go_goblog_app_app_pkgs_plugintypes_Post)(nil)), - "_PostRenderData": reflect.ValueOf((*_go_goblog_app_app_pkgs_plugintypes_PostRenderData)(nil)), - "_RenderData": reflect.ValueOf((*_go_goblog_app_app_pkgs_plugintypes_RenderData)(nil)), - "_SetApp": reflect.ValueOf((*_go_goblog_app_app_pkgs_plugintypes_SetApp)(nil)), - "_SetConfig": reflect.ValueOf((*_go_goblog_app_app_pkgs_plugintypes_SetConfig)(nil)), - "_UI": reflect.ValueOf((*_go_goblog_app_app_pkgs_plugintypes_UI)(nil)), + "_App": reflect.ValueOf((*_go_goblog_app_app_pkgs_plugintypes_App)(nil)), + "_Database": reflect.ValueOf((*_go_goblog_app_app_pkgs_plugintypes_Database)(nil)), + "_Exec": reflect.ValueOf((*_go_goblog_app_app_pkgs_plugintypes_Exec)(nil)), + "_Middleware": reflect.ValueOf((*_go_goblog_app_app_pkgs_plugintypes_Middleware)(nil)), + "_Post": reflect.ValueOf((*_go_goblog_app_app_pkgs_plugintypes_Post)(nil)), + "_RenderContext": reflect.ValueOf((*_go_goblog_app_app_pkgs_plugintypes_RenderContext)(nil)), + "_SetApp": reflect.ValueOf((*_go_goblog_app_app_pkgs_plugintypes_SetApp)(nil)), + "_SetConfig": reflect.ValueOf((*_go_goblog_app_app_pkgs_plugintypes_SetConfig)(nil)), + "_UI": reflect.ValueOf((*_go_goblog_app_app_pkgs_plugintypes_UI)(nil)), } } @@ -75,30 +63,14 @@ func init() { type _go_goblog_app_app_pkgs_plugintypes_App struct { IValue interface{} WGetDatabase func() plugintypes.Database + WGetPost func(path string) (plugintypes.Post, error) } func (W _go_goblog_app_app_pkgs_plugintypes_App) GetDatabase() plugintypes.Database { return W.WGetDatabase() } - -// _go_goblog_app_app_pkgs_plugintypes_Blog is an interface wrapper for Blog type -type _go_goblog_app_app_pkgs_plugintypes_Blog struct { - IValue interface{} - WGetBlog func() string -} - -func (W _go_goblog_app_app_pkgs_plugintypes_Blog) GetBlog() string { - return W.WGetBlog() -} - -// _go_goblog_app_app_pkgs_plugintypes_BlogRenderData is an interface wrapper for BlogRenderData type -type _go_goblog_app_app_pkgs_plugintypes_BlogRenderData struct { - IValue interface{} - WGetBlog func() plugintypes.Blog -} - -func (W _go_goblog_app_app_pkgs_plugintypes_BlogRenderData) GetBlog() plugintypes.Blog { - return W.WGetBlog() +func (W _go_goblog_app_app_pkgs_plugintypes_App) GetPost(path string) (plugintypes.Post, error) { + return W.WGetPost(path) } // _go_goblog_app_app_pkgs_plugintypes_Database is an interface wrapper for Database type @@ -133,43 +105,27 @@ func (W _go_goblog_app_app_pkgs_plugintypes_Database) QueryRowContext(a0 context // _go_goblog_app_app_pkgs_plugintypes_Exec is an interface wrapper for Exec type type _go_goblog_app_app_pkgs_plugintypes_Exec struct { - IValue interface{} - WExec func() - WSetApp func(a0 plugintypes.App) - WSetConfig func(a0 map[string]any) + IValue interface{} + WExec func() } func (W _go_goblog_app_app_pkgs_plugintypes_Exec) Exec() { W.WExec() } -func (W _go_goblog_app_app_pkgs_plugintypes_Exec) SetApp(a0 plugintypes.App) { - W.WSetApp(a0) -} -func (W _go_goblog_app_app_pkgs_plugintypes_Exec) SetConfig(a0 map[string]any) { - W.WSetConfig(a0) -} // _go_goblog_app_app_pkgs_plugintypes_Middleware is an interface wrapper for Middleware type type _go_goblog_app_app_pkgs_plugintypes_Middleware struct { - IValue interface{} - WHandler func(a0 http.Handler) http.Handler - WPrio func() int - WSetApp func(a0 plugintypes.App) - WSetConfig func(a0 map[string]any) + IValue interface{} + WHandler func(next http.Handler) http.Handler + WPrio func() int } -func (W _go_goblog_app_app_pkgs_plugintypes_Middleware) Handler(a0 http.Handler) http.Handler { - return W.WHandler(a0) +func (W _go_goblog_app_app_pkgs_plugintypes_Middleware) Handler(next http.Handler) http.Handler { + return W.WHandler(next) } func (W _go_goblog_app_app_pkgs_plugintypes_Middleware) Prio() int { return W.WPrio() } -func (W _go_goblog_app_app_pkgs_plugintypes_Middleware) SetApp(a0 plugintypes.App) { - W.WSetApp(a0) -} -func (W _go_goblog_app_app_pkgs_plugintypes_Middleware) SetConfig(a0 map[string]any) { - W.WSetConfig(a0) -} // _go_goblog_app_app_pkgs_plugintypes_Post is an interface wrapper for Post type type _go_goblog_app_app_pkgs_plugintypes_Post struct { @@ -181,55 +137,46 @@ func (W _go_goblog_app_app_pkgs_plugintypes_Post) GetParameters() map[string][]s return W.WGetParameters() } -// _go_goblog_app_app_pkgs_plugintypes_PostRenderData is an interface wrapper for PostRenderData type -type _go_goblog_app_app_pkgs_plugintypes_PostRenderData struct { +// _go_goblog_app_app_pkgs_plugintypes_RenderContext is an interface wrapper for RenderContext type +type _go_goblog_app_app_pkgs_plugintypes_RenderContext struct { IValue interface{} - WGetPost func() plugintypes.Post + WGetBlog func() string + WGetPath func() string } -func (W _go_goblog_app_app_pkgs_plugintypes_PostRenderData) GetPost() plugintypes.Post { - return W.WGetPost() +func (W _go_goblog_app_app_pkgs_plugintypes_RenderContext) GetBlog() string { + return W.WGetBlog() } - -// _go_goblog_app_app_pkgs_plugintypes_RenderData is an interface wrapper for RenderData type -type _go_goblog_app_app_pkgs_plugintypes_RenderData struct { - IValue interface{} +func (W _go_goblog_app_app_pkgs_plugintypes_RenderContext) GetPath() string { + return W.WGetPath() } // _go_goblog_app_app_pkgs_plugintypes_SetApp is an interface wrapper for SetApp type type _go_goblog_app_app_pkgs_plugintypes_SetApp struct { IValue interface{} - WSetApp func(a0 plugintypes.App) + WSetApp func(app plugintypes.App) } -func (W _go_goblog_app_app_pkgs_plugintypes_SetApp) SetApp(a0 plugintypes.App) { - W.WSetApp(a0) +func (W _go_goblog_app_app_pkgs_plugintypes_SetApp) SetApp(app plugintypes.App) { + W.WSetApp(app) } // _go_goblog_app_app_pkgs_plugintypes_SetConfig is an interface wrapper for SetConfig type type _go_goblog_app_app_pkgs_plugintypes_SetConfig struct { IValue interface{} - WSetConfig func(a0 map[string]any) + WSetConfig func(config map[string]any) } -func (W _go_goblog_app_app_pkgs_plugintypes_SetConfig) SetConfig(a0 map[string]any) { - W.WSetConfig(a0) +func (W _go_goblog_app_app_pkgs_plugintypes_SetConfig) SetConfig(config map[string]any) { + W.WSetConfig(config) } // _go_goblog_app_app_pkgs_plugintypes_UI is an interface wrapper for UI type type _go_goblog_app_app_pkgs_plugintypes_UI struct { - IValue interface{} - WRender func(a0 *htmlbuilder.HtmlBuilder, a1 plugintypes.RenderType, a2 plugintypes.RenderData, a3 plugintypes.RenderNextFunc) - WSetApp func(a0 plugintypes.App) - WSetConfig func(a0 map[string]any) + IValue interface{} + WRender func(renderContext plugintypes.RenderContext, rendered io.Reader, modified io.Writer) } -func (W _go_goblog_app_app_pkgs_plugintypes_UI) Render(a0 *htmlbuilder.HtmlBuilder, a1 plugintypes.RenderType, a2 plugintypes.RenderData, a3 plugintypes.RenderNextFunc) { - W.WRender(a0, a1, a2, a3) -} -func (W _go_goblog_app_app_pkgs_plugintypes_UI) SetApp(a0 plugintypes.App) { - W.WSetApp(a0) -} -func (W _go_goblog_app_app_pkgs_plugintypes_UI) SetConfig(a0 map[string]any) { - W.WSetConfig(a0) +func (W _go_goblog_app_app_pkgs_plugintypes_UI) Render(renderContext plugintypes.RenderContext, rendered io.Reader, modified io.Writer) { + W.WRender(renderContext, rendered, modified) } diff --git a/pkgs/yaegiwrappers/wrappers.go b/pkgs/yaegiwrappers/wrappers.go index 5b6919e..861c0a2 100644 --- a/pkgs/yaegiwrappers/wrappers.go +++ b/pkgs/yaegiwrappers/wrappers.go @@ -8,5 +8,10 @@ var ( Symbols = make(map[string]map[string]reflect.Value) ) +// GoBlog packages //go:generate yaegi extract -license ../../LICENSE -name yaegiwrappers go.goblog.app/app/pkgs/plugintypes //go:generate yaegi extract -license ../../LICENSE -name yaegiwrappers go.goblog.app/app/pkgs/htmlbuilder +//go:generate yaegi extract -license ../../LICENSE -name yaegiwrappers go.goblog.app/app/pkgs/bufferpool + +// Dependencies +//go:generate yaegi extract -license ../../LICENSE -name yaegiwrappers github.com/PuerkitoBio/goquery diff --git a/plugins.go b/plugins.go index f907c46..ac629ed 100644 --- a/plugins.go +++ b/plugins.go @@ -1,54 +1,68 @@ package main import ( + "embed" + "io/fs" + "reflect" + "go.goblog.app/app/pkgs/plugins" "go.goblog.app/app/pkgs/plugintypes" "go.goblog.app/app/pkgs/yaegiwrappers" ) +//go:embed plugins/* +var pluginsFS embed.FS + const ( - execPlugin = "exec" - middlewarePlugin = "middleware" - uiPlugin = "ui" + pluginSetAppType = "setapp" + pluginSetConfigType = "setconfig" + pluginUiType = "ui" + pluginExecType = "exec" + pluginMiddlewareType = "middleware" ) func (a *goBlog) initPlugins() error { - a.pluginHost = plugins.NewPluginHost(yaegiwrappers.Symbols) - - a.pluginHost.AddPluginType(execPlugin, (*plugintypes.Exec)(nil)) - a.pluginHost.AddPluginType(middlewarePlugin, (*plugintypes.Middleware)(nil)) - a.pluginHost.AddPluginType(uiPlugin, (*plugintypes.UI)(nil)) + subFS, err := fs.Sub(pluginsFS, "plugins") + if err != nil { + return err + } + a.pluginHost = plugins.NewPluginHost( + map[string]reflect.Type{ + pluginSetAppType: reflect.TypeOf((*plugintypes.SetApp)(nil)).Elem(), + pluginSetConfigType: reflect.TypeOf((*plugintypes.SetConfig)(nil)).Elem(), + pluginUiType: reflect.TypeOf((*plugintypes.UI)(nil)).Elem(), + pluginExecType: reflect.TypeOf((*plugintypes.Exec)(nil)).Elem(), + pluginMiddlewareType: reflect.TypeOf((*plugintypes.Middleware)(nil)).Elem(), + }, + yaegiwrappers.Symbols, + subFS, + ) for _, pc := range a.cfg.Plugins { - if pluginInterface, err := a.pluginHost.LoadPlugin(&plugins.PluginConfig{ + plugins, err := a.pluginHost.LoadPlugin(&plugins.PluginConfig{ Path: pc.Path, ImportPath: pc.Import, - PluginType: pc.Type, - }); err != nil { + }) + if err != nil { return err - } else if pluginInterface != nil { - if setAppPlugin, ok := pluginInterface.(plugintypes.SetApp); ok { - setAppPlugin.SetApp(a) - } - if setConfigPlugin, ok := pluginInterface.(plugintypes.SetConfig); ok { - setConfigPlugin.SetConfig(pc.Config) - } + } + if p, ok := plugins[pluginSetConfigType]; ok { + p.(plugintypes.SetConfig).SetConfig(pc.Config) + } + if p, ok := plugins[pluginSetAppType]; ok { + p.(plugintypes.SetApp).SetApp(a) } } - execs := getPluginsForType[plugintypes.Exec](a, execPlugin) - for _, p := range execs { - go p.Exec() + for _, p := range a.getPlugins(pluginExecType) { + go p.(plugintypes.Exec).Exec() } return nil } -func getPluginsForType[T any](a *goBlog, pluginType string) (list []T) { - if a == nil || a.pluginHost == nil { - return nil - } - return plugins.GetPluginsForType[T](a.pluginHost, pluginType) +func (a *goBlog) getPlugins(typ string) []any { + return a.pluginHost.GetPlugins(typ) } // Implement all needed interfaces @@ -57,34 +71,10 @@ func (a *goBlog) GetDatabase() plugintypes.Database { return a.db } +func (a *goBlog) GetPost(path string) (plugintypes.Post, error) { + return a.getPost(path) +} + func (p *post) GetParameters() map[string][]string { return p.Parameters } - -type pluginPostRenderData struct { - p *post -} - -func (d *pluginPostRenderData) GetPost() plugintypes.Post { - return d.p -} - -func (p *post) pluginRenderData() plugintypes.PostRenderData { - return &pluginPostRenderData{p: p} -} - -func (b *configBlog) GetBlog() string { - return b.name -} - -type pluginBlogRenderData struct { - b *configBlog -} - -func (d *pluginBlogRenderData) GetBlog() plugintypes.Blog { - return d.b -} - -func (b *configBlog) pluginRenderData() plugintypes.BlogRenderData { - return &pluginBlogRenderData{b: b} -} diff --git a/plugins/demo/src/demo/demo.go b/plugins/demo/src/demo/demo.go new file mode 100644 index 0000000..74fbe41 --- /dev/null +++ b/plugins/demo/src/demo/demo.go @@ -0,0 +1,87 @@ +package demo + +import ( + "fmt" + "io" + "net/http" + + "github.com/PuerkitoBio/goquery" + "go.goblog.app/app/pkgs/bufferpool" + "go.goblog.app/app/pkgs/htmlbuilder" + "go.goblog.app/app/pkgs/plugintypes" +) + +type plugin struct { + app plugintypes.App + config map[string]any +} + +func GetPlugin() ( + plugintypes.SetApp, plugintypes.SetConfig, + plugintypes.UI, + plugintypes.Exec, + plugintypes.Middleware, +) { + p := &plugin{} + return p, p, p, p, p +} + +// SetApp +func (p *plugin) SetApp(app plugintypes.App) { + p.app = app +} + +// SetConfig +func (p *plugin) SetConfig(config map[string]any) { + p.config = config +} + +// UI +func (p *plugin) Render(_ plugintypes.RenderContext, rendered io.Reader, modified io.Writer) { + doc, err := goquery.NewDocumentFromReader(rendered) + if err != nil { + fmt.Println("demoui plugin: " + err.Error()) + return + } + buf := bufferpool.Get() + defer bufferpool.Put(buf) + hb := htmlbuilder.NewHtmlBuilder(buf) + hb.WriteElementOpen("p") + hb.WriteEscaped("End of post content") + hb.WriteElementClose("p") + doc.Find("main.h-entry article div.e-content").AppendHtml(buf.String()) + _ = goquery.Render(modified, doc.Selection) +} + +// Exec +func (p *plugin) Exec() { + fmt.Println("Hello World from the demo plugin!") + + row, _ := p.app.GetDatabase().QueryRow("select count (*) from posts") + var count int + if err := row.Scan(&count); err != nil { + fmt.Println(fmt.Errorf("failed to count posts: %w", err)) + return + } + + fmt.Printf("Number of posts in database: %d", count) + fmt.Println() +} + +// Middleware +func (p *plugin) Prio() int { + if prioAny, ok := p.config["prio"]; ok { + if prio, ok := prioAny.(int); ok { + return prio + } + } + return 100 +} + +// Middleware +func (p *plugin) Handler(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("X-Demo", fmt.Sprintf("This is from the demo middleware with prio %d", p.Prio())) + next.ServeHTTP(w, r) + }) +} diff --git a/plugins/demo/src/demoexec/demo.go b/plugins/demo/src/demoexec/demo.go deleted file mode 100644 index 1dc0c6c..0000000 --- a/plugins/demo/src/demoexec/demo.go +++ /dev/null @@ -1,37 +0,0 @@ -package demoexec - -import ( - "fmt" - - "go.goblog.app/app/pkgs/plugintypes" -) - -func GetPlugin() plugintypes.Exec { - return &plugin{} -} - -type plugin struct { - app plugintypes.App -} - -func (p *plugin) SetApp(app plugintypes.App) { - p.app = app -} - -func (*plugin) SetConfig(_ map[string]any) { - // Ignore -} - -func (p *plugin) Exec() { - fmt.Println("Hello World from the demo plugin!") - - row, _ := p.app.GetDatabase().QueryRow("select count (*) from posts") - var count int - if err := row.Scan(&count); err != nil { - fmt.Println(fmt.Errorf("failed to count posts: %w", err)) - return - } - - fmt.Printf("Number of posts in database: %d", count) - fmt.Println() -} diff --git a/plugins/demo/src/demomiddleware/demo.go b/plugins/demo/src/demomiddleware/demo.go deleted file mode 100644 index 4332fc0..0000000 --- a/plugins/demo/src/demomiddleware/demo.go +++ /dev/null @@ -1,41 +0,0 @@ -package demomiddleware - -import ( - "fmt" - "net/http" - - "go.goblog.app/app/pkgs/plugintypes" -) - -func GetPlugin() plugintypes.Middleware { - return &plugin{} -} - -type plugin struct { - app plugintypes.App - config map[string]any -} - -func (p *plugin) SetApp(app plugintypes.App) { - p.app = app -} - -func (p *plugin) SetConfig(config map[string]any) { - p.config = config -} - -func (p *plugin) Prio() int { - if prioAny, ok := p.config["prio"]; ok { - if prio, ok := prioAny.(int); ok { - return prio - } - } - return 100 -} - -func (p *plugin) Handler(next http.Handler) http.Handler { - return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - w.Header().Set("X-Demo", fmt.Sprintf("This is from the demo middleware with prio %d", p.Prio())) - next.ServeHTTP(w, r) - }) -} diff --git a/plugins/demo/src/demoui/demo.go b/plugins/demo/src/demoui/demo.go deleted file mode 100644 index e6ecc44..0000000 --- a/plugins/demo/src/demoui/demo.go +++ /dev/null @@ -1,36 +0,0 @@ -package demoui - -import ( - "go.goblog.app/app/pkgs/htmlbuilder" - "go.goblog.app/app/pkgs/plugintypes" -) - -func GetPlugin() plugintypes.UI { - return &plugin{} -} - -type plugin struct{} - -func (*plugin) SetApp(_ plugintypes.App) { - // Ignore -} - -func (*plugin) SetConfig(_ map[string]any) { - // Ignore -} - -func (*plugin) Render(hb *htmlbuilder.HtmlBuilder, t plugintypes.RenderType, _ plugintypes.RenderData, render plugintypes.RenderNextFunc) { - switch t { - case plugintypes.PostMainElementRenderType: - hb.WriteElementOpen("p") - hb.WriteEscaped("Start of post main element") - hb.WriteElementClose("p") - render(hb) - hb.WriteElementOpen("p") - hb.WriteEscaped("End of post main element") - hb.WriteElementClose("p") - return - default: - render(hb) - } -} diff --git a/plugins/syndication/src/syndication/syndication.go b/plugins/syndication/src/syndication/syndication.go index bb5c34f..20d698f 100644 --- a/plugins/syndication/src/syndication/syndication.go +++ b/plugins/syndication/src/syndication/syndication.go @@ -2,53 +2,64 @@ package syndication import ( "fmt" + "io" + "github.com/PuerkitoBio/goquery" + "go.goblog.app/app/pkgs/bufferpool" "go.goblog.app/app/pkgs/htmlbuilder" "go.goblog.app/app/pkgs/plugintypes" ) -func GetPlugin() plugintypes.UI { - return &plugin{} -} - type plugin struct { - config map[string]any + app plugintypes.App + parameterName string } -func (*plugin) SetApp(_ plugintypes.App) { - // Ignore +func GetPlugin() (plugintypes.SetConfig, plugintypes.SetApp, plugintypes.UI) { + p := &plugin{} + return p, p, p +} + +func (p *plugin) SetApp(app plugintypes.App) { + p.app = app } func (p *plugin) SetConfig(config map[string]any) { - p.config = config -} - -func (p *plugin) Render(hb *htmlbuilder.HtmlBuilder, t plugintypes.RenderType, data plugintypes.RenderData, render plugintypes.RenderNextFunc) { - switch t { - case plugintypes.PostMainElementRenderType: - render(hb) - pd, ok := data.(plugintypes.PostRenderData) - if !ok { - fmt.Println("syndication plugin: data is not PostRenderData!") - return + p.parameterName = "syndication" // default + if configParameterAny, ok := config["parameter"]; ok { + if configParameter, ok := configParameterAny.(string); ok { + p.parameterName = configParameter // override default from config } - parameterName := "syndication" // default - if configParameterAny, ok := p.config["parameter"]; ok { - if configParameter, ok := configParameterAny.(string); ok { - parameterName = configParameter // override default from config - } - } - syndicationLinks, ok := pd.GetPost().GetParameters()[parameterName] - if !ok || len(syndicationLinks) == 0 { - // No syndication links - return - } - for _, link := range syndicationLinks { - hb.WriteElementOpen("data", "value", link, "class", "u-syndication hide") - hb.WriteElementClose("data") - } - return - default: - render(hb) } } + +func (p *plugin) Render(rc plugintypes.RenderContext, rendered io.Reader, modified io.Writer) { + def := func() { + _, _ = io.Copy(modified, rendered) + } + post, err := p.app.GetPost(rc.GetPath()) + if err != nil || post == nil { + def() + return + } + syndicationLinks, ok := post.GetParameters()[p.parameterName] + if !ok || len(syndicationLinks) == 0 { + def() + return + } + doc, err := goquery.NewDocumentFromReader(rendered) + if err != nil { + fmt.Println("syndication plugin: " + err.Error()) + def() + return + } + buf := bufferpool.Get() + defer bufferpool.Put(buf) + hb := htmlbuilder.NewHtmlBuilder(buf) + for _, link := range syndicationLinks { + hb.WriteElementOpen("data", "value", link, "class", "u-syndication hide") + hb.WriteElementClose("data") + } + doc.Find("main.h-entry article").AppendHtml(buf.String()) + _ = goquery.Render(modified, doc.Selection) +} diff --git a/plugins/webrings/src/webrings/webrings.go b/plugins/webrings/src/webrings/webrings.go index d64ccaa..719c8ee 100644 --- a/plugins/webrings/src/webrings/webrings.go +++ b/plugins/webrings/src/webrings/webrings.go @@ -2,82 +2,79 @@ package webrings import ( "fmt" + "io" + "github.com/PuerkitoBio/goquery" + "go.goblog.app/app/pkgs/bufferpool" "go.goblog.app/app/pkgs/htmlbuilder" "go.goblog.app/app/pkgs/plugintypes" ) -func GetPlugin() plugintypes.UI { - return &plugin{} +func GetPlugin() (plugintypes.SetConfig, plugintypes.UI) { + p := &plugin{} + return p, p } type plugin struct { config map[string]any } -func (*plugin) SetApp(_ plugintypes.App) { - // Ignore -} - func (p *plugin) SetConfig(config map[string]any) { p.config = config } -func (p *plugin) Render(hb *htmlbuilder.HtmlBuilder, t plugintypes.RenderType, data plugintypes.RenderData, render plugintypes.RenderNextFunc) { - render(hb) - if t == plugintypes.BlogFooterRenderType { - bd, ok := data.(plugintypes.BlogRenderData) - if !ok { - fmt.Println("webrings plugin: data is not BlogRenderData!") - return - } - blogData := bd.GetBlog() - if blogData == nil { - fmt.Println("webrings plugin: blog is nil!") - return - } - blog := blogData.GetBlog() - if blog == "" { - fmt.Println("webrings plugin: blog is empty!") - return - } - if blogWebringsAny, ok := p.config[blog]; ok { - if blogWebrings, ok := blogWebringsAny.([]any); ok { - for _, webringAny := range blogWebrings { - if webring, ok := webringAny.(map[string]any); ok { - title, titleOk := unwrapToString(webring["title"]) - link, linkOk := unwrapToString(webring["link"]) - prev, prevOk := unwrapToString(webring["prev"]) - next, nextOk := unwrapToString(webring["next"]) - if titleOk && (linkOk || prevOk || nextOk) { - hb.WriteElementOpen("p") - if prevOk { - hb.WriteElementOpen("a", "href", prev) - hb.WriteEscaped("←") - hb.WriteElementClose("a") - hb.WriteEscaped(" ") - } - if linkOk { - hb.WriteElementOpen("a", "href", link) - } - hb.WriteEscaped(title) - if linkOk { - hb.WriteElementClose("a") - } - if nextOk { - hb.WriteEscaped(" ") - hb.WriteElementOpen("a", "href", next) - hb.WriteEscaped("→") - hb.WriteElementClose("a") - } - hb.WriteElementClose("p") +func (p *plugin) Render(rc plugintypes.RenderContext, rendered io.Reader, modified io.Writer) { + blog := rc.GetBlog() + if blog == "" { + fmt.Println("webrings plugin: blog is empty!") + return + } + doc, err := goquery.NewDocumentFromReader(rendered) + if err != nil { + fmt.Println("webrings plugin: " + err.Error()) + return + } + if blogWebringsAny, ok := p.config[blog]; ok { + if blogWebrings, ok := blogWebringsAny.([]any); ok { + buf := bufferpool.Get() + defer bufferpool.Put(buf) + hb := htmlbuilder.NewHtmlBuilder(buf) + for _, webringAny := range blogWebrings { + if webring, ok := webringAny.(map[string]any); ok { + title, titleOk := unwrapToString(webring["title"]) + link, linkOk := unwrapToString(webring["link"]) + prev, prevOk := unwrapToString(webring["prev"]) + next, nextOk := unwrapToString(webring["next"]) + if titleOk && (linkOk || prevOk || nextOk) { + buf.Reset() + hb.WriteElementOpen("p") + if prevOk { + hb.WriteElementOpen("a", "href", prev) + hb.WriteEscaped("←") + hb.WriteElementClose("a") + hb.WriteEscaped(" ") } + if linkOk { + hb.WriteElementOpen("a", "href", link) + } + hb.WriteEscaped(title) + if linkOk { + hb.WriteElementClose("a") + } + if nextOk { + hb.WriteEscaped(" ") + hb.WriteElementOpen("a", "href", next) + hb.WriteEscaped("→") + hb.WriteElementClose("a") + } + hb.WriteElementClose("p") + doc.Find("footer").AppendHtml(buf.String()) } } } } - } + _ = goquery.Render(modified, doc.Selection) } func unwrapToString(o any) (string, bool) { diff --git a/plugins_test.go b/plugins_test.go index f080813..01c8b88 100644 --- a/plugins_test.go +++ b/plugins_test.go @@ -11,35 +11,16 @@ import ( var _ plugintypes.App = &goBlog{} var _ plugintypes.Database = &database{} var _ plugintypes.Post = &post{} -var _ plugintypes.PostRenderData = &pluginPostRenderData{} +var _ plugintypes.RenderContext = &pluginRenderContext{} -func TestExecPlugin(t *testing.T) { +func TestDemoPlugin(t *testing.T) { app := &goBlog{ cfg: createDefaultTestConfig(t), } app.cfg.Plugins = []*configPlugin{ { - Path: "./plugins/demo", - Type: "exec", - Import: "demoexec", - }, - } - - err := app.initConfig(false) - require.NoError(t, err) - err = app.initPlugins() - require.NoError(t, err) -} - -func TestMiddlewarePlugin(t *testing.T) { - app := &goBlog{ - cfg: createDefaultTestConfig(t), - } - app.cfg.Plugins = []*configPlugin{ - { - Path: "./plugins/demo", - Type: "middleware", - Import: "demomiddleware", + Path: "embedded:demo", + Import: "demo", Config: map[string]any{ "prio": 99, }, @@ -51,10 +32,9 @@ func TestMiddlewarePlugin(t *testing.T) { err = app.initPlugins() require.NoError(t, err) - middlewarePlugins := getPluginsForType[plugintypes.Middleware](app, "middleware") + middlewarePlugins := app.getPlugins(pluginMiddlewareType) if assert.Len(t, middlewarePlugins, 1) { - mdw := middlewarePlugins[0] + mdw := middlewarePlugins[0].(plugintypes.Middleware) assert.Equal(t, 99, mdw.Prio()) } - } diff --git a/render.go b/render.go index 0a812b2..39d696a 100644 --- a/render.go +++ b/render.go @@ -4,8 +4,11 @@ import ( "io" "net/http" + "github.com/samber/lo" + "go.goblog.app/app/pkgs/bufferpool" "go.goblog.app/app/pkgs/contenttype" "go.goblog.app/app/pkgs/htmlbuilder" + "go.goblog.app/app/pkgs/plugintypes" ) type renderData struct { @@ -39,12 +42,26 @@ func (a *goBlog) renderWithStatusCode(w http.ResponseWriter, r *http.Request, st // Write status code w.WriteHeader(statusCode) // Render - pipeReader, pipeWriter := io.Pipe() - go func() { - f(htmlbuilder.NewHtmlBuilder(pipeWriter), data) - _ = pipeWriter.Close() - }() - _ = pipeReader.CloseWithError(a.min.Get().Minify(contenttype.HTML, w, pipeReader)) + buf := bufferpool.Get() + defer bufferpool.Put(buf) + f(htmlbuilder.NewHtmlBuilder(buf), data) + // Check if UI plugins are registered + uiPlugins := a.getPlugins(pluginUiType) + if len(uiPlugins) > 0 { + pluginBuf := bufferpool.Get() + defer bufferpool.Put(pluginBuf) + for _, plug := range lo.Reverse(uiPlugins) { + pluginBuf.Reset() + plug.(plugintypes.UI).Render(&pluginRenderContext{ + blog: data.BlogString, + path: r.URL.Path, + }, buf, pluginBuf) + buf.Reset() + _, _ = io.Copy(buf, pluginBuf) + } + } + // Return minified HTML + _ = a.min.Get().Minify(contenttype.HTML, w, buf) } func (a *goBlog) checkRenderData(r *http.Request, data *renderData) { @@ -89,3 +106,18 @@ func (a *goBlog) checkRenderData(r *http.Request, data *renderData) { data.Data = map[string]any{} } } + +// Plugins + +type pluginRenderContext struct { + blog string + path string +} + +func (d *pluginRenderContext) GetBlog() string { + return d.blog +} + +func (d *pluginRenderContext) GetPath() string { + return d.path +} diff --git a/ui.go b/ui.go index 9f2e2bd..d366f39 100644 --- a/ui.go +++ b/ui.go @@ -10,31 +10,8 @@ import ( "github.com/samber/lo" "go.goblog.app/app/pkgs/contenttype" "go.goblog.app/app/pkgs/htmlbuilder" - "go.goblog.app/app/pkgs/plugintypes" ) -func (a *goBlog) renderWithPlugins(hb *htmlbuilder.HtmlBuilder, t plugintypes.RenderType, d plugintypes.RenderData, r plugintypes.RenderNextFunc) { - plugins := getPluginsForType[plugintypes.UI](a, uiPlugin) - if len(plugins) == 0 { - r(hb) - return - } - // Reverse plugins, so that the first one in the configuration is executed first - plugins = lo.Reverse(plugins) - plugins[0].Render(hb, t, d, a.wrapUiPlugins(t, d, r, plugins[1:]...)) -} - -func (a *goBlog) wrapUiPlugins(t plugintypes.RenderType, d plugintypes.RenderData, r plugintypes.RenderNextFunc, plugins ...plugintypes.UI) plugintypes.RenderNextFunc { - if len(plugins) == 0 { - // Last element in the chain - return r - } - return func(newHb *htmlbuilder.HtmlBuilder) { - // Wrap the next plugin - plugins[0].Render(newHb, t, d, a.wrapUiPlugins(t, d, r, plugins[1:]...)) - } -} - func (a *goBlog) renderEditorPreview(hb *htmlbuilder.HtmlBuilder, bc *configBlog, p *post) { a.renderPostTitle(hb, p) a.renderPostMeta(hb, p, bc, "preview") @@ -164,34 +141,32 @@ func (a *goBlog) renderBase(hb *htmlbuilder.HtmlBuilder, rd *renderData, title, } // Footer hb.WriteElementOpen("footer") - a.renderWithPlugins(hb, plugintypes.BlogFooterRenderType, rd.Blog.pluginRenderData(), func(hb *htmlbuilder.HtmlBuilder) { - // Footer menu - if fm, ok := rd.Blog.Menus["footer"]; ok { - hb.WriteElementOpen("nav") - for i, item := range fm.Items { - if i > 0 { - hb.WriteUnescaped(" • ") - } - hb.WriteElementOpen("a", "href", item.Link) - hb.WriteEscaped(a.renderMdTitle(item.Title)) - hb.WriteElementClose("a") + // Footer menu + if fm, ok := rd.Blog.Menus["footer"]; ok { + hb.WriteElementOpen("nav") + for i, item := range fm.Items { + if i > 0 { + hb.WriteUnescaped(" • ") } - hb.WriteElementClose("nav") + hb.WriteElementOpen("a", "href", item.Link) + hb.WriteEscaped(a.renderMdTitle(item.Title)) + hb.WriteElementClose("a") } - // Copyright - hb.WriteElementOpen("p", "translate", "no") - hb.WriteUnescaped("© ") - hb.WriteEscaped(time.Now().Format("2006")) - hb.WriteUnescaped(" ") - if user != nil && user.Name != "" { - hb.WriteEscaped(user.Name) - } else { - hb.WriteEscaped(renderedBlogTitle) - } - hb.WriteElementClose("p") - // Tor - a.renderTorNotice(hb, rd) - }) + hb.WriteElementClose("nav") + } + // Copyright + hb.WriteElementOpen("p", "translate", "no") + hb.WriteUnescaped("© ") + hb.WriteEscaped(time.Now().Format("2006")) + hb.WriteUnescaped(" ") + if user != nil && user.Name != "" { + hb.WriteEscaped(user.Name) + } else { + hb.WriteEscaped(renderedBlogTitle) + } + hb.WriteElementClose("p") + // Tor + a.renderTorNotice(hb, rd) hb.WriteElementClose("footer") // Easter egg if rd.EasterEgg { @@ -900,52 +875,50 @@ func (a *goBlog) renderPost(hb *htmlbuilder.HtmlBuilder, rd *renderData) { }, func(hb *htmlbuilder.HtmlBuilder) { hb.WriteElementOpen("main", "class", "h-entry") - a.renderWithPlugins(hb, plugintypes.PostMainElementRenderType, p.pluginRenderData(), func(hb *htmlbuilder.HtmlBuilder) { - // URL (hidden just for microformats) - hb.WriteElementOpen("data", "value", a.getFullAddress(p.Path), "class", "u-url hide") - hb.WriteElementClose("data") - // Start article - hb.WriteElementOpen("article") - // Title - a.renderPostTitle(hb, p) - // Post meta - a.renderPostMeta(hb, p, rd.Blog, "post") - // Post actions - hb.WriteElementOpen("div", "class", "actions") - // Share button - a.renderShareButton(hb, p, rd.Blog) - // Translate button - a.renderTranslateButton(hb, p, rd.Blog) - // Speak button - hb.WriteElementOpen("button", "id", "speakBtn", "class", "hide", "data-speak", a.ts.GetTemplateStringVariant(rd.Blog.Lang, "speak"), "data-stopspeak", a.ts.GetTemplateStringVariant(rd.Blog.Lang, "stopspeak")) - hb.WriteElementClose("button") - hb.WriteElementOpen("script", "defer", "", "src", lo.If(p.TTS() != "", a.assetFileName("js/tts.js")).Else(a.assetFileName("js/speak.js"))) - hb.WriteElementClose("script") - // Close post actions + // URL (hidden just for microformats) + hb.WriteElementOpen("data", "value", a.getFullAddress(p.Path), "class", "u-url hide") + hb.WriteElementClose("data") + // Start article + hb.WriteElementOpen("article") + // Title + a.renderPostTitle(hb, p) + // Post meta + a.renderPostMeta(hb, p, rd.Blog, "post") + // Post actions + hb.WriteElementOpen("div", "class", "actions") + // Share button + a.renderShareButton(hb, p, rd.Blog) + // Translate button + a.renderTranslateButton(hb, p, rd.Blog) + // Speak button + hb.WriteElementOpen("button", "id", "speakBtn", "class", "hide", "data-speak", a.ts.GetTemplateStringVariant(rd.Blog.Lang, "speak"), "data-stopspeak", a.ts.GetTemplateStringVariant(rd.Blog.Lang, "stopspeak")) + hb.WriteElementClose("button") + hb.WriteElementOpen("script", "defer", "", "src", lo.If(p.TTS() != "", a.assetFileName("js/tts.js")).Else(a.assetFileName("js/speak.js"))) + hb.WriteElementClose("script") + // Close post actions + hb.WriteElementClose("div") + // TTS + if tts := p.TTS(); tts != "" { + hb.WriteElementOpen("div", "class", "p hide", "id", "tts") + hb.WriteElementOpen("audio", "controls", "", "preload", "none", "id", "tts-audio") + hb.WriteElementOpen("source", "src", tts) + hb.WriteElementClose("source") + hb.WriteElementClose("audio") hb.WriteElementClose("div") - // TTS - if tts := p.TTS(); tts != "" { - hb.WriteElementOpen("div", "class", "p hide", "id", "tts") - hb.WriteElementOpen("audio", "controls", "", "preload", "none", "id", "tts-audio") - hb.WriteElementOpen("source", "src", tts) - hb.WriteElementClose("source") - hb.WriteElementClose("audio") - hb.WriteElementClose("div") - } - // Old content warning - a.renderOldContentWarning(hb, p, rd.Blog) - // Content - a.postHtmlToWriter(hb, &postHtmlOptions{p: p}) - // External Videp - a.renderPostVideo(hb, p) - // GPS Track - a.renderPostGPX(hb, p, rd.Blog) - // Taxonomies - a.renderPostTax(hb, p, rd.Blog) - hb.WriteElementClose("article") - // Author - a.renderAuthor(hb) - }) + } + // Old content warning + a.renderOldContentWarning(hb, p, rd.Blog) + // Content + a.postHtmlToWriter(hb, &postHtmlOptions{p: p}) + // External Videp + a.renderPostVideo(hb, p) + // GPS Track + a.renderPostGPX(hb, p, rd.Blog) + // Taxonomies + a.renderPostTax(hb, p, rd.Blog) + hb.WriteElementClose("article") + // Author + a.renderAuthor(hb) hb.WriteElementClose("main") // Reactions a.renderPostReactions(hb, p)