From fa6dbeaaacef5e0f63befffcdd96b6be9736df12 Mon Sep 17 00:00:00 2001 From: Ruslan Popov Date: Sat, 29 Mar 2025 20:09:41 +0300 Subject: [PATCH] Application --- go.mod | 7 ++ go.sum | 10 +++ src/main.go | 208 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 225 insertions(+) create mode 100644 go.mod create mode 100644 go.sum create mode 100644 src/main.go diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..c2b1838 --- /dev/null +++ b/go.mod @@ -0,0 +1,7 @@ +module src + +go 1.24.0 + +require github.com/go-ini/ini v1.67.0 + +require github.com/stretchr/testify v1.10.0 // indirect diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..319db2f --- /dev/null +++ b/go.sum @@ -0,0 +1,10 @@ +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/go-ini/ini v1.67.0 h1:z6ZrTEZqSWOTyH2FlglNbNgARyHG8oLW9gMELqKr06A= +github.com/go-ini/ini v1.67.0/go.mod h1:ByCAeIL28uOIIG0E3PJtZPDL8WnHpFKFOtgjp+3Ies8= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= +github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/src/main.go b/src/main.go new file mode 100644 index 0000000..5535399 --- /dev/null +++ b/src/main.go @@ -0,0 +1,208 @@ +package main + +import ( + "flag" + "fmt" + "log" + "os" + "os/exec" + "regexp" + "strconv" + "strings" + + "github.com/go-ini/ini" +) + +type BumpConfig struct { + CurrentVersion string + Commit bool + Tag bool + TagName string + Parse string + Serialize string + Message string + FilePaths []string +} + +func loadConfig(configPath string) (*ini.File, error) { + return ini.Load(configPath) +} + +func getBumpConfig(cfg *ini.File) (*BumpConfig, error) { + sec, err := cfg.GetSection("bumpversion") + if err != nil { + return nil, fmt.Errorf("missing [bumpversion] section: %w", err) + } + bc := &BumpConfig{ + CurrentVersion: sec.Key("current_version").String(), + Commit: sec.Key("commit").MustBool(false), + Tag: sec.Key("tag").MustBool(false), + TagName: sec.Key("tag_name").String(), + Parse: sec.Key("parse").String(), + Serialize: strings.TrimSpace(sec.Key("serialize").String()), + Message: sec.Key("message").String(), + } + // Получим список файлов для обновления из секций, начинающихся с "bumpversion:file:" + for _, s := range cfg.Sections() { + if strings.HasPrefix(s.Name(), "bumpversion:file:") { + // Имя файла после последнего двоеточия: + parts := strings.SplitN(s.Name(), ":", 3) + if len(parts) == 3 { + filePath := strings.TrimSpace(parts[2]) + bc.FilePaths = append(bc.FilePaths, filePath) + } + } + } + return bc, nil +} + +// bumpVersion парсит current_version, увеличивает нужную часть и возвращает новую версию. +func bumpVersion(bc *BumpConfig, part string) (string, error) { + re, err := regexp.Compile(bc.Parse) + if err != nil { + return "", fmt.Errorf("failed to compile parse regex: %w", err) + } + matches := re.FindStringSubmatch(bc.CurrentVersion) + if matches == nil { + return "", fmt.Errorf("current version %s does not match parse pattern", bc.CurrentVersion) + } + // Создадим карту групп по именам + groupNames := re.SubexpNames() + var major, minor, patch int + for i, name := range groupNames { + switch name { + case "major": + major, err = strconv.Atoi(matches[i]) + if err != nil { + return "", err + } + case "minor": + minor, err = strconv.Atoi(matches[i]) + if err != nil { + return "", err + } + case "patch": + patch, err = strconv.Atoi(matches[i]) + if err != nil { + return "", err + } + } + } + switch part { + case "major": + major++ + minor = 0 + patch = 0 + case "minor": + minor++ + patch = 0 + case "patch": + patch++ + default: + return "", fmt.Errorf("unknown bump part: %s", part) + } + newVersion := bc.Serialize + newVersion = strings.ReplaceAll(newVersion, "{major}", fmt.Sprintf("%d", major)) + newVersion = strings.ReplaceAll(newVersion, "{minor}", fmt.Sprintf("%d", minor)) + newVersion = strings.ReplaceAll(newVersion, "{patch}", fmt.Sprintf("%d", patch)) + return newVersion, nil +} + +func updateFiles(filePaths []string, oldVersion, newVersion string) { + for _, path := range filePaths { + data, err := os.ReadFile(path) + if err != nil { + log.Printf("Unable to read file %s: %v", path, err) + continue + } + newData := strings.ReplaceAll(string(data), oldVersion, newVersion) + if err := os.WriteFile(path, []byte(newData), 0644); err != nil { + log.Printf("Unable to write file %s: %v", path, err) + } else { + log.Printf("Updated file: %s", path) + } + } +} + +func gitCommitAndTag(currentVersion, newVersion string, bc *BumpConfig) error { + commitMsg := strings.ReplaceAll(bc.Message, "{current_version}", currentVersion) + commitMsg = strings.ReplaceAll(commitMsg, "{new_version}", newVersion) + if bc.Commit { + if err := exec.Command("git", "add", ".").Run(); err != nil { + return fmt.Errorf("git add error: %w", err) + } + if err := exec.Command("git", "commit", "-m", commitMsg).Run(); err != nil { + return fmt.Errorf("git commit error: %w", err) + } + log.Printf("Committed with message: %s", commitMsg) + } + if bc.Tag { + tagName := strings.ReplaceAll(bc.TagName, "{new_version}", newVersion) + if err := exec.Command("git", "tag", tagName).Run(); err != nil { + return fmt.Errorf("git tag error: %w", err) + } + log.Printf("Created git tag: %s", tagName) + } + return nil +} + +func updateConfigFile(configPath string, newVersion string) error { + cfg, err := ini.Load(configPath) + if err != nil { + return err + } + cfg.Section("bumpversion").Key("current_version").SetValue(newVersion) + return cfg.SaveTo(configPath) +} + +// Версия приложения +const ( + AppName = "BumpVersion" + AppVersion = "0.1.0" +) + +func main() { + // Проверяем аргументы командной строки + args := os.Args[1:] + if len(args) > 0 && strings.ToLower(args[0]) == "--version" { + fmt.Printf("%s version %s\n", AppName, AppVersion) + return + } + + // Печатаем название и версию при старте + fmt.Printf("Starting %s version %s\n", AppName, AppVersion) + + part := flag.String("part", "patch", "Part to bump: major, minor, patch") + configPath := flag.String("config", ".bumpversion.cfg", "Path to config file") + flag.Parse() + + cfgFile, err := loadConfig(*configPath) + if err != nil { + log.Fatalf("Error loading config: %v", err) + } + + bc, err := getBumpConfig(cfgFile) + if err != nil { + log.Fatalf("Error reading bumpversion configuration: %v", err) + } + + fmt.Printf("Current version: %s\n", bc.CurrentVersion) + newVersion, err := bumpVersion(bc, *part) + if err != nil { + log.Fatalf("Error bumping version: %v", err) + } + fmt.Printf("New version: %s\n", newVersion) + + // Обновляем файлы, указанные в конфигурации + updateFiles(bc.FilePaths, bc.CurrentVersion, newVersion) + + // Выполняем git commit и tag, если требуется + if err := gitCommitAndTag(bc.CurrentVersion, newVersion, bc); err != nil { + log.Printf("Git commit/tag error: %v", err) + } + + // Обновляем конфигурационный файл + if err := updateConfigFile(*configPath, newVersion); err != nil { + log.Printf("Error updating config file: %v", err) + } +}