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},
2024-03-15 22:32:19 +08:00
{ "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", },
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", },
//{"deploy", "d", "", "deploy", },
2024-03-15 22:32:19 +08:00
}
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 ( )
2024-03-16 22:54:11 +08:00
_ = runCommand ( "go" , "mod" , "tidy" )
_ = runCommandPipe ( logvPath , gowatchPath , "-pt" , ".go,.yml,.js" , "run" , "." )
//_ = runCommandPipe(logvPath, gowatchPath, "-pt", ".go,.yml,.js", "-ig", "api", "run", ".")
2024-03-15 22:32:19 +08:00
}
func testProject ( args [ ] string ) {
gowatchPath , logvPath := findTool ( )
if u . FileExists ( "tests" ) {
_ = os . Chdir ( "tests" )
2024-03-16 22:54:11 +08:00
_ = runCommand ( "go" , "mod" , "tidy" )
_ = runCommandPipe ( logvPath , gowatchPath , "-p" , ".,.." , "-pt" , ".go,.yml,.js" , "test" , "-v" , "." )
2024-03-15 22:32:19 +08:00
} else {
2024-03-16 22:54:11 +08:00
_ = runCommandPipe ( logvPath , gowatchPath , "-pt" , ".go,.yml,.js" , "test" , "-v" , "." )
2024-03-15 22:32:19 +08:00
}
}
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 ) {
2024-03-16 22:54:11 +08:00
if m [ 1 ] != "current-plugin" {
* imports = u . AppendUniqueString ( * imports , m [ 1 ] )
}
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._\-/\\]+) ` )
2024-03-15 22:32:19 +08:00
func makePluginCode ( args [ ] string ) {
2024-03-16 22:54:11 +08:00
// 判断是否可执行项目(不创建指向项目本身的 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 _)
2024-03-15 22:32:19 +08:00
imports := make ( [ ] string , 0 )
makePluginCodeImports ( "." , & imports )
2024-03-16 22:54:11 +08:00
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
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 )
}
_ = 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 ( ) ) )
2024-03-15 22:32:19 +08:00
}
2024-03-16 22:54:11 +08:00
_ = os . Chdir ( ".." )
_ = os . RemoveAll ( "_makePluginCode" )
2024-03-15 22:32:19 +08:00
}
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 ( )
}