package service import ( "bufio" "fmt" "os" "path/filepath" "strings" ) // ChangelogEntry 变更日志条目 type ChangelogEntry struct { Version string `json:"version"` Date string `json:"date"` Changes map[string][]string `json:"changes"` } // ParseChangelog 解析 CHANGELOG.md 文件 func ParseChangelog() ([]ChangelogEntry, error) { // 获取项目根目录 rootDir := os.Getenv("PROJECT_ROOT") if rootDir == "" { // 使用二进制文件所在目录作为基准 execPath, err := os.Executable() if err == nil { rootDir = filepath.Dir(execPath) } } changelogPath := filepath.Join(rootDir, "CHANGELOG.md") file, err := os.Open(changelogPath) if err != nil { return nil, fmt.Errorf("打开 CHANGELOG.md 失败: %w", err) } defer file.Close() var entries []ChangelogEntry var currentEntry *ChangelogEntry var currentCategory string scanner := bufio.NewScanner(file) for scanner.Scan() { line := scanner.Text() // 匹配版本号行 ## [1.4.0] - 2026-03-23 if strings.HasPrefix(line, "## [") && strings.Contains(line, "]") { // 保存前一个 entry if currentEntry != nil { entries = append(entries, *currentEntry) } // 解析版本号和日期 version, date := parseVersionLine(line) if version != "" && version != "Unreleased" { currentEntry = &ChangelogEntry{ Version: version, Date: date, Changes: make(map[string][]string), } currentCategory = "" } else { currentEntry = nil } continue } // 跳过 Unreleased 和其他非版本行 if currentEntry == nil { continue } // 匹配分类行 ### 新增、### 优化等 if strings.HasPrefix(line, "### ") { currentCategory = strings.TrimPrefix(line, "### ") if currentEntry.Changes[currentCategory] == nil { currentEntry.Changes[currentCategory] = []string{} } continue } // 匹配项目行 - 项目描述 if strings.HasPrefix(line, "- ") && currentCategory != "" { item := strings.TrimPrefix(line, "- ") // 移除加粗标记和链接等 item = cleanItem(item) currentEntry.Changes[currentCategory] = append(currentEntry.Changes[currentCategory], item) } } // 保存最后一个 entry if currentEntry != nil { entries = append(entries, *currentEntry) } if err := scanner.Err(); err != nil { return nil, fmt.Errorf("扫描文件失败: %w", err) } return entries, nil } // parseVersionLine 解析版本行 ## [1.4.0] - 2026-03-23 func parseVersionLine(line string) (version, date string) { // 提取版本号 startIdx := strings.Index(line, "[") endIdx := strings.Index(line, "]") if startIdx >= 0 && endIdx > startIdx { version = line[startIdx+1 : endIdx] } // 提取日期 dateStartIdx := strings.LastIndex(line, "- ") + 2 if dateStartIdx > 1 { date = strings.TrimSpace(line[dateStartIdx:]) } return } // cleanItem 清理项目描述(移除加粗标记等) func cleanItem(item string) string { // 移除加粗标记 **text** item = strings.ReplaceAll(item, "**", "") // 移除代码标记 `text` item = strings.ReplaceAll(item, "`", "") return strings.TrimSpace(item) }