mirror of https://github.com/jlelse/GoBlog
Rework plugins (Part 1)
This commit is contained in:
parent
bff6272350
commit
567eeb1116
2
LICENSE
2
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
|
||||
|
|
|
@ -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"`
|
||||
}
|
||||
|
|
15
go.mod
15
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
|
||||
|
|
30
go.sum
30
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=
|
||||
|
|
3
http.go
3
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()
|
||||
|
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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")
|
||||
)
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
|
@ -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),
|
||||
}
|
||||
}
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
98
plugins.go
98
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}
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
})
|
||||
}
|
|
@ -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()
|
||||
}
|
|
@ -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)
|
||||
})
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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())
|
||||
}
|
||||
|
||||
}
|
||||
|
|
44
render.go
44
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
|
||||
}
|
||||
|
|
161
ui.go
161
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)
|
||||
|
|
Loading…
Reference in New Issue