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) } }