code.gitea.io/gitea@v1.19.3/modules/label/parser.go (about)

     1  // Copyright 2023 The Gitea Authors. All rights reserved.
     2  // SPDX-License-Identifier: MIT
     3  
     4  package label
     5  
     6  import (
     7  	"errors"
     8  	"fmt"
     9  	"strings"
    10  
    11  	"code.gitea.io/gitea/modules/options"
    12  
    13  	"gopkg.in/yaml.v3"
    14  )
    15  
    16  type labelFile struct {
    17  	Labels []*Label `yaml:"labels"`
    18  }
    19  
    20  // ErrTemplateLoad represents a "ErrTemplateLoad" kind of error.
    21  type ErrTemplateLoad struct {
    22  	TemplateFile  string
    23  	OriginalError error
    24  }
    25  
    26  // IsErrTemplateLoad checks if an error is a ErrTemplateLoad.
    27  func IsErrTemplateLoad(err error) bool {
    28  	_, ok := err.(ErrTemplateLoad)
    29  	return ok
    30  }
    31  
    32  func (err ErrTemplateLoad) Error() string {
    33  	return fmt.Sprintf("failed to load label template file %q: %v", err.TemplateFile, err.OriginalError)
    34  }
    35  
    36  // LoadTemplateFile loads the label template file by given file name, returns a slice of Label structs.
    37  func LoadTemplateFile(fileName string) ([]*Label, error) {
    38  	data, err := options.Labels(fileName)
    39  	if err != nil {
    40  		return nil, ErrTemplateLoad{fileName, fmt.Errorf("LoadTemplateFile: %w", err)}
    41  	}
    42  
    43  	if strings.HasSuffix(fileName, ".yaml") || strings.HasSuffix(fileName, ".yml") {
    44  		return parseYamlFormat(fileName, data)
    45  	}
    46  	return parseLegacyFormat(fileName, data)
    47  }
    48  
    49  func parseYamlFormat(fileName string, data []byte) ([]*Label, error) {
    50  	lf := &labelFile{}
    51  
    52  	if err := yaml.Unmarshal(data, lf); err != nil {
    53  		return nil, err
    54  	}
    55  
    56  	// Validate label data and fix colors
    57  	for _, l := range lf.Labels {
    58  		l.Color = strings.TrimSpace(l.Color)
    59  		if len(l.Name) == 0 || len(l.Color) == 0 {
    60  			return nil, ErrTemplateLoad{fileName, errors.New("label name and color are required fields")}
    61  		}
    62  		color, err := NormalizeColor(l.Color)
    63  		if err != nil {
    64  			return nil, ErrTemplateLoad{fileName, fmt.Errorf("bad HTML color code '%s' in label: %s", l.Color, l.Name)}
    65  		}
    66  		l.Color = color
    67  	}
    68  
    69  	return lf.Labels, nil
    70  }
    71  
    72  func parseLegacyFormat(fileName string, data []byte) ([]*Label, error) {
    73  	lines := strings.Split(string(data), "\n")
    74  	list := make([]*Label, 0, len(lines))
    75  	for i := 0; i < len(lines); i++ {
    76  		line := strings.TrimSpace(lines[i])
    77  		if len(line) == 0 {
    78  			continue
    79  		}
    80  
    81  		parts, description, _ := strings.Cut(line, ";")
    82  
    83  		color, labelName, ok := strings.Cut(parts, " ")
    84  		if !ok {
    85  			return nil, ErrTemplateLoad{fileName, fmt.Errorf("line is malformed: %s", line)}
    86  		}
    87  
    88  		color, err := NormalizeColor(color)
    89  		if err != nil {
    90  			return nil, ErrTemplateLoad{fileName, fmt.Errorf("bad HTML color code '%s' in line: %s", color, line)}
    91  		}
    92  
    93  		list = append(list, &Label{
    94  			Name:        strings.TrimSpace(labelName),
    95  			Color:       color,
    96  			Description: strings.TrimSpace(description),
    97  		})
    98  	}
    99  
   100  	return list, nil
   101  }
   102  
   103  // LoadTemplateDescription loads the labels from a template file, returns a description string by joining each Label.Name with comma
   104  func LoadTemplateDescription(fileName string) (string, error) {
   105  	var buf strings.Builder
   106  	list, err := LoadTemplateFile(fileName)
   107  	if err != nil {
   108  		return "", err
   109  	}
   110  
   111  	for i := 0; i < len(list); i++ {
   112  		if i > 0 {
   113  			buf.WriteString(", ")
   114  		}
   115  		buf.WriteString(list[i].Name)
   116  	}
   117  	return buf.String(), nil
   118  }