package main import ( "archive/zip" _ "embed" "fmt" "io" "os" "os/exec" "path" "path/filepath" "regexp" "runtime" "strconv" "strings" "text/template" "time" "github.com/ssgo/httpclient" "github.com/ssgo/u" ) var version = "v0.0.1" //go:embed templates/_main.go var mainCodeTPL string //go:embed templates/_gitignore var gitignoreTPL string var goPath = "go" type Command struct { Name string ShortName string Args string Comment string Func func([]string) bool } type Config struct { Name string Version string Main string Target map[string]string Module map[string]string ModuleAlias map[string]string ExtraImport map[string]string CacheFile []string } var commands = []Command{ {"init", "i", "", "init a new project for empty dir", initProject}, {"build", "b", "", "build project for current os", buildProject}, // {"init plugin", "i p", "", "init a new plugin project for empty dir", initPluginProject}, // {"run", "r", "", "will exec `go run .`", runProject}, // {"watch run", "rr", "[...]", "run project use sskey, if project files changed will restart auto, ... args see sskey help https://github.com/ssgo/tool", devProject}, // {"test", "t", "", "will exec `go test -v .`, will exec into tests if exists tests dir", testProject}, // {"watch test", "tt", "[...]", "test project use sskey, if project files changed will restart auto, ... args see sskey help https://github.com/ssgo/tool", devTestProject}, // {"tidy", "td", "", "tidy project, find imported plugins from .js files add import code to jsImports.go, export typescript code for used plugins into \"plugins/\"", tidy}, // {"tags", "", "", "show git tags", showGitTags}, // {"commit", "co", "comment", "commit git repo and push, comment is need", commitGitRepo}, // {"commit and +tag", "co+", "comment", "commit git repo and push, than update tag, comment is need", commitAndTagGitRepo}, // {"update tag", "t+", "[version]", "add git tag push, if no new tag specified will use last tag +1", addGitTag}, } var hc = httpclient.GetClient(30 * time.Second) var cachePath = ".ag" func init() { hc.SetGlobalHeader("Content-Type", "application/json") if homePath, err := os.UserHomeDir(); err == nil { cachePath = filepath.Join(homePath, ".ag", "cache") } } func showGitTags(args []string) bool { return nil == runCommand("git", "tag", "-l", "v*", "--sort=-taggerdate", "--format=%(refname:short) %(taggerdate:short) %(*objectname:short)") } func commitAndTagGitRepo(args []string) bool { if commitGitRepo(args) { return addGitTag([]string{}) } return false } func commitGitRepo(args []string) bool { comment := strings.Join(args, " ") if comment != "" { if err := runCommand("git", "commit", "-a", "-m", comment); err == nil { if err := runCommand("git", "push"); err == nil { return true } else { fmt.Println("git push failed:", err.Error()) } } else { fmt.Println("git commit failed:", err.Error()) } } else { fmt.Println("commit message is empty") } return false } func addGitTag(args []string) bool { newVer := "" if len(args) > 0 { newVer = args[0] } if newVer == "" { if outs, err := u.RunCommand("git", "tag", "-l", "v*", "--sort=taggerdate"); err == nil { oldVer := "v0.0.0" for i := len(outs) - 1; i >= 0; i-- { if outs[i][0] == 'v' && strings.IndexByte(outs[i], '.') != -1 { oldVer = outs[len(outs)-1] break } } if len(args) > 0 { newVer = args[0] } else { versionParts := strings.Split(oldVer, ".") v, err := strconv.Atoi(versionParts[len(versionParts)-1]) if err != nil { v = 0 } versionParts[len(versionParts)-1] = strconv.Itoa(v + 1) newVer = strings.Join(versionParts, ".") } } else { fmt.Println("get last tag failed:", err.Error()) } } if newVer != "" { if err := runCommand("git", "tag", "-a", newVer, "-m", "update tag by 'ag tag+'"); err == nil { if err := runCommand("git", "push", "origin", newVer); err != nil { fmt.Println("git push failed:", err.Error()) } } else { fmt.Println("git add tag failed:", err.Error()) } } return false } func findTool() (sskeyPath string, logVPath string) { sskeyPath = "sskey" logVPath = "logv" if binPath, err := exec.LookPath("sskey"); err == nil && binPath != "" { sskeyPath = binPath } if binPath, err := exec.LookPath("logv"); err == nil && binPath != "" { logVPath = binPath } if sskeyPath == "sskey" || logVPath == "logv" { _ = runCommand(goPath, "get", "-u", "github.com/ssgo/tool") if sskeyPath == "sskey" { _ = runCommand(goPath, "install", "github.com/ssgo/tool/sskey") } if logVPath == "logv" { _ = runCommand(goPath, "install", "github.com/ssgo/tool/logv") } } return sskeyPath, logVPath } func parseRepo(url string) (apiUrl, owner, repo string) { name := "" if strings.HasPrefix(url, "github.com/") { apiUrl = "https://api.github.com/" name = url[11:] } else if strings.HasPrefix(url, "gitee.com/") { apiUrl = "https://gitee.com/api/v5/" name = url[10:] } else if strings.HasPrefix(url, "apigo.cc/") { apiUrl = "https://apigo.cc/api/v1/" name = url[16:] } else { apiUrl = "https://apigo.cc/api/v1/" if strings.ContainsRune(url, '/') { name = url } else { name = "apigo/" + url } } if !strings.ContainsRune(name, '/') { name = name + "/" + name } a := strings.Split(name, "/") owner = a[0] repo = a[1] return } type Tag struct { Name string Commit struct { Sha string Url string } Zipball_url string Tarball_url string } func fetchRepo(name string) (repoPath string) { apiUrl, owner, repo := parseRepo(name) tagsUrl := apiUrl + filepath.Join("repos", owner, repo, "tags") tags := make([]Tag, 0) fmt.Println("try to fetch tags", tagsUrl) r := hc.Get(tagsUrl) if r.Error == nil { r.To(&tags) } else if apiUrl != "https://api.github.com/" { tagsUrl = "https://api.github.com/" + filepath.Join("repos", owner, repo, "tags") fmt.Println("try to fetch tags", tagsUrl) r = hc.Get(tagsUrl) if r.Error == nil { r.To(&tags) } else { fmt.Println("fetch tags error", r.Error) } } if len(tags) > 0 { lastVersion := tags[0].Name zipUrl := tags[0].Zipball_url tagPath := filepath.Join(cachePath, owner, repo, lastVersion) if !u.FileExists(tagPath) { zipFile := filepath.Join(cachePath, owner, repo, lastVersion+".zip") fmt.Println("Download file ", zipUrl) _, err := hc.Download(zipFile, zipUrl, func(start, end int64, ok bool, finished, total int64) { fmt.Printf(" %.2f%%", float32(finished)/float32(total)*100) }) fmt.Println() if err != nil { fmt.Println(u.BRed("download file failed")) return } if u.FileExists(zipFile) { if reader, err := zip.OpenReader(zipFile); err == nil { for _, f := range reader.File { toFile := filepath.Join(tagPath, f.Name) if f.FileInfo().IsDir() { //fmt.Println("extract dir", f.Name, toFile) os.MkdirAll(toFile, 0755) } else { //fmt.Println("extract file", f.Name, toFile) u.CheckPath(toFile) if fp, err := os.OpenFile(toFile, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0644); err == nil { if rd, err := f.Open(); err == nil { if _, err := io.Copy(fp, rd); err != nil { fmt.Println(u.BRed("write file failed"), toFile, err.Error()) } else { //fmt.Println(u.BGreen("write file success"), toFile) } _ = rd.Close() } else { fmt.Println(u.BRed("open file in zip failed"), f.Name, err.Error()) } _ = fp.Close() } else { fmt.Println(u.BRed("open dst file failed"), toFile, err.Error()) } } } _ = reader.Close() } else { fmt.Println(u.BRed("open zip file failed"), zipFile, err.Error()) } _ = os.Remove(zipFile) } } return filepath.Join(tagPath, repo) } else { fmt.Println(u.BRed("no repo found for"), name) return "" } } func getConfig() *Config { conf := &Config{ Name: "", Version: "v0.0.1", Main: "main.js", Module: map[string]string{}, ModuleAlias: map[string]string{}, ExtraImport: map[string]string{}, CacheFile: []string{}, } u.LoadYaml("apigo.yml", conf) if conf.Name == "" { if pathname, err := os.Getwd(); err != nil { conf.Name = "apigo" } else { conf.Name = filepath.Base(pathname) } } if conf.Target == nil { conf.Target = map[string]string{ "darwin": "amd64 arm64", "linux": "amd64 arm64", "windows": "amd64", } } if conf.Module == nil { conf.Module = map[string]string{} } if conf.ModuleAlias == nil { conf.ModuleAlias = map[string]string{} } if conf.ExtraImport == nil { conf.ExtraImport = map[string]string{} } if conf.CacheFile == nil { conf.CacheFile = []string{} } for pkgName, pkgVersion := range conf.Module { if pkgVersion == "" { conf.Module[pkgName] = "latest" } aliasName := pkgName if strings.HasPrefix(aliasName, "apigo.cc/gojs/") { aliasName = aliasName[14:] } else if strings.HasPrefix(aliasName, "apigo.cc/") { aliasName = aliasName[9:] } if aliasName != pkgName && conf.ModuleAlias[aliasName] == "" { conf.ModuleAlias[aliasName] = pkgName } } for pkgName, pkgVersion := range conf.ExtraImport { if pkgVersion == "" { conf.ExtraImport[pkgName] = "latest" } } return conf } func initProject(args []string) bool { // if u.FileExists("go.mod") { // fmt.Println(u.Red("go.mod exists, are you sure to init this project?")) // sure := 'n' // _, _ = fmt.Scan(&sure) // if sure != 'y' && sure != 'Y' { // return false // } // } if u.FileExists("main.go") || u.FileExists("go.mod") { fmt.Println(u.Red("main.go or go.mod is exists, are you sure to overwrite it? (y/n)")) sure := 'n' _, _ = fmt.Scan(&sure) if sure != 'y' && sure != 'Y' { return false } } if !u.FileExists("apigo.yml") { refProjName := "default" if len(args) > 0 { refProjName = args[0] } // TODO check name is path repoPath := fetchRepo(refProjName) if u.FileExists(repoPath) { if err := CopyFile(repoPath, "."); err == nil { fmt.Println(u.Green("copy project files success"), repoPath) } else { fmt.Println(u.BRed("copy project files failed"), repoPath, err.Error()) return false } } else { fmt.Println(u.BRed("fetch repo failed"), repoPath) return false } } conf := getConfig() writeFile("main.go", mainCodeTPL, conf) _ = runCommand(goPath, "mod", "init", conf.Name) _ = runCommand(goPath, "mod", "edit", "-go=1.18") for pkgName, pkgVersion := range conf.Module { _ = runCommand(goPath, "get", pkgName+"@"+pkgVersion) } for pkgName, pkgVersion := range conf.ExtraImport { _ = runCommand(goPath, "get", pkgName+"@"+pkgVersion) } if !u.FileExists(".gitignore") { writeFile(".gitignore", gitignoreTPL, nil) } _ = runCommand(goPath, "mod", "tidy") findTool() fmt.Println(u.BGreen("new project " + conf.Name + " created")) fmt.Println(u.Cyan("run \"ag build\" to use it")) fmt.Println(u.Cyan("run \"ag test\" to test")) fmt.Println(u.Cyan("run \"ag build all\" to publish")) return true } func packageProject(args []string) bool { return buildProjectWithOption(args, true) } func buildProject(args []string) bool { return buildProjectWithOption(args, false) } func buildProjectWithOption(args []string, isPack bool) bool { conf := getConfig() targets := make([][]string, 0) if len(args) > 0 { if args[0] == "all" { for k, v := range conf.Target { for _, arch := range strings.Split(v, " ") { targets = append(targets, []string{k, arch}) } } } else { if len(args) > 1 { targets = append(targets, []string{args[0], args[1]}) } else { targets = append(targets, []string{args[0], runtime.GOARCH}) } } } else { targets = append(targets, []string{runtime.GOOS, runtime.GOARCH}) } output := "build" if isPack { output = "release" // TODO 生成静态文件和入口文件嵌入代码 // TODO 生成SSKey嵌入代码 defer func() { os.Remove("cacheFiles.go") os.Remove("setSSKey.go") }() } _ = runCommand(goPath, "mod", "tidy") for _, target := range targets { buildOS := target[0] buildArch := target[1] name := path.Base(conf.Name) ext := "" if buildOS == "windows" { ext = ".exe" } buildPath := filepath.Join(output, fmt.Sprintf("%s_%s_%s%s", name, buildOS, buildArch, ext)) env := append(make([]string, 0), "GOOS="+buildOS, "GOARCH="+buildArch) err := runCommandWithEnv("go", env, "build", "-o", buildPath, "-ldflags", "-s -w", ".") if err != nil { fmt.Println(u.BRed("build failed"), buildOS, buildArch, err.Error()) } else { fmt.Println(u.Green("build for"), buildOS, buildArch, "to", buildPath) } } return true } func CopyFile(from, to string) error { fromStat, _ := os.Stat(from) if fromStat.IsDir() { // copy dir for _, f := range u.ReadDirN(from) { err := CopyFile(filepath.Join(from, f.Name), filepath.Join(to, f.Name)) if err != nil { return err } } return nil } else { // copy file toStat, err := os.Stat(to) if err == nil && toStat.IsDir() { to = filepath.Join(to, filepath.Base(from)) } u.CheckPath(to) if writer, err := os.OpenFile(to, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0644); err == nil { defer writer.Close() if reader, err := os.OpenFile(from, os.O_RDONLY, 0644); err == nil { defer reader.Close() _, err = io.Copy(writer, reader) return err } else { return err } } else { return err } } } // func _runProject(args []string, isWatch bool) bool { // _ = runCommand(goPath, "mod", "tidy") // goBinPath, logVPath := findTool() // if isWatch { // args = append(args, "-pt", ".go,.js,.yml", "run", ".") // } else { // goBinPath = goPath // args = append(args, "run", ".") // } // return nil == runCommandPipe (logVPath, goBinPath, goRunEnv, args...) // } // func runProject(args []string) bool { // return _runProject(args, false) // } // func devProject(args []string) bool { // return _runProject(args, true) // } // func testProject(args []string) bool { // return _testProject(args, false) // } // func devTestProject(args []string) bool { // return _testProject(args, true) // } // func _testProject(args []string, isWatch bool) bool { // if u.FileExists("tests") { // if u.FileExists(filepath.Join("tests", "go.mod")) { // _ = os.Chdir("tests") // if isWatch { // args = append(args, "-p", "..") // } // isRun := false // for _, f := range u.ReadDirN(".") { // if strings.HasSuffix(f.Name, ".go") { // goStr := u.ReadFileN(f.FullName) // if strings.Contains(goStr, "package main") && !strings.Contains(goStr, "package main_test") { // isRun = true // } // break // } // } // if !isRun { // args = append(args, "test", "-v", ".") // } else { // args = append(args, "run", ".") // } // } else { // args = append(args, "test", "-v", "tests") // } // } else { // args = append(args, "test", "-v", ".") // } // _ = runCommand(goPath, "mod", "tidy") // sskeyPath, logVPath := findTool() // if isWatch { // args2 := append([]string{"-pt", ".go,.js,.yml"}, args...) // _ = runCommandPipeWithEnv(logVPath, sskeyPath, goRunEnv, args2...) // } else { // _ = runCommandPipeWithEnv(logVPath, goPath, goRunEnv, args...) // } // return true // } var pkgMatcher = regexp.MustCompile(`(?im)^\s*(import)?\s*_\s+"([\w-_/.]+)"`) var importMatcher = regexp.MustCompile(`(?im)^\s*import\s+([\w{}, ]+)\s+from\s+['"]([\w./\\\- ]+)['"]`) func makeJsImports(path string, jsImports *[]string, pkgName string) { if u.FileExists(filepath.Join(path, "go.mod")) { if m := modNameMatcher.FindStringSubmatch(u.ReadFileN(filepath.Join(path, "go.mod"))); m != nil { if pkgName != m[1] { return } } } for _, f := range u.ReadDirN(path) { filename := f.Name fullName := filepath.Join(path, filename) if !strings.HasPrefix(filename, ".") && !strings.HasPrefix(filename, "_") && filename != "_makePluginCode" && filename != "node_modules" && filename != "www" && filename != "src" { if f.IsDir { makeJsImports(fullName, jsImports, pkgName) } else if strings.HasSuffix(filename, ".js") { if code, err := u.ReadFile(fullName); err == nil { for _, m := range importMatcher.FindAllStringSubmatch(code, 1024) { if !strings.HasPrefix(m[2], ".") && !strings.HasPrefix(m[2], "_") && m[2] != "current-plugin" && m[2] != "console" && m[2] != "logger" { checkFile := filepath.Join(path, m[2]) checkFileInfo := u.GetFileInfo(checkFile) if checkFileInfo != nil && checkFileInfo.IsDir { checkFile = filepath.Join(checkFile, "index.js") } else if !strings.HasSuffix(checkFile, ".js") { checkFile += ".js" } //fmt.Println(u.BMagenta(checkFile), u.FileExists(checkFile)) if !u.FileExists(checkFile) { *jsImports = u.AppendUniqueString(*jsImports, m[2]) } } } } } } } } func makePluginCodeImports(from string, imports *[]string, parentModuleName string) { jsImports := make([]string, 0) currentParentModuleName := parentModuleName if u.FileExists(filepath.Join(from, "go.mod")) { pkgName := "" if m := modNameMatcher.FindStringSubmatch(u.ReadFileN(filepath.Join(from, "go.mod"))); m != nil { pkgName = m[1] } makeJsImports(from, &jsImports, pkgName) parentModuleName = pkgName } currentPkgName := "" for _, f := range u.ReadDirN(from) { if f.IsDir { if !strings.HasPrefix(f.Name, ".") { makePluginCodeImports(filepath.Join(from, f.Name), imports, parentModuleName) } } else { if strings.HasSuffix(f.Name, ".go") && !strings.HasPrefix(f.Name, ".") && f.Name != "makePluginCode.go" { if code, err := u.ReadFile(filepath.Join(from, f.Name)); err == nil { if m := pkgNameMatcher.FindStringSubmatch(code); m != nil { if currentPkgName+"_test" != m[1] { currentPkgName = m[1] } } for _, m := range pkgMatcher.FindAllStringSubmatch(code, 1024) { if m[2] != "current-plugin" && !strings.HasPrefix(f.Name, "jsImports") { *imports = u.AppendUniqueString(*imports, m[2]) } } } } } } if currentPkgName != "" && u.FileExists(filepath.Join(from, "go.mod")) { pkgList := make([]string, 0) for _, plgPkg := range jsImports { if plgPkg != currentParentModuleName && !u.StringIn(*imports, plgPkg) { pkgList = append(pkgList, plgPkg) *imports = u.AppendUniqueString(*imports, plgPkg) } } if len(pkgList) > 0 { _ = u.WriteFile(filepath.Join(from, u.StringIf(strings.HasSuffix(currentPkgName, "_test"), "jsImports_test.go", "jsImports.go")), `package `+currentPkgName+` import _ "`+strings.Join(pkgList, "\"\nimport _ \"")+`" `) } } } 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())) } } var replaceMatcher = regexp.MustCompile(`([a-zA-Z0-9._\-/]+)\s+(v[0-9.]+)\s+=>\s+([a-zA-Z0-9._\-/\\]+)`) var modNameMatcher = regexp.MustCompile(`(?m)^module\s+([a-zA-Z0-9._\-/]+)$`) var pkgNameMatcher = regexp.MustCompile(`(?m)^package\s+([a-zA-Z0-9._\-/]+)$`) // func tidy(args []string) bool { // // 判断是否可执行项目(不创建指向项目本身的 import _) // isMainProject := false // isEmptyProject := true // if files, err := os.ReadDir("."); err == nil { // for _, f := range files { // if !f.IsDir() && strings.HasSuffix(f.Name(), ".go") { // isEmptyProject = false // code, _ := u.ReadFile(f.Name()) // if strings.Contains(code, "package main") || strings.Contains(code, "func main(") { // isMainProject = true // break // } // } // } // } // // 扫描用到的插件(import _) // imports := make([]string, 0) // makePluginCodeImports(".", &imports, "") // findGoModCode, _ := u.ReadFile("go.mod") // goModCode := "module main\ngo 1.18\n" // currentModuleName := "current-project" // if !isMainProject { // if m := modNameMatcher.FindStringSubmatch(findGoModCode); m != nil { // currentModuleName = m[1] // } // goModCode += "require " + currentModuleName + " v0.0.0 // indirect\nreplace " + currentModuleName + " v0.0.0 => ../\n" // } // // 扫描 replace,处理路径后加入到 _makePluginCode/go.mod // for _, m := range replaceMatcher.FindAllStringSubmatch(findGoModCode, 100) { // replacePath := m[3] // if absPath, err := filepath.Abs(m[3]); err == nil { // replacePath = absPath // } // goModCode += fmt.Sprintln("replace", m[1], m[2], "=>", replacePath) // } // if !isMainProject && !isEmptyProject { // imports = append(imports, currentModuleName) // } // _ = u.WriteFile(filepath.Join("_makePluginCode", "go.mod"), goModCode) // writeFile(filepath.Join("_makePluginCode", "main.go"), makePluginCodeTPL, map[string]any{"imports": imports}) // _ = os.Chdir("_makePluginCode") // defer func() { // _ = os.Chdir("..") // //_ = os.RemoveAll("_makePluginCode") // }() // _ = runCommand(goPath, "mod", "tidy") // if err := runCommandWithEnv(goPath, goRunEnv, "run", "."); err != nil { // fmt.Println(u.Red(err.Error())) // } // return true // } func runCommand(name string, args ...string) error { return runCommandWithEnv(name, nil, args...) } func runCommandWithEnv(name string, env []string, args ...string) error { // pathname, _ := os.Getwd() // fmt.Println(u.BMagenta(pathname), u.BCyan(name), u.Cyan(strings.Join(args, " "))) cmd := exec.Command(name, args...) if env != nil { // if runtime.GOOS == "windows" { // env = append(env, "GOTMPDIR="+pathname, "GOWORK="+pathname) // } cmd.Env = append(os.Environ(), env...) } cmd.Stdin = os.Stdin cmd.Stdout = os.Stdout cmd.Stderr = os.Stderr err := cmd.Run() if err != nil { fmt.Println(u.Red(err.Error())) } return err } func runCommandPipe(pipeCommandName, commandName string, args ...string) error { return runCommandPipeWithEnv(pipeCommandName, commandName, nil, args...) } func runCommandPipeWithEnv(pipeCommandName, commandName string, env []string, args ...string) error { pathname, _ := os.Getwd() fmt.Println(u.BMagenta(pathname), u.BCyan(commandName), u.Cyan(strings.Join(args, " ")), u.BMagenta(pipeCommandName)) cmd1 := exec.Command(commandName, args...) cmd2 := exec.Command(pipeCommandName) // if env != nil { // if runtime.GOOS == "windows" { // env = append(env, "GOTMPDIR="+pathname, "GOWORK="+pathname) // } // cmd1.Env = append(cmd1.Env, env...) // cmd2.Env = append(cmd2.Env, env...) // } r, w := io.Pipe() wClosed := false defer func() { if !wClosed { w.Close() } }() cmd1.Stdin = os.Stdin cmd1.Stdout = w cmd1.Stderr = w cmd2.Stdin = r cmd2.Stdout = os.Stdout cmd2.Stderr = os.Stderr var err error if err = cmd2.Start(); err == nil { if err = cmd1.Start(); err == nil { if err = cmd1.Wait(); err == nil { w.Close() wClosed = true // 等待第二个命令完成 if err = cmd2.Wait(); err == nil { return nil } } } } fmt.Println(u.Red(err.Error())) return err } func main() { var err error if goPath, err = exec.LookPath("go"); err != nil || goPath == "" { fmt.Println(u.Red("Please install Go SDK first")) fmt.Println(u.Cyan("https://go.dev/")) return } if len(os.Args) > 1 { cmd1 := os.Args[1] cmd2 := cmd1 if len(os.Args) > 2 { cmd2 += " " + os.Args[2] } for i := len(commands) - 1; i >= 0; i-- { cmdInfo := commands[i] if len(os.Args) > 2 && (cmd2 == cmdInfo.Name || cmd2 == cmdInfo.ShortName) { cmdInfo.Func(os.Args[3:]) return } else if cmd1 == cmdInfo.Name || cmd1 == cmdInfo.ShortName { cmdInfo.Func(os.Args[2:]) return } } } fmt.Println("tools for apigo.cloud", version) fmt.Println("go sdk", goPath) 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 { padStr := "" padN := 30 - len(cmdInfo.Name) - len(cmdInfo.ShortName) - len(cmdInfo.Args) if padN > 0 { padStr = strings.Repeat(" ", padN) } fmt.Println(" ", u.Cyan(cmdInfo.Name), u.Dim("[")+u.Magenta(cmdInfo.ShortName)+u.Dim("]"), cmdInfo.Args, padStr, 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() }