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