github.com/arsham/gitrelease@v0.3.2-0.20221207124258-6867180b2c2d/commit/commit.go (about) 1 package commit 2 3 import ( 4 "fmt" 5 "regexp" 6 "strings" 7 ) 8 9 var ( 10 descRe = regexp.MustCompile(`^\s*([[:alpha:]]+!?)\(?([[:alpha:],_-]+)?\)?(!)?:?(.*)`) 11 refRe = regexp.MustCompile(`[[:alpha:]]+\s+#\d+`) 12 ) 13 14 // ItemPrefix is the markdown prefix before each item. 15 var ItemPrefix = "- " 16 17 // A Group is a commit with all of its messages. 18 type Group struct { 19 raw string 20 Verb string 21 Subject string 22 Description string 23 Breaking bool 24 } 25 26 // GroupFromCommit creates a Group object from the given line. 27 func GroupFromCommit(msg string) Group { 28 matches := descRe.FindStringSubmatch(msg) 29 verb := matches[1] 30 subject := matches[2] 31 verbBreak := matches[3] 32 desc := matches[4] 33 34 breaking := false 35 if strings.HasSuffix(verb, "!") || verbBreak != "" { 36 breaking = true 37 verb = strings.TrimSuffix(verb, "!") 38 } 39 40 switch strings.ToLower(verb) { 41 case "ref", "refactor": 42 verb = "Refactor" 43 case "feat", "feature": 44 verb = "Feature" 45 case "fix", "fixed": 46 verb = "Fix" 47 case "chore": 48 verb = "Chore" 49 case "enhance", "enhancements", "enhancement": 50 verb = "Enhancements" 51 case "upgrade": 52 verb = "Upgrades" 53 case "ci": 54 verb = "CI" 55 case "style": 56 verb = "Style" 57 case "docs": 58 verb = "Docs" 59 default: 60 verb = "" 61 } 62 63 if verb == "" { 64 verb = "Misc" 65 } 66 if desc == "" { 67 desc = matches[0] 68 } 69 70 return Group{ 71 raw: msg, 72 Verb: verb, 73 Subject: subject, 74 Description: strings.TrimSpace(desc), 75 Breaking: breaking, 76 } 77 } 78 79 // Section returns a printable line for the section. 80 func (g Group) Section() string { 81 return "### " + upperFirst(g.Verb) 82 } 83 84 // DescriptionString returns a string that is suitable for printing a line in a 85 // Group. 86 func (g Group) DescriptionString() string { 87 subject := g.Subject 88 if strings.EqualFold(subject, "ci") { 89 subject = "CI" 90 } 91 if subject != "" { 92 subjects := strings.Split(subject, ",") 93 for i := range subjects { 94 subjects[i] = upperFirst(subjects[i]) 95 } 96 subject = strings.Join(subjects, ",") 97 subject = "**" + subject + ":** " 98 } 99 100 lines := strings.Split(g.Description, `\n`) 101 refs := make([]string, 0, len(lines)) 102 title := lines[0] 103 for _, line := range lines[1:] { 104 if line == "" { 105 continue 106 } 107 matches := refRe.FindAllStringSubmatch(line, -1) 108 for _, match := range matches { 109 refs = append(refs, match...) 110 } 111 } 112 113 title = strings.TrimPrefix(title, " ") 114 var ref string 115 if len(refs) > 0 { 116 ref = fmt.Sprintf(" (%s)", strings.Join(refs, ", ")) 117 } 118 return fmt.Sprintf("- %s%s%s", subject, upperFirst(title), ref) 119 } 120 121 // ParseGroups parses the lines in the logs and returns them as a string. 122 func ParseGroups(logs []string) string { 123 logs = cleanup(logs) 124 groups := make(map[string][]Group, len(logs)) 125 for _, line := range logs { 126 group := GroupFromCommit(line) 127 groups[group.Verb] = append(groups[group.Verb], group) 128 } 129 130 buf := &strings.Builder{} 131 i := 0 132 for _, desc := range groups { 133 fmt.Fprintln(buf, desc[0].Section()+"\n") 134 for _, line := range desc { 135 fmt.Fprint(buf, line.DescriptionString()) 136 if line.Breaking { 137 fmt.Fprintf(buf, " [**BREAKING CHANGE**]") 138 } 139 fmt.Fprintln(buf, "") 140 } 141 i++ 142 if i < len(groups) { 143 fmt.Fprintf(buf, "\n\n") 144 } 145 } 146 147 str := buf.String() 148 return strings.TrimSuffix(str, "\n") 149 } 150 151 // cleanup returns only the title of the logs. 152 func cleanup(logs []string) []string { 153 ret := make([]string, 0, len(logs)) 154 for _, commit := range logs { 155 items := strings.Split(commit, "\n") 156 item := items[0] 157 breaking := false 158 for _, line := range items[1:] { 159 if strings.Contains(line, "BREAKING CHANGE") { 160 breaking = true 161 } 162 if !strings.Contains(line, "#") { 163 continue 164 } 165 item = fmt.Sprintf("%s (%s)", item, line) 166 } 167 if breaking { 168 item += " [**BREAKING CHANGE**]" 169 } 170 if item == "" { 171 continue 172 } 173 item = strings.TrimPrefix(item, " ") 174 ret = append(ret, item) 175 } 176 return ret 177 } 178 179 // upperFirst makes the first letter of the string an uppercase letter. 180 func upperFirst(s string) string { 181 if s == "" { 182 return "" 183 } 184 return strings.ToUpper(s[:1]) + s[1:] 185 }