golang简单API创建解释器及其他功能插件库sand的使用

Golang简单API创建解释器及其他功能插件库sand的使用

sand库简介

sand是一个用于创建解释器的Go语言库,类似于Python解释器和Haskell解释器。它也可以用于创建基于文本的游戏和CLI测试环境。

GoDoc Go Report Card Build Status Code Coverage

设计架构

sand实现了并发模型。它将解释器视为两个独立的组件:用户界面sand.UI和命令处理器sand.Engine。以下是sand的内部工作原理示意图,每个方块代表一个goroutine。

+--------+                            +--------------------------+
|        |              +------------->     Engines Manager      +--------------+
|  Read  <----------+   |             +--------------------------+              |
|        |          |   |                                                       |
+----+---+          |   |                                                       |
     |            +-+---+------+                                        +-------v------+
     |            |            |                                        |              |     +----------+
     +------------>     UI     |                                        |    Engine    |     |  Engine  |
                  |  (usually  +---------------------------------------->    Runner    +---->+   Exec   |
     +------------>    main)   |                                        |              |     |          |
     |            |            |      XXXXXXXXXXXXXXXXXXXXXXXXXXXX      |              |     +----------+
     |            +-+----------+      X   Manager connects UI    X      +--------------+
+----+---+          |                 X   to Engine Runner       X
|        |          |                 XXXXXXXXXXXXXXXXXXXXXXXXXXXX
| Write  <----------+
|        |
+--------+

核心组件

sand.UI

sand.UI是一个已经提供的结构体,实现尽可能广泛;但缺少一些流行解释器中常见的功能,如:行历史和自动完成。这些功能可能会在以后添加,但目前没有计划。

sand.Engine

sand.Engine是一个必须由用户实现的接口。sand.Engine的实现必须具有可比较的基础类型。

示例代码

下面是一个使用sand库创建简单解释器的完整示例:

package main

import (
	"fmt"
	"strings"

	"github.com/Zaba505/sand"
)

// 定义一个简单的Engine实现
type simpleEngine struct{}

// 实现Engine接口的Exec方法
func (e *simpleEngine) Exec(cmd string) (string, error) {
	// 处理简单的echo命令
	if strings.HasPrefix(cmd, "echo ") {
		return strings.TrimPrefix(cmd, "echo "), nil
	}
	
	// 处理help命令
	if cmd == "help" {
		return "Available commands: echo, help, exit", nil
	}
	
	// 处理exit命令
	if cmd == "exit" {
		return "", sand.ErrExit
	}
	
	// 未知命令
	return "", fmt.Errorf("unknown command: %s", cmd)
}

func main() {
	// 创建UI实例
	ui := sand.NewUI()
	
	// 创建Engine实例
	engine := &simpleEngine{}
	
	// 运行解释器
	err := ui.Run(engine)
	if err != nil {
		fmt.Printf("Error: %v\n", err)
	}
}

更复杂的示例

下面是一个支持多命令和状态的更复杂示例:

package main

import (
	"fmt"
	"strconv"
	"strings"

	"github.com/Zaba505/sand"
)

// 定义一个带状态的Engine
type statefulEngine struct {
	counter int
}

func (e *statefulEngine) Exec(cmd string) (string, error) {
	parts := strings.Fields(cmd)
	if len(parts) == 0 {
		return "", nil
	}

	switch parts[0] {
	case "inc":
		e.counter++
		return fmt.Sprintf("Counter incremented to %d", e.counter), nil
	case "dec":
		e.counter--
		return fmt.Sprintf("Counter decremented to %d", e.counter), nil
	case "set":
		if len(parts) < 2 {
			return "", fmt.Errorf("set requires a value")
		}
		val, err := strconv.Atoi(parts[1])
		if err != nil {
			return "", fmt.Errorf("invalid number: %v", err)
		}
		e.counter = val
		return fmt.Sprintf("Counter set to %d", e.counter), nil
	case "get":
		return fmt.Sprintf("Current counter value: %d", e.counter), nil
	case "help":
		return `Available commands:
  inc - increment counter
  dec - decrement counter
  set <value> - set counter value
  get - get current counter value
  help - show this help
  exit - exit the interpreter`, nil
	case "exit":
		return "", sand.ErrExit
	default:
		return "", fmt.Errorf("unknown command: %s", parts[0])
	}
}

func main() {
	ui := sand.NewUI()
	engine := &statefulEngine{counter: 0}
	
	// 设置自定义提示符
	ui.Prompt = "counter> "
	
	if err := ui.Run(engine); err != nil {
		fmt.Printf("Error: %v\n", err)
	}
}

总结

sand库提供了一个简单而强大的框架来创建解释器和命令行工具。通过实现sand.Engine接口和使用提供的sand.UI结构体,开发者可以快速构建交互式命令行应用程序。库的并发模型使其适合构建需要处理多个输入输出流的复杂应用程序。


更多关于golang简单API创建解释器及其他功能插件库sand的使用的实战教程也可以访问 https://www.itying.com/category-94-b0.html

1 回复

更多关于golang简单API创建解释器及其他功能插件库sand的使用的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


Golang API 创建解释器及使用 Sand 插件库

Sand 是一个 Go 语言的插件系统,允许你动态加载和执行 Go 插件。下面我将介绍如何创建一个简单的 API 解释器以及如何使用 Sand 插件库。

1. 创建简单 API 解释器

首先,我们创建一个基本的 HTTP API 解释器:

package main

import (
	"encoding/json"
	"fmt"
	"log"
	"net/http"
)

// Command 表示解释器接收的命令结构
type Command struct {
	Action string                 `json:"action"`
	Params map[string]interface{} `json:"params"`
}

// Interpreter 是我们的解释器结构
type Interpreter struct {
	handlers map[string]func(params map[string]interface{}) (interface{}, error)
}

// NewInterpreter 创建新的解释器实例
func NewInterpreter() *Interpreter {
	return &Interpreter{
		handlers: make(map[string]func(params map[string]interface{}) (interface{}, error)),
	}
}

// RegisterHandler 注册命令处理器
func (i *Interpreter) RegisterHandler(action string, handler func(params map[string]interface{}) (interface{}, error)) {
	i.handlers[action] = handler
}

// Execute 执行命令
func (i *Interpreter) Execute(cmd Command) (interface{}, error) {
	handler, exists := i.handlers[cmd.Action]
	if !exists {
		return nil, fmt.Errorf("unknown action: %s", cmd.Action)
	}
	return handler(cmd.Params)
}

// APIHandler 处理 HTTP 请求
func (i *Interpreter) APIHandler(w http.ResponseWriter, r *http.Request) {
	if r.Method != http.MethodPost {
		http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
		return
	}

	var cmd Command
	if err := json.NewDecoder(r.Body).Decode(&cmd); err != nil {
		http.Error(w, err.Error(), http.StatusBadRequest)
		return
	}

	result, err := i.Execute(cmd)
	if err != nil {
		http.Error(w, err.Error(), http.StatusBadRequest)
		return
	}

	w.Header().Set("Content-Type", "application/json")
	json.NewEncoder(w).Encode(result)
}

func main() {
	interpreter := NewInterpreter()

	// 注册一个示例处理器
	interpreter.RegisterHandler("echo", func(params map[string]interface{}) (interface{}, error) {
		return params["message"], nil
	})

	http.HandleFunc("/api", interpreter.APIHandler)
	log.Println("Server starting on :8080...")
	log.Fatal(http.ListenAndServe(":8080", nil))
}

2. 使用 Sand 插件库

Sand 是一个 Go 插件系统,允许动态加载和执行 Go 插件。以下是使用 Sand 的示例:

首先安装 Sand:

go get github.com/grafana/sand

然后创建一个插件系统:

package main

import (
	"fmt"
	"log"

	"github.com/grafana/sand"
)

// PluginInterface 定义插件必须实现的接口
type PluginInterface interface {
	Execute(params map[string]interface{}) (interface{}, error)
	Name() string
}

func main() {
	// 创建 Sand 管理器
	manager := sand.NewManager()

	// 加载插件
	plugin, err := manager.Load("./plugins/example.so")
	if err != nil {
		log.Fatalf("Failed to load plugin: %v", err)
	}

	// 获取插件实例
	instance, err := plugin.Instance()
	if err != nil {
		log.Fatalf("Failed to get plugin instance: %v", err)
	}

	// 类型断言确保插件实现了我们的接口
	if pluginImpl, ok := instance.(PluginInterface); ok {
		fmt.Printf("Loaded plugin: %s\n", pluginImpl.Name())
		
		// 执行插件功能
		result, err := pluginImpl.Execute(map[string]interface{}{
			"input": "test data",
		})
		if err != nil {
			log.Printf("Plugin execution error: %v", err)
		} else {
			fmt.Printf("Plugin result: %v\n", result)
		}
	} else {
		log.Fatal("Plugin does not implement the required interface")
	}
}

3. 创建示例插件

创建一个简单的插件:

// go build -buildmode=plugin -o plugins/example.so plugins/example.go
package main

import "fmt"

// ExamplePlugin 实现我们的插件接口
type ExamplePlugin struct{}

func (p *ExamplePlugin) Execute(params map[string]interface{}) (interface{}, error) {
	return fmt.Sprintf("Processed: %v", params["input"]), nil
}

func (p *ExamplePlugin) Name() string {
	return "ExamplePlugin"
}

// Export 的变量名必须为 Plugin
var Plugin ExamplePlugin

4. 结合 API 解释器和 Sand 插件

将两者结合,创建一个支持动态插件的 API 解释器:

func main() {
	interpreter := NewInterpreter()
	manager := sand.NewManager()

	// 加载所有插件
	plugins, err := filepath.Glob("./plugins/*.so")
	if err != nil {
		log.Fatal(err)
	}

	for _, pluginPath := range plugins {
		plugin, err := manager.Load(pluginPath)
		if err != nil {
			log.Printf("Failed to load plugin %s: %v", pluginPath, err)
			continue
		}

		instance, err := plugin.Instance()
		if err != nil {
			log.Printf("Failed to get plugin instance %s: %v", pluginPath, err)
			continue
		}

		if pluginImpl, ok := instance.(PluginInterface); ok {
			interpreter.RegisterHandler(pluginImpl.Name(), pluginImpl.Execute)
			log.Printf("Registered plugin: %s", pluginImpl.Name())
		}
	}

	http.HandleFunc("/api", interpreter.APIHandler)
	log.Println("Server starting on :8080...")
	log.Fatal(http.ListenAndServe(":8080", nil))
}

总结

  1. 我们创建了一个基本的 API 解释器,可以处理 JSON 格式的命令
  2. 使用 Sand 插件系统动态加载 Go 插件
  3. 展示了如何创建兼容的插件
  4. 将两者结合,实现了一个支持动态插件的 API 服务

这种方法允许你在不重启服务的情况下,通过添加新的 .so 插件文件来扩展系统功能。Sand 提供了比 Go 标准库 plugin 包更友好的接口和更好的错误处理。

回到顶部