package common import ( "bufio" "errors" "fmt" "io" "regexp" "strconv" "strings" ) const PrPattern = "PR: %s/%s#%d" type BasicPR struct { org, repo string num uint64 } 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.ParseUint(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 ExtractAssociatedDescriptionAndPRs(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) { fmt.Fprintf(writer, PrPattern, pr.org, pr.repo, pr.num) writer.Write([]byte("\n")) } func AppendPRsToDescription(desc string, prs []BasicPR) string { var out strings.Builder out.WriteString(strings.TrimSpace(desc)) out.WriteString("\n\n") for _, pr := range prs { prToLine(&out, pr) } return out.String() }