├── .gitattributes ├── .github └── workflows │ └── go.yml ├── .gitignore ├── LICENSE ├── Makefile ├── README.MD ├── gf └── main.go ├── go.mod ├── go.sum ├── internal ├── cmd │ ├── cmd.go │ ├── cmd_build.go │ ├── cmd_docker.go │ ├── cmd_env.go │ ├── cmd_gen.go │ ├── cmd_gen_dao.go │ ├── cmd_gen_pb.go │ ├── cmd_gen_pbentity.go │ ├── cmd_init.go │ ├── cmd_install.go │ ├── cmd_pack.go │ ├── cmd_run.go │ ├── cmd_tpl.go │ └── cmd_version.go ├── consts │ ├── consts.go │ ├── consts_gen_dao_template_dao.go │ ├── consts_gen_dao_template_do.go │ ├── consts_gen_dao_template_entity.go │ └── consts_gen_pbentity_template.go ├── packed │ ├── packed.go │ ├── template-mono.go │ └── template-single.go └── service │ └── install.go ├── test └── testdata │ ├── tpls │ ├── tpl1.yaml │ └── tpl2.sql │ └── values.json └── utility ├── allyes └── allyes.go ├── mlog └── mlog.go └── utils └── utils.go /.gitattributes: -------------------------------------------------------------------------------- 1 | *.js linguist-language=GO 2 | *.css linguist-language=GO 3 | *.html linguist-language=GO -------------------------------------------------------------------------------- /.github/workflows/go.yml: -------------------------------------------------------------------------------- 1 | name: GoFrame CLI CI 2 | 3 | on: 4 | push: 5 | # Sequence of patterns matched against refs/tags 6 | tags: 7 | - 'v*' # Push events to matching v*, i.e. v1.0, v20.15.10 8 | 9 | jobs: 10 | build: 11 | name: Build And Release 12 | runs-on: ubuntu-latest 13 | steps: 14 | - name: Checkout Github Code 15 | uses: actions/checkout@v2 16 | 17 | - name: Set Up Golang Environment 18 | uses: actions/setup-go@v2 19 | with: 20 | go-version: 1.17 21 | 22 | - name: Build CLI Binary 23 | run: | 24 | echo "Building linux amd64 binary..." 25 | GOOS=linux GOARCH=amd64 go build gf/main.go 26 | chmod +x main 27 | ./main install -y 28 | 29 | # gf build 30 | - name: Build CLI Binary For All Platform 31 | run: | 32 | gf build gf/main.go -n gf -a all -s all 33 | 34 | # 处理gf-cli批量编译后的文件结构 35 | - name: Move Files Before Upx 36 | run: | 37 | cd bin 38 | for OS in *;do for FILE in $OS/*;\ 39 | do if [[ ${OS} =~ 'windows' ]];\ 40 | then rm -rf noupx && mkdir noupx && mv $FILE noupx/gf_$OS.exe && rm -rf $OS;\ 41 | else mv $FILE gf_$OS && rm -rf $OS;\ 42 | fi;done;done 43 | 44 | # UPX 加壳所有文件 45 | - name: Upx All Binary 46 | uses: gacts/upx@master 47 | with: 48 | dir: './bin' 49 | upx_args: '-9' 50 | 51 | # 移动未UPX的windows程序到上传bin目录下 52 | - name: Move Files After Upx 53 | run: | 54 | cd bin 55 | mv noupx/* ./ && rm -rf noupx 56 | ls -l 57 | 58 | - name: Create Github Release 59 | id: create_release 60 | uses: actions/create-release@v1 61 | env: 62 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 63 | with: 64 | tag_name: ${{ github.ref }} 65 | release_name: Release ${{ github.ref }} 66 | draft: false 67 | prerelease: false 68 | 69 | - name: Upload Release Asset 70 | id: upload-release-asset 71 | uses: alexellis/upload-assets@0.2.2 72 | env: 73 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 74 | with: 75 | asset_paths: '["./bin/gf_*"]' 76 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .buildpath 3 | .hgignore.swp 4 | .project 5 | .orig 6 | .swp 7 | .idea/ 8 | .settings/ 9 | .vscode/ 10 | vender/ 11 | log/ 12 | composer.lock 13 | gitpush.sh 14 | pkg/ 15 | bin/ 16 | cbuild 17 | */.DS_Store 18 | config/config.toml 19 | main 20 | .vscode 21 | buildgf.bat 22 | *.exe 23 | *.exe~ 24 | app/ 25 | gf-cli 26 | .example/ 27 | config.yaml 28 | temp/ -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 john@goframe.org https://goframe.org 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | 2 | pack: pack.template-single pack.template-mono 3 | 4 | pack.template-single: 5 | @rm -fr temp 6 | @mkdir temp || exit 0 7 | @cd temp && git clone https://github.com/gogf/template-single 8 | @rm -fr temp/template-single/.git 9 | @cd temp && gf pack template-single ../internal/packed/template-single.go -n=packed -y 10 | @rm -fr temp 11 | 12 | pack.template-mono: 13 | @rm -fr temp 14 | @mkdir temp || exit 0 15 | @cd temp && git clone https://github.com/gogf/template-mono 16 | @rm -fr temp/template-mono/.git 17 | @cd temp && gf pack template-mono ../internal/packed/template-mono.go -n=packed -y 18 | @rm -fr temp -------------------------------------------------------------------------------- /README.MD: -------------------------------------------------------------------------------- 1 | > 👣👣👣👣👣👣👣👣👣👣👣👣👣👣👣👣👣👣👣👣👣👣👣👣👣👣👣👣👣👣👣👣👣👣👣👣 2 | > 3 | > This repo. is archived and moved to https://github.com/gogf/gf/tree/master/cmd/gf for better maintenance. 4 | > 5 | > 👣👣👣👣👣👣👣👣👣👣👣👣👣👣👣👣👣👣👣👣👣👣👣👣👣👣👣👣👣👣👣👣👣👣👣👣 6 | 7 | 8 | # GoFrame CLI TOOL 9 | 10 | 11 | 12 | `gf` is a powerful CLI tool for building [GoFrame](https://goframe.org) application with convenience. 13 | 14 | ## 1. Install 15 | 16 | ### 1). Manually Install 17 | 18 | > You might need setting the goproxy to make through building. 19 | > Please make sure your Golang version > v1.15. 20 | 21 | 1. Latest version 22 | ``` 23 | go install github.com/gogf/gf-cli/v2/gf@master 24 | ``` 25 | 26 | 2. Specified version 27 | ``` 28 | go install github.com/gogf/gf-cli/v2/gf@v2.0.0-beta 29 | ``` 30 | 31 | 3. Check installation 32 | ``` 33 | gf -v 34 | ``` 35 | ### 2). PreBuilt Binary 36 | 37 | You can also install `gf` tool using pre-built binaries: https://github.com/gogf/gf-cli/releases 38 | 39 | After downloads, please use `gf_xxx_xxx install` command to install gf binary to system binary path. 40 | 41 | 1. `Mac` 42 | ```shell 43 | wget -O gf https://github.com/gogf/gf-cli/releases/download/v2.0.0-rc/gf_darwin_amd64 && chmod +x gf && ./gf install 44 | ``` 45 | > If you're using `zsh`, you might need rename your alias by command `alias gf=gf` to resolve the conflicts between `gf` and `git fetch`. 46 | 47 | 2. `Linux` 48 | ```shell 49 | wget -O gf https://github.com/gogf/gf-cli/releases/download/v2.0.0-rc/gf_linux_amd64 && chmod +x gf && ./gf install 50 | ``` 51 | 52 | 3. `Windows` 53 | 54 | Manually download, execute it and then follow the instruction. 55 | 56 | 4. Database `sqlite` and `oracle` are not support in `gf gen` command in default as it needs `cgo` and `gcc`, you can manually make some changes to the source codes and do the building. 57 | 58 | ## 2. Commands 59 | ```html 60 | $ gf 61 | USAGE 62 | gf COMMAND [OPTION] 63 | 64 | COMMAND 65 | env show current Golang environment variables 66 | run running go codes with hot-compiled-like feature 67 | gen automatically generate go files for dao/do/entity/pb/pbentity 68 | tpl template parsing and building commands 69 | init create and initialize an empty GoFrame project 70 | pack packing any file/directory to a resource file, or a go file 71 | build cross-building go project for lots of platforms 72 | docker build docker image for current GoFrame project 73 | install install gf binary to system (might need root/admin permission) 74 | version show version information of current binary 75 | 76 | OPTION 77 | -y, --yes all yes for all command without prompt ask 78 | -v, --version show version information of current binary 79 | -d, --debug show internal detailed debugging information 80 | -h, --help more information about this command 81 | 82 | ADDITIONAL 83 | Use "gf COMMAND -h" for details about a command. 84 | ``` 85 | 86 | ## 3. FAQ 87 | 88 | ### 1). Command `gf run` returns `pipe: too many open files` 89 | 90 | Please use `ulimit -n 65535` to enlarge your system configuration for max open files for current terminal shell session, and then `gf run`. 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | -------------------------------------------------------------------------------- /gf/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | _ "github.com/gogf/gf-cli/v2/internal/packed" 5 | 6 | "github.com/gogf/gf-cli/v2/internal/cmd" 7 | "github.com/gogf/gf-cli/v2/utility/allyes" 8 | "github.com/gogf/gf-cli/v2/utility/mlog" 9 | "github.com/gogf/gf/v2/os/gcmd" 10 | "github.com/gogf/gf/v2/os/gctx" 11 | "github.com/gogf/gf/v2/os/gfile" 12 | "github.com/gogf/gf/v2/text/gstr" 13 | ) 14 | 15 | func main() { 16 | defer func() { 17 | if exception := recover(); exception != nil { 18 | if err, ok := exception.(error); ok { 19 | mlog.Print(err.Error()) 20 | } else { 21 | panic(exception) 22 | } 23 | } 24 | }() 25 | 26 | // zsh alias "git fetch" conflicts checks. 27 | handleZshAlias() 28 | 29 | // -y option checks. 30 | allyes.Init() 31 | 32 | var ( 33 | ctx = gctx.New() 34 | ) 35 | command, err := gcmd.NewFromObject(cmd.GF) 36 | if err != nil { 37 | panic(err) 38 | } 39 | err = command.AddObject( 40 | cmd.Env, 41 | cmd.Run, 42 | cmd.Gen, 43 | cmd.Tpl, 44 | cmd.Init, 45 | cmd.Pack, 46 | cmd.Build, 47 | cmd.Docker, 48 | cmd.Install, 49 | cmd.Version, 50 | ) 51 | if err != nil { 52 | panic(err) 53 | } 54 | command.Run(ctx) 55 | } 56 | 57 | // zsh alias "git fetch" conflicts checks. 58 | func handleZshAlias() { 59 | if home, err := gfile.Home(); err == nil { 60 | zshPath := gfile.Join(home, ".zshrc") 61 | if gfile.Exists(zshPath) { 62 | var ( 63 | aliasCommand = `alias gf=gf` 64 | content = gfile.GetContents(zshPath) 65 | ) 66 | if !gstr.Contains(content, aliasCommand) { 67 | _ = gfile.PutContentsAppend(zshPath, "\n"+aliasCommand+"\n") 68 | } 69 | } 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/gogf/gf-cli/v2 2 | 3 | go 1.14 4 | 5 | require ( 6 | github.com/denisenkom/go-mssqldb v0.11.0 7 | github.com/gogf/gf/v2 v2.0.0-rc.0.20220124023219-43f1354e7983 8 | github.com/lib/pq v1.10.4 9 | github.com/mattn/go-runewidth v0.0.10 // indirect 10 | github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e // indirect 11 | github.com/olekukonko/tablewriter v0.0.5 12 | go.opentelemetry.io/otel v1.2.0 // indirect 13 | gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f // indirect 14 | ) 15 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/BurntSushi/toml v0.4.1 h1:GaI7EiDXDRfa8VshkTj7Fym7ha+y8/XxIgD2okUIjLw= 2 | github.com/BurntSushi/toml v0.4.1/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= 3 | github.com/cespare/xxhash/v2 v2.1.2 h1:YRXhKfTDauu4ajMg1TPgFO5jnlC2HCbmLXMcTG5cbYE= 4 | github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= 5 | github.com/clbanning/mxj/v2 v2.5.5 h1:oT81vUeEiQQ/DcHbzSytRngP6Ky9O+L+0Bw0zSJag9E= 6 | github.com/clbanning/mxj/v2 v2.5.5/go.mod h1:hNiWqW14h+kc+MdF9C6/YoRfjEJoR3ou6tn/Qo+ve2s= 7 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 8 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 9 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 10 | github.com/denisenkom/go-mssqldb v0.11.0 h1:9rHa233rhdOyrz2GcP9NM+gi2psgJZ4GWDpL/7ND8HI= 11 | github.com/denisenkom/go-mssqldb v0.11.0/go.mod h1:xbL0rPBG9cCiLr28tMa8zpbdarY27NDyej4t/EjAShU= 12 | github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78= 13 | github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc= 14 | github.com/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w= 15 | github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= 16 | github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= 17 | github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= 18 | github.com/fsnotify/fsnotify v1.5.1 h1:mZcQUHVQUQWoPXXtuf9yuEXKudkV2sx1E06UadKWpgI= 19 | github.com/fsnotify/fsnotify v1.5.1/go.mod h1:T3375wBYaZdLLcVNkcVbzGHY7f1l/uK5T5Ai1i3InKU= 20 | github.com/go-redis/redis/v8 v8.11.4 h1:kHoYkfZP6+pe04aFTnhDH6GDROa5yJdHJVNxV3F46Tg= 21 | github.com/go-redis/redis/v8 v8.11.4/go.mod h1:2Z2wHZXdQpCDXEGzqMockDpNyYvi2l4Pxt6RJr792+w= 22 | github.com/go-sql-driver/mysql v1.6.0 h1:BCTh4TKNUYmOmMUcQ3IipzF5prigylS7XXjEkfCHuOE= 23 | github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= 24 | github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= 25 | github.com/gogf/gf/v2 v2.0.0-rc.0.20220114091638-6468d55a8145 h1:AmsImsXOkY3KtrgIrdWdLQfU0xJSi5MkoAkZD1VJwpE= 26 | github.com/gogf/gf/v2 v2.0.0-rc.0.20220114091638-6468d55a8145/go.mod h1:apktt6TleWtCIwpz63vBqUnw8MX8gWKoZyxgDpXFtgM= 27 | github.com/gogf/gf/v2 v2.0.0-rc.0.20220117075545-c72a9f2e1ec1 h1:Hig4VD03j3oKFQBtXqgITka6o8NtsOl1ChPvifN3Vio= 28 | github.com/gogf/gf/v2 v2.0.0-rc.0.20220117075545-c72a9f2e1ec1/go.mod h1:apktt6TleWtCIwpz63vBqUnw8MX8gWKoZyxgDpXFtgM= 29 | github.com/gogf/gf/v2 v2.0.0-rc.0.20220117091044-56f88f759aee h1:iFClfbtTFxhpESnMjO3zLYtQX0w3Blp5pxMv9WiKCD0= 30 | github.com/gogf/gf/v2 v2.0.0-rc.0.20220117091044-56f88f759aee/go.mod h1:apktt6TleWtCIwpz63vBqUnw8MX8gWKoZyxgDpXFtgM= 31 | github.com/gogf/gf/v2 v2.0.0-rc.0.20220117131058-9345eb5e946f h1:pRSy/LMaK+dDMbzEdsTJwNB7Ms0CUd22soLAUIanbCE= 32 | github.com/gogf/gf/v2 v2.0.0-rc.0.20220117131058-9345eb5e946f/go.mod h1:apktt6TleWtCIwpz63vBqUnw8MX8gWKoZyxgDpXFtgM= 33 | github.com/gogf/gf/v2 v2.0.0-rc.0.20220119102639-3dcd9b535bab h1:PBx6O7V2F7cvFnt/8tVpFfilmzP48wXUXG3MhR05Two= 34 | github.com/gogf/gf/v2 v2.0.0-rc.0.20220119102639-3dcd9b535bab/go.mod h1:apktt6TleWtCIwpz63vBqUnw8MX8gWKoZyxgDpXFtgM= 35 | github.com/gogf/gf/v2 v2.0.0-rc.0.20220119122504-69935f3d1c6d h1:7ena3lGQv0xag4POghXkK2Kk1I114MDQYpRyei/4DK8= 36 | github.com/gogf/gf/v2 v2.0.0-rc.0.20220119122504-69935f3d1c6d/go.mod h1:apktt6TleWtCIwpz63vBqUnw8MX8gWKoZyxgDpXFtgM= 37 | github.com/gogf/gf/v2 v2.0.0-rc.0.20220119153523-7401fb09c984 h1:HvExNk4YNgaHTkp3V7osEP52YDCOPxQFlgnh1UDcibQ= 38 | github.com/gogf/gf/v2 v2.0.0-rc.0.20220119153523-7401fb09c984/go.mod h1:apktt6TleWtCIwpz63vBqUnw8MX8gWKoZyxgDpXFtgM= 39 | github.com/gogf/gf/v2 v2.0.0-rc.0.20220124023219-43f1354e7983 h1:INOUPZaXysuSYTEwhPgNDZfdmEPqIpAXwcgXlVEAzeQ= 40 | github.com/gogf/gf/v2 v2.0.0-rc.0.20220124023219-43f1354e7983/go.mod h1:apktt6TleWtCIwpz63vBqUnw8MX8gWKoZyxgDpXFtgM= 41 | github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe h1:lXe2qZdvpiX5WZkZR4hgp4KJVfY3nMkvmwbVkpv1rVY= 42 | github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0= 43 | github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 44 | github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= 45 | github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= 46 | github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= 47 | github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= 48 | github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= 49 | github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= 50 | github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= 51 | github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw= 52 | github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= 53 | github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= 54 | github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= 55 | github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 56 | github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 57 | github.com/google/go-cmp v0.5.6 h1:BKbKCqvP6I+rmFHt06ZmyQtvB8xAkWdhFyr0ZUNZcxQ= 58 | github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 59 | github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc= 60 | github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= 61 | github.com/grokify/html-strip-tags-go v0.0.1 h1:0fThFwLbW7P/kOiTBs03FsJSV9RM2M/Q/MOnCQxKMo0= 62 | github.com/grokify/html-strip-tags-go v0.0.1/go.mod h1:2Su6romC5/1VXOQMaWL2yb618ARB8iVo6/DR99A6d78= 63 | github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= 64 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= 65 | github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= 66 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= 67 | github.com/lib/pq v1.10.4 h1:SO9z7FRPzA03QhHKJrH5BXA6HU1rS4V2nIVrrNC1iYk= 68 | github.com/lib/pq v1.10.4/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= 69 | github.com/mattn/go-colorable v0.1.9 h1:sqDoxXbdeALODt0DAeJCVp38ps9ZogZEAXjus69YV3U= 70 | github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= 71 | github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= 72 | github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y= 73 | github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= 74 | github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= 75 | github.com/mattn/go-runewidth v0.0.10 h1:CoZ3S2P7pvtP45xOtBw+/mDL2z0RKI576gSkzRRpdGg= 76 | github.com/mattn/go-runewidth v0.0.10/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk= 77 | github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs= 78 | github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= 79 | github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= 80 | github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= 81 | github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= 82 | github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec= 83 | github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY= 84 | github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= 85 | github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= 86 | github.com/onsi/ginkgo v1.16.4 h1:29JGrr5oVBm5ulCWet69zQkzWipVXIol6ygQUe/EzNc= 87 | github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0= 88 | github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= 89 | github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= 90 | github.com/onsi/gomega v1.16.0 h1:6gjqkI8iiRHMvdccRJM8rVKjCWk6ZIm6FTm3ddIe4/c= 91 | github.com/onsi/gomega v1.16.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY= 92 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 93 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 94 | github.com/rivo/uniseg v0.1.0 h1:+2KBaVoUmb9XzDsrx/Ct0W/EYOSFf/nWTauy++DprtY= 95 | github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= 96 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 97 | github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= 98 | github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= 99 | github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 100 | github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= 101 | github.com/yuin/goldmark v1.4.0/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= 102 | go.opentelemetry.io/otel v1.0.0/go.mod h1:AjRVh9A5/5DE7S+mZtTR6t8vpKKryam+0lREnfmS4cg= 103 | go.opentelemetry.io/otel v1.2.0 h1:YOQDvxO1FayUcT9MIhJhgMyNO1WqoduiyvQHzGN0kUQ= 104 | go.opentelemetry.io/otel v1.2.0/go.mod h1:aT17Fk0Z1Nor9e0uisf98LrntPGMnk4frBO9+dkf69I= 105 | go.opentelemetry.io/otel/sdk v1.0.0 h1:BNPMYUONPNbLneMttKSjQhOTlFLOD9U22HNG1KrIN2Y= 106 | go.opentelemetry.io/otel/sdk v1.0.0/go.mod h1:PCrDHlSy5x1kjezSdL37PhbFUMjrsLRshJ2zCzeXwbM= 107 | go.opentelemetry.io/otel/trace v1.0.0/go.mod h1:PXTWqayeFUlJV1YDNhsJYB184+IvAH814St6o6ajzIs= 108 | go.opentelemetry.io/otel/trace v1.2.0 h1:Ys3iqbqZhcf28hHzrm5WAquMkDHNZTUkw7KHbuNjej0= 109 | go.opentelemetry.io/otel/trace v1.2.0/go.mod h1:N5FLswTubnxKxOJHM7XZC074qpeEdLy3CgAVsdMucK0= 110 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 111 | golang.org/x/crypto v0.0.0-20190325154230-a5d413f7728c/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 112 | golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 113 | golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 h1:psW17arqaxU48Z5kZ0CQnkZWQJsqcURM6tKiBApRjXI= 114 | golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= 115 | golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 116 | golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 117 | golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 118 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 119 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 120 | golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= 121 | golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= 122 | golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk= 123 | golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= 124 | golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2 h1:CIJ76btIcR3eFI5EgSo6k1qKw9KJexJuRLI9G7Hp5wE= 125 | golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= 126 | golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 127 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 128 | golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 129 | golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 130 | golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 131 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 132 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 133 | golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 134 | golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 135 | golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 136 | golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 137 | golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 138 | golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 139 | golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 140 | golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 141 | golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 142 | golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 143 | golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 144 | golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 145 | golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e h1:WUoyKPm6nCo1BnNUvPGnFG3T5DUVem42yDJZZ4CNxMA= 146 | golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 147 | golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= 148 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 149 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 150 | golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 151 | golang.org/x/text v0.3.8-0.20211105212822-18b340fc7af2 h1:GLw7MR8AfAG2GmGcmVgObFOHXYypgGjnGno25RDwn3Y= 152 | golang.org/x/text v0.3.8-0.20211105212822-18b340fc7af2/go.mod h1:EFNZuWvGYxIRUEX+K8UmCFwYmZjqcrnq15ZuVldZkZ0= 153 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 154 | golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 155 | golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= 156 | golang.org/x/tools v0.1.7/go.mod h1:LGqMHiF4EqQNHR1JncWGqT5BVaXmza+X+BDGol+dOxo= 157 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 158 | golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 159 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 160 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= 161 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 162 | google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= 163 | google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= 164 | google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= 165 | google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= 166 | google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= 167 | google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= 168 | google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= 169 | google.golang.org/protobuf v1.26.0 h1:bxAC2xTBsZGibn2RTntX0oH50xLsqy1OxA9tTL3p/lk= 170 | google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= 171 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 172 | gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU= 173 | gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 174 | gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= 175 | gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= 176 | gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= 177 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 178 | gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 179 | gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 180 | gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= 181 | gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= 182 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 183 | gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo= 184 | gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 185 | -------------------------------------------------------------------------------- /internal/cmd/cmd.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "context" 5 | "strings" 6 | 7 | "github.com/gogf/gf-cli/v2/internal/service" 8 | "github.com/gogf/gf-cli/v2/utility/mlog" 9 | "github.com/gogf/gf/v2/frame/g" 10 | "github.com/gogf/gf/v2/os/gcmd" 11 | "github.com/gogf/gf/v2/util/gtag" 12 | ) 13 | 14 | var ( 15 | GF = cGF{} 16 | ) 17 | 18 | type cGF struct { 19 | g.Meta `name:"gf" ad:"{cGFAd}"` 20 | } 21 | 22 | const ( 23 | cGFAd = ` 24 | ADDITIONAL 25 | Use "gf COMMAND -h" for details about a command. 26 | ` 27 | ) 28 | 29 | func init() { 30 | gtag.Sets(g.MapStrStr{ 31 | `cGFAd`: cGFAd, 32 | }) 33 | } 34 | 35 | type cGFInput struct { 36 | g.Meta `name:"gf"` 37 | Yes bool `short:"y" name:"yes" brief:"all yes for all command without prompt ask" orphan:"true"` 38 | Version bool `short:"v" name:"version" brief:"show version information of current binary" orphan:"true"` 39 | Debug bool `short:"d" name:"debug" brief:"show internal detailed debugging information" orphan:"true"` 40 | } 41 | type cGFOutput struct{} 42 | 43 | func (c cGF) Index(ctx context.Context, in cGFInput) (out *cGFOutput, err error) { 44 | // Version. 45 | if in.Version { 46 | _, err = Version.Index(ctx, cVersionInput{}) 47 | return 48 | } 49 | // No argument or option, do installation checks. 50 | if !service.Install.IsInstalled() { 51 | mlog.Print("hi, it seams it's the first time you installing gf cli.") 52 | s := gcmd.Scanf("do you want to install gf binary to your system? [y/n]: ") 53 | if strings.EqualFold(s, "y") { 54 | if err = service.Install.Run(ctx); err != nil { 55 | return 56 | } 57 | gcmd.Scan("press `Enter` to exit...") 58 | return 59 | } 60 | } 61 | // Print help content. 62 | gcmd.CommandFromCtx(ctx).Print() 63 | return 64 | } 65 | -------------------------------------------------------------------------------- /internal/cmd/cmd_build.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "context" 5 | "encoding/json" 6 | "fmt" 7 | "regexp" 8 | "runtime" 9 | "strings" 10 | 11 | "github.com/gogf/gf-cli/v2/utility/mlog" 12 | "github.com/gogf/gf/v2/encoding/gbase64" 13 | "github.com/gogf/gf/v2/frame/g" 14 | "github.com/gogf/gf/v2/os/gcmd" 15 | "github.com/gogf/gf/v2/os/genv" 16 | "github.com/gogf/gf/v2/os/gfile" 17 | "github.com/gogf/gf/v2/os/gproc" 18 | "github.com/gogf/gf/v2/os/gtime" 19 | "github.com/gogf/gf/v2/text/gregex" 20 | "github.com/gogf/gf/v2/text/gstr" 21 | "github.com/gogf/gf/v2/util/gtag" 22 | ) 23 | 24 | var ( 25 | Build = cBuild{ 26 | nodeNameInConfigFile: "gfcli.build", 27 | packedGoFileName: "build_pack_data.go", 28 | } 29 | ) 30 | 31 | type cBuild struct { 32 | g.Meta `name:"build" brief:"{cBuildBrief}" dc:"{cBuildDc}" eg:"{cBuildEg}" ad:"{cBuildAd}"` 33 | nodeNameInConfigFile string // nodeNameInConfigFile is the node name for compiler configurations in configuration file. 34 | packedGoFileName string // packedGoFileName specifies the file name for packing common folders into one single go file. 35 | } 36 | 37 | const ( 38 | cBuildBrief = `cross-building go project for lots of platforms` 39 | cBuildEg = ` 40 | gf build main.go 41 | gf build main.go --pack public,template 42 | gf build main.go --cgo 43 | gf build main.go -m none 44 | gf build main.go -n my-app -a all -s all 45 | gf build main.go -n my-app -a amd64,386 -s linux -p . 46 | gf build main.go -n my-app -v 1.0 -a amd64,386 -s linux,windows,darwin -p ./docker/bin 47 | ` 48 | cBuildDc = ` 49 | The "build" command is most commonly used command, which is designed as a powerful wrapper for 50 | "go build" command for convenience cross-compiling usage. 51 | It provides much more features for building binary: 52 | 1. Cross-Compiling for many platforms and architectures. 53 | 2. Configuration file support for compiling. 54 | 3. Build-In Variables. 55 | ` 56 | cBuildAd = ` 57 | PLATFORMS 58 | darwin amd64,arm64 59 | freebsd 386,amd64,arm 60 | linux 386,amd64,arm,arm64,ppc64,ppc64le,mips,mipsle,mips64,mips64le 61 | netbsd 386,amd64,arm 62 | openbsd 386,amd64,arm 63 | windows 386,amd64 64 | ` 65 | // https://golang.google.cn/doc/install/source 66 | cBuildPlatforms = ` 67 | darwin amd64 68 | darwin arm64 69 | ios amd64 70 | ios arm64 71 | freebsd 386 72 | freebsd amd64 73 | freebsd arm 74 | linux 386 75 | linux amd64 76 | linux arm 77 | linux arm64 78 | linux ppc64 79 | linux ppc64le 80 | linux mips 81 | linux mipsle 82 | linux mips64 83 | linux mips64le 84 | netbsd 386 85 | netbsd amd64 86 | netbsd arm 87 | openbsd 386 88 | openbsd amd64 89 | openbsd arm 90 | windows 386 91 | windows amd64 92 | android arm 93 | dragonfly amd64 94 | plan9 386 95 | plan9 amd64 96 | solaris amd64 97 | ` 98 | ) 99 | 100 | func init() { 101 | gtag.Sets(g.MapStrStr{ 102 | `cBuildBrief`: cBuildBrief, 103 | `cBuildDc`: cBuildDc, 104 | `cBuildEg`: cBuildEg, 105 | `cBuildAd`: cBuildAd, 106 | }) 107 | } 108 | 109 | type cBuildInput struct { 110 | g.Meta `name:"build" config:"gfcli.build"` 111 | File string `name:"FILE" arg:"true" brief:"building file path"` 112 | Name string `short:"n" name:"name" brief:"output binary name"` 113 | Version string `short:"v" name:"version" brief:"output binary version"` 114 | Arch string `short:"a" name:"arch" brief:"output binary architecture, multiple arch separated with ','"` 115 | System string `short:"s" name:"system" brief:"output binary system, multiple os separated with ','"` 116 | Output string `short:"o" name:"output" brief:"output binary path, used when building single binary file"` 117 | Path string `short:"p" name:"path" brief:"output binary directory path, default is './bin'" d:"./bin"` 118 | Extra string `short:"e" name:"extra" brief:"extra custom \"go build\" options"` 119 | Mod string `short:"m" name:"mod" brief:"like \"-mod\" option of \"go build\", use \"-m none\" to disable go module"` 120 | Cgo bool `short:"c" name:"cgo" brief:"enable or disable cgo feature, it's disabled in default" orphan:"true"` 121 | VarMap g.Map `short:"r" name:"varMap" brief:"custom built embedded variable into binary"` 122 | Pack string `name:"pack" brief:"pack specified folder into temporary go file before building and removes it after built"` 123 | } 124 | type cBuildOutput struct{} 125 | 126 | func (c cBuild) Index(ctx context.Context, in cBuildInput) (out *cBuildOutput, err error) { 127 | mlog.SetHeaderPrint(true) 128 | 129 | mlog.Debugf(`build input: %+v`, in) 130 | // Necessary check. 131 | if gproc.SearchBinary("go") == "" { 132 | mlog.Fatalf(`command "go" not found in your environment, please install golang first to proceed this command`) 133 | } 134 | 135 | var ( 136 | parser = gcmd.ParserFromCtx(ctx) 137 | file = parser.GetArg(2).String() 138 | ) 139 | if len(file) < 1 { 140 | // Check and use the main.go file. 141 | if gfile.Exists("main.go") { 142 | file = "main.go" 143 | } else { 144 | mlog.Fatal("build file path cannot be empty") 145 | } 146 | } 147 | if in.Name == "" { 148 | in.Name = gfile.Name(file) 149 | } 150 | if len(in.Name) < 1 || in.Name == "*" { 151 | mlog.Fatal("name cannot be empty") 152 | } 153 | if in.Mod != "" && in.Mod != "none" { 154 | mlog.Debugf(`mod is %s`, in.Mod) 155 | if in.Extra == "" { 156 | in.Extra = fmt.Sprintf(`-mod=%s`, in.Mod) 157 | } else { 158 | in.Extra = fmt.Sprintf(`-mod=%s %s`, in.Mod, in.Extra) 159 | } 160 | } 161 | if in.Extra != "" { 162 | in.Extra += " " 163 | } 164 | var ( 165 | customSystems = gstr.SplitAndTrim(in.System, ",") 166 | customArches = gstr.SplitAndTrim(in.Arch, ",") 167 | ) 168 | if len(in.Version) > 0 { 169 | in.Path += "/" + in.Version 170 | } 171 | // System and arch checks. 172 | var ( 173 | spaceRegex = regexp.MustCompile(`\s+`) 174 | platformMap = make(map[string]map[string]bool) 175 | ) 176 | for _, line := range strings.Split(strings.TrimSpace(cBuildPlatforms), "\n") { 177 | line = gstr.Trim(line) 178 | line = spaceRegex.ReplaceAllString(line, " ") 179 | var ( 180 | array = strings.Split(line, " ") 181 | system = strings.TrimSpace(array[0]) 182 | arch = strings.TrimSpace(array[1]) 183 | ) 184 | if platformMap[system] == nil { 185 | platformMap[system] = make(map[string]bool) 186 | } 187 | platformMap[system][arch] = true 188 | } 189 | // Auto packing. 190 | if len(in.Pack) > 0 { 191 | dataFilePath := fmt.Sprintf(`packed/%s`, c.packedGoFileName) 192 | if !gfile.Exists(dataFilePath) { 193 | // Remove the go file that is automatically packed resource. 194 | defer func() { 195 | _ = gfile.Remove(dataFilePath) 196 | mlog.Printf(`remove the automatically generated resource go file: %s`, dataFilePath) 197 | }() 198 | } 199 | packCmd := fmt.Sprintf(`gf pack %s %s`, in.Pack, dataFilePath) 200 | mlog.Print(packCmd) 201 | gproc.MustShellRun(packCmd) 202 | } 203 | 204 | // Injected information by building flags. 205 | ldFlags := fmt.Sprintf(`-X 'github.com/gogf/gf/v2/os/gbuild.builtInVarStr=%v'`, c.getBuildInVarStr(in)) 206 | 207 | // start building 208 | mlog.Print("start building...") 209 | if in.Cgo { 210 | genv.MustSet("CGO_ENABLED", "1") 211 | } else { 212 | genv.MustSet("CGO_ENABLED", "0") 213 | } 214 | var ( 215 | cmd = "" 216 | ext = "" 217 | ) 218 | for system, item := range platformMap { 219 | cmd = "" 220 | ext = "" 221 | if len(customSystems) > 0 && customSystems[0] != "all" && !gstr.InArray(customSystems, system) { 222 | continue 223 | } 224 | for arch, _ := range item { 225 | if len(customArches) > 0 && customArches[0] != "all" && !gstr.InArray(customArches, arch) { 226 | continue 227 | } 228 | if len(customSystems) == 0 && len(customArches) == 0 { 229 | if runtime.GOOS == "windows" { 230 | ext = ".exe" 231 | } 232 | // Single binary building, output the binary to current working folder. 233 | output := "" 234 | if len(in.Output) > 0 { 235 | output = "-o " + in.Output + ext 236 | } else { 237 | output = "-o " + in.Name + ext 238 | } 239 | cmd = fmt.Sprintf(`go build %s -ldflags "%s" %s %s`, output, ldFlags, in.Extra, file) 240 | } else { 241 | // Cross-building, output the compiled binary to specified path. 242 | if system == "windows" { 243 | ext = ".exe" 244 | } 245 | genv.MustSet("GOOS", system) 246 | genv.MustSet("GOARCH", arch) 247 | cmd = fmt.Sprintf( 248 | `go build -o %s/%s/%s%s -ldflags "%s" %s%s`, 249 | in.Path, system+"_"+arch, in.Name, ext, ldFlags, in.Extra, file, 250 | ) 251 | } 252 | mlog.Debug(cmd) 253 | // It's not necessary printing the complete command string. 254 | cmdShow, _ := gregex.ReplaceString(`\s+(-ldflags ".+?")\s+`, " ", cmd) 255 | mlog.Print(cmdShow) 256 | if result, err := gproc.ShellExec(cmd); err != nil { 257 | mlog.Printf("failed to build, os:%s, arch:%s, error:\n%s\n", system, arch, gstr.Trim(result)) 258 | } else { 259 | mlog.Debug(gstr.Trim(result)) 260 | } 261 | // single binary building. 262 | if len(customSystems) == 0 && len(customArches) == 0 { 263 | goto buildDone 264 | } 265 | } 266 | } 267 | buildDone: 268 | mlog.Print("done!") 269 | return 270 | } 271 | 272 | // getBuildInVarMapJson retrieves and returns the custom build-in variables in configuration 273 | // file as json. 274 | func (c cBuild) getBuildInVarStr(in cBuildInput) string { 275 | buildInVarMap := in.VarMap 276 | if buildInVarMap == nil { 277 | buildInVarMap = make(g.Map) 278 | } 279 | buildInVarMap["builtGit"] = c.getGitCommit() 280 | buildInVarMap["builtTime"] = gtime.Now().String() 281 | b, err := json.Marshal(buildInVarMap) 282 | if err != nil { 283 | mlog.Fatal(err) 284 | } 285 | return gbase64.EncodeToString(b) 286 | } 287 | 288 | // getGitCommit retrieves and returns the latest git commit hash string if present. 289 | func (c cBuild) getGitCommit() string { 290 | if gproc.SearchBinary("git") == "" { 291 | return "" 292 | } 293 | var ( 294 | cmd = `git log -1 --format="%cd %H" --date=format:"%Y-%m-%d %H:%M:%S"` 295 | s, _ = gproc.ShellExec(cmd) 296 | ) 297 | mlog.Debug(cmd) 298 | if s != "" { 299 | if !gstr.Contains(s, "fatal") { 300 | return gstr.Trim(s) 301 | } 302 | } 303 | return "" 304 | } 305 | -------------------------------------------------------------------------------- /internal/cmd/cmd_docker.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | 7 | "github.com/gogf/gf-cli/v2/utility/mlog" 8 | "github.com/gogf/gf/v2/frame/g" 9 | "github.com/gogf/gf/v2/os/gfile" 10 | "github.com/gogf/gf/v2/os/gproc" 11 | "github.com/gogf/gf/v2/util/gtag" 12 | ) 13 | 14 | var ( 15 | Docker = cDocker{} 16 | ) 17 | 18 | type cDocker struct { 19 | g.Meta `name:"docker" usage:"{cDockerUsage}" brief:"{cDockerBrief}" eg:"{cDockerEg}" dc:"{cDockerDc}"` 20 | } 21 | 22 | const ( 23 | cDockerUsage = `gf docker [MAIN] [OPTION]` 24 | cDockerBrief = `build docker image for current GoFrame project` 25 | cDockerEg = ` 26 | gf docker 27 | gf docker -t hub.docker.com/john/image:tag 28 | gf docker -p -t hub.docker.com/john/image:tag 29 | gf docker main.go 30 | gf docker main.go -t hub.docker.com/john/image:tag 31 | gf docker main.go -t hub.docker.com/john/image:tag 32 | gf docker main.go -p -t hub.docker.com/john/image:tag 33 | ` 34 | cDockerDc = ` 35 | The "docker" command builds the GF project to a docker images. 36 | It runs "gf build" firstly to compile the project to binary file. 37 | It then runs "docker build" command automatically to generate the docker image. 38 | You should have docker installed, and there must be a Dockerfile in the root of the project. 39 | ` 40 | cDockerMainBrief = `main golang file path for "gf build", it's "main.go" in default` 41 | cDockerFileBrief = `file path of the Dockerfile. it's "manifest/docker/Dockerfile" in default` 42 | cDockerShellBrief = `path of the shell file which is executed before docker build` 43 | cDockerPushBrief = `auto push the docker image to docker registry if "-t" option passed` 44 | cDockerTagBrief = `tag name for this docker, which is usually used for docker push` 45 | cDockerExtraBrief = `extra build options passed to "docker image"` 46 | ) 47 | 48 | func init() { 49 | gtag.Sets(g.MapStrStr{ 50 | `cDockerUsage`: cDockerUsage, 51 | `cDockerBrief`: cDockerBrief, 52 | `cDockerEg`: cDockerEg, 53 | `cDockerDc`: cDockerDc, 54 | `cDockerMainBrief`: cDockerMainBrief, 55 | `cDockerFileBrief`: cDockerFileBrief, 56 | `cDockerShellBrief`: cDockerShellBrief, 57 | `cDockerPushBrief`: cDockerPushBrief, 58 | `cDockerTagBrief`: cDockerTagBrief, 59 | `cDockerExtraBrief`: cDockerExtraBrief, 60 | }) 61 | } 62 | 63 | type cDockerInput struct { 64 | g.Meta `name:"docker" config:"gfcli.docker"` 65 | Main string `name:"MAIN" arg:"true" brief:"{cDockerMainBrief}" d:"main.go"` 66 | File string `name:"file" short:"f" brief:"{cDockerFileBrief}" d:"manifest/docker/Dockerfile"` 67 | Shell string `name:"shell" short:"s" brief:"{cDockerShellBrief}" d:"manifest/docker/docker.sh"` 68 | Tag string `name:"tag" short:"t" brief:"{cDockerTagBrief}"` 69 | Push bool `name:"push" short:"p" brief:"{cDockerPushBrief}" orphan:"true"` 70 | Extra string `name:"extra" short:"e" brief:"{cDockerExtraBrief}"` 71 | } 72 | type cDockerOutput struct{} 73 | 74 | func (c cDocker) Index(ctx context.Context, in cDockerInput) (out *cDockerOutput, err error) { 75 | // Necessary check. 76 | if gproc.SearchBinary("docker") == "" { 77 | mlog.Fatalf(`command "docker" not found in your environment, please install docker first to proceed this command`) 78 | } 79 | 80 | // Binary build. 81 | if err = gproc.ShellRun(fmt.Sprintf(`gf build %s -a amd64 -s linux`, in.Main)); err != nil { 82 | return 83 | } 84 | // Shell executing. 85 | if gfile.Exists(in.Shell) { 86 | if err = gproc.ShellRun(gfile.GetContents(in.Shell)); err != nil { 87 | return 88 | } 89 | } 90 | // Docker build. 91 | dockerBuildOptions := "" 92 | if in.Tag != "" { 93 | dockerBuildOptions = fmt.Sprintf(`-t %s`, in.Tag) 94 | } 95 | if in.Extra != "" { 96 | dockerBuildOptions = fmt.Sprintf(`%s %s`, dockerBuildOptions, in.Extra) 97 | } 98 | if err = gproc.ShellRun(fmt.Sprintf(`docker build -f %s . %s`, in.File, dockerBuildOptions)); err != nil { 99 | return 100 | } 101 | // Docker push. 102 | if in.Tag == "" || !in.Push { 103 | return 104 | } 105 | if err = gproc.ShellRun(fmt.Sprintf(`docker push %s`, in.Tag)); err != nil { 106 | return 107 | } 108 | return 109 | } 110 | -------------------------------------------------------------------------------- /internal/cmd/cmd_env.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "bytes" 5 | "context" 6 | 7 | "github.com/gogf/gf-cli/v2/utility/mlog" 8 | "github.com/gogf/gf/v2/frame/g" 9 | "github.com/gogf/gf/v2/os/gproc" 10 | "github.com/gogf/gf/v2/text/gregex" 11 | "github.com/gogf/gf/v2/text/gstr" 12 | "github.com/olekukonko/tablewriter" 13 | ) 14 | 15 | var ( 16 | Env = cEnv{} 17 | ) 18 | 19 | type cEnv struct { 20 | g.Meta `name:"env" brief:"show current Golang environment variables"` 21 | } 22 | 23 | type cEnvInput struct { 24 | g.Meta `name:"env"` 25 | } 26 | type cEnvOutput struct{} 27 | 28 | func (c cEnv) Index(ctx context.Context, in cEnvInput) (out *cEnvOutput, err error) { 29 | result, err := gproc.ShellExec("go env") 30 | if err != nil { 31 | mlog.Fatal(err) 32 | } 33 | if result == "" { 34 | mlog.Fatal(`retrieving Golang environment variables failed, did you install Golang?`) 35 | } 36 | var ( 37 | lines = gstr.Split(result, "\n") 38 | buffer = bytes.NewBuffer(nil) 39 | ) 40 | array := make([][]string, 0) 41 | for _, line := range lines { 42 | line = gstr.Trim(line) 43 | if line == "" { 44 | continue 45 | } 46 | if gstr.Pos(line, "set ") == 0 { 47 | line = line[4:] 48 | } 49 | match, _ := gregex.MatchString(`(.+?)=(.*)`, line) 50 | if len(match) < 3 { 51 | mlog.Fatalf(`invalid Golang environment variable: "%s"`, line) 52 | } 53 | array = append(array, []string{gstr.Trim(match[1]), gstr.Trim(match[2])}) 54 | } 55 | tw := tablewriter.NewWriter(buffer) 56 | tw.SetColumnAlignment([]int{tablewriter.ALIGN_LEFT, tablewriter.ALIGN_LEFT}) 57 | tw.AppendBulk(array) 58 | tw.Render() 59 | mlog.Print(buffer.String()) 60 | return 61 | } 62 | -------------------------------------------------------------------------------- /internal/cmd/cmd_gen.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "github.com/gogf/gf/v2/frame/g" 5 | "github.com/gogf/gf/v2/util/gtag" 6 | ) 7 | 8 | var ( 9 | Gen = cGen{} 10 | ) 11 | 12 | type cGen struct { 13 | g.Meta `name:"gen" brief:"{cGenBrief}" dc:"{cGenDc}"` 14 | } 15 | 16 | const ( 17 | cGenBrief = `automatically generate go files for dao/do/entity/pb/pbentity` 18 | cGenDc = ` 19 | The "gen" command is designed for multiple generating purposes. 20 | It's currently supporting generating go files for ORM models, protobuf and protobuf entity files. 21 | Please use "gf gen dao -h" for specified type help. 22 | ` 23 | ) 24 | 25 | func init() { 26 | gtag.Sets(g.MapStrStr{ 27 | `cGenBrief`: cGenBrief, 28 | `cGenDc`: cGenDc, 29 | }) 30 | } 31 | -------------------------------------------------------------------------------- /internal/cmd/cmd_gen_dao.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "bytes" 5 | "context" 6 | "fmt" 7 | "strings" 8 | 9 | "github.com/gogf/gf-cli/v2/internal/consts" 10 | "github.com/gogf/gf-cli/v2/utility/mlog" 11 | "github.com/gogf/gf-cli/v2/utility/utils" 12 | "github.com/gogf/gf/v2/container/garray" 13 | "github.com/gogf/gf/v2/database/gdb" 14 | "github.com/gogf/gf/v2/frame/g" 15 | "github.com/gogf/gf/v2/os/gfile" 16 | "github.com/gogf/gf/v2/os/gtime" 17 | "github.com/gogf/gf/v2/text/gregex" 18 | "github.com/gogf/gf/v2/text/gstr" 19 | "github.com/gogf/gf/v2/util/gtag" 20 | "github.com/olekukonko/tablewriter" 21 | 22 | _ "github.com/denisenkom/go-mssqldb" 23 | _ "github.com/lib/pq" 24 | //_ "github.com/mattn/go-oci8" 25 | //_ "github.com/mattn/go-sqlite3" 26 | ) 27 | 28 | const ( 29 | defaultDaoPath = `service/internal/dao` 30 | defaultDoPath = `service/internal/do` 31 | defaultEntityPath = `model/entity` 32 | cGenDaoConfig = `gfcli.gen.dao` 33 | cGenDaoUsage = `gf gen dao [OPTION]` 34 | cGenDaoBrief = `automatically generate go files for dao/do/entity` 35 | cGenDaoEg = ` 36 | gf gen dao 37 | gf gen dao -l "mysql:root:12345678@tcp(127.0.0.1:3306)/test" 38 | gf gen dao -p ./model -c config.yaml -g user-center -t user,user_detail,user_login 39 | gf gen dao -r user_ 40 | ` 41 | 42 | cGenDaoAd = ` 43 | CONFIGURATION SUPPORT 44 | Options are also supported by configuration file. 45 | It's suggested using configuration file instead of command line arguments making producing. 46 | The configuration node name is "gf.gen.dao", which also supports multiple databases, for example(config.yaml): 47 | gfcli: 48 | gen: 49 | dao: 50 | - link: "mysql:root:12345678@tcp(127.0.0.1:3306)/test" 51 | tables: "order,products" 52 | jsonCase: "CamelLower" 53 | 54 | - link: "mysql:root:12345678@tcp(127.0.0.1:3306)/primary" 55 | path: "./my-app" 56 | prefix: "primary_" 57 | tables: "user, userDetail" 58 | ` 59 | cGenDaoBriefPath = `directory path for generated files` 60 | cGenDaoBriefLink = `database configuration, the same as the ORM configuration of GoFrame` 61 | cGenDaoBriefTables = `generate models only for given tables, multiple table names separated with ','` 62 | cGenDaoBriefTablesEx = `generate models excluding given tables, multiple table names separated with ','` 63 | cGenDaoBriefPrefix = `add prefix for all table of specified link/database tables` 64 | cGenDaoBriefRemovePrefix = `remove specified prefix of the table, multiple prefix separated with ','` 65 | cGenDaoBriefStdTime = `use time.Time from stdlib instead of gtime.Time for generated time/date fields of tables` 66 | cGenDaoBriefGJsonSupport = `use gJsonSupport to use *gjson.Json instead of string for generated json fields of tables` 67 | cGenDaoBriefImportPrefix = `custom import prefix for generated go files` 68 | cGenDaoBriefOverwriteDao = `overwrite all dao files both inside/outside internal folder` 69 | cGenDaoBriefModelFile = `custom file name for storing generated model content` 70 | cGenDaoBriefModelFileForDao = `custom file name generating model for DAO operations like Where/Data. It's empty in default` 71 | cGenDaoBriefDescriptionTag = `add comment to description tag for each field` 72 | cGenDaoBriefNoJsonTag = `no json tag will be added for each field` 73 | cGenDaoBriefNoModelComment = `no model comment will be added for each field` 74 | cGenDaoBriefGroup = ` 75 | specifying the configuration group name of database for generated ORM instance, 76 | it's not necessary and the default value is "default" 77 | ` 78 | cGenDaoBriefJsonCase = ` 79 | generated json tag case for model struct, cases are as follows: 80 | | Case | Example | 81 | |---------------- |--------------------| 82 | | Camel | AnyKindOfString | 83 | | CamelLower | anyKindOfString | default 84 | | Snake | any_kind_of_string | 85 | | SnakeScreaming | ANY_KIND_OF_STRING | 86 | | SnakeFirstUpper | rgb_code_md5 | 87 | | Kebab | any-kind-of-string | 88 | | KebabScreaming | ANY-KIND-OF-STRING | 89 | ` 90 | 91 | tplVarTableName = `{TplTableName}` 92 | tplVarTableNameCamelCase = `{TplTableNameCamelCase}` 93 | tplVarTableNameCamelLowerCase = `{TplTableNameCamelLowerCase}` 94 | tplVarPackageImports = `{TplPackageImports}` 95 | tplVarImportPrefix = `{TplImportPrefix}` 96 | tplVarStructDefine = `{TplStructDefine}` 97 | tplVarColumnDefine = `{TplColumnDefine}` 98 | tplVarColumnNames = `{TplColumnNames}` 99 | tplVarGroupName = `{TplGroupName}` 100 | tplVarDatetime = `{TplDatetime}` 101 | ) 102 | 103 | var ( 104 | createdAt *gtime.Time 105 | ) 106 | 107 | func init() { 108 | gtag.Sets(g.MapStrStr{ 109 | `cGenDaoConfig`: cGenDaoConfig, 110 | `cGenDaoUsage`: cGenDaoUsage, 111 | `cGenDaoBrief`: cGenDaoBrief, 112 | `cGenDaoEg`: cGenDaoEg, 113 | `cGenDaoAd`: cGenDaoAd, 114 | `cGenDaoBriefPath`: cGenDaoBriefPath, 115 | `cGenDaoBriefLink`: cGenDaoBriefLink, 116 | `cGenDaoBriefTables`: cGenDaoBriefTables, 117 | `cGenDaoBriefTablesEx`: cGenDaoBriefTablesEx, 118 | `cGenDaoBriefPrefix`: cGenDaoBriefPrefix, 119 | `cGenDaoBriefRemovePrefix`: cGenDaoBriefRemovePrefix, 120 | `cGenDaoBriefStdTime`: cGenDaoBriefStdTime, 121 | `cGenDaoBriefGJsonSupport`: cGenDaoBriefGJsonSupport, 122 | `cGenDaoBriefImportPrefix`: cGenDaoBriefImportPrefix, 123 | `cGenDaoBriefOverwriteDao`: cGenDaoBriefOverwriteDao, 124 | `cGenDaoBriefModelFile`: cGenDaoBriefModelFile, 125 | `cGenDaoBriefModelFileForDao`: cGenDaoBriefModelFileForDao, 126 | `cGenDaoBriefDescriptionTag`: cGenDaoBriefDescriptionTag, 127 | `cGenDaoBriefNoJsonTag`: cGenDaoBriefNoJsonTag, 128 | `cGenDaoBriefNoModelComment`: cGenDaoBriefNoModelComment, 129 | `cGenDaoBriefGroup`: cGenDaoBriefGroup, 130 | `cGenDaoBriefJsonCase`: cGenDaoBriefJsonCase, 131 | }) 132 | 133 | createdAt = gtime.Now() 134 | } 135 | 136 | type ( 137 | cGenDaoInput struct { 138 | g.Meta `name:"dao" config:"{cGenDaoConfig}" usage:"{cGenDaoUsage}" brief:"{cGenDaoBrief}" eg:"{cGenDaoEg}" ad:"{cGenDaoAd}"` 139 | Path string `name:"path" short:"p" brief:"{cGenDaoBriefPath}" d:"internal"` 140 | Link string `name:"link" short:"l" brief:"{cGenDaoBriefLink}"` 141 | Tables string `name:"tables" short:"t" brief:"{cGenDaoBriefTables}"` 142 | TablesEx string `name:"tablesEx" short:"e" brief:"{cGenDaoBriefTablesEx}"` 143 | Group string `name:"group" short:"g" brief:"{cGenDaoBriefGroup}" d:"default"` 144 | Prefix string `name:"prefix" short:"f" brief:"{cGenDaoBriefPrefix}"` 145 | RemovePrefix string `name:"removePrefix" short:"r" brief:"{cGenDaoBriefRemovePrefix}"` 146 | JsonCase string `name:"jsonCase" short:"j" brief:"{cGenDaoBriefJsonCase}" d:"CamelLower"` 147 | ImportPrefix string `name:"importPrefix" short:"i" brief:"{cGenDaoBriefImportPrefix}"` 148 | StdTime bool `name:"stdTime" short:"s" brief:"{cGenDaoBriefStdTime}" orphan:"true"` 149 | GJsonSupport bool `name:"gJsonSupport" short:"n" brief:"{cGenDaoBriefGJsonSupport}" orphan:"true"` 150 | OverwriteDao bool `name:"overwriteDao" short:"o" brief:"{cGenDaoBriefOverwriteDao}" orphan:"true"` 151 | DescriptionTag bool `name:"descriptionTag" short:"d" brief:"{cGenDaoBriefDescriptionTag}" orphan:"true"` 152 | NoJsonTag bool `name:"noJsonTag" short:"k" brief:"{cGenDaoBriefNoJsonTag" orphan:"true"` 153 | NoModelComment bool `name:"noModelComment" short:"m" brief:"{cGenDaoBriefNoModelComment}" orphan:"true"` 154 | } 155 | cGenDaoOutput struct{} 156 | 157 | cGenDaoInternalInput struct { 158 | cGenDaoInput 159 | TableName string // TableName specifies the table name of the table. 160 | NewTableName string // NewTableName specifies the prefix-stripped name of the table. 161 | ModName string // ModName specifies the module name of current golang project, which is used for import purpose. 162 | } 163 | ) 164 | 165 | func (c cGen) Dao(ctx context.Context, in cGenDaoInput) (out *cGenDaoOutput, err error) { 166 | if g.Cfg().Available(ctx) { 167 | v := g.Cfg().MustGet(ctx, cGenDaoConfig) 168 | if v.IsSlice() { 169 | for i := 0; i < len(v.Interfaces()); i++ { 170 | doGenDaoForArray(ctx, i, in) 171 | } 172 | } else { 173 | doGenDaoForArray(ctx, -1, in) 174 | } 175 | } else { 176 | doGenDaoForArray(ctx, -1, in) 177 | } 178 | mlog.Print("done!") 179 | return 180 | } 181 | 182 | // doGenDaoForArray implements the "gen dao" command for configuration array. 183 | func doGenDaoForArray(ctx context.Context, index int, in cGenDaoInput) { 184 | var ( 185 | err error 186 | db gdb.DB 187 | modName string // Go module name, eg: github.com/gogf/gf. 188 | ) 189 | if index >= 0 { 190 | err = g.Cfg().MustGet( 191 | ctx, 192 | fmt.Sprintf(`%s.%d`, cGenDaoConfig, index), 193 | ).Scan(&in) 194 | if err != nil { 195 | mlog.Fatalf(`invalid configuration of "%s": %+v`, cGenDaoConfig, err) 196 | } 197 | } 198 | if dirRealPath := gfile.RealPath(in.Path); dirRealPath == "" { 199 | mlog.Fatalf(`path "%s" does not exist`, in.Path) 200 | } 201 | removePrefixArray := gstr.SplitAndTrim(in.RemovePrefix, ",") 202 | if in.ImportPrefix == "" { 203 | if !gfile.Exists("go.mod") { 204 | mlog.Fatal("go.mod does not exist in current working directory") 205 | } 206 | var ( 207 | goModContent = gfile.GetContents("go.mod") 208 | match, _ = gregex.MatchString(`^module\s+(.+)\s*`, goModContent) 209 | ) 210 | if len(match) > 1 { 211 | modName = gstr.Trim(match[1]) 212 | } else { 213 | mlog.Fatal("module name does not found in go.mod") 214 | } 215 | } 216 | 217 | // It uses user passed database configuration. 218 | if in.Link != "" { 219 | tempGroup := gtime.TimestampNanoStr() 220 | match, _ := gregex.MatchString(`([a-z]+):(.+)`, in.Link) 221 | if len(match) == 3 { 222 | gdb.AddConfigNode(tempGroup, gdb.ConfigNode{ 223 | Type: gstr.Trim(match[1]), 224 | Link: gstr.Trim(match[2]), 225 | }) 226 | db, _ = gdb.Instance(tempGroup) 227 | } 228 | } else { 229 | db = g.DB(in.Group) 230 | } 231 | if db == nil { 232 | mlog.Fatal("database initialization failed") 233 | } 234 | 235 | var tableNames []string 236 | if in.Tables != "" { 237 | tableNames = gstr.SplitAndTrim(in.Tables, ",") 238 | } else { 239 | tableNames, err = db.Tables(context.TODO()) 240 | if err != nil { 241 | mlog.Fatalf("fetching tables failed: \n %v", err) 242 | } 243 | } 244 | // Table excluding. 245 | if in.TablesEx != "" { 246 | array := garray.NewStrArrayFrom(tableNames) 247 | for _, v := range gstr.SplitAndTrim(in.TablesEx, ",") { 248 | array.RemoveValue(v) 249 | } 250 | tableNames = array.Slice() 251 | } 252 | 253 | // Generating dao & model go files one by one according to given table name. 254 | newTableNames := make([]string, len(tableNames)) 255 | for i, tableName := range tableNames { 256 | newTableName := tableName 257 | for _, v := range removePrefixArray { 258 | newTableName = gstr.TrimLeftStr(newTableName, v, 1) 259 | } 260 | newTableName = in.Prefix + newTableName 261 | newTableNames[i] = newTableName 262 | // Dao. 263 | generateDao(ctx, db, cGenDaoInternalInput{ 264 | cGenDaoInput: in, 265 | TableName: tableName, 266 | NewTableName: newTableName, 267 | ModName: modName, 268 | }) 269 | } 270 | // Do. 271 | generateDo(ctx, db, tableNames, newTableNames, cGenDaoInternalInput{ 272 | cGenDaoInput: in, 273 | ModName: modName, 274 | }) 275 | // Entity. 276 | generateEntity(ctx, db, tableNames, newTableNames, cGenDaoInternalInput{ 277 | cGenDaoInput: in, 278 | ModName: modName, 279 | }) 280 | } 281 | 282 | // generateDaoContentFile generates the dao and model content of given table. 283 | func generateDao(ctx context.Context, db gdb.DB, in cGenDaoInternalInput) { 284 | // Generating table data preparing. 285 | fieldMap, err := db.TableFields(ctx, in.TableName) 286 | if err != nil { 287 | mlog.Fatalf("fetching tables fields failed for table '%s':\n%v", in.TableName, err) 288 | } 289 | var ( 290 | dirRealPath = gfile.RealPath(in.Path) 291 | dirPathDao = gfile.Join(in.Path, defaultDaoPath) 292 | tableNameCamelCase = gstr.CaseCamel(in.NewTableName) 293 | tableNameCamelLowerCase = gstr.CaseCamelLower(in.NewTableName) 294 | tableNameSnakeCase = gstr.CaseSnake(in.NewTableName) 295 | importPrefix = in.ImportPrefix 296 | ) 297 | if importPrefix == "" { 298 | if dirRealPath == "" { 299 | dirRealPath = in.Path 300 | importPrefix = dirRealPath 301 | importPrefix = gstr.Trim(dirRealPath, "./") 302 | } else { 303 | importPrefix = gstr.Replace(dirRealPath, gfile.Pwd(), "") 304 | } 305 | importPrefix = gstr.Replace(importPrefix, gfile.Separator, "/") 306 | importPrefix = gstr.Join(g.SliceStr{in.ModName, importPrefix, defaultDaoPath}, "/") 307 | importPrefix, _ = gregex.ReplaceString(`\/{2,}`, `/`, gstr.Trim(importPrefix, "/")) 308 | } 309 | 310 | fileName := gstr.Trim(tableNameSnakeCase, "-_.") 311 | if len(fileName) > 5 && fileName[len(fileName)-5:] == "_test" { 312 | // Add suffix to avoid the table name which contains "_test", 313 | // which would make the go file a testing file. 314 | fileName += "_table" 315 | } 316 | 317 | // dao - index 318 | generateDaoIndex(tableNameCamelCase, tableNameCamelLowerCase, importPrefix, dirPathDao, fileName, in) 319 | 320 | // dao - internal 321 | generateDaoInternal(tableNameCamelCase, tableNameCamelLowerCase, importPrefix, dirPathDao, fileName, fieldMap, in) 322 | } 323 | 324 | func generateDo(ctx context.Context, db gdb.DB, tableNames, newTableNames []string, in cGenDaoInternalInput) { 325 | var ( 326 | doDirPath = gfile.Join(in.Path, defaultDoPath) 327 | ) 328 | in.NoJsonTag = true 329 | in.DescriptionTag = false 330 | in.NoModelComment = false 331 | // Model content. 332 | for i, tableName := range tableNames { 333 | in.TableName = tableName 334 | fieldMap, err := db.TableFields(ctx, tableName) 335 | if err != nil { 336 | mlog.Fatalf("fetching tables fields failed for table '%s':\n%v", in.TableName, err) 337 | } 338 | var ( 339 | newTableName = newTableNames[i] 340 | doFilePath = gfile.Join(doDirPath, gstr.CaseSnake(newTableName)+".go") 341 | structDefinition = generateStructDefinition(generateStructDefinitionInput{ 342 | cGenDaoInternalInput: in, 343 | StructName: gstr.CaseCamel(newTableName), 344 | FieldMap: fieldMap, 345 | IsDo: true, 346 | }) 347 | ) 348 | // replace all types to interface{}. 349 | structDefinition, _ = gregex.ReplaceStringFuncMatch( 350 | "([A-Z]\\w*?)\\s+([\\w\\*\\.]+?)\\s+(//)", 351 | structDefinition, 352 | func(match []string) string { 353 | // If the type is already a pointer/slice/map, it does nothing. 354 | if !gstr.HasPrefix(match[2], "*") && !gstr.HasPrefix(match[2], "[]") && !gstr.HasPrefix(match[2], "map") { 355 | return fmt.Sprintf(`%s interface{} %s`, match[1], match[3]) 356 | } 357 | return match[0] 358 | }, 359 | ) 360 | modelContent := generateDoContent( 361 | tableName, 362 | gstr.CaseCamel(newTableName), 363 | structDefinition, 364 | ) 365 | err = gfile.PutContents(doFilePath, strings.TrimSpace(modelContent)) 366 | if err != nil { 367 | mlog.Fatalf("writing content to '%s' failed: %v", doFilePath, err) 368 | } else { 369 | utils.GoFmt(doFilePath) 370 | mlog.Print("generated:", doFilePath) 371 | } 372 | } 373 | } 374 | 375 | func generateEntity(ctx context.Context, db gdb.DB, tableNames, newTableNames []string, in cGenDaoInternalInput) { 376 | var ( 377 | entityDirPath = gfile.Join(in.Path, defaultEntityPath) 378 | ) 379 | 380 | // Model content. 381 | for i, tableName := range tableNames { 382 | fieldMap, err := db.TableFields(ctx, tableName) 383 | if err != nil { 384 | mlog.Fatalf("fetching tables fields failed for table '%s':\n%v", in.TableName, err) 385 | } 386 | var ( 387 | newTableName = newTableNames[i] 388 | entityFilePath = gfile.Join(entityDirPath, gstr.CaseSnake(newTableName)+".go") 389 | entityContent = generateEntityContent( 390 | newTableName, 391 | gstr.CaseCamel(newTableName), 392 | generateStructDefinition(generateStructDefinitionInput{ 393 | cGenDaoInternalInput: in, 394 | StructName: gstr.CaseCamel(newTableName), 395 | FieldMap: fieldMap, 396 | IsDo: false, 397 | }), 398 | ) 399 | ) 400 | err = gfile.PutContents(entityFilePath, strings.TrimSpace(entityContent)) 401 | if err != nil { 402 | mlog.Fatalf("writing content to '%s' failed: %v", entityFilePath, err) 403 | } else { 404 | utils.GoFmt(entityFilePath) 405 | mlog.Print("generated:", entityFilePath) 406 | } 407 | } 408 | } 409 | 410 | func getImportPartContent(source string, isDo bool) string { 411 | var ( 412 | packageImportsArray = garray.NewStrArray() 413 | ) 414 | 415 | if isDo { 416 | packageImportsArray.Append(`"github.com/gogf/gf/v2/frame/g"`) 417 | } 418 | 419 | // Time package recognition. 420 | if strings.Contains(source, "gtime.Time") { 421 | packageImportsArray.Append(`"github.com/gogf/gf/v2/os/gtime"`) 422 | } else if strings.Contains(source, "time.Time") { 423 | packageImportsArray.Append(`"time"`) 424 | } 425 | 426 | // Json type. 427 | if strings.Contains(source, "gjson.Json") { 428 | packageImportsArray.Append(`"github.com/gogf/gf/v2/encoding/gjson"`) 429 | } 430 | 431 | // Generate and write content to golang file. 432 | packageImportsStr := "" 433 | if packageImportsArray.Len() > 0 { 434 | packageImportsStr = fmt.Sprintf("import(\n%s\n)", packageImportsArray.Join("\n")) 435 | } 436 | return packageImportsStr 437 | } 438 | 439 | func generateEntityContent(tableName, tableNameCamelCase, structDefine string) string { 440 | entityContent := gstr.ReplaceByMap(consts.TemplateGenDaoEntityContent, g.MapStrStr{ 441 | tplVarTableName: tableName, 442 | tplVarPackageImports: getImportPartContent(structDefine, false), 443 | tplVarTableNameCamelCase: tableNameCamelCase, 444 | tplVarStructDefine: structDefine, 445 | }) 446 | entityContent = replaceDefaultVar(entityContent) 447 | return entityContent 448 | } 449 | 450 | func generateDoContent(tableName, tableNameCamelCase, structDefine string) string { 451 | doContent := gstr.ReplaceByMap(consts.TemplateGenDaoDoContent, g.MapStrStr{ 452 | tplVarTableName: tableName, 453 | tplVarPackageImports: getImportPartContent(structDefine, true), 454 | tplVarTableNameCamelCase: tableNameCamelCase, 455 | tplVarStructDefine: structDefine, 456 | }) 457 | doContent = replaceDefaultVar(doContent) 458 | return doContent 459 | } 460 | 461 | func generateDaoIndex(tableNameCamelCase, tableNameCamelLowerCase, importPrefix, dirPathDao, fileName string, in cGenDaoInternalInput) { 462 | path := gfile.Join(dirPathDao, fileName+".go") 463 | if in.OverwriteDao || !gfile.Exists(path) { 464 | indexContent := gstr.ReplaceByMap(getTplDaoIndexContent(""), g.MapStrStr{ 465 | tplVarImportPrefix: importPrefix, 466 | tplVarTableName: in.TableName, 467 | tplVarTableNameCamelCase: tableNameCamelCase, 468 | tplVarTableNameCamelLowerCase: tableNameCamelLowerCase, 469 | }) 470 | indexContent = replaceDefaultVar(indexContent) 471 | if err := gfile.PutContents(path, strings.TrimSpace(indexContent)); err != nil { 472 | mlog.Fatalf("writing content to '%s' failed: %v", path, err) 473 | } else { 474 | utils.GoFmt(path) 475 | mlog.Print("generated:", path) 476 | } 477 | } 478 | } 479 | 480 | func generateDaoInternal( 481 | tableNameCamelCase, tableNameCamelLowerCase, importPrefix string, 482 | dirPathDao, fileName string, 483 | fieldMap map[string]*gdb.TableField, 484 | in cGenDaoInternalInput, 485 | ) { 486 | path := gfile.Join(dirPathDao, "internal", fileName+".go") 487 | modelContent := gstr.ReplaceByMap(getTplDaoInternalContent(""), g.MapStrStr{ 488 | tplVarImportPrefix: importPrefix, 489 | tplVarTableName: in.TableName, 490 | tplVarGroupName: in.Group, 491 | tplVarTableNameCamelCase: tableNameCamelCase, 492 | tplVarTableNameCamelLowerCase: tableNameCamelLowerCase, 493 | tplVarColumnDefine: gstr.Trim(generateColumnDefinitionForDao(fieldMap)), 494 | tplVarColumnNames: gstr.Trim(generateColumnNamesForDao(fieldMap)), 495 | }) 496 | modelContent = replaceDefaultVar(modelContent) 497 | if err := gfile.PutContents(path, strings.TrimSpace(modelContent)); err != nil { 498 | mlog.Fatalf("writing content to '%s' failed: %v", path, err) 499 | } else { 500 | utils.GoFmt(path) 501 | mlog.Print("generated:", path) 502 | } 503 | } 504 | 505 | func replaceDefaultVar(origin string) string { 506 | return gstr.ReplaceByMap(origin, g.MapStrStr{ 507 | tplVarDatetime: createdAt.String(), 508 | }) 509 | } 510 | 511 | type generateStructDefinitionInput struct { 512 | cGenDaoInternalInput 513 | StructName string // Struct name. 514 | FieldMap map[string]*gdb.TableField // Table field map. 515 | IsDo bool // Is generating DTO struct. 516 | } 517 | 518 | func generateStructDefinition(in generateStructDefinitionInput) string { 519 | buffer := bytes.NewBuffer(nil) 520 | array := make([][]string, len(in.FieldMap)) 521 | names := sortFieldKeyForDao(in.FieldMap) 522 | for index, name := range names { 523 | field := in.FieldMap[name] 524 | array[index] = generateStructFieldDefinition(field, in) 525 | } 526 | tw := tablewriter.NewWriter(buffer) 527 | tw.SetBorder(false) 528 | tw.SetRowLine(false) 529 | tw.SetAutoWrapText(false) 530 | tw.SetColumnSeparator("") 531 | tw.AppendBulk(array) 532 | tw.Render() 533 | stContent := buffer.String() 534 | // Let's do this hack of table writer for indent! 535 | stContent = gstr.Replace(stContent, " #", "") 536 | stContent = gstr.Replace(stContent, "` ", "`") 537 | stContent = gstr.Replace(stContent, "``", "") 538 | buffer.Reset() 539 | buffer.WriteString(fmt.Sprintf("type %s struct {\n", in.StructName)) 540 | if in.IsDo { 541 | buffer.WriteString(fmt.Sprintf("g.Meta `orm:\"table:%s, do:true\"`\n", in.TableName)) 542 | } 543 | buffer.WriteString(stContent) 544 | buffer.WriteString("}") 545 | return buffer.String() 546 | } 547 | 548 | // generateStructFieldForModel generates and returns the attribute definition for specified field. 549 | func generateStructFieldDefinition(field *gdb.TableField, in generateStructDefinitionInput) []string { 550 | var ( 551 | typeName string 552 | jsonTag = getJsonTagFromCase(field.Name, in.JsonCase) 553 | ) 554 | t, _ := gregex.ReplaceString(`\(.+\)`, "", field.Type) 555 | t = gstr.Split(gstr.Trim(t), " ")[0] 556 | t = gstr.ToLower(t) 557 | switch t { 558 | case "binary", "varbinary", "blob", "tinyblob", "mediumblob", "longblob": 559 | typeName = "[]byte" 560 | 561 | case "bit", "int", "int2", "tinyint", "small_int", "smallint", "medium_int", "mediumint", "serial": 562 | if gstr.ContainsI(field.Type, "unsigned") { 563 | typeName = "uint" 564 | } else { 565 | typeName = "int" 566 | } 567 | 568 | case "int4", "int8", "big_int", "bigint", "bigserial": 569 | if gstr.ContainsI(field.Type, "unsigned") { 570 | typeName = "uint64" 571 | } else { 572 | typeName = "int64" 573 | } 574 | 575 | case "real": 576 | typeName = "float32" 577 | 578 | case "float", "double", "decimal", "smallmoney", "numeric": 579 | typeName = "float64" 580 | 581 | case "bool": 582 | typeName = "bool" 583 | 584 | case "datetime", "timestamp", "date", "time": 585 | if in.StdTime { 586 | typeName = "time.Time" 587 | } else { 588 | typeName = "*gtime.Time" 589 | } 590 | case "json", "jsonb": 591 | if in.GJsonSupport { 592 | typeName = "*gjson.Json" 593 | } else { 594 | typeName = "string" 595 | } 596 | default: 597 | // Automatically detect its data type. 598 | switch { 599 | case strings.Contains(t, "int"): 600 | typeName = "int" 601 | case strings.Contains(t, "text") || strings.Contains(t, "char"): 602 | typeName = "string" 603 | case strings.Contains(t, "float") || strings.Contains(t, "double"): 604 | typeName = "float64" 605 | case strings.Contains(t, "bool"): 606 | typeName = "bool" 607 | case strings.Contains(t, "binary") || strings.Contains(t, "blob"): 608 | typeName = "[]byte" 609 | case strings.Contains(t, "date") || strings.Contains(t, "time"): 610 | if in.StdTime { 611 | typeName = "time.Time" 612 | } else { 613 | typeName = "*gtime.Time" 614 | } 615 | default: 616 | typeName = "string" 617 | } 618 | } 619 | 620 | var ( 621 | tagKey = "`" 622 | result = []string{ 623 | " #" + gstr.CaseCamel(field.Name), 624 | " #" + typeName, 625 | } 626 | descriptionTag = gstr.Replace(formatComment(field.Comment), `"`, `\"`) 627 | ) 628 | 629 | result = append(result, " #"+fmt.Sprintf(tagKey+`json:"%s"`, jsonTag)) 630 | result = append(result, " #"+fmt.Sprintf(`description:"%s"`+tagKey, descriptionTag)) 631 | result = append(result, " #"+fmt.Sprintf(`// %s`, formatComment(field.Comment))) 632 | 633 | for k, v := range result { 634 | if in.NoJsonTag { 635 | v, _ = gregex.ReplaceString(`json:".+"`, ``, v) 636 | } 637 | if !in.DescriptionTag { 638 | v, _ = gregex.ReplaceString(`description:".*"`, ``, v) 639 | } 640 | if in.NoModelComment { 641 | v, _ = gregex.ReplaceString(`//.+`, ``, v) 642 | } 643 | result[k] = v 644 | } 645 | return result 646 | } 647 | 648 | // formatComment formats the comment string to fit the golang code without any lines. 649 | func formatComment(comment string) string { 650 | comment = gstr.ReplaceByArray(comment, g.SliceStr{ 651 | "\n", " ", 652 | "\r", " ", 653 | }) 654 | comment = gstr.Replace(comment, `\n`, " ") 655 | comment = gstr.Trim(comment) 656 | return comment 657 | } 658 | 659 | // generateColumnDefinitionForDao generates and returns the column names definition for specified table. 660 | func generateColumnDefinitionForDao(fieldMap map[string]*gdb.TableField) string { 661 | var ( 662 | buffer = bytes.NewBuffer(nil) 663 | array = make([][]string, len(fieldMap)) 664 | names = sortFieldKeyForDao(fieldMap) 665 | ) 666 | for index, name := range names { 667 | var ( 668 | field = fieldMap[name] 669 | comment = gstr.Trim(gstr.ReplaceByArray(field.Comment, g.SliceStr{ 670 | "\n", " ", 671 | "\r", " ", 672 | })) 673 | ) 674 | array[index] = []string{ 675 | " #" + gstr.CaseCamel(field.Name), 676 | " # " + "string", 677 | " #" + fmt.Sprintf(`// %s`, comment), 678 | } 679 | } 680 | tw := tablewriter.NewWriter(buffer) 681 | tw.SetBorder(false) 682 | tw.SetRowLine(false) 683 | tw.SetAutoWrapText(false) 684 | tw.SetColumnSeparator("") 685 | tw.AppendBulk(array) 686 | tw.Render() 687 | defineContent := buffer.String() 688 | // Let's do this hack of table writer for indent! 689 | defineContent = gstr.Replace(defineContent, " #", "") 690 | buffer.Reset() 691 | buffer.WriteString(defineContent) 692 | return buffer.String() 693 | } 694 | 695 | // generateColumnNamesForDao generates and returns the column names assignment content of column struct 696 | // for specified table. 697 | func generateColumnNamesForDao(fieldMap map[string]*gdb.TableField) string { 698 | var ( 699 | buffer = bytes.NewBuffer(nil) 700 | array = make([][]string, len(fieldMap)) 701 | names = sortFieldKeyForDao(fieldMap) 702 | ) 703 | for index, name := range names { 704 | field := fieldMap[name] 705 | array[index] = []string{ 706 | " #" + gstr.CaseCamel(field.Name) + ":", 707 | fmt.Sprintf(` #"%s",`, field.Name), 708 | } 709 | } 710 | tw := tablewriter.NewWriter(buffer) 711 | tw.SetBorder(false) 712 | tw.SetRowLine(false) 713 | tw.SetAutoWrapText(false) 714 | tw.SetColumnSeparator("") 715 | tw.AppendBulk(array) 716 | tw.Render() 717 | namesContent := buffer.String() 718 | // Let's do this hack of table writer for indent! 719 | namesContent = gstr.Replace(namesContent, " #", "") 720 | buffer.Reset() 721 | buffer.WriteString(namesContent) 722 | return buffer.String() 723 | } 724 | 725 | func getTplDaoIndexContent(tplDaoIndexPath string) string { 726 | if tplDaoIndexPath != "" { 727 | return gfile.GetContents(tplDaoIndexPath) 728 | } 729 | return consts.TemplateDaoDaoIndexContent 730 | } 731 | 732 | func getTplDaoInternalContent(tplDaoInternalPath string) string { 733 | if tplDaoInternalPath != "" { 734 | return gfile.GetContents(tplDaoInternalPath) 735 | } 736 | return consts.TemplateDaoDaoInternalContent 737 | } 738 | 739 | // getJsonTagFromCase call gstr.Case* function to convert the s to specified case. 740 | func getJsonTagFromCase(str, caseStr string) string { 741 | switch gstr.ToLower(caseStr) { 742 | case gstr.ToLower("Camel"): 743 | return gstr.CaseCamel(str) 744 | 745 | case gstr.ToLower("CamelLower"): 746 | return gstr.CaseCamelLower(str) 747 | 748 | case gstr.ToLower("Kebab"): 749 | return gstr.CaseKebab(str) 750 | 751 | case gstr.ToLower("KebabScreaming"): 752 | return gstr.CaseKebabScreaming(str) 753 | 754 | case gstr.ToLower("Snake"): 755 | return gstr.CaseSnake(str) 756 | 757 | case gstr.ToLower("SnakeFirstUpper"): 758 | return gstr.CaseSnakeFirstUpper(str) 759 | 760 | case gstr.ToLower("SnakeScreaming"): 761 | return gstr.CaseSnakeScreaming(str) 762 | } 763 | return str 764 | } 765 | 766 | func sortFieldKeyForDao(fieldMap map[string]*gdb.TableField) []string { 767 | names := make(map[int]string) 768 | for _, field := range fieldMap { 769 | names[field.Index] = field.Name 770 | } 771 | var ( 772 | i = 0 773 | j = 0 774 | result = make([]string, len(names)) 775 | ) 776 | for { 777 | if len(names) == 0 { 778 | break 779 | } 780 | if val, ok := names[i]; ok { 781 | result[j] = val 782 | j++ 783 | delete(names, i) 784 | } 785 | i++ 786 | } 787 | return result 788 | } 789 | -------------------------------------------------------------------------------- /internal/cmd/cmd_gen_pb.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | 7 | "github.com/gogf/gf-cli/v2/utility/mlog" 8 | "github.com/gogf/gf/v2/container/gset" 9 | "github.com/gogf/gf/v2/frame/g" 10 | "github.com/gogf/gf/v2/os/genv" 11 | "github.com/gogf/gf/v2/os/gfile" 12 | "github.com/gogf/gf/v2/os/gproc" 13 | ) 14 | 15 | type ( 16 | cGenPbInput struct { 17 | g.Meta `name:"pb" brief:"parse proto files and generate protobuf go files"` 18 | } 19 | cGenPbOutput struct{} 20 | ) 21 | 22 | func (c cGen) Pb(ctx context.Context, in cGenPbInput) (out *cGenPbOutput, err error) { 23 | // Necessary check. 24 | if gproc.SearchBinary("protoc") == "" { 25 | mlog.Fatalf(`command "protoc" not found in your environment, please install protoc first to proceed this command`) 26 | } 27 | 28 | // protocol fold checks. 29 | protoFolder := "protocol" 30 | if !gfile.Exists(protoFolder) { 31 | mlog.Fatalf(`proto files folder "%s" does not exist`, protoFolder) 32 | } 33 | // folder scanning. 34 | files, err := gfile.ScanDirFile(protoFolder, "*.proto", true) 35 | if err != nil { 36 | mlog.Fatal(err) 37 | } 38 | if len(files) == 0 { 39 | mlog.Fatalf(`no proto files found in folder "%s"`, protoFolder) 40 | } 41 | dirSet := gset.NewStrSet() 42 | for _, file := range files { 43 | dirSet.Add(gfile.Dir(file)) 44 | } 45 | var ( 46 | servicePath = gfile.RealPath(".") 47 | goPathSrc = gfile.RealPath(gfile.Join(genv.Get("GOPATH").String(), "src")) 48 | ) 49 | dirSet.Iterator(func(protoDirPath string) bool { 50 | parsingCommand := fmt.Sprintf( 51 | "protoc --gofast_out=plugins=grpc:. %s/*.proto -I%s", 52 | protoDirPath, 53 | servicePath, 54 | ) 55 | if goPathSrc != "" { 56 | parsingCommand += " -I" + goPathSrc 57 | } 58 | mlog.Print(parsingCommand) 59 | if output, err := gproc.ShellExec(parsingCommand); err != nil { 60 | mlog.Print(output) 61 | mlog.Fatal(err) 62 | } 63 | return true 64 | }) 65 | // Custom replacement. 66 | //pbFolder := "protobuf" 67 | //_, _ = gfile.ScanDirFileFunc(pbFolder, "*.go", true, func(path string) string { 68 | // content := gfile.GetContents(path) 69 | // content = gstr.ReplaceByArray(content, g.SliceStr{ 70 | // `gtime "gtime"`, `gtime "github.com/gogf/gf/v2/os/gtime"`, 71 | // }) 72 | // _ = gfile.PutContents(path, content) 73 | // utils.GoFmt(path) 74 | // return path 75 | //}) 76 | mlog.Print("done!") 77 | return 78 | } 79 | -------------------------------------------------------------------------------- /internal/cmd/cmd_gen_pbentity.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "bytes" 5 | "context" 6 | "fmt" 7 | "strings" 8 | 9 | "github.com/gogf/gf-cli/v2/internal/consts" 10 | "github.com/gogf/gf-cli/v2/utility/mlog" 11 | "github.com/gogf/gf/v2/database/gdb" 12 | "github.com/gogf/gf/v2/frame/g" 13 | "github.com/gogf/gf/v2/os/gfile" 14 | "github.com/gogf/gf/v2/os/gtime" 15 | "github.com/gogf/gf/v2/text/gregex" 16 | "github.com/gogf/gf/v2/text/gstr" 17 | "github.com/gogf/gf/v2/util/gconv" 18 | "github.com/gogf/gf/v2/util/gtag" 19 | "github.com/olekukonko/tablewriter" 20 | ) 21 | 22 | const ( 23 | cGenPbEntityConfig = `gfcli.gen.pbentity` 24 | cGenPbEntityBrief = `generate entity message files in protobuf3 format` 25 | cGenPbEntityEg = ` 26 | gf gen pbentity 27 | gf gen pbentity -l "mysql:root:12345678@tcp(127.0.0.1:3306)/test" 28 | gf gen pbentity -p ./protocol/demos/entity -t user,user_detail,user_login 29 | gf gen pbentity -r user_ 30 | ` 31 | 32 | cGenPbEntityAd = ` 33 | CONFIGURATION SUPPORT 34 | Options are also supported by configuration file. 35 | It's suggested using configuration file instead of command line arguments making producing. 36 | The configuration node name is "gf.gen.pbentity", which also supports multiple databases, for example(config.yaml): 37 | gfcli: 38 | gen: 39 | - pbentity: 40 | link: "mysql:root:12345678@tcp(127.0.0.1:3306)/test" 41 | path: "protocol/demos/entity" 42 | tables: "order,products" 43 | package: "demos" 44 | - pbentity: 45 | link: "mysql:root:12345678@tcp(127.0.0.1:3306)/primary" 46 | path: "protocol/demos/entity" 47 | prefix: "primary_" 48 | tables: "user, userDetail" 49 | package: "demos" 50 | option: | 51 | option go_package = "protobuf/demos"; 52 | option java_package = "protobuf/demos"; 53 | option php_namespace = "protobuf/demos"; 54 | ` 55 | cGenPbEntityBriefPath = `directory path for generated files` 56 | cGenPbEntityBriefPackage = `package name for all entity proto files` 57 | cGenPbEntityBriefLink = `database configuration, the same as the ORM configuration of GoFrame` 58 | cGenPbEntityBriefTables = `generate models only for given tables, multiple table names separated with ','` 59 | cGenPbEntityBriefPrefix = `add specified prefix for all entity names and entity proto files` 60 | cGenPbEntityBriefRemovePrefix = `remove specified prefix of the table, multiple prefix separated with ','` 61 | cGenPbEntityBriefOption = `extra protobuf options` 62 | cGenPbEntityBriefGroup = ` 63 | specifying the configuration group name of database for generated ORM instance, 64 | it's not necessary and the default value is "default" 65 | ` 66 | 67 | cGenPbEntityBriefNameCase = ` 68 | case for message attribute names, default is "Camel": 69 | | Case | Example | 70 | |---------------- |--------------------| 71 | | Camel | AnyKindOfString | 72 | | CamelLower | anyKindOfString | default 73 | | Snake | any_kind_of_string | 74 | | SnakeScreaming | ANY_KIND_OF_STRING | 75 | | SnakeFirstUpper | rgb_code_md5 | 76 | | Kebab | any-kind-of-string | 77 | | KebabScreaming | ANY-KIND-OF-STRING | 78 | ` 79 | 80 | cGenPbEntityBriefJsonCase = ` 81 | case for message json tag, cases are the same as "nameCase", default "CamelLower". 82 | set it to "none" to ignore json tag generating. 83 | ` 84 | ) 85 | 86 | type ( 87 | cGenPbEntityInput struct { 88 | g.Meta `name:"pbentity" config:"{cGenPbEntityConfig}" brief:"{cGenPbEntityBrief}" eg:"{cGenPbEntityEg}" ad:"{cGenPbEntityAd}"` 89 | Path string `name:"path" short:"p" brief:"{cGenPbEntityBriefPath}"` 90 | Package string `name:"package" short:"k" brief:"{cGenPbEntityBriefPackage}"` 91 | Link string `name:"link" short:"l" brief:"{cGenPbEntityBriefLink}"` 92 | Tables string `name:"tables" short:"t" brief:"{cGenPbEntityBriefTables}"` 93 | Prefix string `name:"prefix" short:"f" brief:"{cGenPbEntityBriefPrefix}"` 94 | RemovePrefix string `name:"removePrefix" short:"r" brief:"{cGenPbEntityBriefRemovePrefix}"` 95 | NameCase string `name:"nameCase" short:"n" brief:"{cGenPbEntityBriefNameCase}" d:"Camel"` 96 | JsonCase string `name:"jsonCase" short:"j" brief:"{cGenPbEntityBriefJsonCase}" d:"CamelLower"` 97 | Option string `name:"option" short:"o" brief:"{cGenPbEntityBriefOption}"` 98 | } 99 | cGenPbEntityOutput struct{} 100 | 101 | cGenPbEntityInternalInput struct { 102 | cGenPbEntityInput 103 | TableName string // TableName specifies the table name of the table. 104 | NewTableName string // NewTableName specifies the prefix-stripped name of the table. 105 | } 106 | ) 107 | 108 | func init() { 109 | gtag.Sets(g.MapStrStr{ 110 | `cGenPbEntityConfig`: cGenPbEntityConfig, 111 | `cGenPbEntityBrief`: cGenPbEntityBrief, 112 | `cGenPbEntityEg`: cGenPbEntityEg, 113 | `cGenPbEntityAd`: cGenPbEntityAd, 114 | `cGenPbEntityBriefPath`: cGenPbEntityBriefPath, 115 | `cGenPbEntityBriefPackage`: cGenPbEntityBriefPackage, 116 | `cGenPbEntityBriefLink`: cGenPbEntityBriefLink, 117 | `cGenPbEntityBriefTables`: cGenPbEntityBriefTables, 118 | `cGenPbEntityBriefPrefix`: cGenPbEntityBriefPrefix, 119 | `cGenPbEntityBriefRemovePrefix`: cGenPbEntityBriefRemovePrefix, 120 | `cGenPbEntityBriefGroup`: cGenPbEntityBriefGroup, 121 | `cGenPbEntityBriefNameCase`: cGenPbEntityBriefNameCase, 122 | `cGenPbEntityBriefJsonCase`: cGenPbEntityBriefJsonCase, 123 | `cGenPbEntityBriefOption`: cGenPbEntityBriefOption, 124 | }) 125 | } 126 | 127 | func (c cGen) PbEntity(ctx context.Context, in cGenPbEntityInput) (out *cGenPbEntityOutput, err error) { 128 | var ( 129 | config = g.Cfg() 130 | ) 131 | if config.Available(ctx) { 132 | v := config.MustGet(ctx, cGenPbEntityConfig) 133 | if v.IsSlice() { 134 | for i := 0; i < len(v.Interfaces()); i++ { 135 | doGenPbEntityForArray(ctx, i, in) 136 | } 137 | } else { 138 | doGenPbEntityForArray(ctx, -1, in) 139 | } 140 | } else { 141 | doGenPbEntityForArray(ctx, -1, in) 142 | } 143 | mlog.Print("done!") 144 | return 145 | } 146 | 147 | func doGenPbEntityForArray(ctx context.Context, index int, in cGenPbEntityInput) { 148 | var ( 149 | err error 150 | db gdb.DB 151 | ) 152 | if index >= 0 { 153 | err = g.Cfg().MustGet( 154 | ctx, 155 | fmt.Sprintf(`%s.%d`, cGenDaoConfig, index), 156 | ).Scan(&in) 157 | if err != nil { 158 | mlog.Fatalf(`invalid configuration of "%s": %+v`, cGenDaoConfig, err) 159 | } 160 | } 161 | if in.Package == "" { 162 | mlog.Fatal("package name should not be empty") 163 | } 164 | removePrefixArray := gstr.SplitAndTrim(in.RemovePrefix, ",") 165 | // It uses user passed database configuration. 166 | if in.Link != "" { 167 | var ( 168 | tempGroup = gtime.TimestampNanoStr() 169 | match, _ = gregex.MatchString(`([a-z]+):(.+)`, in.Link) 170 | ) 171 | if len(match) == 3 { 172 | gdb.AddConfigNode(tempGroup, gdb.ConfigNode{ 173 | Type: gstr.Trim(match[1]), 174 | Link: gstr.Trim(match[2]), 175 | }) 176 | db, _ = gdb.Instance(tempGroup) 177 | } 178 | } else { 179 | db = g.DB() 180 | } 181 | if db == nil { 182 | mlog.Fatal("database initialization failed") 183 | } 184 | 185 | tableNames := ([]string)(nil) 186 | if in.Tables != "" { 187 | tableNames = gstr.SplitAndTrim(in.Tables, ",") 188 | } else { 189 | tableNames, err = db.Tables(context.TODO()) 190 | if err != nil { 191 | mlog.Fatalf("fetching tables failed: \n %v", err) 192 | } 193 | } 194 | 195 | for _, tableName := range tableNames { 196 | newTableName := tableName 197 | for _, v := range removePrefixArray { 198 | newTableName = gstr.TrimLeftStr(newTableName, v, 1) 199 | } 200 | generatePbEntityContentFile(ctx, db, cGenPbEntityInternalInput{ 201 | cGenPbEntityInput: in, 202 | TableName: tableName, 203 | NewTableName: newTableName, 204 | }) 205 | } 206 | } 207 | 208 | // generatePbEntityContentFile generates the protobuf files for given table. 209 | func generatePbEntityContentFile(ctx context.Context, db gdb.DB, in cGenPbEntityInternalInput) { 210 | fieldMap, err := db.TableFields(ctx, in.TableName) 211 | if err != nil { 212 | mlog.Fatalf("fetching tables fields failed for table '%s':\n%v", in.TableName, err) 213 | } 214 | // Change the `newTableName` if `Prefix` is given. 215 | newTableName := "Entity_" + in.Prefix + in.NewTableName 216 | var ( 217 | tableNameCamelCase = gstr.CaseCamel(newTableName) 218 | tableNameSnakeCase = gstr.CaseSnake(newTableName) 219 | entityMessageDefine = generateEntityMessageDefinition(tableNameCamelCase, fieldMap, in) 220 | fileName = gstr.Trim(tableNameSnakeCase, "-_.") 221 | path = gfile.Join(in.Path, fileName+".proto") 222 | ) 223 | entityContent := gstr.ReplaceByMap(getTplPbEntityContent(""), g.MapStrStr{ 224 | "{PackageName}": in.Package, 225 | "{OptionContent}": in.Option, 226 | "{EntityMessage}": entityMessageDefine, 227 | }) 228 | if err := gfile.PutContents(path, strings.TrimSpace(entityContent)); err != nil { 229 | mlog.Fatalf("writing content to '%s' failed: %v", path, err) 230 | } else { 231 | mlog.Print("generated:", path) 232 | } 233 | } 234 | 235 | // generateEntityMessageDefinition generates and returns the message definition for specified table. 236 | func generateEntityMessageDefinition(entityName string, fieldMap map[string]*gdb.TableField, in cGenPbEntityInternalInput) string { 237 | var ( 238 | buffer = bytes.NewBuffer(nil) 239 | array = make([][]string, len(fieldMap)) 240 | names = sortFieldKeyForPbEntity(fieldMap) 241 | ) 242 | for index, name := range names { 243 | array[index] = generateMessageFieldForPbEntity(index+1, fieldMap[name], in) 244 | } 245 | tw := tablewriter.NewWriter(buffer) 246 | tw.SetBorder(false) 247 | tw.SetRowLine(false) 248 | tw.SetAutoWrapText(false) 249 | tw.SetColumnSeparator("") 250 | tw.AppendBulk(array) 251 | tw.Render() 252 | stContent := buffer.String() 253 | // Let's do this hack of table writer for indent! 254 | stContent = gstr.Replace(stContent, " #", "") 255 | buffer.Reset() 256 | buffer.WriteString(fmt.Sprintf("message %s {\n", entityName)) 257 | buffer.WriteString(stContent) 258 | buffer.WriteString("}") 259 | return buffer.String() 260 | } 261 | 262 | // generateMessageFieldForPbEntity generates and returns the message definition for specified field. 263 | func generateMessageFieldForPbEntity(index int, field *gdb.TableField, in cGenPbEntityInternalInput) []string { 264 | var ( 265 | typeName string 266 | comment string 267 | jsonTagStr string 268 | ) 269 | t, _ := gregex.ReplaceString(`\(.+\)`, "", field.Type) 270 | t = gstr.Split(gstr.Trim(t), " ")[0] 271 | t = gstr.ToLower(t) 272 | switch t { 273 | case "binary", "varbinary", "blob", "tinyblob", "mediumblob", "longblob": 274 | typeName = "bytes" 275 | 276 | case "bit", "int", "tinyint", "small_int", "smallint", "medium_int", "mediumint", "serial": 277 | if gstr.ContainsI(field.Type, "unsigned") { 278 | typeName = "uint32" 279 | } else { 280 | typeName = "int32" 281 | } 282 | 283 | case "int8", "big_int", "bigint", "bigserial": 284 | if gstr.ContainsI(field.Type, "unsigned") { 285 | typeName = "uint64" 286 | } else { 287 | typeName = "int64" 288 | } 289 | 290 | case "real": 291 | typeName = "float" 292 | 293 | case "float", "double", "decimal", "smallmoney": 294 | typeName = "double" 295 | 296 | case "bool": 297 | typeName = "bool" 298 | 299 | case "datetime", "timestamp", "date", "time": 300 | typeName = "int64" 301 | 302 | default: 303 | // Auto detecting type. 304 | switch { 305 | case strings.Contains(t, "int"): 306 | typeName = "int" 307 | case strings.Contains(t, "text") || strings.Contains(t, "char"): 308 | typeName = "string" 309 | case strings.Contains(t, "float") || strings.Contains(t, "double"): 310 | typeName = "double" 311 | case strings.Contains(t, "bool"): 312 | typeName = "bool" 313 | case strings.Contains(t, "binary") || strings.Contains(t, "blob"): 314 | typeName = "bytes" 315 | case strings.Contains(t, "date") || strings.Contains(t, "time"): 316 | typeName = "int64" 317 | default: 318 | typeName = "string" 319 | } 320 | } 321 | comment = gstr.ReplaceByArray(field.Comment, g.SliceStr{ 322 | "\n", " ", 323 | "\r", " ", 324 | }) 325 | comment = gstr.Trim(comment) 326 | comment = gstr.Replace(comment, `\n`, " ") 327 | comment, _ = gregex.ReplaceString(`\s{2,}`, ` `, comment) 328 | if jsonTagName := formatCase(field.Name, in.JsonCase); jsonTagName != "" { 329 | jsonTagStr = fmt.Sprintf(`[(gogoproto.jsontag) = "%s"]`, jsonTagName) 330 | // beautiful indent. 331 | if index < 10 { 332 | // 3 spaces 333 | jsonTagStr = " " + jsonTagStr 334 | } else if index < 100 { 335 | // 2 spaces 336 | jsonTagStr = " " + jsonTagStr 337 | } else { 338 | // 1 spaces 339 | jsonTagStr = " " + jsonTagStr 340 | } 341 | } 342 | return []string{ 343 | " #" + typeName, 344 | " #" + formatCase(field.Name, in.NameCase), 345 | " #= " + gconv.String(index) + jsonTagStr + ";", 346 | " #" + fmt.Sprintf(`// %s`, comment), 347 | } 348 | } 349 | 350 | func getTplPbEntityContent(tplEntityPath string) string { 351 | if tplEntityPath != "" { 352 | return gfile.GetContents(tplEntityPath) 353 | } 354 | return consts.TemplatePbEntityMessageContent 355 | } 356 | 357 | // formatCase call gstr.Case* function to convert the s to specified case. 358 | func formatCase(str, caseStr string) string { 359 | switch gstr.ToLower(caseStr) { 360 | case gstr.ToLower("Camel"): 361 | return gstr.CaseCamel(str) 362 | 363 | case gstr.ToLower("CamelLower"): 364 | return gstr.CaseCamelLower(str) 365 | 366 | case gstr.ToLower("Kebab"): 367 | return gstr.CaseKebab(str) 368 | 369 | case gstr.ToLower("KebabScreaming"): 370 | return gstr.CaseKebabScreaming(str) 371 | 372 | case gstr.ToLower("Snake"): 373 | return gstr.CaseSnake(str) 374 | 375 | case gstr.ToLower("SnakeFirstUpper"): 376 | return gstr.CaseSnakeFirstUpper(str) 377 | 378 | case gstr.ToLower("SnakeScreaming"): 379 | return gstr.CaseSnakeScreaming(str) 380 | 381 | case "none": 382 | return "" 383 | } 384 | return str 385 | } 386 | 387 | func sortFieldKeyForPbEntity(fieldMap map[string]*gdb.TableField) []string { 388 | names := make(map[int]string) 389 | for _, field := range fieldMap { 390 | names[field.Index] = field.Name 391 | } 392 | var ( 393 | result = make([]string, len(names)) 394 | i = 0 395 | j = 0 396 | ) 397 | for { 398 | if len(names) == 0 { 399 | break 400 | } 401 | if val, ok := names[i]; ok { 402 | result[j] = val 403 | j++ 404 | delete(names, i) 405 | } 406 | i++ 407 | } 408 | return result 409 | } 410 | -------------------------------------------------------------------------------- /internal/cmd/cmd_init.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "strings" 7 | 8 | "github.com/gogf/gf-cli/v2/utility/allyes" 9 | "github.com/gogf/gf-cli/v2/utility/mlog" 10 | "github.com/gogf/gf/v2/frame/g" 11 | "github.com/gogf/gf/v2/os/gcmd" 12 | "github.com/gogf/gf/v2/os/gfile" 13 | "github.com/gogf/gf/v2/os/gres" 14 | "github.com/gogf/gf/v2/util/gtag" 15 | ) 16 | 17 | var ( 18 | Init = cInit{} 19 | ) 20 | 21 | type cInit struct { 22 | g.Meta `name:"init" brief:"{cInitBrief}" eg:"{cInitEg}"` 23 | } 24 | 25 | const ( 26 | cInitRepoPrefix = `github.com/gogf/` 27 | cInitMonoRepo = `template-mono` 28 | cInitSingleRepo = `template-single` 29 | cInitBrief = `create and initialize an empty GoFrame project` 30 | cInitEg = ` 31 | gf init my-project 32 | gf init my-mono-repo -m 33 | ` 34 | cInitNameBrief = ` 35 | name for the project. It will create a folder with NAME in current directory. 36 | The NAME will also be the module name for the project. 37 | ` 38 | ) 39 | 40 | func init() { 41 | gtag.Sets(g.MapStrStr{ 42 | `cInitBrief`: cInitBrief, 43 | `cInitEg`: cInitEg, 44 | `cInitNameBrief`: cInitNameBrief, 45 | }) 46 | } 47 | 48 | type cInitInput struct { 49 | g.Meta `name:"init"` 50 | Name string `name:"NAME" arg:"true" v:"required" brief:"{cInitNameBrief}"` 51 | Mono bool `name:"mono" short:"m" brief:"initialize a mono-repo instead a single-repo" orphan:"true"` 52 | } 53 | type cInitOutput struct{} 54 | 55 | func (c cInit) Index(ctx context.Context, in cInitInput) (out *cInitOutput, err error) { 56 | if !gfile.IsEmpty(in.Name) && !allyes.Check() { 57 | s := gcmd.Scanf(`the folder "%s" is not empty, files might be overwrote, continue? [y/n]: `, in.Name) 58 | if strings.EqualFold(s, "n") { 59 | return 60 | } 61 | } 62 | mlog.Print("initializing...") 63 | 64 | // Create project folder and files. 65 | var ( 66 | templateRepoName string 67 | ) 68 | if in.Mono { 69 | templateRepoName = cInitMonoRepo 70 | } else { 71 | templateRepoName = cInitSingleRepo 72 | } 73 | err = gres.Export(templateRepoName, in.Name, gres.ExportOption{ 74 | RemovePrefix: templateRepoName, 75 | }) 76 | if err != nil { 77 | return 78 | } 79 | 80 | // Replace template name to project name. 81 | err = gfile.ReplaceDir( 82 | cInitRepoPrefix+templateRepoName, 83 | gfile.Basename(gfile.RealPath(in.Name)), 84 | in.Name, 85 | "*", 86 | true, 87 | ) 88 | if err != nil { 89 | return 90 | } 91 | 92 | mlog.Print("initialization done! ") 93 | if !in.Mono { 94 | enjoyCommand := `gf run main.go` 95 | if in.Name != "." { 96 | enjoyCommand = fmt.Sprintf(`cd %s && %s`, in.Name, enjoyCommand) 97 | } 98 | mlog.Printf(`you can now run "%s" to start your journey, enjoy!`, enjoyCommand) 99 | } 100 | return 101 | } 102 | -------------------------------------------------------------------------------- /internal/cmd/cmd_install.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/gogf/gf-cli/v2/internal/service" 7 | "github.com/gogf/gf/v2/frame/g" 8 | ) 9 | 10 | var ( 11 | Install = cInstall{} 12 | ) 13 | 14 | type cInstall struct { 15 | g.Meta `name:"install" brief:"install gf binary to system (might need root/admin permission)"` 16 | } 17 | 18 | type cInstallInput struct { 19 | g.Meta `name:"install"` 20 | } 21 | type cInstallOutput struct{} 22 | 23 | func (c cInstall) Index(ctx context.Context, in cInstallInput) (out *cInstallOutput, err error) { 24 | err = service.Install.Run(ctx) 25 | return 26 | } 27 | -------------------------------------------------------------------------------- /internal/cmd/cmd_pack.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "context" 5 | "strings" 6 | 7 | "github.com/gogf/gf-cli/v2/utility/allyes" 8 | "github.com/gogf/gf-cli/v2/utility/mlog" 9 | "github.com/gogf/gf/v2/frame/g" 10 | "github.com/gogf/gf/v2/os/gcmd" 11 | "github.com/gogf/gf/v2/os/gfile" 12 | "github.com/gogf/gf/v2/os/gres" 13 | "github.com/gogf/gf/v2/util/gtag" 14 | ) 15 | 16 | var ( 17 | Pack = cPack{} 18 | ) 19 | 20 | type cPack struct { 21 | g.Meta `name:"pack" usage:"{cPackUsage}" brief:"{cPackBrief}" eg:"{cPackEg}"` 22 | } 23 | 24 | const ( 25 | cPackUsage = `gf pack SRC DST` 26 | cPackBrief = `packing any file/directory to a resource file, or a go file` 27 | cPackEg = ` 28 | gf pack public data.bin 29 | gf pack public,template data.bin 30 | gf pack public,template packed/data.go 31 | gf pack public,template,config packed/data.go 32 | gf pack public,template,config packed/data.go -n=packed -p=/var/www/my-app 33 | gf pack /var/www/public packed/data.go -n=packed 34 | ` 35 | cPackSrcBrief = `source path for packing, which can be multiple source paths.` 36 | cPackDstBrief = ` 37 | destination file path for packed file. if extension of the filename is ".go" and "-n" option is given, 38 | it enables packing SRC to go file, or else it packs SRC into a binary file. 39 | ` 40 | cPackNameBrief = `package name for output go file, it's set as its directory name if no name passed` 41 | cPackPrefixBrief = `prefix for each file packed into the resource file` 42 | ) 43 | 44 | func init() { 45 | gtag.Sets(g.MapStrStr{ 46 | `cPackUsage`: cPackUsage, 47 | `cPackBrief`: cPackBrief, 48 | `cPackEg`: cPackEg, 49 | `cPackSrcBrief`: cPackSrcBrief, 50 | `cPackDstBrief`: cPackDstBrief, 51 | `cPackNameBrief`: cPackNameBrief, 52 | `cPackPrefixBrief`: cPackPrefixBrief, 53 | }) 54 | } 55 | 56 | type cPackInput struct { 57 | g.Meta `name:"pack"` 58 | Src string `name:"SRC" arg:"true" v:"required" brief:"{cPackSrcBrief}"` 59 | Dst string `name:"DST" arg:"true" v:"required" brief:"{cPackDstBrief}"` 60 | Name string `name:"name" short:"n" brief:"{cPackNameBrief}"` 61 | Prefix string `name:"prefix" short:"p" brief:"{cPackPrefixBrief}"` 62 | } 63 | type cPackOutput struct{} 64 | 65 | func (c cPack) Index(ctx context.Context, in cPackInput) (out *cPackOutput, err error) { 66 | if gfile.Exists(in.Dst) && gfile.IsDir(in.Dst) { 67 | mlog.Fatalf("DST path '%s' cannot be a directory", in.Dst) 68 | } 69 | if !gfile.IsEmpty(in.Dst) && !allyes.Check() { 70 | s := gcmd.Scanf("path '%s' is not empty, files might be overwrote, continue? [y/n]: ", in.Dst) 71 | if strings.EqualFold(s, "n") { 72 | return 73 | } 74 | } 75 | if in.Name == "" && gfile.ExtName(in.Dst) == "go" { 76 | in.Name = gfile.Basename(gfile.Dir(in.Dst)) 77 | } 78 | if in.Name != "" { 79 | if err = gres.PackToGoFile(in.Src, in.Dst, in.Name, in.Prefix); err != nil { 80 | mlog.Fatalf("pack failed: %v", err) 81 | } 82 | } else { 83 | if err = gres.PackToFile(in.Src, in.Dst, in.Prefix); err != nil { 84 | mlog.Fatalf("pack failed: %v", err) 85 | } 86 | } 87 | mlog.Print("done!") 88 | return 89 | } 90 | -------------------------------------------------------------------------------- /internal/cmd/cmd_run.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "runtime" 7 | 8 | "github.com/gogf/gf-cli/v2/utility/mlog" 9 | "github.com/gogf/gf/v2/container/gtype" 10 | "github.com/gogf/gf/v2/frame/g" 11 | "github.com/gogf/gf/v2/os/gfile" 12 | "github.com/gogf/gf/v2/os/gfsnotify" 13 | "github.com/gogf/gf/v2/os/gproc" 14 | "github.com/gogf/gf/v2/os/gtime" 15 | "github.com/gogf/gf/v2/os/gtimer" 16 | "github.com/gogf/gf/v2/util/gtag" 17 | ) 18 | 19 | var ( 20 | Run = cRun{} 21 | ) 22 | 23 | type cRun struct { 24 | g.Meta `name:"run" usage:"{cRunUsage}" brief:"{cRunBrief}" eg:"{cRunEg}" dc:"{cRunDc}"` 25 | } 26 | 27 | type cRunApp struct { 28 | File string // Go run file name. 29 | Path string // Directory storing built binary. 30 | Options string // Extra "go run" options. 31 | Args string // Custom arguments. 32 | } 33 | 34 | const ( 35 | cRunUsage = `gf run FILE [OPTION]` 36 | cRunBrief = `running go codes with hot-compiled-like feature` 37 | cRunEg = ` 38 | gf run main.go 39 | gf run main.go --args "server -p 8080" 40 | gf run main.go -mod=vendor 41 | ` 42 | cRunDc = ` 43 | The "run" command is used for running go codes with hot-compiled-like feature, 44 | which compiles and runs the go codes asynchronously when codes change. 45 | ` 46 | cRunFileBrief = `building file path.` 47 | cRunPathBrief = `output directory path for built binary file. it's "manifest/output" in default` 48 | cRunExtraBrief = `the same options as "go run"/"go build" except some options as follows defined` 49 | ) 50 | 51 | var ( 52 | process *gproc.Process 53 | ) 54 | 55 | func init() { 56 | gtag.Sets(g.MapStrStr{ 57 | `cRunUsage`: cRunUsage, 58 | `cRunBrief`: cRunBrief, 59 | `cRunEg`: cRunEg, 60 | `cRunDc`: cRunDc, 61 | `cRunFileBrief`: cRunFileBrief, 62 | `cRunPathBrief`: cRunPathBrief, 63 | `cRunExtraBrief`: cRunExtraBrief, 64 | }) 65 | } 66 | 67 | type ( 68 | cRunInput struct { 69 | g.Meta `name:"run"` 70 | File string `name:"FILE" arg:"true" brief:"{cRunFileBrief}" v:"required"` 71 | Path string `name:"path" short:"p" brief:"{cRunPathBrief}" d:"./"` 72 | Extra string `name:"extra" short:"e" brief:"{cRunExtraBrief}"` 73 | } 74 | cRunOutput struct{} 75 | ) 76 | 77 | func (c cRun) Index(ctx context.Context, in cRunInput) (out *cRunOutput, err error) { 78 | // Necessary check. 79 | if gproc.SearchBinary("go") == "" { 80 | mlog.Fatalf(`command "go" not found in your environment, please install golang first to proceed this command`) 81 | } 82 | 83 | app := &cRunApp{ 84 | File: in.File, 85 | Path: in.Path, 86 | Options: in.Extra, 87 | } 88 | dirty := gtype.NewBool() 89 | _, err = gfsnotify.Add(gfile.RealPath("."), func(event *gfsnotify.Event) { 90 | if gfile.ExtName(event.Path) != "go" { 91 | return 92 | } 93 | // Variable `dirty` is used for running the changes only one in one second. 94 | if !dirty.Cas(false, true) { 95 | return 96 | } 97 | // With some delay in case of multiple code changes in very short interval. 98 | gtimer.SetTimeout(ctx, 1500*gtime.MS, func(ctx context.Context) { 99 | defer dirty.Set(false) 100 | mlog.Printf(`go file changes: %s`, event.String()) 101 | app.Run() 102 | }) 103 | }) 104 | if err != nil { 105 | mlog.Fatal(err) 106 | } 107 | go app.Run() 108 | select {} 109 | } 110 | 111 | func (app *cRunApp) Run() { 112 | // Rebuild and run the codes. 113 | renamePath := "" 114 | mlog.Printf("build: %s", app.File) 115 | outputPath := gfile.Join(app.Path, gfile.Name(app.File)) 116 | if runtime.GOOS == "windows" { 117 | outputPath += ".exe" 118 | if gfile.Exists(outputPath) { 119 | renamePath = outputPath + "~" 120 | if err := gfile.Rename(outputPath, renamePath); err != nil { 121 | mlog.Print(err) 122 | } 123 | } 124 | } 125 | // In case of `pipe: too many open files` error. 126 | // Build the app. 127 | buildCommand := fmt.Sprintf( 128 | `go build -o %s %s %s`, 129 | outputPath, 130 | app.Options, 131 | app.File, 132 | ) 133 | mlog.Print(buildCommand) 134 | result, err := gproc.ShellExec(buildCommand) 135 | if err != nil { 136 | mlog.Printf("build error: \n%s%s", result, err.Error()) 137 | return 138 | } 139 | // Kill the old process if build successfully. 140 | if process != nil { 141 | if err := process.Kill(); err != nil { 142 | mlog.Debugf("kill process error: %s", err.Error()) 143 | //return 144 | } 145 | } 146 | // Run the binary file. 147 | runCommand := fmt.Sprintf(`%s %s`, outputPath, app.Args) 148 | mlog.Print(runCommand) 149 | if runtime.GOOS == "windows" { 150 | // Special handling for windows platform. 151 | // DO NOT USE "cmd /c" command. 152 | process = gproc.NewProcess(runCommand, nil) 153 | } else { 154 | process = gproc.NewProcessCmd(runCommand, nil) 155 | } 156 | if pid, err := process.Start(); err != nil { 157 | mlog.Printf("build running error: %s", err.Error()) 158 | } else { 159 | mlog.Printf("build running pid: %d", pid) 160 | } 161 | } 162 | -------------------------------------------------------------------------------- /internal/cmd/cmd_tpl.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/gogf/gf-cli/v2/utility/mlog" 7 | "github.com/gogf/gf/v2/encoding/gjson" 8 | "github.com/gogf/gf/v2/errors/gerror" 9 | "github.com/gogf/gf/v2/frame/g" 10 | "github.com/gogf/gf/v2/os/gfile" 11 | "github.com/gogf/gf/v2/text/gstr" 12 | "github.com/gogf/gf/v2/util/gtag" 13 | "github.com/gogf/gf/v2/util/gutil" 14 | ) 15 | 16 | var ( 17 | Tpl = cTpl{} 18 | ) 19 | 20 | type cTpl struct { 21 | g.Meta `name:"tpl" brief:"{cTplBrief}" dc:"{cTplDc}"` 22 | } 23 | 24 | const ( 25 | cTplBrief = `template parsing and building commands` 26 | cTplDc = ` 27 | The "tpl" command is used for template parsing and building purpose. 28 | It can parse either template file or folder with multiple types of values support, 29 | like json/xml/yaml/toml/ini. 30 | ` 31 | cTplParseBrief = `parse either template file or folder with multiple types of values` 32 | cTplParseEg = ` 33 | gf tpl parse -p ./template -v values.json -r 34 | gf tpl parse -p ./template -v values.json -n *.tpl -r 35 | gf tpl parse -p ./template -v values.json -d '${,}}' -r 36 | gf tpl parse -p ./template -v values.json -o ./template.parsed 37 | ` 38 | cTplSupportValuesFilePattern = `*.json,*.xml,*.yaml,*.yml,*.toml,*.ini` 39 | ) 40 | 41 | type ( 42 | cTplParseInput struct { 43 | g.Meta `name:"parse" brief:"{cTplParseBrief}" eg:"{cTplParseEg}"` 44 | Path string `name:"path" short:"p" brief:"template file or folder path" v:"required"` 45 | Pattern string `name:"pattern" short:"n" brief:"template file pattern when path is a folder, default is:*" d:"*"` 46 | Recursive bool `name:"recursive" short:"c" brief:"recursively parsing files if path is folder, default is:true" d:"true"` 47 | Values string `name:"values" short:"v" brief:"template values file/folder, support file types like: json/xml/yaml/toml/ini" v:"required"` 48 | Output string `name:"output" short:"o" brief:"output file/folder path"` 49 | Delimiters string `name:"delimiters" short:"d" brief:"delimiters for template content parsing, default is:{{,}}" d:"{{,}}"` 50 | Replace bool `name:"replace" short:"r" brief:"replace original files" orphan:"true"` 51 | } 52 | cTplParseOutput struct{} 53 | ) 54 | 55 | func init() { 56 | gtag.Sets(g.MapStrStr{ 57 | `cTplBrief`: cTplBrief, 58 | `cTplDc`: cTplDc, 59 | `cTplParseEg`: cTplParseEg, 60 | `cTplParseBrief`: cTplParseBrief, 61 | }) 62 | } 63 | 64 | func (c *cTpl) Parse(ctx context.Context, in cTplParseInput) (out *cTplParseOutput, err error) { 65 | if in.Output == "" && in.Replace == false { 66 | return nil, gerror.New(`parameter output and replace should not be both empty`) 67 | } 68 | delimiters := gstr.SplitAndTrim(in.Delimiters, ",") 69 | mlog.Debugf("delimiters input:%s, parsed:%#v", in.Delimiters, delimiters) 70 | if len(delimiters) != 2 { 71 | return nil, gerror.Newf(`invalid delimiters: %s`, in.Delimiters) 72 | } 73 | g.View().SetDelimiters(delimiters[0], delimiters[1]) 74 | valuesMap, err := c.loadValues(ctx, in.Values) 75 | if err != nil { 76 | return nil, err 77 | } 78 | if len(valuesMap) == 0 { 79 | return nil, gerror.Newf(`empty values loaded from values file/folder "%s"`, in.Values) 80 | } 81 | err = c.parsePath(ctx, valuesMap, in) 82 | if err == nil { 83 | mlog.Print("done!") 84 | } 85 | return 86 | } 87 | 88 | func (c *cTpl) parsePath(ctx context.Context, values g.Map, in cTplParseInput) (err error) { 89 | if !gfile.Exists(in.Path) { 90 | return gerror.Newf(`path "%s" does not exist`, in.Path) 91 | } 92 | var ( 93 | path string 94 | files []string 95 | relativePath string 96 | outputPath string 97 | ) 98 | path = gfile.RealPath(in.Path) 99 | if gfile.IsDir(path) { 100 | files, err = gfile.ScanDirFile(path, in.Pattern, in.Recursive) 101 | if err != nil { 102 | return err 103 | } 104 | for _, file := range files { 105 | relativePath = gstr.Replace(file, path, "") 106 | if in.Output != "" { 107 | outputPath = gfile.Join(in.Output, relativePath) 108 | } 109 | if err = c.parseFile(ctx, file, outputPath, values, in); err != nil { 110 | return 111 | } 112 | } 113 | return 114 | } 115 | if in.Output != "" { 116 | outputPath = in.Output 117 | } 118 | err = c.parseFile(ctx, path, outputPath, values, in) 119 | return 120 | } 121 | 122 | func (c *cTpl) parseFile(ctx context.Context, file string, output string, values g.Map, in cTplParseInput) (err error) { 123 | output = gstr.ReplaceByMap(output, g.MapStrStr{ 124 | `\\`: `\`, 125 | `//`: `/`, 126 | }) 127 | content, err := g.View().Parse(ctx, file, values) 128 | if err != nil { 129 | return err 130 | } 131 | if output != "" { 132 | mlog.Printf(`parse file "%s" to "%s"`, file, output) 133 | return gfile.PutContents(output, content) 134 | } 135 | if in.Replace { 136 | mlog.Printf(`parse and replace file "%s"`, file) 137 | return gfile.PutContents(file, content) 138 | } 139 | return nil 140 | } 141 | 142 | func (c *cTpl) loadValues(ctx context.Context, valuesPath string) (data g.Map, err error) { 143 | if !gfile.Exists(valuesPath) { 144 | return nil, gerror.Newf(`values file/folder "%s" does not exist`, valuesPath) 145 | } 146 | var j *gjson.Json 147 | if gfile.IsDir(valuesPath) { 148 | var valueFiles []string 149 | valueFiles, err = gfile.ScanDirFile(valuesPath, cTplSupportValuesFilePattern, true) 150 | if err != nil { 151 | return nil, err 152 | } 153 | data = make(g.Map) 154 | for _, file := range valueFiles { 155 | if j, err = gjson.Load(file); err != nil { 156 | return nil, err 157 | } 158 | gutil.MapMerge(data, j.Map()) 159 | } 160 | return 161 | } 162 | if j, err = gjson.Load(valuesPath); err != nil { 163 | return nil, err 164 | } 165 | data = j.Map() 166 | return 167 | } 168 | -------------------------------------------------------------------------------- /internal/cmd/cmd_version.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | 7 | "github.com/gogf/gf-cli/v2/internal/consts" 8 | "github.com/gogf/gf-cli/v2/utility/mlog" 9 | "github.com/gogf/gf/v2/errors/gerror" 10 | "github.com/gogf/gf/v2/frame/g" 11 | "github.com/gogf/gf/v2/os/gbuild" 12 | "github.com/gogf/gf/v2/os/gfile" 13 | "github.com/gogf/gf/v2/text/gregex" 14 | "github.com/gogf/gf/v2/text/gstr" 15 | ) 16 | 17 | var ( 18 | Version = cVersion{} 19 | ) 20 | 21 | type cVersion struct { 22 | g.Meta `name:"version" brief:"show version information of current binary"` 23 | } 24 | 25 | type cVersionInput struct { 26 | g.Meta `name:"version"` 27 | } 28 | type cVersionOutput struct{} 29 | 30 | func (c cVersion) Index(ctx context.Context, in cVersionInput) (*cVersionOutput, error) { 31 | info := gbuild.Info() 32 | if info["git"] == "" { 33 | info["git"] = "none" 34 | } 35 | mlog.Printf(`GoFrame CLI Tool %s, https://goframe.org`, consts.Version) 36 | gfVersion, err := c.getGFVersionOfCurrentProject() 37 | if err != nil { 38 | gfVersion = err.Error() 39 | } else { 40 | gfVersion = gfVersion + " in current go.mod" 41 | } 42 | mlog.Printf(`GoFrame Version: %s`, gfVersion) 43 | mlog.Printf(`CLI Installed At: %s`, gfile.SelfPath()) 44 | if info["gf"] == "" { 45 | mlog.Print(`Current is a custom installed version, no installation information.`) 46 | return nil, nil 47 | } 48 | 49 | mlog.Print(gstr.Trim(fmt.Sprintf(` 50 | CLI Built Detail: 51 | Go Version: %s 52 | GF Version: %s 53 | Git Commit: %s 54 | Build Time: %s 55 | `, info["go"], info["gf"], info["git"], info["time"]))) 56 | return nil, nil 57 | } 58 | 59 | // getGFVersionOfCurrentProject checks and returns the GoFrame version current project using. 60 | func (c cVersion) getGFVersionOfCurrentProject() (string, error) { 61 | goModPath := gfile.Join(gfile.Pwd(), "go.mod") 62 | if gfile.Exists(goModPath) { 63 | lines := gstr.SplitAndTrim(gfile.GetContents(goModPath), "\n") 64 | for _, line := range lines { 65 | line = gstr.Trim(line) 66 | // Version 1. 67 | match, err := gregex.MatchString(`^github\.com/gogf/gf\s+(.+)$`, line) 68 | if err != nil { 69 | return "", err 70 | } 71 | if len(match) <= 1 { 72 | // Version > 1. 73 | match, err = gregex.MatchString(`^github\.com/gogf/gf/v\d\s+(.+)$`, line) 74 | if err != nil { 75 | return "", err 76 | } 77 | } 78 | if len(match) > 1 { 79 | return gstr.Trim(match[1]), nil 80 | } 81 | } 82 | 83 | return "", gerror.New("cannot find goframe requirement in go.mod") 84 | } else { 85 | return "", gerror.New("cannot find go.mod") 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /internal/consts/consts.go: -------------------------------------------------------------------------------- 1 | package consts 2 | 3 | const ( 4 | Version = `v2.0.0-rc` 5 | ) 6 | -------------------------------------------------------------------------------- /internal/consts/consts_gen_dao_template_dao.go: -------------------------------------------------------------------------------- 1 | package consts 2 | 3 | const TemplateDaoDaoIndexContent = ` 4 | // ================================================================================= 5 | // This is auto-generated by GoFrame CLI tool only once. Fill this file as you wish. 6 | // ================================================================================= 7 | 8 | package dao 9 | 10 | import ( 11 | "{TplImportPrefix}/internal" 12 | ) 13 | 14 | // {TplTableNameCamelLowerCase}Dao is the data access object for table {TplTableName}. 15 | // You can define custom methods on it to extend its functionality as you wish. 16 | type {TplTableNameCamelLowerCase}Dao struct { 17 | *internal.{TplTableNameCamelCase}Dao 18 | } 19 | 20 | var ( 21 | // {TplTableNameCamelCase} is globally public accessible object for table {TplTableName} operations. 22 | {TplTableNameCamelCase} = {TplTableNameCamelLowerCase}Dao{ 23 | internal.New{TplTableNameCamelCase}Dao(), 24 | } 25 | ) 26 | 27 | // Fill with you ideas below. 28 | 29 | ` 30 | 31 | const TemplateDaoDaoInternalContent = ` 32 | // ========================================================================== 33 | // Code generated by GoFrame CLI tool. DO NOT EDIT. Created at {TplDatetime} 34 | // ========================================================================== 35 | 36 | package internal 37 | 38 | import ( 39 | "context" 40 | "github.com/gogf/gf/v2/database/gdb" 41 | "github.com/gogf/gf/v2/frame/g" 42 | ) 43 | 44 | // {TplTableNameCamelCase}Dao is the data access object for table {TplTableName}. 45 | type {TplTableNameCamelCase}Dao struct { 46 | table string // table is the underlying table name of the DAO. 47 | group string // group is the database configuration group name of current DAO. 48 | columns {TplTableNameCamelCase}Columns // columns contains all the column names of Table for convenient usage. 49 | } 50 | 51 | // {TplTableNameCamelCase}Columns defines and stores column names for table {TplTableName}. 52 | type {TplTableNameCamelCase}Columns struct { 53 | {TplColumnDefine} 54 | } 55 | 56 | // {TplTableNameCamelLowerCase}Columns holds the columns for table {TplTableName}. 57 | var {TplTableNameCamelLowerCase}Columns = {TplTableNameCamelCase}Columns{ 58 | {TplColumnNames} 59 | } 60 | 61 | // New{TplTableNameCamelCase}Dao creates and returns a new DAO object for table data access. 62 | func New{TplTableNameCamelCase}Dao() *{TplTableNameCamelCase}Dao { 63 | return &{TplTableNameCamelCase}Dao{ 64 | group: "{TplGroupName}", 65 | table: "{TplTableName}", 66 | columns: {TplTableNameCamelLowerCase}Columns, 67 | } 68 | } 69 | 70 | // DB retrieves and returns the underlying raw database management object of current DAO. 71 | func (dao *{TplTableNameCamelCase}Dao) DB() gdb.DB { 72 | return g.DB(dao.group) 73 | } 74 | 75 | // Table returns the table name of current dao. 76 | func (dao *{TplTableNameCamelCase}Dao) Table() string { 77 | return dao.table 78 | } 79 | 80 | // Columns returns all column names of current dao. 81 | func (dao *{TplTableNameCamelCase}Dao) Columns() {TplTableNameCamelCase}Columns { 82 | return dao.columns 83 | } 84 | 85 | // Group returns the configuration group name of database of current dao. 86 | func (dao *{TplTableNameCamelCase}Dao) Group() string { 87 | return dao.group 88 | } 89 | 90 | // Ctx creates and returns the Model for current DAO, It automatically sets the context for current operation. 91 | func (dao *{TplTableNameCamelCase}Dao) Ctx(ctx context.Context) *gdb.Model { 92 | return dao.DB().Model(dao.table).Safe().Ctx(ctx) 93 | } 94 | 95 | // Transaction wraps the transaction logic using function f. 96 | // It rollbacks the transaction and returns the error from function f if it returns non-nil error. 97 | // It commits the transaction and returns nil if function f returns nil. 98 | // 99 | // Note that, you should not Commit or Rollback the transaction in function f 100 | // as it is automatically handled by this function. 101 | func (dao *{TplTableNameCamelCase}Dao) Transaction(ctx context.Context, f func(ctx context.Context, tx *gdb.TX) error) (err error) { 102 | return dao.Ctx(ctx).Transaction(ctx, f) 103 | } 104 | ` 105 | -------------------------------------------------------------------------------- /internal/consts/consts_gen_dao_template_do.go: -------------------------------------------------------------------------------- 1 | package consts 2 | 3 | const TemplateGenDaoDoContent = ` 4 | // ================================================================================= 5 | // Code generated by GoFrame CLI tool. DO NOT EDIT. Created at {TplDatetime} 6 | // ================================================================================= 7 | 8 | package do 9 | 10 | {TplPackageImports} 11 | 12 | // {TplTableNameCamelCase} is the golang structure of table {TplTableName} for DAO operations like Where/Data. 13 | {TplStructDefine} 14 | ` 15 | -------------------------------------------------------------------------------- /internal/consts/consts_gen_dao_template_entity.go: -------------------------------------------------------------------------------- 1 | package consts 2 | 3 | const TemplateGenDaoEntityContent = ` 4 | // ================================================================================= 5 | // Code generated by GoFrame CLI tool. DO NOT EDIT. Created at {TplDatetime} 6 | // ================================================================================= 7 | 8 | package entity 9 | 10 | {TplPackageImports} 11 | 12 | // {TplTableNameCamelCase} is the golang structure for table {TplTableName}. 13 | {TplStructDefine} 14 | ` 15 | -------------------------------------------------------------------------------- /internal/consts/consts_gen_pbentity_template.go: -------------------------------------------------------------------------------- 1 | package consts 2 | 3 | const TemplatePbEntityMessageContent = ` 4 | // ========================================================================== 5 | // Code generated by GoFrame CLI tool. DO NOT EDIT. 6 | // ========================================================================== 7 | 8 | syntax = "proto3"; 9 | 10 | package {PackageName}; 11 | 12 | import "github.com/gogo/protobuf/gogoproto/gogo.proto"; 13 | 14 | {OptionContent} 15 | 16 | {EntityMessage} 17 | ` 18 | -------------------------------------------------------------------------------- /internal/packed/packed.go: -------------------------------------------------------------------------------- 1 | package packed 2 | -------------------------------------------------------------------------------- /internal/packed/template-mono.go: -------------------------------------------------------------------------------- 1 | package packed 2 | 3 | import "github.com/gogf/gf/v2/os/gres" 4 | 5 | func init() { 6 | if err := gres.Add("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"); err != nil { 7 | panic("add binary content to resource manager failed: " + err.Error()) 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /internal/packed/template-single.go: -------------------------------------------------------------------------------- 1 | package packed 2 | 3 | import "github.com/gogf/gf/v2/os/gres" 4 | 5 | func init() { 6 | if err := gres.Add("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"); err != nil { 7 | panic("add binary content to resource manager failed: " + err.Error()) 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /internal/service/install.go: -------------------------------------------------------------------------------- 1 | package service 2 | 3 | import ( 4 | "context" 5 | "runtime" 6 | "strings" 7 | 8 | "github.com/gogf/gf-cli/v2/utility/allyes" 9 | "github.com/gogf/gf-cli/v2/utility/mlog" 10 | "github.com/gogf/gf/v2/container/garray" 11 | "github.com/gogf/gf/v2/container/gset" 12 | "github.com/gogf/gf/v2/frame/g" 13 | "github.com/gogf/gf/v2/os/gcmd" 14 | "github.com/gogf/gf/v2/os/genv" 15 | "github.com/gogf/gf/v2/os/gfile" 16 | "github.com/gogf/gf/v2/text/gstr" 17 | "github.com/gogf/gf/v2/util/gconv" 18 | ) 19 | 20 | var ( 21 | Install = serviceInstall{} 22 | ) 23 | 24 | type serviceInstall struct{} 25 | 26 | type serviceInstallAvailablePath struct { 27 | dirPath string 28 | filePath string 29 | writable bool 30 | installed bool 31 | } 32 | 33 | func (s serviceInstall) Run(ctx context.Context) (err error) { 34 | // Ask where to install. 35 | paths := s.getInstallPathsData() 36 | if len(paths) <= 0 { 37 | mlog.Printf("no path detected, you can manually install gf by copying the binary to path folder.") 38 | return 39 | } 40 | mlog.Printf("I found some installable paths for you(from $PATH): ") 41 | mlog.Printf(" %2s | %8s | %9s | %s", "Id", "Writable", "Installed", "Path") 42 | 43 | // Print all paths status and determine the default selectedID value. 44 | var ( 45 | selectedID = -1 46 | pathSet = gset.NewStrSet() // Used for repeated items filtering. 47 | ) 48 | for id, aPath := range paths { 49 | if !pathSet.AddIfNotExist(aPath.dirPath) { 50 | continue 51 | } 52 | mlog.Printf(" %2d | %8t | %9t | %s", id, aPath.writable, aPath.installed, aPath.dirPath) 53 | if selectedID == -1 { 54 | // Use the previously installed path as the most priority choice. 55 | if aPath.installed { 56 | selectedID = id 57 | } 58 | } 59 | } 60 | // If there's no previously installed path, use the first writable path. 61 | if selectedID == -1 { 62 | // Order by choosing priority. 63 | commonPaths := garray.NewStrArrayFrom(g.SliceStr{ 64 | `/usr/local/bin`, 65 | `/usr/bin`, 66 | `/usr/sbin`, 67 | `C:\Windows`, 68 | `C:\Windows\system32`, 69 | `C:\Go\bin`, 70 | `C:\Program Files`, 71 | `C:\Program Files (x86)`, 72 | }) 73 | // Check the common installation directories. 74 | commonPaths.Iterator(func(k int, v string) bool { 75 | for id, aPath := range paths { 76 | if strings.EqualFold(aPath.dirPath, v) { 77 | selectedID = id 78 | return false 79 | } 80 | } 81 | return true 82 | }) 83 | if selectedID == -1 { 84 | selectedID = 0 85 | } 86 | } 87 | 88 | if allyes.Check() { 89 | // Use the default selectedID. 90 | mlog.Printf("please choose one installation destination [default %d]: %d", selectedID, selectedID) 91 | } else { 92 | for { 93 | // Get input and update selectedID. 94 | var ( 95 | inputID int 96 | input = gcmd.Scanf("please choose one installation destination [default %d]: ", selectedID) 97 | ) 98 | if input != "" { 99 | inputID = gconv.Int(input) 100 | } 101 | // Check if out of range. 102 | if inputID >= len(paths) || inputID < 0 { 103 | mlog.Printf("invalid install destination Id: %d", inputID) 104 | continue 105 | } 106 | selectedID = inputID 107 | break 108 | } 109 | } 110 | 111 | // Get selected destination path. 112 | dstPath := paths[selectedID] 113 | 114 | // Install the new binary. 115 | err = gfile.CopyFile(gfile.SelfPath(), dstPath.filePath) 116 | if err != nil { 117 | mlog.Printf("install gf binary to '%s' failed: %v", dstPath.dirPath, err) 118 | mlog.Printf("you can manually install gf by copying the binary to folder: %s", dstPath.dirPath) 119 | } else { 120 | mlog.Printf("gf binary is successfully installed to: %s", dstPath.dirPath) 121 | } 122 | 123 | // Uninstall the old binary. 124 | for _, aPath := range paths { 125 | // Do not delete myself. 126 | if aPath.filePath != "" && aPath.filePath != dstPath.filePath && gfile.SelfPath() != aPath.filePath { 127 | _ = gfile.Remove(aPath.filePath) 128 | } 129 | } 130 | return 131 | } 132 | 133 | // IsInstalled checks and returns whether the binary is installed. 134 | func (s serviceInstall) IsInstalled() bool { 135 | paths := s.getInstallPathsData() 136 | for _, aPath := range paths { 137 | if aPath.installed { 138 | return true 139 | } 140 | } 141 | return false 142 | } 143 | 144 | // GetInstallPathsData returns the installation paths data for the binary. 145 | func (s serviceInstall) getInstallPathsData() []serviceInstallAvailablePath { 146 | var folderPaths []serviceInstallAvailablePath 147 | // Pre generate binaryFileName. 148 | binaryFileName := "gf" + gfile.Ext(gfile.SelfPath()) 149 | switch runtime.GOOS { 150 | case "darwin": 151 | darwinInstallationCheckPaths := []string{"/usr/local/bin"} 152 | for _, v := range darwinInstallationCheckPaths { 153 | folderPaths = s.checkAndAppendToAvailablePath( 154 | folderPaths, v, binaryFileName, 155 | ) 156 | } 157 | fallthrough 158 | 159 | default: 160 | // $GOPATH/bin 161 | gopath := gfile.Join(runtime.GOROOT(), "bin") 162 | folderPaths = s.checkAndAppendToAvailablePath( 163 | folderPaths, gopath, binaryFileName, 164 | ) 165 | // Search and find the writable directory path. 166 | envPath := genv.Get("PATH", genv.Get("Path").String()).String() 167 | if gstr.Contains(envPath, ";") { 168 | for _, v := range gstr.SplitAndTrim(envPath, ";") { 169 | folderPaths = s.checkAndAppendToAvailablePath( 170 | folderPaths, v, binaryFileName, 171 | ) 172 | } 173 | } else if gstr.Contains(envPath, ":") { 174 | for _, v := range gstr.SplitAndTrim(envPath, ":") { 175 | folderPaths = s.checkAndAppendToAvailablePath( 176 | folderPaths, v, binaryFileName, 177 | ) 178 | } 179 | } else if envPath != "" { 180 | folderPaths = s.checkAndAppendToAvailablePath( 181 | folderPaths, envPath, binaryFileName, 182 | ) 183 | } else { 184 | folderPaths = s.checkAndAppendToAvailablePath( 185 | folderPaths, "/usr/local/bin", binaryFileName, 186 | ) 187 | } 188 | } 189 | return folderPaths 190 | } 191 | 192 | // checkAndAppendToAvailablePath checks if `path` is writable and already installed. 193 | // It adds the `path` to `folderPaths` if it is writable or already installed, or else it ignores the `path`. 194 | func (s serviceInstall) checkAndAppendToAvailablePath(folderPaths []serviceInstallAvailablePath, dirPath string, binaryFileName string) []serviceInstallAvailablePath { 195 | var ( 196 | filePath = gfile.Join(dirPath, binaryFileName) 197 | writable = gfile.IsWritable(dirPath) 198 | installed = gfile.Exists(filePath) 199 | ) 200 | if !writable && !installed { 201 | return folderPaths 202 | } 203 | return append( 204 | folderPaths, 205 | serviceInstallAvailablePath{ 206 | dirPath: dirPath, 207 | writable: writable, 208 | filePath: filePath, 209 | installed: installed, 210 | }) 211 | } 212 | -------------------------------------------------------------------------------- /test/testdata/tpls/tpl1.yaml: -------------------------------------------------------------------------------- 1 | server: 2 | address: {{.server.address}} -------------------------------------------------------------------------------- /test/testdata/tpls/tpl2.sql: -------------------------------------------------------------------------------- 1 | insert into {{.sql.table}} -------------------------------------------------------------------------------- /test/testdata/values.json: -------------------------------------------------------------------------------- 1 | { 2 | "server": { 3 | "address": "https://goframe.org" 4 | }, 5 | "sql":{ 6 | "table": "table_name" 7 | } 8 | } -------------------------------------------------------------------------------- /utility/allyes/allyes.go: -------------------------------------------------------------------------------- 1 | package allyes 2 | 3 | import ( 4 | "github.com/gogf/gf/v2/os/gcmd" 5 | "github.com/gogf/gf/v2/os/genv" 6 | ) 7 | 8 | const ( 9 | EnvName = "GF_CLI_ALL_YES" 10 | ) 11 | 12 | // Init initializes the package manually. 13 | func Init() { 14 | if gcmd.GetOpt("y") != nil { 15 | genv.MustSet(EnvName, "1") 16 | } 17 | } 18 | 19 | // Check checks whether option allow all yes for command. 20 | func Check() bool { 21 | return genv.Get(EnvName).String() == "1" 22 | } 23 | -------------------------------------------------------------------------------- /utility/mlog/mlog.go: -------------------------------------------------------------------------------- 1 | package mlog 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/gogf/gf/v2/frame/g" 7 | "github.com/gogf/gf/v2/os/gcmd" 8 | "github.com/gogf/gf/v2/os/genv" 9 | "github.com/gogf/gf/v2/os/glog" 10 | ) 11 | 12 | const ( 13 | headerPrintEnvName = "GF_CLI_MLOG_HEADER" 14 | ) 15 | 16 | var ( 17 | ctx = context.TODO() 18 | logger = glog.New() 19 | ) 20 | 21 | func init() { 22 | logger.SetStack(false) 23 | if genv.Get(headerPrintEnvName).String() == "1" { 24 | logger.SetHeaderPrint(true) 25 | } else { 26 | logger.SetHeaderPrint(false) 27 | } 28 | if gcmd.GetOpt("debug") != nil || gcmd.GetOpt("gf.debug") != nil { 29 | logger.SetDebug(true) 30 | } else { 31 | logger.SetDebug(false) 32 | } 33 | } 34 | 35 | // SetHeaderPrint enables/disables header printing to stdout. 36 | func SetHeaderPrint(enabled bool) { 37 | logger.SetHeaderPrint(enabled) 38 | if enabled { 39 | genv.Set(headerPrintEnvName, "1") 40 | } else { 41 | genv.Set(headerPrintEnvName, "0") 42 | } 43 | } 44 | 45 | func Print(v ...interface{}) { 46 | logger.Print(ctx, v...) 47 | } 48 | 49 | func Printf(format string, v ...interface{}) { 50 | logger.Printf(ctx, format, v...) 51 | } 52 | 53 | func Fatal(v ...interface{}) { 54 | logger.Fatal(ctx, append(g.Slice{"ERROR:"}, v...)...) 55 | } 56 | 57 | func Fatalf(format string, v ...interface{}) { 58 | logger.Fatalf(ctx, "ERROR: "+format, v...) 59 | } 60 | 61 | func Debug(v ...interface{}) { 62 | logger.Debug(ctx, append(g.Slice{"DEBUG:"}, v...)...) 63 | } 64 | 65 | func Debugf(format string, v ...interface{}) { 66 | logger.Debugf(ctx, "DEBUG: "+format, v...) 67 | } 68 | -------------------------------------------------------------------------------- /utility/utils/utils.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "fmt" 5 | "github.com/gogf/gf/v2/os/gproc" 6 | ) 7 | 8 | var ( 9 | // gofmtPath is the binary path of command `gofmt`. 10 | gofmtPath = gproc.SearchBinaryPath("gofmt") 11 | ) 12 | 13 | // GoFmt formats the source file using command `gofmt -w -s PATH`. 14 | func GoFmt(path string) { 15 | if gofmtPath != "" { 16 | gproc.ShellExec(fmt.Sprintf(`%s -w -s %s`, gofmtPath, path)) 17 | } 18 | } 19 | --------------------------------------------------------------------------------