containers/apps/ci-os/packages/forgejo-label/src/config.go

141 lines
3.9 KiB
Go

// src/config.go
package main
import (
"fmt"
"io/ioutil"
"path/filepath"
"regexp"
"gopkg.in/yaml.v3"
)
// LabelerRule represents a rule mapping file patterns to a label area.
type LabelerRule struct {
ChangedFiles []ChangedFile `yaml:"changed-files"`
}
// ChangedFile represents a single file pattern rule.
type ChangedFile struct {
AnyGlobToAnyFile string `yaml:"any-glob-to-any-file"`
}
// LabelerConfig represents the entire labeler configuration.
type LabelerConfig map[string][]LabelerRule
// LoadLabelerConfig loads and parses the labeler.yaml file.
func LoadLabelerConfig(path string) (LabelerConfig, error) {
data, err := ioutil.ReadFile(path)
if err != nil {
return nil, fmt.Errorf("failed to read labeler config file: %w", err)
}
var config LabelerConfig
if err := yaml.Unmarshal(data, &config); err != nil {
return nil, fmt.Errorf("failed to parse labeler config file: %w", err)
}
return config, nil
}
// LoadLabelsConfig loads and parses the labels.yaml file.
func LoadLabelsConfig(path string) ([]ForgejoLabel, error) {
data, err := ioutil.ReadFile(path)
if err != nil {
return nil, fmt.Errorf("failed to read labels config file: %w", err)
}
var labels []ForgejoLabel
if err := yaml.Unmarshal(data, &labels); err != nil {
return nil, fmt.Errorf("failed to parse labels config file: %w", err)
}
return labels, nil
}
// GetLabelPatterns returns a map of label areas to their corresponding file patterns.
func (lc LabelerConfig) GetLabelPatterns() map[string][]string {
patterns := make(map[string][]string)
for area, rules := range lc {
for _, rule := range rules {
for _, cf := range rule.ChangedFiles {
patterns[area] = append(patterns[area], cf.AnyGlobToAnyFile)
}
}
}
return patterns
}
// DetermineLabels determines which labels should be applied based on changed files.
// `changedFiles` is a list of file paths that have been changed.
func DetermineLabels(labelerConfig LabelerConfig, changedFiles []string) ([]string, error) {
patterns := labelerConfig.GetLabelPatterns()
labelSet := make(map[string]struct{})
for area, globs := range patterns {
for _, glob := range globs {
for _, file := range changedFiles {
matched, err := filepath.Match(glob, file)
if err != nil {
return nil, fmt.Errorf("invalid glob pattern '%s': %w", glob, err)
}
if matched {
labelSet[area] = struct{}{}
break // No need to check more files for this pattern
}
}
}
}
var labels []string
for label := range labelSet {
labels = append(labels, label)
}
return labels, nil
}
// ValidateLabelerConfig validates the labeler configuration.
func ValidateLabelerConfig(config LabelerConfig) error {
for area, rules := range config {
if area == "" {
return fmt.Errorf("labeler config contains an empty area")
}
for _, rule := range rules {
for _, cf := range rule.ChangedFiles {
if cf.AnyGlobToAnyFile == "" {
return fmt.Errorf("labeler config for area '%s' contains an empty file pattern", area)
}
}
}
}
return nil
}
// ValidateLabelsConfig validates the labels configuration.
func ValidateLabelsConfig(labels []ForgejoLabel) error {
labelNames := make(map[string]struct{})
for _, label := range labels {
if label.Name == "" {
return fmt.Errorf("labels config contains a label with an empty name")
}
if label.Color == "" {
return fmt.Errorf("labels config contains a label with an empty color")
}
// Optionally, validate color format
if !isValidHexColor(label.Color) {
return fmt.Errorf("label '%s' has an invalid color format: %s", label.Name, label.Color)
}
// Check for duplicate labels
if _, exists := labelNames[label.Name]; exists {
return fmt.Errorf("duplicate label name found: %s", label.Name)
}
labelNames[label.Name] = struct{}{}
}
return nil
}
func isValidHexColor(color string) bool {
match, _ := regexp.MatchString("^([A-Fa-f0-9]{6})$", color)
return match
}