tools/go-changelog/note.go (146 lines of code) (raw):

// Copyright (c) HashiCorp, Inc. // SPDX-License-Identifier: MPL-2.0 package changelog import ( "fmt" "regexp" "sort" "strings" "time" ) type Note struct { Type string Body string Issue string Hash string Date time.Time } var TypeValues = []string{ "enhancement", "bug", "note", "none", "new-resource", "new-datasource", "deprecation", "breaking-change", } var textInBodyREs = []*regexp.Regexp{ regexp.MustCompile("(?ms)^```release-note\r?\n(?P<note>.+?)\r?\n```"), regexp.MustCompile("(?ms)^```releasenote\r?\n(?P<note>.+?)\r?\n```"), regexp.MustCompile("(?ms)^```release-note:(?P<type>[^\r\n]*)\r?\n?(?P<note>.*?)\r?\n?```"), regexp.MustCompile("(?ms)^```releasenote:(?P<type>[^\r\n]*)\r?\n?(?P<note>.*?)\r?\n?```"), } var enhancementOrBugFixRegexp = regexp.MustCompile(`^[a-z0-9_]+: .+$`) var newResourceOrDatasourceRegexp = regexp.MustCompile("`google_[a-z0-9_]+`") var newlineRegexp = regexp.MustCompile(`\n`) func NotesFromEntry(entry Entry) []Note { var res []Note for _, re := range textInBodyREs { matches := re.FindAllStringSubmatch(entry.Body, -1) if len(matches) == 0 { continue } for _, match := range matches { note := "" typ := "" for i, name := range re.SubexpNames() { switch name { case "note": note = match[i] case "type": typ = match[i] } if note != "" && typ != "" { break } } typ = strings.TrimSpace(typ) if note == "" && typ == "" { continue } res = append(res, Note{ Type: typ, Body: note, Issue: entry.Issue, Hash: entry.Hash, Date: entry.Date, }) } } sort.Slice(res, SortNotes(res)) return res } // Validates if a changelog note is properly formatted func (n *Note) Validate() *EntryValidationError { typ := n.Type content := n.Body if !TypeValid(typ) { return &EntryValidationError{ message: fmt.Sprintf("unknown changelog types %v: please use only the configured changelog entry types: %v", typ, content), Code: EntryErrorUnknownTypes, Details: map[string]interface{}{ "type": typ, "note": content, }, } } if newlineRegexp.MatchString(content) { return &EntryValidationError{ message: fmt.Sprintf("multiple lines are found in changelog entry %v: Please only have one CONTENT line per release note block. Use multiple blocks if there are multiple related changes in a single PR.", content), Code: EntryErrorMultipleLines, Details: map[string]interface{}{ "type": typ, "note": content, }, } } if typ == "new-resource" || typ == "new-datasource" { if !newResourceOrDatasourceRegexp.MatchString(content) { return &EntryValidationError{ message: fmt.Sprintf("invalid resource/datasource format in changelog entry %v: Please follow format in https://googlecloudplatform.github.io/magic-modules/contribute/release-notes/#type-specific-guidelines-and-examples", content), Code: EntryErrorInvalidNewReourceOrDatasourceFormat, Details: map[string]interface{}{ "type": typ, "note": content, }, } } } if typ == "enhancement" || typ == "bug" { if !enhancementOrBugFixRegexp.MatchString(content) { return &EntryValidationError{ message: fmt.Sprintf("invalid enhancement/bug fix format in changelog entry %v: Please follow format in https://googlecloudplatform.github.io/magic-modules/contribute/release-notes/#type-specific-guidelines-and-examples", content), Code: EntryErrorInvalidEnhancementOrBugFixFormat, Details: map[string]interface{}{ "type": typ, "note": content, }, } } } return nil } func SortNotes(res []Note) func(i, j int) bool { return func(i, j int) bool { if res[i].Type < res[j].Type { return true } else if res[j].Type < res[i].Type { return false } else if res[i].Body < res[j].Body { return true } else if res[j].Body < res[i].Body { return false } else if res[i].Issue < res[j].Issue { return true } else if res[j].Issue < res[i].Issue { return false } return false } } func TypeValid(Type string) bool { for _, a := range TypeValues { if a == Type { return true } } return false }