ag/main.go

467 lines
15 KiB
Go
Raw Permalink Normal View History

2024-03-15 22:32:19 +08:00
package main
import (
_ "embed"
"fmt"
"github.com/ssgo/u"
"io"
"os"
"os/exec"
"path"
2024-03-16 22:54:11 +08:00
"path/filepath"
2024-03-15 22:32:19 +08:00
"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},
2024-03-16 22:54:11 +08:00
//{"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},
//{"download", "dl", "[plugin name]", "will fetch plugin into project", downloadPlugin},
{"run", "r", "", "will exec `go run .`", runProject},
2024-05-27 16:57:37 +08:00
{"watch run", "rr", "[...]", "run project use gowatch, if project files changed will restart auto, ... args see gowatch help https://github.com/ssgo/tool", devProject},
{"test", "t", "", "will exec `go test -v .`, will exec into tests if exists tests dir", testProject},
2024-05-27 16:57:37 +08:00
{"watch test", "tt", "[...]", "test project use gowatch, if project files changed will restart auto, ... args see gowatch help https://github.com/ssgo/tool", devTestProject},
//{"export plugins", "ep", "", "export typescript code for used plugins into \"plugins/\"", makePluginCode},
{"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},
2024-03-15 22:32:19 +08:00
//{"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", },
2024-03-16 22:54:11 +08:00
//{"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", },
//{"publish", "p", "", "publish project to target server", },
// TODO 从 apigo.cloud/git 中读取信息增加命令例如server
// TODO 从 plugins 中读取信息增加命令例如dao
2024-03-15 22:32:19 +08:00
}
2024-05-27 16:57:37 +08:00
func findTool() (goWatchPath string, logVPath string) {
goWatchPath = "gowatch"
logVPath = "logv"
2024-03-15 22:32:19 +08:00
if binPath, err := exec.LookPath("gowatch"); err == nil && binPath != "" {
2024-05-27 16:57:37 +08:00
goWatchPath = binPath
2024-03-15 22:32:19 +08:00
}
if binPath, err := exec.LookPath("logv"); err == nil && binPath != "" {
2024-05-27 16:57:37 +08:00
logVPath = binPath
2024-03-15 22:32:19 +08:00
}
2024-05-27 16:57:37 +08:00
return goWatchPath, logVPath
2024-03-15 22:32:19 +08:00
}
func checkSSGOTool() {
2024-05-27 16:57:37 +08:00
goWatchPath, logVPath := findTool()
if goWatchPath == "gowatch" || logVPath == "logv" {
2024-03-15 22:32:19 +08:00
_ = runCommand("go", "get", "-u", "github.com/ssgo/tool")
2024-05-27 16:57:37 +08:00
if goWatchPath == "gowatch" {
2024-03-15 22:32:19 +08:00
_ = runCommand("go", "install", "github.com/ssgo/tool/gowatch")
}
2024-05-27 16:57:37 +08:00
if logVPath == "logv" {
2024-03-15 22:32:19 +08:00
_ = 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) {
}
2024-05-27 16:57:37 +08:00
func _runProject(args []string, isWatch bool) {
2024-03-16 22:54:11 +08:00
_ = runCommand("go", "mod", "tidy")
2024-05-27 16:57:37 +08:00
goBinPath, logVPath := findTool()
if isWatch {
args = append(args, "-pt", ".go,.js,.yml", "run", ".")
} else {
goBinPath = "go"
args = append(args, "run", ".")
}
_ = runCommandPipe(logVPath, goBinPath, args...)
}
func runProject(args []string) {
_runProject(args, false)
}
func devProject(args []string) {
_runProject(args, true)
2024-03-15 22:32:19 +08:00
}
func testProject(args []string) {
2024-05-27 16:57:37 +08:00
_testProject(args, false)
}
func devTestProject(args []string) {
_testProject(args, true)
}
func _testProject(args []string, isWatch bool) {
2024-03-15 22:32:19 +08:00
if u.FileExists("tests") {
if u.FileExists(filepath.Join("tests", "go.mod")) {
_ = os.Chdir("tests")
2024-05-27 16:57:37 +08:00
if isWatch {
args = append(args, "-p", "..")
}
args = append(args, "test", "-v", ".")
} else {
2024-05-27 16:57:37 +08:00
args = append(args, "test", "-v", "tests")
}
2024-03-15 22:32:19 +08:00
} else {
2024-05-27 16:57:37 +08:00
args = append(args, "test", "-v", ".")
2024-03-15 22:32:19 +08:00
}
_ = runCommand("go", "mod", "tidy")
2024-05-27 16:57:37 +08:00
goWatchPath, logVPath := findTool()
if isWatch {
args2 := append([]string{"-pt", ".go,.js,.yml"}, args...)
_ = runCommandPipe(logVPath, goWatchPath, args2...)
} else {
_ = runCommandPipe(logVPath, "go", args...)
}
}
2024-05-27 16:57:37 +08:00
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" {
if !u.FileExists(filepath.Join(path, m[2])) {
*jsImports = u.AppendUniqueString(*jsImports, m[2])
}
}
}
}
}
}
}
2024-03-15 22:32:19 +08:00
}
2024-05-27 16:57:37 +08:00
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
}
2024-03-15 22:32:19 +08:00
2024-05-27 16:57:37 +08:00
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 {
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])
2024-03-15 22:32:19 +08:00
}
}
}
}
}
}
2024-05-27 16:57:37 +08:00
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 _ \"")+`"
`)
}
}
2024-03-15 22:32:19 +08:00
}
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()))
}
}
2024-03-16 22:54:11 +08:00
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._\-/]+)$`)
2024-05-27 16:57:37 +08:00
var pkgNameMatcher = regexp.MustCompile(`(?m)^package\s+([a-zA-Z0-9._\-/]+)$`)
2024-03-16 22:54:11 +08:00
2024-05-27 16:57:37 +08:00
func tidy(args []string) {
2024-03-16 22:54:11 +08:00
// 判断是否可执行项目(不创建指向项目本身的 import _
isMainProject := false
isEmptyProject := true
2024-03-16 22:54:11 +08:00
if files, err := os.ReadDir("."); err == nil {
for _, f := range files {
if !f.IsDir() && strings.HasSuffix(f.Name(), ".go") {
isEmptyProject = false
2024-03-16 22:54:11 +08:00
code, _ := u.ReadFile(f.Name())
if strings.Contains(code, "package main") || strings.Contains(code, "func main(") {
isMainProject = true
break
}
}
}
}
// 扫描用到的插件import _
2024-03-15 22:32:19 +08:00
imports := make([]string, 0)
2024-05-27 16:57:37 +08:00
makePluginCodeImports(".", &imports, "")
2024-03-16 22:54:11 +08:00
findGoModCode, _ := u.ReadFile("go.mod")
2024-03-16 22:54:11 +08:00
goModCode := "module main\ngo 1.18\n"
currentModuleName := "current-project"
2024-03-16 22:54:11 +08:00
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"
2024-03-16 22:54:11 +08:00
}
// 扫描 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
2024-03-15 22:32:19 +08:00
}
2024-03-16 22:54:11 +08:00
goModCode += fmt.Sprintln("replace", m[1], m[2], "=>", replacePath)
}
if !isMainProject && !isEmptyProject {
imports = append(imports, currentModuleName)
}
2024-03-16 22:54:11 +08:00
_ = u.WriteFile("_makePluginCode/go.mod", goModCode)
writeFile("_makePluginCode/main.go", makePluginCodeTPL, map[string]any{"imports": imports})
_ = os.Chdir("_makePluginCode")
defer func() {
_ = os.Chdir("..")
_ = os.RemoveAll("_makePluginCode")
}()
2024-03-16 22:54:11 +08:00
_ = runCommand("go", "mod", "tidy")
if err := runCommand("go", "run", "."); err != nil {
fmt.Println(u.Red(err.Error()))
2024-03-15 22:32:19 +08:00
}
}
func runCommand(name string, args ...string) error {
cmd := exec.Command(name, args...)
cmd.Stdin = os.Stdin
2024-03-15 22:32:19 +08:00
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()
wClosed := false
defer func() {
if !wClosed {
w.Close()
}
}()
cmd1.Stdin = os.Stdin
2024-03-15 22:32:19 +08:00
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
}
w.Close()
wClosed = true
2024-03-15 22:32:19 +08:00
// 等待第二个命令完成
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 {
2024-05-27 16:57:37 +08:00
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)
2024-03-15 22:32:19 +08:00
}
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()
}