commit 64e48a4094ce2644401245e84594779deeb16ba4 Author: 程广 Date: Wed Sep 17 08:46:39 2025 +0800 add code diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..35b8419 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2025 kingecg + +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. \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..cbaa53f --- /dev/null +++ b/README.md @@ -0,0 +1,185 @@ +# Gosh + +Gosh 是一个轻量级的 HTTP 服务器类库,允许用户通过注册 [Anko](https://github.com/mattn/anko) 脚本和 Go 函数来快速构建 RESTful API。 + +## 特性 + +- 简单易用的 HTTP 路由注册 +- 支持 GET、POST、PUT、DELETE、PATCH 等 HTTP 方法 +- 通过 Anko 脚本语言实现动态 API 处理 +- 内置请求和响应辅助工具 +- 支持文件系统、网络、进程和 Shell 命令操作 +- 跨平台支持(Linux、Windows) + +## 安装 + +```bash +go get git.kingecg.top/kingecg/gosh +``` + +## 快速开始 + +创建一个简单的 HTTP 服务器: + +```go +package main + +import ( + "fmt" + "net/http" + + "git.kingecg.top/kingecg/gosh" +) + +func main() { + // 创建服务器实例 + serverMux := gosh.NewSHMux() + + // 注册 Go 函数,可在脚本中调用 + serverMux.RegistFunction("hello", func(name string) string { + return fmt.Sprintf("Hello, %s", name) + }) + + // 注册 GET 路由,使用 Anko 脚本处理请求 + serverMux.GET("/hello", ` + name = Req.GetQuery("name") + Res.WriteStr(hello(name)) + `) + + // 启动服务器 + http.ListenAndServe(":8089", serverMux) +} +``` + +## API 参考 + +### 服务器创建 + +```go +// 创建新的服务器实例 +serverMux := gosh.NewSHMux() +``` + +### 路由注册 + +```go +// 注册 HTTP 方法路由 +serverMux.GET(pattern, script) +serverMux.POST(pattern, script) +serverMux.PUT(pattern, script) +serverMux.DELETE(pattern, script) +serverMux.PATCH(pattern, script) +``` + +### 函数注册 + +```go +// 注册 Go 函数,可在脚本中调用 +serverMux.RegistFunction(name, function) +``` + +### 请求处理 + +在 Anko 脚本中,可以使用以下对象和方法: + +- `Req` - 请求对象 + - `Req.GetQuery(key)` - 获取查询参数 + - `Req.GetBodyStr()` - 获取请求体字符串 + - `Req.GetHeader(key)` - 获取请求头 + - `Req.GetBody()` - 获取解析后的请求体(JSON 或表单) + +- `Res` - 响应对象 + - `Res.SetHeader(key, value)` - 设置响应头 + - `Res.WriteStr(str)` - 写入字符串响应 + - `Res.WriteJSON(value)` - 写入 JSON 响应 + - `Res.Status(code)` - 设置状态码 + - `Res.StatusOk()` - 设置 200 状态码 + - `Res.StatusBadRequest()` - 设置 400 状态码 + - `Res.StatusNotFound()` - 设置 404 状态码 + - `Res.StatusInternalServerError()` - 设置 500 状态码 + - `Res.StatusForbidden()` - 设置 403 状态码 + +### 内置模块 + +在 Anko 脚本中,可以使用 `Require` 函数导入以下模块: + +- `fs` - 文件系统模块 + - 提供文件读写操作 + +- `net` - 网络模块 + - 提供网络相关操作,如端口检测 + +- `process` - 进程模块 + - 提供进程信息和环境变量访问 + - 获取和设置环境变量 + - 获取进程 ID 和主机名等信息 + +- `shell` - Shell 命令模块 + - 提供执行 Shell 命令的功能 + - 支持 Linux bash、Windows cmd 和 PowerShell + - 命令输出解析功能 + +## 示例 + +### 处理 JSON 请求 + +```go +serverMux.POST("/api/user", ` + body = Req.GetBody() + name = body["name"] + age = body["age"] + + response = { + "message": "User created", + "user": { + "name": name, + "age": age + } + } + + Res.WriteJSON(response) +`) +``` + +### 使用文件系统模块 + +```go +serverMux.GET("/api/file", ` + fs = Require("fs") + content = fs.ReadFile("/path/to/file.txt", "utf8") + Res.WriteStr(content) +`) +``` + +### 使用进程模块 + +```go +serverMux.GET("/api/env", ` + process = Require("process") + env = process.GetAllEnv() + Res.WriteJSON(env) +`) +``` + +### 使用 Shell 模块 + +```go +serverMux.GET("/api/ls", ` + shell = Require("shell") + result, err = shell.Exec("ls -la") + if err == nil { + Res.WriteStr(result.Stdout) + } else { + Res.StatusInternalServerError() + Res.WriteStr("Error executing command") + } +`) +``` + +## 贡献 + +欢迎提交问题和拉取请求! + +## 许可证 + +本项目采用 MIT 许可证 - 详见 [LICENSE](LICENSE) 文件。 \ No newline at end of file diff --git a/example/main.go b/example/main.go new file mode 100644 index 0000000..e552ef2 --- /dev/null +++ b/example/main.go @@ -0,0 +1,21 @@ +package main + +import ( + "fmt" + "net/http" + + "git.kingecg.top/kingecg/gosh" +) + +func main() { + + serverMux := gosh.NewSHMux() + serverMux.RegistFunction("hello", func(name string) string { + return fmt.Sprintf("Hello, %s", name) + }) + serverMux.GET("/hello", ` + name = Req.GetQuery("name") + Res.WriteStr(hello(name)) + `) + http.ListenAndServe(":8089", serverMux) +} diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..9ecacb9 --- /dev/null +++ b/go.mod @@ -0,0 +1,5 @@ +module git.kingecg.top/kingecg/gosh + +go 1.23.1 + +require github.com/mattn/anko v0.1.10 // indirect diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..75df7a4 --- /dev/null +++ b/go.sum @@ -0,0 +1,2 @@ +github.com/mattn/anko v0.1.10 h1:3QcIxCLirIxOZhIVtvo9eWz8tym/iZ9Nb29VCnzaMvc= +github.com/mattn/anko v0.1.10/go.mod h1:gjrudvzf1t7FWTZo1Nbywnr75g3uDnGjXdp2nkguBjQ= diff --git a/gosh.go b/gosh.go new file mode 100644 index 0000000..98e317a --- /dev/null +++ b/gosh.go @@ -0,0 +1,123 @@ +package gosh + +import ( + "net/http" + + "github.com/mattn/anko/env" + "github.com/mattn/anko/vm" +) + +const ( + GET = "GET" + POST = "POST" + PUT = "PUT" + DEL = "DELETE" + PATCH = "PATCH" +) + +type SHMux struct { + *http.ServeMux + ankoEnv *env.Env + getScripts map[string]string + postScripts map[string]string + putScripts map[string]string + delScripts map[string]string + patchScripts map[string]string +} + +func NewSHMux() *SHMux { + return &SHMux{ + ServeMux: http.NewServeMux(), + ankoEnv: env.NewEnv(), + } +} + +func (s *SHMux) RegistFunction(name string, function interface{}) { + if s.ankoEnv != nil { + s.ankoEnv.Define(name, function) + } +} + +func (s *SHMux) register(method, pattern, script string) { + switch method { + case GET: + if s.getScripts == nil { + s.getScripts = make(map[string]string) + } + s.getScripts[pattern] = script + case POST: + if s.postScripts == nil { + s.postScripts = make(map[string]string) + } + s.postScripts[pattern] = script + case PUT: + if s.putScripts == nil { + s.putScripts = make(map[string]string) + } + s.putScripts[pattern] = script + case DEL: + if s.delScripts == nil { + s.delScripts = make(map[string]string) + } + s.delScripts[pattern] = script + case PATCH: + if s.patchScripts == nil { + s.patchScripts = make(map[string]string) + } + s.patchScripts[pattern] = script + } +} +func (s *SHMux) GET(pattern, script string) { + s.register(GET, pattern, script) +} +func (s *SHMux) POST(pattern, script string) { + s.register(POST, pattern, script) +} +func (s *SHMux) PUT(pattern, script string) { + s.register(PUT, pattern, script) +} +func (s *SHMux) DELETE(pattern, script string) { + s.register(DEL, pattern, script) +} +func (s *SHMux) PATCH(pattern, script string) { + s.register(PATCH, pattern, script) +} + +func (s *SHMux) ServeHTTP(w http.ResponseWriter, r *http.Request) { + var script string + switch r.Method { + case GET: + script = s.getScripts[r.URL.Path] + case POST: + script = s.postScripts[r.URL.Path] + case PUT: + script = s.putScripts[r.URL.Path] + case DEL: + script = s.delScripts[r.URL.Path] + case PATCH: + script = s.patchScripts[r.URL.Path] + } + s.handle(w, r, script) +} + +func (s *SHMux) handle(w http.ResponseWriter, r *http.Request, script string) { + if script == "" { + http.NotFound(w, r) + return + } + reqHelper := &RequestHelper{ + r: r, + } + resHelper := &ResponseHelper{ + w: w, + } + env := s.ankoEnv.Copy() + env.Define("Req", reqHelper) + env.Define("Res", resHelper) + _, err := vm.Execute(env, nil, script) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + // w.Write([]byte(script)) +} diff --git a/helper.go b/helper.go new file mode 100644 index 0000000..4b0df7f --- /dev/null +++ b/helper.go @@ -0,0 +1,121 @@ +package gosh + +import ( + "encoding/json" + "io/ioutil" + "net/http" +) + +type RequestHelper struct { + r *http.Request +} + +func (r *RequestHelper) GetQuery(key string) string { + return r.r.URL.Query().Get(key) +} + +func (r *RequestHelper) GetBodyStr() string { + body, err := ioutil.ReadAll(r.r.Body) + if err != nil { + return "" + } + return string(body) +} + +func (r *RequestHelper) GetHeader(key string) string { + return r.r.Header.Get(key) +} + +func (r *RequestHelper) getJSONBody() (map[string]interface{}, error) { + body, err := ioutil.ReadAll(r.r.Body) + if err != nil { + return nil, err + } + m := make(map[string]interface{}) + err = json.Unmarshal(body, &m) + if err != nil { + return nil, err + } + return m, nil +} + +func (r *RequestHelper) getFormBody() (map[string]interface{}, error) { + err := r.r.ParseForm() + if err != nil { + return nil, err + } + m := make(map[string]interface{}) + for key, values := range r.r.Form { + if len(values) == 1 { + m[key] = values[0] + } else { + m[key] = values + } + } + return m, nil +} + +func (r *RequestHelper) GetBody() map[string]interface{} { + contentType := r.r.Header.Get("Content-Type") + switch contentType { + case "application/json": + m, err := r.getJSONBody() + if err != nil { + return nil + } + return m + case "application/x-www-form-urlencoded": + m, err := r.getFormBody() + if err != nil { + return nil + } + return m + } + return nil +} + +type ResponseHelper struct { + w http.ResponseWriter +} + +func (r *ResponseHelper) SetHeader(key, value string) { + r.w.Header().Set(key, value) +} + +func (r *ResponseHelper) WriteStr(s string) { + r.w.Write([]byte(s)) +} + +func (r *ResponseHelper) WriteJSON(v interface{}) error { + r.w.Header().Set("Content-Type", "application/json") + data, err := json.Marshal(v) + if err != nil { + return err + } + _, err = r.w.Write(data) + return err +} + +func (r *ResponseHelper) Status(code int) { + r.w.WriteHeader(code) +} + +func (r *ResponseHelper) StatusOk() { + r.w.WriteHeader(http.StatusOK) +} + +func (r *ResponseHelper) StatusBadRequest() { + r.w.WriteHeader(http.StatusBadRequest) +} + +func (r *ResponseHelper) StatusNotFound() { + r.w.WriteHeader(http.StatusNotFound) +} + +func (r *ResponseHelper) StatusInternalServerError() { + r.w.WriteHeader(http.StatusInternalServerError) +} + +func (r *ResponseHelper) StatusForbidden() { + r.w.WriteHeader(http.StatusForbidden) +} diff --git a/interceptor/filesystem.go b/interceptor/filesystem.go new file mode 100644 index 0000000..590acc9 --- /dev/null +++ b/interceptor/filesystem.go @@ -0,0 +1,102 @@ +package interceptor + +import ( + "fmt" + "os" + "path/filepath" + "strings" +) + +type FileModule struct{} + +func (f *FileModule) ReadFile(path string, encode string) interface{} { + fileContent, err := os.ReadFile(path) + if err != nil { + panic(err) + } + if encode == "binary" { + return fileContent + } + return string(fileContent) +} + +func (f *FileModule) WriteFile(path string, content interface{}, encode string) { + fileContent, err := os.ReadFile(path) + if err != nil { + panic(err) + } + if encode == "binary" { + os.WriteFile(path, fileContent, 0644) + } + os.WriteFile(path, []byte(content.(string)), 0644) +} + +func (f *FileModule) RemoveFile(path string) { + os.Remove(path) +} + +func (f *FileModule) Exists(path string) bool { + _, err := os.Stat(path) + if err != nil { + return os.IsExist(err) + } + return true +} + +func (f *FileModule) IsDir(path string) bool { + fileInfo, err := os.Stat(path) + if err != nil { + return false + } + return fileInfo.IsDir() +} + +func (f *FileModule) MakeDir(path string, rescursive bool) { + os.MkdirAll(path, 0755) +} + +func (f *FileModule) RemoveDir(path string) { + os.RemoveAll(path) +} + +func (f *FileModule) ListDir(path string) []string { + files, err := os.ReadDir(path) + if err != nil { + return nil + } + fileNames := make([]string, 0, len(files)) + for _, file := range files { + fileNames = append(fileNames, file.Name()) + } + return fileNames +} + +func (f *FileModule) Copy(src, dst string) { + if !f.IsDir(dst) { + if f.IsDir(filepath.Dir(dst)) { + bcontent := f.ReadFile(src, "binary") + f.WriteFile(dst, bcontent, "binary") + return + } else { + panic(fmt.Errorf("destination directory not exists")) + } + + } + if f.IsDir(src) { + if strings.HasSuffix(dst, "/") { + dst = dst + filepath.Base(src) + f.MakeDir(dst, true) + } + os.CopyFS(dst, os.DirFS(src)) + } + // 文件拷贝, 需要先读取文件内容, 然后写入到目标文件中 + bcontent := f.ReadFile(src, "binary") + f.WriteFile(dst, bcontent, "binary") + // 拷贝文件属性 + fileInfo, err := os.Stat(src) + if err != nil { + panic(err) + } + os.Chmod(dst, fileInfo.Mode()) + os.Chtimes(dst, fileInfo.ModTime(), fileInfo.ModTime()) +} diff --git a/interceptor/interceptor.go b/interceptor/interceptor.go new file mode 100644 index 0000000..403d090 --- /dev/null +++ b/interceptor/interceptor.go @@ -0,0 +1,66 @@ +package interceptor + +import ( + "fmt" + "os" + "path/filepath" + + "github.com/mattn/anko/env" + "github.com/mattn/anko/vm" +) + +type Ankointerceptor struct { + importMap map[string]interface{} + ankoEnv *env.Env +} + +func NewAnkointerceptor(ankoEnv *env.Env) *Ankointerceptor { + + a := &Ankointerceptor{ + importMap: make(map[string]interface{}), + ankoEnv: ankoEnv, + } + a.ankoEnv.Define("println", fmt.Println) + a.importMap["fs"] = &FileModule{} + a.importMap["net"] = &NetModule{} + a.importMap["process"] = &ProcessModule{} + a.importMap["shell"] = &ShellModule{} + return a +} + +func (a *Ankointerceptor) Exec(script string) (interface{}, error) { + e := a.ankoEnv.Copy() + e.Define("Require", a.genRequireMethod(script)) + e.Define("__filename", script) + e.Define("__dirname", filepath.Dir(script)) + scriptbytes, frr := os.ReadFile(script) + if frr != nil { + return nil, frr + } + scriptcode := string(scriptbytes) + result, err := vm.Execute(e, nil, scriptcode) + if err != nil { + return nil, err + } + return result, nil +} + +func (a *Ankointerceptor) genRequireMethod(basePath string) interface{} { + return func(s string) interface{} { + if r, ok := a.importMap[s]; ok { + return r + } + if !filepath.IsAbs(s) { + s = filepath.Join(filepath.Dir(basePath), s) + } + if _, ok := a.importMap[s]; ok { + return a.importMap[s] + } + result, err := a.Exec(s) + if err != nil { + panic(err) + } + a.importMap[s] = result + return result + } +} diff --git a/interceptor/interceptor_test.go b/interceptor/interceptor_test.go new file mode 100644 index 0000000..df19d8a --- /dev/null +++ b/interceptor/interceptor_test.go @@ -0,0 +1,24 @@ +package interceptor + +import ( + "path/filepath" + "runtime" + "testing" + + "github.com/mattn/anko/env" +) + +func TestAnkointerceptor_ExecRequire(t *testing.T) { + // 创建测试环境 + e := env.NewEnv() + + // 创建拦截器实例 + interceptor := NewAnkointerceptor(e) + _, file, _, _ := runtime.Caller(0) + scriptPath := filepath.Join(filepath.Dir(file), "testdata/test_caller.ank") + v, _ := interceptor.Exec(scriptPath) + if v != "Hello, tom from test script" { + t.Errorf("Not equal") + } + +} diff --git a/interceptor/net.go b/interceptor/net.go new file mode 100644 index 0000000..4525fde --- /dev/null +++ b/interceptor/net.go @@ -0,0 +1,17 @@ +package interceptor + +import ( + "fmt" + "net" +) + +type NetModule struct{} + +func (n *NetModule) IsPortOpen(host string, port int) bool { + conn, err := net.Dial("tcp", net.JoinHostPort(host, fmt.Sprintf("%d", port))) + if err != nil { + return false + } + conn.Close() + return true +} diff --git a/interceptor/process.go b/interceptor/process.go new file mode 100644 index 0000000..754cc86 --- /dev/null +++ b/interceptor/process.go @@ -0,0 +1,77 @@ +package interceptor + +import ( + "os" + "runtime" + "strings" +) + +// ProcessModule 提供对进程信息和环境变量的访问 +type ProcessModule struct{} + +// GetEnv 获取环境变量 +func (p *ProcessModule) GetEnv(key string) string { + return os.Getenv(key) +} + +// SetEnv 设置环境变量 +func (p *ProcessModule) SetEnv(key, value string) error { + return os.Setenv(key, value) +} + +// GetAllEnv 获取所有环境变量 +func (p *ProcessModule) GetAllEnv() map[string]string { + envMap := make(map[string]string) + for _, env := range os.Environ() { + pair := strings.SplitN(env, "=", 2) + if len(pair) == 2 { + envMap[pair[0]] = pair[1] + } + } + return envMap +} + +// GetPid 获取当前进程ID +func (p *ProcessModule) GetPid() int { + return os.Getpid() +} + +// GetPpid 获取父进程ID +func (p *ProcessModule) GetPpid() int { + return os.Getppid() +} + +// GetHostname 获取主机名 +func (p *ProcessModule) GetHostname() (string, error) { + return os.Hostname() +} + +// GetOS 获取操作系统类型 +func (p *ProcessModule) GetOS() string { + return runtime.GOOS +} + +// GetArch 获取系统架构 +func (p *ProcessModule) GetArch() string { + return runtime.GOARCH +} + +// GetCwd 获取当前工作目录 +func (p *ProcessModule) GetCwd() (string, error) { + return os.Getwd() +} + +// Chdir 改变当前工作目录 +func (p *ProcessModule) Chdir(dir string) error { + return os.Chdir(dir) +} + +// GetArgs 获取命令行参数 +func (p *ProcessModule) GetArgs() []string { + return os.Args +} + +// Exit 退出程序 +func (p *ProcessModule) Exit(code int) { + os.Exit(code) +} diff --git a/interceptor/shell.go b/interceptor/shell.go new file mode 100644 index 0000000..ccc668d --- /dev/null +++ b/interceptor/shell.go @@ -0,0 +1,171 @@ +package interceptor + +import ( + "bytes" + "errors" + "os/exec" + "runtime" + "strings" +) + +// ShellModule 提供执行shell命令的功能 +type ShellModule struct{} + +// CommandResult 表示命令执行结果 +type CommandResult struct { + Stdout string + Stderr string + ExitCode int +} + +// Exec 执行shell命令并返回结果 +func (s *ShellModule) Exec(command string) (*CommandResult, error) { + var cmd *exec.Cmd + var shell, flag string + + switch runtime.GOOS { + case "windows": + shell = "cmd" + flag = "/c" + default: // linux, darwin, etc. + shell = "bash" + flag = "-c" + } + + cmd = exec.Command(shell, flag, command) + + var stdout, stderr bytes.Buffer + cmd.Stdout = &stdout + cmd.Stderr = &stderr + + err := cmd.Run() + + result := &CommandResult{ + Stdout: stdout.String(), + Stderr: stderr.String(), + ExitCode: 0, + } + + if err != nil { + if exitError, ok := err.(*exec.ExitError); ok { + result.ExitCode = exitError.ExitCode() + } else { + return result, err + } + } + + return result, nil +} + +// ExecPowerShell 在Windows上执行PowerShell命令 +func (s *ShellModule) ExecPowerShell(command string) (*CommandResult, error) { + if runtime.GOOS != "windows" { + return nil, errors.New("PowerShell is only available on Windows") + } + + cmd := exec.Command("powershell", "-Command", command) + + var stdout, stderr bytes.Buffer + cmd.Stdout = &stdout + cmd.Stderr = &stderr + + err := cmd.Run() + + result := &CommandResult{ + Stdout: stdout.String(), + Stderr: stderr.String(), + ExitCode: 0, + } + + if err != nil { + if exitError, ok := err.(*exec.ExitError); ok { + result.ExitCode = exitError.ExitCode() + } else { + return result, err + } + } + + return result, nil +} + +// ExecBash 专门执行Bash命令(Linux/macOS) +func (s *ShellModule) ExecBash(command string) (*CommandResult, error) { + if runtime.GOOS == "windows" { + return nil, errors.New("Bash is not available on Windows by default") + } + + cmd := exec.Command("bash", "-c", command) + + var stdout, stderr bytes.Buffer + cmd.Stdout = &stdout + cmd.Stderr = &stderr + + err := cmd.Run() + + result := &CommandResult{ + Stdout: stdout.String(), + Stderr: stderr.String(), + ExitCode: 0, + } + + if err != nil { + if exitError, ok := err.(*exec.ExitError); ok { + result.ExitCode = exitError.ExitCode() + } else { + return result, err + } + } + + return result, nil +} + +// ExecCmd 专门执行Windows CMD命令 +func (s *ShellModule) ExecCmd(command string) (*CommandResult, error) { + if runtime.GOOS != "windows" { + return nil, errors.New("CMD is only available on Windows") + } + + cmd := exec.Command("cmd", "/c", command) + + var stdout, stderr bytes.Buffer + cmd.Stdout = &stdout + cmd.Stderr = &stderr + + err := cmd.Run() + + result := &CommandResult{ + Stdout: stdout.String(), + Stderr: stderr.String(), + ExitCode: 0, + } + + if err != nil { + if exitError, ok := err.(*exec.ExitError); ok { + result.ExitCode = exitError.ExitCode() + } else { + return result, err + } + } + + return result, nil +} + +// ParseOutput 解析命令输出为字符串数组(按行分割) +func (s *ShellModule) ParseOutput(output string) []string { + return strings.Split(strings.TrimSpace(output), "\n") +} + +// ParseKeyValue 解析键值对格式的输出(如 KEY=VALUE) +func (s *ShellModule) ParseKeyValue(output string) map[string]string { + result := make(map[string]string) + lines := s.ParseOutput(output) + + for _, line := range lines { + parts := strings.SplitN(line, "=", 2) + if len(parts) == 2 { + result[strings.TrimSpace(parts[0])] = strings.TrimSpace(parts[1]) + } + } + + return result +} \ No newline at end of file diff --git a/interceptor/testdata/test_caller.ank b/interceptor/testdata/test_caller.ank new file mode 100644 index 0000000..2271f3a --- /dev/null +++ b/interceptor/testdata/test_caller.ank @@ -0,0 +1,7 @@ +sp=Require("./test_script.ank") +fs=Require("fs") +if fs.Exists(__filename) { + println("fs module is ok") +} +ret=sp("tom") +ret \ No newline at end of file diff --git a/interceptor/testdata/test_script.ank b/interceptor/testdata/test_script.ank new file mode 100644 index 0000000..ab3f8dd --- /dev/null +++ b/interceptor/testdata/test_script.ank @@ -0,0 +1,7 @@ +// 这是一个测试脚本文件,用于测试 Require 功能 +message = "Hello from test script" +c = func(name) { + return "Hello, " + name + " from test script" +} +println(__filename) +c \ No newline at end of file diff --git a/require.md b/require.md new file mode 100644 index 0000000..0f796c4 --- /dev/null +++ b/require.md @@ -0,0 +1,3 @@ +# gosh + +创建一个http服务器类库。让用户可以通过注册anko脚本和函数来快速构建RESTful API。 \ No newline at end of file