From c6a2bcbb4c019147dd101e5925fe66b46a6e487a Mon Sep 17 00:00:00 2001 From: Star <> Date: Fri, 15 Mar 2024 22:32:19 +0800 Subject: [PATCH] first --- .gitignore | 4 + README.md | 28 +++- go.mod | 31 ++++ main.go | 295 +++++++++++++++++++++++++++++++++++ templates/_gitignore | 3 + templates/_gitignore_server | 7 + templates/_main.go | 20 +++ templates/_main.js | 7 + templates/_main_test.go | 14 ++ templates/_makePluginCode.go | 29 ++++ templates/_plugin.go | 47 ++++++ templates/_plugin_test.go | 27 ++++ templates/_plugin_test.js | 18 +++ 13 files changed, 529 insertions(+), 1 deletion(-) create mode 100644 .gitignore create mode 100644 go.mod create mode 100644 main.go create mode 100644 templates/_gitignore create mode 100644 templates/_gitignore_server create mode 100644 templates/_main.go create mode 100644 templates/_main.js create mode 100644 templates/_main_test.go create mode 100644 templates/_makePluginCode.go create mode 100644 templates/_plugin.go create mode 100644 templates/_plugin_test.go create mode 100644 templates/_plugin_test.js diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..03d57ed --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +.* +!.gitignore +/go.sum + diff --git a/README.md b/README.md index 8efedd6..b057c24 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,28 @@ -# gojs-cli +# ag - tools for apigo.cloud + +## Install + +```shell +go install github.com/apigo/ag +``` + +## Usage + +``` +tools for apigo.cloud + +Usage: + ag [command] [...] + ag [short command] [...] + +Commands: + init [i] create a new project with "go mod init"、"go mod tidy" etc. + initPlugin [ip] name create a new plugin project with "go mod init"、"go mod tidy" etc. + initServer [is] name create a new server project with "go mod init"、"go mod tidy" etc. + pluginsCode [pc] export typescript code for used plugins into "plugins/" + +Examples: + ag i init a new simple project with Hello World + ag ip ali init a new plugin project named ali for use aliyun services +``` diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..c221dd5 --- /dev/null +++ b/go.mod @@ -0,0 +1,31 @@ +module apigo.cloud/git/apigo/ag + +go 1.18 + +require github.com/ssgo/u v1.7.2 + +require ( + apigo.cloud/git/apigo/plugin v1.0.1 // indirect + github.com/go-ole/go-ole v1.2.6 // indirect + github.com/gomodule/redigo v1.8.8 // indirect + github.com/gorilla/websocket v1.4.2 // indirect + github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect + github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // indirect + github.com/shirou/gopsutil/v3 v3.22.10 // indirect + github.com/ssgo/config v1.7.2 // indirect + github.com/ssgo/discover v1.7.2 // indirect + github.com/ssgo/httpclient v1.7.2 // indirect + github.com/ssgo/log v1.7.2 // indirect + github.com/ssgo/redis v1.7.2 // indirect + github.com/ssgo/s v1.7.2 // indirect + github.com/ssgo/standard v1.7.2 // indirect + github.com/tklauser/go-sysconf v0.3.10 // indirect + github.com/tklauser/numcpus v0.4.0 // indirect + github.com/yusufpapurcu/wmi v1.2.2 // indirect + golang.org/x/net v0.0.0-20190320064053-1272bf9dcd53 // indirect + golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a // indirect + golang.org/x/text v0.3.0 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect +) + +replace apigo.cloud/git/apigo/gojs v0.0.3 => ../gojs diff --git a/main.go b/main.go new file mode 100644 index 0000000..ef863d1 --- /dev/null +++ b/main.go @@ -0,0 +1,295 @@ +package main + +import ( + _ "embed" + "fmt" + "github.com/ssgo/u" + "io" + "os" + "os/exec" + "path" + "regexp" + "strings" + "text/template" +) + +//go:embed templates/_makePluginCode.go +var makePluginCodeTPL string + +//go:embed templates/_main.go +var mainCodeTPL string + +//go:embed templates/_main_test.go +var mainTestCodeTPL string + +//go:embed templates/_main.js +var mainJSCodeTPL string + +//go:embed templates/_plugin.go +var pluginCodeTPL string + +//go:embed templates/_plugin_test.go +var pluginTestCodeTPL string + +//go:embed templates/_plugin_test.js +var pluginTestJSCodeTPL string + +//go:embed templates/_gitignore +var gitignoreTPL string + +//go:embed templates/_gitignore_server +var gitignoreServerTPL string + +type Command struct { + Name string + ShortName string + Args string + Comment string + Func func([]string) +} + +// TODO js 缓存 & 开发模式检测自动(gowatch 不再监听 js) +var commands = []Command{ + {"new", "+", "[name]", "create a new project, will create in the current directory if no name is specified", newProject}, + {"new plugin", "+p", "[name]", "create a new plugin project, will create in the current directory if no name is specified", newPluginProject}, + //{"new server", "+s", "[name]", "create a new server project, will create in the current directory if no name is specified", newServerProject}, + //{"new api", "+a", "[path] [method]", "create a new api for server project, will use restful api if specified http method", newServerAPI}, + {"run", "r", "[...more gowatch args]", "run project use gowatch, if project files changed will restart auto, gowatch args help see: https://github.com/ssgo/tool", runProject}, + {"test", "t", "[...more gowatch args]", "test project use gowatch, if project files changed will restart auto, gowatch args help see: https://github.com/ssgo/tool", testProject}, + {"export plugins", "ep", "", "export typescript code for used plugins into \"plugins/\"", makePluginCode}, + //{"git login", "/lg", "", "login to apigo.cloud/git", }, + //{"git new", "/+", "name", "create new public repository from apigo.cloud/git", }, + //{"git new pri", "/++", "name", "create new private repository from apigo.cloud/git", }, + //{"git clone", "/cl", "name", "clone repository to current dir from apigo.cloud/git", }, + //{"git list", "/l", "", "list current repository tags from apigo.cloud/git", }, + //{"git commit", "/c", "comment", "commit current repository to apigo.cloud/git", }, + //{"git tag", "/t", "tag", "create new tag for current repository and push to apigo.cloud/git", }, +} + +func findTool() (gowatchPath string, logvPath string) { + gowatchPath = "gowatch" + logvPath = "logv" + if binPath, err := exec.LookPath("gowatch"); err == nil && binPath != "" { + gowatchPath = binPath + } + if binPath, err := exec.LookPath("logv"); err == nil && binPath != "" { + logvPath = binPath + } + return gowatchPath, logvPath +} + +func checkSSGOTool() { + gowatchPath, logvPath := findTool() + if gowatchPath == "gowatch" || logvPath == "logv" { + _ = runCommand("go", "get", "-u", "github.com/ssgo/tool") + if gowatchPath == "gowatch" { + _ = runCommand("go", "install", "github.com/ssgo/tool/gowatch") + } + if logvPath == "logv" { + _ = runCommand("go", "install", "github.com/ssgo/tool/logv") + } + } +} + +func checkProjectPath(args []string) string { + if len(args) > 0 { + if err := os.Mkdir(args[0], 0755); err != nil { + fmt.Println(u.BRed("mkdir error: " + err.Error())) + return "" + } + if err := os.Chdir(args[0]); err != nil { + fmt.Println(u.BRed("chdir error: " + err.Error())) + return "" + } + return args[0] + } else { + if pathname, err := os.Getwd(); err != nil { + fmt.Println(u.BRed("getwd error: " + err.Error())) + return "" + } else { + return path.Base(pathname) + } + } +} + +func newProject(args []string) { + if name := checkProjectPath(args); name != "" { + _ = runCommand("go", "mod", "init", name) + _ = runCommand("go", "mod", "edit", "-go=1.18") + _ = runCommand("go", "get", "-u", "apigo.cloud/git/apigo/gojs") + _ = runCommand("go", "get", "-u", "apigo.cloud/git/apigo/plugins") + writeFile("main.go", mainCodeTPL, map[string]any{"name": name}) + writeFile("main_test.go", mainTestCodeTPL, map[string]any{"name": name}) + writeFile("main.js", mainJSCodeTPL, map[string]any{"name": name}) + writeFile(".gitignore", gitignoreTPL, map[string]any{"name": name}) + _ = runCommand("go", "mod", "tidy") + checkSSGOTool() + fmt.Println(u.BGreen("new project " + name + " created")) + } +} + +func newPluginProject(args []string) { + if name := checkProjectPath(args); name != "" { + _ = runCommand("go", "mod", "init", name) + _ = runCommand("go", "mod", "edit", "-go=1.18") + _ = runCommand("go", "get", "-u", "apigo.cloud/git/apigo/plugin") + writeFile("plugin.go", pluginCodeTPL, map[string]any{"name": name}) + writeFile(".gitignore", gitignoreTPL, map[string]any{"name": name}) + _ = runCommand("go", "mod", "tidy") + _ = os.Mkdir("tests", 0755) + _ = os.Chdir("tests") + _ = runCommand("go", "mod", "init", "tests") + _ = runCommand("go", "mod", "edit", "-go=1.18") + _ = runCommand("go", "mod", "edit", "-require=current-plugin@v0.0.0") + _ = runCommand("go", "mod", "edit", "-replace=current-plugin@v0.0.0=../") + _ = runCommand("go", "get", "-u", "apigo.cloud/git/apigo/plugin") + _ = runCommand("go", "get", "-u", "apigo.cloud/git/apigo/gojs") + writeFile("plugin_test.go", pluginTestCodeTPL, map[string]any{"name": name}) + writeFile("plugin_test.js", pluginTestJSCodeTPL, map[string]any{"name": name}) + _ = runCommand("go", "mod", "tidy") + checkSSGOTool() + fmt.Println(u.BGreen("new plugin " + name + " created")) + } +} + +func newServerProject(args []string) { +} + +func newServerAPI(args []string) { +} + +func runProject(args []string) { + gowatchPath, logvPath := findTool() + _ = runCommandPipe(logvPath, gowatchPath, "-pt", ".go,.json,.yml,.js,.ts", "run", ".") +} + +func testProject(args []string) { + gowatchPath, logvPath := findTool() + if u.FileExists("tests") { + _ = os.Chdir("tests") + fmt.Println(logvPath, gowatchPath) + _ = runCommandPipe(logvPath, gowatchPath, "-p", ".,..", "-pt", ".go,.yml,.js,.ts", "test", "-v", ".") + } else { + _ = runCommandPipe(logvPath, gowatchPath, "-pt", ".go,.yml,.js,.ts", "test", "-v", ".") + } +} + +var pkgMatcher = regexp.MustCompile(`(?m)^\s*_\s+"([\w-_/.]+)"`) + +func makePluginCodeImports(from string, imports *[]string) { + if files, err := os.ReadDir(from); err == nil { + for _, f := range files { + if f.IsDir() { + if !strings.HasPrefix(f.Name(), ".") { + makePluginCodeImports(path.Join(from, f.Name()), imports) + } + } else { + if strings.HasSuffix(f.Name(), ".go") && !strings.HasPrefix(f.Name(), ".") && f.Name() != "makePluginCode.go" { + if code, err := u.ReadFile(path.Join(from, f.Name())); err == nil { + for _, m := range pkgMatcher.FindAllStringSubmatch(code, 1024) { + *imports = u.AppendUniqueString(*imports, m[1]) + } + } + } + } + } + } +} + +func writeFile(filename string, fileContent string, data any) { + if fp, err := os.OpenFile(filename, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0600); err == nil { + tpl, _ := template.New(filename).Parse(fileContent) + if err = tpl.Execute(fp, data); err != nil { + fmt.Println(u.Red(err.Error())) + } + _ = fp.Close() + } else { + fmt.Println(u.Red(err.Error())) + } +} + +func makePluginCode(args []string) { + imports := make([]string, 0) + makePluginCodeImports(".", &imports) + if len(imports) > 0 { + writeFile("makePluginCode.go", makePluginCodeTPL, map[string]any{"imports": imports}) + if err := runCommand("go", "run", "makePluginCode.go"); err != nil { + fmt.Println(u.Red(err.Error())) + } + _ = os.Remove("makePluginCode.go") + } else { + fmt.Println(u.Red("no plugin imported")) + } +} + +func runCommand(name string, args ...string) error { + cmd := exec.Command(name, args...) + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + return cmd.Run() +} + +func runCommandPipe(pipeCommandName, commandName string, args ...string) error { + cmd1 := exec.Command(commandName, args...) + cmd2 := exec.Command(pipeCommandName) + + r, w := io.Pipe() + defer w.Close() + cmd1.Stdout = w + cmd1.Stderr = w + cmd2.Stdin = r + cmd2.Stdout = os.Stdout + cmd2.Stderr = os.Stderr + if err := cmd2.Start(); err != nil { + return err + } + if err := cmd1.Start(); err != nil { + return err + } + if err := cmd1.Wait(); err != nil { + return err + } + // 等待第二个命令完成 + if err := cmd2.Wait(); err != nil { + return err + } + return nil +} + +func main() { + if len(os.Args) > 1 { + cmd1 := os.Args[1] + cmd2 := cmd1 + if len(os.Args) > 2 { + cmd2 += " " + os.Args[2] + } + for _, cmdInfo := range commands { + if cmd1 == cmdInfo.Name || cmd1 == cmdInfo.ShortName { + cmdInfo.Func(os.Args[2:]) + return + } else if cmd2 == cmdInfo.Name { + cmdInfo.Func(os.Args[3:]) + return + } + } + } + + fmt.Println("tools for apigo.cloud") + fmt.Println() + fmt.Println("Usage:") + fmt.Println(" ", u.Cyan("ag [command] [...]")) + fmt.Println(" ", u.Magenta("ag [short command] [...]")) + fmt.Println() + fmt.Println("Commands:") + for _, cmdInfo := range commands { + fmt.Println(" ", u.Cyan(cmdInfo.Name), u.Dim("[")+u.Magenta(cmdInfo.ShortName)+u.Dim("]"), cmdInfo.Args, strings.Repeat(" ", 30-len(cmdInfo.Name)-len(cmdInfo.ShortName)-len(cmdInfo.Args)), cmdInfo.Comment) + } + fmt.Println() + fmt.Println("Examples:") + fmt.Println(" ", u.Magenta("ag +"), " create a new simple project with Hello World") + fmt.Println(" ", u.Magenta("ag +p ali"), " create a new plugin project named ali for use aliyun services") + fmt.Println() + + fmt.Println(findTool()) + +} diff --git a/templates/_gitignore b/templates/_gitignore new file mode 100644 index 0000000..8fece96 --- /dev/null +++ b/templates/_gitignore @@ -0,0 +1,3 @@ +.* +!.gitignore +/go.sum diff --git a/templates/_gitignore_server b/templates/_gitignore_server new file mode 100644 index 0000000..b4b8c24 --- /dev/null +++ b/templates/_gitignore_server @@ -0,0 +1,7 @@ +.* +!.gitignore +/go.sum +/www +/server +/env.yml +/env.json diff --git a/templates/_main.go b/templates/_main.go new file mode 100644 index 0000000..ccd8e4f --- /dev/null +++ b/templates/_main.go @@ -0,0 +1,20 @@ +package main + +import ( + "apigo.cloud/git/apigo/gojs" + //_ "apigo.cloud/git/apigo/plugins/file" +) + +func main() { + worldName := "" + gojs.RunFile("main.js", &gojs.RuntimeOption{ + Globals: map[string]interface{}{ + "setWorldName": func(name string) { + worldName = name + }, + "getWorldName": func() string { + return worldName + }, + }, + }) +} diff --git a/templates/_main.js b/templates/_main.js new file mode 100644 index 0000000..774bf1b --- /dev/null +++ b/templates/_main.js @@ -0,0 +1,7 @@ +// import file from 'plugins/file'; +// console.info(file.read('test.txt')) + +setWorldName('gojs') +logger.info('Hello '+getWorldName()) + +// return 'OK' diff --git a/templates/_main_test.go b/templates/_main_test.go new file mode 100644 index 0000000..24f58d9 --- /dev/null +++ b/templates/_main_test.go @@ -0,0 +1,14 @@ +package main_test + +import ( + "apigo.cloud/git/apigo/gojs" + "github.com/ssgo/u" + "testing" +) + +func TestPlusNumber(t *testing.T) { + r, err, _ := gojs.Run("return 1+2", nil) + if err != nil || u.Int(r) != 3 { + t.Fatal("TestPlusNumber failed", r, err) + } +} diff --git a/templates/_makePluginCode.go b/templates/_makePluginCode.go new file mode 100644 index 0000000..cb37eba --- /dev/null +++ b/templates/_makePluginCode.go @@ -0,0 +1,29 @@ +package main + +import ( + "apigo.cloud/git/apigo/gojs" + "apigo.cloud/git/apigo/plugin" +{{- range .imports}} + _ "{{.}}" +{{- end}} + "fmt" + "github.com/ssgo/u" + "path" +) + +func main() { + for _, plg := range plugin.List() { + code := gojs.MakePluginCode(&plg) + errStr := "no code" + if code != "" { + err := u.WriteFile(path.Join("plugins", plg.Id+".ts"), code) + if err == nil { + fmt.Println(u.Cyan(plg.Id), "-", plg.Name, u.BGreen("OK"), ">>", u.Magenta(path.Join("plugins", plg.Id+".ts"))) + continue + } else { + errStr = err.Error() + } + } + fmt.Println(u.Cyan(plg.Id), "-", plg.Name, u.BGreen(errStr)) + } +} diff --git a/templates/_plugin.go b/templates/_plugin.go new file mode 100644 index 0000000..b2dfb5e --- /dev/null +++ b/templates/_plugin.go @@ -0,0 +1,47 @@ +package {{.name}} + +import ( + "apigo.cloud/git/apigo/plugin" + "errors" + "github.com/ssgo/log" + "github.com/ssgo/u" +) + +var prefix = "" + +func init() { + plugin.Register(plugin.Plugin{ + Id: "{{.name}}", + Name: "", + // set plugin contents + Objects: map[string]interface{}{ + // *plugin.Context is optional if need to use, ignore it when invoke + "set": func(key string, value interface{}, ctx *plugin.Context) { + ctx.SetData(prefix+key, value) + }, + // return error is optional if need to throw exception, ignore it when invoke + "get": func(key string, ctx *plugin.Context) (interface{}, error) { + value := ctx.GetData(prefix + key) + if value == nil { + return nil, errors.New("data " + key + " is null") + } + return value, nil + }, + // you can get inject object from *plugin.Context, *log.Logger is default logger with a unique trance id + "remove": func(key string, ctx *plugin.Context) { + if logger, ok := ctx.GetInject("*log.Logger").(*log.Logger); ok { + logger.Warning("context data cannot be remove, will set to null") + } + ctx.SetData(prefix+key, nil) + }, + }, + // set config sample (recommend to use YAML format) + ConfigSample: `prefix: _`, + // init plugin config + Init: func(config map[string]interface{}) { + if config[prefix] != nil { + prefix = u.String(config[prefix]) + } + }, + }) +} diff --git a/templates/_plugin_test.go b/templates/_plugin_test.go new file mode 100644 index 0000000..226eb62 --- /dev/null +++ b/templates/_plugin_test.go @@ -0,0 +1,27 @@ +package {{.name}}_test + +import ( +"apigo.cloud/git/apigo/gojs" +_ "current-plugin" +"fmt" +"github.com/ssgo/u" +"os" +"strings" +"testing" +) + +func TestPlugin(t *testing.T) { + if files, err := os.ReadDir("."); err == nil { + for _, f := range files { + if !f.IsDir() && strings.HasSuffix(f.Name(), "_test.js") { + testName := f.Name()[0 : len(f.Name())-8] + r, err, _ := gojs.RunFile(f.Name(), nil) + if err != nil || r != true { + t.Fatal("test "+testName+" failed", r, err) + } else { + fmt.Println(u.Green("test "+testName), u.BGreen("OK")) + } + } + } + } +} diff --git a/templates/_plugin_test.js b/templates/_plugin_test.js new file mode 100644 index 0000000..3547ba1 --- /dev/null +++ b/templates/_plugin_test.js @@ -0,0 +1,18 @@ +// run "ag export plugins" to make plugins code +import logger from 'plugins/logger'; +import {{.name}} from 'plugins/{{.name}}'; + +{{.name}}.set('aaa', 111) +let aaa = {{.name}}.get('aaa') +if (aaa !== 111) return 'set value error' + +try { + {{.name}}.get('bbb') + return 'no exception when getting non-existent key' +} catch (ex){ + logger.error(ex.message, {stack:ex.stack}) +} +logger.info('aaa') +{{.name}}.remove('bbb') + +return true