Skip to content

Commit

Permalink
config 的 cmd、cli 分离
Browse files Browse the repository at this point in the history
  • Loading branch information
YHYJ committed Apr 11, 2024
1 parent 4dc9801 commit 919130b
Show file tree
Hide file tree
Showing 9 changed files with 344 additions and 306 deletions.
102 changes: 4 additions & 98 deletions cli/clone.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,116 +10,22 @@ Description: 子命令 'clone' 的实现
package cli

import (
"bufio"
"os"
"path/filepath"
"regexp"
"strings"

"github.com/gookit/color"
"github.com/pelletier/go-toml"
"github.com/yhyj/curator/general"
)

// updateGitConfig 更新 .git/config 文件
//
// 参数:
// - configFile: .git/config 文件路径
// - originalLink: 需要替换的原始链接
// - newLink: 替换上去的新链接
//
// 返回:
// - 错误信息
func updateGitConfig(configFile, originalLink, newLink string) error {
// 以读写模式打开文件
file, err := os.OpenFile(configFile, os.O_RDWR, os.ModePerm)
if err != nil {
return err
}
defer file.Close()

// 读取文件
scanner := bufio.NewScanner(file) // 创建一个扫描器来读取文件内容
var lines []string // 存储读取到的行

// 正则匹配(主仓库和子模块的匹配规则一样)
regexPattern := `.*url\s*=\s*.*[:\/].*\.git` // 定义正则匹配规则
regex := regexp.MustCompile(regexPattern) // 创建正则表达式
matched := false // 是否匹配到,用于限制只匹配一次

// 需要新增的行
pushUrl1 := "" // 第一行 pushurl
pushUrl2 := "" // 第二行 pushurl

// 逐行读取文件内容
for scanner.Scan() {
line := scanner.Text()

// 检索一次模糊匹配的行
if !matched && regex.MatchString(line) {
// 第一次匹配:将可能存在的 "ssh://" 删除,并在"/"多于1个时将第1个替换为":"
// 该次匹配是专对子模块的 .git/config 的处理
line = strings.Replace(line, "ssh://", "", 1)
if strings.Count(line, "/") >= 2 {
line = strings.Replace(line, "/", ":", 1)
}
lines = append(lines, line)
// 第二次匹配:创建2行 "pushurl"
// 该次匹配是对于 .git/config 的通用处理
pushUrl1 = strings.ReplaceAll(line, "url", "pushurl")
pushUrl2 = strings.ReplaceAll(pushUrl1, originalLink, newLink)
lines = append(lines, pushUrl1)
lines = append(lines, pushUrl2)
matched = true
} else {
lines = append(lines, line)
}
}

// 将修改后的内容写回文件
file.Truncate(0) // 清空文件内容
file.Seek(0, 0) // 移动光标到文件开头
writer := bufio.NewWriter(file)
for _, line := range lines {
_, _ = writer.WriteString(line + "\n")
}
writer.Flush()

return nil
}

// runScript 运行 shell 脚本
//
// 参数:
// - filePath: 脚本所在目录
// - scriptName: 脚本名
//
// 返回:
// - 错误信息
func runScript(filePath, scriptName string) error {
// 判断是否存在脚本文件,存在则运行脚本,不存在则忽略
if general.FileExist(filepath.Join(filePath, scriptName)) {
// 进到指定目录
if err := os.Chdir(filePath); err != nil {
return err
}
// 运行脚本
bashArgs := []string{scriptName}
if err := general.RunCommand("bash", bashArgs); err != nil {
return err
}
}
return nil
}

// RollingCloneRepos 遍历克隆远端仓库到本地
//
// 参数:
// - configTree: 解析 toml 配置文件得到的配置树
// - source: 远端仓库源,支持 'github' 和 'gitea',默认为 'github'
func RollingCloneRepos(configTree *toml.Tree, source string) {
// 获取配置项
config, err := LoadConfigToStruct(configTree)
config, err := general.LoadConfigToStruct(configTree)
if err != nil {
color.Error.Println(err)
return
Expand Down Expand Up @@ -202,13 +108,13 @@ func RollingCloneRepos(configTree *toml.Tree, source string) {
var errList []string // 使用一个 Slice 存储所有错误信息以美化输出
// 执行脚本
for _, scriptName := range config.Script.NameList {
if err := runScript(repoPath, scriptName); err != nil {
if err := general.RunScript(repoPath, scriptName); err != nil {
errList = append(errList, "Run script "+scriptName+": "+err.Error())
}
}
// 处理主仓库的配置文件 .git/config
configFile := filepath.Join(repoPath, ".git", "config")
if err = updateGitConfig(configFile, repoSource["originalLink"], repoSource["newLink"]); err != nil {
if err = general.ModifyGitConfig(configFile, repoSource["originalLink"], repoSource["newLink"]); err != nil {
errList = append(errList, "Update repository git config (main): "+err.Error())
}
// 获取主仓库的 worktree
Expand Down Expand Up @@ -250,7 +156,7 @@ func RollingCloneRepos(configTree *toml.Tree, source string) {
color.Printf("%s%s %s %s\n", strings.Repeat(" ", length), joiner, general.SubmoduleFlag, general.FgMagentaText(submodule.Config().Name))
// 处理子模块的配置文件 .git/modules/<submodule>/config
configFile := filepath.Join(repoPath, ".git", "modules", submodule.Config().Name, "config")
if err = updateGitConfig(configFile, repoSource["originalLink"], repoSource["newLink"]); err != nil {
if err = general.ModifyGitConfig(configFile, repoSource["originalLink"], repoSource["newLink"]); err != nil {
errList = append(errList, "Update repository git config (submodule): "+err.Error())
}
}
Expand Down
207 changes: 53 additions & 154 deletions cli/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,174 +10,73 @@ Description: 子命令 'config' 的实现
package cli

import (
"fmt"
"os"
"path/filepath"
"strings"

"github.com/pelletier/go-toml"
"github.com/gookit/color"
"github.com/yhyj/curator/general"
)

// 用于转换 Toml 配置树的结构体
type Config struct {
Git GitConfig `toml:"git"`
Script ScriptConfig `toml:"script"`
SSH SSHConfig `toml:"ssh"`
Storage StorageConfig `toml:"storage"`
}
type GitConfig struct {
GithubUrl string `toml:"github_url"`
GithubUsername string `toml:"github_username"`
GiteaUrl string `toml:"gitea_url"`
GiteaUsername string `toml:"gitea_username"`
Repos []string `toml:"repos"`
}
type ScriptConfig struct {
NameList []string `toml:"name_list"`
}
type SSHConfig struct {
RsaFile string `toml:"rsa_file"`
}
type StorageConfig struct {
Path string `toml:"path"`
}

// isTomlFile 检测文件是不是 toml 文件
// CreateConfigFile 创建配置文件
//
// 参数:
// - filePath: 待检测文件路径
//
// 返回:
// - 是 toml 文件返回 true,否则返回 false
func isTomlFile(filePath string) bool {
if strings.HasSuffix(filePath, ".toml") {
return true
}
return false
}
// - configFile: 配置文件路径
// - reWrite: 是否覆写
func CreateConfigFile(configFile string, reWrite bool) {
// 检查配置文件是否存在
fileExist := general.FileExist(configFile)

// GetTomlConfig 读取 toml 配置文件
//
// 参数:
// - filePath: toml 配置文件路径
//
// 返回:
// - toml 配置树
// - 错误信息
func GetTomlConfig(filePath string) (*toml.Tree, error) {
if !general.FileExist(filePath) {
return nil, fmt.Errorf("Open %s: no such file or directory", filePath)
}
if !isTomlFile(filePath) {
return nil, fmt.Errorf("Open %s: is not a toml file", filePath)
}
tree, err := toml.LoadFile(filePath)
if err != nil {
return nil, err
// 检测并创建配置文件
if fileExist {
if reWrite {
if err := general.DeleteFile(configFile); err != nil {
color.Error.Println(err)
return
}
if err := general.CreateFile(configFile); err != nil {
color.Error.Println(err)
return
}
_, err := general.WriteTomlConfig(configFile)
if err != nil {
color.Error.Println(err)
return
}
color.Printf("%s %s: %s\n", general.FgWhiteText("Create"), general.PrimaryText(configFile), general.SuccessText("file overwritten"))
} else {
color.Printf("%s %s: %s %s\n", general.FgWhiteText("Create"), general.PrimaryText(configFile), general.WarnText("file exists"), general.SecondaryText("(use --force to overwrite)"))
}
} else {
if err := general.CreateFile(configFile); err != nil {
color.Error.Println(err)
return
}
_, err := general.WriteTomlConfig(configFile)
if err != nil {
color.Error.Println(err)
return
}
color.Printf("%s %s: %s\n", general.FgWhiteText("Create"), general.PrimaryText(configFile), general.SuccessText("file created"))
}
return tree, nil
}

// LoadConfigToStruct 将 Toml 配置树加载到结构体
// PrintConfigFile 打印配置文件内容
//
// 参数:
// - configTree: 解析 toml 配置文件得到的配置树
//
// 返回:
// - 结构体
// - 错误信息
func LoadConfigToStruct(configTree *toml.Tree) (*Config, error) {
var config Config
if err := configTree.Unmarshal(&config); err != nil {
return nil, err
}
return &config, nil
}
// - configFile: 配置文件路径
func PrintConfigFile(configFile string) {
// 检查配置文件是否存在
fileExist := general.FileExist(configFile)

// WriteTomlConfig 写入 toml 配置文件
//
// 参数:
// - filePath: toml 配置文件路径
//
// 返回:
// - 写入的字节数
// - 错误信息
func WriteTomlConfig(filePath string) (int64, error) {
// 根据系统不同决定某些参数
var (
scriptNameList = []string{} // 脚本名列表
cfgFileNotFoundMessage = "Configuration file not found (use --create to create a configuration file)" // 配置文件不存在
)
if general.Platform == "linux" {
scriptNameList = []string{
"create-hook-link.sh",
}
} else if general.Platform == "darwin" {
scriptNameList = []string{
"create-hook-link.sh",

if fileExist {
configTree, err := general.GetTomlConfig(configFile)
if err != nil {
color.Error.Println(err)
} else {
color.Println(general.NoteText(configTree))
}
} else if general.Platform == "windows" {
}
// 定义一个 map[string]interface{} 类型的变量并赋值
exampleConf := map[string]interface{}{
"ssh": map[string]interface{}{
"rsa_file": filepath.Join(general.UserInfo.HomeDir, ".ssh", "id_rsa"),
},
"storage": map[string]interface{}{
"path": filepath.Join(general.UserInfo.HomeDir, "Documents", "Repos"),
},
"script": map[string]interface{}{
"name_list": scriptNameList,
},
"git": map[string]interface{}{
"github_url": "github.com",
"github_username": "YHYJ",
"gitea_url": "git.yj1516.top",
"gitea_username": "YJ",
"repos": []string{
"checker",
"curator",
"eniac",
"kbdstage",
"LearningCenter",
"LogWrapper",
"manager",
"Modules",
"MyBlogs",
"MyDocker",
"MyDockerfile",
"MyRaspberry",
"MyShell",
"MyWiki",
"rolling",
"scleaner",
"skynet",
"Sniffer",
"System",
"Test",
"trash",
"www",
"YHYJ",
},
},
}
// 检测配置文件是否存在
if !general.FileExist(filePath) {
return 0, fmt.Errorf("Open %s: no such file or directory", filePath)
}
// 检测配置文件是否是 toml 文件
if !isTomlFile(filePath) {
return 0, fmt.Errorf("Open %s: is not a toml file", filePath)
}
// 把 exampleConf 转换为 *toml.Tree 类型
tree, err := toml.TreeFromMap(exampleConf)
if err != nil {
return 0, err
}
// 打开一个文件并获取 io.Writer 接口
file, err := os.OpenFile(filePath, os.O_RDWR|os.O_CREATE, 0644)
if err != nil {
return 0, err
} else {
color.Error.Println(cfgFileNotFoundMessage)
}
return tree.WriteTo(file)
}
2 changes: 1 addition & 1 deletion cli/pull.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ func RollingPullRepos(configTree *toml.Tree, source string) {
// storagePath := conf.Get("storage.path").(string)
// repoNames := conf.Get("git.repos").([]interface{})
// 获取配置项
config, err := LoadConfigToStruct(configTree)
config, err := general.LoadConfigToStruct(configTree)
if err != nil {
color.Error.Println(err)
return
Expand Down
3 changes: 2 additions & 1 deletion cmd/clone.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import (
"github.com/gookit/color"
"github.com/spf13/cobra"
"github.com/yhyj/curator/cli"
"github.com/yhyj/curator/general"
)

// cloneCmd represents the clone command
Expand All @@ -27,7 +28,7 @@ var cloneCmd = &cobra.Command{
sourceFlag, _ := cmd.Flags().GetString("source")

// 读取配置文件
configTree, err := cli.GetTomlConfig(cfgFile)
configTree, err := general.GetTomlConfig(cfgFile)
if err != nil {
color.Error.Println(err)
return
Expand Down
Loading

0 comments on commit 919130b

Please sign in to comment.