package common import ( "bufio" "errors" "fmt" "io" "regexp" "slices" "strconv" "strings" ) const PrPattern = "PR: %s/%s#%d" type BasicPR struct { Org, Repo string Num int64 } var validOrgAndRepoRx *regexp.Regexp = regexp.MustCompile("^[A-Za-z0-9_-]+$") func parsePrLine(line string) (BasicPR, error) { var ret BasicPR trimmedLine := strings.TrimSpace(line) // min size > 9 -> must fit all parameters in th PrPattern with at least one item per parameter if len(trimmedLine) < 9 || trimmedLine[0:4] != "PR: " { return ret, errors.New("Line too short") } trimmedLine = trimmedLine[4:] org := strings.SplitN(trimmedLine, "/", 2) ret.Org = org[0] if len(org) != 2 { return ret, errors.New("missing / separator") } repo := strings.SplitN(org[1], "#", 2) ret.Repo = repo[0] if len(repo) != 2 { return ret, errors.New("Missing # separator") } // Gitea requires that each org and repo be [A-Za-z0-9_-]+ var err error if ret.Num, err = strconv.ParseInt(repo[1], 10, 64); err != nil { return ret, errors.New("Invalid number") } if !validOrgAndRepoRx.MatchString(repo[0]) || !validOrgAndRepoRx.MatchString(org[0]) { return ret, errors.New("Invalid repo or org character set") } return ret, nil } func ExtractDescriptionAndPRs(data *bufio.Scanner) (string, []BasicPR) { prs := make([]BasicPR, 0, 1) var desc strings.Builder for data.Scan() { line := data.Text() pr, err := parsePrLine(line) if err != nil { desc.WriteString(line) desc.WriteByte('\n') } else { prs = append(prs, pr) } } return strings.TrimSpace(desc.String()), prs } func prToLine(writer io.Writer, pr BasicPR) { writer.Write([]byte("\n")) fmt.Fprintf(writer, PrPattern, pr.Org, pr.Repo, pr.Num) } // returns: // <0 for a0 for a>b // =0 when equal func compareBasicPRs(a BasicPR, b BasicPR) int { if c := strings.Compare(a.Org, b.Org); c != 0 { return c } if c := strings.Compare(a.Repo, b.Repo); c != 0 { return c } if a.Num > b.Num { return 1 } if a.Num < b.Num { return -1 } return 0 } func AppendPRsToDescription(desc string, prs []BasicPR) string { var out strings.Builder out.WriteString(strings.TrimSpace(desc)) out.WriteString("\n") slices.SortFunc(prs, compareBasicPRs) prs = slices.Compact(prs) for _, pr := range prs { prToLine(&out, pr) } return out.String() }