| | | 1 | | package git |
| | | 2 | | |
| | | 3 | | import ( |
| | | 4 | | "bytes" |
| | | 5 | | "fmt" |
| | | 6 | | "strings" |
| | | 7 | | "text/template" |
| | | 8 | | |
| | | 9 | | "github.com/jedi-knights/go-semantic-release/internal/domain" |
| | | 10 | | "github.com/jedi-knights/go-semantic-release/internal/ports" |
| | | 11 | | ) |
| | | 12 | | |
| | | 13 | | // Compile-time interface compliance check. |
| | | 14 | | var _ ports.TagService = (*TemplateTagService)(nil) |
| | | 15 | | |
| | | 16 | | // TemplateTagService formats and parses tags using Go templates. |
| | | 17 | | type TemplateTagService struct { |
| | | 18 | | repoTemplate string |
| | | 19 | | projectTemplate string |
| | | 20 | | } |
| | | 21 | | |
| | | 22 | | // NewTemplateTagService creates a tag service with configurable templates. |
| | | 23 | | func NewTemplateTagService(repoTemplate, projectTemplate string) *TemplateTagService { |
| | | 24 | | if repoTemplate == "" { |
| | | 25 | | repoTemplate = "v{{.Version}}" |
| | | 26 | | } |
| | | 27 | | if projectTemplate == "" { |
| | | 28 | | projectTemplate = "{{.Project}}/v{{.Version}}" |
| | | 29 | | } |
| | | 30 | | return &TemplateTagService{ |
| | | 31 | | repoTemplate: repoTemplate, |
| | | 32 | | projectTemplate: projectTemplate, |
| | | 33 | | } |
| | | 34 | | } |
| | | 35 | | |
| | | 36 | | type tagData struct { |
| | | 37 | | Project string |
| | | 38 | | Version string |
| | | 39 | | } |
| | | 40 | | |
| | 4 | 41 | | func (s *TemplateTagService) FormatTag(project string, version domain.Version) (string, error) { |
| | 4 | 42 | | tmplStr := s.repoTemplate |
| | 2 | 43 | | if project != "" { |
| | 2 | 44 | | tmplStr = s.projectTemplate |
| | 2 | 45 | | } |
| | | 46 | | |
| | 4 | 47 | | tmpl, err := template.New("tag").Parse(tmplStr) |
| | 0 | 48 | | if err != nil { |
| | 0 | 49 | | return "", fmt.Errorf("parsing tag template: %w", err) |
| | 0 | 50 | | } |
| | | 51 | | |
| | 4 | 52 | | var buf bytes.Buffer |
| | 4 | 53 | | data := tagData{Project: project, Version: version.String()} |
| | 0 | 54 | | if err := tmpl.Execute(&buf, data); err != nil { |
| | 0 | 55 | | return "", fmt.Errorf("executing tag template: %w", err) |
| | 0 | 56 | | } |
| | 4 | 57 | | return buf.String(), nil |
| | | 58 | | } |
| | | 59 | | |
| | 20 | 60 | | func (s *TemplateTagService) ParseTag(tagName string) (string, domain.Version, error) { |
| | 20 | 61 | | // Try project-scoped patterns first. |
| | 20 | 62 | | // Pattern: project/vX.Y.Z |
| | 8 | 63 | | if idx := strings.Index(tagName, "/v"); idx > 0 { |
| | 8 | 64 | | project := tagName[:idx] |
| | 8 | 65 | | ver, err := domain.ParseVersion(tagName[idx+1:]) |
| | 8 | 66 | | if err == nil { |
| | 8 | 67 | | return project, ver, nil |
| | 8 | 68 | | } |
| | | 69 | | } |
| | | 70 | | |
| | | 71 | | // Pattern: project@X.Y.Z |
| | 1 | 72 | | if idx := strings.LastIndex(tagName, "@"); idx > 0 { |
| | 1 | 73 | | project := tagName[:idx] |
| | 1 | 74 | | ver, err := domain.ParseVersion(tagName[idx+1:]) |
| | 1 | 75 | | if err == nil { |
| | 1 | 76 | | return project, ver, nil |
| | 1 | 77 | | } |
| | | 78 | | } |
| | | 79 | | |
| | | 80 | | // Repo-level: vX.Y.Z or X.Y.Z |
| | 11 | 81 | | ver, err := domain.ParseVersion(tagName) |
| | 1 | 82 | | if err != nil { |
| | 1 | 83 | | return "", domain.Version{}, fmt.Errorf("cannot parse tag %q: %w", tagName, err) |
| | 1 | 84 | | } |
| | 10 | 85 | | return "", ver, nil |
| | | 86 | | } |
| | | 87 | | |
| | 3 | 88 | | func (s *TemplateTagService) FindLatestTag(tags []domain.Tag, project string) (*domain.Tag, error) { |
| | 3 | 89 | | var latest *domain.Tag |
| | 3 | 90 | | |
| | 3 | 91 | | for i := range tags { |
| | 15 | 92 | | proj, ver, err := s.ParseTag(tags[i].Name) |
| | 0 | 93 | | if err != nil { |
| | 0 | 94 | | continue |
| | | 95 | | } |
| | 10 | 96 | | if proj != project { |
| | 10 | 97 | | continue |
| | | 98 | | } |
| | | 99 | | |
| | 5 | 100 | | tags[i].Version = ver |
| | 5 | 101 | | tags[i].Project = proj |
| | 5 | 102 | | |
| | 5 | 103 | | if latest == nil || ver.GreaterThan(latest.Version) { |
| | 5 | 104 | | t := tags[i] |
| | 5 | 105 | | latest = &t |
| | 5 | 106 | | } |
| | | 107 | | } |
| | 3 | 108 | | return latest, nil |
| | | 109 | | } |