ag/main.go
2024-03-16 22:54:11 +08:00

342 lines
11 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package main
import (
_ "embed"
"fmt"
"github.com/ssgo/u"
"io"
"os"
"os/exec"
"path"
"path/filepath"
"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)
}
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},
//{"new game", "+g", "[name]", "create a new game project, will create in the current directory if no name is specified", newServerProject},
//{"new webkit", "+w", "[name]", "create a new webkit project, will create in the current directory if no name is specified", newServerProject},
{"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", },
//{"build", "b", "[-m]", "build for current os, output to build/, -m will mix js files into exec file", },
//{"build mac", "bm", "", "build", },
//{"build macarm", "bma", "", "build", },
//{"build win", "bw", "", "build", },
//{"build win32", "bw32", "", "build", },
//{"build linux", "bl", "", "build", },
//{"build linuxarm", "bla", "", "build", },
//{"build all", "ba", "", "build", },
//{"deploy", "d", "", "deploy", },
}
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()
_ = runCommand("go", "mod", "tidy")
_ = runCommandPipe(logvPath, gowatchPath, "-pt", ".go,.yml,.js", "run", ".")
//_ = runCommandPipe(logvPath, gowatchPath, "-pt", ".go,.yml,.js", "-ig", "api", "run", ".")
}
func testProject(args []string) {
gowatchPath, logvPath := findTool()
if u.FileExists("tests") {
_ = os.Chdir("tests")
_ = runCommand("go", "mod", "tidy")
_ = runCommandPipe(logvPath, gowatchPath, "-p", ".,..", "-pt", ".go,.yml,.js", "test", "-v", ".")
} else {
_ = runCommandPipe(logvPath, gowatchPath, "-pt", ".go,.yml,.js", "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) {
if m[1] != "current-plugin" {
*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()))
}
}
var replaceMatcher = regexp.MustCompile(`([a-zA-Z0-9._\-/]+)\s+(v[0-9.]+)\s+=>\s+([a-zA-Z0-9._\-/\\]+)`)
func makePluginCode(args []string) {
// 判断是否可执行项目(不创建指向项目本身的 import _
isMainProject := false
if files, err := os.ReadDir("."); err == nil {
for _, f := range files {
if !f.IsDir() && strings.HasSuffix(f.Name(), ".go") {
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)
goModCode := "module main\ngo 1.18\n"
if !isMainProject {
imports = append(imports, "current-project")
goModCode += "require current-project v0.0.0 // indirect\nreplace current-project v0.0.0 => ../\n"
}
// 扫描 replace处理路径后加入到 _makePluginCode/go.mod
findGoModCode, _ := u.ReadFile("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)
}
_ = u.WriteFile("_makePluginCode/go.mod", goModCode)
writeFile("_makePluginCode/main.go", makePluginCodeTPL, map[string]any{"imports": imports})
_ = os.Chdir("_makePluginCode")
_ = runCommand("go", "mod", "tidy")
if err := runCommand("go", "run", "."); err != nil {
fmt.Println(u.Red(err.Error()))
}
_ = os.Chdir("..")
_ = os.RemoveAll("_makePluginCode")
}
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()
}