├── README.md ├── bin └── .gitignore ├── commands ├── cmds.go ├── new.go └── version.go ├── go.mod ├── go.sum ├── logic ├── filesystem.go └── replace.go └── main.go /README.md: -------------------------------------------------------------------------------- 1 | > OpenMix 出品:[https://openmix.org](https://openmix.org/mix-go) 2 | 3 | ## Mix CLI 4 | 5 | 一个快速创建 Go 项目的脚手架 6 | 7 | A scaffold to quickly create a go project 8 | 9 | ## Installation 10 | 11 | ``` 12 | go get github.com/mix-go/mixcli 13 | ``` 14 | 15 | ## New Project 16 | 17 | 创建项目 18 | 19 | - 可以生成 `cli`, `api`, `web`, `grpc` 多种项目代码,生成的代码开箱即用 20 | - 可选择是否需要 `.env` 环境配置 21 | - 可选择使用 `viper`, `configor` 加载 `.yml`, `.json`, `.toml` 等独立配置 22 | - 可选择使用 `gorm`, `xorm` 的数据库 23 | - 可选择使用 `zap`, `logrus` 的日志库 24 | 25 | ~~~ 26 | $ mixcli new hello 27 | Use the arrow keys to navigate: ↓ ↑ → ← 28 | ? Select project type: 29 | ▸ CLI 30 | API 31 | Web (contains the websocket) 32 | gRPC 33 | ~~~ 34 | 35 | ## License 36 | 37 | Apache License Version 2.0, http://www.apache.org/licenses/ 38 | -------------------------------------------------------------------------------- /bin/.gitignore: -------------------------------------------------------------------------------- 1 | .gitignore 2 | !.gitignore -------------------------------------------------------------------------------- /commands/cmds.go: -------------------------------------------------------------------------------- 1 | package commands 2 | 3 | import "github.com/mix-go/xcli" 4 | 5 | var Cmds = []*xcli.Command{ 6 | { 7 | Name: "new", 8 | Short: "\tCreate a project", 9 | RunI: &NewCommand{}, 10 | }, 11 | } 12 | -------------------------------------------------------------------------------- /commands/new.go: -------------------------------------------------------------------------------- 1 | package commands 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "github.com/cheggaaa/pb/v3" 7 | "github.com/manifoldco/promptui" 8 | "github.com/mix-go/mixcli/logic" 9 | "github.com/mix-go/xcli/flag" 10 | "os" 11 | "os/exec" 12 | "runtime" 13 | "strings" 14 | "time" 15 | ) 16 | 17 | var ( 18 | None = "none" 19 | CLI = "cli" 20 | API = "api" 21 | Web = "web" 22 | gRPC = "grpc" 23 | Gorm = "gorm" 24 | Xorm = "xorm" 25 | Zap = "zap" 26 | Logrus = "logrus" 27 | GoRedis = "go-redis" 28 | DotEnv = "dotenv" 29 | Configor = "configor" 30 | Viper = "viper" 31 | ) 32 | 33 | type NewCommand struct { 34 | } 35 | 36 | func (t *NewCommand) Main() { 37 | name := flag.Arguments().First().String("hello") 38 | name = strings.ReplaceAll(name, " ", "") 39 | 40 | promp := func(label string, items []string) string { 41 | prompt := promptui.Select{ 42 | Label: label, 43 | Items: items, 44 | } 45 | prompt.HideSelected = true 46 | _, result, err := prompt.Run() 47 | if err != nil { 48 | return "" 49 | } 50 | return result 51 | } 52 | 53 | var selectType string 54 | switch promp("Select project type", []string{"CLI", "API", "Web (contains the websocket)", "gRPC"}) { 55 | case "CLI": 56 | selectType = CLI 57 | break 58 | case "API": 59 | selectType = API 60 | break 61 | case "Web (contains the websocket)": 62 | selectType = Web 63 | break 64 | case "gRPC": 65 | selectType = gRPC 66 | break 67 | default: 68 | return 69 | } 70 | 71 | selectEnv := promp("Select .env configuration file library", []string{DotEnv, None}) 72 | selectConf := promp("Select .yml, .json, .toml configuration files library", []string{Viper, Configor, None}) 73 | 74 | var selectLog string 75 | var selectLogItems []string 76 | if selectType == CLI { 77 | selectLogItems = []string{Zap, Logrus, None} 78 | } else { 79 | selectLogItems = []string{Zap, Logrus} 80 | } 81 | selectLog = promp("Select logger library", selectLogItems) 82 | 83 | var selectDb string 84 | var selectDbItems []string 85 | if selectType == API || selectType == Web { 86 | selectDbItems = []string{Gorm, Xorm} 87 | } else { 88 | selectDbItems = []string{Gorm, Xorm, None} 89 | } 90 | selectDb = promp("Select database library", selectDbItems) 91 | 92 | var selectRedis string 93 | selectRedis = promp("Select redis library", []string{GoRedis, None}) 94 | 95 | t.NewProject(name, selectType, selectEnv, selectConf, selectLog, selectDb, selectRedis) 96 | } 97 | 98 | func (t *NewCommand) NewProject(name, selectType, selectEnv, selectConf, selectLog, selectDb, selectRedis string) { 99 | ver := "" 100 | switch selectType { 101 | case CLI, API, Web, gRPC: 102 | ver = fmt.Sprintf("v%s", SkeletonVersion) 103 | break 104 | default: 105 | fmt.Println("Type error, only be console, api, web, grpc") 106 | return 107 | } 108 | 109 | envCmd := "go env GOPATH" 110 | cmd := exec.Command("go", "env", "GOPATH") 111 | out, err := cmd.CombinedOutput() 112 | if err != nil { 113 | fmt.Printf("Exec error: %v\n", err) 114 | return 115 | } 116 | goPath := string(out[:len(out)-1]) 117 | if goPath == "" { 118 | fmt.Printf("$GOPATH is not configured, see '%s'\n", envCmd) 119 | return 120 | } 121 | 122 | dr := ":" 123 | if runtime.GOOS == "windows" { 124 | dr = ";" 125 | } 126 | if strings.Contains(goPath, dr) { 127 | fmt.Printf("$GOPATH cannot have multiple directories, see '%s'\n", envCmd) 128 | return 129 | } 130 | 131 | srcDir := fmt.Sprintf("%s/pkg/mod/github.com/mix-go/%s-skeleton@%s", goPath, selectType, ver) 132 | if _, err := os.Stat(srcDir); err != nil { 133 | fmt.Printf("No skeleton found 'github.com/mix-go/%s-skeleton@%s'\n", selectType, ver) 134 | 135 | installFunc := func(installCmd string) error { 136 | cmd := exec.Command("go", installCmd, fmt.Sprintf("github.com/mix-go/%s-skeleton@%s", selectType, ver)) 137 | fmt.Printf("Install skeleton 'go %s github.com/mix-go/%s-skeleton@%s'\n", installCmd, selectType, ver) 138 | total := 0 139 | switch selectType { 140 | case CLI: 141 | total = 7695 142 | case API: 143 | total = 13834 144 | case Web: 145 | total = 17705 146 | case gRPC: 147 | total = 15659 148 | } 149 | current := int64(0) 150 | bar := pb.StartNew(total) 151 | go func() { 152 | path := fmt.Sprintf("%s/pkg/mod/cache/download/github.com/mix-go/%s-skeleton/@v/%s.zip", goPath, selectType, ver) 153 | for { 154 | f, err := os.Open(path) 155 | if err != nil { 156 | continue 157 | } 158 | fi, err := f.Stat() 159 | if err != nil { 160 | _ = f.Close() 161 | continue 162 | } 163 | current = fi.Size() 164 | bar.SetCurrent(current) 165 | _ = f.Close() 166 | time.Sleep(time.Millisecond * 100) 167 | } 168 | }() 169 | err = cmd.Run() 170 | if err == nil { 171 | bar.SetTotal(current) 172 | bar.SetCurrent(current) 173 | } else { 174 | bar.SetTotal(0) 175 | bar.SetCurrent(0) 176 | } 177 | bar.Finish() 178 | return err 179 | } 180 | 181 | if err1 := installFunc("get"); err1 != nil { 182 | if err2 := installFunc("install"); err2 != nil { 183 | fmt.Println(fmt.Sprintf("Install failed: %s: %s", err1, err2)) 184 | return 185 | } 186 | } 187 | 188 | time.Sleep(2 * time.Second) // 等待一会,让 gomod 完成解压 189 | _ = os.Remove(fmt.Sprintf("%s/bin/%s-skeleton", goPath, selectType)) 190 | fmt.Println(fmt.Sprintf("Skeleton 'github.com/mix-go/%s-skeleton@%s' installed successfully", selectType, ver)) 191 | } else { 192 | fmt.Println(fmt.Sprintf("Local skeleton found 'github.com/mix-go/%s-skeleton@%s'", selectType, ver)) 193 | } 194 | 195 | fmt.Print(" - Generate code") 196 | pwd, err := os.Getwd() 197 | if err != nil { 198 | panic(err) 199 | } 200 | dest := fmt.Sprintf("%s/%s", pwd, name) 201 | if !logic.CopyPath(srcDir, dest) { 202 | panic(errors.New("copy dir failed")) 203 | } 204 | fmt.Println(" > ok") 205 | 206 | fmt.Print(" - Processing .env") 207 | if selectEnv == None { 208 | if err := logic.ReplaceMain(dest, fmt.Sprintf(`_ "github.com/mix-go/%s-skeleton/config/dotenv"`, selectType), ""); err != nil { 209 | panic(errors.New("replace failed")) 210 | } 211 | _ = os.RemoveAll(fmt.Sprintf("%s/config/dotenv", dest)) 212 | _ = os.RemoveAll(fmt.Sprintf("%s/.env", dest)) 213 | } 214 | fmt.Println(" > ok") 215 | 216 | fmt.Print(" - Processing conf") 217 | switch selectConf { 218 | case Configor: 219 | _ = os.RemoveAll(fmt.Sprintf("%s/config/viper", dest)) 220 | break 221 | case Viper: 222 | if err := logic.ReplaceMain(dest, fmt.Sprintf(`_ "github.com/mix-go/%s-skeleton/config/configor"`, selectType), fmt.Sprintf(`_ "github.com/mix-go/%s-skeleton/config/viper"`, selectType)); err != nil { 223 | panic(errors.New("replace failed")) 224 | } 225 | _ = os.RemoveAll(fmt.Sprintf("%s/config/configor", dest)) 226 | break 227 | case None: 228 | if err := logic.ReplaceMain(dest, fmt.Sprintf(`_ "github.com/mix-go/%s-skeleton/config/configor"`, selectType), ""); err != nil { 229 | panic(errors.New("replace failed")) 230 | } 231 | _ = os.RemoveAll(fmt.Sprintf("%s/config/viper", dest)) 232 | _ = os.RemoveAll(fmt.Sprintf("%s/config/configor", dest)) 233 | _ = os.RemoveAll(fmt.Sprintf("%s/config/main.go", dest)) 234 | _ = os.RemoveAll(fmt.Sprintf("%s/conf", dest)) 235 | } 236 | fmt.Println(" > ok") 237 | 238 | fmt.Print(" - Processing logger") 239 | switch selectLog { 240 | case Zap: 241 | if err := logic.ReplaceAll(dest, `logger := di.Logrus`, `logger := di.Zap`); err != nil { 242 | panic(errors.New("replace failed")) 243 | } 244 | if err := logic.ReplaceAll(dest, `Output: logger.Writer\(\)`, `Output: &di.ZapOutput{Logger: logger}`); err != nil { 245 | panic(errors.New("replace failed")) 246 | } 247 | _ = os.Remove(fmt.Sprintf("%s/di/logrus.go", dest)) 248 | break 249 | case Logrus: 250 | _ = os.Remove(fmt.Sprintf("%s/di/zap.go", dest)) 251 | break 252 | case None: 253 | _ = os.Remove(fmt.Sprintf("%s/di/logrus.go", dest)) 254 | _ = os.Remove(fmt.Sprintf("%s/di/zap.go", dest)) 255 | break 256 | } 257 | fmt.Println(" > ok") 258 | 259 | fmt.Print(" - Processing database") 260 | switch selectDb { 261 | case Gorm: 262 | _ = os.Remove(fmt.Sprintf("%s/di/xorm.go", dest)) 263 | break 264 | case Xorm: 265 | _ = os.Remove(fmt.Sprintf("%s/di/gorm.go", dest)) 266 | break 267 | case None: 268 | _ = os.Remove(fmt.Sprintf("%s/di/gorm.go", dest)) 269 | _ = os.Remove(fmt.Sprintf("%s/di/xorm.go", dest)) 270 | break 271 | } 272 | fmt.Println(" > ok") 273 | 274 | fmt.Print(" - Processing redis") 275 | switch selectRedis { 276 | case GoRedis: 277 | break 278 | case None: 279 | _ = os.Remove(fmt.Sprintf("%s/di/goredis.go", dest)) 280 | break 281 | } 282 | fmt.Println(" > ok") 283 | 284 | // 都没有选 285 | if selectLog == None && selectDb == None && selectRedis == None { 286 | if err := logic.ReplaceMain(dest, fmt.Sprintf(`_ "github.com/mix-go/%s-skeleton/di"`, selectType), ""); err != nil { 287 | panic(errors.New("replace failed")) 288 | } 289 | } 290 | 291 | fmt.Print(" - Processing package name") 292 | if err := logic.ReplaceAll(dest, fmt.Sprintf("github.com/mix-go/%s-skeleton", selectType), name); err != nil { 293 | panic(errors.New("replace failed")) 294 | } 295 | if err := logic.ReplaceMod(dest); err != nil { 296 | panic(errors.New("replace go.mod failed")) 297 | } 298 | fmt.Println(" > ok") 299 | 300 | fmt.Println(fmt.Sprintf("Project '%s' is generated", name)) 301 | } 302 | -------------------------------------------------------------------------------- /commands/version.go: -------------------------------------------------------------------------------- 1 | package commands 2 | 3 | import ( 4 | "strings" 5 | ) 6 | 7 | var ( 8 | SkeletonVersion = "1.1.25" 9 | CLIVersion = "1.1.25" 10 | ) 11 | 12 | var ( 13 | VersionBig = 1 14 | VersionSmall = 2 15 | VersionEqual = 0 16 | ) 17 | 18 | func VersionCompare(verA, verB string) int { 19 | verStrArrA := splitStrByNet(verA) 20 | verStrArrB := splitStrByNet(verB) 21 | lenStrA := len(verStrArrA) 22 | lenStrB := len(verStrArrB) 23 | if lenStrA != lenStrB { 24 | panic("version length is inconsistent") 25 | } 26 | return compareArrStrVers(verStrArrA, verStrArrB) 27 | } 28 | 29 | func compareArrStrVers(verA, verB []string) int { 30 | for index, _ := range verA { 31 | littleResult := compareLittleVer(verA[index], verB[index]) 32 | if littleResult != VersionEqual { 33 | return littleResult 34 | } 35 | } 36 | return VersionEqual 37 | } 38 | 39 | func compareLittleVer(verA, verB string) int { 40 | bytesA := []byte(verA) 41 | bytesB := []byte(verB) 42 | lenA := len(bytesA) 43 | lenB := len(bytesB) 44 | if lenA > lenB { 45 | return VersionBig 46 | } 47 | if lenA < lenB { 48 | return VersionSmall 49 | } 50 | return compareByBytes(bytesA, bytesB) 51 | } 52 | 53 | func compareByBytes(verA, verB []byte) int { 54 | for index, _ := range verA { 55 | if verA[index] > verB[index] { 56 | return VersionBig 57 | } 58 | if verA[index] < verB[index] { 59 | return VersionSmall 60 | } 61 | 62 | } 63 | return VersionEqual 64 | } 65 | 66 | func splitStrByNet(strV string) []string { 67 | return strings.Split(strV, ".") 68 | } 69 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/mix-go/mixcli 2 | 3 | go 1.13 4 | 5 | require ( 6 | github.com/cheggaaa/pb/v3 v3.0.8 7 | github.com/manifoldco/promptui v0.8.0 8 | github.com/mix-go/dotenv v1.1.15 9 | github.com/mix-go/xcli v1.1.20 10 | ) 11 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/VividCortex/ewma v1.1.1 h1:MnEK4VOv6n0RSY4vtRe3h11qjxL3+t0B8yOL8iMXdcM= 2 | github.com/VividCortex/ewma v1.1.1/go.mod h1:2Tkkvm3sRDVXaiyucHiACn4cqf7DpdyLvmxzcbUokwA= 3 | github.com/cheggaaa/pb/v3 v3.0.8 h1:bC8oemdChbke2FHIIGy9mn4DPJ2caZYQnfbRqwmdCoA= 4 | github.com/cheggaaa/pb/v3 v3.0.8/go.mod h1:UICbiLec/XO6Hw6k+BHEtHeQFzzBH4i2/qk/ow1EJTA= 5 | github.com/chzyer/logex v1.1.10 h1:Swpa1K6QvQznwJRcfTfQJmTE72DqScAa40E+fbHEXEE= 6 | github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= 7 | github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e h1:fY5BOSpyZCqRo5OhCuC+XN+r/bBCmeuuJtjz+bCNIf8= 8 | github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= 9 | github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1 h1:q763qf9huN11kDQavWsoZXJNW3xEE4JJyHa5Q25/sd8= 10 | github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= 11 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 12 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 13 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 14 | github.com/fatih/color v1.10.0 h1:s36xzo75JdqLaaWoiEHk767eHiwo0598uUxyfiPkDsg= 15 | github.com/fatih/color v1.10.0/go.mod h1:ELkj/draVOlAH/xkhN6mQ50Qd0MPOk5AAr3maGEBuJM= 16 | github.com/joho/godotenv v1.3.0 h1:Zjp+RcGpHhGlrMbJzXTrZZPrWj+1vfm90La1wgB6Bhc= 17 | github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg= 18 | github.com/juju/ansiterm v0.0.0-20180109212912-720a0952cc2a h1:FaWFmfWdAUKbSCtOU2QjDaorUexogfaMgbipgYATUMU= 19 | github.com/juju/ansiterm v0.0.0-20180109212912-720a0952cc2a/go.mod h1:UJSiEoRfvx3hP73CvoARgeLjaIOjybY9vj8PUPPFGeU= 20 | github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= 21 | github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= 22 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= 23 | github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= 24 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= 25 | github.com/lunixbochs/vtclean v0.0.0-20180621232353-2d01aacdc34a h1:weJVJJRzAJBFRlAiJQROKQs8oC9vOxvm4rZmBBk0ONw= 26 | github.com/lunixbochs/vtclean v0.0.0-20180621232353-2d01aacdc34a/go.mod h1:pHhQNgMf3btfWnGBVipUOjRYhoOsdGqdm/+2c2E2WMI= 27 | github.com/manifoldco/promptui v0.8.0 h1:R95mMF+McvXZQ7j1g8ucVZE1gLP3Sv6j9vlF9kyRqQo= 28 | github.com/manifoldco/promptui v0.8.0/go.mod h1:n4zTdgP0vr0S3w7/O/g98U+e0gwLScEXGwov2nIKuGQ= 29 | github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= 30 | github.com/mattn/go-colorable v0.1.8 h1:c1ghPdyEDarC70ftn0y+A/Ee++9zz8ljHG1b13eJ0s8= 31 | github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= 32 | github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= 33 | github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY= 34 | github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= 35 | github.com/mattn/go-runewidth v0.0.12 h1:Y41i/hVW3Pgwr8gV+J23B9YEY0zxjptBuCWEaxmAOow= 36 | github.com/mattn/go-runewidth v0.0.12/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk= 37 | github.com/mix-go/dotenv v1.1.15 h1:Gw32rQ1ZDFhZdXGmpR/uBqflfqNxOX4A8rskq26K7Ok= 38 | github.com/mix-go/dotenv v1.1.15/go.mod h1:BfPSwcIjd8ozVK3q1+Q5AVAnhZej8cZe02V/T8ZHIzA= 39 | github.com/mix-go/xcli v1.1.20 h1:VL4AGjJokTQdMtB9MZjEYdSq0Ocrw6LSNEcdicxdB60= 40 | github.com/mix-go/xcli v1.1.20/go.mod h1:33kHro/CBkPMDCfqpm0Uc327GMInI3PFdFBb835kC3Q= 41 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 42 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 43 | github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= 44 | github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY= 45 | github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= 46 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 47 | github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= 48 | github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 49 | golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 50 | golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 51 | golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 52 | golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57 h1:F5Gozwx4I1xtr/sr/8CFbb57iKi3297KFs0QDbGN60A= 53 | golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 54 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 55 | gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= 56 | gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 57 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 58 | gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo= 59 | gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 60 | -------------------------------------------------------------------------------- /logic/filesystem.go: -------------------------------------------------------------------------------- 1 | package logic 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "github.com/mix-go/xcli" 7 | "io" 8 | "io/ioutil" 9 | "os" 10 | "path/filepath" 11 | "strings" 12 | ) 13 | 14 | func CopyPath(src, dst string) bool { 15 | debug := xcli.App().Debug 16 | if debug { 17 | fmt.Println("") 18 | } 19 | 20 | src = strings.Replace(src, "\\", "/", -1) 21 | srcFileInfo := GetFileInfo(src) 22 | if srcFileInfo == nil || !srcFileInfo.IsDir() { 23 | return false 24 | } 25 | 26 | err := filepath.Walk(src, func(path string, info os.FileInfo, err error) error { 27 | if err != nil { 28 | return err 29 | } 30 | 31 | path = strings.Replace(path, "\\", "/", -1) 32 | relationPath := strings.Replace(path, src, "", -1) 33 | dstPath := strings.TrimRight(strings.TrimRight(strings.Replace(dst, "\\", "/", -1), "/"), "\\") + relationPath 34 | 35 | if debug { 36 | fmt.Println(fmt.Sprintf(" Copy %s to %s", path, dstPath)) 37 | } 38 | 39 | if !info.IsDir() { 40 | if CopyFile(path, dstPath) { 41 | return nil 42 | } else { 43 | return errors.New(path + " copy fail") 44 | } 45 | } else { 46 | if _, err := os.Stat(dstPath); err != nil { 47 | if os.IsNotExist(err) { 48 | if err := os.MkdirAll(dstPath, os.ModePerm); err != nil { 49 | return err 50 | } else { 51 | return nil 52 | } 53 | } else { 54 | return err 55 | } 56 | } else { 57 | return nil 58 | } 59 | } 60 | }) 61 | 62 | if err != nil { 63 | return false 64 | } 65 | return true 66 | } 67 | 68 | func CopyFile(src, dst string) bool { 69 | if len(src) == 0 || len(dst) == 0 { 70 | return false 71 | } 72 | src = strings.Replace(src, "\\", "/", -1) 73 | srcFile, err := os.OpenFile(src, os.O_RDONLY, os.ModePerm) 74 | if err != nil { 75 | return false 76 | } 77 | defer srcFile.Close() 78 | 79 | dst = strings.Replace(dst, "\\", "/", -1) 80 | dstPathArr := strings.Split(dst, "/") 81 | dstPathArr = dstPathArr[0 : len(dstPathArr)-1] 82 | dstPath := strings.Join(dstPathArr, "/") 83 | 84 | dstFileInfo := GetFileInfo(dstPath) 85 | if dstFileInfo == nil { 86 | if err := os.MkdirAll(dstPath, os.ModePerm); err != nil { 87 | return false 88 | } 89 | } 90 | 91 | //这里要把O_TRUNC 加上,否则会出现新旧文件内容出现重叠现象 92 | dstFile, err := os.OpenFile(dst, os.O_CREATE|os.O_TRUNC|os.O_RDWR, os.ModePerm) 93 | if err != nil { 94 | return false 95 | } 96 | defer dstFile.Close() 97 | 98 | if _, err := io.Copy(dstFile, srcFile); err != nil { 99 | return false 100 | } else { 101 | return true 102 | } 103 | } 104 | 105 | func GetFileInfo(src string) os.FileInfo { 106 | if fileInfo, e := os.Stat(src); e != nil { 107 | if os.IsNotExist(e) { 108 | return nil 109 | } 110 | return nil 111 | } else { 112 | return fileInfo 113 | } 114 | } 115 | 116 | func ReadAll(filePth string) ([]byte, error) { 117 | f, err := os.Open(filePth) 118 | if err != nil { 119 | return nil, err 120 | } 121 | return ioutil.ReadAll(f) 122 | } 123 | 124 | // os.O_TRUNC 覆盖写入,不加则追加写入 125 | func WriteToFile(fileName string, content string) error { 126 | f, err := os.OpenFile(fileName, os.O_WRONLY|os.O_TRUNC|os.O_CREATE, 0644) 127 | if err != nil { 128 | return err 129 | } else { 130 | // offset 131 | //os.Truncate(filename, 0) //clear 132 | n, _ := f.Seek(0, io.SeekEnd) 133 | _, err = f.WriteAt([]byte(content), n) 134 | defer f.Close() 135 | } 136 | return err 137 | } 138 | -------------------------------------------------------------------------------- /logic/replace.go: -------------------------------------------------------------------------------- 1 | package logic 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | "path/filepath" 7 | "regexp" 8 | ) 9 | 10 | func ReplaceAll(root, old, new string) error { 11 | err := filepath.Walk(root, func(path string, info os.FileInfo, err error) error { 12 | if err != nil { 13 | return err 14 | } 15 | 16 | if !info.IsDir() { 17 | // 替换内容 18 | text, err := ReadAll(path) 19 | if err != nil { 20 | return err 21 | } 22 | str := string(text) 23 | reg := regexp.MustCompile(old) 24 | str = reg.ReplaceAllString(str, new) 25 | if err := WriteToFile(path, str); err != nil { 26 | return err 27 | } 28 | } 29 | 30 | return err 31 | }) 32 | return err 33 | } 34 | 35 | func ReplaceMod(root string) error { 36 | path := fmt.Sprintf("%s/go.mod", root) 37 | text, err := ReadAll(path) 38 | if err != nil { 39 | return err 40 | } 41 | str := string(text) 42 | reg := regexp.MustCompile(`(replace \([\s\S]*?\))`) 43 | str = reg.ReplaceAllString(str, "") 44 | if err := WriteToFile(path, str); err != nil { 45 | return err 46 | } 47 | return nil 48 | } 49 | 50 | func ReplaceMain(root, old, new string) error { 51 | path := fmt.Sprintf("%s/main.go", root) 52 | text, err := ReadAll(path) 53 | if err != nil { 54 | return err 55 | } 56 | str := string(text) 57 | reg := regexp.MustCompile(old) 58 | str = reg.ReplaceAllString(str, new) 59 | if err := WriteToFile(path, str); err != nil { 60 | return err 61 | } 62 | return nil 63 | } 64 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/mix-go/xcli" 5 | "github.com/mix-go/dotenv" 6 | "github.com/mix-go/mixcli/commands" 7 | ) 8 | 9 | func main() { 10 | xcli.SetName("mixcli"). 11 | SetVersion(commands.CLIVersion). 12 | SetDebug(dotenv.Getenv("APP_DEBUG").Bool(false)) 13 | xcli.AddCommand(commands.Cmds...).Run() 14 | } 15 | --------------------------------------------------------------------------------