From 251f975dc72a17fa03f5b094fbf3a9af6c1f5f6a Mon Sep 17 00:00:00 2001 From: "diamondburned (Forefront)" Date: Fri, 22 May 2020 16:43:28 -0700 Subject: [PATCH] Added a plugin repository, stabilized --- go.mod | 2 ++ go.sum | 2 ++ services/plugins/plugins.go | 63 +++++++++++++++++++++++++++++++++++++ services/services.go | 55 ++++++++++++++++++++++++++++++++ 4 files changed, 122 insertions(+) create mode 100644 go.sum create mode 100644 services/plugins/plugins.go create mode 100644 services/services.go diff --git a/go.mod b/go.mod index f0c3e85..47d3717 100644 --- a/go.mod +++ b/go.mod @@ -1,3 +1,5 @@ module github.com/diamondburned/cchat go 1.14 + +require github.com/pkg/errors v0.9.1 diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..7c401c3 --- /dev/null +++ b/go.sum @@ -0,0 +1,2 @@ +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= diff --git a/services/plugins/plugins.go b/services/plugins/plugins.go new file mode 100644 index 0000000..5437706 --- /dev/null +++ b/services/plugins/plugins.go @@ -0,0 +1,63 @@ +// Package plugins provides a source for cchat services as Go plugins. This +// package looks in UserConfigDir()/cchat/plugins/ by default. +package plugins + +import ( + "io/ioutil" + "os" + "path/filepath" + "plugin" + + "github.com/diamondburned/cchat/services" + "github.com/pkg/errors" +) + +var pluginPath string + +// SetPluginPath sets the plugin path before loading plugins. This only works +// until LoadPlugins is called. +func SetPluginPath(path string) { + pluginPath = path +} + +// TryConfigPath returns a path to $cfgDir/suffix. cfgDir is from +// os.UserConfigDir. +func TryConfigPath(suffix ...string) (string, error) { + d, err := os.UserConfigDir() + if err != nil { + return "", err + } + return filepath.Join(append([]string{d}, suffix...)...), nil +} + +func init() { + services.RegisterSource(loadPlugins) +} + +func loadPlugins() (errs []error) { + if pluginPath == "" { + p, err := TryConfigPath("cchat", "plugins") + if err != nil { + errs = []error{errors.Wrap(err, "Failed to get config path")} + return + } + pluginPath = p + } + + d, err := ioutil.ReadDir(pluginPath) + if err != nil { + errs = []error{errors.Wrap(err, "Failed to read plugin path")} + return + } + + for _, f := range d { + // We only need the plugin to call its init() function. + _, err := plugin.Open(filepath.Join(pluginPath, f.Name())) + if err != nil { + errs = append(errs, errors.Wrap(err, "Failed to open plugin")) + continue + } + } + + return +} diff --git a/services/services.go b/services/services.go new file mode 100644 index 0000000..103001c --- /dev/null +++ b/services/services.go @@ -0,0 +1,55 @@ +// Package services provides a global repository of cchat services. It also +// supports additional sources. +// +// Registering services +// +// To register a service, it's best to call RegisterService() in the package's +// init(). This allows for dash imports: +// +// _ "git.sr.ht/~user/cchat-abc" +// +// Registering sources +// +// Sources are simply functions that manage other services. An example of this +// would be the plugins package. Note that only packages that can error out on +// load should do this. A package can call RegisterService() multiple times. +// +// For examples on using RegisterSource(), check the plugins package. +package services + +import ( + "sync" + + "github.com/diamondburned/cchat" +) + +var services []cchat.Service + +// RegisterService adds a service. +func RegisterService(service ...cchat.Service) { + services = append(services, service...) +} + +var sources []func() []error +var sourceErrs []error +var sourceOnce sync.Once + +// RegisterSource adds a service source. Services are expected to call +// RegisterService() on source(). +func RegisterSource(source func() []error) { + sources = append(sources, source) +} + +// Get returns all services. It will also fetch the plugins from all sources. +// Future calls will not fetch the plugins again. +func Get() ([]cchat.Service, []error) { + sourceOnce.Do(func() { + sourceErrs = []error{} // mark as non-nil + for _, src := range sources { + sourceErrs = append(sourceErrs, src()...) + } + }) + + // why are we here, just to suffer + return services, sourceErrs +}