├── .gitignore
├── LICENSE
├── Makefile
├── README.EN.md
├── README.md
├── ai
├── ai_chat.go
├── common_response.go
├── deepseek.go
├── deepseek_test.go
├── prompt
│ ├── git_diff_summary.prompt
│ ├── git_log_summary.prompt
│ └── prompt_init.go
├── siliconflow.go
└── siliconflow_test.go
├── cmd
├── env.go
├── es.go
├── fileserver.go
├── format.go
├── fsync.go
├── git.go
├── md5.go
├── navicat.go
├── root.go
├── sqllog.go
├── url.go
└── version.go
├── config
├── config.go
├── config_template.yml
└── config_test.go
├── doc
└── img
│ ├── gcmsg.webp
│ ├── glog.webp
│ └── sqllog.webp
├── go.mod
├── go.sum
├── handler
├── env
│ ├── env_variable.go
│ └── env_variable_test.go
├── es
│ ├── handle_select.go
│ └── handle_select_test.go
├── fileserver
│ ├── file_server.go
│ └── file_server_test.go
├── gitcmd
│ ├── common.go
│ ├── common_test.go
│ ├── gcl.go
│ ├── gcl_test.go
│ ├── gcms.go
│ ├── gcms_test.go
│ ├── git_cmd_tpl.go
│ ├── gitlog.go
│ ├── gitlog_test.go
│ ├── gitpull.go
│ ├── gitpull_test.go
│ ├── glog_output.go
│ ├── gst.go
│ ├── gst_test.go
│ ├── gsum.go
│ └── gsum_test.go
├── navicat
│ ├── handle_ncx.go
│ └── handle_ncx_test.go
├── sql
│ ├── base_parseddl.go
│ ├── base_parseddl_test.go
│ ├── iparseddl.go
│ ├── render_gostruct.go
│ ├── render_javabean.go
│ └── render_json.go
├── sqllog
│ ├── sql_log.go
│ └── sql_log_test.go
├── sync
│ ├── file_sync.go
│ └── file_sync_test.go
└── url
│ ├── url_fmt.go
│ └── url_fmt_test.go
├── main.go
├── templates
├── format_go.tpl
├── format_java.tpl
└── init_templates.go
├── testdata
├── diff.log
└── github.png
└── utils
├── cmderutils.go
├── copyutils.go
├── iputils.go
├── osutils.go
├── osutils_test.go
├── pathutil.go
├── sql2objtypeutils.go
├── stringutils.go
└── tplfunction.go
/.gitignore:
--------------------------------------------------------------------------------
1 | # Binaries for programs and plugins
2 | *.exe
3 | *.exe~
4 | *.dll
5 | *.so
6 | *.dylib
7 |
8 | # Test binary, built with `go test -c`
9 | *.test
10 |
11 | # Output of the go coverage tool, specifically when used with LiteIDE
12 | *.out
13 |
14 | # Dependency directories (remove the comment below to include it)
15 | # vendor/
16 |
17 | # idea
18 | .idea
19 | .DS_Store
20 | # ignore generate file
21 | **/lwe-generate-file
22 | *.tar.gz
23 | /lwe
24 | /lwe_linux
25 | /lwe_mac
26 |
27 | **/out/
28 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2023 yesAnd
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/Makefile:
--------------------------------------------------------------------------------
1 | BINARY_NAME=lwe
2 |
3 | build:
4 | GOARCH=amd64 GOOS=darwin go build -ldflags "-s -w" -o ${BINARY_NAME}_mac main.go
5 | GOARCH=amd64 GOOS=linux go build -ldflags "-s -w" -o ${BINARY_NAME}_linux main.go
6 | GOARCH=amd64 GOOS=windows go build -ldflags "-s -w" -o ${BINARY_NAME}_win64.exe main.go
7 | run:
8 | ./${BINARY_NAME}
9 |
10 | release:
11 | # Clean
12 | go clean
13 | rm -rf ./*.gz
14 |
15 | # Build for mac
16 | GOARCH=amd64 GOOS=darwin go build -ldflags "-s -w" -o ${BINARY_NAME}_mac main.go
17 | tar czvf ${BINARY_NAME}_mac_${VERSION}.tar.gz ./${BINARY_NAME}_mac
18 | rm -rf ./${BINARY_NAME}_mac
19 |
20 | # Build for linux
21 | go clean
22 | GOARCH=amd64 GOOS=linux go build -ldflags "-s -w" -o ${BINARY_NAME}_linux main.go
23 | tar czvf ${BINARY_NAME}_linux_${VERSION}.tar.gz ./${BINARY_NAME}_linux
24 | rm -rf ./${BINARY_NAME}_linux
25 |
26 | # Build for win
27 | go clean
28 | GOARCH=amd64 GOOS=windows go build -ldflags "-s -w" -o ${BINARY_NAME}_win64.exe main.go
29 | tar czvf ${BINARY_NAME}_win64_${VERSION}.tar.gz ./${BINARY_NAME}_win64.exe
30 | rm -rf ./${BINARY_NAME}_win64.exe
31 | go clean
32 |
33 | clean:
34 | go clean
35 | rm -rf ./${BINARY_NAME}_mac
36 | rm -rf ./${BINARY_NAME}_linux
37 | rm -rf ./${BINARY_NAME}_win64.exe
38 | rm -rf ./*.gz
39 |
40 | #test all case
41 | testCase:
42 | go test ./...
43 |
44 | #install to mac
45 | installMac:
46 | go build
47 | sudo -S rm /usr/local/bin/lwe
48 | sudo cp ./lwe /usr/local/bin/
49 |
50 |
51 | # 发版命令
52 | # make release VERSION=1.0.0
--------------------------------------------------------------------------------
/README.EN.md:
--------------------------------------------------------------------------------
1 | **[中文文档](README.md)**
2 |
3 | ## lwe
4 | LWE stands for "Leave Work Early," which is a lighthearted way of saying "finish work early"! 🤣🤣🤣
5 | It is a cross-platform command-line tool designed to help developers increase work efficiency. Of course, it's also suitable for those who want to use it as a project to learn Go!
6 |
7 | In short, feel free to submit issues, fun features, or usage feedback. It would be even better if you could directly participate in the project through Pull Requests. Let's all work together and strive for an early finish!!! 💪💪💪
8 |
9 | ## Features
10 |
11 | [Enhanced Git operations for multiple repositories: glog, gl, gcl, gst](#git)
12 |
13 | [Conversion from SQL statements to Java Beans, Go structures, JSON, etc.](#fmt)
14 |
15 | [Transformation of SQL statements into ElasticSearch query DSL language](#es)
16 |
17 | [PDF tools: merging multiple images or PDFs, extracting specific pages from PDFs](#pdf)
18 |
19 | [Other utilities](#other)
20 | - Retrieving passwords from Navicat connection configurations
21 | - Synchronizing files between two directories
22 | - Show the environmental variables you configured
23 | - Formatting request URLs
24 |
25 | ## Installation
26 |
27 | Download the compiled executable files from the [release](https://github.com/yesAnd92/lwe/releases)
28 |
29 | Usually,the more recommended approach is to configure the binary file to your environment variables, allowing you to use it anytime, anywhere.
30 |
31 | For more installation methods and notes, please refer to the [Wiki](https://github.com/yesAnd92/lwe/wiki/0.%E5%AE%89%E8%A3%85%E3%80%81%E9%85%8D%E7%BD%AE%E4%BD%BF%E7%94%A8)
32 |
33 | ## Usage
34 | You can input `lwe` to view the usage of the LWE commands, including the subcommands and their respective functionality descriptions.
35 |
36 | If you're interested in a specific subcommand, you can use the `-h` flag to see usage examples, such as `lwe glog -h`.
37 |
38 |
Git Enhanced Operations for Multiple Repositories: glog, gl, gcl, gst
39 | Here are several enhanced commands centered around Git, essentially adding cross-repository operations to the original semantics.
40 |
41 | For detailed usage of Git enhanced features, please refer to the [Wiki](https://github.com/yesAnd92/lwe/wiki/3.Git%E5%A2%9E%E5%BC%BA%E5%8A%9F%E8%83%BD)
42 |
43 | #### glog: Enhances Git log functionality
44 | It allows you to view the commit logs of all Git repositories in a given directory. Developers often work on multiple Git repositories and may need to check the commit logs of several repositories at the same time. The `glog` subcommand comes in handy for such scenarios.
45 |
46 | usage:
47 | ```
48 | lwe glog [git repo dir] [-a=yesAnd] [-n=50] [-s=2023-08-04] [-e=2023-08-04]
49 | ```
50 |
51 | #### gl: Enhances the code pulling feature
52 | Pulls the latest code from all Git repositories in a given directory (using `git pull --rebase`).
53 |
54 | usage:
55 | ```
56 | lwe gl [git repo dir]
57 | ```
58 |
59 | #### gcl: Enhances the `git clone` feature
60 | usage:
61 | ```
62 | lwe gcl gitGroupUrl [dir for this git group] -t=yourToken
63 | ```
64 |
65 | #### gst: Views the status of all Git repositories in a specified directory
66 | usage:
67 | ```
68 | lwe gst [your git repo dir]
69 | ```
70 |
71 | SQL Statement Generation of Java Bean Entities, Go Structures, etc.
72 | If we already have a table structure, generating corresponding entities from the table creation statements can greatly reduce "mindless and repetitive" work. Currently supported structures include Java, Go, and JSON.
73 |
74 | usage:
75 | ```
76 | lwe fmt sql-file-path [-t=java|go|json] [-a=yesAnd]
77 | ```
78 |
79 | For detailed usage instructions, please refer to the [Wiki](https://github.com/yesAnd92/lwe/wiki/1.%E5%BB%BA%E8%A1%A8SQL%E8%AF%AD%E5%8F%A5%E7%94%9F%E6%88%90%E4%B8%8D%E7%94%A8%E8%AF%AD%E8%A8%80%E6%89%80%E9%9C%80%E5%AE%9E%E4%BD%93)
80 |
81 |
82 | SQL Statement Generation of DSL Statements
83 | `lwe es [optional parameters] `
84 |
85 | This command helps us escape the tedious ES query syntax by converting SQL statements into the corresponding DSL and outputting them in the form of curl commands, making it convenient for use on servers as well.
86 |
87 | Supported SQL operations in the current version:
88 | ```
89 | lwe es 'select * from user where age >18' [-p=true]
90 | ```
91 |
92 | For detailed usage instructions, please refer to the [Wiki](https://github.com/yesAnd92/lwe/wiki/2.%E5%B0%86SQL%E8%AF%AD%E5%8F%A5%E8%BD%AC%E6%8D%A2%E6%88%90ElasticSearch%E6%9F%A5%E8%AF%A2%E7%9A%84DSL%E8%AF%AD%E8%A8%80)
93 |
94 |
95 | Other utilities
96 | Some highly practical and efficient tools
97 |
98 | Formatting request URLs
99 | Sometimes the URL for a request can be very long, making it difficult to find the target parameters. The `url` command can be used to format the URL, increasing the readability of the request.
100 |
101 | usage:
102 | ```
103 | lwe url yourUrl
104 | ```
105 | For detailed, [Wiki](https://github.com/yesAnd92/lwe/wiki/%E5%85%B6%E5%AE%83%E5%B0%8F%E5%B7%A5%E5%85%B7#%E6%A0%BC%E5%BC%8F%E5%8C%96%E8%AF%B7%E6%B1%82url)
106 |
107 |
108 | Retrieving passwords from Navicat connection configurations
109 | If you want to retrieve the username/password for a corresponding database from a connection saved in Navicat, you can use the `ncx` file. The `ncx` file is a connection configuration file exported by Navicat, but the password in the `ncx` file is an encrypted hexadecimal string. The `ncx` command can retrieve the corresponding plaintext.
110 |
111 | usage:
112 | ```
113 | lwe ncx ncx-file-path
114 | ```
115 | For detailed, [Wiki](https://github.com/yesAnd92/lwe/wiki/%E5%85%B6%E5%AE%83%E5%B0%8F%E5%B7%A5%E5%85%B7#%E8%8E%B7%E5%8F%96navicat%E8%BF%9E%E6%8E%A5%E9%85%8D%E7%BD%AE%E4%B8%AD%E7%9A%84%E5%AF%86%E7%A0%81)
116 |
117 | Synchronizing files between two directories
118 | If you have a habit of backing up files, this tool might help you. It can synchronize newly added files from the source directory to the backup directory, saving you the trouble of manually syncing each folder and file one by one.
119 |
120 | usage:
121 | ```
122 | lwe fsync sourceDir targetDir [-d=true]
123 | ```
124 | For detailed, [Wiki](https://github.com/yesAnd92/lwe/wiki/%E5%85%B6%E5%AE%83%E5%B0%8F%E5%B7%A5%E5%85%B7#%E5%90%8C%E6%AD%A5%E4%B8%A4%E4%B8%AA%E7%9B%AE%E5%BD%95%E4%B8%8B%E6%96%87%E4%BB%B6)
125 |
126 |
127 | ## Disclaimer
128 | 1. The spf13/cobra library is used to conveniently build command-line tools.
129 | 2. The implementation of the `es` subcommand relies on the sqlparser library to parse SQL statements, which is an excellent library for SQL parsing.
130 | 3. The conversion from SQL to DSL heavily borrows from the elasticsql project by Cao Da, which is already a mature and easy-to-use tool. The reason for not directly using this library is to practice on our own and to have more flexibility in adding or removing features later.
131 | 4. The output of Git enhanced command results uses the go-pretty library to tabulate commit information.
132 | 5. The PDF commands are encapsulated based on pdfcpu.
133 |
134 | ## RoadMap
135 | - `fmt`: Support more types of conversions as needed.
136 | - `es`: Add support for insert, update, delete operations as required.
137 | - ...
138 |
139 | ## License
140 | MIT License
141 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | ## lwe
2 |
3 | lwe是leave work early的缩写,也就是"早点下班"!,早完活,早摸鱼!🤣🤣🤣
4 | 它是一个帮助开发者提高工作效率的跨平台命令行工具,当然你把它当做go入门学习的项目也是合适的!
5 | 总之,欢迎提issue、提好玩或者使用的功能需求,最好能直接PR参与到项目中。 💪💪💪
6 |
7 | ## 功能
8 |
9 | 增强Git命令,跨多仓库操作以及AI辅助能力
10 |
11 | - [gcmsg ai生成提交信息](#gcmsg)
12 | - [glog 增强Git日志功能](#glog)
13 | - [gl 增强拉取代码功能](#gl)
14 | - [gcl 增强git clone功能](#gcl)
15 | - [gst 查看指定目录下所有git仓库状态](#gst)
16 |
17 | [解析mybaits的SQL输出日志,生成替换参数后的可执行SQL](#sqllog)
18 |
19 | [由建表SQL语句转换成Java Bean、Go结构体、Json等文件](#fmt)
20 |
21 | [其它小工具](#other)
22 |
23 | - 获取Navicat连接配置中的密码
24 | - 同步两个目录下文件
25 | - 显示本机配置的环境变量
26 | - 静态资源代理
27 |
28 | ## 安装
29 |
30 | ### 下载编译后的可执行文件
31 |
32 | 到[release](https://github.com/yesAnd92/lwe/releases)页获取对应平台的版本,在终端上即可使用该二进制文件!
33 |
34 | 当然,更推荐的方式是将二进制文件配置到环境变量中,这样可以随时随地使用二进制文件
35 |
36 | 更多的安装方式和注意事项,查查阅[Wiki](https://github.com/yesAnd92/lwe/wiki/0.%E5%AE%89%E8%A3%85%E3%80%81%E9%85%8D%E7%BD%AE%E4%BD%BF%E7%94%A8)
37 |
38 | ## AI 配置说明
39 |
40 | 如果使用的需要依赖ai能力,需要配置ai相关参数。
41 |
42 | ### 配置文件位置
43 |
44 | - Linux/Mac: `$HOME/.config/lwe/config.yml`
45 | - Windows: `%USER%\.config\lwe\config.yml`
46 |
47 | ### 示例配置
48 |
49 | 可以将源码config/config_template.yml 复制一份,然后修改为自己的配置放到对应上述指定目录下即可
50 |
51 | ```yaml
52 | ai:
53 | name: siliconflow # AI 服务提供商
54 | apikey: your_api_key # 您的 API 密钥
55 | baseurl: https://api.siliconflow.cn/v1/chat/completions # API 接口地址
56 | model: Qwen/Qwen2.5-Coder-32B-Instruct # 模型名称
57 |
58 | ```
59 |
60 | > 1.目前仅支持siliconflow、deepseek
61 | >
62 | > 2.条件允许下,推荐使用更"智能"的模型,体验更好
63 |
64 | ## 使用
65 |
66 | 你可以输入`lwe` 查看lwe命令的使用方式,有哪些子命令及其各自对的功能描述。
67 |
68 | 如果对某个子命令感兴趣,可以使用`-h`参数查看命令的使用示例 ,如:`lwe glog -h`
69 |
70 | gcmsg ai生成提交信息
71 |
72 | `gcmsg` (Git Commit Message) 是一个借助 AI 能力生成 Git 提交信息的命令。它能够分析当前的代码变更,并生成合适的提交信息,然后自动完成提交和推送操作。**此命令需要配置AI相关参数**
73 |
74 | 使用方式:
75 | 在git仓库下
76 |
77 | ```text
78 | lwe gcsmg
79 | ```
80 |
81 | 示例:
82 | 
83 |
84 | #### 注意事项
85 |
86 | > 1.大量文件提交时,不要使用此命令,不仅消耗token量大,也很容易超时
87 | > 2.虽然对常见账密、token等敏感信息进行了过滤,但仍有泄露的风险,使用时注意甄别
88 |
89 | glog 增强Git日志功能
90 |
91 | `glog` (Git Log)查看给定**目录下所有git仓库**提交日志
92 | 开发人员通常会在多个git仓库下工作,经常会有同时查看多个git仓库提交日志的需求,glog子命令就派上用场了。
93 |
94 | 使用方式:
95 |
96 | ```text
97 | lwe glog [git repo dir] [-a=yesAnd] [-n=50] [-s=2023-08-04] [-e=2023-08-04]
98 | ```
99 |
100 | 示例:
101 | 
102 |
103 | gl 增强拉取代码功能
104 |
105 | `gl` (Git Pull)拉取给定**目录下的所有git仓库**最新代码(使用的git pull --rebase的方式)
106 |
107 | 使用方式:
108 |
109 | ```text
110 | lwe gl [git repo dir]
111 | ```
112 |
113 | 详细使用说明,可以查阅[Wiki](https://github.com/yesAnd92/lwe/wiki/3.Git%E5%A2%9E%E5%BC%BA%E5%8A%9F%E8%83%BD#gl-%E6%8B%89%E5%8F%96%E6%8C%87%E5%AE%9A%E7%9B%AE%E5%BD%95%E4%B8%8B%E6%89%80%E6%9C%89git%E4%BB%93%E5%BA%93https://github.com/yesAnd92/lwe/wiki/3.Git%E5%A2%9E%E5%BC%BA%E5%8A%9F%E8%83%BD#gcl-%E5%85%8B%E9%9A%86%E6%95%B4%E4%B8%AAgroup%E4%B8%8B%E7%9A%84%E6%89%80%E6%9C%89git%E4%BB%93%E5%BA%93https://github.com/yesAnd92/lwe/wiki/1.%E5%BB%BA%E8%A1%A8SQL%E8%AF%AD%E5%8F%A5%E7%94%9F%E6%88%90%E4%B8%8D%E7%94%A8%E8%AF%AD%E8%A8%80%E6%89%80%E9%9C%80%E5%AE%9E%E4%BD%)
114 |
115 | gcl 增强git clone功能
116 |
117 | `gcl` (Git clone) 可以拉取**group**下所有的git仓库,适用group下git仓库拉取数量多的场景
118 |
119 | 使用方式:
120 |
121 | ```text
122 | lwe gcl gitGroupUrl [dir for this git group] -t=yourToken
123 | ```
124 |
125 | 详细使用说明,可以查阅[Wiki](https://github.com/yesAnd92/lwe/wiki/3.Git%E5%A2%9E%E5%BC%BA%E5%8A%9F%E8%83%BD#gcl-%E5%85%8B%E9%9A%86%E6%95%B4%E4%B8%AAgroup%E4%B8%8B%E7%9A%84%E6%89%80%E6%9C%89git%E4%BB%93%E5%BA%93https://github.com/yesAnd92/lwe/wiki/1.%E5%BB%BA%E8%A1%A8SQL%E8%AF%AD%E5%8F%A5%E7%94%9F%E6%88%90%E4%B8%8D%E7%94%A8%E8%AF%AD%E8%A8%80%E6%89%80%E9%9C%80%E5%AE%9E%E4%BD%9)
126 |
127 | gst 查看指定目录下所有git仓库状态
128 |
129 | 查看给定目录下的所有git仓库状态
130 |
131 | 使用方式:
132 |
133 | ```text
134 | lwe gst [your git repo dir]
135 | ```
136 |
137 | ---
138 |
139 | 解析Mybatis的SQL输出日志,生成替换参数后的可执行SQL
140 |
141 | Mybatis输出的日志,SQL语句和参数是分开的,调试SQL时,需要粘出来再去用参数替换对应的占位符,比较繁琐。这个命令可以快速解析出一个填充参数后的可执行SQL。
142 |
143 | ```bash
144 | lwe sqllog
145 | ```
146 |
147 | 使用示例:
148 | 
149 |
150 | #### 注意事项
151 |
152 | - 确保输出的日志包含 "Preparing:" 和 "Parameters:" 两个部分。
153 | - 在将 SQL 日志作为参数传递时,确保用双引号将整个 SQL 日志括起来。
154 |
155 | ---
156 |
157 | 建表语句生成Java Bean实体、Go 结构体等
158 |
159 | 如果我们已经有了表结构,使用建表语句生成对应的实体可以大大减少我们"无脑且重复"工作。
160 | 目前支持生成的结构包括Java、Go、Json。
161 |
162 | 使用方式:
163 |
164 | ```text
165 | lwe fmt sql-file-path [-t=java|go|json] [-a=yesAnd]
166 | ```
167 |
168 | 详细使用说明,可以查阅[Wiki](https://github.com/yesAnd92/lwe/wiki/1.%E5%BB%BA%E8%A1%A8SQL%E8%AF%AD%E5%8F%A5%E7%94%9F%E6%88%90%E4%B8%8D%E7%94%A8%E8%AF%AD%E8%A8%80%E6%89%80%E9%9C%80%E5%AE%9E%E4%BD%93)
169 |
170 | ---
171 |
172 | 其它小工具
173 | 一些非常实用的功能
174 |
175 | 获取Navicat连接配置中的密码
176 | 如果想从Navicat保存的连接中获取对应数据库的用户名/密码,可以使用ncx文件,ncx文件是Navicat导出的连接配置文件,但ncx中的密码是一个加密后的十六进制串,使用ncx命令可以获取对应的明文
177 |
178 | 使用方式:
179 |
180 | ```text
181 | lwe ncx ncx-file-path
182 | ```
183 |
184 | 详细使用说明,可以查阅[Wiki](https://github.com/yesAnd92/lwe/wiki/%E5%85%B6%E5%AE%83%E5%B0%8F%E5%B7%A5%E5%85%B7#%E8%8E%B7%E5%8F%96navicat%E8%BF%9E%E6%8E%A5%E9%85%8D%E7%BD%AE%E4%B8%AD%E7%9A%84%E5%AF%86%E7%A0%81)
185 |
186 | 同步两个目录下文件
187 | 如果你有备份文件的习惯,这个工具可能会帮到你,它可以将源目录文件下的新增的文件同步到备份目录,省去了你逐层文件夹逐个文件去手动同步。
188 |
189 | 使用方式:
190 |
191 | ```text
192 | lwe fsync sourceDir targetDir [-d=true]
193 | ```
194 |
195 | 详细使用说明,可以查阅[Wiki](https://github.com/yesAnd92/lwe/wiki/%E5%85%B6%E5%AE%83%E5%B0%8F%E5%B7%A5%E5%85%B7#%E5%90%8C%E6%AD%A5%E4%B8%A4%E4%B8%AA%E7%9B%AE%E5%BD%95%E4%B8%8B%E6%96%87%E4%BB%B6)
196 |
197 | 静态资源代理
198 | 可以为静态资源提供代理,方便本地访问测试
199 |
200 | 使用方式:
201 |
202 | ```text
203 | lwe fileserver your-file-dir [-p=8080]
204 | ```
205 |
206 | 详细使用说明,可以查阅[Wiki](https://github.com/yesAnd92/lwe/wiki/%E5%85%B6%E5%AE%83%E5%B0%8F%E5%B7%A5%E5%85%B7#%E9%9D%99%E6%80%81%E8%B5%84%E6%BA%90%E4%BB%A3%E7%90%86)
207 |
208 | ## 声明
209 |
210 | 1.使用[spf13/cobra](github.com/spf13/cobra)库来方便的构建命令行工具
211 |
212 | 2.es子命令实现借助了[sqlparser](github.com/xwb1989/sqlparser)库来解析SQL语句,一个库很优秀的解析SQL库
213 |
214 | 3.sql转换成dsl,曹大的[elasticsql](https://github.com/cch123/elasticsql)项目已经是一个很成熟好用的轮子了,lwe也大量借鉴了它的实现思路;没直接调用这个库的原因是想自己练手,同时后续增减功能也更加灵活
215 |
216 | 4.git增强命令结果输出时使用了[go-pretty](https://github.com/jedib0t/go-pretty)库来表格化提交信息
217 |
218 | 5.gcmsg命令接入ai时,借鉴了[aicmt](https://github.com/versun/aicmt)如何写prompt,`aicmt`是个功能更强大ai辅助的git提交的工具
219 |
220 | ## 开源协议
221 |
222 | [MIT License](https://github.com/yesAnd92/lwe/blob/main/LICENSE)
223 |
--------------------------------------------------------------------------------
/ai/ai_chat.go:
--------------------------------------------------------------------------------
1 | package ai
2 |
3 | import (
4 | "github.com/spf13/cobra"
5 | "github.com/yesAnd92/lwe/config"
6 | "github.com/yesAnd92/lwe/utils"
7 | )
8 |
9 | type AIChat interface {
10 | Chat(ctx, prompt string) (string, error)
11 | }
12 |
13 | type AIAgent struct {
14 | AiChat AIChat
15 | }
16 |
17 | // Chat Wrapper AIChat's Chat method ,enhance it
18 | func (aiAgent *AIAgent) Chat(ctx, prompt string) (string, error) {
19 |
20 | //Although it is clearly required in the prompt to return a clean JSON format
21 | //some APIs include the ```json``` tag when returning results.
22 | resp, err := aiAgent.AiChat.Chat(ctx, prompt)
23 | if err == nil {
24 | resp = utils.RemoveJSONTags(resp)
25 | }
26 |
27 | return resp, err
28 | }
29 |
30 | type AIName string
31 |
32 | const (
33 | Deepseek = "deepseek"
34 | Siliconflow = "siliconflow"
35 | )
36 |
37 | func NewAIAgent() *AIAgent {
38 |
39 | // Read the configuration file and decide which AI to use.
40 | config := config.InitConfig()
41 | ai := config.Ai
42 |
43 | var agent AIAgent
44 |
45 | switch ai.Name {
46 | case Deepseek:
47 | agent = AIAgent{AiChat: &DeepSeek{}}
48 | break
49 | case Siliconflow:
50 | agent = AIAgent{AiChat: &SiliconFlow{}}
51 | break
52 | default:
53 | cobra.CheckErr("AI configuration is missing or incorrect.")
54 | }
55 |
56 | return &agent
57 | }
58 |
--------------------------------------------------------------------------------
/ai/common_response.go:
--------------------------------------------------------------------------------
1 | package ai
2 |
3 | type CommonResponse struct {
4 | ID string `json:"id"`
5 | Choices []Choice `json:"choices"`
6 | Created int64 `json:"created"`
7 | Model string `json:"model"`
8 | SystemFingerprint string `json:"system_fingerprint"`
9 | Object string `json:"object"`
10 | Usage Usage `json:"usage"`
11 | }
12 |
13 | type Choice struct {
14 | FinishReason string `json:"finish_reason"`
15 | Index int `json:"index"`
16 | Message Message `json:"message"`
17 | Logprobs Logprobs `json:"logprobs"`
18 | }
19 |
20 | type Message struct {
21 | Content string `json:"content"`
22 | ToolCalls []ToolCall `json:"tool_calls"`
23 | Role string `json:"role"`
24 | }
25 |
26 | type ToolCall struct {
27 | ID string `json:"id"`
28 | Type string `json:"type"`
29 | Function Function `json:"function"`
30 | }
31 |
32 | type Function struct {
33 | Name string `json:"name"`
34 | Arguments string `json:"arguments"`
35 | }
36 |
37 | type Logprobs struct {
38 | Content []ContentItem `json:"content"`
39 | }
40 |
41 | type ContentItem struct {
42 | Token string `json:"token"`
43 | Logprob float64 `json:"logprob"`
44 | Bytes []int `json:"bytes"`
45 | TopLogprobs []TopLogprob `json:"top_logprobs"`
46 | }
47 |
48 | type TopLogprob struct {
49 | Token string `json:"token"`
50 | Logprob float64 `json:"logprob"`
51 | Bytes []int `json:"bytes"`
52 | }
53 |
54 | type Usage struct {
55 | CompletionTokens int64 `json:"completion_tokens"`
56 | PromptTokens int64 `json:"prompt_tokens"`
57 | PromptCacheHitTokens int64 `json:"prompt_cache_hit_tokens"`
58 | PromptCacheMissTokens int64 `json:"prompt_cache_miss_tokens"`
59 | TotalTokens int64 `json:"total_tokens"`
60 | }
61 |
--------------------------------------------------------------------------------
/ai/deepseek.go:
--------------------------------------------------------------------------------
1 | package ai
2 |
3 | import (
4 | "bytes"
5 | "encoding/json"
6 | "fmt"
7 | "io"
8 | "net/http"
9 |
10 | "github.com/mitchellh/mapstructure"
11 | "github.com/spf13/cobra"
12 | "github.com/yesAnd92/lwe/config"
13 | )
14 |
15 | type DeepSeek struct {
16 | }
17 |
18 | func (ds *DeepSeek) Chat(ctx, prompt string) (string, error) {
19 | resp := dsSend(ctx, prompt)
20 | return resp, nil
21 | }
22 |
23 | func dsSend(ctx, prompt string) string {
24 |
25 | aiConfig := config.GlobalConfig.Ai
26 | url := aiConfig.BaseUrl
27 | apiKey := aiConfig.ApiKey
28 | model := aiConfig.Model
29 |
30 | method := "POST"
31 |
32 | // to build message for request
33 | message := []map[string]interface{}{
34 | {
35 | "content": ctx,
36 | "role": "user",
37 | },
38 | {
39 | "content": prompt,
40 | "role": "system",
41 | },
42 | }
43 | requestData := map[string]interface{}{
44 | "messages": message,
45 | "model": model,
46 | "frequency_penalty": 0,
47 | "max_tokens": 4096,
48 | "presence_penalty": 0,
49 | "response_format": map[string]interface{}{
50 | "type": "text",
51 | },
52 | "stop": nil,
53 | "stream": false,
54 | "stream_options": nil,
55 | "temperature": 0.5,
56 | "top_p": 1,
57 | "tools": nil,
58 | "tool_choice": "none",
59 | "logprobs": false,
60 | "top_logprobs": nil,
61 | }
62 |
63 | // 将map转换为JSON格式的字节切片
64 | requestBody, err := json.Marshal(requestData)
65 | if err != nil {
66 | cobra.CheckErr(err)
67 | }
68 |
69 | // 创建一个io.Reader用于请求体
70 | payload := bytes.NewReader(requestBody)
71 |
72 | client := &http.Client{}
73 | req, err := http.NewRequest(method, url, payload)
74 |
75 | if err != nil {
76 | cobra.CheckErr(err)
77 | }
78 |
79 | auth := fmt.Sprintf("Bearer %s", apiKey)
80 | req.Header.Add("Content-Type", "application/json")
81 | req.Header.Add("Accept", "application/json")
82 | req.Header.Add("Authorization", auth)
83 |
84 | resp, err := client.Do(req)
85 | if err != nil {
86 | cobra.CheckErr(err)
87 | }
88 | defer resp.Body.Close()
89 |
90 | if resp.StatusCode != http.StatusOK {
91 | bodyBytes, _ := io.ReadAll(resp.Body)
92 | cobra.CheckErr(fmt.Sprintf("request fail, statusCode: %d, body: %s", resp.StatusCode, string(bodyBytes)))
93 | }
94 |
95 | var result map[string]interface{}
96 | if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
97 | cobra.CheckErr(fmt.Sprintf("parse fail: %v", err))
98 | }
99 |
100 | if errorData, ok := result["error"]; ok {
101 | cobra.CheckErr(fmt.Sprintf("request error: %v", errorData))
102 | }
103 |
104 | dsResp := &CommonResponse{}
105 | if err := mapstructure.Decode(result, &dsResp); err != nil {
106 | cobra.CheckErr(err)
107 | }
108 | return dsResp.Choices[0].Message.Content
109 | }
110 |
--------------------------------------------------------------------------------
/ai/deepseek_test.go:
--------------------------------------------------------------------------------
1 | package ai
2 |
3 | import (
4 | "fmt"
5 | "github.com/yesAnd92/lwe/config"
6 | "testing"
7 | )
8 |
9 | func TestDeepSeek_Chat(t *testing.T) {
10 | config.InitConfig()
11 | ds := &DeepSeek{}
12 | chat, err := ds.Chat("hello,deepseek", "")
13 | if err != nil {
14 | fmt.Println(err)
15 | }
16 | fmt.Println(chat)
17 | }
18 |
--------------------------------------------------------------------------------
/ai/prompt/git_diff_summary.prompt:
--------------------------------------------------------------------------------
1 | You are a git commit expert. Based on the provided git diff result, generate a commit message that adheres to the following structure and explanations:
2 | {
3 | "commitMsg": [
4 | {
5 | "type": "type prefix (feat:, fix:, etc.)",
6 | "scope":"The scope specifies the part of the codebase affected by the change",
7 | "description": "The description is a concise summary of the change, Match the response language to the dominant language of code comments"
8 | }
9 | ],
10 | "optionalBody":"The body provides additional context or details about the change",
11 | "optionalFooter":"Breaking changes or issue references. e.g., Closes #123"
12 | }
13 | Requirements:
14 | Merge similar modify and streamline the commit messages to no more than 5.
15 | Please ensure that the returned result conforms to the above JSON structure, without any extraneous content such as reasoning processes or explanations.
16 | Validate the JSON validity before output, including ensuring that all keys and values have corresponding closing quotes and using empty strings instead of null.
17 |
--------------------------------------------------------------------------------
/ai/prompt/git_log_summary.prompt:
--------------------------------------------------------------------------------
1 | You are a Git commit log expert who must analyze commit msg and provide a summary.
2 | You will be provided with multiple git repositories. Each repository's information is separated by an empty line. For every repository block, the first line represents the repository address, and the following lines are the commit messages.
3 | Your task is to:
4 | 1. Analyze the commit messages and summarize the logs in a clear, concise, and polished manner.
5 | 2. Avoid redundant details and ensure the summary is well-structured and easy to understand. Prioritize logical grouping and readability while maintaining accuracy.
6 | 3. Summary response in English and Chinese.
7 | 4. Summary not more than 6 concise bullet points.
8 |
9 | Respond strictly in this standard JSON format only,without any markdown tag such as json :
10 | {
11 | "repo_summary": [
12 | {
13 | "repo": "repository name",
14 | "summary": [
15 | "summary1","summary2"·
16 | ],
17 | "summary_cn": [
18 | "中文总结1","中文总结2"
19 | ],
20 | }
21 | ]
22 | }
23 |
--------------------------------------------------------------------------------
/ai/prompt/prompt_init.go:
--------------------------------------------------------------------------------
1 | package prompt
2 |
3 | import _ "embed"
4 |
5 | //go:embed git_log_summary.prompt
6 | var LogSummaryPrompt string
7 |
8 | //go:embed git_diff_summary.prompt
9 | var GitDiffPrompt string
10 |
--------------------------------------------------------------------------------
/ai/siliconflow.go:
--------------------------------------------------------------------------------
1 | package ai
2 |
3 | import (
4 | "bytes"
5 | "encoding/json"
6 | "fmt"
7 | "io"
8 | "net/http"
9 |
10 | "github.com/mitchellh/mapstructure"
11 | "github.com/spf13/cobra"
12 | "github.com/yesAnd92/lwe/config"
13 | )
14 |
15 | type SiliconFlow struct {
16 | }
17 |
18 | func (ds *SiliconFlow) Chat(ctx, prompt string) (string, error) {
19 | resp := sfSend(ctx, prompt)
20 | return resp, nil
21 | }
22 |
23 | func sfSend(ctx, prompt string) string {
24 |
25 | aiConfig := config.GlobalConfig.Ai
26 | url := aiConfig.BaseUrl
27 | apiKey := aiConfig.ApiKey
28 | model := aiConfig.Model
29 |
30 | method := "POST"
31 |
32 | // to build message for request
33 | message := []map[string]interface{}{
34 | {
35 | "content": ctx,
36 | "role": "user",
37 | },
38 | {
39 | "content": prompt,
40 | "role": "system",
41 | },
42 | }
43 | requestData := map[string]interface{}{
44 | "messages": message,
45 | "model": model,
46 | "frequency_penalty": 0.5,
47 | "temperature": 0.5,
48 | "max_tokens": 4096,
49 | "response_format": map[string]interface{}{
50 | "type": "text",
51 | },
52 | "stop": nil,
53 | "stream": false,
54 | }
55 |
56 | // 将map转换为JSON格式的字节切片
57 | requestBody, err := json.Marshal(requestData)
58 | if err != nil {
59 | cobra.CheckErr(err)
60 | }
61 |
62 | // 创建一个io.Reader用于请求体
63 | payload := bytes.NewReader(requestBody)
64 |
65 | client := &http.Client{}
66 | req, err := http.NewRequest(method, url, payload)
67 |
68 | if err != nil {
69 | cobra.CheckErr(err)
70 | }
71 |
72 | auth := fmt.Sprintf("Bearer %s", apiKey)
73 | req.Header.Add("Content-Type", "application/json")
74 | req.Header.Add("Authorization", auth)
75 |
76 | resp, err := client.Do(req)
77 | if err != nil {
78 | cobra.CheckErr(err)
79 | }
80 | defer resp.Body.Close()
81 |
82 | if resp.StatusCode != http.StatusOK {
83 | bodyBytes, _ := io.ReadAll(resp.Body)
84 | cobra.CheckErr(fmt.Sprintf("request fail, statusCode: %d, body: %s", resp.StatusCode, string(bodyBytes)))
85 | }
86 |
87 | var result map[string]interface{}
88 | if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
89 | cobra.CheckErr(fmt.Sprintf("parse fail: %v", err))
90 | }
91 |
92 | if errorData, ok := result["error"]; ok {
93 | cobra.CheckErr(fmt.Sprintf("request error: %v", errorData))
94 | }
95 |
96 | dsResp := &CommonResponse{}
97 | if err := mapstructure.Decode(result, &dsResp); err != nil {
98 | cobra.CheckErr(err)
99 | }
100 | return dsResp.Choices[0].Message.Content
101 | }
102 |
--------------------------------------------------------------------------------
/ai/siliconflow_test.go:
--------------------------------------------------------------------------------
1 | package ai
2 |
3 | import (
4 | "fmt"
5 | "github.com/yesAnd92/lwe/config"
6 | "testing"
7 | )
8 |
9 | func TestSiliconFlow_Chat(t *testing.T) {
10 | config.InitConfig()
11 | sf := &SiliconFlow{}
12 | chat, err := sf.Chat("hello,siliconflow", "")
13 | if err != nil {
14 | fmt.Println(err)
15 | }
16 | fmt.Println(chat)
17 | }
18 |
--------------------------------------------------------------------------------
/cmd/env.go:
--------------------------------------------------------------------------------
1 | package cmd
2 |
3 | import (
4 | "fmt"
5 | "github.com/spf13/cobra"
6 | "github.com/yesAnd92/lwe/handler/env"
7 | "github.com/yesAnd92/lwe/utils"
8 | )
9 |
10 | var envCmd = &cobra.Command{
11 | Use: `env`,
12 | Short: `Print all environment variable `,
13 | Long: `Print all environment variable`,
14 | Example: `lwe env`,
15 | Args: cobra.MatchAll(cobra.ExactArgs(0)),
16 | Run: func(cmd *cobra.Command, args []string) {
17 |
18 | var envV env.IEnvVariable
19 | op := utils.OsEnv()
20 | fmt.Println("op:", op)
21 | switch op {
22 | case utils.Mac:
23 | envV = &env.MacEVnVariable{}
24 | default:
25 | cobra.CheckErr("Not support this os!")
26 | }
27 | envInfos := envV.FindEnvInfo()
28 | env.EnvCat(envInfos)
29 | },
30 | }
31 |
--------------------------------------------------------------------------------
/cmd/es.go:
--------------------------------------------------------------------------------
1 | package cmd
2 |
3 | import (
4 | "encoding/json"
5 | "fmt"
6 | "github.com/spf13/cobra"
7 | "github.com/xwb1989/sqlparser"
8 | "github.com/yesAnd92/lwe/handler/es"
9 | )
10 |
11 | const (
12 | CURL_TPL = `curl -XPOST -H "Content-Type: application/json" -u {username}:{password} {ip:port}/%s/_search?pretty -d '%s' `
13 | )
14 |
15 | /**
16 | * es命令
17 | * 将sql语句转译成dsl语句
18 | */
19 | var (
20 | fmtPretty bool
21 | esCmd = &cobra.Command{
22 | Use: `es`,
23 | Short: `Translate SQL to elasticsearch's DSL`,
24 | Long: `Translate SQL to elasticsearch's DSL`,
25 | Example: `lwe es 'select * from user where age >18' [-p=true]`,
26 | Args: cobra.MatchAll(cobra.ExactArgs(1)),
27 | Run: func(cmd *cobra.Command, args []string) {
28 | sql := args[0]
29 | //使用sqlparse对SQL进行解析
30 | stmt, err := sqlparser.Parse(sql)
31 | if err != nil {
32 | fmt.Println("Something error in your sql:", err)
33 | fmt.Println("Please re-check syntax and try it again!")
34 | return
35 | }
36 |
37 | var dsl, esType string
38 | switch stmt.(type) {
39 | case *sqlparser.Select:
40 | dsl, esType, err = es.HandleSelect(stmt.(*sqlparser.Select))
41 | case *sqlparser.Delete:
42 | fmt.Println("Delete syntax is not supported this version!")
43 | return
44 | case *sqlparser.Update:
45 | fmt.Println("Update syntax is not supported this version!")
46 | return
47 | default:
48 | fmt.Println("This syntax is supported this version!")
49 | return
50 | }
51 |
52 | if err != nil {
53 | fmt.Println(err)
54 | return
55 | }
56 |
57 | if fmtPretty {
58 | //需要美化
59 | var re map[string]interface{}
60 | json.Unmarshal([]byte(dsl), &re)
61 | pr, _ := json.MarshalIndent(re, "", " ")
62 | dsl = string(pr)
63 | }
64 | fmt.Printf(CURL_TPL, esType, dsl)
65 | },
66 | }
67 | )
68 |
69 | func init() {
70 |
71 | esCmd.PersistentFlags().BoolVarP(&fmtPretty, "pretty", "p", false, "Beautify DSL")
72 | }
73 |
--------------------------------------------------------------------------------
/cmd/fileserver.go:
--------------------------------------------------------------------------------
1 | package cmd
2 |
3 | import (
4 | "github.com/spf13/cobra"
5 | "github.com/yesAnd92/lwe/handler/fileserver"
6 | "strconv"
7 | )
8 |
9 | /**
10 | * file server
11 | */
12 | var (
13 | fileServerPort int
14 | fileServerCmd = &cobra.Command{
15 | Use: `fileserver`,
16 | Short: `Start a static file server`,
17 | Long: `Start a static file server,designed to provide static resources, which include but are not limited to HTML files,CSS,JS,images,and videos.`,
18 | Example: `lwe fileserver your-file-dir [-p=8080] `,
19 | Args: cobra.MatchAll(cobra.ExactArgs(1)),
20 | Run: func(cmd *cobra.Command, args []string) {
21 | rootDir := args[0]
22 | fileserver.ServerStart(strconv.Itoa(fileServerPort), rootDir)
23 | },
24 | }
25 | )
26 |
27 | func init() {
28 |
29 | fileServerCmd.PersistentFlags().IntVarP(&fileServerPort, "port", "p", 9527, "file server's web port,default 9527")
30 | }
31 |
--------------------------------------------------------------------------------
/cmd/format.go:
--------------------------------------------------------------------------------
1 | package cmd
2 |
3 | import (
4 | "bufio"
5 | "errors"
6 | "fmt"
7 | "github.com/spf13/cobra"
8 | "github.com/yesAnd92/lwe/handler/sql"
9 | "io"
10 | "os"
11 | "strings"
12 | )
13 |
14 | /**
15 | * fmt命令相关功能
16 | */
17 |
18 | var (
19 | target string
20 | author string
21 | fmtCmd = &cobra.Command{
22 | Use: `fmt`,
23 | Short: `Generate the specified file based on SQL`,
24 | Long: `Generate the specified file based on SQL. Such as Java Entity,Go struct and so on`,
25 | Example: `lwe fmt sql-file-path [-t=java|go|json] [-a=yesAnd]`,
26 | Args: cobra.MatchAll(cobra.ExactArgs(1)),
27 | Run: func(cmd *cobra.Command, args []string) {
28 | sqlFilePath := args[0]
29 | sqlCtxList, err := DataCleansing(sqlFilePath)
30 | if err != nil {
31 | cobra.CheckErr(fmt.Errorf("can't find SQL file: %s", sqlFilePath))
32 | }
33 |
34 | //设置作者参数
35 | params := make(map[string]interface{})
36 | if len(author) > 0 {
37 | params["author"] = author
38 | }
39 |
40 | parse := &sql.BaseParseDDL{}
41 | parse.DoParse(target, sqlCtxList, params)
42 | fmt.Println("File generated successfully!")
43 | },
44 | }
45 | )
46 |
47 | // DataCleansing 清洗数据,摘取create语句
48 | func DataCleansing(sqlFilePath string) (ctxList []string, err error) {
49 | fi, err := os.Open(sqlFilePath)
50 | if err != nil {
51 | return nil, errors.New(fmt.Sprintf("can't find SQL file: %s", sqlFilePath))
52 | }
53 | defer fi.Close()
54 |
55 | //存放所有的创建语句
56 | var sqlCtxArr []string
57 | //创建语句
58 | var createStr string
59 | br := bufio.NewReader(fi)
60 | for {
61 | lineByte, _, e := br.ReadLine()
62 | //找到create语句的开始行,一直读取到这个语句结束
63 | if strings.HasPrefix(string(lineByte), "CREATE") || strings.HasPrefix(string(lineByte), "create") {
64 | createStr += string(lineByte)
65 | for {
66 | lb, _, e2 := br.ReadLine()
67 | //本条create语句结束,放到结果容器中,继续寻找下一个create语句
68 | if len(lb) == 0 || e2 == io.EOF {
69 | sqlCtxArr = append(sqlCtxArr, createStr)
70 | createStr = ""
71 | break
72 | }
73 | createStr += string(lb)
74 | }
75 | }
76 | if e == io.EOF {
77 | break
78 | }
79 | }
80 | return sqlCtxArr, nil
81 | }
82 |
83 | func init() {
84 |
85 | fmtCmd.PersistentFlags().StringVarP(&target, "target", "t", "java", "The type[java|json|go] of generate the sql")
86 | fmtCmd.PersistentFlags().StringVarP(&author, "author", "a", "", "Comment for author information will be added to the generated file")
87 | }
88 |
--------------------------------------------------------------------------------
/cmd/fsync.go:
--------------------------------------------------------------------------------
1 | package cmd
2 |
3 | import (
4 | "github.com/spf13/cobra"
5 | "github.com/yesAnd92/lwe/handler/sync"
6 | )
7 |
8 | var (
9 | dryrRun bool
10 |
11 | fsyncCmd = &cobra.Command{
12 | Use: `fsync`,
13 | Short: `Sync file from source dir to target dir`,
14 | Long: `Sync file from source dir to target dir,and it will skip existing files`,
15 | Example: `lwe fsync sourceDir targetDir [-d=true]`,
16 | Args: cobra.MatchAll(cobra.ExactArgs(2)),
17 | Run: func(cmd *cobra.Command, args []string) {
18 | sourceDir := args[0]
19 | targetDir := args[1]
20 |
21 | var thenDo sync.CompareThenDoIfa = &sync.CopyCompareThenDo{}
22 | if dryrRun {
23 | thenDo = &sync.DisplayCompareThenDo{}
24 | }
25 | fsync := sync.InitFsync(sourceDir, targetDir)
26 |
27 | //compare source and target dir diff
28 | fsync.DiffDir()
29 |
30 | fsync.Sync(thenDo)
31 | },
32 | }
33 | )
34 |
35 | func init() {
36 |
37 | //dry-run
38 | fsyncCmd.PersistentFlags().BoolVarP(&dryrRun, "dry-run", "d", false, "Because fsync can make some significant changes, you might prefer to add --dry-run=true option"+
39 | " to the command line to preview what fsync plans to do")
40 | }
41 |
--------------------------------------------------------------------------------
/cmd/git.go:
--------------------------------------------------------------------------------
1 | package cmd
2 |
3 | import (
4 | "github.com/spf13/cobra"
5 | "github.com/yesAnd92/lwe/handler/gitcmd"
6 | "github.com/yesAnd92/lwe/utils"
7 | )
8 |
9 | /**
10 | * git命令相关功能
11 | */
12 | var (
13 | detail = false //是否展示每次提交变动的文件 !!!没找到合适的展示方式,且性能稳定性不可控,暂时先不开放
14 | file bool //日志结果控制台输出,或者生成文件,默认控制台输出
15 | committer string //指定查询提交者,不指定查所有
16 | recentN int16 //指定查询仓库数量
17 | start string //开始时间
18 | end string //结束时间
19 | branchs bool //show all branch,default show current branch
20 |
21 | //gcl
22 | token string //克隆所需要的token
23 |
24 | glogCmd = &cobra.Command{
25 | Use: `glog`,
26 | Short: `Get all git repository commit log under the given dir `,
27 | Long: `Get all git repository commit log under the given dir ,and specify author,date etc. supported!`,
28 | Example: `lwe glog [git repo dir] [-a=yesAnd] [-n=50] [-s=2023-08-04] [-e=2023-08-04] [-b=ture]`,
29 | Args: cobra.MatchAll(),
30 | Run: func(cmd *cobra.Command, args []string) {
31 |
32 | var dir = "."
33 | if len(args) > 0 {
34 | dir = args[0]
35 | }
36 |
37 | if recentN > int16(500) {
38 | cobra.CheckErr("recentN can't exceed 500")
39 | }
40 |
41 | commitLogs, err := gitcmd.GetAllGitRepoCommitLog(detail, recentN, dir, committer, start, end, branchs)
42 | if err != nil {
43 | cobra.CheckErr(err)
44 | }
45 |
46 | var output gitcmd.OutputFormatter = &gitcmd.ConsoleOutput{}
47 | if file {
48 | output = &gitcmd.FileOutput{}
49 | }
50 | output.Output(commitLogs)
51 |
52 | },
53 | }
54 |
55 | glCmd = &cobra.Command{
56 | Use: `gl`,
57 | Short: `Update all git repository under the given dir `,
58 | Long: `Update all git repository under the given dir ,the repository that has modified files will not be updated!`,
59 | Example: `lwe gl [git repo dir]`,
60 | Args: cobra.MatchAll(cobra.MinimumNArgs(0)),
61 | Run: func(cmd *cobra.Command, args []string) {
62 |
63 | var dir = "."
64 | if len(args) > 0 {
65 | dir = args[0]
66 | }
67 |
68 | gitcmd.UpdateAllGitRepo(dir)
69 |
70 | },
71 | }
72 |
73 | gclCmd = &cobra.Command{
74 | Use: `gcl`,
75 | Short: `Git clone all git repository under the given git group `,
76 | Long: `Git clone all git repository under the given git group `,
77 | Example: `lwe gcl gitGroupUrl [dir for this git group] -t=yourToken`,
78 | Args: cobra.MatchAll(cobra.MinimumNArgs(1)),
79 | Run: func(cmd *cobra.Command, args []string) {
80 | if len(token) == 0 {
81 | cobra.CheckErr("please confirm token is not empty!")
82 | }
83 | var dir = "."
84 | if len(args) > 1 {
85 | dir = args[1]
86 | }
87 |
88 | gitcmd.CloneGroup(args[0], token, dir)
89 |
90 | },
91 | }
92 |
93 | gstCmd = &cobra.Command{
94 | Use: `gst`,
95 | Short: `Get all git repository status under the given dir `,
96 | Long: `Get all git repository status under the given dir `,
97 | Example: `lwe gst [your git repo dir]`,
98 | Args: cobra.MatchAll(cobra.MinimumNArgs(0)),
99 | Run: func(cmd *cobra.Command, args []string) {
100 |
101 | var dir = "."
102 | if len(args) > 0 {
103 | dir = args[0]
104 | }
105 |
106 | gitcmd.GetAllGitRepoStatus(dir)
107 |
108 | },
109 | }
110 |
111 | gSumCmd = &cobra.Command{
112 | Use: `gsum`,
113 | Short: `Summary git log with AI's help' `,
114 | Long: `With the help of AI, merge similar or streamline Git commit logs.`,
115 | Example: `lwe gsum [git repo dir] [-a=yesAnd] [-s=2023-08-04] [-e=2023-08-04]`,
116 | Args: cobra.MatchAll(cobra.MinimumNArgs(0)),
117 | Run: func(cmd *cobra.Command, args []string) {
118 |
119 | var dir = "."
120 | if len(args) > 0 {
121 | dir = args[0]
122 | }
123 |
124 | gitcmd.GitLogSummary(detail, dir, committer, start, end)
125 |
126 | },
127 | }
128 |
129 | gcmsgCmd = &cobra.Command{
130 | Use: `gcmsg`,
131 | Short: `Generate commit msg with AI's help `,
132 | Long: `Generate commit msg with AI's help`,
133 | Example: `lwe gcmsg`,
134 | Args: cobra.MatchAll(cobra.MinimumNArgs(0)),
135 | Run: func(cmd *cobra.Command, args []string) {
136 |
137 | var dir = "."
138 | //trans to abslute path
139 | dir = utils.ToAbsPath(dir)
140 |
141 | //git commit msg from ai
142 | commit := gitcmd.GetGitCommitMsg(dir)
143 |
144 | //push to origin repo
145 | gitcmd.CommitAndPush(dir, commit)
146 |
147 | },
148 | }
149 | )
150 |
151 | func init() {
152 |
153 | glogCmd.PersistentFlags().BoolVarP(&file, "file", "f", false, "result output to file,default value is false (meaning output to console). ")
154 | glogCmd.PersistentFlags().StringVarP(&committer, "author", "a", "", "specify name of committer ")
155 | glogCmd.PersistentFlags().StringVarP(&start, "start", "s", "", "specify the start of commit date. eg.'yyyy-MM-dd'")
156 | glogCmd.PersistentFlags().StringVarP(&end, "end", "e", "", "specify the end of commit date. eg.'yyyy-MM-dd'")
157 | glogCmd.PersistentFlags().Int16VarP(&recentN, "recentN", "n", 10, "specify the number of commit log for each git repo. Limit 500 ")
158 | glogCmd.PersistentFlags().BoolVarP(&branchs, "branchs", "b", false, "show all branch logs,default is false (meaning show current branch). ")
159 |
160 | //gcl
161 | gclCmd.PersistentFlags().StringVarP(&token, "token", "t", "", "private token")
162 | }
163 |
--------------------------------------------------------------------------------
/cmd/md5.go:
--------------------------------------------------------------------------------
1 | package cmd
2 |
3 | import (
4 | "crypto/md5"
5 | "encoding/hex"
6 | "fmt"
7 | "github.com/spf13/cobra"
8 | )
9 |
10 | var (
11 | md5Cmd = &cobra.Command{
12 | Use: `md5`,
13 | Short: `Get a md5 for the given value or a random md5 value`,
14 | Long: `Get a md5 for the given value. If not specify value ,it will give a random md5 value`,
15 | Example: `lwe md5 yourValue`,
16 | Args: cobra.MatchAll(cobra.ExactArgs(1)),
17 | Run: func(cmd *cobra.Command, args []string) {
18 | specifyValue := args[0]
19 | sum := md5.Sum([]byte(specifyValue))
20 | fmt.Println(hex.EncodeToString(sum[:]))
21 | },
22 | }
23 | )
24 |
--------------------------------------------------------------------------------
/cmd/navicat.go:
--------------------------------------------------------------------------------
1 | package cmd
2 |
3 | import (
4 | "bufio"
5 | "bytes"
6 | "errors"
7 | "fmt"
8 | "github.com/spf13/cobra"
9 | "github.com/yesAnd92/lwe/handler/navicat"
10 | "io"
11 | "os"
12 | )
13 |
14 | /**
15 | * navicat命令相关功能
16 | */
17 | var (
18 | navicatCmd = &cobra.Command{
19 | Use: `ncx`,
20 | Short: `Decrypt password of connection in .ncx file`,
21 | Long: `The config exported from Navicat is encrypted,ncx command can decrypt it`,
22 | Example: `lwe ncx ncx-file-path `,
23 | Args: cobra.MatchAll(cobra.ExactArgs(1)),
24 | Run: func(cmd *cobra.Command, args []string) {
25 | ncxFilePath := args[0]
26 | data, dataErr := getNcxData(ncxFilePath)
27 | if dataErr != nil {
28 | fmt.Println(dataErr)
29 | }
30 | ncx, parseErr := navicat.ParseNcx(data)
31 | if parseErr != nil {
32 | cobra.CheckErr(fmt.Errorf("parse ncx file error: %s", parseErr))
33 | }
34 |
35 | for _, conn := range ncx.Conns {
36 |
37 | fmt.Printf("-----------%s-----------\n", conn.ConnectionName)
38 | fmt.Printf("DB type: %s\n"+
39 | "Connection host: %s\n"+
40 | "Connection port: %s\n"+
41 | "Connection username: %s\n"+
42 | "Connection password: %s\n\n", conn.ConnType, conn.Host, conn.Port, conn.UserName, conn.Password)
43 | }
44 |
45 | },
46 | }
47 | )
48 |
49 | // getNcxData 获取ncx数据
50 | func getNcxData(ncxFilePath string) (data []byte, err error) {
51 | fi, err := os.Open(ncxFilePath)
52 | if err != nil {
53 | return nil, errors.New(fmt.Sprintf("can't find ncx file: %s", ncxFilePath))
54 | }
55 | defer fi.Close()
56 |
57 | buffer := bytes.Buffer{}
58 | br := bufio.NewReader(fi)
59 | for {
60 | lineByte, _, e := br.ReadLine()
61 | buffer.Write(lineByte)
62 | if e == io.EOF {
63 | break
64 | }
65 | }
66 | return buffer.Bytes(), nil
67 | }
68 |
--------------------------------------------------------------------------------
/cmd/root.go:
--------------------------------------------------------------------------------
1 | package cmd
2 |
3 | import (
4 | "bytes"
5 | "github.com/spf13/cobra"
6 | )
7 |
8 | var (
9 | rootCmd = &cobra.Command{
10 | Use: "lwe",
11 | Short: "meaning leave work early!",
12 | Long: `meaning leave work early!`,
13 | }
14 | )
15 |
16 | // Execute executes the root command.
17 | func Execute() error {
18 | return rootCmd.Execute()
19 | }
20 |
21 | // ExecuteCommand executes the command
22 | func executeCommand(cmd *cobra.Command, args ...string) (outBf *bytes.Buffer, err error) {
23 | root := rootCmd
24 | root.AddCommand(cmd)
25 |
26 | outBf = new(bytes.Buffer)
27 | root.SetOut(outBf)
28 | root.SetErr(outBf)
29 | root.SetArgs(args)
30 |
31 | err = root.Execute()
32 | if err != nil {
33 | return nil, err
34 | }
35 | return outBf, err
36 | }
37 |
38 | func init() {
39 |
40 | rootCmd.AddCommand(versionCmd)
41 | rootCmd.AddCommand(fmtCmd)
42 | rootCmd.AddCommand(md5Cmd)
43 | rootCmd.AddCommand(esCmd)
44 | rootCmd.AddCommand(navicatCmd)
45 | rootCmd.AddCommand(urlCmd)
46 | rootCmd.AddCommand(glogCmd)
47 | rootCmd.AddCommand(glCmd)
48 | rootCmd.AddCommand(gclCmd)
49 | rootCmd.AddCommand(gstCmd)
50 | rootCmd.AddCommand(fsyncCmd)
51 | rootCmd.AddCommand(fileServerCmd)
52 | rootCmd.AddCommand(envCmd)
53 | rootCmd.AddCommand(sqlLogCmd)
54 | rootCmd.AddCommand(gSumCmd)
55 | rootCmd.AddCommand(gcmsgCmd)
56 |
57 | }
58 |
--------------------------------------------------------------------------------
/cmd/sqllog.go:
--------------------------------------------------------------------------------
1 | package cmd
2 |
3 | import (
4 | "fmt"
5 |
6 | "github.com/spf13/cobra"
7 | "github.com/yesAnd92/lwe/handler/sqllog"
8 | )
9 |
10 | /**
11 | * mybatis sql log parse
12 | */
13 |
14 | var (
15 | sqlLogCmd = &cobra.Command{
16 | Use: `sqllog`,
17 | Short: `Parse mybatis sql logs and fill placeholders with parameters`,
18 | Long: `Copy mybatis sql log ,extract sql info and fill placeholders with parameters`,
19 | Example: `lwe sqllog 'mybatis sql log'`,
20 | Args: cobra.MatchAll(cobra.ExactArgs(1)),
21 | Run: func(cmd *cobra.Command, args []string) {
22 | sqlLog := args[0]
23 |
24 | sql, err := sqllog.ParseMybatisSqlLog(sqlLog)
25 | if err != nil {
26 | cobra.CheckErr(err.Error())
27 | }
28 |
29 | fmt.Println("======>")
30 |
31 | fmt.Println(sql)
32 | },
33 | }
34 | )
35 |
--------------------------------------------------------------------------------
/cmd/url.go:
--------------------------------------------------------------------------------
1 | package cmd
2 |
3 | import "github.com/spf13/cobra"
4 | import "github.com/yesAnd92/lwe/handler/url"
5 |
6 | var urlCmd = &cobra.Command{
7 | Use: `url`,
8 | Short: `Format request url to increase readability`,
9 | Long: `Format request url to increase readability`,
10 | Example: `lwe url yourUrl`,
11 | Args: cobra.MatchAll(cobra.ExactArgs(1)),
12 | Run: func(cmd *cobra.Command, args []string) {
13 | params, err := url.HandleUrlPathParams(args[0])
14 | if err != nil {
15 | cobra.CheckErr(err)
16 | }
17 | url.FmtPrint(params)
18 | },
19 | }
20 |
--------------------------------------------------------------------------------
/cmd/version.go:
--------------------------------------------------------------------------------
1 | package cmd
2 |
3 | import (
4 | "fmt"
5 |
6 | "github.com/spf13/cobra"
7 | )
8 |
9 | var versionCmd = &cobra.Command{
10 | Use: "version",
11 | Short: "Print the version number of lwe",
12 | Long: `All software has versions. This is lwe's`,
13 | Run: func(cmd *cobra.Command, args []string) {
14 | fmt.Println("v.1.0.0.beta")
15 | },
16 | }
17 |
--------------------------------------------------------------------------------
/config/config.go:
--------------------------------------------------------------------------------
1 | package config
2 |
3 | import (
4 | "fmt"
5 | "github.com/spf13/cobra"
6 | "github.com/spf13/viper"
7 | "os"
8 | "path/filepath"
9 | )
10 |
11 | var GlobalConfig Config
12 |
13 | type Config struct {
14 | Ai Ai `yaml:"ai"`
15 | }
16 |
17 | type Ai struct {
18 | Name string `yaml:"name"`
19 | ApiKey string `yaml:"apikey"`
20 | BaseUrl string `yaml:"baseurl"`
21 | Model string `yaml:"model"`
22 | }
23 |
24 | func InitConfig() *Config {
25 | return loadingLweConfig("", "")
26 | }
27 |
28 | func loadingLweConfig(configPath, configName string) *Config {
29 |
30 | if len(configPath) == 0 {
31 | home, err := os.UserHomeDir()
32 | if err != nil {
33 | cobra.CheckErr(fmt.Sprintf("Can not get user home: %v", err))
34 | }
35 |
36 | configPath = filepath.Join(home, ".config", "lwe")
37 |
38 | }
39 |
40 | if len(configName) == 0 {
41 | configName = "config"
42 | }
43 | viper.SetConfigType("yaml") // config file type
44 | viper.SetConfigName(configName) // config file name without extension
45 | viper.AddConfigPath(configPath) // config dir
46 |
47 | if err := viper.ReadInConfig(); err != nil {
48 | cobra.CheckErr(err)
49 | }
50 |
51 | if err := viper.Unmarshal(&GlobalConfig); err != nil {
52 | cobra.CheckErr(fmt.Sprintf("Can not parse config file: %v", err))
53 | }
54 |
55 | return &GlobalConfig
56 | }
57 |
--------------------------------------------------------------------------------
/config/config_template.yml:
--------------------------------------------------------------------------------
1 | ################################################
2 | # Lwe Configuration Template
3 | #
4 | # Configuration File Location :
5 | # Linux,Mac:
6 | # $HOME/.config/lwe/config.yml
7 | #
8 | # Windows:
9 | # %USER%\.config\lwe\config.yml
10 | #
11 | #
12 | ################################################
13 |
14 |
15 |
16 | ai:
17 | name: siliconflow #Currently, only [deepseek,siliconflow] is supported
18 | apikey: your_api_key #Your API key
19 | baseurl: https://api.siliconflow.cn/v1/chat/completions #The API address of the model
20 | model: Qwen/Qwen2.5-Coder-32B-Instruct #The model name
21 |
22 |
--------------------------------------------------------------------------------
/config/config_test.go:
--------------------------------------------------------------------------------
1 | package config
2 |
3 | import (
4 | "fmt"
5 | "testing"
6 | )
7 |
8 | func Test_loadingLweConfig(t *testing.T) {
9 | type args struct {
10 | configPath string
11 | configName string
12 | }
13 | tests := []struct {
14 | name string
15 | args args
16 | }{
17 | // TODO: Add test cases.
18 | {
19 | name: "template test",
20 | args: args{
21 | configPath: "./",
22 | configName: "config_template",
23 | },
24 | },
25 | {
26 | name: "template test",
27 | args: args{
28 | configPath: "",
29 | configName: "",
30 | },
31 | },
32 | }
33 | for _, tt := range tests {
34 | t.Run(tt.name, func(t *testing.T) {
35 | config := loadingLweConfig(tt.args.configPath, tt.args.configName)
36 | fmt.Println(config)
37 | })
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/doc/img/gcmsg.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yesAnd92/lwe/4c5dc5808aab38201001b92af50700e689d9425a/doc/img/gcmsg.webp
--------------------------------------------------------------------------------
/doc/img/glog.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yesAnd92/lwe/4c5dc5808aab38201001b92af50700e689d9425a/doc/img/glog.webp
--------------------------------------------------------------------------------
/doc/img/sqllog.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yesAnd92/lwe/4c5dc5808aab38201001b92af50700e689d9425a/doc/img/sqllog.webp
--------------------------------------------------------------------------------
/go.mod:
--------------------------------------------------------------------------------
1 | module github.com/yesAnd92/lwe
2 |
3 | go 1.18
4 |
5 | require (
6 | github.com/AlecAivazis/survey/v2 v2.3.7
7 | github.com/dlclark/regexp2 v1.11.4
8 | github.com/jedib0t/go-pretty/v6 v6.6.5
9 | github.com/micro-plat/lib4go v1.2.0
10 | github.com/mitchellh/mapstructure v1.5.0
11 | github.com/pkg/errors v0.9.1
12 | github.com/spf13/cobra v1.6.1
13 | github.com/spf13/viper v1.19.0
14 | github.com/xwb1989/sqlparser v0.0.0-20180606152119-120387863bf2
15 | )
16 |
17 | require (
18 | github.com/clbanning/mxj v1.8.4 // indirect
19 | github.com/fsnotify/fsnotify v1.7.0 // indirect
20 | github.com/hashicorp/hcl v1.0.0 // indirect
21 | github.com/inconshreveable/mousetrap v1.1.0 // indirect
22 | github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect
23 | github.com/magiconair/properties v1.8.7 // indirect
24 | github.com/mattn/go-colorable v0.1.13 // indirect
25 | github.com/mattn/go-isatty v0.0.17 // indirect
26 | github.com/mattn/go-runewidth v0.0.15 // indirect
27 | github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b // indirect
28 | github.com/pelletier/go-toml/v2 v2.2.2 // indirect
29 | github.com/rivo/uniseg v0.4.4 // indirect
30 | github.com/sagikazarmark/locafero v0.4.0 // indirect
31 | github.com/sagikazarmark/slog-shim v0.1.0 // indirect
32 | github.com/shopspring/decimal v1.2.0 // indirect
33 | github.com/sourcegraph/conc v0.3.0 // indirect
34 | github.com/spf13/afero v1.11.0 // indirect
35 | github.com/spf13/cast v1.6.0 // indirect
36 | github.com/spf13/pflag v1.0.5 // indirect
37 | github.com/subosito/gotenv v1.6.0 // indirect
38 | go.uber.org/atomic v1.9.0 // indirect
39 | go.uber.org/multierr v1.9.0 // indirect
40 | golang.org/x/exp v0.0.0-20230905200255-921286631fa9 // indirect
41 | golang.org/x/sys v0.18.0 // indirect
42 | golang.org/x/term v0.17.0 // indirect
43 | golang.org/x/text v0.21.0 // indirect
44 | gopkg.in/ini.v1 v1.67.0 // indirect
45 | gopkg.in/yaml.v3 v3.0.1 // indirect
46 | )
47 |
--------------------------------------------------------------------------------
/go.sum:
--------------------------------------------------------------------------------
1 | github.com/AlecAivazis/survey/v2 v2.3.7 h1:6I/u8FvytdGsgonrYsVn2t8t4QiRnh6QSTqkkhIiSjQ=
2 | github.com/AlecAivazis/survey/v2 v2.3.7/go.mod h1:xUTIdE4KCOIjsBAE1JYsUPoCqYdZ1reCfTwbto0Fduo=
3 | github.com/Netflix/go-expect v0.0.0-20220104043353-73e0943537d2 h1:+vx7roKuyA63nhn5WAunQHLTznkw5W8b1Xc0dNjp83s=
4 | github.com/Netflix/go-expect v0.0.0-20220104043353-73e0943537d2/go.mod h1:HBCaDeC1lPdgDeDbhX8XFpy1jqjK0IBG8W5K+xYqA0w=
5 | github.com/clbanning/mxj v1.8.4 h1:HuhwZtbyvyOw+3Z1AowPkU87JkJUSv751ELWaiTpj8I=
6 | github.com/clbanning/mxj v1.8.4/go.mod h1:BVjHeAH+rl9rs6f+QIpeRl0tfu10SXn1pUSa5PVGJng=
7 | github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
8 | github.com/creack/pty v1.1.17 h1:QeVUsEDNrLBW4tMgZHvxy18sKtr6VI492kBhUfhDJNI=
9 | github.com/creack/pty v1.1.17/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4=
10 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
11 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
12 | github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
13 | github.com/dlclark/regexp2 v1.11.4 h1:rPYF9/LECdNymJufQKmri9gV604RvvABwgOA8un7yAo=
14 | github.com/dlclark/regexp2 v1.11.4/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
15 | github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=
16 | github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA=
17 | github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM=
18 | github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
19 | github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
20 | github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
21 | github.com/hinshun/vt10x v0.0.0-20220119200601-820417d04eec h1:qv2VnGeEQHchGaZ/u7lxST/RaJw+cv273q79D81Xbog=
22 | github.com/hinshun/vt10x v0.0.0-20220119200601-820417d04eec/go.mod h1:Q48J4R4DvxnHolD5P8pOtXigYlRuPLGl6moFx3ulM68=
23 | github.com/inconshreveable/mousetrap v1.0.1/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
24 | github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
25 | github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
26 | github.com/jedib0t/go-pretty/v6 v6.6.5 h1:9PgMJOVBedpgYLI56jQRJYqngxYAAzfEUua+3NgSqAo=
27 | github.com/jedib0t/go-pretty/v6 v6.6.5/go.mod h1:Uq/HrbhuFty5WSVNfjpQQe47x16RwVGXIveNGEyGtHs=
28 | github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs=
29 | github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8=
30 | github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
31 | github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
32 | github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY=
33 | github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0=
34 | github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
35 | github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
36 | github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
37 | github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
38 | github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
39 | github.com/mattn/go-isatty v0.0.17 h1:BTarxUcIeDqL27Mc+vyvdWYSL28zpIhv3RoTdsLMPng=
40 | github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
41 | github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U=
42 | github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
43 | github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b h1:j7+1HpAFS1zy5+Q4qx1fWh90gTKwiN4QCGoY9TWyyO4=
44 | github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE=
45 | github.com/micro-plat/lib4go v1.2.0 h1:cho4rFfWdwVWefIbFPP7cXghIlKLN0H8k3PX6QdqN0c=
46 | github.com/micro-plat/lib4go v1.2.0/go.mod h1:yh2gi/oGoebmVKH6zUJo0V8BmE7Ogx2biuVTRn1R9zk=
47 | github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
48 | github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
49 | github.com/pelletier/go-toml/v2 v2.2.2 h1:aYUidT7k73Pcl9nb2gScu7NSrKCSHIDE89b3+6Wq+LM=
50 | github.com/pelletier/go-toml/v2 v2.2.2/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs=
51 | github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
52 | github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
53 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
54 | github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
55 | github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
56 | github.com/rivo/uniseg v0.4.4 h1:8TfxU8dW6PdqD27gjM8MVNuicgxIjxpm4K7x4jp8sis=
57 | github.com/rivo/uniseg v0.4.4/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
58 | github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8=
59 | github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
60 | github.com/sagikazarmark/locafero v0.4.0 h1:HApY1R9zGo4DBgr7dqsTH/JJxLTTsOt7u6keLGt6kNQ=
61 | github.com/sagikazarmark/locafero v0.4.0/go.mod h1:Pe1W6UlPYUk/+wc/6KFhbORCfqzgYEpgQ3O5fPuL3H4=
62 | github.com/sagikazarmark/slog-shim v0.1.0 h1:diDBnUNK9N/354PgrxMywXnAwEr1QZcOr6gto+ugjYE=
63 | github.com/sagikazarmark/slog-shim v0.1.0/go.mod h1:SrcSrq8aKtyuqEI1uvTDTK1arOWRIczQRv+GVI1AkeQ=
64 | github.com/shopspring/decimal v1.2.0 h1:abSATXmQEYyShuxI4/vyW3tV1MrKAJzCZ/0zLUXYbsQ=
65 | github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o=
66 | github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo=
67 | github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0=
68 | github.com/spf13/afero v1.11.0 h1:WJQKhtpdm3v2IzqG8VMqrr6Rf3UYpEF239Jy9wNepM8=
69 | github.com/spf13/afero v1.11.0/go.mod h1:GH9Y3pIexgf1MTIWtNGyogA5MwRIDXGUr+hbWNoBjkY=
70 | github.com/spf13/cast v1.6.0 h1:GEiTHELF+vaR5dhz3VqZfFSzZjYbgeKDpBxQVS4GYJ0=
71 | github.com/spf13/cast v1.6.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo=
72 | github.com/spf13/cobra v1.6.1 h1:o94oiPyS4KD1mPy2fmcYYHHfCxLqYjJOhGsCHFZtEzA=
73 | github.com/spf13/cobra v1.6.1/go.mod h1:IOw/AERYS7UzyrGinqmz6HLUo219MORXGxhbaJUqzrY=
74 | github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
75 | github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
76 | github.com/spf13/viper v1.19.0 h1:RWq5SEjt8o25SROyN3z2OrDB9l7RPd3lwTWU8EcEdcI=
77 | github.com/spf13/viper v1.19.0/go.mod h1:GQUN9bilAbhU/jgc1bKs99f/suXKeUMct8Adx5+Ntkg=
78 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
79 | github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
80 | github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
81 | github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
82 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
83 | github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
84 | github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
85 | github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
86 | github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
87 | github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
88 | github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
89 | github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8=
90 | github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU=
91 | github.com/xwb1989/sqlparser v0.0.0-20180606152119-120387863bf2 h1:zzrxE1FKn5ryBNl9eKOeqQ58Y/Qpo3Q9QNxKHX5uzzQ=
92 | github.com/xwb1989/sqlparser v0.0.0-20180606152119-120387863bf2/go.mod h1:hzfGeIUDq/j97IG+FhNqkowIyEcD88LrW6fyU3K3WqY=
93 | github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
94 | go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE=
95 | go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
96 | go.uber.org/multierr v1.9.0 h1:7fIwc/ZtS0q++VgcfqFDxSBZVv/Xo49/SYnDFupUwlI=
97 | go.uber.org/multierr v1.9.0/go.mod h1:X2jQV1h+kxSjClGpnseKVIxpmcjrj7MNnI0bnlfKTVQ=
98 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
99 | golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
100 | golang.org/x/exp v0.0.0-20230905200255-921286631fa9 h1:GoHiUyI/Tp2nVkLI2mCxVkOjsbSXD66ic0XW0js0R9g=
101 | golang.org/x/exp v0.0.0-20230905200255-921286631fa9/go.mod h1:S2oDrQGGwySpoQPVqRShND87VCbxmc6bL1Yd2oYrm6k=
102 | golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
103 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
104 | golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
105 | golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
106 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
107 | golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
108 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
109 | golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
110 | golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
111 | golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
112 | golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
113 | golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
114 | golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
115 | golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4=
116 | golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
117 | golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
118 | golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
119 | golang.org/x/term v0.17.0 h1:mkTF7LCd6WGJNL3K1Ad7kwxNfYAW6a8a8QqtMblp/4U=
120 | golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=
121 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
122 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
123 | golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
124 | golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
125 | golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo=
126 | golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
127 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
128 | golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
129 | golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
130 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
131 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
132 | gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
133 | gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA=
134 | gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
135 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
136 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
137 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
138 |
--------------------------------------------------------------------------------
/handler/env/env_variable.go:
--------------------------------------------------------------------------------
1 | package env
2 |
3 | import (
4 | "fmt"
5 | "github.com/yesAnd92/lwe/utils"
6 | "strings"
7 | "time"
8 | )
9 |
10 | type EnvInfo struct {
11 | Path string
12 | envItems []string
13 | }
14 |
15 | // MacEvnPath MAC env variable file
16 | var MacEvnPath = []string{
17 | "~/.bash_profile", "~/.zshrc", "~/.bashrc"}
18 |
19 | // LinuxEvnPath linux env variable file
20 | var LinuxEvnPath = []string{
21 | "~/.zshrc", "~/.bashrc", "/etc/environment"}
22 |
23 | type AbstractEnvV struct {
24 | }
25 |
26 | func EnvCat(envInfos []*EnvInfo) {
27 | for _, info := range envInfos {
28 | fmt.Printf("%s >>>\n", info.Path)
29 | for _, item := range info.envItems {
30 | fmt.Println(item)
31 | }
32 |
33 | fmt.Printf("\n\n")
34 | }
35 | }
36 |
37 | type IEnvVariable interface {
38 | FindEnvInfo() []*EnvInfo
39 | }
40 |
41 | type WinEVnVariable struct {
42 | }
43 |
44 | func (w *WinEVnVariable) FindEnvInfo() []*EnvInfo {
45 |
46 | //TODO implement me
47 | panic("implement me")
48 | }
49 |
50 | type MacEVnVariable struct {
51 | }
52 |
53 | func (m *MacEVnVariable) FindEnvInfo() (envInfos []*EnvInfo) {
54 |
55 | for _, path := range MacEvnPath {
56 | cmd := fmt.Sprintf("cat %s", path)
57 | result := utils.RunCmd(cmd, 2*time.Second)
58 | if result.Err() != nil {
59 | continue
60 | }
61 |
62 | var envItems []string
63 | envLineSplit := strings.Split(result.String(), "\n")
64 | for _, envLine := range envLineSplit {
65 | envLine = strings.TrimSpace(envLine)
66 | // filter comment line
67 | if strings.HasPrefix(envLine, "#") || len(envLine) == 0 {
68 | continue
69 | }
70 | envItems = append(envItems, envLine)
71 |
72 | }
73 |
74 | if len(envItems) == 0 {
75 | continue
76 | }
77 | envInfos = append(envInfos, &EnvInfo{
78 | Path: path,
79 | envItems: envItems,
80 | })
81 |
82 | }
83 |
84 | return
85 | }
86 |
--------------------------------------------------------------------------------
/handler/env/env_variable_test.go:
--------------------------------------------------------------------------------
1 | package env
2 |
3 | import (
4 | "testing"
5 | )
6 |
7 | func TestMacEVnVariable_findEnvInfo(t *testing.T) {
8 | mac := &MacEVnVariable{}
9 |
10 | EnvCat(mac.FindEnvInfo())
11 | }
12 |
--------------------------------------------------------------------------------
/handler/es/handle_select.go:
--------------------------------------------------------------------------------
1 | package es
2 |
3 | import (
4 | "errors"
5 | "fmt"
6 | "github.com/xwb1989/sqlparser"
7 | "strings"
8 | )
9 |
10 | func HandleSelect(sel *sqlparser.Select) (dsl string, esType string, err error) {
11 |
12 | var defaultQueryDsl = `{"bool":{"must":[{"match_all":{}}]}}`
13 | var queryDsl string
14 |
15 | //目的是告诉下一层级这是个root节点
16 | var root sqlparser.Expr
17 | //处理where子句,构造queryDsl
18 | if sel.Where != nil {
19 | queryDsl, err = handlerWhere(&sel.Where.Expr, true, &root)
20 | if err != nil {
21 | return "", "", err
22 | }
23 | }
24 |
25 | if queryDsl == "" {
26 | queryDsl = defaultQueryDsl
27 | }
28 |
29 | //处理表名
30 | if len(sel.From) > 1 {
31 | return "", "", errors.New("not support multiple table now")
32 | }
33 | esType = sqlparser.String(sel.From)
34 |
35 | //处理分页参数
36 | offset, rowCount := "0", "1"
37 | if sel.Limit != nil {
38 | offset = sqlparser.String(sel.Limit.Offset)
39 | rowCount = sqlparser.String(sel.Limit.Rowcount)
40 | }
41 |
42 | //处理排序,可能有多个字段
43 | var orderByArr []string
44 | if sel.OrderBy != nil {
45 | for _, orderExpr := range sel.OrderBy {
46 | //取出每对<排序字段,升降序>进行拼接
47 | colName := sqlparser.String(orderExpr.Expr)
48 | direction := orderExpr.Direction
49 | orderByArr = append(orderByArr, fmt.Sprintf(`{"%v":"%v"}`, colName, direction))
50 | }
51 | }
52 |
53 | var resultMap = map[string]string{
54 | "query": queryDsl,
55 | "from": offset,
56 | "size": rowCount,
57 | }
58 | if len(orderByArr) > 0 {
59 | //如果存在排序字段,多个排序条件是个数组,用"[]"包起来
60 | querySort := fmt.Sprintf("[%v]", strings.Join(orderByArr, ","))
61 | resultMap["sort"] = querySort
62 | }
63 |
64 | //指定关键字的顺序来保证生成结果的稳定
65 | //稳定的顺序可以使风格更统一,也便于写测试用例
66 | var desiredSequence = []string{"query", "from", "size", "sort", "aggregations"}
67 | var resultArr []string
68 | for _, key := range desiredSequence {
69 | if value, ok := resultMap[key]; ok {
70 | resultArr = append(resultArr, fmt.Sprintf(`"%v":%v`, key, value))
71 | }
72 | }
73 | dsl = fmt.Sprintf("{%v}", strings.Join(resultArr, ","))
74 | return dsl, esType, nil
75 | }
76 |
77 | func handlerWhere(expr *sqlparser.Expr, isTop bool, parent *sqlparser.Expr) (string, error) {
78 | if expr == nil {
79 | return "", errors.New("handlerWhere: expr can't be nil here ")
80 | }
81 | switch (*expr).(type) {
82 |
83 | //处理表达式
84 | case *sqlparser.ComparisonExpr:
85 | return handleSelectWhereComparisonExpr(expr, isTop)
86 | case *sqlparser.AndExpr:
87 | return handleSelectWhereAndExpr(expr, isTop, parent)
88 | case *sqlparser.OrExpr:
89 | return handleSelectWhereOrExpr(expr, isTop, parent)
90 | case *sqlparser.RangeCond:
91 | return handleSelectWhereRangeExpr(expr, isTop, parent)
92 | case *sqlparser.ParenExpr:
93 | return handleSelectWhereParentExpr(expr, isTop, parent)
94 | default:
95 | return "", errors.New(fmt.Sprintf(`[%v] is supported currently! `, sqlparser.String(*expr)))
96 | }
97 |
98 | return "", nil
99 |
100 | }
101 |
102 | //处理between范围,例如between a and b
103 | func handleSelectWhereRangeExpr(expr *sqlparser.Expr, isTop bool, parent *sqlparser.Expr) (string, error) {
104 |
105 | rangeCondExpr := (*expr).(*sqlparser.RangeCond)
106 |
107 | var colNameStr, fromStr, toStr string
108 | if colName, ok := rangeCondExpr.Left.(*sqlparser.ColName); ok {
109 | colNameStr = sqlparser.String(colName)
110 | } else {
111 | return "", errors.New("invalidated comparison expression")
112 | }
113 |
114 | fromStr = sqlparser.String(rangeCondExpr.From)
115 | fromStr = strings.NewReplacer("'", "\"").Replace(fromStr)
116 |
117 | toStr = sqlparser.String(rangeCondExpr.To)
118 | toStr = strings.NewReplacer("'", "\"").Replace(toStr)
119 |
120 | resultStr := fmt.Sprintf(`{"range":{"%v":{"from":"%v","to":"%v"}}}`, colNameStr, fromStr, toStr)
121 |
122 | if isTop {
123 | resultStr = fmt.Sprintf(`{"bool":{"must":[%v]}}`, resultStr)
124 | }
125 |
126 | return resultStr, nil
127 |
128 | }
129 |
130 | //处理带括号的语句,比如 where a=1 and (b=1 or c=1 )
131 | func handleSelectWhereParentExpr(expr *sqlparser.Expr, isTop bool, parent *sqlparser.Expr) (string, error) {
132 | parenExpr := (*expr).(*sqlparser.ParenExpr)
133 | boolExpr := parenExpr.Expr
134 | return handlerWhere(&boolExpr, isTop, parent)
135 |
136 | }
137 |
138 | func handleSelectWhereAndExpr(expr *sqlparser.Expr, isTop bool, parent *sqlparser.Expr) (string, error) {
139 | andExpr := (*expr).(*sqlparser.AndExpr)
140 | var leftStr, rightStr string
141 | var err error
142 |
143 | //AndExpr的左右两边可能是单独的ComparisonExpr,也可能还是个AndExpr等,
144 | //因此还可以调用handlerWhere,即递归
145 |
146 | //处理and左边的表达式
147 | leftStr, err = handlerWhere(&andExpr.Left, false, expr)
148 | if err != nil {
149 | return "", err
150 | }
151 |
152 | //处理and右边的表达式
153 | rightStr, err = handlerWhere(&andExpr.Right, false, expr)
154 | if err != nil {
155 | return "", err
156 | }
157 |
158 | //拼接
159 | var resultStr string
160 | if leftStr == "" || rightStr == "" {
161 | resultStr = leftStr + rightStr
162 | } else {
163 | resultStr = leftStr + "," + rightStr
164 | }
165 |
166 | //如果上一级也是and关系,则没必要在拼接bool must
167 | if _, ok := (*parent).(*sqlparser.AndExpr); ok {
168 | return resultStr, nil
169 | }
170 |
171 | return fmt.Sprintf(`{"bool":{"must":[%v]}}`, resultStr), nil
172 | }
173 |
174 | func handleSelectWhereOrExpr(expr *sqlparser.Expr, isTop bool, parent *sqlparser.Expr) (string, error) {
175 | orExpr := (*expr).(*sqlparser.OrExpr)
176 | var leftStr, rightStr string
177 | var err error
178 |
179 | //AndExpr的左右两边可能是单独的ComparisonExpr,也可能还是个AndExpr等,
180 | //因此还可以调用handlerWhere,即递归
181 |
182 | //处理and左边的表达式
183 | leftStr, err = handlerWhere(&orExpr.Left, false, expr)
184 | //处理and右边的表达式
185 | rightStr, err = handlerWhere(&orExpr.Right, false, expr)
186 |
187 | //拼接
188 | var resultStr string
189 | if leftStr == "" || rightStr == "" {
190 | resultStr = leftStr + rightStr
191 | } else {
192 | resultStr = leftStr + "," + rightStr
193 | }
194 |
195 | //如果上一级也是or关系,则没必要在拼接bool must
196 | if _, ok := (*parent).(*sqlparser.OrExpr); ok {
197 | return resultStr, nil
198 | }
199 |
200 | return fmt.Sprintf(`{"bool":{"should":[%v]}}`, resultStr), err
201 | }
202 |
203 | func handleSelectWhereComparisonExpr(expr *sqlparser.Expr, isTop bool) (string, error) {
204 | comparisonExpr := (*expr).(*sqlparser.ComparisonExpr)
205 | var colNameStr string
206 | var valueStr string
207 | if colName, ok := comparisonExpr.Left.(*sqlparser.ColName); ok {
208 | colNameStr = sqlparser.String(colName)
209 | } else {
210 | return "", errors.New("invalidated comparison expression")
211 | }
212 |
213 | //由于操作符右边的可能性比较多,单独进行处理
214 | valueStr, err := buildComparisonExprRight(comparisonExpr.Right)
215 | if err != nil {
216 | return "", err
217 | }
218 |
219 | var comparisonStr string
220 | //根据不同的比较符号,返回拼接对应的语句
221 | switch comparisonExpr.Operator {
222 |
223 | case "=":
224 | //等号对应到dsl的match_phrase
225 | //{"match_phrase":{k:v}} 和 {"match_phrase":{k:{"query":v}}}等价,这里都是用第一种写法
226 | //match_phrase查询首先解析查询字符串来产生一个词条列表。然后会搜索所有的词条
227 | //但只保留含有了所有搜索词条的文档,并且词条的位置要邻接
228 | comparisonStr = fmt.Sprintf(`{"match_phrase":{"%v":"%v"}}`, colNameStr, valueStr)
229 |
230 | case ">":
231 | //范围类操作符对应到dsl的range
232 | comparisonStr = fmt.Sprintf(`{"range":{"%v":{"gt":"%v"}}}`, colNameStr, valueStr)
233 | case "<":
234 | comparisonStr = fmt.Sprintf(`{"range":{"%v":{"lt":"%v"}}}`, colNameStr, valueStr)
235 |
236 | case ">=":
237 | //范围类操作符对应到dsl的range
238 | comparisonStr = fmt.Sprintf(`{"range":{"%v":{"gte":"%v"}}}`, colNameStr, valueStr)
239 | case "<=":
240 | comparisonStr = fmt.Sprintf(`{"range":{"%v":{"lte":"%v"}}}`, colNameStr, valueStr)
241 |
242 | case "!=":
243 | //!=对应到dsl的must_not
244 | comparisonStr = fmt.Sprintf(`{"bool":{"must_not":[{"match_phrase":{"%v":"%v"}}]}}`, colNameStr, valueStr)
245 |
246 | case "in":
247 | //in对应到dsl的terms
248 | comparisonStr = fmt.Sprintf(`{"terms":{"%v":[%v]}}`, colNameStr, valueStr)
249 |
250 | case "not in":
251 | //in对应到dsl的terms
252 | comparisonStr = fmt.Sprintf(`{"bool":{"must_not":{"terms":{"%v":[%v]}}}}`, colNameStr, valueStr)
253 |
254 | case "like":
255 | //like对应到dsl的match_phrase
256 | comparisonStr = fmt.Sprintf(`{"match_phrase":{"%v":"%v"}}`, colNameStr, valueStr)
257 | case "not like":
258 | //like对应到dsl的match_phrase
259 | comparisonStr = fmt.Sprintf(`{"bool":{"must_not":{"match_phrase":{"%v":"%v"}}}}`, colNameStr, valueStr)
260 | }
261 |
262 | if isTop {
263 | comparisonStr = fmt.Sprintf(`{"bool":{"must":[%v]}}`, comparisonStr)
264 | }
265 | return comparisonStr, nil
266 | }
267 |
268 | func buildComparisonExprRight(expr sqlparser.Expr) (string, error) {
269 |
270 | var valueStr string
271 |
272 | switch expr.(type) {
273 |
274 | case *sqlparser.SQLVal:
275 | valueStr = sqlparser.String(expr)
276 | //统一去掉引号类字符
277 | valueStr = strings.NewReplacer("`", "", "'", "", "\"", "").Replace(valueStr)
278 |
279 | case sqlparser.ValTuple:
280 | //sqlparser.ValTuple为啥不是指针了??
281 | valueStr = sqlparser.String(expr)
282 | //去掉首尾的括号、引号类字符
283 | valueStr = strings.NewReplacer("(", "", ")", "", "'", "\"").Replace(valueStr)
284 | }
285 |
286 | return valueStr, nil
287 | }
288 |
--------------------------------------------------------------------------------
/handler/es/handle_select_test.go:
--------------------------------------------------------------------------------
1 | package es
2 |
3 | import (
4 | "fmt"
5 | "github.com/xwb1989/sqlparser"
6 | "reflect"
7 | "testing"
8 | )
9 |
10 | func TestHandleSelect(t *testing.T) {
11 | selectSqlMap := map[string]string{
12 | //单条件测试用例
13 | `select * from user order by age ,create_time desc limit 10,10`: `{"query":{"bool":{"must":[{"match_all":{}}]}},"from":10,"size":10,"sort":[{"age":"asc"},{"create_time":"desc"}]}`,
14 | `select * from user where last_name='yesand' order by age ,create_time desc limit 10,10`: `{"query":{"bool":{"must":[{"match_phrase":{"last_name":"yesand"}}]}},"from":10,"size":10,"sort":[{"age":"asc"},{"create_time":"desc"}]}`,
15 | `select * from user where age>18 order by age ,create_time desc limit 10,10`: `{"query":{"bool":{"must":[{"range":{"age":{"gt":"18"}}}]}},"from":10,"size":10,"sort":[{"age":"asc"},{"create_time":"desc"}]}`,
16 | `select * from user where age<35 order by age ,create_time desc limit 10,10`: `{"query":{"bool":{"must":[{"range":{"age":{"lt":"35"}}}]}},"from":10,"size":10,"sort":[{"age":"asc"},{"create_time":"desc"}]}`,
17 | `select * from user where age>=18 order by age ,create_time desc limit 10,10`: `{"query":{"bool":{"must":[{"range":{"age":{"gte":"18"}}}]}},"from":10,"size":10,"sort":[{"age":"asc"},{"create_time":"desc"}]}`,
18 | `select * from user where age<=35 order by age ,create_time desc limit 10,10`: `{"query":{"bool":{"must":[{"range":{"age":{"lte":"35"}}}]}},"from":10,"size":10,"sort":[{"age":"asc"},{"create_time":"desc"}]}`,
19 | `select * from user where age != 35 order by age ,create_time desc limit 10,10`: `{"query":{"bool":{"must":[{"bool":{"must_not":[{"match_phrase":{"age":"35"}}]}}]}},"from":10,"size":10,"sort":[{"age":"asc"},{"create_time":"desc"}]}`,
20 | `select * from user where age in (18,19,20) order by age ,create_time desc limit 10,10`: `{"query":{"bool":{"must":[{"terms":{"age":[18, 19, 20]}}]}},"from":10,"size":10,"sort":[{"age":"asc"},{"create_time":"desc"}]}`,
21 | `select * from user where age not in (18,19,20) order by age ,create_time desc limit 10,10`: `{"query":{"bool":{"must":[{"bool":{"must_not":{"terms":{"age":[18, 19, 20]}}}}]}},"from":10,"size":10,"sort":[{"age":"asc"},{"create_time":"desc"}]}`,
22 | `select * from user where age not in (18,19,20) and name in ("y","z")`: `{"query":{"bool":{"must":[{"bool":{"must_not":{"terms":{"age":[18, 19, 20]}}}},{"terms":{"name":["y", "z"]}}]}},"from":0,"size":1}`,
23 | `select * from user where last_name like "yes" limit 10,10`: `{"query":{"bool":{"must":[{"match_phrase":{"last_name":"yes"}}]}},"from":10,"size":10}`,
24 | `select * from user where last_name not like "yes" limit 10,10`: `{"query":{"bool":{"must":[{"bool":{"must_not":{"match_phrase":{"last_name":"yes"}}}}]}},"from":10,"size":10}`,
25 | `select * from user where age between 18 and 35`: `{"query":{"bool":{"must":[{"range":{"age":{"from":"18","to":"35"}}}]}},"from":0,"size":1}`,
26 | `select * from user where age between 18 and 35 and createTime between '2023-04-01 00:00:00' and '2023-04-10 00:00:00'`: `{"query":{"bool":{"must":[{"range":{"age":{"from":"18","to":"35"}}},{"range":{"createTime":{"from":""2023-04-01 00:00:00"","to":""2023-04-10 00:00:00""}}}]}},"from":0,"size":1}`,
27 | //多条件测试用例
28 | `select * from user where age>=18 and gender=1 and last_name = "yesAnd"`: `{"query":{"bool":{"must":[{"range":{"age":{"gte":"18"}}},{"match_phrase":{"gender":"1"}},{"match_phrase":{"last_name":"yesAnd"}}]}},"from":0,"size":1}`,
29 | `select * from user where age>=18 and gender=1 or last_name = "yesAnd"`: `{"query":{"bool":{"should":[{"bool":{"must":[{"range":{"age":{"gte":"18"}}},{"match_phrase":{"gender":"1"}}]}},{"match_phrase":{"last_name":"yesAnd"}}]}},"from":0,"size":1}`,
30 | `select * from user where age>=18 and ( gender=1 or last_name = "yesAnd")`: `{"query":{"bool":{"must":[{"range":{"age":{"gte":"18"}}},{"bool":{"should":[{"match_phrase":{"gender":"1"}},{"match_phrase":{"last_name":"yesAnd"}}]}}]}},"from":0,"size":1}`,
31 | }
32 |
33 | for sql, expect := range selectSqlMap {
34 |
35 | sta, _ := sqlparser.Parse(sql)
36 | dsl, _, _ := HandleSelect(sta.(*sqlparser.Select))
37 | fmt.Println(dsl)
38 | if !reflect.DeepEqual(dsl, expect) {
39 | t.Error("the generated dsl is not equal to expected", sql)
40 | }
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/handler/fileserver/file_server.go:
--------------------------------------------------------------------------------
1 | package fileserver
2 |
3 | import (
4 | "fmt"
5 | "github.com/spf13/cobra"
6 | "github.com/yesAnd92/lwe/utils"
7 | "net/http"
8 | "strconv"
9 | "sync"
10 | )
11 |
12 | var (
13 | defaultIP = "127.0.0.1"
14 | defaultPort = 9527
15 | MaxPort = 9999
16 | fileAccessCounter = make(map[string]int)
17 | lock sync.Mutex
18 | )
19 |
20 | // add visit count
21 | func addAccessCount(filePath string) int {
22 | lock.Lock()
23 | defer lock.Unlock()
24 |
25 | count := 0
26 | if _, ok := fileAccessCounter[filePath]; ok {
27 | count = fileAccessCounter[filePath]
28 | }
29 | count++
30 | fileAccessCounter[filePath] = count
31 | return count
32 | }
33 |
34 | func ServerStart(port, rootDir string) {
35 |
36 | if len(port) == 0 {
37 | port = strconv.Itoa(havaAvailablePort())
38 | }
39 |
40 | rootDir = utils.ToAbsPath(rootDir)
41 |
42 | http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
43 |
44 | filePath := r.URL.Path
45 |
46 | //file access statistic
47 | count := addAccessCount(filePath)
48 |
49 | fmt.Printf("%s - %d visit\n", filePath, count)
50 |
51 | http.ServeFile(w, r, rootDir+filePath)
52 |
53 | })
54 |
55 | // listen local file server
56 | fmt.Printf("%s/ ==> http://%s/\n", rootDir, fmt.Sprintf("%s:%s", defaultIP, port))
57 | err := http.ListenAndServe(fmt.Sprintf(":%s", port), nil)
58 | if err != nil {
59 | cobra.CheckErr(err)
60 | }
61 | }
62 |
63 | func havaAvailablePort() int {
64 |
65 | for defaultPort <= MaxPort && !utils.PortAvailable(defaultPort) {
66 | defaultPort++
67 | }
68 | return defaultPort
69 | }
70 |
--------------------------------------------------------------------------------
/handler/fileserver/file_server_test.go:
--------------------------------------------------------------------------------
1 | package fileserver
2 |
3 | import "testing"
4 |
5 | func TestServerStart(t *testing.T) {
6 |
7 | //go test skip this case
8 | t.Skip()
9 | rootDir := "../../testdata"
10 | ServerStart("", rootDir)
11 |
12 | }
13 |
--------------------------------------------------------------------------------
/handler/gitcmd/common.go:
--------------------------------------------------------------------------------
1 | package gitcmd
2 |
3 | import (
4 | "fmt"
5 | "github.com/spf13/cobra"
6 | "github.com/yesAnd92/lwe/utils"
7 | "log"
8 | "os"
9 | "path/filepath"
10 | "strings"
11 | "time"
12 | )
13 |
14 | func findGitRepo(dir string, res *[]string) {
15 |
16 | // Search for git repo dir recursively
17 | nextGitRepo(dir, res)
18 |
19 | //check the dir is under git repo
20 | if len(*res) == 0 && checkExistGitRepo(".") {
21 |
22 | *res = append(*res, dir)
23 | }
24 | }
25 |
26 | func nextGitRepo(dir string, res *[]string) {
27 | var files []string
28 | fileInfo, err := os.ReadDir(dir)
29 | if err != nil {
30 | cobra.CheckErr(fmt.Errorf(" The dir '%s' is not exist!\n", dir))
31 | return
32 | }
33 |
34 | for _, file := range fileInfo {
35 | //当前目录是git仓库,没必要继续遍历
36 | if ".git" == file.Name() {
37 | *res = append(*res, dir)
38 | return
39 | }
40 | if file.IsDir() {
41 | files = append(files, file.Name())
42 | }
43 | }
44 |
45 | //目录下的子目录递归遍历
46 | for _, fName := range files {
47 | nextGitRepo(filepath.Join(dir, fName), res)
48 | }
49 | }
50 |
51 | type branchInfo struct {
52 | curr string
53 | branchs []string
54 | }
55 |
56 | // ListRepoAllBranch list all git Branch under repository
57 | func ListRepoAllBranch(repo string) (re *branchInfo) {
58 |
59 | // get current dir
60 | originalDir, _ := os.Getwd()
61 |
62 | err := os.Chdir(repo)
63 | if err != nil {
64 | log.Fatal(err)
65 | }
66 |
67 | // change to original dir
68 | defer os.Chdir(originalDir)
69 |
70 | var curr string
71 | var branchs []string
72 |
73 | if result := utils.RunCmd(GIT_BRANCH, time.Second*10); result.Err() == nil {
74 |
75 | branchSplit := strings.Split(result.String(), "\n")
76 | for _, branch := range branchSplit {
77 | branch = strings.TrimSpace(branch)
78 | // current Branch mark with '*'
79 | if strings.HasPrefix(branch, "*") {
80 | branch = strings.ReplaceAll(branch, "* ", "")
81 | curr = branch
82 | }
83 | branchs = append(branchs, branch)
84 |
85 | }
86 |
87 | re = &branchInfo{
88 | curr: curr,
89 | branchs: branchs,
90 | }
91 | }
92 | return
93 | }
94 |
95 | func checkExistGitRepo(dir string) bool {
96 | //check the dir is under git repo
97 | cmd := fmt.Sprintf(EXIST_GIT_REPO, dir)
98 | if result := utils.RunCmd(cmd, time.Second*10); result.String() == "true" {
99 | return true
100 | }
101 | return false
102 | }
103 |
--------------------------------------------------------------------------------
/handler/gitcmd/common_test.go:
--------------------------------------------------------------------------------
1 | package gitcmd
2 |
3 | import (
4 | "fmt"
5 | "testing"
6 | )
7 |
8 | func TestListRepoAllBranch(t *testing.T) {
9 | type args struct {
10 | repo string
11 | }
12 | tests := []struct {
13 | name string
14 | args args
15 | }{
16 | {
17 | name: "case1",
18 | args: args{
19 | repo: ".",
20 | },
21 | },
22 | }
23 | for _, tt := range tests {
24 | t.Run(tt.name, func(t *testing.T) {
25 | got := ListRepoAllBranch(tt.args.repo)
26 | fmt.Println(got)
27 | })
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/handler/gitcmd/gcl.go:
--------------------------------------------------------------------------------
1 | package gitcmd
2 |
3 | import (
4 | "encoding/json"
5 | "fmt"
6 | "github.com/micro-plat/lib4go/net/http"
7 | "github.com/spf13/cobra"
8 | "github.com/yesAnd92/lwe/utils"
9 | "net/url"
10 | "os"
11 | "time"
12 | )
13 |
14 | type ProjectInfo struct {
15 | HttpUrlToRepo string
16 | Path string
17 | }
18 |
19 | // CloneGroup 获取所有项目仓库
20 | func CloneGroup(groupUrl, token, targetDir string) {
21 |
22 | targetDir = utils.ToAbsPath(targetDir)
23 |
24 | projectInfos := getRepoList(groupUrl, token)
25 |
26 | fmt.Printf("clone %s start...\n", groupUrl)
27 |
28 | for _, pj := range projectInfos {
29 | // 校验repo本地是否已经存在
30 | p := targetDir + "/" + pj.Path
31 | if checkPjExist(p) {
32 | fmt.Printf("%s existed,skip this repository...\n", p)
33 | continue
34 | }
35 | cloneRepo(pj, p)
36 | }
37 | fmt.Printf("clone %s end!\n", groupUrl)
38 | }
39 |
40 | func cloneRepo(pj *ProjectInfo, targetDir string) {
41 |
42 | var cmdline = fmt.Sprintf(CLONE_TPL, pj.HttpUrlToRepo, targetDir)
43 |
44 | result := utils.RunCmd(cmdline, time.Second*30)
45 | if result.Err() != nil {
46 | cobra.CheckErr(result.Err())
47 | }
48 |
49 | commitMsg := result.String()
50 | fmt.Println(commitMsg)
51 | }
52 |
53 | // getRepoList 获取组下的仓库列表
54 | func getRepoList(groupUrl, token string) []*ProjectInfo {
55 | u, _ := url.Parse(groupUrl)
56 | path := fmt.Sprintf(GITLAB_GROUP_DETAIL, u.Scheme, u.Host, u.Path, token)
57 | client, _ := http.NewHTTPClient()
58 | respBody, status, err := client.Request("get", path, "", "utf-8", nil)
59 |
60 | if err != nil {
61 | fmt.Println(err)
62 | return nil
63 | }
64 | if status != 200 {
65 | fmt.Printf("error:%d\n", status)
66 | return nil
67 | }
68 |
69 | re := make(map[string]interface{})
70 | json.Unmarshal([]byte(respBody), &re)
71 |
72 | var projectInfos []*ProjectInfo
73 |
74 | projects := re["projects"].([]interface{})
75 | for _, project := range projects {
76 | projectInfos = append(projectInfos, &ProjectInfo{
77 | HttpUrlToRepo: project.(map[string]interface{})["http_url_to_repo"].(string),
78 | Path: project.(map[string]interface{})["path"].(string),
79 | })
80 | }
81 | return projectInfos
82 | }
83 |
84 | func checkPjExist(path string) bool {
85 | _, err := os.Stat(path)
86 | if err == nil {
87 | return true
88 | }
89 | return false
90 | }
91 |
--------------------------------------------------------------------------------
/handler/gitcmd/gcl_test.go:
--------------------------------------------------------------------------------
1 | package gitcmd
2 |
3 | import (
4 | "fmt"
5 | "testing"
6 | )
7 |
8 | var token = ""
9 |
10 | func TestGetRepositories(t *testing.T) {
11 | urls := getRepoList("", token)
12 | for _, url := range urls {
13 | fmt.Println(url)
14 | }
15 | }
16 |
17 | func TestCloneGroup(t *testing.T) {
18 | type args struct {
19 | url string
20 | token string
21 | targetDir string
22 | }
23 | tests := []struct {
24 | name string
25 | args args
26 | }{
27 | // TODO: Add test cases.
28 | {name: "case1",
29 | args: args{
30 | url: "",
31 | token: token,
32 | targetDir: "",
33 | }},
34 | {name: "case2",
35 | args: args{
36 | url: "",
37 | token: token,
38 | targetDir: "",
39 | }},
40 | }
41 | for _, tt := range tests {
42 | t.Run(tt.name, func(t *testing.T) {
43 | CloneGroup(tt.args.url, tt.args.token, tt.args.targetDir)
44 | })
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/handler/gitcmd/gcms.go:
--------------------------------------------------------------------------------
1 | package gitcmd
2 |
3 | import (
4 | _ "embed"
5 | "encoding/json"
6 | "fmt"
7 | "os"
8 | "regexp"
9 | "strings"
10 | "time"
11 |
12 | "github.com/AlecAivazis/survey/v2"
13 | "github.com/jedib0t/go-pretty/v6/table"
14 | "github.com/jedib0t/go-pretty/v6/text"
15 |
16 | "github.com/spf13/cobra"
17 | "github.com/yesAnd92/lwe/ai"
18 | "github.com/yesAnd92/lwe/ai/prompt"
19 | "github.com/yesAnd92/lwe/utils"
20 | )
21 |
22 | var (
23 | // Match common sensitive key-value pairs, such as password=xxx, token: xxx
24 | sensitiveKeyValueRegex = regexp.MustCompile(`(?i)(\b(?:password|token|secret|api[_-]?key|auth)\b\s*[:=]{1}\s*["']?)([^"'\s]+)(["']?)`)
25 | // Match Bearer Token
26 | bearerTokenRegex = regexp.MustCompile(`(?i)(Bearer\s+)([a-zA-Z0-9\-_]+\.[a-zA-Z0-9\-_]+\.[a-zA-Z0-9\-_]+)`)
27 | // Match long hash values
28 | longHashRegex = regexp.MustCompile(`\b[a-f0-9]{32,}\b`)
29 | )
30 |
31 | type CommitMsg struct {
32 | Type string `json:"type"` // Type of the commit, e.g., feat, fix, docs, etc.
33 | Scope string `json:"scope"` // Scope of the impact (optional)
34 | Description string `json:"description"` // Brief description of the commit
35 | }
36 |
37 | type CommitData struct {
38 | CommitMsg []CommitMsg `json:"commitMsg"` // List of commit messages
39 | OptionalBody string `json:"optionalBody"`
40 | OptionalFooter string `json:"optionalFooter"`
41 | }
42 |
43 | // GetGitCommitMsg git commit msg from ai
44 | func GetGitCommitMsg(dir string) string {
45 |
46 | //check and init agent
47 | agent := ai.NewAIAgent()
48 |
49 | //check git repo
50 | if !checkExistGitRepo(dir) {
51 | cobra.CheckErr(fmt.Sprintf("%s Not a git repo!", dir))
52 | }
53 |
54 | //get git diff
55 | fmt.Println("Get git diff...")
56 | diff := buildGitDiffReq(dir)
57 |
58 | if len(diff) == 0 {
59 | cobra.CheckErr("There is no changes!")
60 | }
61 |
62 | //send ai to summary
63 | fmt.Printf("AI is generating commit message...\n\n")
64 | resp, err := gitDiffSubmitToAi(diff, agent)
65 | if err != nil {
66 | cobra.CheckErr(err)
67 | }
68 |
69 | return buildCommitMsg(resp)
70 |
71 | }
72 |
73 | func CommitAndPush(dir, cmsg string) {
74 |
75 | fmt.Println("AI suggested commit message:")
76 | printCommitMsg(dir, cmsg)
77 |
78 | // git add and git commit
79 | addAndCommit(dir, cmsg)
80 |
81 | //push origin repo
82 | pushCommitOriginRepo(dir)
83 | }
84 |
85 | func addAndCommit(dir string, cmsg string) {
86 | //accept cmsg
87 | var accept bool
88 | promptConfirm := &survey.Confirm{
89 | Message: "Accept this commit?",
90 | }
91 | err := survey.AskOne(promptConfirm, &accept)
92 | if err != nil {
93 | cobra.CheckErr(fmt.Sprintf("Commit msg err: %v", err))
94 | }
95 |
96 | if accept {
97 | addCmd := fmt.Sprintf(GIT_ADD, dir)
98 | addResult := utils.RunCmd(addCmd, time.Second*30)
99 | if addResult.Err() != nil {
100 | cobra.CheckErr(addResult.Err())
101 | }
102 |
103 | cmsgCmd := fmt.Sprintf(GIT_COMMIT, dir, cmsg)
104 | gcmsgReulst := utils.RunCmd(cmsgCmd, time.Second*30)
105 | if gcmsgReulst.Err() != nil {
106 | cobra.CheckErr(gcmsgReulst.Err())
107 | }
108 | } else {
109 | // no, exit
110 | os.Exit(0)
111 | }
112 | //highlight hint
113 | fmt.Println(text.Colors{text.FgGreen, text.Bold}.Sprint("\nSuccess commit!\n"))
114 |
115 | }
116 |
117 | func getAllChangedFiles(dir string) string {
118 | var cmdline = fmt.Sprintf(STATUS_TPL_SHORT, dir)
119 |
120 | result := utils.RunCmd(cmdline, time.Second*5)
121 | if result.Err() != nil {
122 | cobra.CheckErr(result.Err().Error())
123 | }
124 | return result.String()
125 | }
126 |
127 | func printCommitMsg(dir, msg string) {
128 |
129 | files := getAllChangedFiles(dir)
130 |
131 | t := table.NewWriter()
132 | // Define the header row and set the style of the header cells, here the header color is set to blue
133 | headerRow := table.Row{"Files", "Commit msg"}
134 | for i := range headerRow {
135 | headerRow[i] = text.Colors{text.FgGreen}.Sprint(headerRow[i])
136 | }
137 | t.AppendHeader(headerRow)
138 | t.SetOutputMirror(os.Stdout)
139 | t.AppendRow(table.Row{files, msg})
140 | t.Render()
141 | }
142 |
143 | func pushCommitOriginRepo(dir string) {
144 |
145 | //accept cmsg
146 | var accept bool
147 | promptConfirm := &survey.Confirm{
148 | Message: "Accept this commit and push to origin repo?",
149 | }
150 | err := survey.AskOne(promptConfirm, &accept)
151 | if err != nil {
152 | cobra.CheckErr(fmt.Sprintf("Confirm Commit msg err: %v", err))
153 | }
154 |
155 | if accept {
156 | // yes, push to origin repo
157 | gitPushCmd := fmt.Sprintf(GIT_PUSH, dir)
158 | addResult := utils.RunCmd(gitPushCmd, time.Second*30)
159 | if addResult.Err() != nil {
160 | fmt.Print(addResult.String())
161 | cobra.CheckErr(addResult.Err())
162 | }
163 | //output push result
164 | fmt.Printf("\n%s\n", addResult.String())
165 | fmt.Println(text.Colors{text.FgGreen, text.Bold}.Sprint("\nSuccess push origin Repo!\n"))
166 | } else {
167 | // no, exit
168 | os.Exit(0)
169 | }
170 | }
171 |
172 | func buildCommitMsg(resp string) string {
173 | var commitData CommitData
174 |
175 | err := json.Unmarshal([]byte(resp), &commitData)
176 | if err != nil {
177 | cobra.CheckErr(fmt.Sprintf("parse %s \n error:%v", resp, err))
178 | }
179 |
180 | var cmsg strings.Builder
181 |
182 | for _, msg := range commitData.CommitMsg {
183 | line := fmt.Sprintf("%s(%s): %s\n", msg.Type, msg.Scope, msg.Description)
184 | cmsg.WriteString(line)
185 | }
186 |
187 | if len(commitData.OptionalBody) > 0 {
188 | cmsg.WriteString("\n")
189 | cmsg.WriteString(commitData.OptionalBody)
190 | }
191 |
192 | if len(commitData.OptionalFooter) > 0 {
193 | cmsg.WriteString("\n")
194 | cmsg.WriteString(commitData.OptionalFooter)
195 | }
196 | return cmsg.String()
197 | }
198 |
199 | func buildGitDiffReq(dir string) string {
200 | //git diff result
201 |
202 | var cmdline = fmt.Sprintf(GIT_DIFF, dir)
203 | result := utils.RunCmd(cmdline, time.Second*30)
204 | if result.Err() != nil {
205 | cobra.CheckErr(result.Err())
206 | }
207 | return optimizeDiff(result.String())
208 | }
209 |
210 | func gitDiffSubmitToAi(ctx string, aiAgent *ai.AIAgent) (string, error) {
211 |
212 | //submit to the AI using the preset prompt
213 | resp, err := aiAgent.Chat(ctx, prompt.GitDiffPrompt)
214 | return resp, err
215 | }
216 |
217 | func optimizeDiff(diffctx string) string {
218 | lines := strings.Split(diffctx, "\n")
219 | var result []string
220 |
221 | for _, line := range lines {
222 | // filter metadata
223 | if isMetadataLine(line) {
224 | continue
225 | }
226 |
227 | // filter structural line
228 | if isStructuralLine(line) {
229 | result = append(result, line)
230 | continue
231 | }
232 |
233 | if isCodeChangeLine(line) {
234 | filtered := filterSensitiveInfo(line)
235 | result = append(result, filtered)
236 | }
237 | }
238 |
239 | return strings.Join(result, "\n")
240 | }
241 |
242 | func isMetadataLine(line string) bool {
243 | return strings.HasPrefix(line, "index ") ||
244 | strings.HasPrefix(line, "old mode ") ||
245 | strings.HasPrefix(line, "new mode ") ||
246 | strings.HasPrefix(line, "similarity index") ||
247 | strings.HasPrefix(line, "rename from") ||
248 | strings.HasPrefix(line, "rename to")
249 | }
250 |
251 | func isStructuralLine(line string) bool {
252 | return strings.HasPrefix(line, "diff --git") ||
253 | strings.HasPrefix(line, "--- ") ||
254 | strings.HasPrefix(line, "+++ ") ||
255 | strings.HasPrefix(line, "@@ ") ||
256 | strings.HasPrefix(line, "Binary files ")
257 | }
258 |
259 | func isCodeChangeLine(line string) bool {
260 | return len(line) > 0 && (line[0] == '+' || line[0] == '-' || line[0] == ' ')
261 | }
262 |
263 | func filterSensitiveInfo(line string) string {
264 | line = sensitiveKeyValueRegex.ReplaceAllString(line, "${1}${3}")
265 |
266 | line = bearerTokenRegex.ReplaceAllString(line, "${1}")
267 |
268 | line = longHashRegex.ReplaceAllString(line, "")
269 |
270 | return line
271 | }
272 |
--------------------------------------------------------------------------------
/handler/gitcmd/gcms_test.go:
--------------------------------------------------------------------------------
1 | package gitcmd
2 |
3 | import (
4 | "fmt"
5 | "github.com/yesAnd92/lwe/ai"
6 | "os"
7 | "testing"
8 | )
9 |
10 | func TestGitCommitMsg(t *testing.T) {
11 | msg := GetGitCommitMsg(".")
12 | fmt.Println(msg)
13 |
14 | }
15 |
16 | func Test_gitDiffSubmitToAi(t *testing.T) {
17 | type args struct {
18 | diffFile string
19 | aiAgent *ai.AIAgent
20 | }
21 | tests := []struct {
22 | name string
23 | args args
24 | }{
25 | // TODO: Add test cases.
26 | {name: "demo",
27 | args: args{diffFile: "../../testdata/diff.log",
28 | aiAgent: ai.NewAIAgent()},
29 | },
30 | }
31 | for _, tt := range tests {
32 | content, err := os.ReadFile(tt.args.diffFile)
33 | if err != nil {
34 | panic(err)
35 | }
36 | ctx := string(content)
37 | fmt.Println(ctx)
38 | got, err := gitDiffSubmitToAi(ctx, tt.args.aiAgent)
39 | if err != nil {
40 | fmt.Println(err)
41 | }
42 | fmt.Println(got)
43 | }
44 | }
45 |
46 | func Test_buildCommitMsg(t *testing.T) {
47 | type args struct {
48 | resp string
49 | }
50 | tests := []struct {
51 | name string
52 | args args
53 | }{
54 | // TODO: Add test cases.
55 | {name: "",
56 | args: args{resp: `{
57 | "commitMsg": [
58 | {
59 | "type": "feat",
60 | "scope": "ai",
61 | "description": "Add Siliconflow as a new AI agent type"
62 | },
63 | {
64 | "type": "refactor",
65 | "scope": "deepseek",
66 | "description": "Rename Send function to dsSend and update response struct to CommonResponse"
67 | },
68 | {
69 | "type": "test",
70 | "scope": "deepseek",
71 | "description": "Enhance DeepSeek Chat test with config initialization and proper error handling"
72 | }
73 | ]
74 | }`},
75 | },
76 | }
77 | for _, tt := range tests {
78 | t.Run(tt.name, func(t *testing.T) {
79 | got := buildCommitMsg(tt.args.resp)
80 | fmt.Println(got)
81 | })
82 | }
83 | }
84 |
85 | func Test_optimizeDiff(t *testing.T) {
86 | type args struct {
87 | diff string
88 | }
89 | tests := []struct {
90 | name string
91 | args args
92 | }{
93 | // TODO: Add test cases.
94 | {name: "",
95 | args: args{
96 | diff: `diff --git a/main.go b/main.go
97 | index 1234567..89abcde 100644
98 | --- a/main.go
99 | +++ b/main.go
100 | @@ -5,6 +5,7 @@ import (
101 | )
102 |
103 | func main() {
104 | + api_key := "sk-1234567890abcdef"
105 | fmt.Println("Hello, World!")
106 | - password = "secret123"
107 | + authToken := "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.xxxxxx"
108 | }`,
109 | },
110 | },
111 | }
112 | for _, tt := range tests {
113 | t.Run(tt.name, func(t *testing.T) {
114 | got := optimizeDiff(tt.args.diff)
115 | fmt.Println(got)
116 | })
117 | }
118 | }
119 |
--------------------------------------------------------------------------------
/handler/gitcmd/git_cmd_tpl.go:
--------------------------------------------------------------------------------
1 | package gitcmd
2 |
3 | //git command
4 |
5 | var (
6 |
7 | //git log
8 | LOG_TPL = "git -C %s --no-pager log %s --no-merges "
9 | LOG_FORMAT_TPL = `--format=format:'%h*-*%an*-*%ct*-*%s' ` //使用*-*作为分隔符
10 | LOG_AUTHOR_TPL = `--author=%s `
11 | LOG_START_DATE_TPL = `--since=%s `
12 | LOG_END_DATE_TPL = `--until=%s `
13 | LOG_RECENTN_TPL = `-n %d `
14 |
15 | // git show
16 | SHOW_TPL = "git show %s"
17 |
18 | // git status
19 | STATUS_TPL = "git -C %s status"
20 |
21 | // git status short
22 | STATUS_TPL_SHORT = "git -C %s status --short | grep '^[ MADRCU]' | awk '{print $2}'"
23 |
24 | // git status in short result
25 | STATUS_CHECK_TPL = "git -C %s status -s"
26 |
27 | //git pull
28 | GIT_PULL = `git -C %s pull --rebase`
29 |
30 | // git clone
31 | CLONE_TPL = "git clone %s %s"
32 |
33 | // git lab group api
34 | // gitlab doc https://docs.gitlab.com/ee/api/groups.html#details-of-a-group
35 | GITLAB_GROUP_DETAIL = "%s://%s/api/v4/groups%s?private_token=%s"
36 |
37 | // Determine if the current directory is a git repository
38 | EXIST_GIT_REPO = "git -C %s rev-parse --is-inside-work-tree"
39 |
40 | //git Branch
41 | GIT_BRANCH = `git branch`
42 |
43 | //git diff
44 | GIT_DIFF = `git -C %s diff`
45 |
46 | //git add
47 | GIT_ADD = `git -C %s add .`
48 |
49 | //git commit
50 | GIT_COMMIT = `git -C %s commit -m "%s"`
51 |
52 | //git push
53 | GIT_PUSH = `git -C %s push`
54 | )
55 |
--------------------------------------------------------------------------------
/handler/gitcmd/gitlog.go:
--------------------------------------------------------------------------------
1 | package gitcmd
2 |
3 | import (
4 | "errors"
5 | "fmt"
6 | "github.com/spf13/cobra"
7 | "github.com/yesAnd92/lwe/utils"
8 | "regexp"
9 | "sort"
10 | "strconv"
11 | "strings"
12 | "time"
13 | )
14 |
15 | // CommitLog 提交记录
16 | type CommitLog struct {
17 | Branch string //Branch
18 | CommitHash string //abbreviated commit hash
19 | Username string //username
20 | CommitAt string //commit time
21 | CommitMsg string //commit log
22 | FilesChanged []string //changed file arr
23 | }
24 |
25 | // ResultLog 封装日志分析结果
26 | type ResultLog struct {
27 | RepoName string //git repository name
28 | CommitLogs *[]CommitLog
29 | }
30 |
31 | // GetCommitLog 获取提交日志
32 | func GetCommitLog(detail bool, recentN int16, dir, author, start, end string, branchs bool) (*[]CommitLog, error) {
33 |
34 | var logs []CommitLog
35 |
36 | branchInfo := ListRepoAllBranch(dir)
37 |
38 | //filter given branch
39 | if !branchs {
40 | branchInfo.branchs = []string{branchInfo.curr}
41 | }
42 |
43 | // find all branch commit log
44 | for _, branch := range branchInfo.branchs {
45 |
46 | cmdline := buildCmdline(dir, branch, recentN, author, start, end)
47 |
48 | result := utils.RunCmd(cmdline, time.Second*30)
49 | if result.Err() != nil {
50 | return nil, result.Err()
51 | }
52 |
53 | reStr := result.String()
54 | if len(reStr) == 0 {
55 | continue
56 | }
57 |
58 | commitLines := strings.Split(reStr, "\n")
59 | for _, msg := range commitLines {
60 | //win环境下会多“'”符号,替换去除
61 | msg = strings.Trim(msg, "'")
62 | infoArr := strings.Split(msg, "*-*")
63 | //commitAt
64 | commitAtMill, _ := strconv.ParseInt(infoArr[2], 10, 64)
65 |
66 | log := CommitLog{
67 | Branch: branch,
68 | CommitHash: infoArr[0],
69 | Username: infoArr[1],
70 | CommitAt: time.UnixMilli(commitAtMill * 1000).Format("2006-01-02 15:04:05"),
71 | CommitMsg: infoArr[3],
72 | }
73 |
74 | //change file
75 | if detail {
76 | filesChanged, err := GetChangedFile(infoArr[0])
77 | if err == nil {
78 | log.FilesChanged = filesChanged
79 | }
80 | }
81 |
82 | logs = append(logs, log)
83 | }
84 | }
85 |
86 | //merge same commit log in different branch
87 | mergeLog := mergeAndSortCommitLog(&logs, int(recentN))
88 |
89 | return &mergeLog, nil
90 | }
91 |
92 | func mergeAndSortCommitLog(logs *[]CommitLog, n int) []CommitLog {
93 |
94 | var uniqueLogs []CommitLog
95 | seen := make(map[string]struct{})
96 | for _, log := range *logs {
97 | if _, ok := seen[log.CommitHash]; !ok {
98 | seen[log.CommitHash] = struct{}{}
99 | uniqueLogs = append(uniqueLogs, log)
100 | }
101 | }
102 |
103 | sort.Slice(uniqueLogs, func(i, j int) bool {
104 | return uniqueLogs[i].CommitAt > uniqueLogs[j].CommitAt
105 | })
106 |
107 | if len(uniqueLogs) < n {
108 | n = len(uniqueLogs)
109 | }
110 |
111 | return uniqueLogs[:n]
112 | }
113 |
114 | func buildCmdline(dir string, branch string, recentN int16, author string, start string, end string) string {
115 | //使用bytes.Buffer这种方式拼接字符串会%!h(MISSING)?
116 | //指定仓库地址
117 | var cmdline = fmt.Sprintf(LOG_TPL, dir, branch)
118 |
119 | if recentN >= 0 {
120 | cmdline += fmt.Sprintf(LOG_RECENTN_TPL, recentN)
121 | }
122 |
123 | cmdline += LOG_FORMAT_TPL
124 |
125 | if len(author) > 0 {
126 | cmdline += fmt.Sprintf(LOG_AUTHOR_TPL, author)
127 | }
128 |
129 | if len(start) > 0 {
130 | cmdline += fmt.Sprintf(LOG_START_DATE_TPL, start)
131 | }
132 |
133 | if len(end) > 0 {
134 | cmdline += fmt.Sprintf(LOG_END_DATE_TPL, end)
135 | }
136 | return cmdline
137 | }
138 |
139 | // GetChangedFile 获取本次提交变动的文件名
140 | func GetChangedFile(commitId string) ([]string, error) {
141 | var cmdline = fmt.Sprintf(SHOW_TPL, commitId)
142 |
143 | result := utils.RunCmd(cmdline, time.Second*30)
144 | if result.Err() != nil {
145 | return nil, result.Err()
146 | }
147 |
148 | commitMsg := result.String()
149 | if len(commitMsg) == 0 {
150 | return nil, errors.New("")
151 | }
152 | var fileNames []string
153 | re := regexp.MustCompile("--- a.+")
154 | finds := re.FindAllString(commitMsg, -1)
155 | for _, find := range finds {
156 | find = find[strings.LastIndex(find, "/")+1:]
157 | fileNames = append(fileNames, find)
158 | }
159 | return fileNames, nil
160 | }
161 |
162 | // GetAllGitRepoCommitLog 封装所有仓库的提交信息
163 | func GetAllGitRepoCommitLog(detail bool, recentN int16, dir, author, start, end string, branchs bool) (*[]ResultLog, error) {
164 | var res []string
165 | var reLog []ResultLog
166 |
167 | //相对路径转换成绝对路径进行处理
168 | dir = utils.ToAbsPath(dir)
169 |
170 | //递归找到所有的git仓库
171 | findGitRepo(dir, &res)
172 |
173 | //遍历获取每个仓库的提交信息
174 | for _, gitDir := range res {
175 | commitLogs, err := GetCommitLog(detail, recentN, gitDir, author, start, end, branchs)
176 | if err != nil {
177 | cobra.CheckErr(err)
178 | }
179 | if len(*commitLogs) > 0 {
180 | reLog = append(reLog, ResultLog{
181 | RepoName: gitDir,
182 | CommitLogs: commitLogs,
183 | })
184 | }
185 | }
186 |
187 | return &reLog, nil
188 | }
189 |
--------------------------------------------------------------------------------
/handler/gitcmd/gitlog_test.go:
--------------------------------------------------------------------------------
1 | package gitcmd
2 |
3 | import (
4 | "fmt"
5 | "os"
6 | "reflect"
7 | "testing"
8 | )
9 |
10 | const testGitDir = "."
11 |
12 | func TestGetCommitLog(t *testing.T) {
13 | type args struct {
14 | detail bool
15 | recentN int16
16 | dir string
17 | author string
18 | start string
19 | end string
20 | branchs bool
21 | }
22 | tests := []struct {
23 | name string
24 | args args
25 | wantErr bool
26 | }{
27 | // TODO: Add test cases.
28 | {
29 | name: "case 1",
30 | args: args{
31 | detail: false,
32 | recentN: 3,
33 | dir: testGitDir,
34 | },
35 | wantErr: false,
36 | },
37 | }
38 | for _, tt := range tests {
39 | t.Run(tt.name, func(t *testing.T) {
40 | got, err := GetCommitLog(tt.args.detail, tt.args.recentN, tt.args.dir, tt.args.author, tt.args.start, tt.args.end, tt.args.branchs)
41 | if (err != nil) != tt.wantErr {
42 | t.Errorf("GetCommitLog() error = %v, wantErr %v", err, tt.wantErr)
43 | return
44 | }
45 | for _, log := range *got {
46 | fmt.Printf("Branch:%s-----------Hash:%s-----------\n", log.Branch, log.CommitHash)
47 | fmt.Printf("@%s %s commit msg: %s\n\n", log.Username, log.CommitAt, log.CommitMsg)
48 | }
49 | })
50 | }
51 | }
52 |
53 | func TestGetChangedFile(t *testing.T) {
54 | type args struct {
55 | commitId string
56 | }
57 | tests := []struct {
58 | name string
59 | args args
60 | want []string
61 | wantErr bool
62 | }{
63 | // TODO: Add test cases.
64 | {
65 | name: "case 1",
66 | args: args{
67 | //current lwe git repository commit id 6f635d7
68 | commitId: "6f635d7",
69 | },
70 | want: []string{".gitignore", "README.md"},
71 | wantErr: false,
72 | },
73 | }
74 | for _, tt := range tests {
75 | t.Run(tt.name, func(t *testing.T) {
76 | got, err := GetChangedFile(tt.args.commitId)
77 | if (err != nil) != tt.wantErr {
78 | t.Errorf("GetChangedFile() error = %v, wantErr %v", err, tt.wantErr)
79 | return
80 | }
81 | if !reflect.DeepEqual(got, tt.want) {
82 | t.Errorf("GetChangedFile() got = %v, want %v", got, tt.want)
83 | }
84 | })
85 | }
86 | }
87 |
88 | func TestGetAllGitRepoCommitLog2(t *testing.T) {
89 |
90 | resLogs, _ := GetAllGitRepoCommitLog(true, 3, testGitDir, "", "", "", false)
91 |
92 | //控制台
93 | console := ConsoleOutput{}
94 | console.Output(resLogs)
95 |
96 | defer func() {
97 | os.Remove(REPORT_PATH)
98 | }()
99 | //写文件
100 | file := FileOutput{}
101 | file.Output(resLogs)
102 |
103 | if f, err := os.Stat(REPORT_PATH); err != nil || f.Size() == 0 {
104 | t.Error("file not exist >>>", REPORT_PATH)
105 | }
106 | }
107 |
108 | func TestGetAllGitRepoCommitLog(t *testing.T) {
109 | type args struct {
110 | detail bool
111 | recentN int16
112 | dir string
113 | author string
114 | start string
115 | end string
116 | branchs bool
117 | }
118 | tests := []struct {
119 | name string
120 | args args
121 | wantErr bool
122 | }{
123 | // TODO: Add test cases.
124 | {
125 | name: "case 1",
126 | args: args{
127 | detail: false,
128 | recentN: 3,
129 | dir: testGitDir,
130 | },
131 | wantErr: false,
132 | },
133 | }
134 | for _, tt := range tests {
135 | t.Run(tt.name, func(t *testing.T) {
136 | got, err := GetAllGitRepoCommitLog(tt.args.detail, tt.args.recentN, tt.args.dir, tt.args.author, tt.args.start, tt.args.end, tt.args.branchs)
137 | if (err != nil) != tt.wantErr {
138 | t.Errorf("GetAllGitRepoCommitLog() error = %v, wantErr %v", err, tt.wantErr)
139 | return
140 | }
141 |
142 | defer func() {
143 | os.Remove(REPORT_PATH)
144 | }()
145 | //控制台
146 | console := ConsoleOutput{}
147 | console.Output(got)
148 |
149 | //写文件
150 | file := FileOutput{}
151 | file.Output(got)
152 | if f, err := os.Stat(REPORT_PATH); err != nil || f.Size() == 0 {
153 | t.Error("file not exist >>>", REPORT_PATH)
154 | }
155 | })
156 | }
157 | }
158 |
--------------------------------------------------------------------------------
/handler/gitcmd/gitpull.go:
--------------------------------------------------------------------------------
1 | package gitcmd
2 |
3 | import (
4 | "fmt"
5 | "github.com/yesAnd92/lwe/utils"
6 | "time"
7 | )
8 |
9 | func updateRepo(dir string) (string, error) {
10 |
11 | //指定仓库地址
12 | var cmdline = fmt.Sprintf(GIT_PULL, dir)
13 |
14 | result := utils.RunCmd(cmdline, time.Second*30)
15 | if result.Err() != nil {
16 | return "", result.Err()
17 | }
18 |
19 | reStr := result.String()
20 | return reStr, nil
21 | }
22 |
23 | // checkRepoClean 检测当前仓库是否干净
24 | func checkRepoClean(dir string) (bool, string) {
25 | var cmdline = fmt.Sprintf(STATUS_CHECK_TPL, dir)
26 |
27 | result := utils.RunCmd(cmdline, time.Second*30)
28 | if result.Err() != nil {
29 | return false, result.Err().Error()
30 | }
31 |
32 | commitMsg := result.String()
33 | if len(commitMsg) == 0 {
34 | return true, ""
35 | }
36 | return false, commitMsg
37 | }
38 |
39 | // UpdateAllGitRepo 更新所有仓库
40 | func UpdateAllGitRepo(dir string) {
41 | var res []string
42 |
43 | //相对路径转换成绝对路径进行处理
44 | dir = utils.ToAbsPath(dir)
45 |
46 | //递归找到所有的git仓库
47 | findGitRepo(dir, &res)
48 |
49 | //遍历获取每个仓库的提交信息
50 | for idx, gitDir := range res {
51 | fmt.Printf("#%d Git Repo >> %s\n", idx+1, gitDir)
52 | if clean, msg := checkRepoClean(gitDir); !clean {
53 | //存在未提交的变动,防止冲突等问题,不进行更新
54 | fmt.Println(msg)
55 | fmt.Println(">> Modified Files in this Repo have not been submitted yet, terminating the update!")
56 | fmt.Println()
57 | continue
58 | }
59 |
60 | result, err := updateRepo(gitDir)
61 | if err != nil {
62 | fmt.Println(err)
63 | }
64 | fmt.Println(result)
65 | fmt.Println()
66 | }
67 |
68 | }
69 |
--------------------------------------------------------------------------------
/handler/gitcmd/gitpull_test.go:
--------------------------------------------------------------------------------
1 | package gitcmd
2 |
3 | import (
4 | "fmt"
5 | "testing"
6 | )
7 |
8 | func TestCheckRepoClean(t *testing.T) {
9 | type args struct {
10 | dir string
11 | }
12 | tests := []struct {
13 | name string
14 | args args
15 | want bool
16 | }{
17 | // TODO: Add test cases.
18 | {args: args{dir: "."},
19 | want: true},
20 | }
21 | for _, tt := range tests {
22 | t.Run(tt.name, func(t *testing.T) {
23 | clean, msg := checkRepoClean(tt.args.dir)
24 | if !clean {
25 | fmt.Println(msg)
26 | }
27 | })
28 | }
29 | }
30 |
31 | func TestUpdateAllGitRepo(t *testing.T) {
32 | type args struct {
33 | dir string
34 | }
35 | tests := []struct {
36 | name string
37 | args args
38 | }{
39 | // TODO: Add test cases.
40 | {args: args{dir: testGitDir}},
41 | }
42 | for _, tt := range tests {
43 | t.Run(tt.name, func(t *testing.T) {
44 | UpdateAllGitRepo(tt.args.dir)
45 | })
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/handler/gitcmd/glog_output.go:
--------------------------------------------------------------------------------
1 | package gitcmd
2 |
3 | import (
4 | "bytes"
5 | "fmt"
6 | "os"
7 | "path/filepath"
8 |
9 | "github.com/jedib0t/go-pretty/v6/table"
10 | "github.com/jedib0t/go-pretty/v6/text"
11 | "github.com/spf13/cobra"
12 | )
13 |
14 | const REPORT_PATH = "./commit.log"
15 |
16 | //输出形式:控制台数据、写入文件
17 |
18 | type OutputFormatter interface {
19 | Output(*[]ResultLog)
20 | }
21 |
22 | type ConsoleOutput struct {
23 | }
24 |
25 | func (c *ConsoleOutput) Output(resLogs *[]ResultLog) {
26 |
27 | t := table.NewWriter()
28 | t.SetOutputMirror(os.Stdout)
29 | // set column width
30 | t.SetColumnConfigs([]table.ColumnConfig{
31 | {
32 | Number: 4,
33 | WidthMax: 100,
34 | WidthMaxEnforcer: func(col string, maxLen int) string {
35 |
36 | return text.WrapText(col, maxLen)
37 | },
38 | },
39 | })
40 | t.AppendHeader(table.Row{"Branch", "Hash", "Author", "Commit", "Time"})
41 |
42 | if *resLogs == nil {
43 | fmt.Printf("No matching commit log found in this Dir\n")
44 | return
45 | }
46 |
47 | for idx, res := range *resLogs {
48 | logs := res.CommitLogs
49 | fmt.Printf("#%d Git Repo >> %s\n", idx+1, res.RepoName)
50 |
51 | for _, log := range *logs {
52 | t.AppendRow(table.Row{log.Branch, log.CommitHash, log.Username, log.CommitMsg, log.CommitAt})
53 | }
54 |
55 | t.Render()
56 | t.ResetRows()
57 | fmt.Println()
58 | }
59 | }
60 |
61 | type FileOutput struct {
62 | }
63 |
64 | func (c *FileOutput) Output(resLogs *[]ResultLog) {
65 |
66 | //渲染的分析日志放到buffer中,最后一起写入文件
67 | commitData := &bytes.Buffer{}
68 | t := table.NewWriter()
69 | t.SetOutputMirror(commitData)
70 | t.AppendHeader(table.Row{"Hash", "Author", "Commit", "Time"})
71 |
72 | if *resLogs == nil {
73 | commitData.WriteString("No matching commit log found in this git repo\n")
74 | }
75 |
76 | for idx, res := range *resLogs {
77 | logs := res.CommitLogs
78 | commitData.WriteString(fmt.Sprintf("#%d Git Repo >> %s\n", idx+1, res.RepoName))
79 |
80 | for _, log := range *logs {
81 | t.AppendRow(table.Row{log.CommitHash, log.Username, log.CommitMsg, log.CommitAt})
82 | if len(log.FilesChanged) > 0 {
83 | t.AppendRow(table.Row{log.FilesChanged})
84 | }
85 | }
86 |
87 | t.Render()
88 | t.ResetRows()
89 | commitData.WriteString("\n")
90 | }
91 |
92 | path, _ := filepath.Abs(REPORT_PATH)
93 | f, err := os.Create(path)
94 | if f != nil {
95 | defer f.Close()
96 | }
97 |
98 | if err != nil {
99 | cobra.CheckErr(err)
100 | }
101 | f.Write(commitData.Bytes())
102 |
103 | fmt.Println("Commit log has finished >> " + path)
104 | }
105 |
--------------------------------------------------------------------------------
/handler/gitcmd/gst.go:
--------------------------------------------------------------------------------
1 | package gitcmd
2 |
3 | import (
4 | "fmt"
5 | "github.com/spf13/cobra"
6 | "github.com/yesAnd92/lwe/utils"
7 | "time"
8 | )
9 |
10 | // checkRepoClean 查看当前仓库状态
11 | func printRepoStatus(dir string) {
12 | var cmdline = fmt.Sprintf(STATUS_TPL, dir)
13 |
14 | result := utils.RunCmd(cmdline, time.Second*5)
15 | if result.Err() != nil {
16 | cobra.CheckErr(result.Err().Error())
17 | }
18 |
19 | fmt.Println(result.String())
20 | }
21 |
22 | // GetAllGitRepoStatus 查看所有仓库的状态
23 | func GetAllGitRepoStatus(dir string) {
24 | var res []string
25 |
26 | //相对路径转换成绝对路径进行处理
27 | dir = utils.ToAbsPath(dir)
28 |
29 | //递归找到所有的git仓库
30 | findGitRepo(dir, &res)
31 |
32 | //遍历获取每个仓库的提交信息
33 | for idx, gitDir := range res {
34 | fmt.Printf("#%d Git Repo >> %s\n", idx+1, gitDir)
35 | printRepoStatus(gitDir)
36 | fmt.Println()
37 | }
38 |
39 | }
40 |
--------------------------------------------------------------------------------
/handler/gitcmd/gst_test.go:
--------------------------------------------------------------------------------
1 | package gitcmd
2 |
3 | import "testing"
4 |
5 | func TestGetAllGitRepoStatus(t *testing.T) {
6 | type args struct {
7 | dir string
8 | }
9 | tests := []struct {
10 | name string
11 | args args
12 | }{
13 | // TODO: Add test cases.
14 | {
15 | name: "",
16 | args: args{dir: "."},
17 | },
18 | }
19 | for _, tt := range tests {
20 | t.Run(tt.name, func(t *testing.T) {
21 | GetAllGitRepoStatus(tt.args.dir)
22 | })
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/handler/gitcmd/gsum.go:
--------------------------------------------------------------------------------
1 | package gitcmd
2 |
3 | import (
4 | _ "embed"
5 | "encoding/json"
6 | "fmt"
7 | "github.com/spf13/cobra"
8 | "github.com/yesAnd92/lwe/ai"
9 | "github.com/yesAnd92/lwe/ai/prompt"
10 | "github.com/yesAnd92/lwe/utils"
11 | "strings"
12 | )
13 |
14 | type GitSummaryPromptResp struct {
15 | RepoSummary []RepoSummary `json:"repo_summary"`
16 | }
17 |
18 | type RepoSummary struct {
19 | Repo string `json:"repo"`
20 | Summary []string `json:"summary"`
21 | SummaryCN []string `json:"summary_cn"`
22 | }
23 |
24 | // GitLogSummary summary commit log
25 | func GitLogSummary(detail bool, dir, committer, start, end string) {
26 |
27 | //check and init agent
28 | agent := ai.NewAIAgent()
29 |
30 | sb := buildGitLogReq(detail, dir, committer, start, end)
31 |
32 | //send ai to summary
33 | resp, err := logSubmitToAi(sb, agent)
34 | if err != nil {
35 | cobra.CheckErr(err)
36 | }
37 |
38 | promptResp := parseResp(resp)
39 |
40 | consoleResult(promptResp)
41 |
42 | }
43 |
44 | func consoleResult(promptResp *GitSummaryPromptResp) {
45 | //输出
46 | for i, repoSum := range promptResp.RepoSummary {
47 | fmt.Printf("#%d. %s", i+1, repoSum.Repo)
48 |
49 | fmt.Print("\nEN:\n")
50 | for no, s := range repoSum.Summary {
51 | fmt.Printf("%d. %s\n", no, s)
52 | }
53 |
54 | fmt.Print("\nZH:\n")
55 |
56 | for no, s := range repoSum.SummaryCN {
57 | fmt.Printf("%d. %s\n", no, s)
58 | }
59 |
60 | //each repo split by to blank line
61 | fmt.Print("\n\n")
62 | }
63 | }
64 |
65 | func parseResp(resp string) *GitSummaryPromptResp {
66 | promptResp := &GitSummaryPromptResp{}
67 |
68 | err := json.Unmarshal([]byte(resp), promptResp)
69 | if err != nil {
70 | cobra.CheckErr(err)
71 | }
72 | return promptResp
73 | }
74 |
75 | func buildGitLogReq(detail bool, dir string, committer string, start string, end string) string {
76 | var res []string
77 |
78 | //相对路径转换成绝对路径进行处理
79 | dir = utils.ToAbsPath(dir)
80 |
81 | //递归找到所有的git仓库
82 | findGitRepo(dir, &res)
83 |
84 | //recentN not more than 1000
85 | var recentN int16 = 1000
86 |
87 | //get all branch commit log
88 | var allBranch = true
89 |
90 | //text to be submitted to AI for summarization
91 | var sb strings.Builder
92 |
93 | //遍历获取每个仓库的提交信息
94 | for _, gitDir := range res {
95 | commitLogs, err := GetAllGitRepoCommitLog(detail, recentN, gitDir, committer, start, end, allBranch)
96 | if err != nil {
97 | cobra.CheckErr(err)
98 | }
99 | if len(*commitLogs) == 0 {
100 | break
101 | }
102 | for _, repoLogs := range *commitLogs {
103 | sb.WriteString(repoLogs.RepoName + "\n")
104 | for _, log := range *repoLogs.CommitLogs {
105 | logMsg := strings.TrimSpace(log.CommitMsg)
106 | if len(logMsg) == 0 {
107 | continue
108 | }
109 | sb.WriteString(logMsg + "\n")
110 | }
111 | sb.WriteString("\n")
112 | }
113 |
114 | }
115 | return sb.String()
116 | }
117 |
118 | func logSubmitToAi(ctx string, aiAgent *ai.AIAgent) (string, error) {
119 |
120 | //submit to the AI using the preset prompt
121 | resp, err := aiAgent.AiChat.Chat(ctx, prompt.LogSummaryPrompt)
122 | return resp, err
123 | }
124 |
--------------------------------------------------------------------------------
/handler/gitcmd/gsum_test.go:
--------------------------------------------------------------------------------
1 | package gitcmd
2 |
3 | import (
4 | "fmt"
5 | "github.com/yesAnd92/lwe/ai"
6 | "reflect"
7 | "testing"
8 | )
9 |
10 | func TestGitLogSummary(t *testing.T) {
11 | type args struct {
12 | detail bool
13 | dir string
14 | committer string
15 | start string
16 | end string
17 | }
18 | tests := []struct {
19 | name string
20 | args args
21 | }{
22 | // TODO: Add test cases.
23 | {name: "",
24 | args: args{detail: false,
25 | dir: "",
26 | committer: "yesAnd",
27 | start: "",
28 | end: ""},
29 | },
30 | }
31 | for _, tt := range tests {
32 | t.Run(tt.name, func(t *testing.T) {
33 | GitLogSummary(tt.args.detail, tt.args.dir, tt.args.committer, tt.args.start, tt.args.end)
34 | })
35 | }
36 | }
37 |
38 | func Test_logSubmitToAi(t *testing.T) {
39 | type args struct {
40 | ctx string
41 | }
42 | tests := []struct {
43 | name string
44 | args args
45 | }{
46 | // TODO: Add test cases.
47 |
48 | {name: "pageHelper commit log",
49 | args: args{ctx: `
50 | Mybatis-PageHelper
51 | fix 兼容jakarta/javax的ServletRequest
52 | 补充注释信息
53 | 重载一个PageInfo.of方法,支持手动指定查询记录总数返回分页信息
54 | 更新发布日志
55 | 更新release脚本
56 | 6.1.0 更新日志,写文档好麻烦。
57 | 修复错误
58 | 发布6.1.0,jsqlparser直接依赖都是中间接口,可以通过SPI替换默认实现。
59 | 升级jsqlparser版本4.7
60 | 简化pom.xml配置,去掉shade内嵌jsqlparser方式,改为通过外部依赖选择不同的jsqlparser版本,允许自己SPI扩展。
61 | jsqlparser解析不使用线程池,支持SPI扩展覆盖SqlParser实现
62 | 为了方便使用SqlParser实现,支持SPI方式扩展
63 | SqlServer分页改为SqlServerSqlParser接口,添加参数 sqlServerSqlParser 覆盖默认值
64 | OrderByParser提取OrderBySqlParser接口,增加 orderBySqlParser 参数,可以覆盖默认实现。
65 | OrderByParser静态方法改为普通方法,为后续改接口做准备
66 | jdk8+后不再需要JSqlParser接口,移除该接口,文档标记该参数
67 | update README
68 | 发布时打包两个版本,正常版本和standalone版本,standalone版本会内嵌jsqlparser,避免因standalone各个版本不兼容导致的依赖问题。
69 | 内嵌 jsqlparser 以规避版本冲突
70 | chore: maven-compiler-plugin固定版本以去除警告,并增加构建稳定性`,
71 | }},
72 | {name: "lwe commit log",
73 | args: args{ctx: `
74 | lwe
75 | update readme
76 | add a file server
77 | add en readme.md
78 | add env command
79 | glog add branchs option to determined branch ,limit 50 change to 500
80 | glog: merge and sort commit log
81 | get branch commit log
82 | add output dir hint of generated file
83 | add output dir hint of generated file
84 | Update README.md
85 | Fsync (#8)
86 | Gcl command (#7)
87 | Url command (#5)
88 | Git command (#3)
89 | Navi command (#2)
90 | Initial commit`,
91 | }},
92 | }
93 | aiAgent := ai.NewAIAgent()
94 | for _, tt := range tests {
95 | t.Run(tt.name, func(t *testing.T) {
96 | got, err := logSubmitToAi(tt.args.ctx, aiAgent)
97 | if err != nil {
98 | fmt.Println(err)
99 | return
100 | }
101 |
102 | fmt.Println(got)
103 | })
104 | }
105 | }
106 |
107 | func Test_parseResp(t *testing.T) {
108 | type args struct {
109 | resp string
110 | }
111 | tests := []struct {
112 | name string
113 | args args
114 | want *GitSummaryPromptResp
115 | }{
116 | {name: "demo",
117 | args: args{resp: `
118 | {
119 | "repo_summary": [
120 | {
121 | "repo": "repository name",
122 | "summary": [
123 | "summary1",
124 | "summary2"
125 | ],
126 | "summary_cn": [
127 | "中文总结1",
128 | "中文总结2"
129 | ]
130 | }
131 | ]
132 | }`,
133 | },
134 | want: &GitSummaryPromptResp{
135 | RepoSummary: []RepoSummary{
136 | {Repo: "repository name",
137 | Summary: []string{"summary1", "summary2"},
138 | SummaryCN: []string{"中文总结1", "中文总结2"},
139 | },
140 | }},
141 | },
142 | }
143 | for _, tt := range tests {
144 | t.Run(tt.name, func(t *testing.T) {
145 | if got := parseResp(tt.args.resp); !reflect.DeepEqual(got, tt.want) {
146 | t.Errorf("parseResp() = %v, want %v", got, tt.want)
147 | }
148 | })
149 | }
150 | }
151 |
152 | func Test_consoleResult(t *testing.T) {
153 | type args struct {
154 | promptResp *GitSummaryPromptResp
155 | }
156 | tests := []struct {
157 | name string
158 | args args
159 | }{
160 | // TODO: Add test cases.
161 | {name: "demo",
162 | args: args{promptResp: &GitSummaryPromptResp{
163 | RepoSummary: []RepoSummary{
164 | {Repo: "repository name",
165 | Summary: []string{"summary1", "summary2"},
166 | SummaryCN: []string{"中文总结1", "中文总结2"},
167 | },
168 | {Repo: "repository name2",
169 | Summary: []string{"summary1", "summary2"},
170 | SummaryCN: []string{"中文总结1", "中文总结2"},
171 | },
172 | }}}},
173 | }
174 | for _, tt := range tests {
175 | t.Run(tt.name, func(t *testing.T) {
176 | consoleResult(tt.args.promptResp)
177 | })
178 | }
179 | }
180 |
181 | func Test_buildGitLogReq(t *testing.T) {
182 | type args struct {
183 | detail bool
184 | dir string
185 | committer string
186 | start string
187 | end string
188 | }
189 | tests := []struct {
190 | name string
191 | args args
192 | }{
193 | {name: "test",
194 | args: args{
195 | detail: false,
196 | dir: ".",
197 | committer: "",
198 | start: "",
199 | end: "",
200 | }},
201 | }
202 | for _, tt := range tests {
203 | t.Run(tt.name, func(t *testing.T) {
204 | got := buildGitLogReq(tt.args.detail, tt.args.dir, tt.args.committer, tt.args.start, tt.args.end)
205 | fmt.Println(got)
206 | })
207 | }
208 | }
209 |
--------------------------------------------------------------------------------
/handler/navicat/handle_ncx.go:
--------------------------------------------------------------------------------
1 | package navicat
2 |
3 | import (
4 | "crypto/aes"
5 | "crypto/cipher"
6 | "encoding/hex"
7 | "encoding/xml"
8 | "errors"
9 | "github.com/spf13/cobra"
10 | )
11 |
12 | const (
13 | //Navicat加密时使用的key和iv
14 | AES_KEY = "libcckeylibcckey"
15 | AES_IV = "libcciv libcciv "
16 | )
17 |
18 | type NxcConnections struct {
19 | Conns []NxcConn `xml:"Connection"`
20 | Version string `xml:"Ver,attr"`
21 | }
22 |
23 | type NxcConn struct {
24 | ConnectionName string `xml:"ConnectionName,attr"`
25 | ConnType string `xml:"ConnType,attr"`
26 | Host string `xml:"Host,attr"`
27 | UserName string `xml:"UserName,attr"`
28 | Port string `xml:"Port,attr"`
29 | Password string `xml:"Password,attr"`
30 | }
31 |
32 | func ParseNcx(data []byte) (*NxcConnections, error) {
33 |
34 | cons := NxcConnections{}
35 | err := xml.Unmarshal(data, &cons)
36 | if err != nil {
37 | return nil, errors.New("ncx file format is incorrect!")
38 | }
39 | for idx := range cons.Conns {
40 |
41 | decrPwd := decryptPwd(cons.Conns[idx].Password)
42 |
43 | cons.Conns[idx].Password = decrPwd
44 | }
45 | return &cons, nil
46 | }
47 |
48 | //decryptPwd navicat的加密规则可以参照这个文档
49 | //https://github.com/HyperSine/how-does-navicat-encrypt-password/blob/master/doc/how-does-navicat-encrypt-password.md
50 | func decryptPwd(encryptTxt string) string {
51 | key := []byte(AES_KEY)
52 | ciphertext, _ := hex.DecodeString(encryptTxt)
53 |
54 | block, err := aes.NewCipher(key)
55 | if err != nil {
56 | panic(err)
57 | }
58 |
59 | if len(ciphertext) < aes.BlockSize {
60 | cobra.CheckErr("Decrypt password failed! Please confirm export connection with password.")
61 | }
62 | iv := []byte(AES_IV)
63 |
64 | if len(ciphertext)%aes.BlockSize != 0 {
65 | panic("ciphertext is not a multiple of the block size")
66 | }
67 |
68 | mode := cipher.NewCBCDecrypter(block, iv)
69 |
70 | mode.CryptBlocks(ciphertext, ciphertext)
71 |
72 | return unPadding(ciphertext)
73 | }
74 |
75 | // unPadding remove redundant padding data
76 | func unPadding(src []byte) string {
77 | length := len(src)
78 | unpadding := int(src[length-1])
79 | return string(src[:(length - unpadding)])
80 | }
81 |
--------------------------------------------------------------------------------
/handler/navicat/handle_ncx_test.go:
--------------------------------------------------------------------------------
1 | package navicat
2 |
3 | import (
4 | "encoding/json"
5 | "reflect"
6 | "testing"
7 | )
8 |
9 | func TestParseNcx(t *testing.T) {
10 |
11 | tests := []struct {
12 | name string
13 | data []byte
14 | expect string
15 | }{
16 | // test cases
17 | {
18 | name: "pwd case",
19 | data: []byte(``),
20 | expect: `{"Conns":[{"ConnectionName":"pwd case","ConnType":"MYSQL","Host":"127.0.0.1","UserName":"root","Port":"3306","Password":"This is a test"}],"Version":"1.5"}`,
21 | },
22 | }
23 | for _, tt := range tests {
24 | t.Run(tt.name, func(t *testing.T) {
25 | gotCons, err := ParseNcx(tt.data)
26 | result, err := json.Marshal(gotCons)
27 | if err != nil {
28 | t.Errorf("ParseNcx() error %e", err)
29 | return
30 | }
31 | if !reflect.DeepEqual(string(result), tt.expect) {
32 | t.Errorf("except:%s\n actual:%s", tt.expect, result)
33 | }
34 | })
35 | }
36 | }
37 |
38 | func Test_decryptPwd(t *testing.T) {
39 | type args struct {
40 | encryptTxt string
41 | expect string
42 | }
43 | tests := []struct {
44 | name string
45 | args args
46 | }{
47 | // test cases.
48 | {
49 | name: "case 1",
50 | args: args{
51 | encryptTxt: "833E4ABBC56C89041A9070F043641E3B",
52 | expect: "123456",
53 | },
54 | },
55 | }
56 | for _, tt := range tests {
57 | t.Run(tt.name, func(t *testing.T) {
58 | got := decryptPwd(tt.args.encryptTxt)
59 | if !reflect.DeepEqual(got, tt.args.expect) {
60 | t.Errorf("except:%s\n actual:%s", tt.args.expect, got)
61 | }
62 | })
63 | }
64 | }
65 |
--------------------------------------------------------------------------------
/handler/sql/base_parseddl.go:
--------------------------------------------------------------------------------
1 | package sql
2 |
3 | import (
4 | "errors"
5 | "fmt"
6 | "github.com/spf13/cobra"
7 | "github.com/yesAnd92/lwe/utils"
8 | "regexp"
9 | "strings"
10 | "time"
11 | )
12 |
13 | // BaseParseDDL IParseDDL 接口抽象实现
14 | type BaseParseDDL struct {
15 | }
16 |
17 | // DoParse 定义了整个解析、生成的流程
18 | func (a *BaseParseDDL) DoParse(target string, sqlTextArr []string, args map[string]interface{}) {
19 |
20 | //由target参数找到对应的handle
21 | handle, err := GetParser(target)
22 | if err != nil {
23 | cobra.CheckErr(err)
24 | }
25 |
26 | //解析ddl文本
27 | var objInfos = make([]*ObjInfo, 0)
28 | for _, sqlText := range sqlTextArr {
29 | objInfo, err := handle.ParseDDL(sqlText, args)
30 | if err != nil {
31 | fmt.Println(err)
32 | }
33 | objInfos = append(objInfos, objInfo)
34 | }
35 | //适配不同的生成类型,由子类实现
36 | handle.CovertSyntax(objInfos)
37 |
38 | //渲染数据
39 | handle.RenderData(objInfos)
40 |
41 | fmt.Println("Parse result >> " + utils.ToAbsPath(GENERATE_DIR))
42 | }
43 |
44 | func GetParser(target string) (IParseDDL, error) {
45 | var handle IParseDDL
46 | switch target {
47 | case "java":
48 | handle = NewJavaRenderData()
49 | case "go":
50 | handle = NewGoStructRenderData()
51 | case "json":
52 | handle = NewJsonRenderData()
53 | }
54 | if handle == nil {
55 | return nil, errors.New("target " + target + " param error!")
56 | }
57 | return handle, nil
58 | }
59 |
60 | func (a *BaseParseDDL) ParseDDL(sqlText string, args map[string]interface{}) (*ObjInfo, error) {
61 |
62 | if sqlText == "" {
63 | return nil, errors.New("SQL不能为空")
64 | }
65 |
66 | //替换掉误输入字符、空格等
67 | r1 := strings.NewReplacer("'", "`", ",", ",", "\n", "", "\t", "")
68 | sqlText = strings.ToLower(r1.Replace(sqlText))
69 |
70 | //新增处理create table if not exists members情况
71 | sqlText = strings.ReplaceAll(sqlText, "if not exists", "")
72 |
73 | //表名
74 | var tableName string
75 | if strings.Contains(sqlText, "table") {
76 | tableName = sqlText[strings.Index(sqlText, "table")+5 : strings.Index(sqlText, "(")]
77 | }
78 |
79 | if strings.Contains(tableName, "`") {
80 | tableName = tableName[strings.Index(tableName, "`")+1 : strings.LastIndex(tableName, "`")]
81 | } else {
82 | //空格开头的,需要替换掉\n\t空格
83 | tableName = strings.NewReplacer("\n", "", "\t", "").Replace(tableName)
84 | }
85 | var originTableName = tableName
86 |
87 | //转化为类名
88 | className := utils.UderscoreToUpperCamelCase(tableName)
89 |
90 | //表注释
91 | var tableComment string
92 | //mysql是comment=,pgsql/oracle是comment on table
93 | if strings.Contains(sqlText, "comment=") {
94 | tableComment = sqlText[strings.Index(sqlText, "comment=")+8:]
95 | } else if strings.Contains(sqlText, "comment on table") {
96 | tableComment = sqlText[strings.Index(sqlText, "comment on table")+17:]
97 | } else {
98 | //没有表注释,使用表名作为缺省值
99 | tableComment = originTableName
100 | }
101 | tableComment = strings.NewReplacer("`", "", ";", "").Replace(tableComment)
102 |
103 | //主键对应的字段名
104 | var primaryFiled string
105 | //因为primary key ()中亦可能出现",",因此提前处理掉,防止被当成切割符号切割
106 | //using btree可有可无都允许
107 | //go标准regexp库中如果支持零宽断言,可以直接使用(?<=key \(`)\S*(?=`\))匹配出主键字段来
108 | primaryFiledRe := regexp.MustCompile("primary key \\((.*?)\\)( using btree)*( comment `(.*?)`)*")
109 | primaryFiledFound := primaryFiledRe.FindAllString(sqlText, -1)
110 | if len(primaryFiledFound) == 1 {
111 | filedStr := primaryFiledFound[0]
112 | primaryFiled = filedStr[strings.Index(filedStr, "(`")+2 : strings.Index(filedStr, "`)")]
113 | //移除sqlText中的关于primary key部分
114 | sqlText = strings.ReplaceAll(sqlText, filedStr, "")
115 | }
116 |
117 | //移除索引
118 | //普通索引,全文索引,唯一索引都转换成普通索引统一处理
119 | sqlText = strings.NewReplacer("unique key", "key", "fulltext key", "key").Replace(sqlText)
120 | //所有后面也可以有评论comment
121 | indexRe := regexp.MustCompile("key `(.*?)`\\)( using btree)*( comment `(.*?)`)*")
122 | indexFound := indexRe.FindAllString(sqlText, -1)
123 | for _, indexStr := range indexFound {
124 | //索引相关与生成文件无关,这里处理掉避免产生干扰
125 | sqlText = strings.NewReplacer(indexStr, "").Replace(sqlText)
126 | }
127 |
128 | //截取字段部分
129 | var filedListTmp = sqlText[strings.Index(sqlText, "(")+1 : strings.LastIndex(sqlText, ")")]
130 |
131 | // 匹配 comment,替换备注里的小逗号, 防止被当成切割符号切割
132 | re := regexp.MustCompile("comment `(.*?)\\`")
133 | found := re.FindAllString(filedListTmp, -1)
134 | for _, filedStr := range found {
135 | //使用中文逗号,替换注释中的英文逗号
136 | filedStrNew := strings.ReplaceAll(filedStr, ",", ",")
137 | //再替换回filedLisTmp中的相应位置
138 | filedListTmp = strings.ReplaceAll(filedListTmp, filedStr, filedStrNew)
139 | }
140 |
141 | //匹配 double(20,2) 这种精度描述,替换备注里的小逗号, 防止被当成切割符号切割
142 | re = regexp.MustCompile("\\([0-9]+,[0-9]*\\)")
143 | found = re.FindAllString(filedListTmp, -1)
144 | for _, filedStr := range found {
145 | //直接替换掉(x,x)
146 | filedListTmp = strings.ReplaceAll(filedListTmp, filedStr, "")
147 | }
148 | //解析、组装各个字段
149 | var fileds = make([]*FieldInfo, 0)
150 | filedArr := strings.Split(filedListTmp, ",")
151 | for _, commandline := range filedArr {
152 |
153 | //去除字段行定义语句的前后空格
154 | commandline = strings.TrimSpace(commandline)
155 | if len(commandline) == 0 {
156 | continue
157 | }
158 |
159 | //列名
160 | columnName := commandline[0:strings.Index(commandline, " ")]
161 | columnName = strings.ReplaceAll(columnName, "`", "")
162 |
163 | //columnType
164 | columnType := strings.Split(commandline, " ")[1]
165 | if strings.Contains(columnType, "(") {
166 | columnType = columnType[0:strings.Index(columnType, "(")]
167 | }
168 |
169 | //filedComment
170 | var filedComment string
171 | //mysql的字段注释位于行末
172 | if strings.Contains(commandline, "comment `") {
173 | filedComment = commandline[strings.Index(commandline, "comment")+7:]
174 | filedComment = strings.ReplaceAll(strings.TrimSpace(filedComment), "`", "")
175 | }
176 |
177 | fileds = append(fileds, &FieldInfo{
178 | ColumnName: columnName,
179 | ColumnType: columnType,
180 | FieldComment: filedComment,
181 | })
182 | }
183 |
184 | obj := &ObjInfo{
185 | TableName: originTableName,
186 | ObjName: className,
187 | ObjComment: tableComment,
188 | GenerateDate: time.Now().Format("2006/01/02 15:04"),
189 | PrimaryField: primaryFiled,
190 | FieldInfos: fileds,
191 | Args: args,
192 | }
193 |
194 | return obj, nil
195 | }
196 |
197 | func (a *BaseParseDDL) CovertSyntax(info *ObjInfo) {
198 | //TODO implement me
199 | panic("implement me")
200 | }
201 |
202 | func (a *BaseParseDDL) RenderData(info *ObjInfo) {
203 | //TODO implement me
204 | panic("implement me")
205 | }
206 |
--------------------------------------------------------------------------------
/handler/sql/base_parseddl_test.go:
--------------------------------------------------------------------------------
1 | package sql
2 |
3 | import (
4 | "os"
5 | "path"
6 | "testing"
7 | )
8 |
9 | var (
10 | sqlTextArr = []string{
11 | // TODO: Add test cases.
12 | `CREATE TABLE 'student_info' (
13 | 'id' int(11) NOT NULL AUTO_INCREMENT COMMENT '用户编号,学号',
14 | 'class_id' varchar(255) NOT NULL COMMENT '班级id',
15 | 'user_name' varchar(255) NOT NULL COMMENT '用户名',
16 | 'status' tinyint(1) NOT NULL COMMENT '状态',
17 | 'create_time' datetime NOT NULL COMMENT '创建时间',
18 | PRIMARY KEY ('id')
19 | ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='学生信息'`,
20 | `CREATE TABLE 'class_info' (
21 | 'id' int(11) NOT NULL AUTO_INCREMENT COMMENT '用户编号,学号',
22 | 'name' varchar(255) NOT NULL COMMENT '班级名',
23 | 'create_time' datetime NOT NULL COMMENT '创建时间',
24 | 'total_size' double(20,2) DEFAULT NULL,
25 | PRIMARY KEY ('id') USING BTREE COMMENT '主键'
26 | ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='班级信息'`,
27 | //索引
28 | `CREATE TABLE 'template_role' (
29 | 'role_id' varchar(32) NOT NULL COMMENT '角色id',
30 | 'template_id' varchar(32) NOT NULL COMMENT '模板id',
31 | 'create_time' datetime DEFAULT NULL COMMENT '创建时间',
32 | 'update_time' datetime DEFAULT NULL COMMENT '更新时间',
33 | PRIMARY KEY ('role_id','template_id') USING BTREE,
34 | FULLTEXT KEY 'title_tags' ('title','tags'),
35 | UNIQUE KEY 'userId' ('user_id'),
36 | KEY 'createTime' ('create_time') USING BTREE COMMENT '创建时间索引'
37 | ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='角色关联表 ';`}
38 | )
39 |
40 | func TestDoParse(t *testing.T) {
41 | type args struct {
42 | target string
43 | sqlTextArr []string
44 | args map[string]interface{}
45 | }
46 | tests := []struct {
47 | name string
48 | args args
49 | want []string
50 | }{
51 | // TODO: Add test cases.
52 | {
53 | name: "json",
54 | args: args{
55 | target: "json",
56 | sqlTextArr: sqlTextArr,
57 | args: nil,
58 | },
59 | want: []string{},
60 | },
61 | {
62 | name: "java",
63 | args: args{
64 | target: "java",
65 | sqlTextArr: sqlTextArr,
66 | args: nil,
67 | },
68 | want: []string{"ClassInfo.java", "StudentInfo.java", "TemplateRole.java"},
69 | },
70 | {
71 | name: "go",
72 | args: args{
73 | target: "go",
74 | sqlTextArr: sqlTextArr,
75 | args: nil,
76 | },
77 | want: []string{"lwe_struct.go"},
78 | },
79 | }
80 | defer func() {
81 | os.RemoveAll(GENERATE_DIR)
82 | }()
83 |
84 | parse := &BaseParseDDL{}
85 | for _, tt := range tests {
86 | t.Run(tt.name, func(t *testing.T) {
87 | parse.DoParse(tt.args.target, tt.args.sqlTextArr, tt.args.args)
88 | for _, p := range tt.want {
89 | if f, err := os.Stat(path.Join(GENERATE_DIR, p)); err != nil || f.Size() == 0 {
90 | t.Errorf("file >>> %s is not except", p)
91 | }
92 | }
93 | })
94 | }
95 | }
96 |
--------------------------------------------------------------------------------
/handler/sql/iparseddl.go:
--------------------------------------------------------------------------------
1 | package sql
2 |
3 | // IParseDDL 解析生成目标文件的核心接口
4 | type IParseDDL interface {
5 |
6 | // ParseDDL 解析DDL文本
7 | //args 命令行传入的参数,如:注释中的author字段
8 | ParseDDL(sqlText string, args map[string]interface{}) (*ObjInfo, error)
9 |
10 | // CovertSyntax 转换到不同语言的字段类型
11 | // 比如sql中int对应到Java中的Integer,对应到go中的int32等
12 | CovertSyntax(info []*ObjInfo)
13 |
14 | // RenderData 根据模版渲染数据
15 | RenderData(info []*ObjInfo)
16 | }
17 |
18 | // 定义sql所用的常量
19 | const (
20 | //生成文件所在的目录
21 | GENERATE_DIR = `./lwe-generate-file`
22 | //生成JAVA文件名
23 | GENERATE_JAVA_FILENAME = "%s.java"
24 | //生成Go文件名
25 | GENERATE_GO_FILENAME = "lwe_struct.go"
26 | //go模板头内容
27 | GO_TPL_HEAD = `package lwe
28 | import ("time")`
29 | )
30 |
31 | // ObjInfo 表映射的对象及其字段信息
32 | type ObjInfo struct {
33 | TableName string // 表名
34 | ObjName string // 对象名
35 | ObjComment string // 对象的注释
36 | GenerateDate string // 生成日期
37 | PrimaryField string // 主键对应的字段
38 | FieldInfos []*FieldInfo // 字段的切片
39 | Args map[string]interface{} // 命令行传入的参数
40 | }
41 |
42 | // FieldInfo 字段信息
43 | type FieldInfo struct {
44 | ColumnName string // 列名
45 | ColumnType string // 列类型
46 | FieldName string // 字段名
47 | FieldType string // 字段类型
48 | FieldComment string // 字段的注释
49 | }
50 |
--------------------------------------------------------------------------------
/handler/sql/render_gostruct.go:
--------------------------------------------------------------------------------
1 | package sql
2 |
3 | import (
4 | "bytes"
5 | "github.com/yesAnd92/lwe/templates"
6 | "github.com/yesAnd92/lwe/utils"
7 | "go/format"
8 | "log"
9 | "os"
10 | path2 "path"
11 | "path/filepath"
12 | "text/template"
13 | )
14 |
15 | type GoStructRenderData struct {
16 | *BaseParseDDL
17 | goStructTpl *template.Template
18 | }
19 |
20 | func NewGoStructRenderData() *GoStructRenderData {
21 | //加载实体对应的模板
22 | goStructTpl := templates.InitGoStructTpl()
23 | return &GoStructRenderData{
24 | goStructTpl: goStructTpl,
25 | }
26 | }
27 |
28 | func (g *GoStructRenderData) CovertSyntax(objInfos []*ObjInfo) {
29 | for _, objInfo := range objInfos {
30 | for _, f := range objInfo.FieldInfos {
31 | //sql类型映射成java类型
32 | f.FieldType = utils.SqlToGoType(f.ColumnType)
33 | //sql字段名对应的Bean名字
34 | f.FieldName = utils.UderscoreToLowerCamelCase(f.ColumnName)
35 | }
36 | }
37 | }
38 |
39 | func (g *GoStructRenderData) RenderData(objInfos []*ObjInfo) {
40 |
41 | utils.MkdirIfNotExist(GENERATE_DIR)
42 |
43 | //生成的多个结构体先放到buffer中,最后一起写入文件
44 | bf := &bytes.Buffer{}
45 |
46 | //追加package import信息
47 | bf.Write([]byte(GO_TPL_HEAD))
48 | for _, objInfo := range objInfos {
49 | g.goStructTpl.Execute(bf, objInfo)
50 | }
51 |
52 | //使用objName作为生成的文件名
53 | fileName := path2.Join(GENERATE_DIR, GENERATE_GO_FILENAME)
54 | path, _ := filepath.Abs(fileName)
55 | f, err := os.Create(path)
56 | defer f.Close()
57 |
58 | if err != nil {
59 | log.Println("Create go file err", err)
60 | return
61 | }
62 | //按照go的风格进行格式化
63 | fmtBfBytes, _ := format.Source(bf.Bytes())
64 |
65 | f.Write(fmtBfBytes)
66 | }
67 |
--------------------------------------------------------------------------------
/handler/sql/render_javabean.go:
--------------------------------------------------------------------------------
1 | package sql
2 |
3 | import (
4 | "fmt"
5 | "github.com/yesAnd92/lwe/templates"
6 | "github.com/yesAnd92/lwe/utils"
7 | "log"
8 | "os"
9 | path2 "path"
10 | "path/filepath"
11 | "text/template"
12 | )
13 |
14 | type JavaRenderData struct {
15 | *BaseParseDDL
16 | JavaTpl *template.Template
17 | }
18 |
19 | func NewJavaRenderData() *JavaRenderData {
20 | //加载实体对应的模板
21 | javaTpl := templates.InitJavaTpl()
22 | return &JavaRenderData{
23 | JavaTpl: javaTpl,
24 | }
25 | }
26 |
27 | func (m *JavaRenderData) CovertSyntax(objInfos []*ObjInfo) {
28 | for _, objInfo := range objInfos {
29 | //sql类型映射成java类型
30 | //sql字段名对应的Bean名字
31 | for _, f := range objInfo.FieldInfos {
32 | f.FieldType = utils.SqlToJavaType(f.ColumnType)
33 | f.FieldName = utils.UderscoreToLowerCamelCase(f.ColumnName)
34 | }
35 | }
36 | }
37 |
38 | func (m *JavaRenderData) RenderData(objInfos []*ObjInfo) {
39 | utils.MkdirIfNotExist(GENERATE_DIR)
40 |
41 | for _, objInfo := range objInfos {
42 | //使用objName作为生成的文件名
43 | fileName := fmt.Sprintf(path2.Join(GENERATE_DIR, GENERATE_JAVA_FILENAME), objInfo.ObjName)
44 | path, _ := filepath.Abs(fileName)
45 | f, err := os.Create(path)
46 | defer f.Close()
47 |
48 | if err != nil {
49 | log.Println("Create java file err", err)
50 | return
51 | }
52 | m.JavaTpl.Execute(f, objInfo)
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/handler/sql/render_json.go:
--------------------------------------------------------------------------------
1 | package sql
2 |
3 | import (
4 | "encoding/json"
5 | "fmt"
6 | "github.com/yesAnd92/lwe/utils"
7 | "time"
8 | )
9 |
10 | type JsonRenderData struct {
11 | *BaseParseDDL
12 | }
13 |
14 | func NewJsonRenderData() *JsonRenderData {
15 | return &JsonRenderData{}
16 | }
17 |
18 | func (j *JsonRenderData) RenderData(objInfos []*ObjInfo) {
19 | for _, objInfo := range objInfos {
20 | infos := objInfo.FieldInfos
21 | filedMap := make(map[string]interface{}, len(infos))
22 | for _, info := range infos {
23 | switch info.FieldType {
24 |
25 | //塞入默认值
26 | case "Integer":
27 | filedMap[info.FieldName] = 0
28 | case "Long":
29 | filedMap[info.FieldName] = 0
30 | case "Float":
31 | filedMap[info.FieldName] = 1.0
32 | case "Double":
33 | filedMap[info.FieldName] = 1.0
34 | case "String":
35 | filedMap[info.FieldName] = info.FieldComment
36 | case "Date":
37 | filedMap[info.FieldName] = time.Now().Format("2006-01-02 15:04:05")
38 | }
39 | }
40 | marshal, _ := json.MarshalIndent(filedMap, "", " ")
41 | fmt.Println(string(marshal))
42 | }
43 | }
44 | func (j *JsonRenderData) CovertSyntax(objInfos []*ObjInfo) {
45 |
46 | for _, objInfo := range objInfos {
47 | //sql类型映射成java类型
48 | //sql字段名对应的Bean名字
49 | for _, f := range objInfo.FieldInfos {
50 | f.FieldType = utils.SqlToJavaType(f.ColumnType)
51 | f.FieldName = utils.UderscoreToLowerCamelCase(f.ColumnName)
52 | }
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/handler/sqllog/sql_log.go:
--------------------------------------------------------------------------------
1 | package sqllog
2 |
3 | import (
4 | "fmt"
5 | "strings"
6 |
7 | "github.com/dlclark/regexp2"
8 | )
9 |
10 | const (
11 | separatorPreparing = "Preparing:"
12 | separatorParameter = "Parameters:"
13 | )
14 |
15 | var (
16 | preparingPattern = separatorPreparing + "(.*?)(?=\n|\r|\r\n)"
17 | parameterPattern = separatorParameter + "(.*?)(?=\n|\r|\r\n)"
18 | )
19 |
20 | // ParseMybatisSqlLog parses a Mybatis SQL log and returns the formatted SQL query.
21 | func ParseMybatisSqlLog(sqlLog string) (string, error) {
22 | if !strings.HasSuffix(sqlLog, "\n") && !strings.HasSuffix(sqlLog, "\r\n") {
23 | sqlLog += "\n"
24 | }
25 |
26 | prepare, err := extractPattern(preparingPattern, sqlLog)
27 | if err != nil {
28 | return "", fmt.Errorf("parsing Preparing section: %w", err)
29 | }
30 |
31 | prepare = strings.ReplaceAll(prepare, separatorPreparing, "")
32 |
33 | param, err := extractPattern(parameterPattern, sqlLog)
34 | if err != nil {
35 | return "", fmt.Errorf("parsing Parameters section: %w", err)
36 | }
37 |
38 | params := strings.Split(strings.ReplaceAll(param, separatorParameter, ""), ",")
39 |
40 | values := make([]string, 0, len(params))
41 | for _, p := range params {
42 | values = append(values, extractValue(p))
43 | }
44 |
45 | if strings.Count(prepare, "?") != len(values) {
46 | return "", fmt.Errorf("mismatch between placeholders (?) and parameters count")
47 | }
48 |
49 | // Replace ? in prepare with values from values
50 | var result strings.Builder
51 | paramIndex := 0
52 | for _, char := range prepare {
53 | if char == '?' {
54 | result.WriteString(values[paramIndex])
55 | paramIndex++
56 | } else {
57 | result.WriteRune(char)
58 | }
59 | }
60 |
61 | return strings.TrimSpace(result.String()), nil
62 | }
63 |
64 | var typesNeedQuotes = []string{"String", "Timestamp", "Date", "Time"}
65 |
66 | func needQuotes(s string) bool {
67 | for _, t := range typesNeedQuotes {
68 | if strings.Contains(s, t) {
69 | return true
70 | }
71 | }
72 | return false
73 | }
74 |
75 | func extractPattern(pattern, log string) (string, error) {
76 | re := regexp2.MustCompile(pattern, 0)
77 | match, err := re.FindStringMatch(log)
78 | if err != nil {
79 | return "", fmt.Errorf("regex match failed: %w", err)
80 | }
81 | if match == nil {
82 | return "", fmt.Errorf("no match found")
83 | }
84 | return match.String(), nil
85 | }
86 |
87 | // First, remove whitespace from both ends of the string.
88 | // Iterate through each character in the string:
89 | // When encountering a left parenthesis '(', if it's the first one, record its position.
90 | // When encountering a right parenthesis ')', check if it matches the outermost pair of parentheses.
91 | // If the outermost pair of parentheses is found, return the content before the parentheses.
92 | // If no matching pair of parentheses is found, return the entire string.
93 | func extractValue(s string) string {
94 | s = strings.TrimSpace(s)
95 | lastOpenParen := -1
96 | parenCount := 0
97 |
98 | for i, char := range s {
99 | if char == '(' {
100 | if parenCount == 0 {
101 | lastOpenParen = i
102 | }
103 | parenCount++
104 | } else if char == ')' {
105 | parenCount--
106 | if parenCount == 0 && lastOpenParen != -1 {
107 | innerContent := s[lastOpenParen+1 : i]
108 | result := strings.TrimSpace(s[:lastOpenParen])
109 | if needQuotes(innerContent) {
110 | return "'" + result + "'"
111 | }
112 | return result
113 | }
114 | }
115 | }
116 |
117 | return s
118 | }
119 |
--------------------------------------------------------------------------------
/handler/sqllog/sql_log_test.go:
--------------------------------------------------------------------------------
1 | package sqllog
2 |
3 | import (
4 | "testing"
5 | )
6 |
7 | func TestParseMybatisSqlLog(t *testing.T) {
8 | tests := []struct {
9 | name string
10 | input string
11 | expected string
12 | hasError bool
13 | }{
14 | {
15 | name: "Basic SQL log",
16 | input: `Preparing: SELECT * FROM users WHERE id = ?
17 | Parameters: 1(Integer)`,
18 | expected: "SELECT * FROM users WHERE id = 1",
19 | hasError: false,
20 | },
21 | {
22 | name: "SQL log with string parameter",
23 | input: `Preparing: SELECT * FROM users WHERE name = ?
24 | Parameters: John(String)`,
25 | expected: "SELECT * FROM users WHERE name = 'John'",
26 | hasError: false,
27 | },
28 | {
29 | name: "SQL log with timestamp parameter",
30 | input: `Preparing: SELECT * FROM logs WHERE created_at > ?
31 | Parameters: 2023-05-01 10:00:00(Timestamp)`,
32 | expected: "SELECT * FROM logs WHERE created_at > '2023-05-01 10:00:00'",
33 | hasError: false,
34 | },
35 | {
36 | name: "SQL log with multiple parameters",
37 | input: `Preparing: INSERT INTO users (name, age, created_at) VALUES (?, ?, ?)
38 | Parameters: Alice(String), 30(Integer), 2023-05-01 12:00:00(Timestamp)`,
39 | expected: "INSERT INTO users (name, age, created_at) VALUES ('Alice', 30, '2023-05-01 12:00:00')",
40 | hasError: false,
41 | },
42 | {
43 | name: "Invalid SQL log format",
44 | input: "This is not a valid SQL log",
45 | expected: "",
46 | hasError: true,
47 | },
48 | {
49 | name: "SQL log missing parameters",
50 | input: `Preparing: SELECT * FROM users WHERE id = ?`,
51 | expected: "",
52 | hasError: true,
53 | },
54 | {
55 | name: "Mismatch between placeholders and parameters",
56 | input: `Preparing: SELECT * FROM users WHERE id = ? AND name = ?
57 | Parameters: 1(Integer)`,
58 | expected: "",
59 | hasError: true,
60 | },
61 | {
62 | name: "Like query",
63 | input: `Preparing: SELECT * FROM users WHERE id name LIKE CONCAT(?, '%')
64 | Parameters: yesA(String)`,
65 | expected: "SELECT * FROM users WHERE id name LIKE CONCAT('yesA', '%')",
66 | hasError: false,
67 | },
68 | {
69 | name: "Insert SQL log",
70 | input: `Preparing: INSERT INTO products (name, price, category) VALUES (?, ?, ?)
71 | Parameters: New Product(String), 19.99(Double), Electronics(String)`,
72 | expected: "INSERT INTO products (name, price, category) VALUES ('New Product', 19.99, 'Electronics')",
73 | hasError: false,
74 | },
75 | {
76 | name: "Update SQL log",
77 | input: `Preparing: UPDATE orders SET status = ?, updated_at = ? WHERE id = ?
78 | Parameters: Shipped(String), 2023-05-02 15:30:00(Timestamp), 1001(Integer)`,
79 | expected: "UPDATE orders SET status = 'Shipped', updated_at = '2023-05-02 15:30:00' WHERE id = 1001",
80 | hasError: false,
81 | },
82 | }
83 |
84 | for _, tt := range tests {
85 | t.Run(tt.name, func(t *testing.T) {
86 | result, err := ParseMybatisSqlLog(tt.input)
87 |
88 | if err != nil {
89 | t.Logf("Error: %v", err)
90 | }
91 |
92 | if tt.hasError {
93 | if err == nil {
94 | t.Errorf("Expected an error, but got none")
95 | }
96 | } else {
97 | if err != nil {
98 | t.Errorf("Unexpected error: %v", err)
99 | } else if result != tt.expected {
100 | t.Errorf("Result mismatch\nExpected: %s\nActual: %s", tt.expected, result)
101 | // Additional debug information
102 | t.Logf("Expected length: %d", len(tt.expected))
103 | t.Logf("Actual length: %d", len(result))
104 | // Character-by-character comparison
105 | for i := 0; i < len(tt.expected) && i < len(result); i++ {
106 | if tt.expected[i] != result[i] {
107 | t.Logf("First mismatch at position %d: Expected '%c', Actual '%c'", i, tt.expected[i], result[i])
108 | break
109 | }
110 | }
111 | }
112 | }
113 | })
114 | }
115 | }
116 |
--------------------------------------------------------------------------------
/handler/sync/file_sync.go:
--------------------------------------------------------------------------------
1 | package sync
2 |
3 | import (
4 | "fmt"
5 | "github.com/jedib0t/go-pretty/v6/table"
6 | "github.com/yesAnd92/lwe/utils"
7 | "os"
8 | "strings"
9 | "sync"
10 | )
11 |
12 | // struct{}不占用空间
13 | type void struct{}
14 |
15 | type CompareThenDoIfa interface {
16 | Do(*Fsync)
17 | }
18 |
19 | type Fsync struct {
20 | sourceDir string
21 | targetDir string
22 | commonSet map[string]void
23 | sUnique *[]string
24 | tUnique *[]string
25 | }
26 |
27 | func InitFsync(sourceDir, targetDir string) *Fsync {
28 | return &Fsync{
29 | sourceDir: utils.ToAbsPath(sourceDir),
30 | targetDir: utils.ToAbsPath(targetDir),
31 | commonSet: make(map[string]void),
32 | sUnique: &[]string{},
33 | tUnique: &[]string{},
34 | }
35 | }
36 |
37 | func (f *Fsync) DiffDir() {
38 | sourceDir := f.sourceDir
39 | targetDir := f.targetDir
40 |
41 | wg := sync.WaitGroup{}
42 | wg.Add(2)
43 | var sourceFileSet = make(map[string]void)
44 | var targetFileSet = make(map[string]void)
45 | go func() {
46 | defer wg.Done()
47 | findAllFile(sourceDir, sourceFileSet)
48 | }()
49 |
50 | go func() {
51 | defer wg.Done()
52 | findAllFile(targetDir, targetFileSet)
53 | }()
54 | wg.Wait()
55 |
56 | if len(sourceFileSet) < len(targetFileSet) {
57 | for s := range sourceFileSet {
58 | rlvSource := strings.TrimPrefix(s, sourceDir)
59 | if _, ok := targetFileSet[targetDir+rlvSource]; ok {
60 | //common
61 | f.commonSet[rlvSource] = void{}
62 | }
63 | }
64 | } else {
65 | for t := range targetFileSet {
66 | rlvTarget := strings.TrimPrefix(t, targetDir)
67 | if _, ok := sourceFileSet[sourceDir+rlvTarget]; ok {
68 | //common
69 | f.commonSet[rlvTarget] = void{}
70 | }
71 | }
72 | }
73 |
74 | for s := range sourceFileSet {
75 | sp := strings.TrimPrefix(s, sourceDir)
76 | if _, ok := f.commonSet[sp]; !ok {
77 | *f.sUnique = append(*f.sUnique, sp)
78 | }
79 | }
80 |
81 | for t := range targetFileSet {
82 | tp := strings.TrimPrefix(t, targetDir)
83 | if _, ok := f.commonSet[tp]; !ok {
84 | *f.tUnique = append(*f.tUnique, tp)
85 | }
86 | }
87 | }
88 |
89 | func (f *Fsync) Sync(thenDo CompareThenDoIfa) {
90 | thenDo.Do(f)
91 | }
92 |
93 | type DisplayCompareThenDo struct {
94 | }
95 |
96 | func (d *DisplayCompareThenDo) Do(fsync *Fsync) {
97 | t := table.NewWriter()
98 | t.SetOutputMirror(os.Stdout)
99 | t.AppendHeader(table.Row{"Source", " VS", "Target"})
100 |
101 | sourceDir := fsync.sourceDir
102 | targetDir := fsync.targetDir
103 |
104 | for k, _ := range fsync.commonSet {
105 | t.AppendRow(table.Row{sourceDir + k, "<==>", targetDir + k})
106 | }
107 |
108 | for _, v := range *fsync.sUnique {
109 | t.AppendRow(table.Row{sourceDir + v, "===>", ""})
110 | }
111 |
112 | for _, v := range *fsync.tUnique {
113 | t.AppendRow(table.Row{"", "<===", targetDir + v})
114 | }
115 |
116 | if t.Length() > 0 {
117 | t.Render()
118 | } else {
119 | fmt.Println("Source and target dir are empty dir!")
120 | }
121 | fmt.Println()
122 | }
123 |
124 | type CopyCompareThenDo struct {
125 | }
126 |
127 | func (c *CopyCompareThenDo) Do(fsync *Fsync) {
128 |
129 | sourceDir := fsync.sourceDir
130 | targetDir := fsync.targetDir
131 |
132 | //source unique to target
133 | for _, path := range *fsync.sUnique {
134 | written, err := utils.Copy(sourceDir+path, targetDir+path)
135 | if err != nil {
136 | fmt.Println(err)
137 | continue
138 | } else {
139 | fmt.Printf("%s\tcopy finished!,total:%2fKB\n", targetDir+path, float64(written)/1014)
140 | }
141 | }
142 | fmt.Println("All file copy finished!")
143 |
144 | }
145 |
146 | func findAllFile(dir string, re map[string]void) {
147 | // 打开目录并获取文件信息
148 | walk(dir, re)
149 |
150 | }
151 |
152 | func walk(dir string, pathMap map[string]void) {
153 | // 打开目录并获取文件信息
154 | dirEntry, err := os.ReadDir(dir)
155 | if err != nil {
156 | fmt.Println(err)
157 | return
158 | }
159 | for _, file := range dirEntry {
160 | //根据文件或文件夹名是否"."开头判断是否隐藏内容,隐藏暂不同步
161 | if strings.HasPrefix(file.Name(), ".") {
162 | continue
163 | }
164 |
165 | //遇到目录递归遍历
166 | if file.IsDir() {
167 | walk(dir+"/"+file.Name(), pathMap)
168 | } else {
169 | pathMap[dir+"/"+file.Name()] = void{}
170 | }
171 |
172 | }
173 | }
174 |
--------------------------------------------------------------------------------
/handler/sync/file_sync_test.go:
--------------------------------------------------------------------------------
1 | package sync
2 |
3 | import (
4 | "testing"
5 | )
6 |
7 | func TestFsync_compareDirDiff(t *testing.T) {
8 |
9 | tests := []struct {
10 | name string
11 | fsync *Fsync
12 | }{
13 | // TODO: Add test cases.
14 | {
15 | name: "mac",
16 | fsync: InitFsync("/Users/wangyj/ideaProject/my/lwe", "/Users/wangyj/Desktop/lwe_copy"),
17 | },
18 | {
19 | name: "win",
20 | fsync: InitFsync("D:\\ideaProject\\my\\go_works\\src\\lwe", "C:\\Users\\Administrator\\Desktop\\lwe_copy"),
21 | },
22 | {
23 | name: "empty dir",
24 | fsync: InitFsync("/Users/wangyj/Desktop/a", "/Users/wangyj/Desktop/a"),
25 | },
26 | }
27 | for _, tt := range tests {
28 |
29 | t.Run(tt.name, func(t *testing.T) {
30 | f := tt.fsync
31 | f.DiffDir()
32 | f.Sync(&DisplayCompareThenDo{})
33 | })
34 | }
35 | }
36 |
37 | func TestFsync_CopyCompareThenDo(t *testing.T) {
38 |
39 | tests := []struct {
40 | name string
41 | fsync *Fsync
42 | }{
43 | // TODO: Add test cases.
44 | {
45 | name: "mac",
46 | fsync: InitFsync("/Users/wangyj/ideaProject/my/lwe", "/Users/wangyj/Desktop/lwe_copy"),
47 | },
48 | {
49 | name: "win",
50 | fsync: InitFsync("D:\\ideaProject\\my\\go_works\\src\\lwe", "C:\\Users\\Administrator\\Desktop\\lwe_copy"),
51 | },
52 | {
53 | name: "empty dir",
54 | fsync: InitFsync("/Users/wangyj/Desktop/a", "/Users/wangyj/Desktop/a"),
55 | },
56 | }
57 | for _, tt := range tests {
58 |
59 | t.Run(tt.name, func(t *testing.T) {
60 | f := tt.fsync
61 | f.DiffDir()
62 | f.Sync(&CopyCompareThenDo{})
63 | })
64 | }
65 | }
66 |
--------------------------------------------------------------------------------
/handler/url/url_fmt.go:
--------------------------------------------------------------------------------
1 | package url
2 |
3 | import (
4 | "fmt"
5 | "github.com/pkg/errors"
6 | "net/url"
7 | "sort"
8 | "strconv"
9 | "strings"
10 | )
11 |
12 | type UrlPares struct {
13 | Host, Path string
14 | keys []string
15 | paramMap map[string]string
16 | }
17 |
18 | func HandleUrlPathParams(uri string) (*UrlPares, error) {
19 | URL, parseErr := url.Parse(uri)
20 | if parseErr != nil {
21 | return nil, parseErr
22 | }
23 |
24 | //paramMap := make(map[string]string, 8)
25 |
26 | rawQuery := URL.RawQuery
27 |
28 | kvStr := strings.Split(rawQuery, "&")
29 | var keys []string
30 | var paramMap map[string]string
31 | for _, kv := range kvStr {
32 | if len(kv) == 0 {
33 | continue
34 | }
35 |
36 | kvPair := strings.Split(kv, "=")
37 | //left of '=' require not empty
38 | if len(kvPair) == 1 {
39 | return nil, errors.New(fmt.Sprintf("Can't format [%s],please check url", uri))
40 | }
41 | kStr := kvPair[0]
42 | //For the convenience of testing
43 | if paramMap == nil {
44 | paramMap = make(map[string]string, 8)
45 | }
46 | paramMap[kStr] = kvPair[1]
47 | keys = append(keys, kStr)
48 | }
49 |
50 | sort.Slice(keys, func(i, j int) bool {
51 | return len(keys[i]) < len(keys[j])
52 | })
53 |
54 | return &UrlPares{
55 | Host: URL.Host,
56 | Path: URL.Path,
57 | keys: keys,
58 | paramMap: paramMap,
59 | }, nil
60 | }
61 |
62 | func FmtPrint(pares *UrlPares) {
63 |
64 | fmt.Printf("Host: %s\n", pares.Host)
65 | fmt.Printf("Path: %s\n", pares.Path)
66 | fmt.Printf("%s\n", strings.Repeat("-", len(pares.Path)+6))
67 |
68 | //根据key的最大长度来格式化输出
69 | maxLen := len(pares.keys[len(pares.keys)-1])
70 |
71 | for _, key := range pares.keys {
72 | fmt.Printf("%-"+strconv.Itoa(maxLen)+"s\t%s\n", key, pares.paramMap[key])
73 | }
74 | }
75 |
--------------------------------------------------------------------------------
/handler/url/url_fmt_test.go:
--------------------------------------------------------------------------------
1 | package url
2 |
3 | import (
4 | "reflect"
5 | "testing"
6 | )
7 |
8 | func TestHandleUrlPathParams(t *testing.T) {
9 | type args struct {
10 | uri string
11 | }
12 | tests := []struct {
13 | name string
14 | args args
15 | want *UrlPares
16 | wantErr bool
17 | }{
18 | // TODO: Add test cases.
19 | {
20 | name: "case 1",
21 | args: args{
22 | "http://api.demo.com/api/user/getList?a=v1&b=v2&c=1685673384000",
23 | },
24 | want: &UrlPares{
25 | Host: "api.demo.com",
26 | Path: "/api/user/getList",
27 | keys: []string{"a", "b", "c"},
28 | paramMap: map[string]string{
29 | "a": "v1",
30 | "b": "v2",
31 | "c": "1685673384000",
32 | },
33 | },
34 | wantErr: false,
35 | },
36 | {
37 | name: "case 2",
38 | args: args{
39 | "/api/user/getList?a=v1&b=v2&c=1685673384000",
40 | },
41 | want: &UrlPares{
42 | Path: "/api/user/getList",
43 | keys: []string{"a", "b", "c"},
44 | paramMap: map[string]string{
45 | "a": "v1",
46 | "b": "v2",
47 | "c": "1685673384000",
48 | },
49 | },
50 | wantErr: false,
51 | },
52 | {
53 | name: "case 3",
54 | args: args{
55 | "/api/user/getList",
56 | },
57 | want: &UrlPares{
58 | Path: "/api/user/getList",
59 | },
60 | wantErr: false,
61 | },
62 | {
63 | name: "case 4",
64 | args: args{
65 | "?a=111",
66 | },
67 | want: &UrlPares{
68 | keys: []string{"a"},
69 | paramMap: map[string]string{
70 | "a": "111",
71 | },
72 | },
73 | wantErr: false,
74 | },
75 | {
76 | name: "bad case 5",
77 | args: args{
78 | "&a=&&",
79 | },
80 | want: &UrlPares{
81 | Path: "&a=&&",
82 | },
83 | wantErr: false,
84 | },
85 |
86 | {
87 | name: "bad case 6",
88 | args: args{
89 | "111",
90 | },
91 | want: &UrlPares{
92 | Path: "111",
93 | },
94 | wantErr: false,
95 | },
96 |
97 | {
98 | name: "bad case 7",
99 | args: args{
100 | "http://api.demo.com/api/user/getList?platfor=&a",
101 | },
102 | wantErr: true,
103 | },
104 | }
105 |
106 | for _, tt := range tests {
107 | t.Run(tt.name, func(t *testing.T) {
108 | got, err := HandleUrlPathParams(tt.args.uri)
109 | if (err != nil) != tt.wantErr {
110 | t.Errorf("HandleUrlPathParams() error = %v, wantErr %v", err, tt.wantErr)
111 | return
112 | }
113 | if !reflect.DeepEqual(got, tt.want) {
114 | t.Errorf("HandleUrlPathParams() got = %v, want %v", got, tt.want)
115 | }
116 | })
117 | }
118 | }
119 |
--------------------------------------------------------------------------------
/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import "github.com/yesAnd92/lwe/cmd"
4 |
5 | func main() {
6 | cmd.Execute()
7 | }
8 |
--------------------------------------------------------------------------------
/templates/format_go.tpl:
--------------------------------------------------------------------------------
1 | //{{.ObjName}} {{.ObjComment}}
2 | type {{.ObjName}} struct {
3 | {{/* 渲染字段 */ -}}
4 | {{range .FieldInfos}}
5 | {{upperFirst .FieldName}} {{.FieldType}} `gorm:"{{ if eq $.PrimaryField .ColumnName}}primary_key;{{ end}}" json:"{{ .FieldName}}"` {{if .FieldComment}}//{{.FieldComment}}{{- end -}}
6 | {{end}}
7 |
8 | }
9 |
--------------------------------------------------------------------------------
/templates/format_java.tpl:
--------------------------------------------------------------------------------
1 | import java.util.Date;
2 | import java.util.List;
3 | import java.io.Serializable;
4 | import javax.persistence.Column;
5 | import javax.persistence.Id;
6 | import javax.persistence.Table;
7 | import javax.persistence.GeneratedValue;
8 |
9 | /**
10 | * @Description {{.ObjComment}}
11 | * @Author {{if .Args.author}} {{.Args.author}} {{- end}}
12 | * @Date {{.GenerateDate}}
13 | */
14 | @Table ( name ="{{.TableName}}" )
15 | public class {{.ObjName}} implements Serializable {
16 |
17 | private static final long serialVersionUID = 1L;
18 |
19 | {{/* 渲染字段 */ -}}
20 | {{range .FieldInfos}}
21 | {{ if eq $.PrimaryField .ColumnName}}@Id {{ end}}
22 | @Column(name = "{{.ColumnName}}" )
23 | private {{.FieldType}} {{.FieldName}}; {{if .FieldComment}}//{{.FieldComment}}{{- end -}}
24 | {{end}}
25 |
26 | {{/* 渲染字段对应的setter、getter */ -}}
27 | {{range .FieldInfos}}
28 | public {{ .FieldType}} get{{upperFirst .FieldName}}() {
29 | return {{.FieldName}};
30 | }
31 |
32 | public void set{{upperFirst .FieldName}}({{.FieldType}} {{.FieldName}}) {
33 | this.{{.FieldName}} = {{.FieldName}};
34 | }
35 | {{end}}
36 | }
--------------------------------------------------------------------------------
/templates/init_templates.go:
--------------------------------------------------------------------------------
1 | package templates
2 |
3 | import (
4 | _ "embed"
5 | "github.com/yesAnd92/lwe/utils"
6 | "text/template"
7 | )
8 |
9 | //go:embed format_go.tpl
10 | var goStructTpl string
11 |
12 | //go:embed format_java.tpl
13 | var javaStructTpl string
14 |
15 | // InitGoStructTpl go template
16 | func InitGoStructTpl() *template.Template {
17 | tpl := template.Must(template.New("goTpl").Funcs(utils.TemplateFunc).Parse(goStructTpl))
18 | return tpl
19 | }
20 |
21 | // InitJavaTpl java template
22 | func InitJavaTpl() *template.Template {
23 | tpl := template.Must(template.New("javaTpl").Funcs(utils.TemplateFunc).Parse(javaStructTpl))
24 | return tpl
25 | }
26 |
--------------------------------------------------------------------------------
/testdata/diff.log:
--------------------------------------------------------------------------------
1 | --- a/ai/ai_chat.go
2 | @@ -17,6 +17,7 @@ type AIName string
3 |
4 | const (
5 | Deepseek = "deepseek"
6 | + Siliconflow = "siliconflow"
7 | )
8 |
9 | func NewAIAgent() *AIAgent {
10 | @@ -31,7 +32,9 @@ func NewAIAgent() *AIAgent {
11 | case Deepseek:
12 | agent = AIAgent{AiChat: &DeepSeek{}}
13 | break
14 | -
15 | + case Siliconflow:
16 | + agent = AIAgent{AiChat: &SiliconFlow{}}
17 | + break
18 | default:
19 | cobra.CheckErr("AI configuration is missing or incorrect.")
20 | }
21 | --- a/ai/deepseek.go
22 | @@ -14,11 +14,11 @@ type DeepSeek struct {
23 | }
24 |
25 | func (ds *DeepSeek) Chat(ctx, prompt string) (string, error) {
26 | - resp := Send(ctx, prompt)
27 | + resp := dsSend(ctx, prompt)
28 | return resp, nil
29 | }
30 |
31 | -func Send(ctx, prompt string) string {
32 | +func dsSend(ctx, prompt string) string {
33 |
34 | aiConfig := config.GlobalConfig.Ai
35 | url := aiConfig.BaseUrl
36 | @@ -98,7 +98,7 @@ func Send(ctx, prompt string) string {
37 | cobra.CheckErr(fmt.Sprintf("request error: %v", errorData))
38 | }
39 |
40 | - dsResp := &DeepSeekResponse{}
41 | + dsResp := &CommonResponse{}
42 | if err := mapstructure.Decode(result, &dsResp); err != nil {
43 | cobra.CheckErr(err)
44 | }
45 | --- a/ai/deepseek_test.go
46 | @@ -1,8 +1,17 @@
47 | package ai
48 |
49 | -import "testing"
50 | +import (
51 | + "fmt"
52 | + "github.com/yesAnd92/lwe/config"
53 | + "testing"
54 | +)
55 |
56 | func TestDeepSeek_Chat(t *testing.T) {
57 | + config.InitConfig()
58 | ds := &DeepSeek{}
59 | - ds.Chat("hh", "") // 添加第二个参数为空字符串以满足函数签名要求
60 | + chat, err := ds.Chat("hello,deepseek", "")
61 | + if err != nil {
62 | + fmt.Println(err)
63 | + }
64 | + fmt.Println(chat)
65 | }
66 |
--------------------------------------------------------------------------------
/testdata/github.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yesAnd92/lwe/4c5dc5808aab38201001b92af50700e689d9425a/testdata/github.png
--------------------------------------------------------------------------------
/utils/cmderutils.go:
--------------------------------------------------------------------------------
1 | package utils
2 |
3 | import (
4 | "bytes"
5 | "errors"
6 | "fmt"
7 | "os"
8 | "os/exec"
9 | "time"
10 | "unsafe"
11 | )
12 |
13 | /**
14 | 执行系统脚本工具类
15 | 参照这个工具类,写的很棒 https://github.com/andeya/goutil/blob/master/cmder/cmd.go
16 | */
17 |
18 | var cmdArg = make([]string, 2)
19 |
20 | // 根据操作系统环境初始化cmd关闭命令行的参数
21 | func init() {
22 | if OsEnv() == Win {
23 | cmdArg[0] = "cmd"
24 | cmdArg[1] = "/c"
25 | } else {
26 | cmdArg[0] = "/bin/sh"
27 | cmdArg[1] = "-c"
28 | }
29 | }
30 |
31 | type Result struct {
32 | buf bytes.Buffer
33 | err error
34 | str *string
35 | }
36 |
37 | func RunCmd(cmdLine string, timeout ...time.Duration) *Result {
38 | cmd := exec.Command(cmdArg[0], cmdArg[1], cmdLine)
39 |
40 | var re = new(Result)
41 | cmd.Stdout = &re.buf
42 | cmd.Stderr = &re.buf
43 | cmd.Env = os.Environ()
44 | re.err = cmd.Start()
45 | if re.err != nil {
46 | return re
47 | }
48 | if len(timeout) == 0 || timeout[0] <= 0 {
49 | re.err = cmd.Wait()
50 | return re
51 | }
52 | timer := time.NewTimer(timeout[0])
53 | done := make(chan error)
54 | //启动协程去执行命令
55 | go func() { done <- cmd.Wait() }()
56 | select {
57 | case re.err = <-done:
58 | //正常执行完,或者出现异常
59 | timer.Stop()
60 | case <-timer.C:
61 | //时间到期
62 | if err := cmd.Process.Kill(); err != nil {
63 | re.err = fmt.Errorf("command timed out and killing process fail: %s", err.Error())
64 | } else {
65 | // wait for the command to return after killing it
66 | <-done
67 | re.err = errors.New("command timed out")
68 | }
69 | }
70 | return re
71 | }
72 |
73 | func (r *Result) Err() error {
74 | return r.err
75 | }
76 |
77 | // String returns the exec log.
78 | func (r *Result) String() string {
79 | if r.str == nil {
80 | b := bytes.TrimSpace(r.buf.Bytes())
81 | r.str = (*string)(unsafe.Pointer(&b))
82 | }
83 | return *r.str
84 | }
85 |
--------------------------------------------------------------------------------
/utils/copyutils.go:
--------------------------------------------------------------------------------
1 | package utils
2 |
3 | import (
4 | "io"
5 | "os"
6 | "path/filepath"
7 | )
8 |
9 | func Copy(source, target string) (written int64, err error) {
10 |
11 | // source file
12 | file1, err := os.Open(source)
13 | if err != nil {
14 | return
15 | }
16 |
17 | // create target file
18 |
19 | if _, err := os.Stat(filepath.Dir(target)); os.IsNotExist(err) {
20 | os.MkdirAll(filepath.Dir(target), os.ModeDir|0755)
21 | }
22 | file2, err := os.OpenFile(target, os.O_RDWR|os.O_CREATE, os.ModePerm)
23 | if err != nil {
24 | return
25 | }
26 |
27 | defer file1.Close()
28 | defer file2.Close()
29 | written, err = io.Copy(file2, file1)
30 | if err != nil {
31 | return
32 | }
33 | return
34 | }
35 |
--------------------------------------------------------------------------------
/utils/iputils.go:
--------------------------------------------------------------------------------
1 | package utils
2 |
3 | import (
4 | "fmt"
5 | "net"
6 | )
7 |
8 | // PortAvailable whether the port can be used
9 | func PortAvailable(port int) bool {
10 |
11 | listener, err := net.Listen("tcp", fmt.Sprintf(":%d", port))
12 | if err != nil {
13 | return false
14 | }
15 |
16 | defer listener.Close()
17 | return true
18 |
19 | }
20 |
--------------------------------------------------------------------------------
/utils/osutils.go:
--------------------------------------------------------------------------------
1 | package utils
2 |
3 | import (
4 | "runtime"
5 | )
6 |
7 | type OsPlatform int
8 |
9 | const (
10 | Other OsPlatform = iota
11 | Win
12 | Mac
13 | Linux
14 | )
15 |
16 | func OsEnv() OsPlatform {
17 | // get runtime op platform
18 | op := runtime.GOOS
19 |
20 | if op == "windows" {
21 | return Win
22 | } else if op == "darwin" {
23 | return Mac
24 | } else if op == "linux" {
25 | return Linux
26 | }
27 | return Other
28 | }
29 |
--------------------------------------------------------------------------------
/utils/osutils_test.go:
--------------------------------------------------------------------------------
1 | package utils
2 |
3 | import "testing"
4 |
5 | func TestOsEnv(t *testing.T) {
6 | println(OsEnv())
7 | }
8 |
--------------------------------------------------------------------------------
/utils/pathutil.go:
--------------------------------------------------------------------------------
1 | package utils
2 |
3 | import (
4 | "fmt"
5 | "github.com/spf13/cobra"
6 | "os"
7 | "path/filepath"
8 | )
9 |
10 | //相对路径转换成绝对路径进行处理
11 |
12 | func ToAbsPath(path string) string {
13 |
14 | if !filepath.IsAbs(path) {
15 | absDir, err := filepath.Abs(path)
16 | if err != nil {
17 | cobra.CheckErr(err)
18 | }
19 | path = absDir
20 | }
21 | return path
22 | }
23 |
24 | func MkdirIfNotExist(dir string) {
25 |
26 | if _, e := os.Stat(dir); os.IsNotExist(e) {
27 | err := os.MkdirAll(dir, os.ModePerm)
28 | if err != nil {
29 | panic(fmt.Sprintf("mkdir failed![%v]\n", err))
30 | }
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/utils/sql2objtypeutils.go:
--------------------------------------------------------------------------------
1 | package utils
2 |
3 | //sql->java
4 | func mysqlToJavaTypeMap() map[string]string {
5 | mysqlJavaTypeMap := map[string]string{
6 | "bigint": "Long",
7 | "int": "Integer",
8 | "tinyint": "Integer",
9 | "smallint": "Integer",
10 | "mediumint": "Integer",
11 | "integer": "Integer",
12 | //小数
13 | "float": "Float",
14 | "double": "Double",
15 | "decimal": "Double",
16 | //bool
17 | "bit": "Boolean",
18 | //字符串
19 | "char": "String",
20 | "varchar": "String",
21 | "tinytext": "String",
22 | "text": "String",
23 | "mediumtext": "String",
24 | "longtext": "String",
25 | //日期
26 | "date": "Date",
27 | "datetime": "Date",
28 | "timestamp": "Date"}
29 | return mysqlJavaTypeMap
30 | }
31 |
32 | //sql->go
33 | func mysqlToGoTypeMap() map[string]string {
34 | mysqlJavaTypeMap := map[string]string{
35 | "bigint": "int",
36 | "int": "int",
37 | "tinyint": "int64",
38 | "smallint": "uint",
39 | "mediumint": "uint",
40 | "integer": "uint",
41 | //小数
42 | "float": "float64",
43 | "double": "float64",
44 | "decimal": "string",
45 | //bool
46 | "bit": "bool",
47 | //字符串
48 | "varbinary": "string", //二进制流
49 | "char": "string",
50 | "varchar": "string",
51 | "tinytext": "string",
52 | "text": "string",
53 | "mediumtext": "string",
54 | "longtext": "string",
55 | //日期
56 | "date": "time.Time",
57 | "datetime": "time.Time",
58 | "timestamp": "time.Time"}
59 | return mysqlJavaTypeMap
60 | }
61 |
62 | func SqlToJavaType(sqlType string) string {
63 | return mysqlToJavaTypeMap()[sqlType]
64 | }
65 |
66 | func SqlToGoType(sqlType string) string {
67 | return mysqlToGoTypeMap()[sqlType]
68 | }
69 |
--------------------------------------------------------------------------------
/utils/stringutils.go:
--------------------------------------------------------------------------------
1 | package utils
2 |
3 | import (
4 | "strings"
5 | "unicode"
6 | )
7 |
8 | // 单词全部转化为大写
9 | func ToUpper(s string) string {
10 | return strings.ToUpper(s)
11 | }
12 |
13 | // 单词全部转化为小写
14 | func ToLower(s string) string {
15 | return strings.ToLower(s)
16 | }
17 |
18 | // 下划线单词转为大写驼峰单词
19 | func UderscoreToUpperCamelCase(s string) string {
20 | s = strings.Replace(s, "_", " ", -1)
21 | s = strings.Title(s)
22 | return strings.Replace(s, " ", "", -1)
23 | }
24 |
25 | // 下划线单词转为小写驼峰单词
26 | func UderscoreToLowerCamelCase(s string) string {
27 | s = UderscoreToUpperCamelCase(s)
28 | return string(unicode.ToLower(rune(s[0]))) + s[1:]
29 | return s
30 | }
31 |
32 | // 驼峰单词转下划线单词
33 | func CamelCaseToUdnderscore(s string) string {
34 | var output []rune
35 | for i, r := range s {
36 | if i == 0 {
37 | output = append(output, unicode.ToLower(r))
38 | } else {
39 | if unicode.IsUpper(r) {
40 | output = append(output, '_')
41 | }
42 |
43 | output = append(output, unicode.ToLower(r))
44 | }
45 | }
46 | return string(output)
47 | }
48 |
49 | // removeJSONTags 函数用于去掉JSON文本中的 ```json``` 标签
50 | func RemoveJSONTags(jsonText string) string {
51 | // 去掉开头的 ```json```
52 | jsonText = strings.TrimPrefix(jsonText, "```json")
53 | // 去掉开头可能存在的换行符
54 | jsonText = strings.TrimLeft(jsonText, "\n")
55 | // 去掉结尾的 ```
56 | jsonText = strings.TrimSuffix(jsonText, "```")
57 | // 去掉结尾可能存在的换行符
58 | jsonText = strings.TrimRight(jsonText, "\n")
59 | return jsonText
60 | }
61 |
--------------------------------------------------------------------------------
/utils/tplfunction.go:
--------------------------------------------------------------------------------
1 | package utils
2 |
3 | import (
4 | "strings"
5 | "text/template"
6 | )
7 |
8 | // TemplateFunc 自定义的函数,可以在模板渲染时使用
9 | // 增加了模板的灵活度
10 | var TemplateFunc = template.FuncMap{
11 | // 将第一个字母大写
12 | "upperFirst": strings.Title,
13 | }
14 |
--------------------------------------------------------------------------------