Skip to content

Commit

Permalink
feat: plugin support (#71)
Browse files Browse the repository at this point in the history
* example plugin

* plugins

* simplify

* simplify

* use `*cobra.Command` instead of a func
  • Loading branch information
Reecepbcups authored Feb 29, 2024
1 parent e5f27ca commit 556d8c1
Show file tree
Hide file tree
Showing 4 changed files with 174 additions and 0 deletions.
84 changes: 84 additions & 0 deletions cmd/spawn/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,19 @@ package main

import (
"fmt"
"io/fs"
"log"
"log/slog"
"os"
"path"
"plugin"
"strings"
"time"

"github.com/lmittmann/tint"
"github.com/mattn/go-isatty"
"github.com/spf13/cobra"
"gitub.com/strangelove-ventures/spawn/plugins"
)

// Set in the makefile ld_flags on compile
Expand All @@ -19,11 +23,14 @@ var SpawnVersion = ""
var LogLevelFlag = "log-level"

func main() {

rootCmd.AddCommand(newChain)
rootCmd.AddCommand(LocalICCmd)
rootCmd.AddCommand(versionCmd)
rootCmd.AddCommand(ModuleCmd())

applyPluginCmds()

rootCmd.PersistentFlags().String(LogLevelFlag, "info", "log level (debug, info, warn, error)")

if err := rootCmd.Execute(); err != nil {
Expand All @@ -50,6 +57,83 @@ func GetLogger() *slog.Logger {
return slog.Default()
}

func applyPluginCmds() {
plugins := &cobra.Command{
Use: "plugins",
Short: "Manage plugins",
Run: func(cmd *cobra.Command, args []string) {
if err := cmd.Help(); err != nil {
log.Fatal(err)
}
},
}

for _, plugin := range loadPlugins() {
plugins.AddCommand(plugin.Cmd())
}

rootCmd.AddCommand(plugins)
}

func loadPlugins() map[string]*plugins.SpawnPluginBase {
p := make(map[string]*plugins.SpawnPluginBase)

homeDir, err := os.UserHomeDir()
if err != nil {
panic(err)
}

pluginsDir := path.Join(homeDir, ".spawn", "plugins")

d := os.DirFS(pluginsDir)
if _, err := d.Open("."); err != nil {
if os.IsNotExist(err) {
if err := os.MkdirAll(pluginsDir, 0755); err != nil {
panic(err)
}
} else {
panic(err)
}
}

err = fs.WalkDir(d, ".", func(relPath string, d fs.DirEntry, e error) error {
if d.IsDir() {
return nil
}

if !strings.Contains(relPath, ".so") {
return nil
}

absPath := path.Join(pluginsDir, relPath)

// read the absolute path
plug, err := plugin.Open(absPath)
if err != nil {
return fmt.Errorf("error opening plugin %s: %w", absPath, err)
}

base, err := plug.Lookup("Plugin")
if err != nil {
return fmt.Errorf("error looking up symbol: %w", err)
}

pluginInstance, ok := base.(plugins.SpawnPlugin)
if !ok {
log.Fatal("Symbol 'Plugin' does not implement the SpawnPlugin interface")
}

p[relPath] = plugins.NewSpawnPluginBase(pluginInstance.Cmd())

return nil
})
if err != nil {
panic(err)
}

return p
}

var rootCmd = &cobra.Command{
Use: "spawn",
Short: "Entry into the Interchain",
Expand Down
4 changes: 4 additions & 0 deletions plugins/example/build.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
EXPORT_LOC=$HOME/.spawn/plugins
mkdir -p $EXPORT_LOC

go build -buildmode=plugin -o $EXPORT_LOC/example.so plugins/example/example-plugin.go
61 changes: 61 additions & 0 deletions plugins/example/example-plugin.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
package main

import (
"log"
"os"
"path"

"github.com/spf13/cobra"
plugins "gitub.com/strangelove-ventures/spawn/plugins"
)

// Make the plugin public
var Plugin SpawnMainExamplePlugin

var _ plugins.SpawnPlugin = &SpawnMainExamplePlugin{}

const (
cmdName = "example"
)

type SpawnMainExamplePlugin struct {
Impl plugins.SpawnPluginBase
}

// Cmd implements plugins.SpawnPlugin.
func (e *SpawnMainExamplePlugin) Cmd() *cobra.Command {
rootCmd := &cobra.Command{
Use: cmdName,
Short: cmdName + " plugin command",
Run: func(cmd *cobra.Command, args []string) {
if err := cmd.Help(); err != nil {
log.Fatal(err)
}
},
}

rootCmd.AddCommand(&cobra.Command{
Use: "touch-file [name]",
Short: "An example plugin sub command",
Args: cobra.ExactArgs(1),
Run: func(cmd *cobra.Command, args []string) {
cwd, err := os.Getwd()
if err != nil {
log.Fatal(err)
}

fileName := args[0]

filePath := path.Join(cwd, fileName)
file, err := os.Create(filePath)
if err != nil {
log.Fatal(err)
}
defer file.Close()

cmd.Printf("Created file: %s\n", filePath)
},
})

return rootCmd
}
25 changes: 25 additions & 0 deletions plugins/plugin.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package plugins

import "github.com/spf13/cobra"

// SpawnPlugin is the interface that we're exposing as a plugin.
type SpawnPlugin interface {
Cmd() *cobra.Command
}

var _ SpawnPlugin = &SpawnPluginBase{}

type SpawnPluginBase struct {
cmd *cobra.Command
}

func NewSpawnPluginBase(cmd *cobra.Command) *SpawnPluginBase {
return &SpawnPluginBase{
cmd: cmd,
}
}

// Cmd implements SpawnPlugin.
func (s *SpawnPluginBase) Cmd() *cobra.Command {
return s.cmd
}

0 comments on commit 556d8c1

Please sign in to comment.