Compare commits

..

2 Commits

Author SHA1 Message Date
f63747e876 Merge branch 'master' of https://github.com/gogf/gf 2023-01-11 19:20:21 +08:00
2854daded6 v2.3.0 2023-01-09 14:39:42 +08:00
1263 changed files with 24717 additions and 74547 deletions

View File

@ -1,2 +0,0 @@
ignore:
- "cmd" # ignore cmd folders and all its contents

38
.github/ISSUE_TEMPLATE.MD vendored Normal file
View File

@ -0,0 +1,38 @@
<!-- Please answer these questions before submitting your issue. Thanks! -->
<!-- 为高效处理您的疑问如果觉得是BUG类问题请您务必提供可复现该问题的最小可运行代码否则issue可能会被延期处理 -->
<!-- 为高效处理您的疑问如果觉得是BUG类问题请您务必提供可复现该问题的最小可运行代码否则issue可能会被延期处理 -->
<!-- 为高效处理您的疑问如果觉得是BUG类问题请您务必提供可复现该问题的最小可运行代码否则issue可能会被延期处理 -->
<!-- 重要的事情说三遍! -->
### 1. What version of `Go` and system type/arch are you using?
<!--
Please paste the output of command `go version` from your terminal.
What expect to see is like: `go 1.12, linux/amd64`
-->
### 2. What version of `GoFrame` are you using?
<!-- You can find the GF version from your `go.mod`, or from the `version.go` in `GF` -->
### 3. Can this issue be re-produced with the latest release?
### 4. What did you do?
<!--
If possible, provide a copy of shortest codes for reproducing the error.
A complete runnable program is best.
-->
### 5. What did you expect to see?
### 6. What did you see instead?

View File

@ -1,71 +0,0 @@
# https://docs.github.com/en/communities/using-templates-to-encourage-useful-issues-and-pull-requests/configuring-issue-templates-for-your-repository#creating-issue-forms
# https://docs.github.com/en/communities/using-templates-to-encourage-useful-issues-and-pull-requests/syntax-for-githubs-form-schema
name: Bugs
description: GoFrame command, module, or anything else
title: "os/gtime: issue title"
labels:
- bug
body:
- type: markdown
attributes:
value: |
Thanks for helping us improve! 🙏 Please answer these questions and provide as much information as possible about your problem.
The issue title uses the format of the package name goes before the colon. Thanks!
标题使用包名在冒号前的格式!谢谢!
- type: input
id: go-version
attributes:
label: Go version
description: |
What version of Go are you using (`go version`)?
placeholder: ex. go version go1.20.7 darwin/arm64
validations:
required: true
- type: input
id: gf-version
attributes:
label: GoFrame version
description: |
What version of GoFrame are you using (`gf version`)?
placeholder: ex. 2.7.0
validations:
required: true
- type: dropdown
id: is-reproducible
attributes:
label: Can this bug be reproduced with the latest release?
options:
- Option Yes
- Option No
validations:
required: true
- type: textarea
id: what-did-you-do
attributes:
label: "What did you do?"
description: "If possible, provide a recipe for reproducing the error. A complete runnable program is good. A link on [go.dev/play](https://go.dev/play) is best."
validations:
required: true
- type: textarea
id: actual-behavior
attributes:
label: "What did you see happen?"
description: Command invocations and their associated output, functions with their arguments and return results, full stacktraces for panics (upload a file if it is very long), etc. Prefer copying text output over using screenshots.
validations:
required: true
- type: textarea
id: expected-behavior
attributes:
label: "What did you expect to see?"
description: Why is the current output incorrect, and any additional context we may need to understand the issue.
validations:
required: true

View File

@ -1,32 +0,0 @@
# https://docs.github.com/en/communities/using-templates-to-encourage-useful-issues-and-pull-requests/configuring-issue-templates-for-your-repository#creating-issue-forms
# https://docs.github.com/en/communities/using-templates-to-encourage-useful-issues-and-pull-requests/syntax-for-githubs-form-schema
name: Enhancements
description: Enhance an idea for this project
title: "os/gtime: issue title"
labels:
- enhancement
body:
- type: markdown
attributes:
value: |
Thanks for helping us improve! 🙏 Please answer these questions and provide as much information as possible about your problem.
The issue title uses the format of the package name goes before the colon. Thanks!
标题使用包名在冒号前的格式!谢谢!
- type: textarea
id: description
attributes:
label: "Description"
description: "Please describe your idea in detail."
validations:
required: true
- type: textarea
id: additional
attributes:
label: "Additional"
validations:
required: false

View File

@ -1,48 +0,0 @@
# https://docs.github.com/en/communities/using-templates-to-encourage-useful-issues-and-pull-requests/configuring-issue-templates-for-your-repository#creating-issue-forms
# https://docs.github.com/en/communities/using-templates-to-encourage-useful-issues-and-pull-requests/syntax-for-githubs-form-schema
name: Features
description: Suggest an idea for this project
title: "os/gtime: issue title"
labels:
- feature
body:
- type: markdown
attributes:
value: |
Thanks for helping us improve! 🙏 Please answer these questions and provide as much information as possible about your problem.
The issue title uses the format of the package name goes before the colon. Thanks!
标题使用包名在冒号前的格式!谢谢!
- type: dropdown
id: is-problem
attributes:
label: Is your feature request related to a problem?
options:
- Option Yes
- Option No
validations:
required: true
- type: textarea
id: description-solution
attributes:
label: "Describe the solution you'd like"
validations:
required: true
- type: textarea
id: description-considered
attributes:
label: "Describe alternatives you've considered"
validations:
required: true
- type: textarea
id: additional
attributes:
label: "Additional"
validations:
required: false

View File

@ -1,25 +0,0 @@
# https://docs.github.com/en/communities/using-templates-to-encourage-useful-issues-and-pull-requests/configuring-issue-templates-for-your-repository#creating-issue-forms
# https://docs.github.com/en/communities/using-templates-to-encourage-useful-issues-and-pull-requests/syntax-for-githubs-form-schema
name: Questions
description: I want to ask a question
title: "os/gtime: issue title"
labels:
- question
body:
- type: markdown
attributes:
value: |
Please read the document carefully. 请先仔细阅读文档
The issue title uses the format of the package name goes before the colon. Thanks!
标题使用包名在冒号前的格式!谢谢!
- type: textarea
id: ask
attributes:
label: "What do you want to ask?"
description: "Please describe the details of your questions. 请详细描述你的问题。"
validations:
required: true

View File

@ -1,44 +0,0 @@
**Please ensure you adhere to every item in this list.**
+ The PR title is formatted as follows: `<type>[optional scope]: <description>` For example, `fix(os/gtime): fix time zone issue`
+ `<type>` is mandatory and can be one of `fix`, `feat`, `build`, `ci`, `docs`, `style`, `refactor`, `perf`, `test`, `chore`
+ fix: Used when a bug has been fixed.
+ feat: Used when a new feature has been added.
+ build: Used for modifications to the project build system, such as changes to dependencies, external interfaces, or upgrading Node version.
+ ci: Used for modifications to continuous integration processes, such as changes to Travis, Jenkins workflow configurations.
+ docs: Used for modifications to documentation, such as changes to README files, API documentation, etc.
+ style: Used for changes to code style, such as adjustments to indentation, spaces, blank lines, etc.
+ refactor: Used for code refactoring, such as changes to code structure, variable names, function names, without altering functionality.
+ perf: Used for performance optimization, such as improving code performance, reducing memory usage, etc.
+ test: Used for modifications to test cases, such as adding, deleting, or modifying test cases for code.
+ chore: Used for modifications to non-business-related code, such as changes to build processes or tool configurations.
+ After `<type>`, specify the affected package name or scope in parentheses, for example, `(os/gtime)`.
+ The part after the colon uses the verb tense + phrase that completes the blank in
+ Lowercase verb after the colon
+ No trailing period
+ Keep the title as short as possible. ideally under 76 characters or shorter
+ [Reference Documentation](https://www.conventionalcommits.org/en/v1.0.0/)
+ If there is a corresponding issue, add either `Fixes #1234` or `Updates #1234`
(the latter if this is not a complete fix) to this comment
+ Delete these instructions once you have read and applied them
**提交前请遵守每个事项,感谢!**
+ PR 标题格式如下:`<类型>[可选 范围]: <描述>` 例如 `fix(os/gtime): fix time zone issue`
+ `<类型>`是必须的,可以是 `fix`、`feat`、`build`、`ci`、`docs`、`style`、`refactor`、`perf`、`test`、`chore` 中的一个
+ fix: 用于修复了一个 bug
+ feat: 用于新增了一个功能
+ build: 用于修改项目构建系统,例如修改依赖库、外部接口或者升级 Node 版本等
+ ci: 用于修改持续集成流程,例如修改 Travis、Jenkins 等工作流配置
+ docs: 用于修改文档,例如修改 README 文件、API 文档等
+ style: 用于修改代码的样式,例如调整缩进、空格、空行等
+ refactor: 用于重构代码,例如修改代码结构、变量名、函数名等但不修改功能逻辑
+ perf: 用于优化性能,例如提升代码的性能、减少内存占用等
+ test: 用于修改测试用例,例如添加、删除、修改代码的测试用例等
+ chore: 用于对非业务性代码进行修改,例如修改构建流程或者工具配置等
+ `<类型>`后在括号中填写受影响的包名或范围,例如 `(os/gtime)`
+ 冒号后使用动词时态 + 短语
+ 冒号后的动词小写
+ 不要有结尾句号
+ 标题尽量保持简短,最好在 76 个字符或更短
+ [参考文档](https://www.conventionalcommits.org/zh-hans/v1.0.0/)
+ 如果有对应的 issue请在此评论中添加 `Fixes #1234`,如果不是完全修复则添加 `Updates #1234`
+ 应用这些规则后删除所有的说明

BIN
.github/logo.png vendored

Binary file not shown.

Before

Width:  |  Height:  |  Size: 232 KiB

49
.github/workflows/build_and_test.sh vendored Normal file
View File

@ -0,0 +1,49 @@
#!/usr/bin/env bash
for file in `find . -name go.mod`; do
dirpath=$(dirname $file)
echo $dirpath
# package oracle needs golang >= v1.17
if [ "oracle" = $(basename $dirpath) ]; then
if ! go version|grep -q "1.17"; then
echo "ignore oracle as go version: $(go version)"
continue 1
fi
fi
# package kuhecm needs golang >= v1.18
if [ "kubecm" = $(basename $dirpath) ]; then
if ! go version|grep -q "1.18"; then
echo "ignore kubecm as go version: $(go version)"
continue 1
fi
fi
# package example needs golang >= v1.18
if [ "example" = $(basename $dirpath) ]; then
if ! go version|grep -q "1.18"; then
echo "ignore example as go version: $(go version)"
continue 1
fi
fi
# package cmd/gf needs golang >= v1.18
if [ "gf" = $(basename $dirpath) ]; then
if ! go version|grep -q "1.18"; then
echo "ignore example as go version: $(go version)"
continue 1
fi
fi
cd $dirpath
go mod tidy
go build ./...
go test ./... -race -coverprofile=coverage.out -covermode=atomic -coverpkg=./...,github.com/gogf/gf/... || exit 1
if grep -q "/gogf/gf/.*/v2" go.mod; then
sed -i "s/gogf\/gf\(\/.*\)\/v2/gogf\/gf\/v2\1/g" coverage.out
fi
cd -
done

View File

@ -1,103 +0,0 @@
#!/usr/bin/env bash
coverage=$1
# find all path that contains go.mod.
for file in `find . -name go.mod`; do
dirpath=$(dirname $file)
echo $dirpath
# ignore mssql tests as its docker service failed
# TODO remove this ignoring codes after the mssql docker service OK
if [ "mssql" = $(basename $dirpath) ]; then
continue 1
fi
if [[ $file =~ "/testdata/" ]]; then
echo "ignore testdata path $file"
continue 1
fi
# package kuhecm was moved to sub ci procedure.
if [ "kubecm" = $(basename $dirpath) ]; then
continue 1
fi
# package consul needs golang >= v1.19
if [ "consul" = $(basename $dirpath) ]; then
if ! go version|grep -qE "go1.[2-9][0-9]"; then
echo "ignore consul as go version: $(go version)"
continue 1
fi
fi
# package etcd needs golang >= v1.19
if [ "etcd" = $(basename $dirpath) ]; then
if ! go version|grep -qE "go1.[2-9][0-9]"; then
echo "ignore etcd as go version: $(go version)"
continue 1
fi
fi
# package polaris needs golang >= v1.19
if [ "polaris" = $(basename $dirpath) ]; then
if ! go version|grep -qE "go1.[2-9][0-9]"; then
echo "ignore polaris as go version: $(go version)"
continue 1
fi
fi
# package example needs golang >= v1.20
if [ "example" = $(basename $dirpath) ]; then
if ! go version|grep -qE "go1.[2-9][0-9]"; then
echo "ignore example as go version: $(go version)"
continue 1
fi
echo "the example directory only needs to be built, not unit tests and coverage tests."
cd $dirpath
go mod tidy
go build ./...
cd -
continue 1
fi
# package otlpgrpc needs golang >= v1.20
if [ "otlpgrpc" = $(basename $dirpath) ]; then
if ! go version|grep -qE "go1.[2-9][0-9]"; then
echo "ignore otlpgrpc as go version: $(go version)"
continue 1
fi
fi
# package otlphttp needs golang >= v1.20
if [ "otlphttp" = $(basename $dirpath) ]; then
if ! go version|grep -qE "go1.[2-9][0-9]"; then
echo "ignore otlphttp as go version: $(go version)"
continue 1
fi
fi
# package otelmetric needs golang >= v1.20
if [ "otelmetric" = $(basename $dirpath) ]; then
if ! go version|grep -qE "go1.[2-9][0-9]"; then
echo "ignore otelmetric as go version: $(go version)"
continue 1
fi
fi
cd $dirpath
go mod tidy
go build ./...
# check coverage
if [ "${coverage}" = "coverage" ]; then
go test ./... -race -coverprofile=coverage.out -covermode=atomic -coverpkg=./...,github.com/gogf/gf/... || exit 1
if grep -q "/gogf/gf/.*/v2" go.mod; then
sed -i "s/gogf\/gf\(\/.*\)\/v2/gogf\/gf\/v2\1/g" coverage.out
fi
else
go test ./... -race || exit 1
fi
cd -
done

View File

@ -1,27 +0,0 @@
#!/usr/bin/env bash
coverage=$1
# find all path that contains go.mod.
for file in `find . -name go.mod`; do
dirpath=$(dirname $file)
echo $dirpath
# package kuhecm needs golang >= v1.19
if [ "kubecm" = $(basename $dirpath) ]; then
if ! go version|grep -qE "go1.[2-9][0-9]"; then
echo "ignore kubecm as go version: $(go version)"
continue 1
fi
else
continue 1
fi
cd $dirpath
go mod tidy
go build ./...
go test ./... -race || exit 1
cd -
done

View File

@ -1,67 +0,0 @@
# The sub codes build and unit testing running workflow.
# It maintains the ci of unimportant packages.
name: GoFrame Sub CI
on:
push:
branches:
- master
- develop
- personal/**
- feature/**
- enhance/**
- fix/**
pull_request:
branches:
- master
- develop
- personal/**
- feature/**
- enhance/**
- fix/**
# This allows a subsequently queued workflow run to interrupt previous runs
concurrency:
group: '${{ github.workflow }} @ ${{ github.event.pull_request.head.label || github.head_ref || github.ref }}'
cancel-in-progress: true
env:
TZ: "Asia/Shanghai"
jobs:
code-test:
runs-on: ubuntu-latest
strategy:
matrix:
go-version: [ "1.20", "1.21", "1.22", "1.23" ]
goarch: [ "386", "amd64" ]
steps:
- name: Setup Timezone
uses: szenius/set-timezone@v2.0
with:
timezoneLinux: "Asia/Shanghai"
- name: Checkout Repository
uses: actions/checkout@v4
- name: Start Minikube
uses: medyagh/setup-minikube@master
- name: Setup Golang ${{ matrix.go-version }}
uses: actions/setup-go@v5
with:
go-version: ${{ matrix.go-version }}
cache-dependency-path: '**/go.sum'
- name: Before Script
run: bash .github/workflows/before_script.sh
- name: Build & Test
run: bash .github/workflows/ci-sub.sh

View File

@ -1,4 +1,4 @@
name: GoFrame Release
name: GoFrame CLI Build Release
on:
push:
@ -16,12 +16,12 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout Github Code
uses: actions/checkout@v4
uses: actions/checkout@v2
- name: Set Up Golang Environment
uses: actions/setup-go@v5
uses: actions/setup-go@v2
with:
go-version: 1.22.5
go-version: 1.17
- name: Build CLI Binary
run: |
@ -34,7 +34,7 @@ jobs:
- name: Build CLI Binary For All Platform
run: |
cd cmd/gf
gf build main.go -n gf -a all -s all -p temp
gf build main.go -n gf -a all -s all
- name: Move Files Before Release
run: |
@ -47,18 +47,18 @@ jobs:
- name: Create Github Release
id: create_release
uses: softprops/action-gh-release@v1
uses: actions/create-release@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
tag_name: ${{ github.ref }}
name: GoFrame Release ${{ github.ref }}
release_name: GoFrame Release ${{ github.ref }}
draft: false
prerelease: false
- name: Upload Release Asset
id: upload-release-asset
uses: alexellis/upload-assets@0.4.0
uses: alexellis/upload-assets@0.2.2
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:

View File

@ -1,7 +0,0 @@
{
"node_name": "consul-client",
"data_dir": "/consul/data",
"retry_join":[
"consul-server"
]
}

View File

@ -1,31 +0,0 @@
version: '3.7'
services:
consul-server:
image: loads/consul:1.15
container_name: consul-server
restart: always
volumes:
- ./server.json:/consul/config/server.json:ro
networks:
- consul
ports:
- "8500:8500"
- "8600:8600/tcp"
- "8600:8600/udp"
command: "agent"
consul-client:
image: loads/consul:1.15
container_name: consul-client
restart: always
volumes:
- ./client.json:/consul/config/client.json:ro
networks:
- consul
command: "agent"
networks:
consul:
driver: bridge

View File

@ -1,12 +0,0 @@
{
"node_name": "consul-server",
"server": true,
"bootstrap" : true,
"ui_config": {
"enabled" : true
},
"data_dir": "/consul/data",
"addresses": {
"http" : "0.0.0.0"
}
}

View File

@ -1,38 +0,0 @@
name: Deploy to GitHub Pages
on:
push:
branches:
- 'doc-build'
schedule:
- cron: '0 15 * * *'
jobs:
deploy:
name: Deploy to GitHub Pages
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
ref: doc-build
- uses: actions/setup-node@v4
with:
node-version: 18
cache: npm
- name: Set Up Golang Environment
uses: actions/setup-go@v5
with:
go-version: 1.22.5
cache: false
- name: download goframe docs
run: ./download.sh
- name: Install dependencies
run: npm ci
- name: Build website
run: npm run build
- name: Deploy to GitHub Pages
uses: peaceiris/actions-gh-pages@v4
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
publish_dir: ./build
cname: pages.goframe.org

View File

@ -1,4 +1,3 @@
# The main codes build and unit testing running workflow.
name: GoFrame Main CI
@ -32,19 +31,10 @@ env:
jobs:
code-test:
runs-on: ubuntu-20.04
runs-on: ubuntu-latest
# Service containers to run with `code-test`
services:
# Etcd service.
# docker run -d --name etcd -p 2379:2379 -e ALLOW_NONE_AUTHENTICATION=yes loads/etcd:3.4.24
etcd:
image: loads/etcd:3.4.24
env:
ALLOW_NONE_AUTHENTICATION: yes
ports:
- 2379:2379
# Redis backend server.
redis:
image : loads/redis:7.0
@ -58,36 +48,17 @@ jobs:
- 6379:6379
# MySQL backend server.
# docker run -d --name mysql \
# -p 3306:3306 \
# -e MYSQL_DATABASE=test \
# -e MYSQL_ROOT_PASSWORD=12345678 \
# loads/mysql:5.7
mysql:
image: loads/mysql:5.7
env:
MYSQL_DATABASE : test
MYSQL_ROOT_PASSWORD: 12345678
ports:
# Maps tcp port 3306 on service container to the host
- 3306:3306
# MariaDb backend server.
mariadb:
image: loads/mariadb:10.4
env:
MARIADB_DATABASE: test
MARIADB_ROOT_PASSWORD: 12345678
ports:
- 3307:3306
# PostgreSQL backend server.
# docker run -d --name postgres \
# -p 5432:5432 \
# -e POSTGRES_PASSWORD=12345678 \
# -e POSTGRES_USER=postgres \
# -e POSTGRES_DB=test \
# -v postgres:/Users/john/Temp/postgresql/data \
# loads/postgres:13
# docker run -d --name postgres -p 5432:5432 -e POSTGRES_PASSWORD=12345678 -e POSTGRES_USER=postgres -e POSTGRES_DB=test -v postgres:/Users/john/Temp/postgresql/data loads/postgres:13
postgres:
image: loads/postgres:13
env:
@ -105,14 +76,6 @@ jobs:
--health-retries 5
# MSSQL backend server.
# docker run \
# -p 1433:1433 \
# -e ACCEPT_EULA=Y \
# -e SA_PASSWORD=LoremIpsum86 \
# -e MSSQL_DB=test \
# -e MSSQL_USER=root \
# -e MSSQL_PASSWORD=LoremIpsum86 \
# loads/mssqldocker:14.0.3391.2
mssql:
image: loads/mssqldocker:14.0.3391.2
env:
@ -131,9 +94,7 @@ jobs:
--health-retries 10
# ClickHouse backend server.
# docker run -d --name clickhouse \
# -p 9000:9000 -p 8123:8123 -p 9001:9001 \
# loads/clickhouse-server:22.1.3.7
# docker run -d --name clickhouse -p 9000:9000 -p 8123:8123 -p 9001:9001 loads/clickhouse-server:22.1.3.7
clickhouse-server:
image: loads/clickhouse-server:22.1.3.7
ports:
@ -142,30 +103,14 @@ jobs:
- 9001:9001
# Polaris backend server.
# docker run -d --name polaris \
# -p 8090:8090 -p 8091:8091 -p 8093:8093 -p 9090:9090 -p 9091:9091 \
# loads/polaris-server-standalone:1.11.2
#
# docker run -d --name polaris \
# -p 8090:8090 -p 8091:8091 -p 8093:8093 -p 9090:9090 -p 9091:9091 \
# loads/polaris-standalone:v1.16.3
polaris:
image: loads/polaris-standalone:v1.17.2
image: loads/polaris-server-standalone:1.11.2
ports:
- 8090:8090
- 8091:8091
- 8093:8093
- 9090:9090
- 9091:9091
# Oracle 11g server.
# docker run \
# -e ORACLE_ALLOW_REMOTE=true \
# -e ORACLE_SID=XE \
# -e ORACLE_DB_USER_NAME=system \
# -e ORACLE_DB_PASSWORD=oracle \
# -p 1521:1521 \
# loads/oracle-xe-11g-r2:11.2.0
# Oracle 11g server
oracle-server:
image: loads/oracle-xe-11g-r2:11.2.0
env:
@ -177,7 +122,6 @@ jobs:
- 1521:1521
# dm8 server
# docker run -p 5236:5236 loads/dm:v8.1.2.128_ent_x86_64_ctm_pack4
dm-server:
image: loads/dm:v8.1.2.128_ent_x86_64_ctm_pack4
ports:
@ -190,76 +134,65 @@ jobs:
strategy:
matrix:
go-version: [ "1.20", "1.21", "1.22", "1.23" ]
go-version: [ "1.15", "1.16", "1.17", "1.18" ]
goarch: [ "386", "amd64" ]
steps:
# TODO: szenius/set-timezone update to node16
# sudo ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime
- name: Setup Timezone
uses: szenius/set-timezone@v2.0
uses: szenius/set-timezone@v1.1
with:
timezoneLinux: "Asia/Shanghai"
- name: Checkout Repository
uses: actions/checkout@v4
uses: actions/checkout@v3
- name: Start Apollo Containers
run: docker compose -f ".github/workflows/apollo/docker-compose.yml" up -d --build
run: docker-compose -f ".github/workflows/apollo/docker-compose.yml" up -d --build
- name: Start Nacos Containers
run: docker compose -f ".github/workflows/nacos/docker-compose.yml" up -d --build
run: docker-compose -f ".github/workflows/nacos/docker-compose.yml" up -d --build
- name: Start Redis Cluster Containers
run: docker compose -f ".github/workflows/redis/docker-compose.yml" up -d --build
run: docker-compose -f ".github/workflows/redis/docker-compose.yml" up -d --build
- name: Start Consul Containers
run: docker compose -f ".github/workflows/consul/docker-compose.yml" up -d --build
- name: Start Minikube
uses: medyagh/setup-minikube@master
- name: Setup Golang ${{ matrix.go-version }}
uses: actions/setup-go@v5
uses: actions/setup-go@v3
with:
go-version: ${{ matrix.go-version }}
cache-dependency-path: '**/go.sum'
- name: Install Protoc
uses: arduino/setup-protoc@v2
- name: Setup Golang caches
uses: actions/cache@v3
with:
version: "23.x"
repo-token: ${{ secrets.GITHUB_TOKEN }}
- name: Install the protocol compiler plugins for Go
run: |
go install google.golang.org/protobuf/cmd/protoc-gen-go@latest
go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest
export PATH="$PATH:$(go env GOPATH)/bin"
path: |
~/go/pkg/mod
~/.cache/go-build
~/Library/Caches/go-build
~\AppData\Local\go-build
key: ${{ runner.os }}-go-${{ matrix.go-version }}-${{ hashFiles('**/go.sum') }}
restore-keys: |
${{ runner.os }}-go-${{ matrix.go-version }}-
- name: Before Script
run: bash .github/workflows/before_script.sh
- name: Build & Test
if: ${{ (github.event_name == 'push' && github.ref != 'refs/heads/master') || github.event_name == 'pull_request' }}
run: bash .github/workflows/ci-main.sh
- name: Build & Test & Coverage
if: ${{ github.event_name == 'push' && github.ref == 'refs/heads/master' }}
run: bash .github/workflows/ci-main.sh coverage
run: bash .github/workflows/build_and_test.sh
- name: Stop Redis Cluster Containers
run: docker compose -f ".github/workflows/redis/docker-compose.yml" down
run: docker-compose -f ".github/workflows/redis/docker-compose.yml" down
- name: Stop Apollo Containers
run: docker compose -f ".github/workflows/apollo/docker-compose.yml" down
run: docker-compose -f ".github/workflows/apollo/docker-compose.yml" down
- name: Stop Nacos Containers
run: docker compose -f ".github/workflows/nacos/docker-compose.yml" down
- name: Stop Consul Containers
run: docker compose -f ".github/workflows/consul/docker-compose.yml" down
run: docker-compose -f ".github/workflows/nacos/docker-compose.yml" down
- name: Report Coverage
uses: codecov/codecov-action@v4
if: ${{ github.event_name == 'push' && github.ref == 'refs/heads/master' }}
uses: codecov/codecov-action@v3
with:
flags: go-${{ matrix.go-version }}-${{ matrix.goarch }}
token: ${{ secrets.CODECOV_TOKEN }}

View File

@ -1,28 +0,0 @@
on:
push:
branches:
- master
tags:
- "*"
name: Sync to Gitee
jobs:
run:
name: Run
runs-on: ubuntu-latest
steps:
- name: Checkout source code
uses: actions/checkout@v4
- name: Mirror GitHub to Gitee
uses: Yikun/hub-mirror-action@v1.4
with:
src: github/gogf
dst: gitee/johng
dst_key: ${{ secrets.GITEE_PRIVATE_KEY }}
dst_token: ${{ secrets.GITEE_TOKEN }}
src_account_type: org
dst_account_type: user
timeout: 600
debug: true
force_update: true
static_list: "gf"

View File

@ -17,38 +17,34 @@ name: GolangCI-Lint
on:
push:
branches:
- master
- develop
- personal/**
- feature/**
- enhance/**
- fix/**
- master
- develop
- personal/**
- feature/**
- enhance/**
- fix/**
pull_request:
branches:
- master
- develop
- personal/**
- feature/**
- enhance/**
- fix/**
- master
- develop
- personal/**
- feature/**
- enhance/**
- fix/**
jobs:
golangci:
strategy:
matrix:
go-version: [ '1.20','1.21.4','1.22', '1.23' ]
go-version: [1.15.x,1.16.x,1.17.x,1.18.x]
name: golangci-lint
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup Golang ${{ matrix.go-version }}
uses: actions/setup-go@v5
with:
go-version: ${{ matrix.go-version }}
- uses: actions/setup-go@v3
- uses: actions/checkout@v3
- name: golangci-lint
uses: golangci/golangci-lint-action@v6
uses: golangci/golangci-lint-action@v3.3.0
with:
# Required: the version of golangci-lint is required and must be specified without patch version: we always use the latest patch version.
version: v1.60.1
version: latest
args: --timeout 3m0s

View File

@ -14,8 +14,8 @@ permissions:
jobs:
issue-check-inactive:
permissions:
issues: write # for actions-cool/issues-helper to update issues
# pull-requests: write # for actions-cool/issues-helper to update PRs
issues: write # for actions-cool/issues-helper to update issues
# pull-requests: write # for actions-cool/issues-helper to update PRs
runs-on: ubuntu-latest
steps:
- name: check-inactive
@ -25,4 +25,4 @@ jobs:
inactive-label: 'inactive'
inactive-day: 7
issue-state: open
exclude-labels: 'bug,planned,$exclude-empty'
exclude-labels: 'bug,$exclude-empty'

View File

@ -1,4 +1,4 @@
# 规则描述:每天凌晨 4 点 (GMT+8) 执行一次,将最近 30 天没有活跃且非 BUGISSUE 关闭
# 规则描述:每天凌晨4点(GMT+8)执行一次将最近30天没有活跃且非BUGISSUE关闭
name: Issue Close Inactive
on:

View File

@ -1,4 +1,4 @@
## 规则描述:当 issue 被标记为 help wanted 时,增加评论
## 规则描述当issue被标记为help wanted 时,增加评论
name: Issue Labeled

View File

@ -1,4 +1,4 @@
# 规则描述:在 issue 没有活跃且尚未被关闭期间,若 issue 作者更新或评论该 ISSUE则移除其 inactive 标签
# 规则描述在issue没有活跃且尚未被关闭期间若issue作者更新或评论该ISSUE则移除其inactive标签
name: Issue Remove Inactive
on:
@ -17,13 +17,13 @@ jobs:
issue-remove-inactive:
permissions:
issues: write # for actions-cool/issues-helper to update issues
# pull-requests: write # for actions-cool/issues-helper to update PRs
# pull-requests: write # for actions-cool/issues-helper to update PRs
runs-on: ubuntu-latest
steps:
- name: remove inactive
if: github.event.issue.state == 'open'
if: github.event.issue.state == 'open' && github.actor == github.event.issue.user.login
uses: actions-cool/issues-helper@v3
with:
actions: 'remove-labels'
issue-number: ${{ github.event.issue.number }}
labels: 'inactive'
labels: 'inactive'

View File

@ -1,4 +1,4 @@
# 规则描述:将需要提供更多细节且暂未关闭的 issue issue 作者评论后,移除 need more details 标签
# 规则描述将需要提供更多细节且暂未关闭的issue在issue作者评论后移除 need more details 标签
name: Issue Remove Need More Details
on:
@ -16,8 +16,8 @@ permissions:
jobs:
issue-remove-need-more-details:
permissions:
issues: write # for actions-cool/issues-helper to update issues
# pull-requests: write # for actions-cool/issues-helper to update PRs
issues: write # for actions-cool/issues-helper to update issues
# pull-requests: write # for actions-cool/issues-helper to update PRs
runs-on: ubuntu-latest
steps:
- name: remove need more details

View File

@ -1,19 +0,0 @@
# https://github.com/usthe/issues-translate-action
name: 'Issue Translator'
on:
issue_comment:
types: [created]
issues:
types: [opened]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: usthe/issues-translate-action@v2.7
with:
IS_MODIFY_TITLE: true
# not require, default false. Decide whether to modify the issue title
# if true, the robot account @Issues-translate-bot must have modification permissions,
# invite @Issues-translate-bot to your project or use your custom bot.
CUSTOM_BOT_NOTE: Bot detected the issue body's language is not English, translate it automatically. 👯👭🏻🧑‍🤝‍🧑👫🧑🏿‍🤝‍🧑🏻👩🏾‍🤝‍👨🏿👬🏿

View File

@ -15,6 +15,22 @@ services:
interval: 5s
timeout: 3s
retries: 10
depends_on:
mysql:
condition: service_healthy
mysql:
build:
context: .
dockerfile: ./image/mysql/Dockerfile
container_name: mysql
env_file:
- ./env/mysql.env
healthcheck:
test: [ "CMD", "mysqladmin" ,"ping", "-h", "localhost" ]
interval: 5s
timeout: 3s
retries: 10
initializer:
image: loads/curl:latest

4
.github/workflows/nacos/env/mysql.env vendored Normal file
View File

@ -0,0 +1,4 @@
MYSQL_ROOT_PASSWORD=root
MYSQL_DATABASE=nacos_devtest
MYSQL_USER=nacos
MYSQL_PASSWORD=nacos

View File

@ -1,2 +1,9 @@
PREFER_HOST_MODE=hostname
MODE=standalone
SPRING_DATASOURCE_PLATFORM=mysql
MYSQL_SERVICE_HOST=mysql
MYSQL_SERVICE_DB_NAME=nacos_devtest
MYSQL_SERVICE_PORT=3306
MYSQL_SERVICE_USER=nacos
MYSQL_SERVICE_PASSWORD=nacos
MYSQL_SERVICE_DB_PARAM=characterEncoding=utf8&connectTimeout=1000&socketTimeout=3000&autoReconnect=true&useSSL=false&allowPublicKeyRetrieval=true

View File

@ -0,0 +1,5 @@
FROM loads/mysql:5.7
ADD https://raw.githubusercontent.com/alibaba/nacos/develop/distribution/conf/mysql-schema.sql /docker-entrypoint-initdb.d/nacos-mysql.sql
RUN chown -R mysql:mysql /docker-entrypoint-initdb.d/nacos-mysql.sql
EXPOSE 3306
CMD ["mysqld", "--character-set-server=utf8mb4", "--collation-server=utf8mb4_unicode_ci"]

View File

@ -1,53 +0,0 @@
name: Sonarcloud Scan
on:
schedule:
# Weekly on Saturdays.
- cron: '30 1 * * 6'
push:
branches: [ master ]
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
# Declare default permissions as read only.
permissions: read-all
jobs:
analysis:
name: Scorecards analysis
runs-on: ubuntu-22.04
permissions:
# Needed to upload the results to code-scanning dashboard.
security-events: write
# Used to receive a badge. (Upcoming feature)
id-token: write
# Needs for private repositories.
contents: read
actions: read
steps:
- name: "Checkout code"
uses: actions/checkout@v4
with:
persist-credentials: false
- name: "Run analysis"
uses: ossf/scorecard-action@0864cf19026789058feabb7e87baa5f140aac736 # v2.3.1
with:
results_file: results.sarif
results_format: sarif
publish_results: true
- name: "Upload artifact"
uses: actions/upload-artifact@v4
with:
name: SARIF file
path: results.sarif
retention-days: 5
- name: "Upload to code-scanning"
uses: github/codeql-action/upload-sarif@3ebbd71c74ef574dbc558c82f70e52732c8b44fe # v2.2.1
with:
sarif_file: results.sarif

View File

@ -17,23 +17,14 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout Github Code
uses: actions/checkout@v4
uses: actions/checkout@v2
- name: Auto Creating Tags For Contrib Packages
- name: Auto Creating Tags
run: |
git config --global user.email "tagrobot@goframe.org"
git config --global user.name "TagRobot"
# auto create tags for contrib packages.
for file in `find contrib -name go.mod`; do
tag=$(dirname $file)/$GITHUB_REF_NAME
git tag $tag
git push origin $tag
done
# auto create tag for cli tool
for file in `find cmd -name go.mod`; do
tag=$(dirname $file)/$GITHUB_REF_NAME
git tag $tag
git push origin $tag
done

12
.gitignore vendored
View File

@ -7,19 +7,15 @@
.settings/
.vscode/
vendor/
composer.lock
gitpush.sh
pkg/
bin/
cbuild
**/.DS_Store
.test/
cmd/gf/main
cmd/gf/gf
temp/
example/log
go.work
go.work.sum
!cmd/gf/go.work
# Ignore for docs
node_modules
.docusaurus
output
temp/

View File

@ -38,7 +38,7 @@ linters:
# Custom enable linters we want to use.
enable:
- errcheck # Errcheck is a program for checking for unchecked errors in go programs.
- errchkjson # Checks types passed to the JSON encoding functions. Reports unsupported types and optionally reports occasions, where the check for the returned error can be omitted.
- errchkjson # Checks types passed to the json encoding functions. Reports unsupported types and optionally reports occasions, where the check for the returned error can be omitted.
- funlen # Tool for detection of long functions
- goconst # Finds repeated strings that could be replaced by a constant
- gocritic # Provides diagnostics that check for bugs, performance and style issues.
@ -125,7 +125,7 @@ linters-settings:
# Checks the number of lines in a function.
# If lower than 0, disable the check.
# Default: 60
lines: 340
lines: 330
# Checks the number of statements in a function.
# If lower than 0, disable the check.
# Default: 40
@ -135,7 +135,7 @@ linters-settings:
goconst:
# Minimal length of string constant.
# Default: 3
min-len: 4
min-len: 2
# Minimum occurrences of constant string count to trigger issue.
# Default: 3
# For subsequent optimization, the value is reduced.
@ -172,6 +172,10 @@ linters-settings:
# https://golangci-lint.run/usage/linters/#gosimple
gosimple:
# Select the Go version to target.
# Default: 1.13
# Deprecated: use the global `run.go` instead.
go: "1.15"
# Sxxxx checks in https://staticcheck.io/docs/configuration/options/#checks
# Default: ["*"]
checks: [
@ -182,7 +186,7 @@ linters-settings:
govet:
# Report about shadowed variables.
# Default: false
# check-shadowing: true
check-shadowing: true
# Settings per analyzer.
settings:
# Analyzer name, run `go tool vet help` to see all analyzers.
@ -255,6 +259,10 @@ linters-settings:
# https://golangci-lint.run/usage/linters/#staticcheck
staticcheck:
# Select the Go version to target.
# Default: "1.13"
# Deprecated: use the global `run.go` instead.
go: "1.15"
# SAxxxx checks in https://staticcheck.io/docs/configuration/options/#checks
# Default: ["*"]
checks: [ "all","-SA1019","-SA4015","-SA1029","-SA1016","-SA9003","-SA4006","-SA6003" ]

View File

@ -1,82 +0,0 @@
#!/usr/bin/env bash
if [ $# -ne 2 ]; then
echo "Parameter exception, please execute in the format of $0 [directory] [version number]"
echo "PS$0 ./ v2.4.0"
exit 1
fi
if [ ! -d "$1" ]; then
echo "Error: Directory does not exist"
exit 1
fi
if [[ "$2" != v* ]]; then
echo "Error: Version number must start with v"
exit 1
fi
workdir=.
newVersion=$2
echo "Prepare to replace the GF library version numbers in all go.mod files in the ${workdir} directory with ${newVersion}"
# check find command support or not
output=$(find "${workdir}" -name go.mod 2>&1)
if [[ $? -ne 0 ]]; then
echo "Error: please use bash or zsh to run!"
exit 1
fi
if [[ true ]]; then
echo "package gf" > version.go
echo "" >> version.go
echo "const (" >> version.go
echo -e "\t// VERSION is the current GoFrame version." >> version.go
echo -e "\tVERSION = \"${newVersion}\"" >> version.go
echo ")" >> version.go
fi
if [ -f "go.work" ]; then
mv go.work go.work.version.bak
echo "Back up the go.work file to avoid affecting the upgrade"
fi
for file in `find ${workdir} -name go.mod`; do
goModPath=$(dirname $file)
echo ""
echo "processing dir: $goModPath"
cd $goModPath
if [ $goModPath = "./cmd/gf" ]; then
mv go.work go.work.version.bak
go mod edit -replace github.com/gogf/gf/v2=../../
go mod edit -replace github.com/gogf/gf/contrib/drivers/clickhouse/v2=../../contrib/drivers/clickhouse
go mod edit -replace github.com/gogf/gf/contrib/drivers/mssql/v2=../../contrib/drivers/mssql
go mod edit -replace github.com/gogf/gf/contrib/drivers/mysql/v2=../../contrib/drivers/mysql
go mod edit -replace github.com/gogf/gf/contrib/drivers/oracle/v2=../../contrib/drivers/oracle
go mod edit -replace github.com/gogf/gf/contrib/drivers/pgsql/v2=../../contrib/drivers/pgsql
go mod edit -replace github.com/gogf/gf/contrib/drivers/sqlite/v2=../../contrib/drivers/sqlite
# else
# cd -
# continue 1
fi
go mod tidy
# Upgrading only GF related libraries, sometimes even if a version number is specified, it may not be possible to successfully upgrade. Please confirm before submitting the code
go list -f "{{if and (not .Indirect) (not .Main)}}{{.Path}}@${newVersion}{{end}}" -m all | grep "^github.com/gogf/gf"
go list -f "{{if and (not .Indirect) (not .Main)}}{{.Path}}@${newVersion}{{end}}" -m all | grep "^github.com/gogf/gf" | xargs -L1 go get -v
go mod tidy
if [ $goModPath = "./cmd/gf" ]; then
go mod edit -dropreplace github.com/gogf/gf/v2
go mod edit -dropreplace github.com/gogf/gf/contrib/drivers/clickhouse/v2
go mod edit -dropreplace github.com/gogf/gf/contrib/drivers/mssql/v2
go mod edit -dropreplace github.com/gogf/gf/contrib/drivers/mysql/v2
go mod edit -dropreplace github.com/gogf/gf/contrib/drivers/oracle/v2
go mod edit -dropreplace github.com/gogf/gf/contrib/drivers/pgsql/v2
go mod edit -dropreplace github.com/gogf/gf/contrib/drivers/sqlite/v2
mv go.work.version.bak go.work
fi
cd -
done
if [ -f "go.work.version.bak" ]; then
mv go.work.version.bak go.work
echo "Restore the go.work file"
fi

View File

@ -1,4 +1,4 @@
SHELL := /bin/bash
.PHONY: tidy
tidy:
@ -13,14 +13,4 @@ tidy:
.PHONY: lint
lint:
golangci-lint run
# make version to=v2.4.0
.PHONY: version
version:
@set -e; \
newVersion=$(to); \
./.set_version.sh ./ $$newVersion; \
echo "make version to=$(to) done"
golangci-lint run

View File

@ -1,29 +1,18 @@
# GoFrame
<div align=center>
<img src="https://goframe.org/img/logo_full.png" width="300"/>
<img src="https://goframe.org/statics/image/gf-head-large.png" width="100"/>
[![Go Reference](https://pkg.go.dev/badge/github.com/gogf/gf/v2.svg)](https://pkg.go.dev/github.com/gogf/gf/v2)
[![GoFrame CI](https://github.com/gogf/gf/actions/workflows/ci-main.yml/badge.svg)](https://github.com/gogf/gf/actions/workflows/ci-main.yml)
[![Go Report Card](https://goreportcard.com/badge/github.com/gogf/gf/v2)](https://goreportcard.com/report/github.com/gogf/gf/v2)
[![Code Coverage](https://codecov.io/gh/gogf/gf/branch/master/graph/badge.svg)](https://codecov.io/gh/gogf/gf)
[![Production Ready](https://img.shields.io/badge/production-ready-blue.svg?style=flat)](https://github.com/gogf/gf)
[![Go Doc](https://godoc.org/github.com/gogf/gf?status.svg)](https://godoc.org/github.com/gogf/gf)
[![GoFrame CI](https://github.com/gogf/gf/actions/workflows/gf.yml/badge.svg)](https://github.com/gogf/gf/actions/workflows/gf.yml)
[![Go Report](https://goreportcard.com/badge/github.com/gogf/gf?v=1)](https://goreportcard.com/report/github.com/gogf/gf)
[![Code Coverage](https://codecov.io/gh/gogf/gf/branch/master/graph/badge.svg)](https://codecov.io/gh/gogf/gf/branch/master)
[![Production Ready](https://img.shields.io/badge/production-ready-blue.svg)](https://github.com/gogf/gf)
[![License](https://img.shields.io/github/license/gogf/gf.svg?style=flat)](https://github.com/gogf/gf)
[![Release](https://img.shields.io/github/v/release/gogf/gf?style=flat)](https://github.com/gogf/gf/releases)
[![GitHub pull requests](https://img.shields.io/github/issues-pr/gogf/gf?style=flat)](https://github.com/gogf/gf/pulls)
[![GitHub closed pull requests](https://img.shields.io/github/issues-pr-closed/gogf/gf?style=flat)](https://github.com/gogf/gf/pulls?q=is%3Apr+is%3Aclosed)
[![GitHub issues](https://img.shields.io/github/issues/gogf/gf?style=flat)](https://github.com/gogf/gf/issues)
[![GitHub closed issues](https://img.shields.io/github/issues-closed/gogf/gf?style=flat)](https://github.com/gogf/gf/issues?q=is%3Aissue+is%3Aclosed)
![Stars](https://img.shields.io/github/stars/gogf/gf?style=flat)
![Forks](https://img.shields.io/github/forks/gogf/gf?style=flat)
</div>
`GoFrame` is a modular, powerful, high-performance and enterprise-class application development framework of Golang.
`GoFrame` is a modular, powerful, high-performance and enterprise-class application development framework of Golang.
# Features
- modular, loosely coupled design
- rich components, out-of-the-box
- automatic codes generating for efficiency
@ -37,56 +26,82 @@
- OpenAPIV3 documentation generating, automatically
- much, much more...ready to explore?
# Documentation
- GoFrame Official Site: [https://goframe.org](https://goframe.org)
- GoDoc API: [https://pkg.go.dev/github.com/gogf/gf/v2](https://pkg.go.dev/github.com/gogf/gf/v2)
# Installation
Enter your repo. directory and execute following command:
## primary module
```bash
go get -u -v github.com/gogf/gf/v2
```
## cli tool
```bash
go install github.com/gogf/gf/cmd/gf/v2@latest
go install github.com/gogf/gf/cmd/gf/v2
```
# Limitation
```
golang version >= 1.20
golang version >= 1.15
```
# Contributors
# Architecture
<div align=center>
<img src="https://goframe.org/download/attachments/1114119/arch.png"/>
</div>
💖 Thanks all the contributors making GoFrame awesome! [[Contributors](https://github.com/gogf/gf/graphs/contributors)] 💖
<a href="https://github.com/gogf/gf/graphs/contributors">
<img src="https://goframe.org/img/contributors.svg" />
</a>
# Documentation
* Chinese Official Site(中文官网): [https://goframe.org](https://goframe.org/display/gf)
* GoDoc API: [https://pkg.go.dev/github.com/gogf/gf/v2](https://pkg.go.dev/github.com/gogf/gf/v2)
# License
`GoFrame` is licensed under the [MIT License](LICENSE), 100% free and open-source, forever.
# Part Of Users
- [Tencent](https://www.tencent.com/)
- [ZTE](https://www.zte.com.cn/china/)
- [Ant Financial Services](https://www.antfin.com/)
- [VIVO](https://www.vivo.com/)
- [MedLinker](https://www.medlinker.com/)
- [KuCoin](https://www.kucoin.io/)
- [LeYouJia](https://www.leyoujia.com/)
- [IGG](https://igg.com)
- [37](https://www.37.com)
- [XiMaLaYa](https://www.ximalaya.com)
- [ZYBang](https://www.zybang.com/)
> We list part of the users here, if your company or products are using `GoFrame`, please let us know [here](https://goframe.org/pages/viewpage.action?pageId=1114415).
# Contributors
This project exists thanks to all the people who contribute. [[Contributors](https://github.com/gogf/gf/graphs/contributors)].
<a href="https://github.com/gogf/gf/graphs/contributors"><img src="https://contributors-img.web.app/image?repo=gogf/gf" /></a>
# Donators
If you love `GoFrame`, why not [buy developer a cup of coffee](https://goframe.org/supportus/donate)?
If you love `GoFrame`, why not [buy developer a cup of coffee](https://goframe.org/pages/viewpage.action?pageId=1115633)?
# Sponsors
We appreciate any kind of sponsorship for `GoFrame` development. If you've got some interesting, please contact WeChat `389961817` / Email `john@goframe.org`.
# Thanks
<a href="https://www.jetbrains.com/?from=GoFrame"><img src="https://goframe.org/img/jetbrains.png" height="120" alt="JetBrains"/></a>
<a href="https://www.atlassian.com/?from=GoFrame"><img src="https://goframe.org/img/atlassian.jpg" height="120" alt="Atlassian"/></a>
# Thanks
<a href="https://www.jetbrains.com/?from=GoFrame"><img src="https://goframe.org/download/thumbnails/1114119/jetbrains.png" height="120" alt="JetBrains"/></a>
<a href="https://www.atlassian.com/?from=GoFrame"><img src="https://goframe.org/download/attachments/1114119/atlassian.jpg" height="120" alt="Atlassian"/></a>

View File

@ -1,6 +1,5 @@
.DEFAULT_GOAL := pack
pack: pack.template-single pack.template-mono pack.template-mono-app
pack: pack.template-single pack.template-mono
pack.template-single:
@rm -fr temp
@ -16,21 +15,4 @@ pack.template-mono:
@cd temp && git clone https://github.com/gogf/template-mono
@rm -fr temp/template-mono/.git
@cd temp && gf pack template-mono ../internal/packed/template-mono.go -n=packed -y
@rm -fr temp
# Note:
# command `sed` only works on MacOS.
# use `grep -irl 'template-single' temp| xargs sed -i'' -e 's/template-single/template-mono-app/g'` on other platforms.
pack.template-mono-app:
@rm -fr temp
@mkdir temp || exit 0
@cd temp && git clone https://github.com/gogf/template-single
@cd temp && mv template-single template-mono-app
@rm -fr temp/template-mono-app/.git
@rm -fr temp/template-mono-app/.gitattributes
@rm -fr temp/template-mono-app/.gitignore
@rm -fr temp/template-mono-app/go.mod
@rm -fr temp/template-mono-app/go.sum
@grep -irl 'template-single' temp| xargs sed -i '' -e 's/template-single/template-mono-app/g'
@cd temp && gf pack template-mono-app ../internal/packed/template-mono-app.go -n=packed -y
@rm -fr temp

View File

@ -2,56 +2,39 @@
`gf` is a powerful CLI tool for building [GoFrame](https://goframe.org) application with convenience.
## 1. Install
## 1) PreCompiled Binary
You can also install `gf` tool using pre-built binaries: <https://github.com/gogf/gf/releases>
You can also install `gf` tool using pre-built binaries: https://github.com/gogf/gf/releases
1. `Mac` & `Linux`
```shell
```shell
wget -O gf https://github.com/gogf/gf/releases/latest/download/gf_$(go env GOOS)_$(go env GOARCH) && chmod +x gf && ./gf install -y && rm ./gf
```
```
> 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`.
2. `Windows`
Manually download, execute in command line it and then follow the instruction.
3. Database support
Manually download, execute it and then follow the instruction.
| DB | builtin support | remarks |
|:----------:|:---------------:|:----------------------------------------------------------------------------------------------------------------------------------------------------------------:|
| mysql | yes | - |
| mariadb | yes | - |
| tidb | yes | - |
| mssql | yes | - |
| oracle | yes | - |
| pgsql | yes | - |
| sqlite | yes | - |
| sqlitecgo | no | to support sqlite database on 32bit architecture systems, manually add package import to the [source codes](./internal/cmd/cmd_gen_dao.go) and do the building. |
| clickhouse | no | manually add package import to the [source codes](./internal/cmd/cmd_gen_dao.go) and do the building. |
| dm | no | manually add package import to the [source codes](./internal/cmd/cmd_gen_dao.go) and do the building. |
3. 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.
## 2) Manually Install
```shell
go install github.com/gogf/gf/cmd/gf/v2@latest # latest version
go install github.com/gogf/gf/cmd/gf/v2@v2.5.5 # certain version(should be >= v2.5.5)
```
```shell
git clone https://github.com/gogf/gf && cd gf/cmd/gf && go install
```
## 2. Commands
```html
$ gf
USAGE
gf COMMAND [OPTION]
COMMAND
up upgrade GoFrame version/tool to latest one in current project
env show current Golang environment variables
fix auto fixing codes after upgrading to new GoFrame version
run running go codes with hot-compiled-like feature
gen automatically generate go files for dao/do/entity/pb/pbentity
tpl template parsing and building commands
@ -77,3 +60,10 @@ ADDITIONAL
### 1). Command `gf run` returns `pipe: too many open files`
Please use `ulimit -n 65535` to enlarge your system configuration for max open files for current terminal shell session, and then `gf run`.

View File

@ -1,119 +0,0 @@
// Copyright GoFrame gf Author(https://goframe.org). All Rights Reserved.
//
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with this file,
// You can obtain one at https://github.com/gogf/gf.
package gfcmd
import (
"context"
"runtime"
"github.com/gogf/gf/v2/errors/gcode"
"github.com/gogf/gf/v2/errors/gerror"
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/os/gcfg"
"github.com/gogf/gf/v2/os/gcmd"
"github.com/gogf/gf/v2/os/gfile"
"github.com/gogf/gf/v2/text/gstr"
"github.com/gogf/gf/cmd/gf/v2/internal/cmd"
_ "github.com/gogf/gf/cmd/gf/v2/internal/packed"
"github.com/gogf/gf/cmd/gf/v2/internal/utility/allyes"
"github.com/gogf/gf/cmd/gf/v2/internal/utility/mlog"
)
const (
cliFolderName = `hack`
)
// Command manages the CLI command of `gf`.
// This struct can be globally accessible and extended with custom struct.
type Command struct {
*gcmd.Command
}
// Run starts running the command according the command line arguments and options.
func (c *Command) Run(ctx context.Context) {
defer func() {
if exception := recover(); exception != nil {
if err, ok := exception.(error); ok {
mlog.Print(err.Error())
} else {
panic(gerror.NewCodef(gcode.CodeInternalPanic, "%+v", exception))
}
}
}()
// CLI configuration, using the `hack/config.yaml` in priority.
if path, _ := gfile.Search(cliFolderName); path != "" {
if adapter, ok := g.Cfg().GetAdapter().(*gcfg.AdapterFile); ok {
if err := adapter.SetPath(path); err != nil {
mlog.Fatal(err)
}
}
}
// zsh alias "git fetch" conflicts checks.
handleZshAlias()
// -y option checks.
allyes.Init()
// just run.
if err := c.RunWithError(ctx); err != nil {
// Exit with error message and exit code 1.
// It is very important to exit the command process with code 1.
mlog.Fatalf(`%+v`, err)
}
}
// GetCommand retrieves and returns the root command of CLI `gf`.
func GetCommand(ctx context.Context) (*Command, error) {
root, err := gcmd.NewFromObject(cmd.GF)
if err != nil {
return nil, err
}
err = root.AddObject(
cmd.Up,
cmd.Env,
cmd.Fix,
cmd.Run,
cmd.Gen,
cmd.Tpl,
cmd.Init,
cmd.Pack,
cmd.Build,
cmd.Docker,
cmd.Install,
cmd.Version,
cmd.Doc,
)
if err != nil {
return nil, err
}
command := &Command{
root,
}
return command, nil
}
// zsh alias "git fetch" conflicts checks.
func handleZshAlias() {
if runtime.GOOS == "windows" {
return
}
if home, err := gfile.Home(); err == nil {
zshPath := gfile.Join(home, ".zshrc")
if gfile.Exists(zshPath) {
var (
aliasCommand = `alias gf=gf`
content = gfile.GetContents(zshPath)
)
if !gstr.Contains(content, aliasCommand) {
_ = gfile.PutContentsAppend(zshPath, "\n"+aliasCommand+"\n")
}
}
}
}

View File

@ -3,61 +3,57 @@ module github.com/gogf/gf/cmd/gf/v2
go 1.18
require (
github.com/gogf/gf/contrib/drivers/clickhouse/v2 v2.8.0
github.com/gogf/gf/contrib/drivers/mssql/v2 v2.8.0
github.com/gogf/gf/contrib/drivers/mysql/v2 v2.8.0
github.com/gogf/gf/contrib/drivers/oracle/v2 v2.8.0
github.com/gogf/gf/contrib/drivers/pgsql/v2 v2.8.0
github.com/gogf/gf/contrib/drivers/sqlite/v2 v2.8.0
github.com/gogf/gf/v2 v2.8.0
github.com/gogf/selfupdate v0.0.0-20231215043001-5c48c528462f
github.com/gogf/gf/contrib/drivers/mssql/v2 v2.1.0
github.com/gogf/gf/contrib/drivers/mysql/v2 v2.1.0
github.com/gogf/gf/contrib/drivers/oracle/v2 v2.1.0
github.com/gogf/gf/contrib/drivers/pgsql/v2 v2.1.0
github.com/gogf/gf/contrib/drivers/sqlite/v2 v2.1.0
github.com/gogf/gf/v2 v2.2.2
github.com/olekukonko/tablewriter v0.0.5
golang.org/x/mod v0.17.0
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d
golang.org/x/tools v0.2.0
)
require (
aead.dev/minisign v0.2.0 // indirect
github.com/BurntSushi/toml v1.4.0 // indirect
github.com/ClickHouse/clickhouse-go/v2 v2.0.15 // indirect
github.com/clbanning/mxj/v2 v2.7.0 // indirect
github.com/dustin/go-humanize v1.0.1 // indirect
github.com/emirpasic/gods v1.18.1 // indirect
github.com/fatih/color v1.17.0 // indirect
github.com/fsnotify/fsnotify v1.7.0 // indirect
github.com/glebarez/go-sqlite v1.21.2 // indirect
github.com/go-logr/logr v1.4.2 // indirect
github.com/BurntSushi/toml v1.2.1 // indirect
github.com/clbanning/mxj/v2 v2.5.5 // indirect
github.com/denisenkom/go-mssqldb v0.11.0 // indirect
github.com/fatih/color v1.13.0 // indirect
github.com/fsnotify/fsnotify v1.5.4 // indirect
github.com/glebarez/go-sqlite v1.17.3 // indirect
github.com/go-logr/logr v1.2.3 // indirect
github.com/go-logr/stdr v1.2.2 // indirect
github.com/go-sql-driver/mysql v1.7.1 // indirect
github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9 // indirect
github.com/golang-sql/sqlexp v0.1.0 // indirect
github.com/google/uuid v1.6.0 // indirect
github.com/gorilla/websocket v1.5.3 // indirect
github.com/grokify/html-strip-tags-go v0.1.0 // indirect
github.com/lib/pq v1.10.9 // indirect
github.com/magiconair/properties v1.8.7 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/mattn/go-runewidth v0.0.15 // indirect
github.com/microsoft/go-mssqldb v1.7.1 // indirect
github.com/paulmach/orb v0.7.1 // indirect
github.com/pierrec/lz4/v4 v4.1.14 // indirect
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
github.com/rivo/uniseg v0.4.7 // indirect
github.com/shopspring/decimal v1.3.1 // indirect
github.com/sijms/go-ora/v2 v2.7.10 // indirect
go.opentelemetry.io/otel v1.24.0 // indirect
go.opentelemetry.io/otel/metric v1.24.0 // indirect
go.opentelemetry.io/otel/sdk v1.24.0 // indirect
go.opentelemetry.io/otel/trace v1.24.0 // indirect
golang.org/x/crypto v0.25.0 // indirect
golang.org/x/net v0.27.0 // indirect
golang.org/x/sync v0.7.0 // indirect
golang.org/x/sys v0.22.0 // indirect
golang.org/x/text v0.16.0 // indirect
github.com/go-sql-driver/mysql v1.6.0 // indirect
github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe // indirect
github.com/google/uuid v1.3.0 // indirect
github.com/gorilla/websocket v1.5.0 // indirect
github.com/grokify/html-strip-tags-go v0.0.1 // indirect
github.com/lib/pq v1.10.4 // indirect
github.com/magiconair/properties v1.8.6 // indirect
github.com/mattn/go-colorable v0.1.9 // indirect
github.com/mattn/go-isatty v0.0.14 // indirect
github.com/mattn/go-runewidth v0.0.9 // indirect
github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0 // indirect
github.com/sijms/go-ora/v2 v2.4.20 // indirect
go.opentelemetry.io/otel v1.7.0 // indirect
go.opentelemetry.io/otel/sdk v1.7.0 // indirect
go.opentelemetry.io/otel/trace v1.7.0 // indirect
golang.org/x/crypto v0.1.0 // indirect
golang.org/x/mod v0.6.0 // indirect
golang.org/x/net v0.1.0 // indirect
golang.org/x/sys v0.1.0 // indirect
golang.org/x/text v0.4.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
modernc.org/libc v1.22.5 // indirect
modernc.org/mathutil v1.5.0 // indirect
modernc.org/memory v1.5.0 // indirect
modernc.org/sqlite v1.23.1 // indirect
modernc.org/libc v1.16.8 // indirect
modernc.org/mathutil v1.4.1 // indirect
modernc.org/memory v1.1.1 // indirect
modernc.org/sqlite v1.17.3 // indirect
)
replace (
github.com/gogf/gf/contrib/drivers/mssql/v2 => ../../contrib/drivers/mssql/
github.com/gogf/gf/contrib/drivers/mysql/v2 => ../../contrib/drivers/mysql/
github.com/gogf/gf/contrib/drivers/oracle/v2 => ../../contrib/drivers/oracle/
github.com/gogf/gf/contrib/drivers/pgsql/v2 => ../../contrib/drivers/pgsql/
github.com/gogf/gf/contrib/drivers/sqlite/v2 => ../../contrib/drivers/sqlite/
github.com/gogf/gf/v2 => ../../
)

View File

@ -1,194 +1,150 @@
aead.dev/minisign v0.2.0 h1:kAWrq/hBRu4AARY6AlciO83xhNnW9UaC8YipS2uhLPk=
aead.dev/minisign v0.2.0/go.mod h1:zdq6LdSd9TbuSxchxwhpA9zEb9YXcVGoE8JakuiGaIQ=
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.9.1 h1:lGlwhPtrX6EVml1hO0ivjkUxsSyl4dsiw9qcA1k/3IQ=
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.5.1 h1:sO0/P7g68FrryJzljemN+6GTssUXdANk6aJ7T1ZxnsQ=
github.com/Azure/azure-sdk-for-go/sdk/internal v1.5.1 h1:6oNBlSdi1QqM1PNW7FPA6xOGA5UNsXnkaYZz9vdPGhA=
github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azkeys v1.0.1 h1:MyVTgWR8qd/Jw1Le0NZebGBUCLbtak3bJ3z1OlqZBpw=
github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/internal v1.0.0 h1:D3occbWoio4EBLkbkevetNMAVX197GkzbUMtqjGWn80=
github.com/AzureAD/microsoft-authentication-library-for-go v1.2.1 h1:DzHpqpoJVaCgOUdVHxE8QB52S6NiVdDQvGlny1qvPqA=
github.com/BurntSushi/toml v1.4.0 h1:kuoIxZQy2WRRk1pttg9asf+WVv6tWQuBNVmK8+nqPr0=
github.com/BurntSushi/toml v1.4.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho=
github.com/ClickHouse/clickhouse-go v1.5.4/go.mod h1:EaI/sW7Azgz9UATzd5ZdZHRUhHgv5+JMS9NSr2smCJI=
github.com/ClickHouse/clickhouse-go/v2 v2.0.15 h1:lLAZliqrZEygkxosLaW1qHyeTb4Ho7fVCZ0WKCpLocU=
github.com/ClickHouse/clickhouse-go/v2 v2.0.15/go.mod h1:Z21o82zD8FFqefOQDg93c0XITlxGbTsWQuRm588Azkk=
github.com/StackExchange/wmi v0.0.0-20190523213315-cbe66965904d/go.mod h1:3eOhrUMpNV+6aFIbp5/iudMxNCF27Vw2OZgy4xEx0Fg=
github.com/bkaradzic/go-lz4 v1.0.0/go.mod h1:0YdlkowM3VswSROI7qDxhRvJ3sLhlFrRRwjwegp5jy4=
github.com/clbanning/mxj/v2 v2.7.0 h1:WA/La7UGCanFe5NpHF0Q3DNtnCsVoxbPKuyBNHWRyME=
github.com/clbanning/mxj/v2 v2.7.0/go.mod h1:hNiWqW14h+kc+MdF9C6/YoRfjEJoR3ou6tn/Qo+ve2s=
github.com/cloudflare/golz4 v0.0.0-20150217214814-ef862a3cdc58/go.mod h1:EOBUe0h4xcZ5GoxqC5SDxFQ8gwyZPKQoEzownBlhI80=
github.com/BurntSushi/toml v1.1.0/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
github.com/BurntSushi/toml v1.2.1 h1:9F2/+DoOYIOksmaJFPw1tGFy1eDnIJXg+UHjuD8lTak=
github.com/BurntSushi/toml v1.2.1/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
github.com/clbanning/mxj/v2 v2.5.5 h1:oT81vUeEiQQ/DcHbzSytRngP6Ky9O+L+0Bw0zSJag9E=
github.com/clbanning/mxj/v2 v2.5.5/go.mod h1:hNiWqW14h+kc+MdF9C6/YoRfjEJoR3ou6tn/Qo+ve2s=
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc=
github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ=
github.com/fatih/color v1.17.0 h1:GlRw1BRJxkpqUCBKzKOw098ed57fEsKeNjpTe3cSjK4=
github.com/fatih/color v1.17.0/go.mod h1:YZ7TlrGPkiz6ku9fK3TLD/pl3CpsiFyu8N92HLgmosI=
github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA=
github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM=
github.com/glebarez/go-sqlite v1.21.2 h1:3a6LFC4sKahUunAmynQKLZceZCOzUthkRkEAl9gAXWo=
github.com/glebarez/go-sqlite v1.21.2/go.mod h1:sfxdZyhQjTM2Wry3gVYWaW072Ri1WMdWJi0k6+3382k=
github.com/denisenkom/go-mssqldb v0.11.0 h1:9rHa233rhdOyrz2GcP9NM+gi2psgJZ4GWDpL/7ND8HI=
github.com/denisenkom/go-mssqldb v0.11.0/go.mod h1:xbL0rPBG9cCiLr28tMa8zpbdarY27NDyej4t/EjAShU=
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
github.com/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w=
github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk=
github.com/fsnotify/fsnotify v1.5.4 h1:jRbGcIw6P2Meqdwuo0H1p6JVLbL5DHKAKlYndzMwVZI=
github.com/fsnotify/fsnotify v1.5.4/go.mod h1:OVB6XrOHzAwXMpEM7uPOzcehqUV2UqJxmVXmkdnm1bU=
github.com/glebarez/go-sqlite v1.17.3 h1:Rji9ROVSTTfjuWD6j5B+8DtkNvPILoUC3xRhkQzGxvk=
github.com/glebarez/go-sqlite v1.17.3/go.mod h1:Hg+PQuhUy98XCxWEJEaWob8x7lhJzhNYF1nZbUiRGIY=
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
github.com/go-logr/logr v1.2.3 h1:2DntVwHkVopvECVRSlL5PSo9eG+cAkDCuckLubN+rq0=
github.com/go-logr/logr v1.2.3/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY=
github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
github.com/go-ole/go-ole v1.2.4/go.mod h1:XCwSNxSkXRo4vlyPy93sltvi/qJq0jqQhjqQNIwKuxM=
github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
github.com/go-sql-driver/mysql v1.7.1 h1:lUIinVbN1DY0xBg0eMOzmmtGoHwWBbvnWubQUrtU8EI=
github.com/go-sql-driver/mysql v1.7.1/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI=
github.com/gogf/selfupdate v0.0.0-20231215043001-5c48c528462f h1:7xfXR/BhG3JDqO1s45n65Oyx9t4E/UqDOXep6jXdLCM=
github.com/gogf/selfupdate v0.0.0-20231215043001-5c48c528462f/go.mod h1:HnYoio6S7VaFJdryKcD/r9HgX+4QzYfr00XiXUo/xz0=
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
github.com/golang-jwt/jwt/v5 v5.2.0 h1:d/ix8ftRUorsN+5eMIlF4T6J8CAt9rch3My2winC1Jw=
github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9 h1:au07oEsX2xN0ktxqI+Sida1w446QrXBRJ0nee3SNZlA=
github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0=
github.com/golang-sql/sqlexp v0.1.0 h1:ZCD6MBpcuOVfGVqsEmY5/4FtYiKz6tSyUv9LPEDei6A=
github.com/golang-sql/sqlexp v0.1.0/go.mod h1:J4ad9Vo8ZCWQ2GMrC4UCQy1JpCbwU9m3EOqtpKwwwHI=
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/go-sql-driver/mysql v1.6.0 h1:BCTh4TKNUYmOmMUcQ3IipzF5prigylS7XXjEkfCHuOE=
github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe h1:lXe2qZdvpiX5WZkZR4hgp4KJVfY3nMkvmwbVkpv1rVY=
github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0=
github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.7 h1:81/ik6ipDQS2aGcBfIN5dHDB36BwrStyeAQquSYCV4o=
github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/pprof v0.0.0-20221118152302-e6195bd50e26 h1:Xim43kblpZXfIBQsbuBVKCudVG457BR2GZFIz3uw3hQ=
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/gorilla/handlers v1.4.2/go.mod h1:Qkdc/uu4tH4g6mTK6auzZ766c4CA0Ng8+o/OAirnOIQ=
github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg=
github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/grokify/html-strip-tags-go v0.1.0 h1:03UrQLjAny8xci+R+qjCce/MYnpNXCtgzltlQbOBae4=
github.com/grokify/html-strip-tags-go v0.1.0/go.mod h1:ZdzgfHEzAfz9X6Xe5eBLVblWIxXfYSQ40S/VKrAOGpc=
github.com/jmoiron/sqlx v1.2.0/go.mod h1:1FEQNm3xlJgrMD+FBdI9+xvCksHtbpVBBw5dYhBSsks=
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw=
github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY=
github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0=
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc=
github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/grokify/html-strip-tags-go v0.0.1 h1:0fThFwLbW7P/kOiTBs03FsJSV9RM2M/Q/MOnCQxKMo0=
github.com/grokify/html-strip-tags-go v0.0.1/go.mod h1:2Su6romC5/1VXOQMaWL2yb618ARB8iVo6/DR99A6d78=
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8=
github.com/lib/pq v1.10.4 h1:SO9z7FRPzA03QhHKJrH5BXA6HU1rS4V2nIVrrNC1iYk=
github.com/lib/pq v1.10.4/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
github.com/magiconair/properties v1.8.6 h1:5ibWZ6iY0NctNGWo87LalDlEZ6R41TqbbDamhfG/Qzo=
github.com/magiconair/properties v1.8.6/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60=
github.com/mattn/go-colorable v0.1.9 h1:sqDoxXbdeALODt0DAeJCVp38ps9ZogZEAXjus69YV3U=
github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y=
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
github.com/mattn/go-runewidth v0.0.9 h1:Lm995f3rfxdpd6TSmuVCHVb/QhupuXlYr8sCI/QdE+0=
github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U=
github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
github.com/mattn/go-sqlite3 v1.9.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
github.com/microsoft/go-mssqldb v1.7.1 h1:KU/g8aWeM3Hx7IMOFpiwYiUkU+9zeISb4+tx3ScVfsM=
github.com/microsoft/go-mssqldb v1.7.1/go.mod h1:kOvZKUdrhhFQmxLZqbwUV0rHkNkZpthMITIb2Ko1IoA=
github.com/mkevac/debugcharts v0.0.0-20191222103121-ae1c48aa8615/go.mod h1:Ad7oeElCZqA1Ufj0U9/liOF4BtVepxRcTvr2ey7zTvM=
github.com/mattn/go-sqlite3 v1.14.12/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU=
github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec=
github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY=
github.com/paulmach/orb v0.7.1 h1:Zha++Z5OX/l168sqHK3k4z18LDvr+YAO/VjK0ReQ9rU=
github.com/paulmach/orb v0.7.1/go.mod h1:FWRlTgl88VI1RBx/MkrwWDRhQ96ctqMCh8boXhmqB/A=
github.com/paulmach/protoscan v0.2.1/go.mod h1:SpcSwydNLrxUGSDvXvO0P7g7AuhJ7lcKfDlhJCDw2gY=
github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY=
github.com/pierrec/lz4/v4 v4.1.14 h1:+fL8AQEZtz/ijeNnpduH0bROTu0O3NZAlPjQxGn8LwE=
github.com/pierrec/lz4/v4 v4.1.14/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4=
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c h1:+mdjkGKdHQG3305AYmdv1U2eRNDiU2ErMBj1gwrq8eQ=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0 h1:OdAsTTz6OkFY5QxjkYwrChwuRruF69c169dPK26NUlk=
github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE=
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
github.com/shirou/gopsutil v2.19.11+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA=
github.com/shirou/gopsutil v3.21.11+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA=
github.com/shirou/w32 v0.0.0-20160930032740-bb4de0191aa4/go.mod h1:qsXQc7+bwAM3Q1u/4XEfrquwF8Lw7D7y5cD8CuHnfIc=
github.com/shopspring/decimal v1.3.1 h1:2Usl1nmF/WZucqkFZhnfFYxxxu8LG21F6nPQBE5gKV8=
github.com/shopspring/decimal v1.3.1/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o=
github.com/sijms/go-ora/v2 v2.7.10 h1:GSLdj0PYYgSndhsnm7b6p32OqgnwnUZSkFb3j+htfhI=
github.com/sijms/go-ora/v2 v2.7.10/go.mod h1:EHxlY6x7y9HAsdfumurRfTd+v8NrEOTR3Xl4FWlH6xk=
github.com/sijms/go-ora/v2 v2.4.20 h1:9e3z7VLBQXRAHGiIda1GEFtRhfxata0LghyMZqvLKew=
github.com/sijms/go-ora/v2 v2.4.20/go.mod h1:EHxlY6x7y9HAsdfumurRfTd+v8NrEOTR3Xl4FWlH6xk=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.7.1 h1:5TQK59W5E3v0r2duFAb7P95B6hEeOyEnHRa8MjYSMTY=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals=
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
github.com/tklauser/go-sysconf v0.3.10/go.mod h1:C8XykCvCb+Gn0oNCWPIlcb0RuglQTYaQ2hGm7jmxEFk=
github.com/tklauser/numcpus v0.4.0/go.mod h1:1+UI3pD8NW14VMwdgJNJ1ESk2UnwhAnz5hMwiKKqXCQ=
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yusufpapurcu/wmi v1.2.2/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
github.com/yuin/goldmark v1.4.0/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
go.opentelemetry.io/otel v1.7.0 h1:Z2lA3Tdch0iDcrhJXDIlC94XE+bxok1F9B+4Lz/lGsM=
go.opentelemetry.io/otel v1.7.0/go.mod h1:5BdUoMIz5WEs0vt0CUEMtSSaTSHBBVwrhnz7+nrD5xk=
go.opentelemetry.io/otel v1.24.0 h1:0LAOdjNmQeSTzGBzduGe/rU4tZhMwL5rWgtp9Ku5Jfo=
go.opentelemetry.io/otel v1.24.0/go.mod h1:W7b9Ozg4nkF5tWI5zsXkaKKDjdVjpD4oAt9Qi/MArHo=
go.opentelemetry.io/otel/metric v1.24.0 h1:6EhoGWWK28x1fbpA4tYTOWBkPefTDQnb8WSGXlc88kI=
go.opentelemetry.io/otel/metric v1.24.0/go.mod h1:VYhLe1rFfxuTXLgj4CBiyz+9WYBA8pNGJgDcSFRKBco=
go.opentelemetry.io/otel/sdk v1.24.0 h1:YMPPDNymmQN3ZgczicBY3B6sf9n62Dlj9pWD3ucgoDw=
go.opentelemetry.io/otel/sdk v1.24.0/go.mod h1:KVrIYw6tEubO9E96HQpcmpTKDVn9gdv35HoYiQWGDFg=
go.opentelemetry.io/otel/sdk v1.7.0 h1:4OmStpcKVOfvDOgCt7UriAPtKolwIhxpnSNI/yK+1B0=
go.opentelemetry.io/otel/sdk v1.7.0/go.mod h1:uTEOTwaqIVuTGiJN7ii13Ibp75wJmYUDe374q6cZwUU=
go.opentelemetry.io/otel/trace v1.7.0 h1:O37Iogk1lEkMRXewVtZ1BBTVn5JEp8GrJvP92bJqC6o=
go.opentelemetry.io/otel/trace v1.7.0/go.mod h1:fzLSB9nqR2eXzxPXb2JW9IKE+ScyXA48yyE4TNvoHqU=
go.opentelemetry.io/otel/trace v1.24.0 h1:CsKnnL4dUAr/0llH9FKuc698G04IrpWV0MQA/Y1YELI=
go.opentelemetry.io/otel/trace v1.24.0/go.mod h1:HPc3Xr/cOApsBI154IU0OI0HJexz+aw5uPdbs3UCjNU=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190325154230-a5d413f7728c/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
golang.org/x/crypto v0.25.0 h1:ypSNr+bnYL2YhwoMt2zPxHFmbAN1KZs/njMG3hxUp30=
golang.org/x/crypto v0.25.0/go.mod h1:T+wALwcMOSE0kXgUAnPAHqTLW+XHgcELELW8VaDgm/M=
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/crypto v0.1.0 h1:MDRAIl0xIo9Io2xV565hzXHw3zVseKrJKodhohM5CjU=
golang.org/x/crypto v0.1.0/go.mod h1:RecgLatLF4+eUMCP1PoPZQb+cVrJcOPbHkTkbkB9sbw=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.17.0 h1:zY54UmvipHiNd+pm+m0x9KhZ9hl1/7QNMyxXbc6ICqA=
golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.6.0 h1:b9gGHsz9/HhJ3HF5DHQytPpuwocVTChQJK3AvoLRD5I=
golang.org/x/mod v0.6.0/go.mod h1:4mET923SAdbXp2ki8ey+zGs1SLqsuM2Y0uvdZR/fUNI=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.27.0 h1:5K3Njcw06/l2y9vpGCSdcxWOYHOUk3dVNGDXN+FvAys=
golang.org/x/net v0.27.0/go.mod h1:dDi0PyhWNoiUOrAS8uXv/vnScO4wnHQO4mj9fn/RytE=
golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.1.0 h1:hZ/3BUoy5aId7sCpA/Tc5lt8DkFgdVS2onTpJsZ/fl0=
golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M=
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191220220014-0732a990476f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210228012217-479acdf4ea46/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20220128215802-99c3d69c2c27/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220429233432-b5fbb4746d32/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI=
golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220405052023-b1e9470b6e64/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.1.0 h1:kunALQeHf1/185U1i0GOB/fy1IPRDDpuoOOqRReG57U=
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4=
golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.8-0.20211105212822-18b340fc7af2/go.mod h1:EFNZuWvGYxIRUEX+K8UmCFwYmZjqcrnq15ZuVldZkZ0=
golang.org/x/text v0.4.0 h1:BrVqGRd7+k1DiOgtnFvAkoQEWQvBc25ouMJM6429SFg=
golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d h1:vU5i/LfpvrRCpgM/VPfJLg5KjxD3E+hfT1SH+d9zLwg=
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk=
golang.org/x/tools v0.0.0-20201124115921-2c860bdd6e78/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.1.7/go.mod h1:LGqMHiF4EqQNHR1JncWGqT5BVaXmza+X+BDGol+dOxo=
golang.org/x/tools v0.2.0 h1:G6AHpWxTMGY1KyEYoAQ5WTtIekUUvDNjan3ugu60JvE=
golang.org/x/tools v0.2.0/go.mod h1:y4OqIKeOV/fWJetJ8bXPU1sEVniLMIyDAZWeHdV+NTA=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
modernc.org/libc v1.22.5 h1:91BNch/e5B0uPbJFgqbxXuOnxBQjlS//icfQEGmvyjE=
modernc.org/libc v1.22.5/go.mod h1:jj+Z7dTNX8fBScMVNRAYZ/jF91K8fdT2hYMThc3YjBY=
modernc.org/mathutil v1.5.0 h1:rV0Ko/6SfM+8G+yKiyI830l3Wuz1zRutdslNoQ0kfiQ=
modernc.org/mathutil v1.5.0/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E=
modernc.org/memory v1.5.0 h1:N+/8c5rE6EqugZwHii4IFsaJ7MUhoWX07J5tC/iI5Ds=
modernc.org/memory v1.5.0/go.mod h1:PkUhL0Mugw21sHPeskwZW4D6VscE/GQJOnIpCnW6pSU=
modernc.org/sqlite v1.23.1 h1:nrSBg4aRQQwq59JpvGEQ15tNxoO5pX/kUjcRNwSAGQM=
modernc.org/sqlite v1.23.1/go.mod h1:OrDj17Mggn6MhE+iPbBNf7RGKODDE9NFT0f3EwDzJqk=
lukechampine.com/uint128 v1.1.1/go.mod h1:c4eWIwlEGaxC/+H1VguhU4PHXNWDCDMUlWdIWl2j1gk=
modernc.org/cc/v3 v3.36.0/go.mod h1:NFUHyPn4ekoC/JHeZFfZurN6ixxawE1BnVonP/oahEI=
modernc.org/ccgo/v3 v3.0.0-20220428102840-41399a37e894/go.mod h1:eI31LL8EwEBKPpNpA4bU1/i+sKOwOrQy8D87zWUcRZc=
modernc.org/ccgo/v3 v3.0.0-20220430103911-bc99d88307be/go.mod h1:bwdAnOoaIt8Ax9YdWGjxWsdkPcZyRPHqrOvJxaKAKGw=
modernc.org/ccgo/v3 v3.16.4/go.mod h1:tGtX0gE9Jn7hdZFeU88slbTh1UtCYKusWOoCJuvkWsQ=
modernc.org/ccgo/v3 v3.16.6/go.mod h1:tGtX0gE9Jn7hdZFeU88slbTh1UtCYKusWOoCJuvkWsQ=
modernc.org/ccorpus v1.11.6/go.mod h1:2gEUTrWqdpH2pXsmTM1ZkjeSrUWDpjMu2T6m29L/ErQ=
modernc.org/httpfs v1.0.6/go.mod h1:7dosgurJGp0sPaRanU53W4xZYKh14wfzX420oZADeHM=
modernc.org/libc v0.0.0-20220428101251-2d5f3daf273b/go.mod h1:p7Mg4+koNjc8jkqwcoFBJx7tXkpj00G77X7A72jXPXA=
modernc.org/libc v1.16.0/go.mod h1:N4LD6DBE9cf+Dzf9buBlzVJndKr/iJHG97vGLHYnb5A=
modernc.org/libc v1.16.1/go.mod h1:JjJE0eu4yeK7tab2n4S1w8tlWd9MxXLRzheaRnAKymU=
modernc.org/libc v1.16.7/go.mod h1:hYIV5VZczAmGZAnG15Vdngn5HSF5cSkbvfz2B7GRuVU=
modernc.org/libc v1.16.8 h1:Ux98PaOMvolgoFX/YwusFOHBnanXdGRmWgI8ciI2z4o=
modernc.org/libc v1.16.8/go.mod h1:hYIV5VZczAmGZAnG15Vdngn5HSF5cSkbvfz2B7GRuVU=
modernc.org/mathutil v1.2.2/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E=
modernc.org/mathutil v1.4.1 h1:ij3fYGe8zBF4Vu+g0oT7mB06r8sqGWKuJu1yXeR4by8=
modernc.org/mathutil v1.4.1/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E=
modernc.org/memory v1.1.1 h1:bDOL0DIDLQv7bWhP3gMvIrnoFw+Eo6F7a2QK9HPDiFU=
modernc.org/memory v1.1.1/go.mod h1:/0wo5ibyrQiaoUoH7f9D8dnglAmILJ5/cxZlRECf+Nw=
modernc.org/opt v0.1.1/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0=
modernc.org/sqlite v1.17.3 h1:iE+coC5g17LtByDYDWKpR6m2Z9022YrSh3bumwOnIrI=
modernc.org/sqlite v1.17.3/go.mod h1:10hPVYar9C0kfXuTWGz8s0XtB8uAGymUy51ZzStYe3k=
modernc.org/strutil v1.1.1/go.mod h1:DE+MQQ/hjKBZS2zNInV5hhcipt5rLPWkmpbGeW5mmdw=
modernc.org/tcl v1.13.1/go.mod h1:XOLfOwzhkljL4itZkK6T72ckMgvj0BDsnKNdZVUOecw=
modernc.org/token v1.0.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM=
modernc.org/z v1.5.1/go.mod h1:eWFB510QWW5Th9YGZT81s+LwvaAs3Q2yr4sP0rmLkv8=

View File

@ -1,21 +0,0 @@
go 1.18
use (
./
)
// =====================================================================================================
// NOTE:
// Please update associated commands in ../../.set_version.sh if any of the follows replacements change.
// =====================================================================================================
replace (
github.com/gogf/gf/contrib/drivers/clickhouse/v2 => ../../contrib/drivers/clickhouse
github.com/gogf/gf/contrib/drivers/mssql/v2 => ../../contrib/drivers/mssql
github.com/gogf/gf/contrib/drivers/mysql/v2 => ../../contrib/drivers/mysql
github.com/gogf/gf/contrib/drivers/oracle/v2 => ../../contrib/drivers/oracle
github.com/gogf/gf/contrib/drivers/pgsql/v2 => ../../contrib/drivers/pgsql
github.com/gogf/gf/contrib/drivers/sqlite/v2 => ../../contrib/drivers/sqlite
github.com/gogf/gf/contrib/drivers/dm/v2 => ../../contrib/drivers/dm
github.com/gogf/gf/v2 => ../../
)

View File

@ -1,16 +1,9 @@
// Copyright GoFrame gf Author(https://goframe.org). All Rights Reserved.
//
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with this file,
// You can obtain one at https://github.com/gogf/gf.
package cmd
import (
"context"
"strings"
"github.com/gogf/gf/v2"
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/os/gcmd"
"github.com/gogf/gf/v2/util/gtag"
@ -55,24 +48,18 @@ func (c cGF) Index(ctx context.Context, in cGFInput) (out *cGFOutput, err error)
_, err = Version.Index(ctx, cVersionInput{})
return
}
answer := "n"
// No argument or option, do installation checks.
if data, isInstalled := service.Install.IsInstalled(); !isInstalled {
mlog.Print("hi, it seems it's the first time you installing gf cli.")
answer = gcmd.Scanf("do you want to install gf(%s) binary to your system? [y/n]: ", gf.VERSION)
} else if !data.IsSelf {
mlog.Print("hi, you have installed gf cli.")
answer = gcmd.Scanf("do you want to install gf(%s) binary to your system? [y/n]: ", gf.VERSION)
}
if strings.EqualFold(answer, "y") {
if err = service.Install.Run(ctx); err != nil {
if !service.Install.IsInstalled() {
mlog.Print("hi, it seams it's the first time you installing gf cli.")
s := gcmd.Scanf("do you want to install gf binary to your system? [y/n]: ")
if strings.EqualFold(s, "y") {
if err = service.Install.Run(ctx); err != nil {
return
}
gcmd.Scan("press `Enter` to exit...")
return
}
gcmd.Scan("press `Enter` to exit...")
return
}
// Print help content.
gcmd.CommandFromCtx(ctx).Print()
return

View File

@ -1,9 +1,3 @@
// Copyright GoFrame gf Author(https://goframe.org). All Rights Reserved.
//
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with this file,
// You can obtain one at https://github.com/gogf/gf.
package cmd
import (
@ -17,7 +11,6 @@ import (
"github.com/gogf/gf/v2/encoding/gbase64"
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/os/gbuild"
"github.com/gogf/gf/v2/os/gcmd"
"github.com/gogf/gf/v2/os/genv"
"github.com/gogf/gf/v2/os/gfile"
@ -44,11 +37,10 @@ type cBuild struct {
}
const (
cBuildDefaultFile = "main.go"
cBuildBrief = `cross-building go project for lots of platforms`
cBuildEg = `
cBuildBrief = `cross-building go project for lots of platforms`
cBuildEg = `
gf build main.go
gf build main.go --ps public,template
gf build main.go --pack public,template
gf build main.go --cgo
gf build main.go -m none
gf build main.go -n my-app -a all -s all
@ -124,28 +116,25 @@ type cBuildInput struct {
Arch string `short:"a" name:"arch" brief:"output binary architecture, multiple arch separated with ','"`
System string `short:"s" name:"system" brief:"output binary system, multiple os separated with ','"`
Output string `short:"o" name:"output" brief:"output binary path, used when building single binary file"`
Path string `short:"p" name:"path" brief:"output binary directory path, default is '.'" d:"."`
Path string `short:"p" name:"path" brief:"output binary directory path, default is './temp'" d:"./temp"`
Extra string `short:"e" name:"extra" brief:"extra custom \"go build\" options"`
Mod string `short:"m" name:"mod" brief:"like \"-mod\" option of \"go build\", use \"-m none\" to disable go module"`
Cgo bool `short:"c" name:"cgo" brief:"enable or disable cgo feature, it's disabled in default" orphan:"true"`
VarMap g.Map `short:"r" name:"varMap" brief:"custom built embedded variable into binary"`
PackSrc string `short:"ps" name:"packSrc" brief:"pack one or more folders into one go file before building"`
PackDst string `short:"pd" name:"packDst" brief:"temporary go file path for pack, this go file will be automatically removed after built" d:"internal/packed/build_pack_data.go"`
ExitWhenError bool `short:"ew" name:"exitWhenError" brief:"exit building when any error occurs, specially for multiple arch and system buildings. default is false" orphan:"true"`
DumpENV bool `short:"de" name:"dumpEnv" brief:"dump current go build environment before building binary" orphan:"true"`
ExitWhenError bool `short:"ew" name:"exitWhenError" brief:"exit building when any error occurs, default is false" orphan:"true"`
}
type cBuildOutput struct{}
func (c cBuild) Index(ctx context.Context, in cBuildInput) (out *cBuildOutput, err error) {
// print used go env
if in.DumpENV {
_, _ = Env.Index(ctx, cEnvInput{})
}
_, _ = Env.Index(ctx, cEnvInput{})
mlog.SetHeaderPrint(true)
mlog.Debugf(`build command input: %+v`, in)
mlog.Debugf(`build input: %+v`, in)
// Necessary check.
if gproc.SearchBinary("go") == "" {
mlog.Fatalf(`command "go" not found in your environment, please install golang first to proceed this command`)
@ -153,15 +142,14 @@ func (c cBuild) Index(ctx context.Context, in cBuildInput) (out *cBuildOutput, e
var (
parser = gcmd.ParserFromCtx(ctx)
file = in.File
file = parser.GetArg(2).String()
)
if file == "" {
file = parser.GetArg(2).String()
if len(file) < 1 {
// Check and use the main.go file.
if gfile.Exists(cBuildDefaultFile) {
file = cBuildDefaultFile
if gfile.Exists("main.go") {
file = "main.go"
} else {
mlog.Fatal("build file path is empty or main.go not found in current working directory")
mlog.Fatal("build file path cannot be empty")
}
}
if in.Name == "" {
@ -217,7 +205,7 @@ func (c cBuild) Index(ctx context.Context, in cBuildInput) (out *cBuildOutput, e
if !gfile.Exists(in.PackDst) {
// Remove the go file that is automatically packed resource.
defer func() {
_ = gfile.RemoveFile(in.PackDst)
_ = gfile.Remove(in.PackDst)
mlog.Printf(`remove the automatically generated resource go file: %s`, in.PackDst)
}()
}
@ -241,35 +229,60 @@ func (c cBuild) Index(ctx context.Context, in cBuildInput) (out *cBuildOutput, e
} else {
genv.MustSet("CGO_ENABLED", "0")
}
var (
cmd = ""
ext = ""
)
for system, item := range platformMap {
cmd = ""
ext = ""
if len(customSystems) > 0 && customSystems[0] != "all" && !gstr.InArray(customSystems, system) {
continue
}
for arch := range item {
for arch, _ := range item {
if len(customArches) > 0 && customArches[0] != "all" && !gstr.InArray(customArches, arch) {
continue
}
if len(customSystems) == 0 && len(customArches) == 0 {
if runtime.GOOS == "windows" {
ext = ".exe"
}
// Single binary building, output the binary to current working folder.
// For example:
// `gf build`
// `gf build -o main.exe`
c.doBinaryBuild(
ctx, file,
in.Output, in.Path,
runtime.GOOS, runtime.GOARCH, in.Name, ldFlags, in.Extra,
in.ExitWhenError,
true,
)
output := ""
if len(in.Output) > 0 {
output = "-o " + in.Output + ext
} else {
output = "-o " + in.Name + ext
}
cmd = fmt.Sprintf(`go build %s -ldflags "%s" %s %s`, output, ldFlags, in.Extra, file)
} else {
c.doBinaryBuild(
ctx, file,
in.Output, in.Path,
system, arch, in.Name, ldFlags, in.Extra,
in.ExitWhenError,
false,
// Cross-building, output the compiled binary to specified path.
if system == "windows" {
ext = ".exe"
}
genv.MustSet("GOOS", system)
genv.MustSet("GOARCH", arch)
cmd = fmt.Sprintf(
`go build -o %s/%s/%s%s -ldflags "%s" %s%s`,
in.Path, system+"_"+arch, in.Name, ext, ldFlags, in.Extra, file,
)
}
mlog.Debug(cmd)
// It's not necessary printing the complete command string.
cmdShow, _ := gregex.ReplaceString(`\s+(-ldflags ".+?")\s+`, " ", cmd)
mlog.Print(cmdShow)
if result, err := gproc.ShellExec(ctx, cmd); err != nil {
mlog.Printf(
"failed to build, os:%s, arch:%s, error:\n%s\n\n%s\n",
system, arch, gstr.Trim(result),
`you may use command option "--debug" to enable debug info and check the details`,
)
if in.ExitWhenError {
os.Exit(1)
}
} else {
mlog.Debug(gstr.Trim(result))
}
// single binary building.
if len(customSystems) == 0 && len(customArches) == 0 {
goto buildDone
@ -282,78 +295,15 @@ buildDone:
return
}
func (c cBuild) doBinaryBuild(
ctx context.Context,
filePath string,
outputPath, dirPath string,
system, arch, name, ldFlags, extra string,
exitWhenError bool,
singleBuild bool,
) {
var (
cmd string
ext string
)
// Cross-building, output the compiled binary to specified path.
if system == "windows" {
ext = ".exe"
}
genv.MustSet("GOOS", system)
genv.MustSet("GOARCH", arch)
if outputPath != "" {
outputPath = "-o " + outputPath
} else {
if dirPath == "" {
dirPath = "."
} else {
dirPath = gstr.TrimRight(dirPath, "/")
}
if singleBuild {
outputPath = fmt.Sprintf(
"-o %s/%s%s",
dirPath, name, ext,
)
} else {
outputPath = fmt.Sprintf(
"-o %s/%s/%s%s",
dirPath, system+"_"+arch, name, ext,
)
}
}
cmd = fmt.Sprintf(
`go build %s -ldflags "%s" %s%s`,
outputPath, ldFlags, extra, filePath,
)
mlog.Debug(fmt.Sprintf("build for GOOS=%s GOARCH=%s", system, arch))
mlog.Debug(cmd)
// It's not necessary printing the complete command string, filtering ldFlags.
cmdShow, _ := gregex.ReplaceString(`\s+(-ldflags ".+?")\s+`, " ", cmd)
mlog.Print(cmdShow)
if result, err := gproc.ShellExec(ctx, cmd); err != nil {
mlog.Printf(
"failed to build, os:%s, arch:%s, error:\n%s\n\n%s\n",
system, arch, gstr.Trim(result),
`you may use command option "--debug" to enable debug info and check the details`,
)
if exitWhenError {
os.Exit(1)
}
} else {
mlog.Debug(gstr.Trim(result))
}
}
// getBuildInVarStr retrieves and returns the custom build-in variables in configuration
// getBuildInVarMapJson retrieves and returns the custom build-in variables in configuration
// file as json.
func (c cBuild) getBuildInVarStr(ctx context.Context, in cBuildInput) string {
buildInVarMap := in.VarMap
if buildInVarMap == nil {
buildInVarMap = make(g.Map)
}
buildInVarMap[gbuild.BuiltGit] = c.getGitCommit(ctx)
buildInVarMap[gbuild.BuiltTime] = gtime.Now().String()
buildInVarMap[gbuild.BuiltVersion] = in.Version
buildInVarMap["builtGit"] = c.getGitCommit(ctx)
buildInVarMap["builtTime"] = gtime.Now().String()
b, err := json.Marshal(buildInVarMap)
if err != nil {
mlog.Fatal(err)

View File

@ -1,183 +0,0 @@
// Copyright GoFrame gf Author(https://goframe.org). All Rights Reserved.
//
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with this file,
// You can obtain one at https://github.com/gogf/gf.
package cmd
import (
"context"
"fmt"
"io"
"net/http"
"os"
"path"
"path/filepath"
"time"
"github.com/gogf/gf/cmd/gf/v2/internal/utility/mlog"
"github.com/gogf/gf/v2/encoding/gcompress"
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/os/gfile"
)
const (
GitName = "gf-site"
BranchName = "gh-pages"
SiteFileName = GitName + "-" + BranchName
// DocURL is the download address of the document
DocURL = "https://github.com/gogf/" + GitName + "/archive/refs/heads/" + BranchName + ".zip"
)
var (
Doc = cDoc{}
)
type cDoc struct {
g.Meta `name:"doc" brief:"download https://pages.goframe.org/ to run locally"`
}
type cDocInput struct {
g.Meta `name:"doc" config:"gfcli.doc"`
Path string `short:"p" name:"path" brief:"download docs directory path, default is \"%temp%/goframe\""`
Port int `short:"o" name:"port" brief:"http server port, default is 8080" d:"8080"`
Update bool `short:"u" name:"update" brief:"clean docs directory and update docs"`
Clean bool `short:"c" name:"clean" brief:"clean docs directory"`
Proxy string `short:"x" name:"proxy" brief:"proxy for download, such as https://hub.gitmirror.com/;https://ghproxy.com/;https://ghproxy.net/;https://ghps.cc/"`
}
type cDocOutput struct{}
func (c cDoc) Index(ctx context.Context, in cDocInput) (out *cDocOutput, err error) {
docs := NewDocSetting(ctx, in)
mlog.Print("Directory where the document is downloaded:", docs.TempDir)
if in.Clean {
mlog.Print("Cleaning document directory")
err = docs.Clean()
if err != nil {
mlog.Print("Failed to clean document directory:", err)
return
}
return
}
if in.Update {
mlog.Print("Cleaning old document directory")
err = docs.Clean()
if err != nil {
mlog.Print("Failed to clean old document directory:", err)
return
}
}
err = docs.DownloadDoc()
if err != nil {
mlog.Print("Failed to download document:", err)
return
}
http.Handle("/", http.FileServer(http.Dir(docs.DocDir)))
mlog.Printf("Access address http://127.0.0.1:%d in %s", in.Port, docs.DocDir)
err = http.ListenAndServe(fmt.Sprintf(":%d", in.Port), nil)
if err != nil {
return nil, err
}
return
}
// DocSetting doc setting
type DocSetting struct {
TempDir string
DocURL string
DocDir string
DocZipFile string
}
// NewDocSetting new DocSetting
func NewDocSetting(ctx context.Context, in cDocInput) *DocSetting {
fileName := SiteFileName + ".zip"
tempDir := in.Path
if tempDir == "" {
tempDir = gfile.Temp("goframe/docs")
} else {
tempDir = gfile.Abs(path.Join(tempDir, "docs"))
}
return &DocSetting{
TempDir: filepath.FromSlash(tempDir),
DocDir: filepath.FromSlash(path.Join(tempDir, SiteFileName)),
DocURL: in.Proxy + DocURL,
DocZipFile: filepath.FromSlash(path.Join(tempDir, fileName)),
}
}
// Clean cleans the temporary directory
func (d *DocSetting) Clean() error {
if _, err := os.Stat(d.TempDir); err == nil {
err = gfile.RemoveAll(d.TempDir)
if err != nil {
mlog.Print("Failed to delete temporary directory:", err)
return err
}
}
return nil
}
// DownloadDoc download the document
func (d *DocSetting) DownloadDoc() error {
if _, err := os.Stat(d.TempDir); err != nil {
err = gfile.Mkdir(d.TempDir)
if err != nil {
mlog.Print("Failed to create temporary directory:", err)
return nil
}
}
// Check if the file exists
if _, err := os.Stat(d.DocDir); err == nil {
mlog.Print("Document already exists, no need to download and unzip")
return nil
}
if _, err := os.Stat(d.DocZipFile); err == nil {
mlog.Print("File already exists, no need to download")
} else {
mlog.Printf("File does not exist, start downloading: %s", d.DocURL)
startTime := time.Now()
// Download the file
resp, err := http.Get(d.DocURL)
if err != nil {
mlog.Print("Failed to download file:", err)
return err
}
defer resp.Body.Close()
// Create the file
out, err := os.Create(d.DocZipFile)
if err != nil {
mlog.Print("Failed to create file:", err)
return err
}
defer out.Close()
// Write the response body to the file
_, err = io.Copy(out, resp.Body)
if err != nil {
mlog.Print("Failed to write file:", err)
return err
}
mlog.Printf("Download successful, time-consuming: %v", time.Since(startTime))
}
mlog.Print("Start unzipping the file...")
// Unzip the file
err := gcompress.UnZipFile(d.DocZipFile, d.TempDir)
if err != nil {
mlog.Print("Failed to unzip the file, please run again:", err)
_ = gfile.RemoveFile(d.DocZipFile)
return err
}
mlog.Print("Download and unzip successful")
return nil
}

View File

@ -1,9 +1,3 @@
// Copyright GoFrame gf Author(https://goframe.org). All Rights Reserved.
//
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with this file,
// You can obtain one at https://github.com/gogf/gf.
package cmd
import (
@ -39,7 +33,6 @@ gf docker main.go
gf docker main.go -t hub.docker.com/john/image:tag
gf docker main.go -t hub.docker.com/john/image:tag
gf docker main.go -p -t hub.docker.com/john/image:tag
gf docker main.go -p -tp ["hub.docker.com/john","hub.docker.com/smith"] -tn image:tag
`
cDockerDc = `
The "docker" command builds the GF project to a docker images.
@ -52,7 +45,6 @@ You should have docker installed, and there must be a Dockerfile in the root of
cDockerFileBrief = `file path of the Dockerfile. it's "manifest/docker/Dockerfile" in default`
cDockerShellBrief = `path of the shell file which is executed before docker build`
cDockerPushBrief = `auto push the docker image to docker registry if "-t" option passed`
cDockerTagBrief = `full tag for this docker, pattern like "xxx.xxx.xxx/image:tag"`
cDockerTagNameBrief = `tag name for this docker, pattern like "image:tag". this option is required with TagPrefixes`
cDockerTagPrefixesBrief = `tag prefixes for this docker, which are used for docker push. this option is required with TagName`
cDockerExtraBrief = `extra build options passed to "docker image"`
@ -69,7 +61,6 @@ func init() {
`cDockerShellBrief`: cDockerShellBrief,
`cDockerBuildBrief`: cDockerBuildBrief,
`cDockerPushBrief`: cDockerPushBrief,
`cDockerTagBrief`: cDockerTagBrief,
`cDockerTagNameBrief`: cDockerTagNameBrief,
`cDockerTagPrefixesBrief`: cDockerTagPrefixesBrief,
`cDockerExtraBrief`: cDockerExtraBrief,
@ -78,11 +69,10 @@ func init() {
type cDockerInput struct {
g.Meta `name:"docker" config:"gfcli.docker"`
Main string `name:"MAIN" arg:"true" brief:"{cDockerMainBrief}" d:"main.go"`
Main string `name:"MAIN" arg:"true" brief:"{cDockerMainBrief}" d:"main.go"`
File string `name:"file" short:"f" brief:"{cDockerFileBrief}" d:"manifest/docker/Dockerfile"`
Shell string `name:"shell" short:"s" brief:"{cDockerShellBrief}" d:"manifest/docker/docker.sh"`
Build string `name:"build" short:"b" brief:"{cDockerBuildBrief}"`
Tag string `name:"tag" short:"t" brief:"{cDockerTagBrief}"`
Build string `name:"build" short:"b" brief:"{cDockerBuildBrief}" d:"-a amd64 -s linux"`
TagName string `name:"tagName" short:"tn" brief:"{cDockerTagNameBrief}" v:"required-with:TagPrefixes"`
TagPrefixes []string `name:"tagPrefixes" short:"tp" brief:"{cDockerTagPrefixesBrief}" v:"required-with:TagName"`
Push bool `name:"push" short:"p" brief:"{cDockerPushBrief}" orphan:"true"`
@ -97,23 +87,17 @@ func (c cDocker) Index(ctx context.Context, in cDockerInput) (out *cDockerOutput
mlog.Fatalf(`command "docker" not found in your environment, please install docker first to proceed this command`)
}
mlog.Debugf(`docker command input: %+v`, in)
// Binary build.
if in.Main != "" && in.Build != "" {
in.Build += " --exitWhenError"
if in.Main != "" {
if err = gproc.ShellRun(ctx, fmt.Sprintf(`gf build %s %s`, in.Main, in.Build)); err != nil {
mlog.Debugf(`build binary failed with error: %+v`, err)
return
}
in.Build += " --exit"
if in.Main != "" {
if err = gproc.ShellRun(ctx, fmt.Sprintf(`gf build %s %s`, in.Main, in.Build)); err != nil {
return
}
}
// Shell executing.
if in.Shell != "" && gfile.Exists(in.Shell) {
if err = c.exeDockerShell(ctx, in.Shell); err != nil {
mlog.Debugf(`build docker failed with error: %+v`, err)
return
}
}
@ -130,7 +114,7 @@ func (c cDocker) Index(ctx context.Context, in cDockerInput) (out *cDockerOutput
}
}
if len(dockerTags) == 0 {
dockerTags = []string{in.Tag}
dockerTags = []string{""}
}
for i, dockerTag := range dockerTags {
if i > 0 {

View File

@ -1,21 +1,14 @@
// Copyright GoFrame gf Author(https://goframe.org). All Rights Reserved.
//
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with this file,
// You can obtain one at https://github.com/gogf/gf.
package cmd
import (
"bytes"
"context"
"github.com/olekukonko/tablewriter"
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/os/gproc"
"github.com/gogf/gf/v2/text/gregex"
"github.com/gogf/gf/v2/text/gstr"
"github.com/olekukonko/tablewriter"
"github.com/gogf/gf/cmd/gf/v2/internal/utility/mlog"
)

View File

@ -1,17 +1,8 @@
// Copyright GoFrame gf Author(https://goframe.org). All Rights Reserved.
//
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with this file,
// You can obtain one at https://github.com/gogf/gf.
package cmd
import (
"context"
"github.com/gogf/gf/v2/os/gproc"
"github.com/gogf/gf/v2/text/gregex"
"github.com/gogf/gf/cmd/gf/v2/internal/utility/mlog"
"github.com/gogf/gf/v2/errors/gerror"
"github.com/gogf/gf/v2/frame/g"
@ -28,9 +19,8 @@ type cFix struct {
}
type cFixInput struct {
g.Meta `name:"fix"`
Path string `name:"path" short:"p" brief:"directory path, it uses current working directory in default"`
Version string `name:"version" short:"v" brief:"custom specified version to fix, leave it empty to auto detect"`
g.Meta `name:"fix"`
Path string `name:"path" brief:"directory path, it uses current working directory in default"`
}
type cFixOutput struct{}
@ -41,46 +31,38 @@ type cFixItem struct {
}
func (c cFix) Index(ctx context.Context, in cFixInput) (out *cFixOutput, err error) {
mlog.Print(`start auto fixing...`)
defer mlog.Print(`done!`)
if in.Path == "" {
in.Path = gfile.Pwd()
}
if in.Version == "" {
in.Version, err = c.autoDetectVersion(in)
if err != nil {
mlog.Fatal(err)
}
if in.Version == "" {
mlog.Print(`no GoFrame usage found, exit fixing`)
return
}
mlog.Debugf(`current GoFrame version auto detect "%s"`, in.Version)
}
if !gproc.IsChild() {
mlog.Printf(`start auto fixing directory path "%s"...`, in.Path)
defer mlog.Print(`done!`)
}
err = c.doFix(in)
return
}
func (c cFix) doFix(in cFixInput) (err error) {
version, err := c.getVersion(in)
if err != nil {
mlog.Fatal(err)
}
if version == "" {
mlog.Print(`no GoFrame usage found, exit fixing`)
return
}
mlog.Debugf(`current GoFrame version found "%s"`, version)
var items = []cFixItem{
{Version: "v2.3", Func: c.doFixV23},
{Version: "v2.5", Func: c.doFixV25},
}
for _, item := range items {
if gstr.CompareVersionGo(in.Version, item.Version) < 0 {
if gstr.CompareVersionGo(version, item.Version) < 0 {
mlog.Debugf(
`current GoFrame or contrib package version "%s" is lesser than "%s", nothing to do`,
in.Version, item.Version,
`current GoFrame version "%s" is lesser than "%s", nothing to do`,
version, item.Version,
)
continue
}
if err = item.Func(in.Version); err != nil {
if err = item.Func(version); err != nil {
return
}
}
@ -105,29 +87,7 @@ func (c cFix) doFixV23(version string) error {
return gfile.ReplaceDirFunc(replaceFunc, ".", "*.go", true)
}
// doFixV25 fixes code when upgrading to GoFrame v2.5.
func (c cFix) doFixV25(version string) (err error) {
replaceFunc := func(path, content string) string {
content, err = c.doFixV25Content(content)
return content
}
return gfile.ReplaceDirFunc(replaceFunc, ".", "*.go", true)
}
func (c cFix) doFixV25Content(content string) (newContent string, err error) {
newContent = content
if gstr.Contains(content, `.BindHookHandlerByMap(`) {
var pattern = `\.BindHookHandlerByMap\((.+?), map\[string\]ghttp\.HandlerFunc`
newContent, err = gregex.ReplaceString(
pattern,
`.BindHookHandlerByMap($1, map[ghttp.HookName]ghttp.HandlerFunc`,
content,
)
}
return
}
func (c cFix) autoDetectVersion(in cFixInput) (string, error) {
func (c cFix) getVersion(in cFixInput) (string, error) {
var (
err error
path = gfile.Join(in.Path, "go.mod")

View File

@ -1,9 +1,3 @@
// Copyright GoFrame gf Author(https://goframe.org). All Rights Reserved.
//
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with this file,
// You can obtain one at https://github.com/gogf/gf.
package cmd
import (
@ -18,8 +12,6 @@ var (
type cGen struct {
g.Meta `name:"gen" brief:"{cGenBrief}" dc:"{cGenDc}"`
cGenDao
cGenEnums
cGenCtrl
cGenPb
cGenPbEntity
cGenService

View File

@ -1,15 +0,0 @@
// Copyright GoFrame gf Author(https://goframe.org). All Rights Reserved.
//
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with this file,
// You can obtain one at https://github.com/gogf/gf.
package cmd
import (
"github.com/gogf/gf/cmd/gf/v2/internal/cmd/genctrl"
)
type (
cGenCtrl = genctrl.CGenCtrl
)

View File

@ -1,14 +1,6 @@
// Copyright GoFrame gf Author(https://goframe.org). All Rights Reserved.
//
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with this file,
// You can obtain one at https://github.com/gogf/gf.
package cmd
import (
_ "github.com/gogf/gf/contrib/drivers/clickhouse/v2"
// _ "github.com/gogf/gf/contrib/drivers/dm/v2" // precompilation does not support certain target platforms.
_ "github.com/gogf/gf/contrib/drivers/mssql/v2"
_ "github.com/gogf/gf/contrib/drivers/mysql/v2"
_ "github.com/gogf/gf/contrib/drivers/oracle/v2"

View File

@ -1,15 +0,0 @@
// Copyright GoFrame gf Author(https://goframe.org). All Rights Reserved.
//
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with this file,
// You can obtain one at https://github.com/gogf/gf.
package cmd
import (
"github.com/gogf/gf/cmd/gf/v2/internal/cmd/genenums"
)
type (
cGenEnums = genenums.CGenEnums
)

View File

@ -1,13 +1,79 @@
// Copyright GoFrame gf Author(https://goframe.org). All Rights Reserved.
//
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with this file,
// You can obtain one at https://github.com/gogf/gf.
package cmd
import "github.com/gogf/gf/cmd/gf/v2/internal/cmd/genpb"
import (
"context"
"fmt"
"github.com/gogf/gf/cmd/gf/v2/internal/utility/mlog"
"github.com/gogf/gf/v2/container/gset"
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/os/genv"
"github.com/gogf/gf/v2/os/gfile"
"github.com/gogf/gf/v2/os/gproc"
)
type (
cGenPb = genpb.CGenPb
cGenPb struct{}
cGenPbInput struct {
g.Meta `name:"pb" brief:"parse proto files and generate protobuf go files"`
}
cGenPbOutput struct{}
)
func (c cGenPb) Pb(ctx context.Context, in cGenPbInput) (out *cGenPbOutput, err error) {
// Necessary check.
if gproc.SearchBinary("protoc") == "" {
mlog.Fatalf(`command "protoc" not found in your environment, please install protoc first to proceed this command`)
}
// protocol fold checks.
protoFolder := "protocol"
if !gfile.Exists(protoFolder) {
mlog.Fatalf(`proto files folder "%s" does not exist`, protoFolder)
}
// folder scanning.
files, err := gfile.ScanDirFile(protoFolder, "*.proto", true)
if err != nil {
mlog.Fatal(err)
}
if len(files) == 0 {
mlog.Fatalf(`no proto files found in folder "%s"`, protoFolder)
}
dirSet := gset.NewStrSet()
for _, file := range files {
dirSet.Add(gfile.Dir(file))
}
var (
servicePath = gfile.RealPath(".")
goPathSrc = gfile.RealPath(gfile.Join(genv.Get("GOPATH").String(), "src"))
)
dirSet.Iterator(func(protoDirPath string) bool {
parsingCommand := fmt.Sprintf(
"protoc --gofast_out=plugins=grpc:. %s/*.proto -I%s",
protoDirPath,
servicePath,
)
if goPathSrc != "" {
parsingCommand += " -I" + goPathSrc
}
mlog.Print(parsingCommand)
if output, err := gproc.ShellExec(ctx, parsingCommand); err != nil {
mlog.Print(output)
mlog.Fatal(err)
}
return true
})
// Custom replacement.
//pbFolder := "protobuf"
//_, _ = gfile.ScanDirFileFunc(pbFolder, "*.go", true, func(path string) string {
// content := gfile.GetContents(path)
// content = gstr.ReplaceByArray(content, g.SliceStr{
// `gtime "gtime"`, `gtime "github.com/gogf/gf/v2/os/gtime"`,
// })
// _ = gfile.PutContents(path, content)
// utils.GoFmt(path)
// return path
//})
mlog.Print("done!")
return
}

View File

@ -1,13 +1,411 @@
// Copyright GoFrame gf Author(https://goframe.org). All Rights Reserved.
//
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with this file,
// You can obtain one at https://github.com/gogf/gf.
package cmd
import "github.com/gogf/gf/cmd/gf/v2/internal/cmd/genpbentity"
import (
"bytes"
"context"
"fmt"
"strings"
"github.com/gogf/gf/v2/database/gdb"
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/os/gfile"
"github.com/gogf/gf/v2/os/gtime"
"github.com/gogf/gf/v2/text/gregex"
"github.com/gogf/gf/v2/text/gstr"
"github.com/gogf/gf/v2/util/gconv"
"github.com/gogf/gf/v2/util/gtag"
"github.com/olekukonko/tablewriter"
"github.com/gogf/gf/cmd/gf/v2/internal/consts"
"github.com/gogf/gf/cmd/gf/v2/internal/utility/mlog"
)
type (
cGenPbEntity = genpbentity.CGenPbEntity
cGenPbEntity struct{}
cGenPbEntityInput struct {
g.Meta `name:"pbentity" config:"{cGenPbEntityConfig}" brief:"{cGenPbEntityBrief}" eg:"{cGenPbEntityEg}" ad:"{cGenPbEntityAd}"`
Path string `name:"path" short:"p" brief:"{cGenPbEntityBriefPath}"`
Package string `name:"package" short:"k" brief:"{cGenPbEntityBriefPackage}"`
Link string `name:"link" short:"l" brief:"{cGenPbEntityBriefLink}"`
Tables string `name:"tables" short:"t" brief:"{cGenPbEntityBriefTables}"`
Prefix string `name:"prefix" short:"f" brief:"{cGenPbEntityBriefPrefix}"`
RemovePrefix string `name:"removePrefix" short:"r" brief:"{cGenPbEntityBriefRemovePrefix}"`
NameCase string `name:"nameCase" short:"n" brief:"{cGenPbEntityBriefNameCase}" d:"Camel"`
JsonCase string `name:"jsonCase" short:"j" brief:"{cGenPbEntityBriefJsonCase}" d:"CamelLower"`
Option string `name:"option" short:"o" brief:"{cGenPbEntityBriefOption}"`
}
cGenPbEntityOutput struct{}
cGenPbEntityInternalInput struct {
cGenPbEntityInput
TableName string // TableName specifies the table name of the table.
NewTableName string // NewTableName specifies the prefix-stripped name of the table.
}
)
const (
cGenPbEntityConfig = `gfcli.gen.pbentity`
cGenPbEntityBrief = `generate entity message files in protobuf3 format`
cGenPbEntityEg = `
gf gen pbentity
gf gen pbentity -l "mysql:root:12345678@tcp(127.0.0.1:3306)/test"
gf gen pbentity -p ./protocol/demos/entity -t user,user_detail,user_login
gf gen pbentity -r user_
`
cGenPbEntityAd = `
CONFIGURATION SUPPORT
Options are also supported by configuration file.
It's suggested using configuration file instead of command line arguments making producing.
The configuration node name is "gf.gen.pbentity", which also supports multiple databases, for example(config.yaml):
gfcli:
gen:
- pbentity:
link: "mysql:root:12345678@tcp(127.0.0.1:3306)/test"
path: "protocol/demos/entity"
tables: "order,products"
package: "demos"
- pbentity:
link: "mysql:root:12345678@tcp(127.0.0.1:3306)/primary"
path: "protocol/demos/entity"
prefix: "primary_"
tables: "user, userDetail"
package: "demos"
option: |
option go_package = "protobuf/demos";
option java_package = "protobuf/demos";
option php_namespace = "protobuf/demos";
`
cGenPbEntityBriefPath = `directory path for generated files`
cGenPbEntityBriefPackage = `package name for all entity proto files`
cGenPbEntityBriefLink = `database configuration, the same as the ORM configuration of GoFrame`
cGenPbEntityBriefTables = `generate models only for given tables, multiple table names separated with ','`
cGenPbEntityBriefPrefix = `add specified prefix for all entity names and entity proto files`
cGenPbEntityBriefRemovePrefix = `remove specified prefix of the table, multiple prefix separated with ','`
cGenPbEntityBriefOption = `extra protobuf options`
cGenPbEntityBriefGroup = `
specifying the configuration group name of database for generated ORM instance,
it's not necessary and the default value is "default"
`
cGenPbEntityBriefNameCase = `
case for message attribute names, default is "Camel":
| Case | Example |
|---------------- |--------------------|
| Camel | AnyKindOfString |
| CamelLower | anyKindOfString | default
| Snake | any_kind_of_string |
| SnakeScreaming | ANY_KIND_OF_STRING |
| SnakeFirstUpper | rgb_code_md5 |
| Kebab | any-kind-of-string |
| KebabScreaming | ANY-KIND-OF-STRING |
`
cGenPbEntityBriefJsonCase = `
case for message json tag, cases are the same as "nameCase", default "CamelLower".
set it to "none" to ignore json tag generating.
`
)
func init() {
gtag.Sets(g.MapStrStr{
`cGenPbEntityConfig`: cGenPbEntityConfig,
`cGenPbEntityBrief`: cGenPbEntityBrief,
`cGenPbEntityEg`: cGenPbEntityEg,
`cGenPbEntityAd`: cGenPbEntityAd,
`cGenPbEntityBriefPath`: cGenPbEntityBriefPath,
`cGenPbEntityBriefPackage`: cGenPbEntityBriefPackage,
`cGenPbEntityBriefLink`: cGenPbEntityBriefLink,
`cGenPbEntityBriefTables`: cGenPbEntityBriefTables,
`cGenPbEntityBriefPrefix`: cGenPbEntityBriefPrefix,
`cGenPbEntityBriefRemovePrefix`: cGenPbEntityBriefRemovePrefix,
`cGenPbEntityBriefGroup`: cGenPbEntityBriefGroup,
`cGenPbEntityBriefNameCase`: cGenPbEntityBriefNameCase,
`cGenPbEntityBriefJsonCase`: cGenPbEntityBriefJsonCase,
`cGenPbEntityBriefOption`: cGenPbEntityBriefOption,
})
}
func (c cGenPbEntity) PbEntity(ctx context.Context, in cGenPbEntityInput) (out *cGenPbEntityOutput, err error) {
var (
config = g.Cfg()
)
if config.Available(ctx) {
v := config.MustGet(ctx, cGenPbEntityConfig)
if v.IsSlice() {
for i := 0; i < len(v.Interfaces()); i++ {
doGenPbEntityForArray(ctx, i, in)
}
} else {
doGenPbEntityForArray(ctx, -1, in)
}
} else {
doGenPbEntityForArray(ctx, -1, in)
}
mlog.Print("done!")
return
}
func doGenPbEntityForArray(ctx context.Context, index int, in cGenPbEntityInput) {
var (
err error
db gdb.DB
)
if index >= 0 {
err = g.Cfg().MustGet(
ctx,
fmt.Sprintf(`%s.%d`, cGenPbEntityConfig, index),
).Scan(&in)
if err != nil {
mlog.Fatalf(`invalid configuration of "%s": %+v`, cGenPbEntityConfig, err)
}
}
if in.Package == "" {
mlog.Fatal("package name should not be empty")
}
removePrefixArray := gstr.SplitAndTrim(in.RemovePrefix, ",")
// It uses user passed database configuration.
if in.Link != "" {
var (
tempGroup = gtime.TimestampNanoStr()
match, _ = gregex.MatchString(`([a-z]+):(.+)`, in.Link)
)
if len(match) == 3 {
gdb.AddConfigNode(tempGroup, gdb.ConfigNode{
Type: gstr.Trim(match[1]),
Link: gstr.Trim(match[2]),
})
db, _ = gdb.Instance(tempGroup)
}
} else {
db = g.DB()
}
if db == nil {
mlog.Fatal("database initialization failed")
}
tableNames := ([]string)(nil)
if in.Tables != "" {
tableNames = gstr.SplitAndTrim(in.Tables, ",")
} else {
tableNames, err = db.Tables(context.TODO())
if err != nil {
mlog.Fatalf("fetching tables failed: \n %v", err)
}
}
for _, tableName := range tableNames {
newTableName := tableName
for _, v := range removePrefixArray {
newTableName = gstr.TrimLeftStr(newTableName, v, 1)
}
generatePbEntityContentFile(ctx, db, cGenPbEntityInternalInput{
cGenPbEntityInput: in,
TableName: tableName,
NewTableName: newTableName,
})
}
}
// generatePbEntityContentFile generates the protobuf files for given table.
func generatePbEntityContentFile(ctx context.Context, db gdb.DB, in cGenPbEntityInternalInput) {
fieldMap, err := db.TableFields(ctx, in.TableName)
if err != nil {
mlog.Fatalf("fetching tables fields failed for table '%s':\n%v", in.TableName, err)
}
// Change the `newTableName` if `Prefix` is given.
newTableName := "Entity_" + in.Prefix + in.NewTableName
var (
tableNameCamelCase = gstr.CaseCamel(newTableName)
tableNameSnakeCase = gstr.CaseSnake(newTableName)
entityMessageDefine = generateEntityMessageDefinition(tableNameCamelCase, fieldMap, in)
fileName = gstr.Trim(tableNameSnakeCase, "-_.")
path = gfile.Join(in.Path, fileName+".proto")
)
entityContent := gstr.ReplaceByMap(getTplPbEntityContent(""), g.MapStrStr{
"{PackageName}": in.Package,
"{OptionContent}": in.Option,
"{EntityMessage}": entityMessageDefine,
})
if err := gfile.PutContents(path, strings.TrimSpace(entityContent)); err != nil {
mlog.Fatalf("writing content to '%s' failed: %v", path, err)
} else {
mlog.Print("generated:", path)
}
}
// generateEntityMessageDefinition generates and returns the message definition for specified table.
func generateEntityMessageDefinition(entityName string, fieldMap map[string]*gdb.TableField, in cGenPbEntityInternalInput) string {
var (
buffer = bytes.NewBuffer(nil)
array = make([][]string, len(fieldMap))
names = sortFieldKeyForPbEntity(fieldMap)
)
for index, name := range names {
array[index] = generateMessageFieldForPbEntity(index+1, fieldMap[name], in)
}
tw := tablewriter.NewWriter(buffer)
tw.SetBorder(false)
tw.SetRowLine(false)
tw.SetAutoWrapText(false)
tw.SetColumnSeparator("")
tw.AppendBulk(array)
tw.Render()
stContent := buffer.String()
// Let's do this hack of table writer for indent!
stContent = gstr.Replace(stContent, " #", "")
buffer.Reset()
buffer.WriteString(fmt.Sprintf("message %s {\n", entityName))
buffer.WriteString(stContent)
buffer.WriteString("}")
return buffer.String()
}
// generateMessageFieldForPbEntity generates and returns the message definition for specified field.
func generateMessageFieldForPbEntity(index int, field *gdb.TableField, in cGenPbEntityInternalInput) []string {
var (
typeName string
comment string
jsonTagStr string
)
t, _ := gregex.ReplaceString(`\(.+\)`, "", field.Type)
t = gstr.Split(gstr.Trim(t), " ")[0]
t = gstr.ToLower(t)
switch t {
case "binary", "varbinary", "blob", "tinyblob", "mediumblob", "longblob":
typeName = "bytes"
case "bit", "int", "tinyint", "small_int", "smallint", "medium_int", "mediumint", "serial":
if gstr.ContainsI(field.Type, "unsigned") {
typeName = "uint32"
} else {
typeName = "int32"
}
case "int8", "big_int", "bigint", "bigserial":
if gstr.ContainsI(field.Type, "unsigned") {
typeName = "uint64"
} else {
typeName = "int64"
}
case "real":
typeName = "float"
case "float", "double", "decimal", "smallmoney":
typeName = "double"
case "bool":
typeName = "bool"
case "datetime", "timestamp", "date", "time":
typeName = "int64"
default:
// Auto detecting type.
switch {
case strings.Contains(t, "int"):
typeName = "int"
case strings.Contains(t, "text") || strings.Contains(t, "char"):
typeName = "string"
case strings.Contains(t, "float") || strings.Contains(t, "double"):
typeName = "double"
case strings.Contains(t, "bool"):
typeName = "bool"
case strings.Contains(t, "binary") || strings.Contains(t, "blob"):
typeName = "bytes"
case strings.Contains(t, "date") || strings.Contains(t, "time"):
typeName = "int64"
default:
typeName = "string"
}
}
comment = gstr.ReplaceByArray(field.Comment, g.SliceStr{
"\n", " ",
"\r", " ",
})
comment = gstr.Trim(comment)
comment = gstr.Replace(comment, `\n`, " ")
comment, _ = gregex.ReplaceString(`\s{2,}`, ` `, comment)
if jsonTagName := formatCase(field.Name, in.JsonCase); jsonTagName != "" {
jsonTagStr = fmt.Sprintf(`[(gogoproto.jsontag) = "%s"]`, jsonTagName)
// beautiful indent.
if index < 10 {
// 3 spaces
jsonTagStr = " " + jsonTagStr
} else if index < 100 {
// 2 spaces
jsonTagStr = " " + jsonTagStr
} else {
// 1 spaces
jsonTagStr = " " + jsonTagStr
}
}
return []string{
" #" + typeName,
" #" + formatCase(field.Name, in.NameCase),
" #= " + gconv.String(index) + jsonTagStr + ";",
" #" + fmt.Sprintf(`// %s`, comment),
}
}
func getTplPbEntityContent(tplEntityPath string) string {
if tplEntityPath != "" {
return gfile.GetContents(tplEntityPath)
}
return consts.TemplatePbEntityMessageContent
}
// formatCase call gstr.Case* function to convert the s to specified case.
func formatCase(str, caseStr string) string {
switch gstr.ToLower(caseStr) {
case gstr.ToLower("Camel"):
return gstr.CaseCamel(str)
case gstr.ToLower("CamelLower"):
return gstr.CaseCamelLower(str)
case gstr.ToLower("Kebab"):
return gstr.CaseKebab(str)
case gstr.ToLower("KebabScreaming"):
return gstr.CaseKebabScreaming(str)
case gstr.ToLower("Snake"):
return gstr.CaseSnake(str)
case gstr.ToLower("SnakeFirstUpper"):
return gstr.CaseSnakeFirstUpper(str)
case gstr.ToLower("SnakeScreaming"):
return gstr.CaseSnakeScreaming(str)
case "none":
return ""
}
return str
}
func sortFieldKeyForPbEntity(fieldMap map[string]*gdb.TableField) []string {
names := make(map[int]string)
for _, field := range fieldMap {
names[field.Index] = field.Name
}
var (
result = make([]string, len(names))
i = 0
j = 0
)
for {
if len(names) == 0 {
break
}
if val, ok := names[i]; ok {
result[j] = val
j++
delete(names, i)
}
i++
}
return result
}

View File

@ -1,9 +1,3 @@
// Copyright GoFrame gf Author(https://goframe.org). All Rights Reserved.
//
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with this file,
// You can obtain one at https://github.com/gogf/gf.
package cmd
import (

View File

@ -1,31 +1,22 @@
// Copyright GoFrame gf Author(https://goframe.org). All Rights Reserved.
//
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with this file,
// You can obtain one at https://github.com/gogf/gf.
package cmd
import (
"context"
"fmt"
"os"
"strings"
"github.com/gogf/gf/cmd/gf/v2/internal/utility/allyes"
"github.com/gogf/gf/cmd/gf/v2/internal/utility/mlog"
"github.com/gogf/gf/cmd/gf/v2/internal/utility/utils"
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/os/gcmd"
"github.com/gogf/gf/v2/os/gfile"
"github.com/gogf/gf/v2/os/gproc"
"github.com/gogf/gf/v2/os/gres"
"github.com/gogf/gf/v2/text/gstr"
"github.com/gogf/gf/v2/util/gtag"
"github.com/gogf/gf/cmd/gf/v2/internal/utility/allyes"
"github.com/gogf/gf/cmd/gf/v2/internal/utility/mlog"
)
var (
// Init .
Init = cInit{}
)
@ -34,24 +25,18 @@ type cInit struct {
}
const (
cInitRepoPrefix = `github.com/gogf/`
cInitMonoRepo = `template-mono`
cInitMonoRepoApp = `template-mono-app`
cInitSingleRepo = `template-single`
cInitBrief = `create and initialize an empty GoFrame project`
cInitEg = `
cInitRepoPrefix = `github.com/gogf/`
cInitMonoRepo = `template-mono`
cInitSingleRepo = `template-single`
cInitBrief = `create and initialize an empty GoFrame project`
cInitEg = `
gf init my-project
gf init my-mono-repo -m
gf init my-mono-repo -a
`
cInitNameBrief = `
name for the project. It will create a folder with NAME in current directory.
The NAME will also be the module name for the project.
`
// cInitGitDir the git directory
cInitGitDir = ".git"
// cInitGitignore the gitignore file
cInitGitignore = ".gitignore"
)
func init() {
@ -63,41 +48,32 @@ func init() {
}
type cInitInput struct {
g.Meta `name:"init"`
Name string `name:"NAME" arg:"true" v:"required" brief:"{cInitNameBrief}"`
Mono bool `name:"mono" short:"m" brief:"initialize a mono-repo instead a single-repo" orphan:"true"`
MonoApp bool `name:"monoApp" short:"a" brief:"initialize a mono-repo-app instead a single-repo" orphan:"true"`
Update bool `name:"update" short:"u" brief:"update to the latest goframe version" orphan:"true"`
Module string `name:"module" short:"g" brief:"custom go module"`
g.Meta `name:"init"`
Name string `name:"NAME" arg:"true" v:"required" brief:"{cInitNameBrief}"`
Mono bool `name:"mono" short:"m" brief:"initialize a mono-repo instead a single-repo" orphan:"true"`
Update bool `name:"update" short:"u" brief:"update to the latest goframe version" orphan:"true"`
}
type cInitOutput struct{}
func (c cInit) Index(ctx context.Context, in cInitInput) (out *cInitOutput, err error) {
var overwrote = false
if !gfile.IsEmpty(in.Name) && !allyes.Check() {
s := gcmd.Scanf(`the folder "%s" is not empty, files might be overwrote, continue? [y/n]: `, in.Name)
if strings.EqualFold(s, "n") {
return
}
overwrote = true
}
mlog.Print("initializing...")
// Create project folder and files.
var (
templateRepoName string
gitignoreFile = in.Name + "/" + cInitGitignore
)
if in.Mono {
templateRepoName = cInitMonoRepo
} else if in.MonoApp {
templateRepoName = cInitMonoRepoApp
} else {
templateRepoName = cInitSingleRepo
}
err = gres.Export(templateRepoName, in.Name, gres.ExportOption{
RemovePrefix: templateRepoName,
})
@ -105,45 +81,14 @@ func (c cInit) Index(ctx context.Context, in cInitInput) (out *cInitOutput, err
return
}
// build ignoreFiles from the .gitignore file
ignoreFiles := make([]string, 0, 10)
ignoreFiles = append(ignoreFiles, cInitGitDir)
// in.MonoApp is a mono-repo-app, it should ignore the .gitignore file
if overwrote && !in.MonoApp {
err = gfile.ReadLines(gitignoreFile, func(line string) error {
// Add only hidden files or directories
// If other directories are added, it may cause the entire directory to be ignored
// such as 'main' in the .gitignore file, but the path is ' D:\main\my-project '
if line != "" && strings.HasPrefix(line, ".") {
ignoreFiles = append(ignoreFiles, line)
}
return nil
})
// if not found the .gitignore file will skip os.ErrNotExist error
if err != nil && !os.IsNotExist(err) {
return
}
}
// Get template name and module name.
if in.Module == "" {
in.Module = gfile.Basename(gfile.RealPath(in.Name))
}
if in.MonoApp {
pwd := gfile.Pwd() + string(os.PathSeparator) + in.Name
in.Module = utils.GetImportPath(pwd)
}
// Replace template name to project name.
err = gfile.ReplaceDirFunc(func(path, content string) string {
for _, ignoreFile := range ignoreFiles {
if strings.Contains(path, ignoreFile) {
return content
}
}
return gstr.Replace(gfile.GetContents(path), cInitRepoPrefix+templateRepoName, in.Module)
}, in.Name, "*", true)
err = gfile.ReplaceDir(
cInitRepoPrefix+templateRepoName,
gfile.Basename(gfile.RealPath(in.Name)),
in.Name,
"*",
true,
)
if err != nil {
return
}

View File

@ -1,9 +1,3 @@
// Copyright GoFrame gf Author(https://goframe.org). All Rights Reserved.
//
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with this file,
// You can obtain one at https://github.com/gogf/gf.
package cmd
import (

View File

@ -1,9 +1,3 @@
// Copyright GoFrame gf Author(https://goframe.org). All Rights Reserved.
//
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with this file,
// You can obtain one at https://github.com/gogf/gf.
package cmd
import (
@ -63,7 +57,7 @@ func init() {
}
type cPackInput struct {
g.Meta `name:"pack" config:"gfcli.pack"`
g.Meta `name:"pack"`
Src string `name:"SRC" arg:"true" v:"required" brief:"{cPackSrcBrief}"`
Dst string `name:"DST" arg:"true" v:"required" brief:"{cPackDstBrief}"`
Name string `name:"name" short:"n" brief:"{cPackNameBrief}"`

View File

@ -1,18 +1,9 @@
// Copyright GoFrame gf Author(https://goframe.org). All Rights Reserved
//
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with this file,
// You can obtain one at https://github.com/gogf/gf.
package cmd
import (
"context"
"fmt"
"os"
"path/filepath"
"runtime"
"strings"
"github.com/gogf/gf/v2/container/gtype"
"github.com/gogf/gf/v2/frame/g"
@ -35,11 +26,10 @@ type cRun struct {
}
type cRunApp struct {
File string // Go run file name.
Path string // Directory storing built binary.
Options string // Extra "go run" options.
Args string // Custom arguments.
WatchPaths []string // Watch paths for live reload.
File string // Go run file name.
Path string // Directory storing built binary.
Options string // Extra "go run" options.
Args string // Custom arguments.
}
const (
@ -49,17 +39,15 @@ const (
gf run main.go
gf run main.go --args "server -p 8080"
gf run main.go -mod=vendor
gf run main.go -w "manifest/config/*.yaml"
`
cRunDc = `
The "run" command is used for running go codes with hot-compiled-like feature,
which compiles and runs the go codes asynchronously when codes change.
`
cRunFileBrief = `building file path.`
cRunPathBrief = `output directory path for built binary file. it's "./" in default`
cRunExtraBrief = `the same options as "go run"/"go build" except some options as follows defined`
cRunArgsBrief = `custom arguments for your process`
cRunWatchPathsBrief = `watch additional paths for live reload, separated by ",". i.e. "manifest/config/*.yaml"`
cRunFileBrief = `building file path.`
cRunPathBrief = `output directory path for built binary file. it's "manifest/output" in default`
cRunExtraBrief = `the same options as "go run"/"go build" except some options as follows defined`
cRunArgsBrief = `custom arguments for your process`
)
var (
@ -68,104 +56,77 @@ var (
func init() {
gtag.Sets(g.MapStrStr{
`cRunUsage`: cRunUsage,
`cRunBrief`: cRunBrief,
`cRunEg`: cRunEg,
`cRunDc`: cRunDc,
`cRunFileBrief`: cRunFileBrief,
`cRunPathBrief`: cRunPathBrief,
`cRunExtraBrief`: cRunExtraBrief,
`cRunArgsBrief`: cRunArgsBrief,
`cRunWatchPathsBrief`: cRunWatchPathsBrief,
`cRunUsage`: cRunUsage,
`cRunBrief`: cRunBrief,
`cRunEg`: cRunEg,
`cRunDc`: cRunDc,
`cRunFileBrief`: cRunFileBrief,
`cRunPathBrief`: cRunPathBrief,
`cRunExtraBrief`: cRunExtraBrief,
`cRunArgsBrief`: cRunArgsBrief,
})
}
type (
cRunInput struct {
g.Meta `name:"run" config:"gfcli.run"`
File string `name:"FILE" arg:"true" brief:"{cRunFileBrief}" v:"required"`
Path string `name:"path" short:"p" brief:"{cRunPathBrief}" d:"./"`
Extra string `name:"extra" short:"e" brief:"{cRunExtraBrief}"`
Args string `name:"args" short:"a" brief:"{cRunArgsBrief}"`
WatchPaths []string `name:"watchPaths" short:"w" brief:"{cRunWatchPathsBrief}"`
g.Meta `name:"run"`
File string `name:"FILE" arg:"true" brief:"{cRunFileBrief}" v:"required"`
Path string `name:"path" short:"p" brief:"{cRunPathBrief}" d:"./"`
Extra string `name:"extra" short:"e" brief:"{cRunExtraBrief}"`
Args string `name:"args" short:"a" brief:"{cRunArgsBrief}"`
}
cRunOutput struct{}
)
func (c cRun) Index(ctx context.Context, in cRunInput) (out *cRunOutput, err error) {
if !gfile.Exists(in.File) {
mlog.Fatalf(`given file "%s" not found`, in.File)
}
if !gfile.IsFile(in.File) {
mlog.Fatalf(`given "%s" is not a file`, in.File)
}
// Necessary check.
if gproc.SearchBinary("go") == "" {
mlog.Fatalf(`command "go" not found in your environment, please install golang first to proceed this command`)
}
if len(in.WatchPaths) == 1 {
in.WatchPaths = strings.Split(in.WatchPaths[0], ",")
mlog.Printf("watchPaths: %v", in.WatchPaths)
}
app := &cRunApp{
File: in.File,
Path: filepath.FromSlash(in.Path),
Options: in.Extra,
Args: in.Args,
WatchPaths: in.WatchPaths,
File: in.File,
Path: in.Path,
Options: in.Extra,
Args: in.Args,
}
dirty := gtype.NewBool()
var outputPath = app.genOutputPath()
callbackFunc := func(event *gfsnotify.Event) {
_, err = gfsnotify.Add(gfile.RealPath("."), func(event *gfsnotify.Event) {
if gfile.ExtName(event.Path) != "go" {
return
}
// Variable `dirty` is used for running the changes only one in one second.
if !dirty.Cas(false, true) {
return
}
// With some delay in case of multiple code changes in very short interval.
gtimer.SetTimeout(ctx, 1500*gtime.MS, func(ctx context.Context) {
defer dirty.Set(false)
mlog.Printf(`watched file changes: %s`, event.String())
app.Run(ctx, outputPath)
mlog.Printf(`go file changes: %s`, event.String())
app.Run(ctx)
})
}
if len(app.WatchPaths) > 0 {
for _, path := range app.WatchPaths {
_, err = gfsnotify.Add(gfile.RealPath(path), callbackFunc)
if err != nil {
mlog.Fatal(err)
}
}
} else {
_, err = gfsnotify.Add(gfile.RealPath("."), callbackFunc)
if err != nil {
mlog.Fatal(err)
}
}
go app.Run(ctx, outputPath)
gproc.AddSigHandlerShutdown(func(sig os.Signal) {
app.End(ctx, sig, outputPath)
os.Exit(0)
})
gproc.Listen()
if err != nil {
mlog.Fatal(err)
}
go app.Run(ctx)
select {}
}
func (app *cRunApp) Run(ctx context.Context, outputPath string) {
func (app *cRunApp) Run(ctx context.Context) {
// Rebuild and run the codes.
renamePath := ""
mlog.Printf("build: %s", app.File)
outputPath := gfile.Join(app.Path, gfile.Name(app.File))
if runtime.GOOS == "windows" {
outputPath += ".exe"
if gfile.Exists(outputPath) {
renamePath = outputPath + "~"
if err := gfile.Rename(outputPath, renamePath); err != nil {
mlog.Print(err)
}
}
}
// In case of `pipe: too many open files` error.
// Build the app.
buildCommand := fmt.Sprintf(
@ -184,6 +145,7 @@ func (app *cRunApp) Run(ctx context.Context, outputPath string) {
if process != nil {
if err := process.Kill(); err != nil {
mlog.Debugf("kill process error: %s", err.Error())
//return
}
}
// Run the binary file.
@ -192,7 +154,7 @@ func (app *cRunApp) Run(ctx context.Context, outputPath string) {
if runtime.GOOS == "windows" {
// Special handling for windows platform.
// DO NOT USE "cmd /c" command.
process = gproc.NewProcess(outputPath, strings.Fields(app.Args))
process = gproc.NewProcess(runCommand, nil)
} else {
process = gproc.NewProcessCmd(runCommand, nil)
}
@ -202,52 +164,3 @@ func (app *cRunApp) Run(ctx context.Context, outputPath string) {
mlog.Printf("build running pid: %d", pid)
}
}
func (app *cRunApp) End(ctx context.Context, sig os.Signal, outputPath string) {
// Delete the binary file.
// firstly, kill the process.
if process != nil {
if err := process.Kill(); err != nil {
mlog.Debugf("kill process error: %s", err.Error())
}
}
if err := gfile.RemoveFile(outputPath); err != nil {
mlog.Printf("delete binary file error: %s", err.Error())
} else {
mlog.Printf("deleted binary file: %s", outputPath)
}
}
func (app *cRunApp) genOutputPath() (outputPath string) {
var renamePath string
outputPath = gfile.Join(app.Path, gfile.Name(app.File))
if runtime.GOOS == "windows" {
outputPath += ".exe"
if gfile.Exists(outputPath) {
renamePath = outputPath + "~"
if err := gfile.Rename(outputPath, renamePath); err != nil {
mlog.Print(err)
}
}
}
return filepath.FromSlash(outputPath)
}
func matchWatchPaths(watchPaths []string, eventPath string) bool {
for _, path := range watchPaths {
absPath, err := filepath.Abs(path)
if err != nil {
mlog.Printf("match watchPath '%s' error: %s", path, err.Error())
continue
}
matched, err := filepath.Match(absPath, eventPath)
if err != nil {
mlog.Printf("match watchPath '%s' error: %s", path, err.Error())
continue
}
if matched {
return true
}
}
return false
}

View File

@ -1,9 +1,3 @@
// Copyright GoFrame gf Author(https://goframe.org). All Rights Reserved.
//
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with this file,
// You can obtain one at https://github.com/gogf/gf.
package cmd
import (
@ -39,7 +33,7 @@ like json/xml/yaml/toml/ini.
cTplParseEg = `
gf tpl parse -p ./template -v values.json -r
gf tpl parse -p ./template -v values.json -n *.tpl -r
gf tpl parse -p ./template -v values.json -d '${{,}}' -r
gf tpl parse -p ./template -v values.json -d '${,}}' -r
gf tpl parse -p ./template -v values.json -o ./template.parsed
`
cTplSupportValuesFilePattern = `*.json,*.xml,*.yaml,*.yml,*.toml,*.ini`
@ -47,7 +41,7 @@ gf tpl parse -p ./template -v values.json -o ./template.parsed
type (
cTplParseInput struct {
g.Meta `name:"parse" config:"gfcli.tpl.parse" brief:"{cTplParseBrief}" eg:"{cTplParseEg}"`
g.Meta `name:"parse" brief:"{cTplParseBrief}" eg:"{cTplParseEg}"`
Path string `name:"path" short:"p" brief:"template file or folder path" v:"required"`
Pattern string `name:"pattern" short:"n" brief:"template file pattern when path is a folder, default is:*" d:"*"`
Recursive bool `name:"recursive" short:"c" brief:"recursively parsing files if path is folder, default is:true" d:"true"`
@ -69,7 +63,7 @@ func init() {
}
func (c *cTpl) Parse(ctx context.Context, in cTplParseInput) (out *cTplParseOutput, err error) {
if in.Output == "" && !in.Replace {
if in.Output == "" && in.Replace == false {
return nil, gerror.New(`parameter output and replace should not be both empty`)
}
delimiters := gstr.SplitAndTrim(in.Delimiters, ",")

View File

@ -1,21 +1,10 @@
// Copyright GoFrame gf Author(https://goframe.org). All Rights Reserved.
//
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with this file,
// You can obtain one at https://github.com/gogf/gf.
package cmd
import (
"context"
"fmt"
"runtime"
"github.com/gogf/selfupdate"
"github.com/gogf/gf/cmd/gf/v2/internal/utility/mlog"
"github.com/gogf/gf/cmd/gf/v2/internal/utility/utils"
"github.com/gogf/gf/v2/container/gset"
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/os/gfile"
"github.com/gogf/gf/v2/os/gproc"
@ -37,7 +26,7 @@ const (
gf up
gf up -a
gf up -c
gf up -cf
gf up -f -c
`
)
@ -48,10 +37,10 @@ func init() {
}
type cUpInput struct {
g.Meta `name:"up" config:"gfcli.up"`
g.Meta `name:"up" config:"gfcli.up"`
All bool `name:"all" short:"a" brief:"upgrade both version and cli, auto fix codes" orphan:"true"`
Cli bool `name:"cli" short:"c" brief:"also upgrade CLI tool" orphan:"true"`
Fix bool `name:"fix" short:"f" brief:"auto fix codes(it only make sense if cli is to be upgraded)" orphan:"true"`
Fix bool `name:"fix" short:"f" brief:"auto fix codes" orphan:"true"`
Cli bool `name:"cli" short:"c" brief:"also upgrade CLI tool (not supported yet)" orphan:"true"`
}
type cUpOutput struct{}
@ -59,74 +48,42 @@ type cUpOutput struct{}
func (c cUp) Index(ctx context.Context, in cUpInput) (out *cUpOutput, err error) {
defer func() {
if err == nil {
mlog.Print()
mlog.Print(`👏congratulations! you've upgraded to the latest version of GoFrame! enjoy it!👏`)
mlog.Print()
mlog.Print(`done!`)
}
}()
var doUpgradeVersionOut *doUpgradeVersionOutput
if in.All {
in.Cli = true
in.Fix = true
}
if doUpgradeVersionOut, err = c.doUpgradeVersion(ctx, in); err != nil {
if err = c.doUpgradeVersion(ctx, in); err != nil {
return nil, err
}
if in.Cli {
if err = c.doUpgradeCLI(ctx); err != nil {
return nil, err
}
}
if in.Cli && in.Fix {
if doUpgradeVersionOut != nil && len(doUpgradeVersionOut.Items) > 0 {
upgradedPathSet := gset.NewStrSet()
for _, item := range doUpgradeVersionOut.Items {
if !upgradedPathSet.AddIfNotExist(item.DirPath) {
continue
}
if err = c.doAutoFixing(ctx, item.DirPath, item.Version); err != nil {
return nil, err
}
}
}
}
//if in.Cli {
// if err = c.doUpgradeCLI(ctx); err != nil {
// return nil, err
// }
//}
return
}
type doUpgradeVersionOutput struct {
Items []doUpgradeVersionOutputItem
}
type doUpgradeVersionOutputItem struct {
DirPath string
Version string
}
func (c cUp) doUpgradeVersion(ctx context.Context, in cUpInput) (out *doUpgradeVersionOutput, err error) {
func (c cUp) doUpgradeVersion(ctx context.Context, in cUpInput) (err error) {
mlog.Print(`start upgrading version...`)
out = &doUpgradeVersionOutput{
Items: make([]doUpgradeVersionOutputItem, 0),
}
type Package struct {
Name string
Version string
}
var (
temp string
dirPath = gfile.Pwd()
goModPath = gfile.Join(dirPath, "go.mod")
dir = gfile.Pwd()
temp string
path = gfile.Join(dir, "go.mod")
)
// It recursively upgrades the go.mod from sub folder to its parent folders.
for {
if gfile.Exists(goModPath) {
if gfile.Exists(path) {
var packages []Package
err = gfile.ReadLines(goModPath, func(line string) error {
line = gstr.Trim(line)
line = gstr.TrimLeftStr(line, "require ")
err = gfile.ReadLines(path, func(line string) error {
line = gstr.Trim(line)
if gstr.HasPrefix(line, gfPackage) {
array := gstr.SplitAndTrim(line, " ")
@ -142,79 +99,38 @@ func (c cUp) doUpgradeVersion(ctx context.Context, in cUpInput) (out *doUpgradeV
}
for _, pkg := range packages {
mlog.Printf(`upgrading "%s" from "%s" to "latest"`, pkg.Name, pkg.Version)
mlog.Printf(`running command: go get %s@latest`, pkg.Name)
// go get @latest
command := fmt.Sprintf(`cd %s && go get %s@latest`, dirPath, pkg.Name)
command := fmt.Sprintf(`go get -u %s@latest`, pkg.Name)
if err = gproc.ShellRun(ctx, command); err != nil {
return
}
// go mod tidy
if err = utils.GoModTidy(ctx, dirPath); err != nil {
return nil, err
mlog.Print()
}
if in.Fix {
if err = c.doAutoFixing(ctx, dir); err != nil {
return err
}
out.Items = append(out.Items, doUpgradeVersionOutputItem{
DirPath: dirPath,
Version: pkg.Version,
})
mlog.Print()
}
return
}
temp = gfile.Dir(dirPath)
if temp == "" || temp == dirPath {
temp = gfile.Dir(dir)
if temp == "" || temp == dir {
return
}
dirPath = temp
goModPath = gfile.Join(dirPath, "go.mod")
dir = temp
path = gfile.Join(dir, "go.mod")
}
}
// doUpgradeCLI downloads the new version binary with process.
func (c cUp) doUpgradeCLI(ctx context.Context) (err error) {
mlog.Print(`start upgrading cli...`)
var (
downloadUrl = fmt.Sprintf(
`https://github.com/gogf/gf/releases/latest/download/gf_%s_%s`,
runtime.GOOS, runtime.GOARCH,
)
localSaveFilePath = gfile.SelfPath() + "~"
)
if runtime.GOOS == "windows" {
downloadUrl += ".exe"
}
mlog.Printf(`start downloading "%s" to "%s", it may take some time`, downloadUrl, localSaveFilePath)
err = utils.HTTPDownloadFileWithPercent(downloadUrl, localSaveFilePath)
if err != nil {
return err
}
defer func() {
mlog.Printf(`new version cli binary is successfully installed to "%s"`, gfile.SelfPath())
mlog.Printf(`remove temporary buffer file "%s"`, localSaveFilePath)
_ = gfile.RemoveFile(localSaveFilePath)
}()
// It fails if file not exist or its size is less than 1MB.
if !gfile.Exists(localSaveFilePath) || gfile.Size(localSaveFilePath) < 1024*1024 {
mlog.Fatalf(`download "%s" to "%s" failed`, downloadUrl, localSaveFilePath)
}
newFile, err := gfile.Open(localSaveFilePath)
if err != nil {
return err
}
// selfupdate
err = selfupdate.Apply(newFile, selfupdate.Options{})
if err != nil {
return err
}
return
}
func (c cUp) doAutoFixing(ctx context.Context, dirPath string, version string) (err error) {
mlog.Printf(`auto fixing directory path "%s" from version "%s" ...`, dirPath, version)
command := fmt.Sprintf(`gf fix -p %s`, dirPath)
_ = gproc.ShellRun(ctx, command)
func (c cUp) doAutoFixing(ctx context.Context, dirPath string) (err error) {
mlog.Printf(`auto fixing path "%s"...`, dirPath)
err = cFix{}.doFix(cFixInput{
Path: dirPath,
})
return
}

View File

@ -1,25 +1,14 @@
// Copyright GoFrame gf Author(https://goframe.org). All Rights Reserved.
//
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with this file,
// You can obtain one at https://github.com/gogf/gf.
package cmd
import (
"bytes"
"context"
"fmt"
"runtime"
"strings"
"time"
"github.com/gogf/gf/v2"
"github.com/gogf/gf/v2/errors/gerror"
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/os/gbuild"
"github.com/gogf/gf/v2/os/gfile"
"github.com/gogf/gf/v2/os/gproc"
"github.com/gogf/gf/v2/text/gregex"
"github.com/gogf/gf/v2/text/gstr"
@ -30,10 +19,6 @@ var (
Version = cVersion{}
)
const (
defaultIndent = "{{indent}}"
)
type cVersion struct {
g.Meta `name:"version" brief:"show version information of current binary"`
}
@ -45,98 +30,40 @@ type cVersionInput struct {
type cVersionOutput struct{}
func (c cVersion) Index(ctx context.Context, in cVersionInput) (*cVersionOutput, error) {
detailBuffer := &detailBuffer{}
detailBuffer.WriteString(fmt.Sprintf("%s", gf.VERSION))
detailBuffer.appendLine(0, "Welcome to GoFrame!")
detailBuffer.appendLine(0, "Env Detail:")
goVersion, ok := getGoVersion()
if ok {
detailBuffer.appendLine(1, fmt.Sprintf("Go Version: %s", goVersion))
detailBuffer.appendLine(1, fmt.Sprintf("GF Version(go.mod): %s", getGoFrameVersion(2)))
} else {
v, err := c.getGFVersionOfCurrentProject()
if err == nil {
detailBuffer.appendLine(1, fmt.Sprintf("GF Version(go.mod): %s", v))
} else {
detailBuffer.appendLine(1, fmt.Sprintf("GF Version(go.mod): %s", err.Error()))
}
}
detailBuffer.appendLine(0, "CLI Detail:")
detailBuffer.appendLine(1, fmt.Sprintf("Installed At: %s", gfile.SelfPath()))
info := gbuild.Info()
if info.GoFrame == "" {
detailBuffer.appendLine(1, fmt.Sprintf("Built Go Version: %s", runtime.Version()))
detailBuffer.appendLine(1, fmt.Sprintf("Built GF Version: %s", gf.VERSION))
if info.Git == "" {
info.Git = "none"
}
mlog.Printf(`GoFrame CLI Tool %s, https://goframe.org`, gf.VERSION)
gfVersion, err := c.getGFVersionOfCurrentProject()
if err != nil {
gfVersion = err.Error()
} else {
if info.Git == "" {
info.Git = "none"
}
detailBuffer.appendLine(1, fmt.Sprintf("Built Go Version: %s", info.Golang))
detailBuffer.appendLine(1, fmt.Sprintf("Built GF Version: %s", info.GoFrame))
detailBuffer.appendLine(1, fmt.Sprintf("Git Commit: %s", info.Git))
detailBuffer.appendLine(1, fmt.Sprintf("Built Time: %s", info.Time))
gfVersion = gfVersion + " in current go.mod"
}
mlog.Printf(`GoFrame Version: %s`, gfVersion)
mlog.Printf(`CLI Installed At: %s`, gfile.SelfPath())
if info.GoFrame == "" {
mlog.Print(`Current is a custom installed version, no installation information.`)
return nil, nil
}
detailBuffer.appendLine(0, "Others Detail:")
detailBuffer.appendLine(1, "Docs: https://goframe.org")
detailBuffer.appendLine(1, fmt.Sprintf("Now : %s", time.Now().Format(time.RFC3339)))
mlog.Print(detailBuffer.replaceAllIndent(" "))
mlog.Print(gstr.Trim(fmt.Sprintf(`
CLI Built Detail:
Go Version: %s
GF Version: %s
Git Commit: %s
Build Time: %s
`, info.Golang, info.GoFrame, info.Git, info.Time)))
return nil, nil
}
// detailBuffer is a buffer for detail information.
type detailBuffer struct {
bytes.Buffer
}
// appendLine appends a line to the buffer with given indent level.
func (d *detailBuffer) appendLine(indentLevel int, line string) {
d.WriteString(fmt.Sprintf("\n%s%s", strings.Repeat(defaultIndent, indentLevel), line))
}
// replaceAllIndent replaces the tab with given indent string and prints the buffer content.
func (d *detailBuffer) replaceAllIndent(indentStr string) string {
return strings.ReplaceAll(d.String(), defaultIndent, indentStr)
}
// getGoFrameVersion returns the goframe version of current project using.
func getGoFrameVersion(indentLevel int) (gfVersion string) {
pkgInfo, err := gproc.ShellExec(context.Background(), `go list -f "{{if (not .Main)}}{{.Path}}@{{.Version}}{{end}}" -m all`)
if err != nil {
return "cannot find go.mod"
}
pkgList := gstr.Split(pkgInfo, "\n")
for _, v := range pkgList {
if strings.HasPrefix(v, "github.com/gogf/gf") {
gfVersion += fmt.Sprintf("\n%s%s", strings.Repeat(defaultIndent, indentLevel), v)
}
}
return
}
// getGoVersion returns the go version
func getGoVersion() (goVersion string, ok bool) {
goVersion, err := gproc.ShellExec(context.Background(), "go version")
if err != nil {
return "", false
}
goVersion = gstr.TrimLeftStr(goVersion, "go version ")
goVersion = gstr.TrimRightStr(goVersion, "\n")
return goVersion, true
}
// getGFVersionOfCurrentProject checks and returns the GoFrame version current project using.
func (c cVersion) getGFVersionOfCurrentProject() (string, error) {
goModPath := gfile.Join(gfile.Pwd(), "go.mod")
if gfile.Exists(goModPath) {
lines := gstr.SplitAndTrim(gfile.GetContents(goModPath), "\n")
for _, line := range lines {
line = gstr.Trim(line)
line = gstr.TrimLeftStr(line, "require ")
line = gstr.Trim(line)
// Version 1.
match, err := gregex.MatchString(`^github\.com/gogf/gf\s+(.+)$`, line)

View File

@ -1,38 +0,0 @@
// Copyright GoFrame gf Author(https://goframe.org). All Rights Reserved.
//
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with this file,
// You can obtain one at https://github.com/gogf/gf.
package cmd
import (
"context"
"fmt"
"github.com/gogf/gf/v2/database/gdb"
"github.com/gogf/gf/v2/test/gtest"
)
var (
ctx = context.Background()
testDB gdb.DB
link = "mysql:root:12345678@tcp(127.0.0.1:3306)/test?loc=Local&parseTime=true"
)
func init() {
var err error
testDB, err = gdb.New(gdb.ConfigNode{
Link: link,
})
if err != nil {
panic(err)
}
}
func dropTableWithDb(db gdb.DB, table string) {
dropTableStmt := fmt.Sprintf("DROP TABLE IF EXISTS `%s`", table)
if _, err := db.Exec(ctx, dropTableStmt); err != nil {
gtest.Error(err)
}
}

View File

@ -1,151 +0,0 @@
// Copyright GoFrame gf Author(https://goframe.org). All Rights Reserved.
//
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with this file,
// You can obtain one at https://github.com/gogf/gf.
package cmd
import (
"testing"
"github.com/gogf/gf/v2/os/gfile"
"github.com/gogf/gf/v2/os/gproc"
"github.com/gogf/gf/v2/test/gtest"
"github.com/gogf/gf/v2/text/gstr"
)
func Test_Build_Single(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
var (
buildPath = gtest.DataPath(`build`, `single`)
pwd = gfile.Pwd()
binaryName = `t.test`
binaryPath = gtest.DataPath(`build`, `single`, binaryName)
f = cBuild{}
)
defer gfile.Chdir(pwd)
defer gfile.Remove(binaryPath)
err := gfile.Chdir(buildPath)
t.AssertNil(err)
t.Assert(gfile.Exists(binaryPath), false)
_, err = f.Index(ctx, cBuildInput{
File: cBuildDefaultFile,
Name: binaryName,
})
t.AssertNil(err)
t.Assert(gfile.Exists(binaryPath), true)
})
}
func Test_Build_Single_Output(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
var (
buildPath = gtest.DataPath(`build`, `single`)
pwd = gfile.Pwd()
binaryName = `tt`
binaryDirPath = gtest.DataPath(`build`, `single`, `tt`)
binaryPath = gtest.DataPath(`build`, `single`, `tt`, binaryName)
f = cBuild{}
)
defer gfile.Chdir(pwd)
defer gfile.Remove(binaryDirPath)
err := gfile.Chdir(buildPath)
t.AssertNil(err)
t.Assert(gfile.Exists(binaryPath), false)
_, err = f.Index(ctx, cBuildInput{
Output: "./tt/tt",
})
t.AssertNil(err)
t.Assert(gfile.Exists(binaryPath), true)
})
}
func Test_Build_Single_Path(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
var (
buildPath = gtest.DataPath(`build`, `single`)
pwd = gfile.Pwd()
dirName = "ttt"
binaryName = `main`
binaryDirPath = gtest.DataPath(`build`, `single`, dirName)
binaryPath = gtest.DataPath(`build`, `single`, dirName, binaryName)
f = cBuild{}
)
defer gfile.Chdir(pwd)
defer gfile.Remove(binaryDirPath)
err := gfile.Chdir(buildPath)
t.AssertNil(err)
t.Assert(gfile.Exists(binaryPath), false)
_, err = f.Index(ctx, cBuildInput{
Path: "ttt",
})
t.AssertNil(err)
t.Assert(gfile.Exists(binaryPath), true)
})
}
func Test_Build_Single_VarMap(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
var (
buildPath = gtest.DataPath(`build`, `varmap`)
pwd = gfile.Pwd()
binaryName = `main`
binaryPath = gtest.DataPath(`build`, `varmap`, binaryName)
f = cBuild{}
)
defer gfile.Chdir(pwd)
defer gfile.Remove(binaryPath)
err := gfile.Chdir(buildPath)
t.AssertNil(err)
t.Assert(gfile.Exists(binaryPath), false)
_, err = f.Index(ctx, cBuildInput{
VarMap: map[string]interface{}{
"a": "1",
"b": "2",
},
})
t.AssertNil(err)
t.Assert(gfile.Exists(binaryPath), true)
result, err := gproc.ShellExec(ctx, binaryPath)
t.AssertNil(err)
t.Assert(gstr.Contains(result, `a: 1`), true)
t.Assert(gstr.Contains(result, `b: 2`), true)
})
}
func Test_Build_Multiple(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
var (
buildPath = gtest.DataPath(`build`, `multiple`)
pwd = gfile.Pwd()
binaryDirPath = gtest.DataPath(`build`, `multiple`, `temp`)
binaryPathLinux = gtest.DataPath(`build`, `multiple`, `temp`, `v1.1`, `linux_amd64`, `ttt`)
binaryPathWindows = gtest.DataPath(`build`, `multiple`, `temp`, `v1.1`, `windows_amd64`, `ttt.exe`)
f = cBuild{}
)
defer gfile.Chdir(pwd)
defer gfile.Remove(binaryDirPath)
err := gfile.Chdir(buildPath)
t.AssertNil(err)
t.Assert(gfile.Exists(binaryPathLinux), false)
t.Assert(gfile.Exists(binaryPathWindows), false)
_, err = f.Index(ctx, cBuildInput{
File: "multiple.go",
Name: "ttt",
Version: "v1.1",
Arch: "amd64",
System: "linux, windows",
Path: "temp",
})
t.AssertNil(err)
t.Assert(gfile.Exists(binaryPathLinux), true)
t.Assert(gfile.Exists(binaryPathWindows), true)
})
}

View File

@ -1,24 +0,0 @@
// Copyright GoFrame gf Author(https://goframe.org). All Rights Reserved.
//
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with this file,
// You can obtain one at https://github.com/gogf/gf.
package cmd
import (
"testing"
"github.com/gogf/gf/v2/test/gtest"
)
func Test_Fix_doFixV25Content(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
var (
content = gtest.DataContent(`fix`, `fix25_content.go`)
f = cFix{}
)
_, err := f.doFixV25Content(content)
t.AssertNil(err)
})
}

View File

@ -1,309 +0,0 @@
// Copyright GoFrame gf Author(https://goframe.org). All Rights Reserved.
//
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with this file,
// You can obtain one at https://github.com/gogf/gf.
package cmd
import (
"path/filepath"
"testing"
"github.com/gogf/gf/cmd/gf/v2/internal/cmd/genctrl"
"github.com/gogf/gf/v2/os/gfile"
"github.com/gogf/gf/v2/test/gtest"
"github.com/gogf/gf/v2/util/guid"
"github.com/gogf/gf/v2/util/gutil"
)
func Test_Gen_Ctrl_Default(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
var (
path = gfile.Temp(guid.S())
apiFolder = gtest.DataPath("genctrl", "api")
in = genctrl.CGenCtrlInput{
SrcFolder: apiFolder,
DstFolder: path,
WatchFile: "",
SdkPath: "",
SdkStdVersion: false,
SdkNoV1: false,
Clear: false,
Merge: false,
}
)
err := gutil.FillStructWithDefault(&in)
t.AssertNil(err)
err = gfile.Mkdir(path)
t.AssertNil(err)
defer gfile.Remove(path)
_, err = genctrl.CGenCtrl{}.Ctrl(ctx, in)
t.AssertNil(err)
// apiInterface file
var (
genApi = apiFolder + filepath.FromSlash("/article/article.go")
genApiExpect = apiFolder + filepath.FromSlash("/article/article_expect.go")
)
defer gfile.Remove(genApi)
t.Assert(gfile.GetContents(genApi), gfile.GetContents(genApiExpect))
// files
files, err := gfile.ScanDir(path, "*.go", true)
t.AssertNil(err)
t.Assert(files, []string{
path + filepath.FromSlash("/article/article.go"),
path + filepath.FromSlash("/article/article_new.go"),
path + filepath.FromSlash("/article/article_v1_create.go"),
path + filepath.FromSlash("/article/article_v1_get_list.go"),
path + filepath.FromSlash("/article/article_v1_get_one.go"),
path + filepath.FromSlash("/article/article_v1_update.go"),
path + filepath.FromSlash("/article/article_v2_create.go"),
path + filepath.FromSlash("/article/article_v2_update.go"),
})
// content
testPath := gtest.DataPath("genctrl", "controller")
expectFiles := []string{
testPath + filepath.FromSlash("/article/article.go"),
testPath + filepath.FromSlash("/article/article_new.go"),
testPath + filepath.FromSlash("/article/article_v1_create.go"),
testPath + filepath.FromSlash("/article/article_v1_get_list.go"),
testPath + filepath.FromSlash("/article/article_v1_get_one.go"),
testPath + filepath.FromSlash("/article/article_v1_update.go"),
testPath + filepath.FromSlash("/article/article_v2_create.go"),
testPath + filepath.FromSlash("/article/article_v2_update.go"),
}
for i := range files {
t.Assert(gfile.GetContents(files[i]), gfile.GetContents(expectFiles[i]))
}
})
}
func expectFilesContent(t *gtest.T, paths []string, expectPaths []string) {
for i, expectFile := range expectPaths {
val := gfile.GetContents(paths[i])
expect := gfile.GetContents(expectFile)
t.Assert(val, expect)
}
}
// gf gen ctrl -m
// In the same module, different API files are added
func Test_Gen_Ctrl_UseMerge_AddNewFile(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
var (
ctrlPath = gfile.Temp(guid.S())
//ctrlPath = gtest.DataPath("issue", "3460", "controller")
apiFolder = gtest.DataPath("genctrl-merge", "add_new_file", "api")
in = genctrl.CGenCtrlInput{
SrcFolder: apiFolder,
DstFolder: ctrlPath,
Merge: true,
}
)
const testNewApiFile = `
package v1
import "github.com/gogf/gf/v2/frame/g"
type DictTypeAddReq struct {
g.Meta
}
type DictTypeAddRes struct {
}
`
err := gfile.Mkdir(ctrlPath)
t.AssertNil(err)
defer gfile.Remove(ctrlPath)
_, err = genctrl.CGenCtrl{}.Ctrl(ctx, in)
t.AssertNil(err)
var (
genApi = filepath.Join(apiFolder, "/dict/dict.go")
genApiExpect = filepath.Join(apiFolder, "/dict/dict_expect.go")
)
defer gfile.Remove(genApi)
t.Assert(gfile.GetContents(genApi), gfile.GetContents(genApiExpect))
genCtrlFiles, err := gfile.ScanDir(ctrlPath, "*.go", true)
t.AssertNil(err)
t.Assert(genCtrlFiles, []string{
filepath.Join(ctrlPath, "/dict/dict.go"),
filepath.Join(ctrlPath, "/dict/dict_new.go"),
filepath.Join(ctrlPath, "/dict/dict_v1_dict_type.go"),
})
expectCtrlPath := gtest.DataPath("genctrl-merge", "add_new_file", "controller")
expectFiles := []string{
filepath.Join(expectCtrlPath, "/dict/dict.go"),
filepath.Join(expectCtrlPath, "/dict/dict_new.go"),
filepath.Join(expectCtrlPath, "/dict/dict_v1_dict_type.go"),
}
// Line Feed maybe \r\n or \n
expectFilesContent(t, genCtrlFiles, expectFiles)
// Add a new API file
newApiFilePath := filepath.Join(apiFolder, "/dict/v1/test_new.go")
err = gfile.PutContents(newApiFilePath, testNewApiFile)
t.AssertNil(err)
defer gfile.Remove(newApiFilePath)
// Then execute the command
_, err = genctrl.CGenCtrl{}.Ctrl(ctx, in)
t.AssertNil(err)
genApi = filepath.Join(apiFolder, "/dict.go")
genApiExpect = filepath.Join(apiFolder, "/dict_add_new_ctrl_expect.gotest")
t.Assert(gfile.GetContents(genApi), gfile.GetContents(genApiExpect))
genCtrlFiles = append(genCtrlFiles, filepath.Join(ctrlPath, "/dict/dict_v1_test_new.go"))
// Use the gotest suffix, otherwise the IDE will delete the import
expectFiles = append(expectFiles, filepath.Join(expectCtrlPath, "/dict/dict_v1_test_new.gotest"))
// Line Feed maybe \r\n or \n
expectFilesContent(t, genCtrlFiles, expectFiles)
})
}
// gf gen ctrl -m
// In the same module, Add the same file to the API
func Test_Gen_Ctrl_UseMerge_AddNewCtrl(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
var (
ctrlPath = gfile.Temp(guid.S())
//ctrlPath = gtest.DataPath("issue", "3460", "controller")
apiFolder = gtest.DataPath("genctrl-merge", "add_new_ctrl", "api")
in = genctrl.CGenCtrlInput{
SrcFolder: apiFolder,
DstFolder: ctrlPath,
Merge: true,
}
)
err := gfile.Mkdir(ctrlPath)
t.AssertNil(err)
defer gfile.Remove(ctrlPath)
_, err = genctrl.CGenCtrl{}.Ctrl(ctx, in)
t.AssertNil(err)
var (
genApi = filepath.Join(apiFolder, "/dict/dict.go")
genApiExpect = filepath.Join(apiFolder, "/dict/dict_expect.go")
)
defer gfile.Remove(genApi)
t.Assert(gfile.GetContents(genApi), gfile.GetContents(genApiExpect))
genCtrlFiles, err := gfile.ScanDir(ctrlPath, "*.go", true)
t.AssertNil(err)
t.Assert(genCtrlFiles, []string{
filepath.Join(ctrlPath, "/dict/dict.go"),
filepath.Join(ctrlPath, "/dict/dict_new.go"),
filepath.Join(ctrlPath, "/dict/dict_v1_dict_type.go"),
})
expectCtrlPath := gtest.DataPath("genctrl-merge", "add_new_ctrl", "controller")
expectFiles := []string{
filepath.Join(expectCtrlPath, "/dict/dict.go"),
filepath.Join(expectCtrlPath, "/dict/dict_new.go"),
filepath.Join(expectCtrlPath, "/dict/dict_v1_dict_type.go"),
}
// Line Feed maybe \r\n or \n
expectFilesContent(t, genCtrlFiles, expectFiles)
const testNewApiFile = `
type DictTypeAddReq struct {
g.Meta
}
type DictTypeAddRes struct {
}
`
dictModuleFileName := filepath.Join(apiFolder, "/dict/v1/dict_type.go")
// Save the contents of the file before the changes
apiFileContents := gfile.GetContents(dictModuleFileName)
// Add a new API file
err = gfile.PutContentsAppend(dictModuleFileName, testNewApiFile)
t.AssertNil(err)
//==================================
// Then execute the command
_, err = genctrl.CGenCtrl{}.Ctrl(ctx, in)
t.AssertNil(err)
genApi = filepath.Join(apiFolder, "/dict.go")
genApiExpect = filepath.Join(apiFolder, "/dict_add_new_ctrl_expect.gotest")
t.Assert(gfile.GetContents(genApi), gfile.GetContents(genApiExpect))
// Use the gotest suffix, otherwise the IDE will delete the import
expectFiles[2] = filepath.Join(expectCtrlPath, "/dict/dict_v1_test_new.gotest")
// Line Feed maybe \r\n or \n
expectFilesContent(t, genCtrlFiles, expectFiles)
// Restore the contents of the original API file
err = gfile.PutContents(dictModuleFileName, apiFileContents)
t.AssertNil(err)
})
}
// https://github.com/gogf/gf/issues/3460
func Test_Gen_Ctrl_UseMerge_Issue3460(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
var (
ctrlPath = gfile.Temp(guid.S())
//ctrlPath = gtest.DataPath("issue", "3460", "controller")
apiFolder = gtest.DataPath("issue", "3460", "api")
in = genctrl.CGenCtrlInput{
SrcFolder: apiFolder,
DstFolder: ctrlPath,
WatchFile: "",
SdkPath: "",
SdkStdVersion: false,
SdkNoV1: false,
Clear: false,
Merge: true,
}
)
err := gfile.Mkdir(ctrlPath)
t.AssertNil(err)
defer gfile.Remove(ctrlPath)
_, err = genctrl.CGenCtrl{}.Ctrl(ctx, in)
t.AssertNil(err)
files, err := gfile.ScanDir(ctrlPath, "*.go", true)
t.AssertNil(err)
t.Assert(files, []string{
filepath.Join(ctrlPath, "/hello/hello.go"),
filepath.Join(ctrlPath, "/hello/hello_new.go"),
filepath.Join(ctrlPath, "/hello/hello_v1_req.go"),
filepath.Join(ctrlPath, "/hello/hello_v2_req.go"),
})
expectCtrlPath := gtest.DataPath("issue", "3460", "controller")
expectFiles := []string{
filepath.Join(expectCtrlPath, "/hello/hello.go"),
filepath.Join(expectCtrlPath, "/hello/hello_new.go"),
filepath.Join(expectCtrlPath, "/hello/hello_v1_req.go"),
filepath.Join(expectCtrlPath, "/hello/hello_v2_req.go"),
}
// Line Feed maybe \r\n or \n
for i, expectFile := range expectFiles {
val := gfile.GetContents(files[i])
expect := gfile.GetContents(expectFile)
t.Assert(val, expect)
}
})
}

View File

@ -1,841 +0,0 @@
// Copyright GoFrame gf Author(https://goframe.org). All Rights Reserved.
//
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with this file,
// You can obtain one at https://github.com/gogf/gf.
package cmd
import (
"fmt"
"path/filepath"
"testing"
"github.com/gogf/gf/cmd/gf/v2/internal/cmd/gendao"
"github.com/gogf/gf/v2/database/gdb"
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/os/gcfg"
"github.com/gogf/gf/v2/os/gfile"
"github.com/gogf/gf/v2/test/gtest"
"github.com/gogf/gf/v2/text/gstr"
"github.com/gogf/gf/v2/util/guid"
"github.com/gogf/gf/v2/util/gutil"
)
func Test_Gen_Dao_Default(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
var (
err error
db = testDB
table = "table_user"
sqlContent = fmt.Sprintf(
gtest.DataContent(`gendao`, `user.tpl.sql`),
table,
)
)
dropTableWithDb(db, table)
array := gstr.SplitAndTrim(sqlContent, ";")
for _, v := range array {
if _, err = db.Exec(ctx, v); err != nil {
t.AssertNil(err)
}
}
defer dropTableWithDb(db, table)
var (
path = gfile.Temp(guid.S())
group = "test"
in = gendao.CGenDaoInput{
Path: path,
Link: link,
Tables: "",
TablesEx: "",
Group: group,
Prefix: "",
RemovePrefix: "",
JsonCase: "SnakeScreaming",
ImportPrefix: "",
DaoPath: "",
DoPath: "",
EntityPath: "",
TplDaoIndexPath: "",
TplDaoInternalPath: "",
TplDaoDoPath: "",
TplDaoEntityPath: "",
StdTime: false,
WithTime: false,
GJsonSupport: false,
OverwriteDao: false,
DescriptionTag: false,
NoJsonTag: false,
NoModelComment: false,
Clear: false,
TypeMapping: nil,
FieldMapping: nil,
}
)
err = gutil.FillStructWithDefault(&in)
t.AssertNil(err)
err = gfile.Mkdir(path)
t.AssertNil(err)
// for go mod import path auto retrieve.
err = gfile.Copy(
gtest.DataPath("gendao", "go.mod.txt"),
gfile.Join(path, "go.mod"),
)
t.AssertNil(err)
_, err = gendao.CGenDao{}.Dao(ctx, in)
t.AssertNil(err)
defer gfile.Remove(path)
// files
files, err := gfile.ScanDir(path, "*.go", true)
t.AssertNil(err)
t.Assert(files, []string{
filepath.FromSlash(path + "/dao/internal/table_user.go"),
filepath.FromSlash(path + "/dao/table_user.go"),
filepath.FromSlash(path + "/model/do/table_user.go"),
filepath.FromSlash(path + "/model/entity/table_user.go"),
})
// content
testPath := gtest.DataPath("gendao", "generated_user")
expectFiles := []string{
filepath.FromSlash(testPath + "/dao/internal/table_user.go"),
filepath.FromSlash(testPath + "/dao/table_user.go"),
filepath.FromSlash(testPath + "/model/do/table_user.go"),
filepath.FromSlash(testPath + "/model/entity/table_user.go"),
}
for i, _ := range files {
t.Assert(gfile.GetContents(files[i]), gfile.GetContents(expectFiles[i]))
}
})
}
func Test_Gen_Dao_TypeMapping(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
var (
err error
db = testDB
table = "table_user"
sqlContent = fmt.Sprintf(
gtest.DataContent(`gendao`, `user.tpl.sql`),
table,
)
)
defer dropTableWithDb(db, table)
array := gstr.SplitAndTrim(sqlContent, ";")
for _, v := range array {
if _, err = db.Exec(ctx, v); err != nil {
t.AssertNil(err)
}
}
defer dropTableWithDb(db, table)
var (
path = gfile.Temp(guid.S())
group = "test"
in = gendao.CGenDaoInput{
Path: path,
Link: link,
Tables: "",
TablesEx: "",
Group: group,
Prefix: "",
RemovePrefix: "",
JsonCase: "",
ImportPrefix: "",
DaoPath: "",
DoPath: "",
EntityPath: "",
TplDaoIndexPath: "",
TplDaoInternalPath: "",
TplDaoDoPath: "",
TplDaoEntityPath: "",
StdTime: false,
WithTime: false,
GJsonSupport: false,
OverwriteDao: false,
DescriptionTag: false,
NoJsonTag: false,
NoModelComment: false,
Clear: false,
TypeMapping: map[gendao.DBFieldTypeName]gendao.CustomAttributeType{
"int": {
Type: "int64",
Import: "",
},
"decimal": {
Type: "decimal.Decimal",
Import: "github.com/shopspring/decimal",
},
},
FieldMapping: nil,
}
)
err = gutil.FillStructWithDefault(&in)
t.AssertNil(err)
err = gfile.Mkdir(path)
t.AssertNil(err)
// for go mod import path auto retrieve.
err = gfile.Copy(
gtest.DataPath("gendao", "go.mod.txt"),
gfile.Join(path, "go.mod"),
)
t.AssertNil(err)
_, err = gendao.CGenDao{}.Dao(ctx, in)
t.AssertNil(err)
defer gfile.Remove(path)
// files
files, err := gfile.ScanDir(path, "*.go", true)
t.AssertNil(err)
t.Assert(files, []string{
filepath.FromSlash(path + "/dao/internal/table_user.go"),
filepath.FromSlash(path + "/dao/table_user.go"),
filepath.FromSlash(path + "/model/do/table_user.go"),
filepath.FromSlash(path + "/model/entity/table_user.go"),
})
// content
testPath := gtest.DataPath("gendao", "generated_user_type_mapping")
expectFiles := []string{
filepath.FromSlash(testPath + "/dao/internal/table_user.go"),
filepath.FromSlash(testPath + "/dao/table_user.go"),
filepath.FromSlash(testPath + "/model/do/table_user.go"),
filepath.FromSlash(testPath + "/model/entity/table_user.go"),
}
for i, _ := range files {
t.Assert(gfile.GetContents(files[i]), gfile.GetContents(expectFiles[i]))
}
})
}
func Test_Gen_Dao_FieldMapping(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
var (
err error
db = testDB
table = "table_user"
sqlContent = fmt.Sprintf(
gtest.DataContent(`gendao`, `user.tpl.sql`),
table,
)
)
defer dropTableWithDb(db, table)
array := gstr.SplitAndTrim(sqlContent, ";")
for _, v := range array {
if _, err = db.Exec(ctx, v); err != nil {
t.AssertNil(err)
}
}
defer dropTableWithDb(db, table)
var (
path = gfile.Temp(guid.S())
group = "test"
in = gendao.CGenDaoInput{
Path: path,
Link: link,
Tables: "",
TablesEx: "",
Group: group,
Prefix: "",
RemovePrefix: "",
JsonCase: "",
ImportPrefix: "",
DaoPath: "",
DoPath: "",
EntityPath: "",
TplDaoIndexPath: "",
TplDaoInternalPath: "",
TplDaoDoPath: "",
TplDaoEntityPath: "",
StdTime: false,
WithTime: false,
GJsonSupport: false,
OverwriteDao: false,
DescriptionTag: false,
NoJsonTag: false,
NoModelComment: false,
Clear: false,
TypeMapping: map[gendao.DBFieldTypeName]gendao.CustomAttributeType{
"int": {
Type: "int64",
Import: "",
},
},
FieldMapping: map[gendao.DBTableFieldName]gendao.CustomAttributeType{
"table_user.score": {
Type: "decimal.Decimal",
Import: "github.com/shopspring/decimal",
},
},
}
)
err = gutil.FillStructWithDefault(&in)
t.AssertNil(err)
err = gfile.Mkdir(path)
t.AssertNil(err)
// for go mod import path auto retrieve.
err = gfile.Copy(
gtest.DataPath("gendao", "go.mod.txt"),
gfile.Join(path, "go.mod"),
)
t.AssertNil(err)
_, err = gendao.CGenDao{}.Dao(ctx, in)
t.AssertNil(err)
defer gfile.Remove(path)
// files
files, err := gfile.ScanDir(path, "*.go", true)
t.AssertNil(err)
t.Assert(files, []string{
filepath.FromSlash(path + "/dao/internal/table_user.go"),
filepath.FromSlash(path + "/dao/table_user.go"),
filepath.FromSlash(path + "/model/do/table_user.go"),
filepath.FromSlash(path + "/model/entity/table_user.go"),
})
// content
testPath := gtest.DataPath("gendao", "generated_user_field_mapping")
expectFiles := []string{
filepath.FromSlash(testPath + "/dao/internal/table_user.go"),
filepath.FromSlash(testPath + "/dao/table_user.go"),
filepath.FromSlash(testPath + "/model/do/table_user.go"),
filepath.FromSlash(testPath + "/model/entity/table_user.go"),
}
for i, _ := range files {
t.Assert(gfile.GetContents(files[i]), gfile.GetContents(expectFiles[i]))
}
})
}
func execSqlFile(db gdb.DB, filePath string, args ...any) error {
sqlContent := fmt.Sprintf(
gfile.GetContents(filePath),
args...,
)
array := gstr.SplitAndTrim(sqlContent, ";")
for _, v := range array {
if _, err := db.Exec(ctx, v); err != nil {
return err
}
}
return nil
}
// https://github.com/gogf/gf/issues/2572
func Test_Gen_Dao_Issue2572(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
var (
err error
db = testDB
table1 = "user1"
table2 = "user2"
issueDirPath = gtest.DataPath(`issue`, `2572`)
)
t.AssertNil(execSqlFile(db, gtest.DataPath(`issue`, `2572`, `sql1.sql`)))
t.AssertNil(execSqlFile(db, gtest.DataPath(`issue`, `2572`, `sql2.sql`)))
defer dropTableWithDb(db, table1)
defer dropTableWithDb(db, table2)
var (
path = gfile.Temp(guid.S())
group = "test"
in = gendao.CGenDaoInput{
Path: path,
Link: "",
Tables: "",
TablesEx: "",
Group: group,
Prefix: "",
RemovePrefix: "",
JsonCase: "SnakeScreaming",
ImportPrefix: "",
DaoPath: "",
DoPath: "",
EntityPath: "",
TplDaoIndexPath: "",
TplDaoInternalPath: "",
TplDaoDoPath: "",
TplDaoEntityPath: "",
StdTime: false,
WithTime: false,
GJsonSupport: false,
OverwriteDao: false,
DescriptionTag: false,
NoJsonTag: false,
NoModelComment: false,
Clear: false,
TypeMapping: nil,
FieldMapping: nil,
}
)
err = gutil.FillStructWithDefault(&in)
t.AssertNil(err)
err = gfile.Copy(issueDirPath, path)
t.AssertNil(err)
defer gfile.Remove(path)
pwd := gfile.Pwd()
err = gfile.Chdir(path)
t.AssertNil(err)
defer gfile.Chdir(pwd)
_, err = gendao.CGenDao{}.Dao(ctx, in)
t.AssertNil(err)
generatedFiles, err := gfile.ScanDir(path, "*.go", true)
t.AssertNil(err)
t.Assert(len(generatedFiles), 8)
for i, generatedFile := range generatedFiles {
generatedFiles[i] = gstr.TrimLeftStr(generatedFile, path)
}
t.Assert(gstr.InArray(generatedFiles,
filepath.FromSlash("/dao/internal/user_1.go")), true)
t.Assert(gstr.InArray(generatedFiles,
filepath.FromSlash("/dao/internal/user_2.go")), true)
t.Assert(gstr.InArray(generatedFiles,
filepath.FromSlash("/dao/user_1.go")), true)
t.Assert(gstr.InArray(generatedFiles,
filepath.FromSlash("/dao/user_2.go")), true)
t.Assert(gstr.InArray(generatedFiles,
filepath.FromSlash("/model/do/user_1.go")), true)
t.Assert(gstr.InArray(generatedFiles,
filepath.FromSlash("/model/do/user_2.go")), true)
t.Assert(gstr.InArray(generatedFiles,
filepath.FromSlash("/model/entity/user_1.go")), true)
t.Assert(gstr.InArray(generatedFiles,
filepath.FromSlash("/model/entity/user_2.go")), true)
})
}
// https://github.com/gogf/gf/issues/2616
func Test_Gen_Dao_Issue2616(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
var (
err error
db = testDB
table1 = "user1"
table2 = "user2"
issueDirPath = gtest.DataPath(`issue`, `2616`)
)
t.AssertNil(execSqlFile(db, gtest.DataPath(`issue`, `2616`, `sql1.sql`)))
t.AssertNil(execSqlFile(db, gtest.DataPath(`issue`, `2616`, `sql2.sql`)))
defer dropTableWithDb(db, table1)
defer dropTableWithDb(db, table2)
var (
path = gfile.Temp(guid.S())
group = "test"
in = gendao.CGenDaoInput{
Path: path,
Link: "",
Tables: "",
TablesEx: "",
Group: group,
Prefix: "",
RemovePrefix: "",
JsonCase: "SnakeScreaming",
ImportPrefix: "",
DaoPath: "",
DoPath: "",
EntityPath: "",
TplDaoIndexPath: "",
TplDaoInternalPath: "",
TplDaoDoPath: "",
TplDaoEntityPath: "",
StdTime: false,
WithTime: false,
GJsonSupport: false,
OverwriteDao: false,
DescriptionTag: false,
NoJsonTag: false,
NoModelComment: false,
Clear: false,
TypeMapping: nil,
FieldMapping: nil,
}
)
err = gutil.FillStructWithDefault(&in)
t.AssertNil(err)
err = gfile.Copy(issueDirPath, path)
t.AssertNil(err)
defer gfile.Remove(path)
pwd := gfile.Pwd()
err = gfile.Chdir(path)
t.AssertNil(err)
defer gfile.Chdir(pwd)
_, err = gendao.CGenDao{}.Dao(ctx, in)
t.AssertNil(err)
generatedFiles, err := gfile.ScanDir(path, "*.go", true)
t.AssertNil(err)
t.Assert(len(generatedFiles), 8)
for i, generatedFile := range generatedFiles {
generatedFiles[i] = gstr.TrimLeftStr(generatedFile, path)
}
t.Assert(gstr.InArray(generatedFiles,
filepath.FromSlash("/dao/internal/user_1.go")), true)
t.Assert(gstr.InArray(generatedFiles,
filepath.FromSlash("/dao/internal/user_2.go")), true)
t.Assert(gstr.InArray(generatedFiles,
filepath.FromSlash("/dao/user_1.go")), true)
t.Assert(gstr.InArray(generatedFiles,
filepath.FromSlash("/dao/user_2.go")), true)
t.Assert(gstr.InArray(generatedFiles,
filepath.FromSlash("/model/do/user_1.go")), true)
t.Assert(gstr.InArray(generatedFiles,
filepath.FromSlash("/model/do/user_2.go")), true)
t.Assert(gstr.InArray(generatedFiles,
filepath.FromSlash("/model/entity/user_1.go")), true)
t.Assert(gstr.InArray(generatedFiles,
filepath.FromSlash("/model/entity/user_2.go")), true)
// Key string to check if overwrite the dao files.
// dao user1 is not be overwritten as configured in config.yaml.
// dao user2 is to be overwritten as configured in config.yaml.
var (
keyStr = `// I am not overwritten.`
daoUser1Content = gfile.GetContents(path + "/dao/user_1.go")
daoUser2Content = gfile.GetContents(path + "/dao/user_2.go")
)
t.Assert(gstr.Contains(daoUser1Content, keyStr), true)
t.Assert(gstr.Contains(daoUser2Content, keyStr), false)
})
}
// https://github.com/gogf/gf/issues/2746
func Test_Gen_Dao_Issue2746(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
var (
err error
mdb gdb.DB
link2746 = "mariadb:root:12345678@tcp(127.0.0.1:3307)/test?loc=Local&parseTime=true"
table = "issue2746"
sqlContent = fmt.Sprintf(
gtest.DataContent(`issue`, `2746`, `sql.sql`),
table,
)
)
mdb, err = gdb.New(gdb.ConfigNode{
Link: link2746,
})
t.AssertNil(err)
array := gstr.SplitAndTrim(sqlContent, ";")
for _, v := range array {
if _, err = mdb.Exec(ctx, v); err != nil {
t.AssertNil(err)
}
}
defer dropTableWithDb(mdb, table)
var (
path = gfile.Temp(guid.S())
group = "test"
in = gendao.CGenDaoInput{
Path: path,
Link: link2746,
Tables: "",
TablesEx: "",
Group: group,
Prefix: "",
RemovePrefix: "",
JsonCase: "SnakeScreaming",
ImportPrefix: "",
DaoPath: "",
DoPath: "",
EntityPath: "",
TplDaoIndexPath: "",
TplDaoInternalPath: "",
TplDaoDoPath: "",
TplDaoEntityPath: "",
StdTime: false,
WithTime: false,
GJsonSupport: true,
OverwriteDao: false,
DescriptionTag: false,
NoJsonTag: false,
NoModelComment: false,
Clear: false,
TypeMapping: nil,
FieldMapping: nil,
}
)
err = gutil.FillStructWithDefault(&in)
t.AssertNil(err)
err = gfile.Mkdir(path)
t.AssertNil(err)
_, err = gendao.CGenDao{}.Dao(ctx, in)
t.AssertNil(err)
defer gfile.Remove(path)
var (
file = filepath.FromSlash(path + "/model/entity/issue_2746.go")
expectContent = gtest.DataContent(`issue`, `2746`, `issue_2746.go`)
)
t.Assert(expectContent, gfile.GetContents(file))
})
}
// https://github.com/gogf/gf/issues/3459
func Test_Gen_Dao_Issue3459(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
var (
err error
db = testDB
table = "table_user"
sqlContent = fmt.Sprintf(
gtest.DataContent(`gendao`, `user.tpl.sql`),
table,
)
)
dropTableWithDb(db, table)
array := gstr.SplitAndTrim(sqlContent, ";")
for _, v := range array {
if _, err = db.Exec(ctx, v); err != nil {
t.AssertNil(err)
}
}
defer dropTableWithDb(db, table)
var (
confDir = gtest.DataPath("issue", "3459")
path = gfile.Temp(guid.S())
group = "test"
in = gendao.CGenDaoInput{
Path: path,
Link: link,
Tables: "",
TablesEx: "",
Group: group,
Prefix: "",
RemovePrefix: "",
JsonCase: "SnakeScreaming",
ImportPrefix: "",
DaoPath: "",
DoPath: "",
EntityPath: "",
TplDaoIndexPath: "",
TplDaoInternalPath: "",
TplDaoDoPath: "",
TplDaoEntityPath: "",
StdTime: false,
WithTime: false,
GJsonSupport: false,
OverwriteDao: false,
DescriptionTag: false,
NoJsonTag: false,
NoModelComment: false,
Clear: false,
TypeMapping: nil,
}
)
err = g.Cfg().GetAdapter().(*gcfg.AdapterFile).SetPath(confDir)
t.AssertNil(err)
err = gutil.FillStructWithDefault(&in)
t.AssertNil(err)
err = gfile.Mkdir(path)
t.AssertNil(err)
// for go mod import path auto retrieve.
err = gfile.Copy(
gtest.DataPath("gendao", "go.mod.txt"),
gfile.Join(path, "go.mod"),
)
t.AssertNil(err)
_, err = gendao.CGenDao{}.Dao(ctx, in)
t.AssertNil(err)
defer gfile.Remove(path)
// files
files, err := gfile.ScanDir(path, "*.go", true)
t.AssertNil(err)
t.Assert(files, []string{
filepath.FromSlash(path + "/dao/internal/table_user.go"),
filepath.FromSlash(path + "/dao/table_user.go"),
filepath.FromSlash(path + "/model/do/table_user.go"),
filepath.FromSlash(path + "/model/entity/table_user.go"),
})
// content
testPath := gtest.DataPath("gendao", "generated_user")
expectFiles := []string{
filepath.FromSlash(testPath + "/dao/internal/table_user.go"),
filepath.FromSlash(testPath + "/dao/table_user.go"),
filepath.FromSlash(testPath + "/model/do/table_user.go"),
filepath.FromSlash(testPath + "/model/entity/table_user.go"),
}
for i, _ := range files {
t.Assert(gfile.GetContents(files[i]), gfile.GetContents(expectFiles[i]))
}
})
}
// https://github.com/gogf/gf/issues/3749
func Test_Gen_Dao_Issue3749(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
var (
err error
db = testDB
table = "table_user"
sqlContent = fmt.Sprintf(
gtest.DataContent(`issue`, `3749`, `user.tpl.sql`),
table,
)
)
dropTableWithDb(db, table)
array := gstr.SplitAndTrim(sqlContent, ";")
for _, v := range array {
if _, err = db.Exec(ctx, v); err != nil {
t.AssertNil(err)
}
}
defer dropTableWithDb(db, table)
var (
path = gfile.Temp(guid.S())
group = "test"
in = gendao.CGenDaoInput{
Path: path,
Link: link,
Group: group,
}
)
err = gutil.FillStructWithDefault(&in)
t.AssertNil(err)
err = gfile.Mkdir(path)
t.AssertNil(err)
// for go mod import path auto retrieve.
err = gfile.Copy(
gtest.DataPath("gendao", "go.mod.txt"),
gfile.Join(path, "go.mod"),
)
t.AssertNil(err)
_, err = gendao.CGenDao{}.Dao(ctx, in)
t.AssertNil(err)
defer gfile.Remove(path)
// files
files, err := gfile.ScanDir(path, "*.go", true)
t.AssertNil(err)
t.Assert(files, []string{
filepath.FromSlash(path + "/dao/internal/table_user.go"),
filepath.FromSlash(path + "/dao/table_user.go"),
filepath.FromSlash(path + "/model/do/table_user.go"),
filepath.FromSlash(path + "/model/entity/table_user.go"),
})
// content
testPath := gtest.DataPath(`issue`, `3749`)
expectFiles := []string{
filepath.FromSlash(testPath + "/dao/internal/table_user.go"),
filepath.FromSlash(testPath + "/dao/table_user.go"),
filepath.FromSlash(testPath + "/model/do/table_user.go"),
filepath.FromSlash(testPath + "/model/entity/table_user.go"),
}
for i, _ := range files {
t.Assert(gfile.GetContents(files[i]), gfile.GetContents(expectFiles[i]))
}
})
}
func Test_Gen_Dao_Sqlite3(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
var (
err error
table = "table_user"
path = gfile.Temp(guid.S())
linkSqlite3 = fmt.Sprintf("sqlite::@file(%s/db.sqlite3)", path)
sqlContent = fmt.Sprintf(
gtest.DataContent(`gendao`, `sqlite3`, `user.sqlite3.sql`),
table,
)
)
err = gfile.Mkdir(path)
t.AssertNil(err)
defer gfile.Remove(path)
dbSqlite3, err := gdb.New(gdb.ConfigNode{
Link: linkSqlite3,
})
t.AssertNil(err)
array := gstr.SplitAndTrim(sqlContent, ";")
for _, v := range array {
if v == "" {
continue
}
if _, err = dbSqlite3.Exec(ctx, v); err != nil {
t.AssertNil(err)
}
}
var (
group = "test"
in = gendao.CGenDaoInput{
Path: path,
Link: linkSqlite3,
Group: group,
Tables: table,
}
)
err = gutil.FillStructWithDefault(&in)
t.AssertNil(err)
// for go mod import path auto retrieve.
err = gfile.Copy(
gtest.DataPath("gendao", "go.mod.txt"),
gfile.Join(path, "go.mod"),
)
t.AssertNil(err)
_, err = gendao.CGenDao{}.Dao(ctx, in)
t.AssertNil(err)
defer gfile.Remove(path)
// files
files, err := gfile.ScanDir(path, "*.go", true)
t.AssertNil(err)
t.Assert(files, []string{
filepath.FromSlash(path + "/dao/internal/table_user.go"),
filepath.FromSlash(path + "/dao/table_user.go"),
filepath.FromSlash(path + "/model/do/table_user.go"),
filepath.FromSlash(path + "/model/entity/table_user.go"),
})
// content
testPath := gtest.DataPath("gendao", "generated_user_sqlite3")
expectFiles := []string{
filepath.FromSlash(testPath + "/dao/internal/table_user.go"),
filepath.FromSlash(testPath + "/dao/table_user.go"),
filepath.FromSlash(testPath + "/model/do/table_user.go"),
filepath.FromSlash(testPath + "/model/entity/table_user.go"),
}
for i, _ := range files {
t.Assert(gfile.GetContents(files[i]), gfile.GetContents(expectFiles[i]))
}
})
}

View File

@ -1,50 +0,0 @@
// Copyright GoFrame gf Author(https://goframe.org). All Rights Reserved.
//
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with this file,
// You can obtain one at https://github.com/gogf/gf.
package cmd
import (
"path/filepath"
"testing"
"github.com/gogf/gf/cmd/gf/v2/internal/cmd/genpb"
"github.com/gogf/gf/v2/os/gfile"
"github.com/gogf/gf/v2/test/gtest"
"github.com/gogf/gf/v2/text/gstr"
"github.com/gogf/gf/v2/util/guid"
)
func TestGenPbIssue3882(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
var (
outputPath = gfile.Temp(guid.S())
outputApiPath = filepath.Join(outputPath, "api")
outputCtrlPath = filepath.Join(outputPath, "controller")
protobufFolder = gtest.DataPath("issue", "3882")
in = genpb.CGenPbInput{
Path: protobufFolder,
OutputApi: outputApiPath,
OutputCtrl: outputCtrlPath,
}
err error
)
err = gfile.Mkdir(outputApiPath)
t.AssertNil(err)
err = gfile.Mkdir(outputCtrlPath)
t.AssertNil(err)
defer gfile.Remove(outputPath)
_, err = genpb.CGenPb{}.Pb(ctx, in)
t.AssertNil(err)
var (
genContent = gfile.GetContents(filepath.Join(outputApiPath, "issue3882.pb.go"))
exceptText = `dc:"Some comment on field with 'one' 'two' 'three' in the comment."`
)
t.Assert(gstr.Contains(genContent, exceptText), true)
})
}

View File

@ -1,210 +0,0 @@
// Copyright GoFrame gf Author(https://goframe.org). All Rights Reserved.
//
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with this file,
// You can obtain one at https://github.com/gogf/gf.
package cmd
import (
"fmt"
"path/filepath"
"testing"
"github.com/gogf/gf/cmd/gf/v2/internal/cmd/genpbentity"
"github.com/gogf/gf/v2/os/gfile"
"github.com/gogf/gf/v2/test/gtest"
"github.com/gogf/gf/v2/text/gstr"
"github.com/gogf/gf/v2/util/guid"
"github.com/gogf/gf/v2/util/gutil"
)
func Test_Gen_Pbentity_Default(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
var (
err error
db = testDB
table = "table_user"
sqlContent = fmt.Sprintf(
gtest.DataContent(`genpbentity`, `user.tpl.sql`),
table,
)
)
dropTableWithDb(db, table)
array := gstr.SplitAndTrim(sqlContent, ";")
for _, v := range array {
if _, err = db.Exec(ctx, v); err != nil {
t.AssertNil(err)
}
}
defer dropTableWithDb(db, table)
var (
path = gfile.Temp(guid.S())
in = genpbentity.CGenPbEntityInput{
Path: path,
Package: "unittest",
Link: link,
Tables: "",
Prefix: "",
RemovePrefix: "",
RemoveFieldPrefix: "",
NameCase: "",
JsonCase: "",
Option: "",
}
)
err = gutil.FillStructWithDefault(&in)
t.AssertNil(err)
err = gfile.Mkdir(path)
t.AssertNil(err)
defer gfile.Remove(path)
_, err = genpbentity.CGenPbEntity{}.PbEntity(ctx, in)
t.AssertNil(err)
// files
files, err := gfile.ScanDir(path, "*.proto", false)
t.AssertNil(err)
t.Assert(files, []string{
path + filepath.FromSlash("/table_user.proto"),
})
// contents
testPath := gtest.DataPath("genpbentity", "generated")
expectFiles := []string{
testPath + filepath.FromSlash("/table_user.proto"),
}
for i := range files {
t.Assert(gfile.GetContents(files[i]), gfile.GetContents(expectFiles[i]))
}
})
}
func Test_Gen_Pbentity_NameCase_SnakeScreaming(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
var (
err error
db = testDB
table = "table_user"
sqlContent = fmt.Sprintf(
gtest.DataContent(`genpbentity`, `user.tpl.sql`),
table,
)
)
dropTableWithDb(db, table)
array := gstr.SplitAndTrim(sqlContent, ";")
for _, v := range array {
if _, err = db.Exec(ctx, v); err != nil {
t.AssertNil(err)
}
}
defer dropTableWithDb(db, table)
var (
path = gfile.Temp(guid.S())
in = genpbentity.CGenPbEntityInput{
Path: path,
Package: "unittest",
Link: link,
Tables: "",
Prefix: "",
RemovePrefix: "",
RemoveFieldPrefix: "",
NameCase: "SnakeScreaming",
JsonCase: "",
Option: "",
}
)
err = gutil.FillStructWithDefault(&in)
t.AssertNil(err)
err = gfile.Mkdir(path)
t.AssertNil(err)
defer gfile.Remove(path)
_, err = genpbentity.CGenPbEntity{}.PbEntity(ctx, in)
t.AssertNil(err)
// files
files, err := gfile.ScanDir(path, "*.proto", false)
t.AssertNil(err)
t.Assert(files, []string{
path + filepath.FromSlash("/table_user.proto"),
})
// contents
testPath := gtest.DataPath("genpbentity", "generated")
expectFiles := []string{
testPath + filepath.FromSlash("/table_user_snake_screaming.proto"),
}
for i := range files {
t.Assert(gfile.GetContents(files[i]), gfile.GetContents(expectFiles[i]))
}
})
}
// https://github.com/gogf/gf/issues/3545
func Test_Issue_3545(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
var (
err error
db = testDB
table = "table_user"
sqlContent = fmt.Sprintf(
gtest.DataContent(`genpbentity`, `user.tpl.sql`),
table,
)
)
dropTableWithDb(db, table)
array := gstr.SplitAndTrim(sqlContent, ";")
for _, v := range array {
if _, err = db.Exec(ctx, v); err != nil {
t.AssertNil(err)
}
}
defer dropTableWithDb(db, table)
var (
path = gfile.Temp(guid.S())
in = genpbentity.CGenPbEntityInput{
Path: path,
Package: "",
Link: link,
Tables: "",
Prefix: "",
RemovePrefix: "",
RemoveFieldPrefix: "",
NameCase: "",
JsonCase: "",
Option: "",
}
)
err = gutil.FillStructWithDefault(&in)
t.AssertNil(err)
err = gfile.Mkdir(path)
t.AssertNil(err)
defer gfile.Remove(path)
_, err = genpbentity.CGenPbEntity{}.PbEntity(ctx, in)
t.AssertNil(err)
// files
files, err := gfile.ScanDir(path, "*.proto", false)
t.AssertNil(err)
t.Assert(files, []string{
path + filepath.FromSlash("/table_user.proto"),
})
// contents
testPath := gtest.DataPath("issue", "3545")
expectFiles := []string{
testPath + filepath.FromSlash("/table_user.proto"),
}
for i := range files {
t.Assert(gfile.GetContents(files[i]), gfile.GetContents(expectFiles[i]))
}
})
}

View File

@ -1,155 +0,0 @@
// Copyright GoFrame gf Author(https://goframe.org). All Rights Reserved.
//
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with this file,
// You can obtain one at https://github.com/gogf/gf.
package cmd
import (
"path/filepath"
"testing"
"github.com/gogf/gf/cmd/gf/v2/internal/cmd/genservice"
"github.com/gogf/gf/v2/os/gfile"
"github.com/gogf/gf/v2/test/gtest"
"github.com/gogf/gf/v2/util/guid"
"github.com/gogf/gf/v2/util/gutil"
)
func Test_Gen_Service_Default(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
var (
path = gfile.Temp(guid.S())
dstFolder = path + filepath.FromSlash("/service")
srvFolder = gtest.DataPath("genservice", "logic")
in = genservice.CGenServiceInput{
SrcFolder: srvFolder,
DstFolder: dstFolder,
DstFileNameCase: "Snake",
WatchFile: "",
StPattern: "",
Packages: nil,
ImportPrefix: "",
Clear: false,
}
)
err := gutil.FillStructWithDefault(&in)
t.AssertNil(err)
err = gfile.Mkdir(path)
t.AssertNil(err)
defer gfile.Remove(path)
_, err = genservice.CGenService{}.Service(ctx, in)
t.AssertNil(err)
// logic file
var (
genSrv = srvFolder + filepath.FromSlash("/logic.go")
genSrvExpect = srvFolder + filepath.FromSlash("/logic_expect.go")
)
defer gfile.Remove(genSrv)
t.Assert(gfile.GetContents(genSrv), gfile.GetContents(genSrvExpect))
// files
files, err := gfile.ScanDir(dstFolder, "*.go", true)
t.AssertNil(err)
t.Assert(files, []string{
dstFolder + filepath.FromSlash("/article.go"),
dstFolder + filepath.FromSlash("/delivery.go"),
dstFolder + filepath.FromSlash("/user.go"),
})
// contents
testPath := gtest.DataPath("genservice", "service")
expectFiles := []string{
testPath + filepath.FromSlash("/article.go"),
testPath + filepath.FromSlash("/delivery.go"),
testPath + filepath.FromSlash("/user.go"),
}
for i := range files {
t.Assert(gfile.GetContents(files[i]), gfile.GetContents(expectFiles[i]))
}
})
}
// https://github.com/gogf/gf/issues/3328
func Test_Issue3328(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
var (
path = gfile.Temp(guid.S())
dstFolder = path + filepath.FromSlash("/service")
srvFolder = gtest.DataPath("issue", "3328", "logic")
logicGoPath = srvFolder + filepath.FromSlash("/logic.go")
in = genservice.CGenServiceInput{
SrcFolder: srvFolder,
DstFolder: dstFolder,
DstFileNameCase: "Snake",
WatchFile: "",
StPattern: "",
Packages: nil,
ImportPrefix: "",
Clear: false,
}
)
gfile.Remove(logicGoPath)
defer gfile.Remove(logicGoPath)
err := gutil.FillStructWithDefault(&in)
t.AssertNil(err)
err = gfile.Mkdir(path)
t.AssertNil(err)
defer gfile.Remove(path)
_, err = genservice.CGenService{}.Service(ctx, in)
t.AssertNil(err)
files, err := gfile.ScanDir(srvFolder, "*", true)
for _, file := range files {
if file == logicGoPath {
if gfile.IsDir(logicGoPath) {
t.Fatalf("%s should not is folder", logicGoPath)
}
}
}
})
}
// https://github.com/gogf/gf/issues/3835
func Test_Issue3835(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
var (
path = gfile.Temp(guid.S())
dstFolder = path + filepath.FromSlash("/service")
srvFolder = gtest.DataPath("issue", "3835", "logic")
in = genservice.CGenServiceInput{
SrcFolder: srvFolder,
DstFolder: dstFolder,
DstFileNameCase: "Snake",
WatchFile: "",
StPattern: "",
Packages: nil,
ImportPrefix: "",
Clear: false,
}
)
err := gutil.FillStructWithDefault(&in)
t.AssertNil(err)
err = gfile.Mkdir(path)
t.AssertNil(err)
defer gfile.Remove(path)
_, err = genservice.CGenService{}.Service(ctx, in)
t.AssertNil(err)
// contents
var (
genFile = dstFolder + filepath.FromSlash("/issue_3835.go")
expectFile = gtest.DataPath("issue", "3835", "service", "issue_3835.go")
)
t.Assert(gfile.GetContents(genFile), gfile.GetContents(expectFile))
})
}

View File

@ -1,237 +0,0 @@
// Copyright GoFrame gf Author(https://goframe.org). All Rights Reserved.
//
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with this file,
// You can obtain one at https://github.com/gogf/gf.
package genctrl
import (
"context"
"github.com/gogf/gf/cmd/gf/v2/internal/utility/mlog"
"github.com/gogf/gf/v2/container/gset"
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/os/gfile"
"github.com/gogf/gf/v2/os/gtime"
"github.com/gogf/gf/v2/util/gconv"
"github.com/gogf/gf/v2/util/gtag"
)
const (
CGenCtrlConfig = `gfcli.gen.ctrl`
CGenCtrlUsage = `gf gen ctrl [OPTION]`
CGenCtrlBrief = `parse api definitions to generate controller/sdk go files`
CGenCtrlEg = `
gf gen ctrl
`
CGenCtrlBriefSrcFolder = `source folder path to be parsed. default: api`
CGenCtrlBriefDstFolder = `destination folder path storing automatically generated go files. default: internal/controller`
CGenCtrlBriefWatchFile = `used in file watcher, it re-generates go files only if given file is under srcFolder`
CGenCtrlBriefSdkPath = `also generate SDK go files for api definitions to specified directory`
CGenCtrlBriefSdkStdVersion = `use standard version prefix for generated sdk request path`
CGenCtrlBriefSdkNoV1 = `do not add version suffix for interface module name if version is v1`
CGenCtrlBriefClear = `auto delete generated and unimplemented controller go files if api definitions are missing`
CGenCtrlControllerMerge = `generate all controller files into one go file by name of api definition source go file`
)
const (
PatternCtrlDefinition = `func\s+\(.+?\)\s+\w+\(.+?\*(\w+)\.(\w+)Req\)\s+\(.+?\*(\w+)\.(\w+)Res,\s+\w+\s+error\)\s+{`
)
const (
genCtrlFileLockSeconds = 10
)
func init() {
gtag.Sets(g.MapStrStr{
`CGenCtrlConfig`: CGenCtrlConfig,
`CGenCtrlUsage`: CGenCtrlUsage,
`CGenCtrlBrief`: CGenCtrlBrief,
`CGenCtrlEg`: CGenCtrlEg,
`CGenCtrlBriefSrcFolder`: CGenCtrlBriefSrcFolder,
`CGenCtrlBriefDstFolder`: CGenCtrlBriefDstFolder,
`CGenCtrlBriefWatchFile`: CGenCtrlBriefWatchFile,
`CGenCtrlBriefSdkPath`: CGenCtrlBriefSdkPath,
`CGenCtrlBriefSdkStdVersion`: CGenCtrlBriefSdkStdVersion,
`CGenCtrlBriefSdkNoV1`: CGenCtrlBriefSdkNoV1,
`CGenCtrlBriefClear`: CGenCtrlBriefClear,
`CGenCtrlControllerMerge`: CGenCtrlControllerMerge,
})
}
type (
CGenCtrl struct{}
CGenCtrlInput struct {
g.Meta `name:"ctrl" config:"{CGenCtrlConfig}" usage:"{CGenCtrlUsage}" brief:"{CGenCtrlBrief}" eg:"{CGenCtrlEg}"`
SrcFolder string `short:"s" name:"srcFolder" brief:"{CGenCtrlBriefSrcFolder}" d:"api"`
DstFolder string `short:"d" name:"dstFolder" brief:"{CGenCtrlBriefDstFolder}" d:"internal/controller"`
WatchFile string `short:"w" name:"watchFile" brief:"{CGenCtrlBriefWatchFile}"`
SdkPath string `short:"k" name:"sdkPath" brief:"{CGenCtrlBriefSdkPath}"`
SdkStdVersion bool `short:"v" name:"sdkStdVersion" brief:"{CGenCtrlBriefSdkStdVersion}" orphan:"true"`
SdkNoV1 bool `short:"n" name:"sdkNoV1" brief:"{CGenCtrlBriefSdkNoV1}" orphan:"true"`
Clear bool `short:"c" name:"clear" brief:"{CGenCtrlBriefClear}" orphan:"true"`
Merge bool `short:"m" name:"merge" brief:"{CGenCtrlControllerMerge}" orphan:"true"`
}
CGenCtrlOutput struct{}
)
func (c CGenCtrl) Ctrl(ctx context.Context, in CGenCtrlInput) (out *CGenCtrlOutput, err error) {
if in.WatchFile != "" {
err = c.generateByWatchFile(
in.WatchFile, in.SdkPath, in.SdkStdVersion, in.SdkNoV1, in.Clear, in.Merge,
)
mlog.Print(`done!`)
return
}
if !gfile.Exists(in.SrcFolder) {
mlog.Fatalf(`source folder path "%s" does not exist`, in.SrcFolder)
}
// retrieve all api modules.
apiModuleFolderPaths, err := gfile.ScanDir(in.SrcFolder, "*", false)
if err != nil {
return nil, err
}
for _, apiModuleFolderPath := range apiModuleFolderPaths {
if !gfile.IsDir(apiModuleFolderPath) {
continue
}
// generate go files by api module.
var (
module = gfile.Basename(apiModuleFolderPath)
dstModuleFolderPath = gfile.Join(in.DstFolder, module)
)
err = c.generateByModule(
apiModuleFolderPath, dstModuleFolderPath, in.SdkPath,
in.SdkStdVersion, in.SdkNoV1, in.Clear, in.Merge,
)
if err != nil {
return nil, err
}
}
mlog.Print(`done!`)
return
}
func (c CGenCtrl) generateByWatchFile(watchFile, sdkPath string, sdkStdVersion, sdkNoV1, clear, merge bool) (err error) {
// File lock to avoid multiple processes.
var (
flockFilePath = gfile.Temp("gf.cli.gen.service.lock")
flockContent = gfile.GetContents(flockFilePath)
)
if flockContent != "" {
if gtime.Timestamp()-gconv.Int64(flockContent) < genCtrlFileLockSeconds {
// If another generating process is running, it just exits.
mlog.Debug(`another "gen service" process is running, exit`)
return
}
}
defer gfile.RemoveFile(flockFilePath)
_ = gfile.PutContents(flockFilePath, gtime.TimestampStr())
// check this updated file is an api file.
// watch file should be in standard goframe project structure.
var (
apiVersionPath = gfile.Dir(watchFile)
apiModuleFolderPath = gfile.Dir(apiVersionPath)
shouldBeNameOfAPi = gfile.Basename(gfile.Dir(apiModuleFolderPath))
)
if shouldBeNameOfAPi != "api" {
return nil
}
// watch file should have api definitions.
if gfile.Exists(watchFile) {
structsInfo, err := c.getStructsNameInSrc(watchFile)
if err != nil {
return err
}
if len(structsInfo) == 0 {
return nil
}
}
var (
projectRootPath = gfile.Dir(gfile.Dir(apiModuleFolderPath))
module = gfile.Basename(apiModuleFolderPath)
dstModuleFolderPath = gfile.Join(projectRootPath, "internal", "controller", module)
)
return c.generateByModule(
apiModuleFolderPath, dstModuleFolderPath, sdkPath, sdkStdVersion, sdkNoV1, clear, merge,
)
}
// parseApiModule parses certain api and generate associated go files by certain module, not all api modules.
func (c CGenCtrl) generateByModule(
apiModuleFolderPath, dstModuleFolderPath, sdkPath string,
sdkStdVersion, sdkNoV1, clear, merge bool,
) (err error) {
// parse src and dst folder go files.
apiItemsInSrc, err := c.getApiItemsInSrc(apiModuleFolderPath)
if err != nil {
return err
}
apiItemsInDst, err := c.getApiItemsInDst(dstModuleFolderPath)
if err != nil {
return err
}
// generate api interface go files.
if err = newApiInterfaceGenerator().Generate(apiModuleFolderPath, apiItemsInSrc); err != nil {
return
}
// generate controller go files.
// api filtering for already implemented api controllers.
var (
alreadyImplementedCtrlSet = gset.NewStrSet()
toBeImplementedApiItems = make([]apiItem, 0)
)
for _, item := range apiItemsInDst {
alreadyImplementedCtrlSet.Add(item.String())
}
for _, item := range apiItemsInSrc {
if alreadyImplementedCtrlSet.Contains(item.String()) {
continue
}
toBeImplementedApiItems = append(toBeImplementedApiItems, item)
}
if len(toBeImplementedApiItems) > 0 {
err = newControllerGenerator().Generate(dstModuleFolderPath, toBeImplementedApiItems, merge)
if err != nil {
return
}
}
// delete unimplemented controllers if api definitions are missing.
if clear {
var (
apiDefinitionSet = gset.NewStrSet()
extraApiItemsInCtrl = make([]apiItem, 0)
)
for _, item := range apiItemsInSrc {
apiDefinitionSet.Add(item.String())
}
for _, item := range apiItemsInDst {
if apiDefinitionSet.Contains(item.String()) {
continue
}
extraApiItemsInCtrl = append(extraApiItemsInCtrl, item)
}
if len(extraApiItemsInCtrl) > 0 {
err = newControllerClearer().Clear(dstModuleFolderPath, extraApiItemsInCtrl)
if err != nil {
return
}
}
}
// generate sdk go files.
if sdkPath != "" {
if err = newApiSdkGenerator().Generate(apiItemsInSrc, sdkPath, sdkStdVersion, sdkNoV1); err != nil {
return
}
}
return
}

View File

@ -1,23 +0,0 @@
// Copyright GoFrame gf Author(https://goframe.org). All Rights Reserved.
//
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with this file,
// You can obtain one at https://github.com/gogf/gf.
package genctrl
import "github.com/gogf/gf/v2/text/gstr"
type apiItem struct {
Import string `eg:"demo.com/api/user/v1"`
FileName string `eg:"user"`
Module string `eg:"user"`
Version string `eg:"v1"`
MethodName string `eg:"GetList"`
}
func (a apiItem) String() string {
return gstr.Join([]string{
a.Import, a.Module, a.Version, a.MethodName,
}, ",")
}

View File

@ -1,78 +0,0 @@
// Copyright GoFrame gf Author(https://goframe.org). All Rights Reserved.
//
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with this file,
// You can obtain one at https://github.com/gogf/gf.
package genctrl
import (
"bytes"
"go/ast"
"go/parser"
"go/printer"
"go/token"
"github.com/gogf/gf/v2/os/gfile"
"github.com/gogf/gf/v2/text/gstr"
)
// getStructsNameInSrc retrieves all struct names
// that end in "Req" and have "g.Meta" in their body.
func (c CGenCtrl) getStructsNameInSrc(filePath string) (structsName []string, err error) {
var (
fileContent = gfile.GetContents(filePath)
fileSet = token.NewFileSet()
)
node, err := parser.ParseFile(fileSet, "", fileContent, parser.ParseComments)
if err != nil {
return
}
ast.Inspect(node, func(n ast.Node) bool {
if typeSpec, ok := n.(*ast.TypeSpec); ok {
methodName := typeSpec.Name.Name
if !gstr.HasSuffix(methodName, "Req") {
// ignore struct name that do not end in "Req"
return true
}
if structType, ok := typeSpec.Type.(*ast.StructType); ok {
var buf bytes.Buffer
if err := printer.Fprint(&buf, fileSet, structType); err != nil {
return false
}
// ignore struct name that match a request, but has no g.Meta in its body.
if !gstr.Contains(buf.String(), `g.Meta`) {
return true
}
structsName = append(structsName, methodName)
}
}
return true
})
return
}
// getImportsInDst retrieves all import paths in the file.
func (c CGenCtrl) getImportsInDst(filePath string) (imports []string, err error) {
var (
fileContent = gfile.GetContents(filePath)
fileSet = token.NewFileSet()
)
node, err := parser.ParseFile(fileSet, "", fileContent, parser.ParseComments)
if err != nil {
return
}
ast.Inspect(node, func(n ast.Node) bool {
if imp, ok := n.(*ast.ImportSpec); ok {
imports = append(imports, imp.Path.Value)
}
return true
})
return
}

View File

@ -1,43 +0,0 @@
// Copyright GoFrame gf Author(https://goframe.org). All Rights Reserved.
//
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with this file,
// You can obtain one at https://github.com/gogf/gf.
package genctrl
import (
"bytes"
"go/ast"
"go/parser"
"go/printer"
"go/token"
"github.com/gogf/gf/v2/os/gfile"
)
// getFuncInDst retrieves all function declarations and bodies in the file.
func (c *controllerClearer) getFuncInDst(filePath string) (funcs []string, err error) {
var (
fileContent = gfile.GetContents(filePath)
fileSet = token.NewFileSet()
)
node, err := parser.ParseFile(fileSet, "", fileContent, parser.ParseComments)
if err != nil {
return
}
ast.Inspect(node, func(n ast.Node) bool {
if fun, ok := n.(*ast.FuncDecl); ok {
var buf bytes.Buffer
if err := printer.Fprint(&buf, fileSet, fun); err != nil {
return false
}
funcs = append(funcs, buf.String())
}
return true
})
return
}

View File

@ -1,135 +0,0 @@
// Copyright GoFrame gf Author(https://goframe.org). All Rights Reserved.
//
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with this file,
// You can obtain one at https://github.com/gogf/gf.
package genctrl
import (
"github.com/gogf/gf/cmd/gf/v2/internal/utility/utils"
"github.com/gogf/gf/v2/os/gfile"
"github.com/gogf/gf/v2/text/gregex"
"github.com/gogf/gf/v2/text/gstr"
)
func (c CGenCtrl) getApiItemsInSrc(apiModuleFolderPath string) (items []apiItem, err error) {
var importPath string
// The second level folders: versions.
apiVersionFolderPaths, err := gfile.ScanDir(apiModuleFolderPath, "*", false)
if err != nil {
return nil, err
}
for _, apiVersionFolderPath := range apiVersionFolderPaths {
if !gfile.IsDir(apiVersionFolderPath) {
continue
}
// The second level folders: versions.
apiFileFolderPaths, err := gfile.ScanDir(apiVersionFolderPath, "*.go", false)
if err != nil {
return nil, err
}
importPath = utils.GetImportPath(apiVersionFolderPath)
for _, apiFileFolderPath := range apiFileFolderPaths {
if gfile.IsDir(apiFileFolderPath) {
continue
}
structsInfo, err := c.getStructsNameInSrc(apiFileFolderPath)
if err != nil {
return nil, err
}
for _, methodName := range structsInfo {
// remove end "Req"
methodName = gstr.TrimRightStr(methodName, "Req", 1)
item := apiItem{
Import: gstr.Trim(importPath, `"`),
FileName: gfile.Name(apiFileFolderPath),
Module: gfile.Basename(apiModuleFolderPath),
Version: gfile.Basename(apiVersionFolderPath),
MethodName: methodName,
}
items = append(items, item)
}
}
}
return
}
func (c CGenCtrl) getApiItemsInDst(dstFolder string) (items []apiItem, err error) {
if !gfile.Exists(dstFolder) {
return nil, nil
}
type importItem struct {
Path string
Alias string
}
filePaths, err := gfile.ScanDir(dstFolder, "*.go", true)
if err != nil {
return nil, err
}
for _, filePath := range filePaths {
var (
array []string
importItems []importItem
importLines []string
module = gfile.Basename(gfile.Dir(filePath))
)
importLines, err = c.getImportsInDst(filePath)
if err != nil {
return nil, err
}
// retrieve all imports.
for _, importLine := range importLines {
array = gstr.SplitAndTrim(importLine, " ")
if len(array) == 2 {
importItems = append(importItems, importItem{
Path: gstr.Trim(array[1], `"`),
Alias: array[0],
})
} else {
importItems = append(importItems, importItem{
Path: gstr.Trim(array[0], `"`),
})
}
}
// retrieve all api usages.
// retrieve it without using AST, but use regular expressions to retrieve.
// It's because the api definition is simple and regular.
// Use regular expressions to get better performance.
fileContent := gfile.GetContents(filePath)
matches, err := gregex.MatchAllString(PatternCtrlDefinition, fileContent)
if err != nil {
return nil, err
}
for _, match := range matches {
// try to find the import path of the api.
var (
importPath string
version = match[1]
methodName = match[2] // not the function name, but the method name in api definition.
)
for _, item := range importItems {
if item.Alias != "" {
if item.Alias == version {
importPath = item.Path
break
}
continue
}
if gfile.Basename(item.Path) == version {
importPath = item.Path
break
}
}
item := apiItem{
Import: gstr.Trim(importPath, `"`),
Module: module,
Version: gfile.Basename(importPath),
MethodName: methodName,
}
items = append(items, item)
}
}
return
}

View File

@ -1,227 +0,0 @@
// Copyright GoFrame gf Author(https://goframe.org). All Rights Reserved.
//
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with this file,
// You can obtain one at https://github.com/gogf/gf.
package genctrl
import (
"fmt"
"path/filepath"
"strings"
"github.com/gogf/gf/cmd/gf/v2/internal/consts"
"github.com/gogf/gf/cmd/gf/v2/internal/utility/mlog"
"github.com/gogf/gf/v2/container/gset"
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/os/gfile"
"github.com/gogf/gf/v2/text/gstr"
)
type controllerGenerator struct{}
func newControllerGenerator() *controllerGenerator {
return &controllerGenerator{}
}
func (c *controllerGenerator) Generate(dstModuleFolderPath string, apiModuleApiItems []apiItem, merge bool) (err error) {
var (
doneApiItemSet = gset.NewStrSet()
)
for _, item := range apiModuleApiItems {
if doneApiItemSet.Contains(item.String()) {
continue
}
// retrieve all api items of the same module.
var (
subItems = c.getSubItemsByModuleAndVersion(apiModuleApiItems, item.Module, item.Version)
importPath = gstr.Replace(gfile.Dir(item.Import), "\\", "/", -1)
)
if err = c.doGenerateCtrlNewByModuleAndVersion(
dstModuleFolderPath, item.Module, item.Version, importPath,
); err != nil {
return
}
// use -merge
if merge {
err = c.doGenerateCtrlMergeItem(dstModuleFolderPath, subItems, doneApiItemSet)
continue
}
for _, subItem := range subItems {
err = c.doGenerateCtrlItem(dstModuleFolderPath, subItem)
if err != nil {
return
}
doneApiItemSet.Add(subItem.String())
}
}
return
}
func (c *controllerGenerator) getSubItemsByModuleAndVersion(items []apiItem, module, version string) (subItems []apiItem) {
for _, item := range items {
if item.Module == module && item.Version == version {
subItems = append(subItems, item)
}
}
return
}
func (c *controllerGenerator) doGenerateCtrlNewByModuleAndVersion(
dstModuleFolderPath, module, version, importPath string,
) (err error) {
var (
moduleFilePath = filepath.FromSlash(gfile.Join(dstModuleFolderPath, module+".go"))
moduleFilePathNew = filepath.FromSlash(gfile.Join(dstModuleFolderPath, module+"_new.go"))
ctrlName = fmt.Sprintf(`Controller%s`, gstr.UcFirst(version))
interfaceName = fmt.Sprintf(`%s.I%s%s`, module, gstr.CaseCamel(module), gstr.UcFirst(version))
newFuncName = fmt.Sprintf(`New%s`, gstr.UcFirst(version))
newFuncNameDefinition = fmt.Sprintf(`func %s()`, newFuncName)
alreadyCreated bool
)
if !gfile.Exists(moduleFilePath) {
content := gstr.ReplaceByMap(consts.TemplateGenCtrlControllerEmpty, g.MapStrStr{
"{Module}": module,
})
if err = gfile.PutContents(moduleFilePath, gstr.TrimLeft(content)); err != nil {
return err
}
mlog.Printf(`generated: %s`, gfile.RealPath(moduleFilePath))
}
if !gfile.Exists(moduleFilePathNew) {
content := gstr.ReplaceByMap(consts.TemplateGenCtrlControllerNewEmpty, g.MapStrStr{
"{Module}": module,
"{ImportPath}": fmt.Sprintf(`"%s"`, importPath),
})
if err = gfile.PutContents(moduleFilePathNew, gstr.TrimLeft(content)); err != nil {
return err
}
mlog.Printf(`generated: %s`, gfile.RealPath(moduleFilePathNew))
}
filePaths, err := gfile.ScanDir(dstModuleFolderPath, "*.go", false)
if err != nil {
return err
}
for _, filePath := range filePaths {
if gstr.Contains(gfile.GetContents(filePath), newFuncNameDefinition) {
alreadyCreated = true
break
}
}
if !alreadyCreated {
content := gstr.ReplaceByMap(consts.TemplateGenCtrlControllerNewFunc, g.MapStrStr{
"{CtrlName}": ctrlName,
"{NewFuncName}": newFuncName,
"{InterfaceName}": interfaceName,
})
err = gfile.PutContentsAppend(moduleFilePathNew, content)
if err != nil {
return err
}
}
return
}
func (c *controllerGenerator) doGenerateCtrlItem(dstModuleFolderPath string, item apiItem) (err error) {
var (
methodNameSnake = gstr.CaseSnake(item.MethodName)
ctrlName = fmt.Sprintf(`Controller%s`, gstr.UcFirst(item.Version))
methodFilePath = filepath.FromSlash(gfile.Join(dstModuleFolderPath, fmt.Sprintf(
`%s_%s_%s.go`, item.Module, item.Version, methodNameSnake,
)))
)
var content string
if gfile.Exists(methodFilePath) {
content = gstr.ReplaceByMap(consts.TemplateGenCtrlControllerMethodFuncMerge, g.MapStrStr{
"{Module}": item.Module,
"{CtrlName}": ctrlName,
"{Version}": item.Version,
"{MethodName}": item.MethodName,
})
if gstr.Contains(gfile.GetContents(methodFilePath), fmt.Sprintf(`func (c *%v) %v(`, ctrlName, item.MethodName)) {
return
}
if err = gfile.PutContentsAppend(methodFilePath, gstr.TrimLeft(content)); err != nil {
return err
}
} else {
content = gstr.ReplaceByMap(consts.TemplateGenCtrlControllerMethodFunc, g.MapStrStr{
"{Module}": item.Module,
"{ImportPath}": item.Import,
"{CtrlName}": ctrlName,
"{Version}": item.Version,
"{MethodName}": item.MethodName,
})
if err = gfile.PutContents(methodFilePath, gstr.TrimLeft(content)); err != nil {
return err
}
}
mlog.Printf(`generated: %s`, gfile.RealPath(methodFilePath))
return
}
// use -merge
func (c *controllerGenerator) doGenerateCtrlMergeItem(dstModuleFolderPath string, apiItems []apiItem, doneApiSet *gset.StrSet) (err error) {
type controllerFileItem struct {
module string
version string
importPath string
// Each ctrlFileItem has multiple CTRLs
controllers strings.Builder
}
// It is possible that there are multiple files under one module
ctrlFileItemMap := make(map[string]*controllerFileItem)
for _, api := range apiItems {
ctrlFileItem, found := ctrlFileItemMap[api.FileName]
if !found {
ctrlFileItem = &controllerFileItem{
module: api.Module,
version: api.Version,
controllers: strings.Builder{},
importPath: api.Import,
}
ctrlFileItemMap[api.FileName] = ctrlFileItem
}
ctrl := gstr.TrimLeft(gstr.ReplaceByMap(consts.TemplateGenCtrlControllerMethodFuncMerge, g.MapStrStr{
"{Module}": api.Module,
"{CtrlName}": fmt.Sprintf(`Controller%s`, gstr.UcFirst(api.Version)),
"{Version}": api.Version,
"{MethodName}": api.MethodName,
}))
ctrlFileItem.controllers.WriteString(ctrl)
doneApiSet.Add(api.String())
}
for ctrlFileName, ctrlFileItem := range ctrlFileItemMap {
ctrlFilePath := gfile.Join(dstModuleFolderPath, fmt.Sprintf(
`%s_%s_%s.go`, ctrlFileItem.module, ctrlFileItem.version, ctrlFileName,
))
// This logic is only followed when a new ctrlFileItem is generated
// Most of the rest of the time, the following logic is followed
if !gfile.Exists(ctrlFilePath) {
ctrlFileHeader := gstr.TrimLeft(gstr.ReplaceByMap(consts.TemplateGenCtrlControllerHeader, g.MapStrStr{
"{Module}": ctrlFileItem.module,
"{ImportPath}": ctrlFileItem.importPath,
}))
err = gfile.PutContents(ctrlFilePath, ctrlFileHeader)
if err != nil {
return err
}
}
if err = gfile.PutContentsAppend(ctrlFilePath, ctrlFileItem.controllers.String()); err != nil {
return err
}
mlog.Printf(`generated: %s`, gfile.RealPath(ctrlFilePath))
}
return
}

View File

@ -1,56 +0,0 @@
// Copyright GoFrame gf Author(https://goframe.org). All Rights Reserved.
//
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with this file,
// You can obtain one at https://github.com/gogf/gf.
package genctrl
import (
"fmt"
"github.com/gogf/gf/cmd/gf/v2/internal/utility/mlog"
"github.com/gogf/gf/v2/os/gfile"
"github.com/gogf/gf/v2/text/gstr"
)
type controllerClearer struct{}
func newControllerClearer() *controllerClearer {
return &controllerClearer{}
}
func (c *controllerClearer) Clear(dstModuleFolderPath string, extraApiItemsInCtrl []apiItem) (err error) {
for _, item := range extraApiItemsInCtrl {
if err = c.doClear(dstModuleFolderPath, item); err != nil {
return err
}
}
return
}
func (c *controllerClearer) doClear(dstModuleFolderPath string, item apiItem) (err error) {
var (
methodNameSnake = gstr.CaseSnake(item.MethodName)
methodFilePath = gfile.Join(dstModuleFolderPath, fmt.Sprintf(
`%s_%s_%s.go`, item.Module, item.Version, methodNameSnake,
))
)
funcs, err := c.getFuncInDst(methodFilePath)
if err != nil {
return err
}
if len(funcs) > 1 {
// One line.
if !gstr.Contains(funcs[0], "\n") && gstr.Contains(funcs[0], `CodeNotImplemented`) {
mlog.Printf(
`remove unimplemented and of no api definitions controller file: %s`,
methodFilePath,
)
err = gfile.RemoveFile(methodFilePath)
}
}
return
}

View File

@ -1,117 +0,0 @@
// Copyright GoFrame gf Author(https://goframe.org). All Rights Reserved.
//
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with this file,
// You can obtain one at https://github.com/gogf/gf.
package genctrl
import (
"fmt"
"path/filepath"
"github.com/gogf/gf/cmd/gf/v2/internal/consts"
"github.com/gogf/gf/cmd/gf/v2/internal/utility/mlog"
"github.com/gogf/gf/cmd/gf/v2/internal/utility/utils"
"github.com/gogf/gf/v2/container/gmap"
"github.com/gogf/gf/v2/container/gset"
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/os/gfile"
"github.com/gogf/gf/v2/text/gstr"
"github.com/gogf/gf/v2/util/gconv"
)
type apiInterfaceGenerator struct{}
func newApiInterfaceGenerator() *apiInterfaceGenerator {
return &apiInterfaceGenerator{}
}
func (c *apiInterfaceGenerator) Generate(apiModuleFolderPath string, apiModuleApiItems []apiItem) (err error) {
if len(apiModuleApiItems) == 0 {
return nil
}
var firstApiItem = apiModuleApiItems[0]
if err = c.doGenerate(apiModuleFolderPath, firstApiItem.Module, apiModuleApiItems); err != nil {
return
}
return
}
func (c *apiInterfaceGenerator) doGenerate(apiModuleFolderPath string, module string, items []apiItem) (err error) {
var (
moduleFilePath = filepath.FromSlash(gfile.Join(apiModuleFolderPath, fmt.Sprintf(`%s.go`, module)))
importPathMap = gmap.NewListMap()
importPaths []string
)
// if there's already exist file that with the same but not auto generated go file,
// it uses another file name.
if !utils.IsFileDoNotEdit(moduleFilePath) {
moduleFilePath = filepath.FromSlash(gfile.Join(apiModuleFolderPath, fmt.Sprintf(`%s.if.go`, module)))
}
// all import paths.
importPathMap.Set("\t"+`"context"`+"\n", 1)
for _, item := range items {
importPathMap.Set(fmt.Sprintf("\t"+`"%s"`, item.Import), 1)
}
importPaths = gconv.Strings(importPathMap.Keys())
// interface definitions.
var (
doneApiItemSet = gset.NewStrSet()
interfaceDefinition string
interfaceContent = gstr.TrimLeft(gstr.ReplaceByMap(consts.TemplateGenCtrlApiInterface, g.MapStrStr{
"{Module}": module,
"{ImportPaths}": gstr.Join(importPaths, "\n"),
}))
)
for _, item := range items {
if doneApiItemSet.Contains(item.String()) {
continue
}
// retrieve all api items of the same module.
subItems := c.getSubItemsByModuleAndVersion(items, item.Module, item.Version)
var (
method string
methods = make([]string, 0)
interfaceName = fmt.Sprintf(`I%s%s`, gstr.CaseCamel(item.Module), gstr.UcFirst(item.Version))
)
for _, subItem := range subItems {
method = fmt.Sprintf(
"\t%s(ctx context.Context, req *%s.%sReq) (res *%s.%sRes, err error)",
subItem.MethodName, subItem.Version, subItem.MethodName, subItem.Version, subItem.MethodName,
)
methods = append(methods, method)
doneApiItemSet.Add(subItem.String())
}
interfaceDefinition += fmt.Sprintf("type %s interface {", interfaceName)
interfaceDefinition += "\n"
interfaceDefinition += gstr.Join(methods, "\n")
interfaceDefinition += "\n"
interfaceDefinition += fmt.Sprintf("}")
interfaceDefinition += "\n\n"
}
interfaceContent = gstr.TrimLeft(gstr.ReplaceByMap(interfaceContent, g.MapStrStr{
"{Interfaces}": gstr.TrimRightStr(interfaceDefinition, "\n", 2),
}))
err = gfile.PutContents(moduleFilePath, interfaceContent)
mlog.Printf(`generated: %s`, gfile.RealPath(moduleFilePath))
return
}
func (c *apiInterfaceGenerator) getSubItemsByModule(items []apiItem, module string) (subItems []apiItem) {
for _, item := range items {
if item.Module == module {
subItems = append(subItems, item)
}
}
return
}
func (c *apiInterfaceGenerator) getSubItemsByModuleAndVersion(items []apiItem, module, version string) (subItems []apiItem) {
for _, item := range items {
if item.Module == module && item.Version == version {
subItems = append(subItems, item)
}
}
return
}

View File

@ -1,197 +0,0 @@
// Copyright GoFrame gf Author(https://goframe.org). All Rights Reserved.
//
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with this file,
// You can obtain one at https://github.com/gogf/gf.
package genctrl
import (
"fmt"
"path/filepath"
"github.com/gogf/gf/cmd/gf/v2/internal/consts"
"github.com/gogf/gf/cmd/gf/v2/internal/utility/mlog"
"github.com/gogf/gf/v2/container/gset"
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/os/gfile"
"github.com/gogf/gf/v2/text/gregex"
"github.com/gogf/gf/v2/text/gstr"
)
type apiSdkGenerator struct{}
func newApiSdkGenerator() *apiSdkGenerator {
return &apiSdkGenerator{}
}
func (c *apiSdkGenerator) Generate(apiModuleApiItems []apiItem, sdkFolderPath string, sdkStdVersion, sdkNoV1 bool) (err error) {
if err = c.doGenerateSdkPkgFile(sdkFolderPath); err != nil {
return
}
var doneApiItemSet = gset.NewStrSet()
for _, item := range apiModuleApiItems {
if doneApiItemSet.Contains(item.String()) {
continue
}
// retrieve all api items of the same module.
subItems := c.getSubItemsByModuleAndVersion(apiModuleApiItems, item.Module, item.Version)
if err = c.doGenerateSdkIClient(sdkFolderPath, item.Import, item.Module, item.Version, sdkNoV1); err != nil {
return
}
if err = c.doGenerateSdkImplementer(
subItems, sdkFolderPath, item.Import, item.Module, item.Version, sdkStdVersion, sdkNoV1,
); err != nil {
return
}
for _, subItem := range subItems {
doneApiItemSet.Add(subItem.String())
}
}
return
}
func (c *apiSdkGenerator) doGenerateSdkPkgFile(sdkFolderPath string) (err error) {
var (
pkgName = gfile.Basename(sdkFolderPath)
pkgFilePath = filepath.FromSlash(gfile.Join(sdkFolderPath, fmt.Sprintf(`%s.go`, pkgName)))
fileContent string
)
if gfile.Exists(pkgFilePath) {
return nil
}
fileContent = gstr.TrimLeft(gstr.ReplaceByMap(consts.TemplateGenCtrlSdkPkgNew, g.MapStrStr{
"{PkgName}": pkgName,
}))
err = gfile.PutContents(pkgFilePath, fileContent)
mlog.Printf(`generated: %s`, gfile.RealPath(pkgFilePath))
return
}
func (c *apiSdkGenerator) doGenerateSdkIClient(
sdkFolderPath, versionImportPath, module, version string, sdkNoV1 bool,
) (err error) {
var (
fileContent string
isDirty bool
isExist bool
pkgName = gfile.Basename(sdkFolderPath)
funcName = gstr.CaseCamel(module) + gstr.UcFirst(version)
interfaceName = fmt.Sprintf(`I%s`, funcName)
moduleImportPath = gstr.Replace(fmt.Sprintf(`"%s"`, gfile.Dir(versionImportPath)), "\\", "/", -1)
iClientFilePath = filepath.FromSlash(gfile.Join(sdkFolderPath, fmt.Sprintf(`%s.iclient.go`, pkgName)))
interfaceFuncDefinition = fmt.Sprintf(
`%s() %s.%s`,
gstr.CaseCamel(module)+gstr.UcFirst(version), module, interfaceName,
)
)
if sdkNoV1 && version == "v1" {
interfaceFuncDefinition = fmt.Sprintf(
`%s() %s.%s`,
gstr.CaseCamel(module), module, interfaceName,
)
}
if isExist = gfile.Exists(iClientFilePath); isExist {
fileContent = gfile.GetContents(iClientFilePath)
} else {
fileContent = gstr.TrimLeft(gstr.ReplaceByMap(consts.TemplateGenCtrlSdkIClient, g.MapStrStr{
"{PkgName}": pkgName,
}))
}
// append the import path to current import paths.
if !gstr.Contains(fileContent, moduleImportPath) {
isDirty = true
// It is without using AST, because it is from a template.
fileContent, err = gregex.ReplaceString(
`(import \([\s\S]*?)\)`,
fmt.Sprintf("$1\t%s\n)", moduleImportPath),
fileContent,
)
if err != nil {
return
}
}
// append the function definition to interface definition.
if !gstr.Contains(fileContent, interfaceFuncDefinition) {
isDirty = true
// It is without using AST, because it is from a template.
fileContent, err = gregex.ReplaceString(
`(type IClient interface {[\s\S]*?)}`,
fmt.Sprintf("$1\t%s\n}", interfaceFuncDefinition),
fileContent,
)
if err != nil {
return
}
}
if isDirty {
err = gfile.PutContents(iClientFilePath, fileContent)
if isExist {
mlog.Printf(`updated: %s`, gfile.RealPath(iClientFilePath))
} else {
mlog.Printf(`generated: %s`, gfile.RealPath(iClientFilePath))
}
}
return
}
func (c *apiSdkGenerator) doGenerateSdkImplementer(
items []apiItem, sdkFolderPath, versionImportPath, module, version string, sdkStdVersion, sdkNoV1 bool,
) (err error) {
var (
pkgName = gfile.Basename(sdkFolderPath)
moduleNameCamel = gstr.CaseCamel(module)
moduleNameSnake = gstr.CaseSnake(module)
moduleImportPath = gstr.Replace(gfile.Dir(versionImportPath), "\\", "/", -1)
versionPrefix = ""
implementerName = moduleNameCamel + gstr.UcFirst(version)
implementerFilePath = filepath.FromSlash(gfile.Join(sdkFolderPath, fmt.Sprintf(
`%s_%s_%s.go`, pkgName, moduleNameSnake, version,
)))
)
if sdkNoV1 && version == "v1" {
implementerName = moduleNameCamel
}
// implementer file template.
var importPaths = make([]string, 0)
importPaths = append(importPaths, fmt.Sprintf("\t\"%s\"", moduleImportPath))
importPaths = append(importPaths, fmt.Sprintf("\t\"%s\"", versionImportPath))
implementerFileContent := gstr.TrimLeft(gstr.ReplaceByMap(consts.TemplateGenCtrlSdkImplementer, g.MapStrStr{
"{PkgName}": pkgName,
"{ImportPaths}": gstr.Join(importPaths, "\n"),
"{ImplementerName}": implementerName,
}))
// implementer new function definition.
if sdkStdVersion {
versionPrefix = fmt.Sprintf(`/api/%s`, version)
}
implementerFileContent += gstr.TrimLeft(gstr.ReplaceByMap(consts.TemplateGenCtrlSdkImplementerNew, g.MapStrStr{
"{Module}": module,
"{VersionPrefix}": versionPrefix,
"{ImplementerName}": implementerName,
}))
// implementer functions definitions.
for _, item := range items {
implementerFileContent += gstr.TrimLeft(gstr.ReplaceByMap(consts.TemplateGenCtrlSdkImplementerFunc, g.MapStrStr{
"{Version}": item.Version,
"{MethodName}": item.MethodName,
"{ImplementerName}": implementerName,
}))
implementerFileContent += "\n"
}
err = gfile.PutContents(implementerFilePath, implementerFileContent)
mlog.Printf(`generated: %s`, gfile.RealPath(implementerFilePath))
return
}
func (c *apiSdkGenerator) getSubItemsByModuleAndVersion(items []apiItem, module, version string) (subItems []apiItem) {
for _, item := range items {
if item.Module == module && item.Version == version {
subItems = append(subItems, item)
}
}
return
}

View File

@ -1,9 +1,3 @@
// Copyright GoFrame gf Author(https://goframe.org). All Rights Reserved.
//
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with this file,
// You can obtain one at https://github.com/gogf/gf.
package gendao
import (
@ -11,15 +5,12 @@ import (
"fmt"
"strings"
"golang.org/x/mod/modfile"
"github.com/gogf/gf/cmd/gf/v2/internal/utility/utils"
"github.com/gogf/gf/v2/container/garray"
"github.com/gogf/gf/v2/database/gdb"
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/os/gfile"
"github.com/gogf/gf/v2/os/gproc"
"github.com/gogf/gf/v2/os/gtime"
"github.com/gogf/gf/v2/text/gregex"
"github.com/gogf/gf/v2/text/gstr"
"github.com/gogf/gf/v2/util/gtag"
@ -52,41 +43,28 @@ CONFIGURATION SUPPORT
path: "./my-app"
prefix: "primary_"
tables: "user, userDetail"
typeMapping:
decimal:
type: decimal.Decimal
import: github.com/shopspring/decimal
numeric:
type: string
fieldMapping:
table_name.field_name:
type: decimal.Decimal
import: github.com/shopspring/decimal
`
CGenDaoBriefPath = `directory path for generated files`
CGenDaoBriefLink = `database configuration, the same as the ORM configuration of GoFrame`
CGenDaoBriefTables = `generate models only for given tables, multiple table names separated with ','`
CGenDaoBriefTablesEx = `generate models excluding given tables, multiple table names separated with ','`
CGenDaoBriefPrefix = `add prefix for all table of specified link/database tables`
CGenDaoBriefRemovePrefix = `remove specified prefix of the table, multiple prefix separated with ','`
CGenDaoBriefRemoveFieldPrefix = `remove specified prefix of the field, multiple prefix separated with ','`
CGenDaoBriefStdTime = `use time.Time from stdlib instead of gtime.Time for generated time/date fields of tables`
CGenDaoBriefWithTime = `add created time for auto produced go files`
CGenDaoBriefGJsonSupport = `use gJsonSupport to use *gjson.Json instead of string for generated json fields of tables`
CGenDaoBriefImportPrefix = `custom import prefix for generated go files`
CGenDaoBriefDaoPath = `directory path for storing generated dao files under path`
CGenDaoBriefDoPath = `directory path for storing generated do files under path`
CGenDaoBriefEntityPath = `directory path for storing generated entity files under path`
CGenDaoBriefOverwriteDao = `overwrite all dao files both inside/outside internal folder`
CGenDaoBriefModelFile = `custom file name for storing generated model content`
CGenDaoBriefModelFileForDao = `custom file name generating model for DAO operations like Where/Data. It's empty in default`
CGenDaoBriefDescriptionTag = `add comment to description tag for each field`
CGenDaoBriefNoJsonTag = `no json tag will be added for each field`
CGenDaoBriefNoModelComment = `no model comment will be added for each field`
CGenDaoBriefClear = `delete all generated go files that do not exist in database`
CGenDaoBriefTypeMapping = `custom local type mapping for generated struct attributes relevant to fields of table`
CGenDaoBriefFieldMapping = `custom local type mapping for generated struct attributes relevant to specific fields of table`
CGenDaoBriefGroup = `
CGenDaoBriefPath = `directory path for generated files`
CGenDaoBriefLink = `database configuration, the same as the ORM configuration of GoFrame`
CGenDaoBriefTables = `generate models only for given tables, multiple table names separated with ','`
CGenDaoBriefTablesEx = `generate models excluding given tables, multiple table names separated with ','`
CGenDaoBriefPrefix = `add prefix for all table of specified link/database tables`
CGenDaoBriefRemovePrefix = `remove specified prefix of the table, multiple prefix separated with ','`
CGenDaoBriefStdTime = `use time.Time from stdlib instead of gtime.Time for generated time/date fields of tables`
CGenDaoBriefWithTime = `add created time for auto produced go files`
CGenDaoBriefGJsonSupport = `use gJsonSupport to use *gjson.Json instead of string for generated json fields of tables`
CGenDaoBriefImportPrefix = `custom import prefix for generated go files`
CGenDaoBriefDaoPath = `directory path for storing generated dao files under path`
CGenDaoBriefDoPath = `directory path for storing generated do files under path`
CGenDaoBriefEntityPath = `directory path for storing generated entity files under path`
CGenDaoBriefOverwriteDao = `overwrite all dao files both inside/outside internal folder`
CGenDaoBriefModelFile = `custom file name for storing generated model content`
CGenDaoBriefModelFileForDao = `custom file name generating model for DAO operations like Where/Data. It's empty in default`
CGenDaoBriefDescriptionTag = `add comment to description tag for each field`
CGenDaoBriefNoJsonTag = `no json tag will be added for each field`
CGenDaoBriefNoModelComment = `no model comment will be added for each field`
CGenDaoBriefClear = `delete all generated go files that do not exist in database`
CGenDaoBriefGroup = `
specifying the configuration group name of database for generated ORM instance,
it's not necessary and the default value is "default"
`
@ -118,25 +96,10 @@ generated json tag case for model struct, cases are as follows:
tplVarGroupName = `{TplGroupName}`
tplVarDatetimeStr = `{TplDatetimeStr}`
tplVarCreatedAtDatetimeStr = `{TplCreatedAtDatetimeStr}`
tplVarPackageName = `{TplPackageName}`
)
var (
createdAt = gtime.Now()
defaultTypeMapping = map[DBFieldTypeName]CustomAttributeType{
"decimal": {
Type: "float64",
},
"money": {
Type: "float64",
},
"numeric": {
Type: "float64",
},
"smallmoney": {
Type: "float64",
},
}
createdAt = gtime.Now()
)
func init() {
@ -152,7 +115,6 @@ func init() {
`CGenDaoBriefTablesEx`: CGenDaoBriefTablesEx,
`CGenDaoBriefPrefix`: CGenDaoBriefPrefix,
`CGenDaoBriefRemovePrefix`: CGenDaoBriefRemovePrefix,
`CGenDaoBriefRemoveFieldPrefix`: CGenDaoBriefRemoveFieldPrefix,
`CGenDaoBriefStdTime`: CGenDaoBriefStdTime,
`CGenDaoBriefWithTime`: CGenDaoBriefWithTime,
`CGenDaoBriefDaoPath`: CGenDaoBriefDaoPath,
@ -167,8 +129,6 @@ func init() {
`CGenDaoBriefNoJsonTag`: CGenDaoBriefNoJsonTag,
`CGenDaoBriefNoModelComment`: CGenDaoBriefNoModelComment,
`CGenDaoBriefClear`: CGenDaoBriefClear,
`CGenDaoBriefTypeMapping`: CGenDaoBriefTypeMapping,
`CGenDaoBriefFieldMapping`: CGenDaoBriefFieldMapping,
`CGenDaoBriefGroup`: CGenDaoBriefGroup,
`CGenDaoBriefJsonCase`: CGenDaoBriefJsonCase,
`CGenDaoBriefTplDaoIndexPath`: CGenDaoBriefTplDaoIndexPath,
@ -189,7 +149,6 @@ type (
Group string `name:"group" short:"g" brief:"{CGenDaoBriefGroup}" d:"default"`
Prefix string `name:"prefix" short:"f" brief:"{CGenDaoBriefPrefix}"`
RemovePrefix string `name:"removePrefix" short:"r" brief:"{CGenDaoBriefRemovePrefix}"`
RemoveFieldPrefix string `name:"removeFieldPrefix" short:"rf" brief:"{CGenDaoBriefRemoveFieldPrefix}"`
JsonCase string `name:"jsonCase" short:"j" brief:"{CGenDaoBriefJsonCase}" d:"CamelLower"`
ImportPrefix string `name:"importPrefix" short:"i" brief:"{CGenDaoBriefImportPrefix}"`
DaoPath string `name:"daoPath" short:"d" brief:"{CGenDaoBriefDaoPath}" d:"dao"`
@ -207,12 +166,6 @@ type (
NoJsonTag bool `name:"noJsonTag" short:"k" brief:"{CGenDaoBriefNoJsonTag}" orphan:"true"`
NoModelComment bool `name:"noModelComment" short:"m" brief:"{CGenDaoBriefNoModelComment}" orphan:"true"`
Clear bool `name:"clear" short:"a" brief:"{CGenDaoBriefClear}" orphan:"true"`
TypeMapping map[DBFieldTypeName]CustomAttributeType `name:"typeMapping" short:"y" brief:"{CGenDaoBriefTypeMapping}" orphan:"true"`
FieldMapping map[DBTableFieldName]CustomAttributeType `name:"fieldMapping" short:"fm" brief:"{CGenDaoBriefFieldMapping}" orphan:"true"`
// internal usage purpose.
genItems *CGenDaoInternalGenItems
}
CGenDaoOutput struct{}
@ -221,20 +174,12 @@ type (
DB gdb.DB
TableNames []string
NewTableNames []string
}
DBTableFieldName = string
DBFieldTypeName = string
CustomAttributeType struct {
Type string `brief:"custom attribute type name"`
Import string `brief:"custom import for this type"`
ModName string // Module name of current golang project, which is used for import purpose.
}
)
func (c CGenDao) Dao(ctx context.Context, in CGenDaoInput) (out *CGenDaoOutput, err error) {
in.genItems = newCGenDaoInternalGenItems()
if in.Link != "" {
doGenDaoForArray(ctx, -1, in)
} else if g.Cfg().Available(ctx) {
if g.Cfg().Available(ctx) {
v := g.Cfg().MustGet(ctx, CGenDaoConfig)
if v.IsSlice() {
for i := 0; i < len(v.Interfaces()); i++ {
@ -246,7 +191,6 @@ func (c CGenDao) Dao(ctx context.Context, in CGenDaoInput) (out *CGenDaoOutput,
} else {
doGenDaoForArray(ctx, -1, in)
}
doClear(in.genItems)
mlog.Print("done!")
return
}
@ -254,8 +198,9 @@ func (c CGenDao) Dao(ctx context.Context, in CGenDaoInput) (out *CGenDaoOutput,
// doGenDaoForArray implements the "gen dao" command for configuration array.
func doGenDaoForArray(ctx context.Context, index int, in CGenDaoInput) {
var (
err error
db gdb.DB
err error
db gdb.DB
modName string // Go module name, eg: github.com/gogf/gf.
)
if index >= 0 {
err = g.Cfg().MustGet(
@ -270,6 +215,20 @@ func doGenDaoForArray(ctx context.Context, index int, in CGenDaoInput) {
mlog.Fatalf(`path "%s" does not exist`, in.Path)
}
removePrefixArray := gstr.SplitAndTrim(in.RemovePrefix, ",")
if in.ImportPrefix == "" {
if !gfile.Exists("go.mod") {
mlog.Fatal("go.mod does not exist in current working directory")
}
var (
goModContent = gfile.GetContents("go.mod")
match, _ = gregex.MatchString(`^module\s+(.+)\s*`, goModContent)
)
if len(match) > 1 {
modName = gstr.Trim(match[1])
} else {
mlog.Fatal("module name does not found in go.mod")
}
}
// It uses user passed database configuration.
if in.Link != "" {
@ -305,17 +264,6 @@ func doGenDaoForArray(ctx context.Context, index int, in CGenDaoInput) {
tableNames = array.Slice()
}
// merge default typeMapping to input typeMapping.
if in.TypeMapping == nil {
in.TypeMapping = defaultTypeMapping
} else {
for key, typeMapping := range defaultTypeMapping {
if _, ok := in.TypeMapping[key]; !ok {
in.TypeMapping[key] = typeMapping
}
}
}
// Generating dao & model go files one by one according to given table name.
newTableNames := make([]string, len(tableNames))
for i, tableName := range tableNames {
@ -326,15 +274,13 @@ func doGenDaoForArray(ctx context.Context, index int, in CGenDaoInput) {
newTableName = in.Prefix + newTableName
newTableNames[i] = newTableName
}
in.genItems.Scale()
// Dao: index and internal.
generateDao(ctx, CGenDaoInternalInput{
CGenDaoInput: in,
DB: db,
TableNames: tableNames,
NewTableNames: newTableNames,
ModName: modName,
})
// Do.
generateDo(ctx, CGenDaoInternalInput{
@ -342,6 +288,7 @@ func doGenDaoForArray(ctx context.Context, index int, in CGenDaoInput) {
DB: db,
TableNames: tableNames,
NewTableNames: newTableNames,
ModName: modName,
})
// Entity.
generateEntity(ctx, CGenDaoInternalInput{
@ -349,13 +296,15 @@ func doGenDaoForArray(ctx context.Context, index int, in CGenDaoInput) {
DB: db,
TableNames: tableNames,
NewTableNames: newTableNames,
ModName: modName,
})
in.genItems.SetClear(in.Clear)
}
func getImportPartContent(ctx context.Context, source string, isDo bool, appendImports []string) string {
var packageImportsArray = garray.NewStrArray()
func getImportPartContent(source string, isDo bool) string {
var (
packageImportsArray = garray.NewStrArray()
)
if isDo {
packageImportsArray.Append(`"github.com/gogf/gf/v2/frame/g"`)
}
@ -372,33 +321,6 @@ func getImportPartContent(ctx context.Context, source string, isDo bool, appendI
packageImportsArray.Append(`"github.com/gogf/gf/v2/encoding/gjson"`)
}
// Check and update imports in go.mod
if len(appendImports) > 0 {
goModPath := utils.GetModPath()
if goModPath == "" {
mlog.Fatal("go.mod not found in current project")
}
mod, err := modfile.Parse(goModPath, gfile.GetBytes(goModPath), nil)
if err != nil {
mlog.Fatalf("parse go.mod failed: %+v", err)
}
for _, appendImport := range appendImports {
found := false
for _, require := range mod.Require {
if gstr.Contains(appendImport, require.Mod.Path) {
found = true
break
}
}
if !found {
if err = gproc.ShellRun(ctx, `go get `+appendImport); err != nil {
mlog.Fatalf(`%+v`, err)
}
}
packageImportsArray.Append(fmt.Sprintf(`"%s"`, appendImport))
}
}
// Generate and write content to golang file.
packageImportsStr := ""
if packageImportsArray.Len() > 0 {

View File

@ -1,46 +1,22 @@
// Copyright GoFrame gf Author(https://goframe.org). All Rights Reserved.
//
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with this file,
// You can obtain one at https://github.com/gogf/gf.
package gendao
import (
"context"
"github.com/gogf/gf/v2/os/gfile"
"github.com/gogf/gf/v2/text/gstr"
"github.com/gogf/gf/cmd/gf/v2/internal/utility/mlog"
"github.com/gogf/gf/cmd/gf/v2/internal/utility/utils"
)
func doClear(items *CGenDaoInternalGenItems) {
var allGeneratedFilePaths = make([]string, 0)
for _, item := range items.Items {
allGeneratedFilePaths = append(allGeneratedFilePaths, item.GeneratedFilePaths...)
func doClear(ctx context.Context, dirPath string) {
files, err := gfile.ScanDirFile(dirPath, "*.go", true)
if err != nil {
mlog.Fatal(err)
}
for i, v := range allGeneratedFilePaths {
allGeneratedFilePaths[i] = gfile.RealPath(v)
}
for _, item := range items.Items {
if !item.Clear {
continue
}
doClearItem(item, allGeneratedFilePaths)
}
}
func doClearItem(item CGenDaoInternalGenItem, allGeneratedFilePaths []string) {
var generatedFilePaths = make([]string, 0)
for _, dirPath := range item.StorageDirPaths {
filePaths, err := gfile.ScanDirFile(dirPath, "*.go", true)
if err != nil {
mlog.Fatal(err)
}
generatedFilePaths = append(generatedFilePaths, filePaths...)
}
for _, filePath := range generatedFilePaths {
if !gstr.InArray(allGeneratedFilePaths, filePath) {
if err := gfile.RemoveFile(filePath); err != nil {
for _, file := range files {
if utils.IsFileDoNotEdit(file) {
if err = gfile.Remove(file); err != nil {
mlog.Print(err)
}
}

View File

@ -1,24 +1,17 @@
// Copyright GoFrame gf Author(https://goframe.org). All Rights Reserved.
//
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with this file,
// You can obtain one at https://github.com/gogf/gf.
package gendao
import (
"bytes"
"context"
"fmt"
"path/filepath"
"strings"
"github.com/olekukonko/tablewriter"
"github.com/gogf/gf/v2/database/gdb"
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/os/gfile"
"github.com/gogf/gf/v2/text/gregex"
"github.com/gogf/gf/v2/text/gstr"
"github.com/olekukonko/tablewriter"
"github.com/gogf/gf/cmd/gf/v2/internal/consts"
"github.com/gogf/gf/cmd/gf/v2/internal/utility/mlog"
@ -30,7 +23,9 @@ func generateDao(ctx context.Context, in CGenDaoInternalInput) {
dirPathDao = gfile.Join(in.Path, in.DaoPath)
dirPathDaoInternal = gfile.Join(dirPathDao, "internal")
)
in.genItems.AppendDirPath(dirPathDao)
if in.Clear {
doClear(ctx, dirPathDao)
}
for i := 0; i < len(in.TableNames); i++ {
generateDaoSingle(ctx, generateDaoSingleInput{
CGenDaoInternalInput: in,
@ -58,13 +53,23 @@ func generateDaoSingle(ctx context.Context, in generateDaoSingleInput) {
mlog.Fatalf(`fetching tables fields failed for table "%s": %+v`, in.TableName, err)
}
var (
tableNameCamelCase = formatFieldName(in.NewTableName, FieldNameCaseCamel)
tableNameCamelLowerCase = formatFieldName(in.NewTableName, FieldNameCaseCamelLower)
dirRealPath = gfile.RealPath(in.Path)
tableNameCamelCase = gstr.CaseCamel(in.NewTableName)
tableNameCamelLowerCase = gstr.CaseCamelLower(in.NewTableName)
tableNameSnakeCase = gstr.CaseSnake(in.NewTableName)
importPrefix = in.ImportPrefix
)
if importPrefix == "" {
importPrefix = utils.GetImportPath(gfile.Join(in.Path, in.DaoPath))
if dirRealPath == "" {
dirRealPath = in.Path
importPrefix = dirRealPath
importPrefix = gstr.Trim(dirRealPath, "./")
} else {
importPrefix = gstr.Replace(dirRealPath, gfile.Pwd(), "")
}
importPrefix = gstr.Replace(importPrefix, gfile.Separator, "/")
importPrefix = gstr.Join(g.SliceStr{in.ModName, importPrefix, in.DaoPath}, "/")
importPrefix, _ = gregex.ReplaceString(`\/{2,}`, `/`, gstr.Trim(importPrefix, "/"))
} else {
importPrefix = gstr.Join(g.SliceStr{importPrefix, in.DaoPath}, "/")
}
@ -105,9 +110,7 @@ type generateDaoIndexInput struct {
}
func generateDaoIndex(in generateDaoIndexInput) {
path := filepath.FromSlash(gfile.Join(in.DirPathDao, in.FileName+".go"))
// It should add path to result slice whenever it would generate the path file or not.
in.genItems.AppendGeneratedFilePath(path)
path := gfile.Join(in.DirPathDao, in.FileName+".go")
if in.OverwriteDao || !gfile.Exists(path) {
indexContent := gstr.ReplaceByMap(
getTemplateFromPathOrDefault(in.TplDaoIndexPath, consts.TemplateGenDaoIndexContent),
@ -116,14 +119,13 @@ func generateDaoIndex(in generateDaoIndexInput) {
tplVarTableName: in.TableName,
tplVarTableNameCamelCase: in.TableNameCamelCase,
tplVarTableNameCamelLowerCase: in.TableNameCamelLowerCase,
tplVarPackageName: filepath.Base(in.DaoPath),
})
indexContent = replaceDefaultVar(in.CGenDaoInternalInput, indexContent)
if err := gfile.PutContents(path, strings.TrimSpace(indexContent)); err != nil {
mlog.Fatalf("writing content to '%s' failed: %v", path, err)
} else {
utils.GoFmt(path)
mlog.Print("generated:", gfile.RealPath(path))
mlog.Print("generated:", path)
}
}
}
@ -138,8 +140,7 @@ type generateDaoInternalInput struct {
}
func generateDaoInternal(in generateDaoInternalInput) {
path := filepath.FromSlash(gfile.Join(in.DirPathDaoInternal, in.FileName+".go"))
removeFieldPrefixArray := gstr.SplitAndTrim(in.RemoveFieldPrefix, ",")
path := gfile.Join(in.DirPathDaoInternal, in.FileName+".go")
modelContent := gstr.ReplaceByMap(
getTemplateFromPathOrDefault(in.TplDaoInternalPath, consts.TemplateGenDaoInternalContent),
g.MapStrStr{
@ -148,38 +149,30 @@ func generateDaoInternal(in generateDaoInternalInput) {
tplVarGroupName: in.Group,
tplVarTableNameCamelCase: in.TableNameCamelCase,
tplVarTableNameCamelLowerCase: in.TableNameCamelLowerCase,
tplVarColumnDefine: gstr.Trim(generateColumnDefinitionForDao(in.FieldMap, removeFieldPrefixArray)),
tplVarColumnNames: gstr.Trim(generateColumnNamesForDao(in.FieldMap, removeFieldPrefixArray)),
tplVarColumnDefine: gstr.Trim(generateColumnDefinitionForDao(in.FieldMap)),
tplVarColumnNames: gstr.Trim(generateColumnNamesForDao(in.FieldMap)),
})
modelContent = replaceDefaultVar(in.CGenDaoInternalInput, modelContent)
in.genItems.AppendGeneratedFilePath(path)
if err := gfile.PutContents(path, strings.TrimSpace(modelContent)); err != nil {
mlog.Fatalf("writing content to '%s' failed: %v", path, err)
} else {
utils.GoFmt(path)
mlog.Print("generated:", gfile.RealPath(path))
mlog.Print("generated:", path)
}
}
// generateColumnNamesForDao generates and returns the column names assignment content of column struct
// for specified table.
func generateColumnNamesForDao(fieldMap map[string]*gdb.TableField, removeFieldPrefixArray []string) string {
func generateColumnNamesForDao(fieldMap map[string]*gdb.TableField) string {
var (
buffer = bytes.NewBuffer(nil)
array = make([][]string, len(fieldMap))
names = sortFieldKeyForDao(fieldMap)
)
for index, name := range names {
field := fieldMap[name]
newFiledName := field.Name
for _, v := range removeFieldPrefixArray {
newFiledName = gstr.TrimLeftStr(newFiledName, v, 1)
}
array[index] = []string{
" #" + formatFieldName(newFiledName, FieldNameCaseCamel) + ":",
" #" + gstr.CaseCamel(field.Name) + ":",
fmt.Sprintf(` #"%s",`, field.Name),
}
}
@ -199,13 +192,12 @@ func generateColumnNamesForDao(fieldMap map[string]*gdb.TableField, removeFieldP
}
// generateColumnDefinitionForDao generates and returns the column names definition for specified table.
func generateColumnDefinitionForDao(fieldMap map[string]*gdb.TableField, removeFieldPrefixArray []string) string {
func generateColumnDefinitionForDao(fieldMap map[string]*gdb.TableField) string {
var (
buffer = bytes.NewBuffer(nil)
array = make([][]string, len(fieldMap))
names = sortFieldKeyForDao(fieldMap)
)
for index, name := range names {
var (
field = fieldMap[name]
@ -214,12 +206,8 @@ func generateColumnDefinitionForDao(fieldMap map[string]*gdb.TableField, removeF
"\r", " ",
}))
)
newFiledName := field.Name
for _, v := range removeFieldPrefixArray {
newFiledName = gstr.TrimLeftStr(newFiledName, v, 1)
}
array[index] = []string{
" #" + formatFieldName(newFiledName, FieldNameCaseCamel),
" #" + gstr.CaseCamel(field.Name),
" # " + "string",
" #" + fmt.Sprintf(`// %s`, comment),
}

View File

@ -1,15 +1,8 @@
// Copyright GoFrame gf Author(https://goframe.org). All Rights Reserved.
//
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with this file,
// You can obtain one at https://github.com/gogf/gf.
package gendao
import (
"context"
"fmt"
"path/filepath"
"strings"
"github.com/gogf/gf/v2/frame/g"
@ -23,8 +16,10 @@ import (
)
func generateDo(ctx context.Context, in CGenDaoInternalInput) {
var dirPathDo = filepath.FromSlash(gfile.Join(in.Path, in.DoPath))
in.genItems.AppendDirPath(dirPathDo)
var dirPathDo = gfile.Join(in.Path, in.DoPath)
if in.Clear {
doClear(ctx, dirPathDo)
}
in.NoJsonTag = true
in.DescriptionTag = false
in.NoModelComment = false
@ -35,12 +30,12 @@ func generateDo(ctx context.Context, in CGenDaoInternalInput) {
mlog.Fatalf("fetching tables fields failed for table '%s':\n%v", tableName, err)
}
var (
newTableName = in.NewTableNames[i]
doFilePath = gfile.Join(dirPathDo, gstr.CaseSnake(newTableName)+".go")
structDefinition, _ = generateStructDefinition(ctx, generateStructDefinitionInput{
newTableName = in.NewTableNames[i]
doFilePath = gfile.Join(dirPathDo, gstr.CaseSnake(newTableName)+".go")
structDefinition = generateStructDefinition(ctx, generateStructDefinitionInput{
CGenDaoInternalInput: in,
TableName: tableName,
StructName: formatFieldName(newTableName, FieldNameCaseCamel),
StructName: gstr.CaseCamel(newTableName),
FieldMap: fieldMap,
IsDo: true,
})
@ -58,36 +53,30 @@ func generateDo(ctx context.Context, in CGenDaoInternalInput) {
},
)
modelContent := generateDoContent(
ctx,
in,
tableName,
formatFieldName(newTableName, FieldNameCaseCamel),
gstr.CaseCamel(newTableName),
structDefinition,
)
in.genItems.AppendGeneratedFilePath(doFilePath)
err = gfile.PutContents(doFilePath, strings.TrimSpace(modelContent))
if err != nil {
mlog.Fatalf(`writing content to "%s" failed: %v`, doFilePath, err)
} else {
utils.GoFmt(doFilePath)
mlog.Print("generated:", gfile.RealPath(doFilePath))
mlog.Print("generated:", doFilePath)
}
}
}
func generateDoContent(
ctx context.Context, in CGenDaoInternalInput, tableName, tableNameCamelCase, structDefine string,
) string {
func generateDoContent(in CGenDaoInternalInput, tableName, tableNameCamelCase, structDefine string) string {
doContent := gstr.ReplaceByMap(
getTemplateFromPathOrDefault(in.TplDaoDoPath, consts.TemplateGenDaoDoContent),
g.MapStrStr{
tplVarTableName: tableName,
tplVarPackageImports: getImportPartContent(ctx, structDefine, true, nil),
tplVarPackageImports: getImportPartContent(structDefine, true),
tplVarTableNameCamelCase: tableNameCamelCase,
tplVarStructDefine: structDefine,
tplVarPackageName: filepath.Base(in.DoPath),
},
)
})
doContent = replaceDefaultVar(in, doContent)
return doContent
}

View File

@ -1,14 +1,7 @@
// Copyright GoFrame gf Author(https://goframe.org). All Rights Reserved.
//
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with this file,
// You can obtain one at https://github.com/gogf/gf.
package gendao
import (
"context"
"path/filepath"
"strings"
"github.com/gogf/gf/v2/frame/g"
@ -22,57 +15,50 @@ import (
func generateEntity(ctx context.Context, in CGenDaoInternalInput) {
var dirPathEntity = gfile.Join(in.Path, in.EntityPath)
in.genItems.AppendDirPath(dirPathEntity)
if in.Clear {
doClear(ctx, dirPathEntity)
}
// Model content.
for i, tableName := range in.TableNames {
fieldMap, err := in.DB.TableFields(ctx, tableName)
if err != nil {
mlog.Fatalf("fetching tables fields failed for table '%s':\n%v", tableName, err)
}
var (
newTableName = in.NewTableNames[i]
entityFilePath = filepath.FromSlash(gfile.Join(dirPathEntity, gstr.CaseSnake(newTableName)+".go"))
structDefinition, appendImports = generateStructDefinition(ctx, generateStructDefinitionInput{
CGenDaoInternalInput: in,
TableName: tableName,
StructName: formatFieldName(newTableName, FieldNameCaseCamel),
FieldMap: fieldMap,
IsDo: false,
})
entityContent = generateEntityContent(
ctx,
newTableName = in.NewTableNames[i]
entityFilePath = gfile.Join(dirPathEntity, gstr.CaseSnake(newTableName)+".go")
entityContent = generateEntityContent(
in,
newTableName,
formatFieldName(newTableName, FieldNameCaseCamel),
structDefinition,
appendImports,
gstr.CaseCamel(newTableName),
generateStructDefinition(ctx, generateStructDefinitionInput{
CGenDaoInternalInput: in,
TableName: tableName,
StructName: gstr.CaseCamel(newTableName),
FieldMap: fieldMap,
IsDo: false,
}),
)
)
in.genItems.AppendGeneratedFilePath(entityFilePath)
err = gfile.PutContents(entityFilePath, strings.TrimSpace(entityContent))
if err != nil {
mlog.Fatalf("writing content to '%s' failed: %v", entityFilePath, err)
} else {
utils.GoFmt(entityFilePath)
mlog.Print("generated:", gfile.RealPath(entityFilePath))
mlog.Print("generated:", entityFilePath)
}
}
}
func generateEntityContent(
ctx context.Context, in CGenDaoInternalInput, tableName, tableNameCamelCase, structDefine string, appendImports []string,
) string {
func generateEntityContent(in CGenDaoInternalInput, tableName, tableNameCamelCase, structDefine string) string {
entityContent := gstr.ReplaceByMap(
getTemplateFromPathOrDefault(in.TplDaoEntityPath, consts.TemplateGenDaoEntityContent),
g.MapStrStr{
tplVarTableName: tableName,
tplVarPackageImports: getImportPartContent(ctx, structDefine, false, appendImports),
tplVarPackageImports: getImportPartContent(structDefine, false),
tplVarTableNameCamelCase: tableNameCamelCase,
tplVarStructDefine: structDefine,
tplVarPackageName: filepath.Base(in.EntityPath),
},
)
})
entityContent = replaceDefaultVar(in, entityContent)
return entityContent
}

View File

@ -1,53 +0,0 @@
// Copyright GoFrame gf Author(https://goframe.org). All Rights Reserved.
//
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with this file,
// You can obtain one at https://github.com/gogf/gf.
package gendao
type (
CGenDaoInternalGenItems struct {
index int
Items []CGenDaoInternalGenItem
}
CGenDaoInternalGenItem struct {
Clear bool
StorageDirPaths []string
GeneratedFilePaths []string
}
)
func newCGenDaoInternalGenItems() *CGenDaoInternalGenItems {
return &CGenDaoInternalGenItems{
index: -1,
Items: make([]CGenDaoInternalGenItem, 0),
}
}
func (i *CGenDaoInternalGenItems) Scale() {
i.Items = append(i.Items, CGenDaoInternalGenItem{
StorageDirPaths: make([]string, 0),
GeneratedFilePaths: make([]string, 0),
Clear: false,
})
i.index++
}
func (i *CGenDaoInternalGenItems) SetClear(clear bool) {
i.Items[i.index].Clear = clear
}
func (i CGenDaoInternalGenItems) AppendDirPath(storageDirPath string) {
i.Items[i.index].StorageDirPaths = append(
i.Items[i.index].StorageDirPaths,
storageDirPath,
)
}
func (i CGenDaoInternalGenItems) AppendGeneratedFilePath(generatedFilePath string) {
i.Items[i.index].GeneratedFilePaths = append(
i.Items[i.index].GeneratedFilePaths,
generatedFilePath,
)
}

View File

@ -1,23 +1,15 @@
// Copyright GoFrame gf Author(https://goframe.org). All Rights Reserved.
//
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with this file,
// You can obtain one at https://github.com/gogf/gf.
package gendao
import (
"bytes"
"context"
"fmt"
"strings"
"github.com/olekukonko/tablewriter"
"github.com/gogf/gf/v2/database/gdb"
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/text/gregex"
"github.com/gogf/gf/v2/text/gstr"
"github.com/olekukonko/tablewriter"
)
type generateStructDefinitionInput struct {
@ -28,18 +20,13 @@ type generateStructDefinitionInput struct {
IsDo bool // Is generating DTO struct.
}
func generateStructDefinition(ctx context.Context, in generateStructDefinitionInput) (string, []string) {
var appendImports []string
func generateStructDefinition(ctx context.Context, in generateStructDefinitionInput) string {
buffer := bytes.NewBuffer(nil)
array := make([][]string, len(in.FieldMap))
names := sortFieldKeyForDao(in.FieldMap)
for index, name := range names {
var imports string
field := in.FieldMap[name]
array[index], imports = generateStructFieldDefinition(ctx, field, in)
if imports != "" {
appendImports = append(appendImports, imports)
}
array[index] = generateStructFieldDefinition(ctx, field, in)
}
tw := tablewriter.NewWriter(buffer)
tw.SetBorder(false)
@ -60,99 +47,59 @@ func generateStructDefinition(ctx context.Context, in generateStructDefinitionIn
}
buffer.WriteString(stContent)
buffer.WriteString("}")
return buffer.String(), appendImports
return buffer.String()
}
// generateStructFieldDefinition generates and returns the attribute definition for specified field.
// generateStructFieldForModel generates and returns the attribute definition for specified field.
func generateStructFieldDefinition(
ctx context.Context, field *gdb.TableField, in generateStructDefinitionInput,
) (attrLines []string, appendImport string) {
) []string {
var (
err error
localTypeName gdb.LocalType
localTypeNameStr string
jsonTag = gstr.CaseConvert(field.Name, gstr.CaseTypeMatch(in.JsonCase))
err error
typeName string
jsonTag = getJsonTagFromCase(field.Name, in.JsonCase)
)
if in.TypeMapping != nil && len(in.TypeMapping) > 0 {
var (
tryTypeName string
)
tryTypeMatch, _ := gregex.MatchString(`(.+?)\((.+)\)`, field.Type)
if len(tryTypeMatch) == 3 {
tryTypeName = gstr.Trim(tryTypeMatch[1])
} else {
tryTypeName = gstr.Split(field.Type, " ")[0]
}
if tryTypeName != "" {
if typeMapping, ok := in.TypeMapping[strings.ToLower(tryTypeName)]; ok {
localTypeNameStr = typeMapping.Type
appendImport = typeMapping.Import
}
}
typeName, err = in.DB.CheckLocalTypeForField(ctx, field.Type, nil)
if err != nil {
panic(err)
}
if localTypeNameStr == "" {
localTypeName, err = in.DB.CheckLocalTypeForField(ctx, field.Type, nil)
if err != nil {
panic(err)
switch typeName {
case gdb.LocalTypeDate, gdb.LocalTypeDatetime:
if in.StdTime {
typeName = "time.Time"
} else {
typeName = "*gtime.Time"
}
localTypeNameStr = string(localTypeName)
switch localTypeName {
case gdb.LocalTypeDate, gdb.LocalTypeTime, gdb.LocalTypeDatetime:
if in.StdTime {
localTypeNameStr = "time.Time"
} else {
localTypeNameStr = "*gtime.Time"
}
case gdb.LocalTypeInt64Bytes:
localTypeNameStr = "int64"
case gdb.LocalTypeInt64Bytes:
typeName = "int64"
case gdb.LocalTypeUint64Bytes:
localTypeNameStr = "uint64"
case gdb.LocalTypeUint64Bytes:
typeName = "uint64"
// Special type handle.
case gdb.LocalTypeJson, gdb.LocalTypeJsonb:
if in.GJsonSupport {
localTypeNameStr = "*gjson.Json"
} else {
localTypeNameStr = "string"
}
// Special type handle.
case gdb.LocalTypeJson, gdb.LocalTypeJsonb:
if in.GJsonSupport {
typeName = "*gjson.Json"
} else {
typeName = "string"
}
}
var (
tagKey = "`"
tagKey = "`"
result = []string{
" #" + gstr.CaseCamel(field.Name),
" #" + typeName,
}
descriptionTag = gstr.Replace(formatComment(field.Comment), `"`, `\"`)
)
removeFieldPrefixArray := gstr.SplitAndTrim(in.RemoveFieldPrefix, ",")
newFiledName := field.Name
for _, v := range removeFieldPrefixArray {
newFiledName = gstr.TrimLeftStr(newFiledName, v, 1)
}
if in.FieldMapping != nil && len(in.FieldMapping) > 0 {
if typeMapping, ok := in.FieldMapping[fmt.Sprintf("%s.%s", in.TableName, newFiledName)]; ok {
localTypeNameStr = typeMapping.Type
appendImport = typeMapping.Import
}
}
result = append(result, " #"+fmt.Sprintf(tagKey+`json:"%s"`, jsonTag))
result = append(result, " #"+fmt.Sprintf(`description:"%s"`+tagKey, descriptionTag))
result = append(result, " #"+fmt.Sprintf(`// %s`, formatComment(field.Comment)))
attrLines = []string{
" #" + formatFieldName(newFiledName, FieldNameCaseCamel),
" #" + localTypeNameStr,
}
attrLines = append(attrLines, fmt.Sprintf(` #%sjson:"%s"`, tagKey, jsonTag))
// orm tag
if !in.IsDo {
// entity
attrLines = append(attrLines, fmt.Sprintf(` #orm:"%s"`, field.Name))
}
attrLines = append(attrLines, fmt.Sprintf(` #description:"%s"%s`, descriptionTag, tagKey))
attrLines = append(attrLines, fmt.Sprintf(` #// %s`, formatComment(field.Comment)))
for k, v := range attrLines {
for k, v := range result {
if in.NoJsonTag {
v, _ = gregex.ReplaceString(`json:".+"`, ``, v)
}
@ -162,46 +109,9 @@ func generateStructFieldDefinition(
if in.NoModelComment {
v, _ = gregex.ReplaceString(`//.+`, ``, v)
}
attrLines[k] = v
result[k] = v
}
return attrLines, appendImport
}
type FieldNameCase string
const (
FieldNameCaseCamel FieldNameCase = "CaseCamel"
FieldNameCaseCamelLower FieldNameCase = "CaseCamelLower"
)
// formatFieldName formats and returns a new field name that is used for golang codes generating.
func formatFieldName(fieldName string, nameCase FieldNameCase) string {
// For normal databases like mysql, pgsql, sqlite,
// field/table names of that are in normal case.
var newFieldName = fieldName
if isAllUpper(fieldName) {
// For special databases like dm, oracle,
// field/table names of that are in upper case.
newFieldName = strings.ToLower(fieldName)
}
switch nameCase {
case FieldNameCaseCamel:
return gstr.CaseCamel(newFieldName)
case FieldNameCaseCamelLower:
return gstr.CaseCamelLower(newFieldName)
default:
return ""
}
}
// isAllUpper checks and returns whether given `fieldName` all letters are upper case.
func isAllUpper(fieldName string) bool {
for _, b := range fieldName {
if b >= 'a' && b <= 'z' {
return false
}
}
return true
return result
}
// formatComment formats the comment string to fit the golang code without any lines.
@ -214,3 +124,30 @@ func formatComment(comment string) string {
comment = gstr.Trim(comment)
return comment
}
// getJsonTagFromCase call gstr.Case* function to convert the s to specified case.
func getJsonTagFromCase(str, caseStr string) string {
switch gstr.ToLower(caseStr) {
case gstr.ToLower("Camel"):
return gstr.CaseCamel(str)
case gstr.ToLower("CamelLower"):
return gstr.CaseCamelLower(str)
case gstr.ToLower("Kebab"):
return gstr.CaseKebab(str)
case gstr.ToLower("KebabScreaming"):
return gstr.CaseKebabScreaming(str)
case gstr.ToLower("Snake"):
return gstr.CaseSnake(str)
case gstr.ToLower("SnakeFirstUpper"):
return gstr.CaseSnakeFirstUpper(str)
case gstr.ToLower("SnakeScreaming"):
return gstr.CaseSnakeScreaming(str)
}
return str
}

View File

@ -1,83 +0,0 @@
// Copyright GoFrame gf Author(https://goframe.org). All Rights Reserved.
//
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with this file,
// You can obtain one at https://github.com/gogf/gf.
package genenums
import (
"context"
"golang.org/x/tools/go/packages"
"github.com/gogf/gf/cmd/gf/v2/internal/consts"
"github.com/gogf/gf/cmd/gf/v2/internal/utility/mlog"
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/os/gfile"
"github.com/gogf/gf/v2/text/gstr"
"github.com/gogf/gf/v2/util/gtag"
)
type (
CGenEnums struct{}
CGenEnumsInput struct {
g.Meta `name:"enums" config:"{CGenEnumsConfig}" brief:"{CGenEnumsBrief}" eg:"{CGenEnumsEg}"`
Src string `name:"src" short:"s" dc:"source folder path to be parsed" d:"."`
Path string `name:"path" short:"p" dc:"output go file path storing enums content" d:"internal/boot/boot_enums.go"`
Prefixes []string `name:"prefixes" short:"x" dc:"only exports packages that starts with specified prefixes"`
}
CGenEnumsOutput struct{}
)
const (
CGenEnumsConfig = `gfcli.gen.enums`
CGenEnumsBrief = `parse go files in current project and generate enums go file`
CGenEnumsEg = `
gf gen enums
gf gen enums -p internal/boot/boot_enums.go
gf gen enums -p internal/boot/boot_enums.go -s .
gf gen enums -x github.com/gogf
`
)
func init() {
gtag.Sets(g.MapStrStr{
`CGenEnumsEg`: CGenEnumsEg,
`CGenEnumsBrief`: CGenEnumsBrief,
`CGenEnumsConfig`: CGenEnumsConfig,
})
}
func (c CGenEnums) Enums(ctx context.Context, in CGenEnumsInput) (out *CGenEnumsOutput, err error) {
realPath := gfile.RealPath(in.Src)
if realPath == "" {
mlog.Fatalf(`source folder path "%s" does not exist`, in.Src)
}
err = gfile.Chdir(realPath)
if err != nil {
mlog.Fatal(err)
}
mlog.Printf(`scanning for enums: %s`, realPath)
cfg := &packages.Config{
Dir: realPath,
Mode: pkgLoadMode,
Tests: false,
}
pkgs, err := packages.Load(cfg)
if err != nil {
mlog.Fatal(err)
}
p := NewEnumsParser(in.Prefixes)
p.ParsePackages(pkgs)
var enumsContent = gstr.ReplaceByMap(consts.TemplateGenEnums, g.MapStrStr{
"{PackageName}": gfile.Basename(gfile.Dir(in.Path)),
"{EnumsJson}": "`" + p.Export() + "`",
})
enumsContent = gstr.Trim(enumsContent)
if err = gfile.PutContents(in.Path, enumsContent); err != nil {
return
}
mlog.Printf(`generated enums go file: %s`, in.Path)
mlog.Print("done!")
return
}

View File

@ -1,148 +0,0 @@
// Copyright GoFrame gf Author(https://goframe.org). All Rights Reserved.
//
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with this file,
// You can obtain one at https://github.com/gogf/gf.
package genenums
import (
"go/constant"
"go/types"
"golang.org/x/tools/go/packages"
"github.com/gogf/gf/v2/encoding/gjson"
"github.com/gogf/gf/v2/text/gstr"
"github.com/gogf/gf/v2/util/gconv"
)
const pkgLoadMode = 0xffffff
type EnumsParser struct {
enums []EnumItem
parsedPkg map[string]struct{}
prefixes []string
standardPackages map[string]struct{}
}
type EnumItem struct {
Name string
Value string
Kind constant.Kind // String/Int/Bool/Float/Complex/Unknown
Type string // Pkg.ID + TypeName
}
func NewEnumsParser(prefixes []string) *EnumsParser {
return &EnumsParser{
enums: make([]EnumItem, 0),
parsedPkg: make(map[string]struct{}),
prefixes: prefixes,
standardPackages: getStandardPackages(),
}
}
func (p *EnumsParser) ParsePackages(pkgs []*packages.Package) {
for _, pkg := range pkgs {
p.ParsePackage(pkg)
}
}
func (p *EnumsParser) ParsePackage(pkg *packages.Package) {
// Ignore std packages.
if _, ok := p.standardPackages[pkg.ID]; ok {
return
}
// Ignore pared packages.
if _, ok := p.parsedPkg[pkg.ID]; ok {
return
}
p.parsedPkg[pkg.ID] = struct{}{}
// Only parse specified prefixes.
if len(p.prefixes) > 0 {
var hasPrefix bool
for _, prefix := range p.prefixes {
if hasPrefix = gstr.HasPrefix(pkg.ID, prefix); hasPrefix {
break
}
}
if !hasPrefix {
return
}
}
var (
scope = pkg.Types.Scope()
names = scope.Names()
)
for _, name := range names {
con, ok := scope.Lookup(name).(*types.Const)
if !ok {
// Only constants can be enums.
continue
}
if !con.Exported() {
// Ignore unexported values.
continue
}
var enumType = con.Type().String()
if !gstr.Contains(enumType, "/") {
// Ignore std types.
continue
}
var (
enumName = con.Name()
enumValue = con.Val().ExactString()
enumKind = con.Val().Kind()
)
if con.Val().Kind() == constant.String {
enumValue = constant.StringVal(con.Val())
}
p.enums = append(p.enums, EnumItem{
Name: enumName,
Value: enumValue,
Type: enumType,
Kind: enumKind,
})
}
for _, im := range pkg.Imports {
p.ParsePackage(im)
}
}
func (p *EnumsParser) Export() string {
var typeEnumMap = make(map[string][]interface{})
for _, enum := range p.enums {
if typeEnumMap[enum.Type] == nil {
typeEnumMap[enum.Type] = make([]interface{}, 0)
}
var value interface{}
switch enum.Kind {
case constant.Int:
value = gconv.Int64(enum.Value)
case constant.String:
value = enum.Value
case constant.Float:
value = gconv.Float64(enum.Value)
case constant.Bool:
value = gconv.Bool(enum.Value)
default:
value = enum.Value
}
typeEnumMap[enum.Type] = append(typeEnumMap[enum.Type], value)
}
return gjson.MustEncodeString(typeEnumMap)
}
func getStandardPackages() map[string]struct{} {
standardPackages := make(map[string]struct{})
stdPackages, err := packages.Load(nil, "std")
if err != nil {
panic(err)
}
for _, p := range stdPackages {
standardPackages[p.ID] = struct{}{}
}
return standardPackages
}

View File

@ -1,131 +0,0 @@
// Copyright GoFrame gf Author(https://goframe.org). All Rights Reserved.
//
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with this file,
// You can obtain one at https://github.com/gogf/gf.
package genpb
import (
"context"
"github.com/gogf/gf/cmd/gf/v2/internal/utility/mlog"
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/os/gfile"
"github.com/gogf/gf/v2/os/gproc"
"github.com/gogf/gf/v2/util/gtag"
)
type (
CGenPb struct{}
CGenPbInput struct {
g.Meta `name:"pb" config:"{CGenPbConfig}" brief:"{CGenPbBrief}" eg:"{CGenPbEg}"`
Path string `name:"path" short:"p" dc:"protobuf file folder path" d:"manifest/protobuf"`
OutputApi string `name:"api" short:"a" dc:"output folder path storing generated go files of api" d:"api"`
OutputCtrl string `name:"ctrl" short:"c" dc:"output folder path storing generated go files of controller" d:"internal/controller"`
}
CGenPbOutput struct{}
)
const (
CGenPbConfig = `gfcli.gen.pb`
CGenPbBrief = `parse proto files and generate protobuf go files`
CGenPbEg = `
gf gen pb
gf gen pb -p . -a . -p .
`
)
func init() {
gtag.Sets(g.MapStrStr{
`CGenPbEg`: CGenPbEg,
`CGenPbBrief`: CGenPbBrief,
`CGenPbConfig`: CGenPbConfig,
})
}
func (c CGenPb) Pb(ctx context.Context, in CGenPbInput) (out *CGenPbOutput, err error) {
// Necessary check.
protoc := gproc.SearchBinary("protoc")
if protoc == "" {
mlog.Fatalf(`command "protoc" not found in your environment, please install protoc first: https://grpc.io/docs/languages/go/quickstart/`)
}
// protocol fold checks.
var (
protoPath = gfile.RealPath(in.Path)
isParsingPWD bool
)
if protoPath == "" {
// Use current working directory as protoPath if there are proto files under.
currentPath := gfile.Pwd()
currentFiles, _ := gfile.ScanDirFile(currentPath, "*.proto")
if len(currentFiles) > 0 {
protoPath = currentPath
isParsingPWD = true
} else {
mlog.Fatalf(`proto files folder "%s" does not exist`, in.Path)
}
}
// output path checks.
outputApiPath := gfile.RealPath(in.OutputApi)
if outputApiPath == "" {
if isParsingPWD {
outputApiPath = protoPath
} else {
mlog.Fatalf(`output api folder "%s" does not exist`, in.OutputApi)
}
}
outputCtrlPath := gfile.RealPath(in.OutputCtrl)
if outputCtrlPath == "" {
if isParsingPWD {
outputCtrlPath = ""
} else {
mlog.Fatalf(`output controller folder "%s" does not exist`, in.OutputCtrl)
}
}
// folder scanning.
files, err := gfile.ScanDirFile(protoPath, "*.proto", true)
if err != nil {
mlog.Fatal(err)
}
if len(files) == 0 {
mlog.Fatalf(`no proto files found in folder "%s"`, in.Path)
}
var originPwd = gfile.Pwd()
defer gfile.Chdir(originPwd)
if err = gfile.Chdir(protoPath); err != nil {
mlog.Fatal(err)
}
for _, file := range files {
var command = gproc.NewProcess(protoc, nil)
command.Args = append(command.Args, "--proto_path="+gfile.Pwd())
command.Args = append(command.Args, "--go_out=paths=source_relative:"+outputApiPath)
command.Args = append(command.Args, "--go-grpc_out=paths=source_relative:"+outputApiPath)
command.Args = append(command.Args, file)
mlog.Print(command.String())
if err = command.Run(ctx); err != nil {
mlog.Fatal(err)
}
}
// Generate struct tag according comment rules.
err = c.generateStructTag(ctx, generateStructTagInput{OutputApiPath: outputApiPath})
if err != nil {
return
}
// Generate controllers according comment rules.
if outputCtrlPath != "" {
err = c.generateController(ctx, generateControllerInput{
OutputApiPath: outputApiPath,
OutputCtrlPath: outputCtrlPath,
})
if err != nil {
return
}
}
mlog.Print("done!")
return
}

View File

@ -1,196 +0,0 @@
// Copyright GoFrame gf Author(https://goframe.org). All Rights Reserved.
//
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with this file,
// You can obtain one at https://github.com/gogf/gf.
package genpb
import (
"context"
"fmt"
"strings"
"github.com/gogf/gf/cmd/gf/v2/internal/utility/utils"
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/os/gfile"
"github.com/gogf/gf/v2/text/gregex"
"github.com/gogf/gf/v2/text/gstr"
)
type generateControllerInput struct {
OutputApiPath string
OutputCtrlPath string
}
type generateCtrl struct {
Name string
Package string
Version string
Methods []generateCtrlMethod
}
type generateCtrlMethod struct {
Name string
Definition string
}
const (
controllerTemplate = `
package {Package}
type Controller struct {
{Version}.Unimplemented{Name}Server
}
func Register(s *grpcx.GrpcServer) {
{Version}.Register{Name}Server(s.Server, &Controller{})
}
`
controllerMethodTemplate = `
func (*Controller) {Definition} {
return nil, gerror.NewCode(gcode.CodeNotImplemented)
}
`
)
func (c CGenPb) generateController(ctx context.Context, in generateControllerInput) (err error) {
files, err := gfile.ScanDirFile(in.OutputApiPath, "*_grpc.pb.go", true)
if err != nil {
return err
}
var controllers []generateCtrl
for _, file := range files {
fileControllers, err := c.parseControllers(file)
if err != nil {
return err
}
controllers = append(controllers, fileControllers...)
}
if len(controllers) == 0 {
return nil
}
// Generate controller files.
err = c.doGenerateControllers(in, controllers)
return
}
func (c CGenPb) parseControllers(filePath string) ([]generateCtrl, error) {
var (
controllers []generateCtrl
content = gfile.GetContents(filePath)
)
_, err := gregex.ReplaceStringFuncMatch(
`type (\w+)Server interface {([\s\S]+?)}`,
content,
func(match []string) string {
ctrl := generateCtrl{
Name: match[1],
Package: strings.ReplaceAll(gfile.Basename(gfile.Dir(gfile.Dir(filePath))), "-", "_"),
Version: gfile.Basename(gfile.Dir(filePath)),
Methods: make([]generateCtrlMethod, 0),
}
lines := gstr.Split(match[2], "\n")
for _, line := range lines {
line = gstr.Trim(line)
if line == "" || !gstr.IsLetterUpper(line[0]) {
continue
}
// Comment.
if gregex.IsMatchString(`^//.+`, line) {
continue
}
line, _ = gregex.ReplaceStringFuncMatch(
`^(\w+)\(context\.Context, \*(\w+)\) \(\*(\w+), error\)$`,
line,
func(match []string) string {
return fmt.Sprintf(
`%s(ctx context.Context, req *%s.%s) (res *%s.%s, err error)`,
match[1], ctrl.Version, match[2], ctrl.Version, match[3],
)
},
)
ctrl.Methods = append(ctrl.Methods, generateCtrlMethod{
Name: gstr.Split(line, "(")[0],
Definition: line,
})
}
if len(ctrl.Methods) > 0 {
controllers = append(controllers, ctrl)
}
return match[0]
},
)
return controllers, err
}
func (c CGenPb) doGenerateControllers(in generateControllerInput, controllers []generateCtrl) (err error) {
for _, controller := range controllers {
err = c.doGenerateController(in, controller)
if err != nil {
return err
}
}
err = utils.ReplaceGeneratedContentGFV2(in.OutputCtrlPath)
return nil
}
func (c CGenPb) doGenerateController(in generateControllerInput, controller generateCtrl) (err error) {
var (
folderPath = gfile.Join(in.OutputCtrlPath, controller.Package)
filePath = gfile.Join(folderPath, controller.Package+".go")
isDirty bool
)
if !gfile.Exists(folderPath) {
if err = gfile.Mkdir(folderPath); err != nil {
return err
}
}
if !gfile.Exists(filePath) {
templateContent := gstr.ReplaceByMap(controllerTemplate, g.MapStrStr{
"{Name}": controller.Name,
"{Version}": controller.Version,
"{Package}": controller.Package,
})
if err = gfile.PutContents(filePath, templateContent); err != nil {
return err
}
isDirty = true
}
// Exist controller content.
var ctrlContent string
files, err := gfile.ScanDirFile(folderPath, "*.go", false)
if err != nil {
return err
}
for _, file := range files {
if ctrlContent != "" {
ctrlContent += "\n"
}
ctrlContent += gfile.GetContents(file)
}
// Generate method content.
var generatedContent string
for _, method := range controller.Methods {
if gstr.Contains(ctrlContent, fmt.Sprintf(`%s(`, method.Name)) {
continue
}
if generatedContent != "" {
generatedContent += "\n"
}
generatedContent += gstr.ReplaceByMap(controllerMethodTemplate, g.MapStrStr{
"{Definition}": method.Definition,
})
}
if generatedContent != "" {
err = gfile.PutContentsAppend(filePath, generatedContent)
if err != nil {
return err
}
isDirty = true
}
if isDirty {
utils.GoFmt(filePath)
}
return nil
}

View File

@ -1,118 +0,0 @@
// Copyright GoFrame gf Author(https://goframe.org). All Rights Reserved.
//
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with this file,
// You can obtain one at https://github.com/gogf/gf.
package genpb
import (
"context"
"fmt"
"github.com/gogf/gf/cmd/gf/v2/internal/utility/utils"
"github.com/gogf/gf/v2/container/gmap"
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/os/gfile"
"github.com/gogf/gf/v2/text/gregex"
"github.com/gogf/gf/v2/text/gstr"
"github.com/gogf/gf/v2/util/gconv"
)
type generateStructTagInput struct {
OutputApiPath string
}
func (c CGenPb) generateStructTag(ctx context.Context, in generateStructTagInput) (err error) {
files, err := gfile.ScanDirFile(in.OutputApiPath, "*.pb.go", true)
if err != nil {
return err
}
var content string
for _, file := range files {
content = gfile.GetContents(file)
content, err = c.doTagReplacement(ctx, content)
if err != nil {
return err
}
if err = gfile.PutContents(file, content); err != nil {
return err
}
utils.GoFmt(file)
}
return
}
func (c CGenPb) doTagReplacement(ctx context.Context, content string) (string, error) {
content, err := gregex.ReplaceStringFuncMatch(`type (\w+) struct {([\s\S]+?)}`, content, func(match []string) string {
var (
topCommentMatch []string
tailCommentMatch []string
lines = gstr.Split(match[2], "\n")
lineTagMap = gmap.NewListMap()
)
for index, line := range lines {
line = gstr.Trim(line)
if line == "" {
continue
}
// Top comment.
topCommentMatch, _ = gregex.MatchString(`^/[/|\*](.+)`, line)
if len(topCommentMatch) > 1 {
c.tagCommentIntoListMap(gstr.Trim(topCommentMatch[1]), lineTagMap)
continue
}
// Tail comment.
tailCommentMatch, _ = gregex.MatchString(".+?`.+?`.+?//(.+)", line)
if len(tailCommentMatch) > 1 {
c.tagCommentIntoListMap(gstr.Trim(tailCommentMatch[1]), lineTagMap)
}
// Tag injection.
if !lineTagMap.IsEmpty() {
tagContent := c.listMapToStructTag(lineTagMap)
lineTagMap.Clear()
line, _ = gregex.ReplaceString("`(.+)`", fmt.Sprintf("`$1 %s`", tagContent), line)
}
lines[index] = line
}
match[2] = gstr.Join(lines, "\n")
return fmt.Sprintf("type %s struct {%s}", match[1], match[2])
})
return content, err
}
func (c CGenPb) tagCommentIntoListMap(comment string, lineTagMap *gmap.ListMap) {
tagCommentMatch, _ := gregex.MatchString(`^(\w+):(.+)`, comment)
if len(tagCommentMatch) > 1 {
var (
tagName = gstr.Trim(tagCommentMatch[1])
tagContent = gstr.Trim(tagCommentMatch[2])
)
lineTagMap.Set(tagName, lineTagMap.GetVar(tagName).String()+tagContent)
} else {
var (
tagName = "dc"
// Convert backticks and double quotes to single quotes.
tagContent = gstr.ReplaceByMap(comment, g.MapStrStr{
"`": `'`,
`"`: `'`,
})
)
lineTagMap.Set(tagName, lineTagMap.GetVar(tagName).String()+tagContent)
}
}
func (c CGenPb) listMapToStructTag(lineTagMap *gmap.ListMap) string {
var tag string
lineTagMap.Iterator(func(key, value interface{}) bool {
if tag != "" {
tag += " "
}
tag += fmt.Sprintf(
`%s:"%s"`,
key, gstr.Replace(gconv.String(value), `"`, `\"`),
)
return true
})
return tag
}

View File

@ -1,399 +0,0 @@
// Copyright GoFrame gf Author(https://goframe.org). All Rights Reserved.
//
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with this file,
// You can obtain one at https://github.com/gogf/gf.
package genpbentity
import (
"bytes"
"context"
"fmt"
"path/filepath"
"strings"
"github.com/gogf/gf/cmd/gf/v2/internal/utility/utils"
"github.com/olekukonko/tablewriter"
"github.com/gogf/gf/cmd/gf/v2/internal/consts"
"github.com/gogf/gf/cmd/gf/v2/internal/utility/mlog"
"github.com/gogf/gf/v2/database/gdb"
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/os/gctx"
"github.com/gogf/gf/v2/os/gfile"
"github.com/gogf/gf/v2/os/gtime"
"github.com/gogf/gf/v2/text/gregex"
"github.com/gogf/gf/v2/text/gstr"
"github.com/gogf/gf/v2/util/gconv"
"github.com/gogf/gf/v2/util/gtag"
)
type (
CGenPbEntity struct{}
CGenPbEntityInput struct {
g.Meta `name:"pbentity" config:"{CGenPbEntityConfig}" brief:"{CGenPbEntityBrief}" eg:"{CGenPbEntityEg}" ad:"{CGenPbEntityAd}"`
Path string `name:"path" short:"p" brief:"{CGenPbEntityBriefPath}" d:"manifest/protobuf/pbentity"`
Package string `name:"package" short:"k" brief:"{CGenPbEntityBriefPackage}"`
Link string `name:"link" short:"l" brief:"{CGenPbEntityBriefLink}"`
Tables string `name:"tables" short:"t" brief:"{CGenPbEntityBriefTables}"`
Prefix string `name:"prefix" short:"f" brief:"{CGenPbEntityBriefPrefix}"`
RemovePrefix string `name:"removePrefix" short:"r" brief:"{CGenPbEntityBriefRemovePrefix}"`
RemoveFieldPrefix string `name:"removeFieldPrefix" short:"rf" brief:"{CGenPbEntityBriefRemoveFieldPrefix}"`
NameCase string `name:"nameCase" short:"n" brief:"{CGenPbEntityBriefNameCase}" d:"Camel"`
JsonCase string `name:"jsonCase" short:"j" brief:"{CGenPbEntityBriefJsonCase}" d:"none"`
Option string `name:"option" short:"o" brief:"{CGenPbEntityBriefOption}"`
}
CGenPbEntityOutput struct{}
CGenPbEntityInternalInput struct {
CGenPbEntityInput
DB gdb.DB
TableName string // TableName specifies the table name of the table.
NewTableName string // NewTableName specifies the prefix-stripped name of the table.
}
)
const (
defaultPackageSuffix = `api/pbentity`
CGenPbEntityConfig = `gfcli.gen.pbentity`
CGenPbEntityBrief = `generate entity message files in protobuf3 format`
CGenPbEntityEg = `
gf gen pbentity
gf gen pbentity -l "mysql:root:12345678@tcp(127.0.0.1:3306)/test"
gf gen pbentity -p ./protocol/demos/entity -t user,user_detail,user_login
gf gen pbentity -r user_ -k github.com/gogf/gf/example/protobuf
gf gen pbentity -r user_
`
CGenPbEntityAd = `
CONFIGURATION SUPPORT
Options are also supported by configuration file.
It's suggested using configuration file instead of command line arguments making producing.
The configuration node name is "gf.gen.pbentity", which also supports multiple databases, for example(config.yaml):
gfcli:
gen:
- pbentity:
link: "mysql:root:12345678@tcp(127.0.0.1:3306)/test"
path: "protocol/demos/entity"
tables: "order,products"
package: "demos"
- pbentity:
link: "mysql:root:12345678@tcp(127.0.0.1:3306)/primary"
path: "protocol/demos/entity"
prefix: "primary_"
tables: "user, userDetail"
package: "demos"
option: |
option go_package = "protobuf/demos";
option java_package = "protobuf/demos";
option php_namespace = "protobuf/demos";
`
CGenPbEntityBriefPath = `directory path for generated files storing`
CGenPbEntityBriefPackage = `package path for all entity proto files`
CGenPbEntityBriefLink = `database configuration, the same as the ORM configuration of GoFrame`
CGenPbEntityBriefTables = `generate models only for given tables, multiple table names separated with ','`
CGenPbEntityBriefPrefix = `add specified prefix for all entity names and entity proto files`
CGenPbEntityBriefRemovePrefix = `remove specified prefix of the table, multiple prefix separated with ','`
CGenPbEntityBriefRemoveFieldPrefix = `remove specified prefix of the field, multiple prefix separated with ','`
CGenPbEntityBriefOption = `extra protobuf options`
CGenPbEntityBriefGroup = `
specifying the configuration group name of database for generated ORM instance,
it's not necessary and the default value is "default"
`
CGenPbEntityBriefNameCase = `
case for message attribute names, default is "Camel":
| Case | Example |
|---------------- |--------------------|
| Camel | AnyKindOfString |
| CamelLower | anyKindOfString | default
| Snake | any_kind_of_string |
| SnakeScreaming | ANY_KIND_OF_STRING |
| SnakeFirstUpper | rgb_code_md5 |
| Kebab | any-kind-of-string |
| KebabScreaming | ANY-KIND-OF-STRING |
`
CGenPbEntityBriefJsonCase = `
case for message json tag, cases are the same as "nameCase", default "CamelLower".
set it to "none" to ignore json tag generating.
`
)
func init() {
gtag.Sets(g.MapStrStr{
`CGenPbEntityConfig`: CGenPbEntityConfig,
`CGenPbEntityBrief`: CGenPbEntityBrief,
`CGenPbEntityEg`: CGenPbEntityEg,
`CGenPbEntityAd`: CGenPbEntityAd,
`CGenPbEntityBriefPath`: CGenPbEntityBriefPath,
`CGenPbEntityBriefPackage`: CGenPbEntityBriefPackage,
`CGenPbEntityBriefLink`: CGenPbEntityBriefLink,
`CGenPbEntityBriefTables`: CGenPbEntityBriefTables,
`CGenPbEntityBriefPrefix`: CGenPbEntityBriefPrefix,
`CGenPbEntityBriefRemovePrefix`: CGenPbEntityBriefRemovePrefix,
`CGenPbEntityBriefRemoveFieldPrefix`: CGenPbEntityBriefRemoveFieldPrefix,
`CGenPbEntityBriefGroup`: CGenPbEntityBriefGroup,
`CGenPbEntityBriefNameCase`: CGenPbEntityBriefNameCase,
`CGenPbEntityBriefJsonCase`: CGenPbEntityBriefJsonCase,
`CGenPbEntityBriefOption`: CGenPbEntityBriefOption,
})
}
func (c CGenPbEntity) PbEntity(ctx context.Context, in CGenPbEntityInput) (out *CGenPbEntityOutput, err error) {
var (
config = g.Cfg()
)
if config.Available(ctx) {
v := config.MustGet(ctx, CGenPbEntityConfig)
if v.IsSlice() {
for i := 0; i < len(v.Interfaces()); i++ {
doGenPbEntityForArray(ctx, i, in)
}
} else {
doGenPbEntityForArray(ctx, -1, in)
}
} else {
doGenPbEntityForArray(ctx, -1, in)
}
mlog.Print("done!")
return
}
func doGenPbEntityForArray(ctx context.Context, index int, in CGenPbEntityInput) {
var (
err error
db gdb.DB
)
if index >= 0 {
err = g.Cfg().MustGet(
ctx,
fmt.Sprintf(`%s.%d`, CGenPbEntityConfig, index),
).Scan(&in)
if err != nil {
mlog.Fatalf(`invalid configuration of "%s": %+v`, CGenPbEntityConfig, err)
}
}
if in.Package == "" {
mlog.Debug(`package parameter is empty, trying calculating the package path using go.mod`)
modName := utils.GetImportPath(gfile.Pwd())
in.Package = modName + "/" + defaultPackageSuffix
}
removePrefixArray := gstr.SplitAndTrim(in.RemovePrefix, ",")
// It uses user passed database configuration.
if in.Link != "" {
var (
tempGroup = gtime.TimestampNanoStr()
match, _ = gregex.MatchString(`([a-z]+):(.+)`, in.Link)
)
if len(match) == 3 {
gdb.AddConfigNode(tempGroup, gdb.ConfigNode{
Type: gstr.Trim(match[1]),
Link: in.Link,
})
db, _ = gdb.Instance(tempGroup)
}
} else {
db = g.DB()
}
if db == nil {
mlog.Fatal("database initialization failed")
}
tableNames := ([]string)(nil)
if in.Tables != "" {
tableNames = gstr.SplitAndTrim(in.Tables, ",")
} else {
tableNames, err = db.Tables(context.TODO())
if err != nil {
mlog.Fatalf("fetching tables failed: \n %v", err)
}
}
for _, tableName := range tableNames {
newTableName := tableName
for _, v := range removePrefixArray {
newTableName = gstr.TrimLeftStr(newTableName, v, 1)
}
generatePbEntityContentFile(ctx, CGenPbEntityInternalInput{
CGenPbEntityInput: in,
DB: db,
TableName: tableName,
NewTableName: newTableName,
})
}
}
// generatePbEntityContentFile generates the protobuf files for given table.
func generatePbEntityContentFile(ctx context.Context, in CGenPbEntityInternalInput) {
fieldMap, err := in.DB.TableFields(ctx, in.TableName)
if err != nil {
mlog.Fatalf("fetching tables fields failed for table '%s':\n%v", in.TableName, err)
}
// Change the `newTableName` if `Prefix` is given.
newTableName := in.Prefix + in.NewTableName
var (
imports string
tableNameCamelCase = gstr.CaseCamel(newTableName)
tableNameSnakeCase = gstr.CaseSnake(newTableName)
entityMessageDefine = generateEntityMessageDefinition(tableNameCamelCase, fieldMap, in)
fileName = gstr.Trim(tableNameSnakeCase, "-_.")
path = filepath.FromSlash(gfile.Join(in.Path, fileName+".proto"))
)
if gstr.Contains(entityMessageDefine, "google.protobuf.Timestamp") {
imports = `import "google/protobuf/timestamp.proto";`
}
entityContent := gstr.ReplaceByMap(getTplPbEntityContent(""), g.MapStrStr{
"{Imports}": imports,
"{PackageName}": gfile.Basename(in.Package),
"{GoPackage}": in.Package,
"{OptionContent}": in.Option,
"{EntityMessage}": entityMessageDefine,
})
if err := gfile.PutContents(path, strings.TrimSpace(entityContent)); err != nil {
mlog.Fatalf("writing content to '%s' failed: %v", path, err)
} else {
mlog.Print("generated:", gfile.RealPath(path))
}
}
// generateEntityMessageDefinition generates and returns the message definition for specified table.
func generateEntityMessageDefinition(entityName string, fieldMap map[string]*gdb.TableField, in CGenPbEntityInternalInput) string {
var (
buffer = bytes.NewBuffer(nil)
array = make([][]string, len(fieldMap))
names = sortFieldKeyForPbEntity(fieldMap)
)
for index, name := range names {
array[index] = generateMessageFieldForPbEntity(index+1, fieldMap[name], in)
}
tw := tablewriter.NewWriter(buffer)
tw.SetBorder(false)
tw.SetRowLine(false)
tw.SetAutoWrapText(false)
tw.SetColumnSeparator("")
tw.AppendBulk(array)
tw.Render()
stContent := buffer.String()
// Let's do this hack of table writer for indent!
stContent = gstr.Replace(stContent, " #", "")
buffer.Reset()
buffer.WriteString(fmt.Sprintf("message %s {\n", entityName))
buffer.WriteString(stContent)
buffer.WriteString("}")
return buffer.String()
}
// generateMessageFieldForPbEntity generates and returns the message definition for specified field.
func generateMessageFieldForPbEntity(index int, field *gdb.TableField, in CGenPbEntityInternalInput) []string {
var (
localTypeName gdb.LocalType
comment string
jsonTagStr string
err error
ctx = gctx.GetInitCtx()
)
localTypeName, err = in.DB.CheckLocalTypeForField(ctx, field.Type, nil)
if err != nil {
panic(err)
}
var typeMapping = map[gdb.LocalType]string{
gdb.LocalTypeString: "string",
gdb.LocalTypeDate: "google.protobuf.Timestamp",
gdb.LocalTypeDatetime: "google.protobuf.Timestamp",
gdb.LocalTypeInt: "int32",
gdb.LocalTypeUint: "uint32",
gdb.LocalTypeInt64: "int64",
gdb.LocalTypeUint64: "uint64",
gdb.LocalTypeIntSlice: "repeated int32",
gdb.LocalTypeInt64Slice: "repeated int64",
gdb.LocalTypeUint64Slice: "repeated uint64",
gdb.LocalTypeInt64Bytes: "repeated int64",
gdb.LocalTypeUint64Bytes: "repeated uint64",
gdb.LocalTypeFloat32: "float",
gdb.LocalTypeFloat64: "double",
gdb.LocalTypeBytes: "bytes",
gdb.LocalTypeBool: "bool",
gdb.LocalTypeJson: "string",
gdb.LocalTypeJsonb: "string",
}
localTypeNameStr := typeMapping[localTypeName]
if localTypeNameStr == "" {
localTypeNameStr = "string"
}
comment = gstr.ReplaceByArray(field.Comment, g.SliceStr{
"\n", " ",
"\r", " ",
})
comment = gstr.Trim(comment)
comment = gstr.Replace(comment, `\n`, " ")
comment, _ = gregex.ReplaceString(`\s{2,}`, ` `, comment)
if jsonTagName := formatCase(field.Name, in.JsonCase); jsonTagName != "" {
jsonTagStr = fmt.Sprintf(`[json_name = "%s"]`, jsonTagName)
// beautiful indent.
if index < 10 {
// 3 spaces
jsonTagStr = " " + jsonTagStr
} else if index < 100 {
// 2 spaces
jsonTagStr = " " + jsonTagStr
} else {
// 1 spaces
jsonTagStr = " " + jsonTagStr
}
}
removeFieldPrefixArray := gstr.SplitAndTrim(in.RemoveFieldPrefix, ",")
newFiledName := field.Name
for _, v := range removeFieldPrefixArray {
newFiledName = gstr.TrimLeftStr(newFiledName, v, 1)
}
return []string{
" #" + localTypeNameStr,
" #" + formatCase(newFiledName, in.NameCase),
" #= " + gconv.String(index) + jsonTagStr + ";",
" #" + fmt.Sprintf(`// %s`, comment),
}
}
func getTplPbEntityContent(tplEntityPath string) string {
if tplEntityPath != "" {
return gfile.GetContents(tplEntityPath)
}
return consts.TemplatePbEntityMessageContent
}
// formatCase call gstr.Case* function to convert the s to specified case.
func formatCase(str, caseStr string) string {
if caseStr == "none" {
return ""
}
return gstr.CaseConvert(str, gstr.CaseTypeMatch(caseStr))
}
func sortFieldKeyForPbEntity(fieldMap map[string]*gdb.TableField) []string {
names := make(map[int]string)
for _, field := range fieldMap {
names[field.Index] = field.Name
}
var (
result = make([]string, len(names))
i = 0
j = 0
)
for {
if len(names) == 0 {
break
}
if val, ok := names[i]; ok {
result[j] = val
j++
delete(names, i)
}
i++
}
return result
}

View File

@ -1,30 +1,22 @@
// Copyright GoFrame gf Author(https://goframe.org). All Rights Reserved.
//
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with this file,
// You can obtain one at https://github.com/gogf/gf.
package genservice
import (
"context"
"fmt"
"path/filepath"
"sync"
"sync/atomic"
"github.com/gogf/gf/cmd/gf/v2/internal/utility/mlog"
"github.com/gogf/gf/cmd/gf/v2/internal/utility/utils"
"github.com/gogf/gf/v2/container/garray"
"github.com/gogf/gf/v2/container/gmap"
"github.com/gogf/gf/v2/container/gset"
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/os/gfile"
"github.com/gogf/gf/v2/os/gproc"
"github.com/gogf/gf/v2/os/gtime"
"github.com/gogf/gf/v2/text/gregex"
"github.com/gogf/gf/v2/text/gstr"
"github.com/gogf/gf/v2/util/gconv"
"github.com/gogf/gf/v2/util/gtag"
"github.com/gogf/gf/cmd/gf/v2/internal/utility/mlog"
"github.com/gogf/gf/cmd/gf/v2/internal/utility/utils"
)
const (
@ -52,7 +44,7 @@ destination file name storing automatically generated go files, cases are as fol
`
CGenServiceBriefWatchFile = `used in file watcher, it re-generates all service go files only if given file is under srcFolder`
CGenServiceBriefStPattern = `regular expression matching struct name for generating service. default: ^s([A-Z]\\\\w+)$`
CGenServiceBriefPackages = `produce go files only for given source packages(source folders)`
CGenServiceBriefPackages = `produce go files only for given source packages`
CGenServiceBriefImportPrefix = `custom import prefix to calculate import path for generated importing go file of logic`
CGenServiceBriefClear = `delete all generated go files that are not used any further`
)
@ -95,28 +87,28 @@ const (
)
func (c CGenService) Service(ctx context.Context, in CGenServiceInput) (out *CGenServiceOutput, err error) {
in.SrcFolder = filepath.ToSlash(in.SrcFolder)
in.SrcFolder = gstr.TrimRight(in.SrcFolder, `/`)
in.WatchFile = filepath.ToSlash(in.WatchFile)
in.WatchFile = gstr.TrimRight(in.WatchFile, `/`)
// File lock to avoid multiple processes.
var (
flockFilePath = gfile.Temp("gf.cli.gen.service.lock")
flockContent = gfile.GetContents(flockFilePath)
)
if flockContent != "" {
if gtime.Timestamp()-gconv.Int64(flockContent) < genServiceFileLockSeconds {
// If another "gen service" process is running, it just exits.
mlog.Debug(`another "gen service" process is running, exit`)
return
}
}
defer gfile.Remove(flockFilePath)
_ = gfile.PutContents(flockFilePath, gtime.TimestampStr())
in.SrcFolder = gstr.TrimRight(in.SrcFolder, `\/`)
in.SrcFolder = gstr.Replace(in.SrcFolder, "\\", "/")
in.WatchFile = gstr.TrimRight(in.WatchFile, `\/`)
in.WatchFile = gstr.Replace(in.WatchFile, "\\", "/")
// Watch file handling.
if in.WatchFile != "" {
// File lock to avoid multiple processes.
var (
flockFilePath = gfile.Temp("gf.cli.gen.service.lock")
flockContent = gfile.GetContents(flockFilePath)
)
if flockContent != "" {
if gtime.Timestamp()-gconv.Int64(flockContent) < genServiceFileLockSeconds {
// If another "gen service" process is running, it just exits.
mlog.Debug(`another "gen service" process is running, exit`)
return
}
}
defer gfile.RemoveFile(flockFilePath)
_ = gfile.PutContents(flockFilePath, gtime.TimestampStr())
// It works only if given WatchFile is in SrcFolder.
var (
watchFileDir = gfile.Dir(in.WatchFile)
@ -133,10 +125,13 @@ func (c CGenService) Service(ctx context.Context, in CGenServiceInput) (out *CGe
mlog.Fatalf(`%+v`, err)
}
mlog.Debug("Chdir:", newWorkingDir)
in.WatchFile = ""
in.Packages = []string{gfile.Basename(watchFileDir)}
return c.Service(ctx, in)
_ = gfile.Remove(flockFilePath)
var command = fmt.Sprintf(
`%s gen service -packages=%s`,
gfile.SelfName(), gfile.Basename(watchFileDir),
)
err = gproc.ShellRun(ctx, command)
return
}
if !gfile.Exists(in.SrcFolder) {
@ -144,26 +139,32 @@ func (c CGenService) Service(ctx context.Context, in CGenServiceInput) (out *CGe
}
if in.ImportPrefix == "" {
in.ImportPrefix = utils.GetImportPath(in.SrcFolder)
if !gfile.Exists("go.mod") {
mlog.Fatal("ImportPrefix is empty and go.mod does not exist in current working directory")
}
var (
goModContent = gfile.GetContents("go.mod")
match, _ = gregex.MatchString(`^module\s+(.+)\s*`, goModContent)
)
if len(match) > 1 {
in.ImportPrefix = fmt.Sprintf(`%s/%s`, gstr.Trim(match[1]), gstr.Replace(in.SrcFolder, `\`, `/`))
}
}
var (
isDirty atomic.Value // Temp boolean.
isDirty bool // Temp boolean.
files []string // Temp file array.
fileContent string // Temp file content for handling go file.
initImportSrcPackages []string // Used for generating logic.go.
inputPackages = in.Packages // Custom packages.
dstPackageName = gstr.ToLower(gfile.Basename(in.DstFolder)) // Package name for generated go files.
generatedDstFilePathSet = gset.NewStrSet() // All generated file path set.
)
isDirty.Store(false)
// The first level folders.
srcFolderPaths, err := gfile.ScanDir(in.SrcFolder, "*", false)
if err != nil {
return nil, err
}
// it will use goroutine to generate service files for each package.
var wg = sync.WaitGroup{}
for _, srcFolderPath := range srcFolderPaths {
if !gfile.IsDir(srcFolderPath) {
continue
@ -175,37 +176,30 @@ func (c CGenService) Service(ctx context.Context, in CGenServiceInput) (out *CGe
if len(files) == 0 {
continue
}
// Parse single logic package folder.
var (
srcPackageName = gfile.Basename(srcFolderPath)
// StructName => FunctionDefinitions
srcPkgInterfaceMap = make(map[string]*garray.StrArray)
srcImportedPackages = garray.NewSortedStrArray().SetUnique(true)
srcStructFunctions = gmap.NewListMap()
srcPackageName = gfile.Basename(srcFolderPath)
ok bool
dstFilePath = gfile.Join(in.DstFolder,
c.getDstFileNameCase(srcPackageName, in.DstFileNameCase)+".go",
)
)
generatedDstFilePathSet.Add(dstFilePath)
// if it were to use goroutine,
// it would cause the order of the generated functions in the file to be disordered.
for _, file := range files {
pkgItems, funcItems, err := c.parseItemsInSrc(file)
fileContent = gfile.GetContents(file)
// Calculate imported packages of source go files.
err = c.calculateImportedPackages(fileContent, srcImportedPackages)
if err != nil {
return nil, err
}
// Calculate imported packages for service generating.
err = c.calculateImportedItems(in, pkgItems, funcItems, srcImportedPackages)
if err != nil {
return nil, err
}
// Calculate functions and interfaces for service generating.
err = c.calculateFuncItems(in, funcItems, srcStructFunctions)
err = c.calculateInterfaceFunctions(in, fileContent, srcPkgInterfaceMap, dstPackageName)
if err != nil {
return nil, err
}
}
initImportSrcPackages = append(
initImportSrcPackages,
fmt.Sprintf(`%s/%s`, in.ImportPrefix, srcPackageName),
@ -218,28 +212,21 @@ func (c CGenService) Service(ctx context.Context, in CGenServiceInput) (out *CGe
)
continue
}
// Generating service go file for single logic package.
wg.Add(1)
go func(generateServiceFilesInput generateServiceFilesInput) {
defer wg.Done()
ok, err := c.generateServiceFile(generateServiceFilesInput)
if err != nil {
mlog.Printf(`error generating service file "%s": %v`, generateServiceFilesInput.DstFilePath, err)
}
if !isDirty.Load().(bool) && ok {
isDirty.Store(true)
}
}(generateServiceFilesInput{
// Generating service go file for logic.
if ok, err = c.generateServiceFile(generateServiceFilesInput{
CGenServiceInput: in,
SrcPackageName: srcPackageName,
SrcStructFunctions: srcPkgInterfaceMap,
SrcImportedPackages: srcImportedPackages.Slice(),
SrcStructFunctions: srcStructFunctions,
SrcPackageName: srcPackageName,
DstPackageName: dstPackageName,
DstFilePath: dstFilePath,
})
}); err != nil {
return
}
if ok {
isDirty = true
}
}
wg.Wait()
if in.Clear {
files, err = gfile.ScanDirFile(in.DstFolder, "*.go", false)
@ -249,18 +236,16 @@ func (c CGenService) Service(ctx context.Context, in CGenServiceInput) (out *CGe
var relativeFilePath string
for _, file := range files {
relativeFilePath = gstr.SubStrFromR(file, in.DstFolder)
if !generatedDstFilePathSet.Contains(relativeFilePath) &&
utils.IsFileDoNotEdit(relativeFilePath) {
if !generatedDstFilePathSet.Contains(relativeFilePath) && utils.IsFileDoNotEdit(relativeFilePath) {
mlog.Printf(`remove no longer used service file: %s`, relativeFilePath)
if err = gfile.RemoveFile(file); err != nil {
if err = gfile.Remove(file); err != nil {
return nil, err
}
}
}
}
if isDirty.Load().(bool) {
if isDirty {
// Generate initialization go file.
if len(initImportSrcPackages) > 0 {
if err = c.generateInitializationFile(in, initImportSrcPackages); err != nil {
@ -269,71 +254,24 @@ func (c CGenService) Service(ctx context.Context, in CGenServiceInput) (out *CGe
}
// Replace v1 to v2 for GoFrame.
if err = utils.ReplaceGeneratedContentGFV2(in.DstFolder); err != nil {
if err = c.replaceGeneratedServiceContentGFV2(in); err != nil {
return nil, err
}
mlog.Printf(`gofmt go files in "%s"`, in.DstFolder)
utils.GoFmt(in.DstFolder)
}
// auto update main.go.
if err = c.checkAndUpdateMain(in.SrcFolder); err != nil {
return nil, err
}
mlog.Print(`done!`)
return
}
func (c CGenService) checkAndUpdateMain(srcFolder string) (err error) {
var (
logicPackageName = gstr.ToLower(gfile.Basename(srcFolder))
logicFilePath = gfile.Join(srcFolder, logicPackageName+".go")
importPath = utils.GetImportPath(srcFolder)
importStr = fmt.Sprintf(`_ "%s"`, importPath)
mainFilePath = gfile.Join(gfile.Dir(gfile.Dir(gfile.Dir(logicFilePath))), "main.go")
mainFileContent = gfile.GetContents(mainFilePath)
)
// No main content found.
if mainFileContent == "" {
return nil
}
if gstr.Contains(mainFileContent, importStr) {
return nil
}
match, err := gregex.MatchString(`import \(([\s\S]+?)\)`, mainFileContent)
if err != nil {
return err
}
// No match.
if len(match) < 2 {
return nil
}
lines := garray.NewStrArrayFrom(gstr.Split(match[1], "\n"))
for i, line := range lines.Slice() {
line = gstr.Trim(line)
if len(line) == 0 {
continue
func (c CGenService) replaceGeneratedServiceContentGFV2(in CGenServiceInput) (err error) {
return gfile.ReplaceDirFunc(func(path, content string) string {
if gstr.Contains(content, `"github.com/gogf/gf`) && !gstr.Contains(content, `"github.com/gogf/gf/v2`) {
content = gstr.Replace(content, `"github.com/gogf/gf"`, `"github.com/gogf/gf/v2"`)
content = gstr.Replace(content, `"github.com/gogf/gf/`, `"github.com/gogf/gf/v2/`)
return content
}
if line[0] == '_' {
continue
}
// Insert the logic import into imports.
if err = lines.InsertBefore(i, fmt.Sprintf("\t%s\n\n", importStr)); err != nil {
return err
}
break
}
mainFileContent, err = gregex.ReplaceString(
`import \(([\s\S]+?)\)`,
fmt.Sprintf(`import (%s)`, lines.Join("\n")),
mainFileContent,
)
if err != nil {
return err
}
mlog.Print(`update main.go`)
err = gfile.PutContents(mainFilePath, mainFileContent)
utils.GoFmt(mainFilePath)
return
return content
}, in.DstFolder, "*.go", false)
}

View File

@ -1,221 +0,0 @@
// Copyright GoFrame gf Author(https://goframe.org). All Rights Reserved.
//
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with this file,
// You can obtain one at https://github.com/gogf/gf.
package genservice
import (
"go/ast"
"go/parser"
"go/token"
"github.com/gogf/gf/v2/os/gfile"
"github.com/gogf/gf/v2/text/gstr"
)
type pkgItem struct {
Alias string `eg:"gdbas"`
Path string `eg:"github.com/gogf/gf/v2/database/gdb"`
RawImport string `eg:"gdbas github.com/gogf/gf/v2/database/gdb"`
}
type funcItem struct {
Receiver string `eg:"sUser"`
MethodName string `eg:"GetList"`
Params []map[string]string `eg:"ctx: context.Context, cond: *SearchInput"`
Results []map[string]string `eg:"list: []*User, err: error"`
Comment string `eg:"Get user list"`
}
// parseItemsInSrc parses the pkgItem and funcItem from the specified file.
// It can't skip the private methods.
// It can't skip the imported packages of import alias equal to `_`.
func (c CGenService) parseItemsInSrc(filePath string) (pkgItems []pkgItem, funcItems []funcItem, err error) {
var (
fileContent = gfile.GetContents(filePath)
fileSet = token.NewFileSet()
)
node, err := parser.ParseFile(fileSet, "", fileContent, parser.ParseComments)
if err != nil {
return
}
ast.Inspect(node, func(n ast.Node) bool {
switch x := n.(type) {
case *ast.ImportSpec:
// parse the imported packages.
pkgItems = append(pkgItems, c.parseImportPackages(x))
case *ast.FuncDecl:
// parse the function items.
if x.Recv == nil {
return true
}
var funcName = x.Name.Name
funcItems = append(funcItems, funcItem{
Receiver: c.parseFuncReceiverTypeName(x),
MethodName: funcName,
Params: c.parseFuncParams(x),
Results: c.parseFuncResults(x),
Comment: c.parseFuncComment(x),
})
}
return true
})
return
}
// parseImportPackages retrieves the imported packages from the specified ast.ImportSpec.
func (c CGenService) parseImportPackages(node *ast.ImportSpec) (packages pkgItem) {
if node.Path == nil {
return
}
var (
alias string
path = node.Path.Value
rawImport string
)
if node.Name != nil {
alias = node.Name.Name
rawImport = node.Name.Name + " " + path
} else {
rawImport = path
}
// if the alias is empty, it will further retrieve the real alias.
if alias == "" {
alias = c.getRealAlias(path)
}
return pkgItem{
Alias: alias,
Path: path,
RawImport: rawImport,
}
}
// getRealAlias retrieves the real alias of the package.
// If package is "github.com/gogf/gf", the alias is "gf".
// If package is "github.com/gogf/gf/v2", the alias is "gf" instead of "v2".
func (c CGenService) getRealAlias(importPath string) (pkgName string) {
importPath = gstr.Trim(importPath, `"`)
parts := gstr.Split(importPath, "/")
if len(parts) == 0 {
return
}
pkgName = parts[len(parts)-1]
if !gstr.HasPrefix(pkgName, "v") {
return pkgName
}
if len(parts) < 2 {
return pkgName
}
if gstr.IsNumeric(gstr.SubStr(pkgName, 1)) {
pkgName = parts[len(parts)-2]
}
return pkgName
}
// parseFuncReceiverTypeName retrieves the receiver type of the function.
// For example:
//
// func(s *sArticle) -> *sArticle
// func(s sArticle) -> sArticle
func (c CGenService) parseFuncReceiverTypeName(node *ast.FuncDecl) (receiverType string) {
if node.Recv == nil {
return ""
}
receiverType, err := c.astExprToString(node.Recv.List[0].Type)
if err != nil {
return ""
}
return
}
// parseFuncParams retrieves the input parameters of the function.
// It returns the name and type of the input parameters.
// For example:
//
// []map[string]string{paramName:ctx paramType:context.Context, paramName:info paramType:struct{}}
func (c CGenService) parseFuncParams(node *ast.FuncDecl) (params []map[string]string) {
if node.Type.Params == nil {
return
}
for _, param := range node.Type.Params.List {
if param.Names == nil {
// No name for the return value.
resultType, err := c.astExprToString(param.Type)
if err != nil {
continue
}
params = append(params, map[string]string{
"paramName": "",
"paramType": resultType,
})
continue
}
for _, name := range param.Names {
paramType, err := c.astExprToString(param.Type)
if err != nil {
continue
}
params = append(params, map[string]string{
"paramName": name.Name,
"paramType": paramType,
})
}
}
return
}
// parseFuncResults retrieves the output parameters of the function.
// It returns the name and type of the output parameters.
// For example:
//
// []map[string]string{resultName:list resultType:[]*User, resultName:err resultType:error}
// []map[string]string{resultName: "", resultType: error}
func (c CGenService) parseFuncResults(node *ast.FuncDecl) (results []map[string]string) {
if node.Type.Results == nil {
return
}
for _, result := range node.Type.Results.List {
if result.Names == nil {
// No name for the return value.
resultType, err := c.astExprToString(result.Type)
if err != nil {
continue
}
results = append(results, map[string]string{
"resultName": "",
"resultType": resultType,
})
continue
}
for _, name := range result.Names {
resultType, err := c.astExprToString(result.Type)
if err != nil {
continue
}
results = append(results, map[string]string{
"resultName": name.Name,
"resultType": resultType,
})
}
}
return
}
// parseFuncComment retrieves the comment of the function.
func (c CGenService) parseFuncComment(node *ast.FuncDecl) string {
return c.astCommentToString(node.Doc)
}

View File

@ -1,48 +0,0 @@
// Copyright GoFrame gf Author(https://goframe.org). All Rights Reserved.
//
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with this file,
// You can obtain one at https://github.com/gogf/gf.
package genservice
import (
"bytes"
"go/ast"
"go/format"
"go/token"
"strings"
)
// exprToString converts ast.Expr to string.
// For example:
//
// ast.Expr -> "context.Context"
// ast.Expr -> "*v1.XxxReq"
// ast.Expr -> "error"
// ast.Expr -> "int"
func (c CGenService) astExprToString(expr ast.Expr) (string, error) {
var (
buf bytes.Buffer
err error
)
err = format.Node(&buf, token.NewFileSet(), expr)
if err != nil {
return "", err
}
return buf.String(), nil
}
// astCommentToString returns the raw (original) text of the comment.
// It includes the comment markers (//, /*, and */).
// It adds a newline at the end of the comment.
func (c CGenService) astCommentToString(node *ast.CommentGroup) string {
if node == nil {
return ""
}
var b strings.Builder
for _, c := range node.List {
b.WriteString(c.Text + "\n")
}
return b.String()
}

View File

@ -1,152 +1,105 @@
// Copyright GoFrame gf Author(https://goframe.org). All Rights Reserved.
//
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with this file,
// You can obtain one at https://github.com/gogf/gf.
package genservice
import (
"fmt"
"strings"
"go/parser"
"go/token"
"github.com/gogf/gf/cmd/gf/v2/internal/utility/mlog"
"github.com/gogf/gf/v2/container/garray"
"github.com/gogf/gf/v2/container/gmap"
"github.com/gogf/gf/v2/os/gfile"
"github.com/gogf/gf/v2/text/gregex"
"github.com/gogf/gf/v2/text/gstr"
)
func (c CGenService) calculateImportedItems(
in CGenServiceInput,
pkgItems []pkgItem, funcItems []funcItem,
srcImportedPackages *garray.SortedStrArray,
) (err error) {
// allFuncParamType saves all the param and result types of the functions.
var allFuncParamType strings.Builder
for _, item := range funcItems {
for _, param := range item.Params {
allFuncParamType.WriteString(param["paramType"] + ",")
}
for _, result := range item.Results {
allFuncParamType.WriteString(result["resultType"] + ",")
}
func (c CGenService) calculateImportedPackages(fileContent string, srcImportedPackages *garray.SortedStrArray) (err error) {
f, err := parser.ParseFile(token.NewFileSet(), "", fileContent, parser.ImportsOnly)
if err != nil {
return err
}
for _, item := range pkgItems {
alias := item.Alias
// If the alias is _, it means that the package is not generated.
if alias == "_" {
mlog.Debugf(`ignore anonymous package: %s`, item.RawImport)
continue
for _, s := range f.Imports {
if s.Path != nil {
if s.Name != nil {
// If it has alias, and it is not `_`.
if pkgAlias := s.Name.String(); pkgAlias != "_" {
srcImportedPackages.Add(pkgAlias + " " + s.Path.Value)
}
} else {
// no alias
srcImportedPackages.Add(s.Path.Value)
}
}
// If the alias is empty, it will use the package name as the alias.
if alias == "" {
alias = gfile.Basename(gstr.Trim(item.Path, `"`))
}
if !gstr.Contains(allFuncParamType.String(), alias) {
mlog.Debugf(`ignore unused package: %s`, item.RawImport)
continue
}
srcImportedPackages.Add(item.RawImport)
}
return nil
}
func (c CGenService) calculateFuncItems(
in CGenServiceInput,
funcItems []funcItem,
srcPkgInterfaceMap *gmap.ListMap,
func (c CGenService) calculateInterfaceFunctions(
in CGenServiceInput, fileContent string, srcPkgInterfaceMap map[string]*garray.StrArray, dstPackageName string,
) (err error) {
var srcPkgInterfaceFunc []map[string]string
for _, item := range funcItems {
var (
ok bool
matches [][]string
srcPkgInterfaceFuncArray *garray.StrArray
)
// calculate struct name and its functions according function definitions.
matches, err = gregex.MatchAllString(`func \((.+?)\) ([\s\S]+?) {`, fileContent)
if err != nil {
return err
}
for _, match := range matches {
var (
// eg: "sArticle"
receiverName string
receiverMatch []string
// eg: "GetList(ctx context.Context, req *v1.ArticleListReq) (list []*v1.Article, err error)"
funcHead string
structName string
structMatch []string
funcReceiver = gstr.Trim(match[1])
receiverArray = gstr.SplitAndTrim(funcReceiver, " ")
functionHead = gstr.Trim(gstr.Replace(match[2], "\n", ""))
)
if len(receiverArray) > 1 {
structName = receiverArray[1]
} else {
structName = receiverArray[0]
}
structName = gstr.Trim(structName, "*")
// handle the receiver name.
if item.Receiver == "" {
// Case of:
// Xxx(\n ctx context.Context, req *v1.XxxReq,\n) -> Xxx(ctx context.Context, req *v1.XxxReq)
functionHead = gstr.Replace(functionHead, `,)`, `)`)
functionHead, _ = gregex.ReplaceString(`\(\s+`, `(`, functionHead)
functionHead, _ = gregex.ReplaceString(`\s{2,}`, ` `, functionHead)
if !gstr.IsLetterUpper(functionHead[0]) {
continue
}
receiverName = item.Receiver
receiverName = gstr.Trim(receiverName, "*")
// Match and pick the struct name from receiver.
if receiverMatch, err = gregex.MatchString(in.StPattern, receiverName); err != nil {
if structMatch, err = gregex.MatchString(in.StPattern, structName); err != nil {
return err
}
if len(receiverMatch) < 1 {
if len(structMatch) < 1 {
continue
}
receiverName = gstr.CaseCamel(receiverMatch[1])
// check if the func name is public.
if !gstr.IsLetterUpper(item.MethodName[0]) {
structName = gstr.CaseCamel(structMatch[1])
if srcPkgInterfaceFuncArray, ok = srcPkgInterfaceMap[structName]; !ok {
srcPkgInterfaceMap[structName] = garray.NewStrArray()
srcPkgInterfaceFuncArray = srcPkgInterfaceMap[structName]
}
srcPkgInterfaceFuncArray.Append(functionHead)
}
// calculate struct name according type definitions.
matches, err = gregex.MatchAllString(`type (.+) struct\s*{`, fileContent)
if err != nil {
return err
}
for _, match := range matches {
var (
structName string
structMatch []string
)
if structMatch, err = gregex.MatchString(in.StPattern, match[1]); err != nil {
return err
}
if len(structMatch) < 1 {
continue
}
if !srcPkgInterfaceMap.Contains(receiverName) {
srcPkgInterfaceFunc = make([]map[string]string, 0)
srcPkgInterfaceMap.Set(receiverName, srcPkgInterfaceFunc)
} else {
srcPkgInterfaceFunc = srcPkgInterfaceMap.Get(receiverName).([]map[string]string)
structName = gstr.CaseCamel(structMatch[1])
if srcPkgInterfaceFuncArray, ok = srcPkgInterfaceMap[structName]; !ok {
srcPkgInterfaceMap[structName] = garray.NewStrArray()
}
// make the func head.
paramsStr := c.tidyParam(item.Params)
resultsStr := c.tidyResult(item.Results)
funcHead = fmt.Sprintf("%s(%s) (%s)", item.MethodName, paramsStr, resultsStr)
srcPkgInterfaceFunc = append(srcPkgInterfaceFunc, map[string]string{
"funcHead": funcHead,
"funcComment": item.Comment,
})
srcPkgInterfaceMap.Set(receiverName, srcPkgInterfaceFunc)
}
return nil
}
// tidyParam tidies the input parameters.
// For example:
//
// []map[string]string{paramName:ctx paramType:context.Context, paramName:info paramType:struct{}}
// -> ctx context.Context, info struct{}
func (c CGenService) tidyParam(paramSlice []map[string]string) (paramStr string) {
for i, param := range paramSlice {
if i > 0 {
paramStr += ", "
}
paramStr += fmt.Sprintf("%s %s", param["paramName"], param["paramType"])
}
return
}
// tidyResult tidies the output parameters.
// For example:
//
// []map[string]string{resultName:list resultType:[]*User, resultName:err resultType:error}
// -> list []*User, err error
//
// []map[string]string{resultName: "", resultType: error}
// -> error
func (c CGenService) tidyResult(resultSlice []map[string]string) (resultStr string) {
for i, result := range resultSlice {
if i > 0 {
resultStr += ", "
}
if result["resultName"] != "" {
resultStr += fmt.Sprintf("%s %s", result["resultName"], result["resultType"])
} else {
resultStr += result["resultType"]
}
}
return
}

View File

@ -1,40 +1,97 @@
// Copyright GoFrame gf Author(https://goframe.org). All Rights Reserved.
//
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with this file,
// You can obtain one at https://github.com/gogf/gf.
package genservice
import (
"bytes"
"fmt"
"github.com/gogf/gf/v2/container/garray"
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/os/gfile"
"github.com/gogf/gf/v2/text/gregex"
"github.com/gogf/gf/v2/text/gstr"
"github.com/gogf/gf/cmd/gf/v2/internal/consts"
"github.com/gogf/gf/cmd/gf/v2/internal/utility/mlog"
"github.com/gogf/gf/cmd/gf/v2/internal/utility/utils"
"github.com/gogf/gf/v2/container/gmap"
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/os/gfile"
"github.com/gogf/gf/v2/text/gstr"
)
type generateServiceFilesInput struct {
CGenServiceInput
SrcPackageName string
SrcImportedPackages []string
SrcStructFunctions *gmap.ListMap
DstPackageName string
DstFilePath string // Absolute file path for generated service go file.
SrcStructFunctions map[string]*garray.StrArray
SrcImportedPackages []string
SrcPackageName string
DstPackageName string
}
func (c CGenService) generateServiceFile(in generateServiceFilesInput) (ok bool, err error) {
var generatedContent bytes.Buffer
var (
generatedContent string
allFuncArray = garray.NewStrArray() // Used for check whether interface dirty, going to change file content.
importedPackagesContent = fmt.Sprintf(
"import (\n%s\n)", gstr.Join(in.SrcImportedPackages, "\n"),
)
)
generatedContent += gstr.ReplaceByMap(consts.TemplateGenServiceContentHead, g.MapStrStr{
"{Imports}": importedPackagesContent,
"{PackageName}": in.DstPackageName,
})
c.generatePackageImports(&generatedContent, in.DstPackageName, in.SrcImportedPackages)
c.generateType(&generatedContent, in.SrcStructFunctions, in.DstPackageName)
c.generateVar(&generatedContent, in.SrcStructFunctions)
c.generateFunc(&generatedContent, in.SrcStructFunctions)
// Type definitions.
generatedContent += "type("
generatedContent += "\n"
for structName, funcArray := range in.SrcStructFunctions {
allFuncArray.Append(funcArray.Slice()...)
generatedContent += gstr.Trim(gstr.ReplaceByMap(consts.TemplateGenServiceContentInterface, g.MapStrStr{
"{InterfaceName}": "I" + structName,
"{FuncDefinition}": funcArray.Join("\n\t"),
}))
generatedContent += "\n"
}
generatedContent += ")"
generatedContent += "\n"
// Generating variable and register definitions.
var (
variableContent string
generatingInterfaceCheck string
)
// Variable definitions.
for structName, _ := range in.SrcStructFunctions {
generatingInterfaceCheck = fmt.Sprintf(`[^\w\d]+%s.I%s[^\w\d]`, in.DstPackageName, structName)
if gregex.IsMatchString(generatingInterfaceCheck, generatedContent) {
continue
}
variableContent += gstr.Trim(gstr.ReplaceByMap(consts.TemplateGenServiceContentVariable, g.MapStrStr{
"{StructName}": structName,
"{InterfaceName}": "I" + structName,
}))
variableContent += "\n"
}
if variableContent != "" {
generatedContent += "var("
generatedContent += "\n"
generatedContent += variableContent
generatedContent += ")"
generatedContent += "\n"
}
// Variable register function definitions.
for structName, _ := range in.SrcStructFunctions {
generatingInterfaceCheck = fmt.Sprintf(`[^\w\d]+%s.I%s[^\w\d]`, in.DstPackageName, structName)
if gregex.IsMatchString(generatingInterfaceCheck, generatedContent) {
continue
}
generatedContent += gstr.Trim(gstr.ReplaceByMap(consts.TemplateGenServiceContentRegister, g.MapStrStr{
"{StructName}": structName,
"{InterfaceName}": "I" + structName,
}))
generatedContent += "\n\n"
}
// Replace empty braces that have new line.
generatedContent, _ = gregex.ReplaceString(`{[\s\t]+}`, `{}`, generatedContent)
// Remove package name calls of `dstPackageName` in produced codes.
generatedContent, _ = gregex.ReplaceString(fmt.Sprintf(`\*{0,1}%s\.`, in.DstPackageName), ``, generatedContent)
// Write file content to disk.
if gfile.Exists(in.DstFilePath) {
@ -42,45 +99,101 @@ func (c CGenService) generateServiceFile(in generateServiceFilesInput) (ok bool,
mlog.Printf(`ignore file as it is manually maintained: %s`, in.DstFilePath)
return false, nil
}
if !c.isToGenerateServiceGoFile(in.DstPackageName, in.DstFilePath, allFuncArray) {
mlog.Printf(`not dirty, ignore generating service go file: %s`, in.DstFilePath)
return false, nil
}
}
mlog.Printf(`generating service go file: %s`, in.DstFilePath)
if err = gfile.PutBytes(in.DstFilePath, generatedContent.Bytes()); err != nil {
if err = gfile.PutContents(in.DstFilePath, generatedContent); err != nil {
return true, err
}
return true, nil
}
// generateInitializationFile generates `logic.go`.
// isToGenerateServiceGoFile checks and returns whether the service content dirty.
func (c CGenService) isToGenerateServiceGoFile(dstPackageName, filePath string, funcArray *garray.StrArray) bool {
var (
fileContent = gfile.GetContents(filePath)
generatedFuncArray = garray.NewSortedStrArrayFrom(funcArray.Slice())
contentFuncArray = garray.NewSortedStrArray()
)
if fileContent == "" {
return true
}
matches, _ := gregex.MatchAllString(`\s+interface\s+{([\s\S]+?)}`, fileContent)
for _, match := range matches {
contentFuncArray.Append(gstr.SplitAndTrim(match[1], "\n")...)
}
if generatedFuncArray.Len() != contentFuncArray.Len() {
mlog.Debugf(
`dirty, generatedFuncArray.Len()[%d] != contentFuncArray.Len()[%d]`,
generatedFuncArray.Len(), contentFuncArray.Len(),
)
return true
}
var funcDefinition string
for i := 0; i < generatedFuncArray.Len(); i++ {
funcDefinition, _ = gregex.ReplaceString(
fmt.Sprintf(`\*{0,1}%s\.`, dstPackageName), ``, generatedFuncArray.At(i),
)
if funcDefinition != contentFuncArray.At(i) {
mlog.Debugf(`dirty, %s != %s`, funcDefinition, contentFuncArray.At(i))
return true
}
}
return false
}
func (c CGenService) generateInitializationFile(in CGenServiceInput, importSrcPackages []string) (err error) {
var (
logicPackageName = gstr.ToLower(gfile.Basename(in.SrcFolder))
logicFilePath = gfile.Join(in.SrcFolder, logicPackageName+".go")
logicImports string
srcPackageName = gstr.ToLower(gfile.Basename(in.SrcFolder))
srcFilePath = gfile.Join(in.SrcFolder, srcPackageName+".go")
srcImports string
generatedContent string
)
if !utils.IsFileDoNotEdit(logicFilePath) {
mlog.Debugf(`ignore file as it is manually maintained: %s`, logicFilePath)
if !utils.IsFileDoNotEdit(srcFilePath) {
mlog.Debugf(`ignore file as it is manually maintained: %s`, srcFilePath)
return nil
}
for _, importSrcPackage := range importSrcPackages {
logicImports += fmt.Sprintf(`%s_ "%s"%s`, "\t", importSrcPackage, "\n")
srcImports += fmt.Sprintf(`%s_ "%s"%s`, "\t", importSrcPackage, "\n")
}
generatedContent = gstr.ReplaceByMap(consts.TemplateGenServiceLogicContent, g.MapStrStr{
"{PackageName}": logicPackageName,
"{Imports}": logicImports,
"{PackageName}": srcPackageName,
"{Imports}": srcImports,
})
mlog.Printf(`generating init go file: %s`, logicFilePath)
if err = gfile.PutContents(logicFilePath, generatedContent); err != nil {
mlog.Printf(`generating init go file: %s`, srcFilePath)
if err = gfile.PutContents(srcFilePath, generatedContent); err != nil {
return err
}
utils.GoFmt(logicFilePath)
utils.GoFmt(srcFilePath)
return nil
}
// getDstFileNameCase call gstr.Case* function to convert the s to specified case.
func (c CGenService) getDstFileNameCase(str, caseStr string) (newStr string) {
if newStr := gstr.CaseConvert(str, gstr.CaseTypeMatch(caseStr)); newStr != str {
return newStr
func (c CGenService) getDstFileNameCase(str, caseStr string) string {
switch gstr.ToLower(caseStr) {
case gstr.ToLower("Lower"):
return gstr.ToLower(str)
case gstr.ToLower("Camel"):
return gstr.CaseCamel(str)
case gstr.ToLower("CamelLower"):
return gstr.CaseCamelLower(str)
case gstr.ToLower("Kebab"):
return gstr.CaseKebab(str)
case gstr.ToLower("KebabScreaming"):
return gstr.CaseKebabScreaming(str)
case gstr.ToLower("SnakeFirstUpper"):
return gstr.CaseSnakeFirstUpper(str)
case gstr.ToLower("SnakeScreaming"):
return gstr.CaseSnakeScreaming(str)
}
return gstr.CaseSnake(str)
}

View File

@ -1,104 +0,0 @@
// Copyright GoFrame gf Author(https://goframe.org). All Rights Reserved.
//
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with this file,
// You can obtain one at https://github.com/gogf/gf.
package genservice
import (
"bytes"
"fmt"
"github.com/gogf/gf/cmd/gf/v2/internal/consts"
"github.com/gogf/gf/v2/container/gmap"
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/text/gregex"
"github.com/gogf/gf/v2/text/gstr"
)
func (c CGenService) generatePackageImports(generatedContent *bytes.Buffer, packageName string, imports []string) {
generatedContent.WriteString(gstr.ReplaceByMap(consts.TemplateGenServiceContentHead, g.MapStrStr{
"{PackageName}": packageName,
"{Imports}": fmt.Sprintf(
"import (\n%s\n)", gstr.Join(imports, "\n"),
),
}))
}
// generateType type definitions.
// See: const.TemplateGenServiceContentInterface
func (c CGenService) generateType(generatedContent *bytes.Buffer, srcStructFunctions *gmap.ListMap, dstPackageName string) {
generatedContent.WriteString("type(")
generatedContent.WriteString("\n")
srcStructFunctions.Iterator(func(key, value interface{}) bool {
var (
funcContents = make([]string, 0)
funcContent string
)
structName, funcSlice := key.(string), value.([]map[string]string)
// Generating interface content.
for _, funcInfo := range funcSlice {
// Remove package name calls of `dstPackageName` in produced codes.
funcHead, _ := gregex.ReplaceString(
fmt.Sprintf(`\*{0,1}%s\.`, dstPackageName),
``, funcInfo["funcHead"],
)
funcContent = funcInfo["funcComment"] + funcHead
funcContents = append(funcContents, funcContent)
}
// funcContents to string.
generatedContent.WriteString(
gstr.Trim(gstr.ReplaceByMap(consts.TemplateGenServiceContentInterface, g.MapStrStr{
"{InterfaceName}": "I" + structName,
"{FuncDefinition}": gstr.Join(funcContents, "\n\t"),
})),
)
generatedContent.WriteString("\n")
return true
})
generatedContent.WriteString(")")
generatedContent.WriteString("\n")
}
// generateVar variable definitions.
// See: const.TemplateGenServiceContentVariable
func (c CGenService) generateVar(generatedContent *bytes.Buffer, srcStructFunctions *gmap.ListMap) {
// Generating variable and register definitions.
var variableContent string
srcStructFunctions.Iterator(func(key, value interface{}) bool {
structName := key.(string)
variableContent += gstr.Trim(gstr.ReplaceByMap(consts.TemplateGenServiceContentVariable, g.MapStrStr{
"{StructName}": structName,
"{InterfaceName}": "I" + structName,
}))
variableContent += "\n"
return true
})
if variableContent != "" {
generatedContent.WriteString("var(")
generatedContent.WriteString("\n")
generatedContent.WriteString(variableContent)
generatedContent.WriteString(")")
generatedContent.WriteString("\n")
}
}
// generateFunc function definitions.
// See: const.TemplateGenServiceContentRegister
func (c CGenService) generateFunc(generatedContent *bytes.Buffer, srcStructFunctions *gmap.ListMap) {
// Variable register function definitions.
srcStructFunctions.Iterator(func(key, value interface{}) bool {
structName := key.(string)
generatedContent.WriteString(gstr.Trim(gstr.ReplaceByMap(consts.TemplateGenServiceContentRegister, g.MapStrStr{
"{StructName}": structName,
"{InterfaceName}": "I" + structName,
})))
generatedContent.WriteString("\n\n")
return true
})
}

Some files were not shown because too many files have changed in this diff Show More