├── .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 | ![glog](doc/img/gcmsg.webp) 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 | ![glog](doc/img/glog.webp) 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 | ![sqllog](doc/img/sqllog.webp) 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 | --------------------------------------------------------------------------------