package main import ( "bufio" "fmt" "io" "os" "sort" "strconv" "strings" "unicode" "code.gitea.io/sdk/gitea" "github.com/bbrks/wrap/v2" "gopkg.in/yaml.v2" ) type Config struct { Gitea struct { InstanceURL string `yaml:"instance_url"` Owner string `yaml:"owner"` Repository string `yaml:"repository"` } `yaml:"gitea"` Language struct { OpeningOnSeparateLine bool `yaml:"separate_opening"` Opening string `yaml:"opening"` Continuation string `yaml:"continuation"` Closing string `yaml:"closing"` } `yaml:"language"` } var config Config = Config{} type ByLineNum []*gitea.PullReviewComment func (a ByLineNum) Len() int { return len(a) } func (a ByLineNum) Swap(i, j int) { a[i], a[j] = a[j], a[i] } func (a ByLineNum) Less(i, j int) bool { return a[i].LineNum < a[j].LineNum } func ExitOnError(err error) { if err != nil { fmt.Fprintln(os.Stderr, err) os.Exit(1) } } func RetrieveComments(client *gitea.Client, prID, reviewID int64) []*gitea.PullReviewComment { comments, _, err := client.ListPullReviewComments( config.Gitea.Owner, config.Gitea.Repository, prID, reviewID, ) ExitOnError(err) return comments } func SplitByFile( splitComments map[string]([]*gitea.PullReviewComment), comments []*gitea.PullReviewComment, ) map[string]([]*gitea.PullReviewComment) { for _, comment := range comments { arr, found := splitComments[comment.Path] if !found { arr = make([]*gitea.PullReviewComment, 0) } splitComments[comment.Path] = append(arr, comment) } return splitComments } func BackUpSource(filepath string) { inputFile, err := os.Open(filepath) ExitOnError(err) defer inputFile.Close() BackUpSourceFile, err := os.Create(filepath + ".bck") ExitOnError(err) defer BackUpSourceFile.Close() _, err = io.Copy(BackUpSourceFile, inputFile) ExitOnError(err) } func GetSeparatorForMultiline() string { if config.Language.OpeningOnSeparateLine { return "\n" } return " " } func FormatComment(body string, beenWrapped bool) string { if beenWrapped && !config.Language.OpeningOnSeparateLine { body = " " + strings.TrimPrefix(body, " "+config.Language.Continuation) } if !beenWrapped && !config.Language.OpeningOnSeparateLine && !strings.HasSuffix(body, "\n") { body = body + "\n" } return fmt.Sprintf("%s%s%s", config.Language.Opening, body, config.Language.Closing) } func PatchFiles(wrapper *wrap.Wrapper, commentsByFile map[string]([]*gitea.PullReviewComment)) { for filepath, comments := range commentsByFile { fmt.Printf("FILE: %s\n", filepath) BackUpSource(filepath) inputFile, err := os.Open(filepath + ".bck") ExitOnError(err) defer inputFile.Close() outputFile, err := os.Create(filepath) ExitOnError(err) defer outputFile.Close() var i uint64 = 1 nextComment, commentsLength := 0, len(comments) scanner := bufio.NewScanner(inputFile) for scanner.Scan() { for (nextComment < commentsLength) && comments[nextComment].LineNum == i { body := comments[nextComment].Body beenWrapped := len(body)+len(config.Language.Opening)+len(config.Language.Closing) > 100 if beenWrapped { body = GetSeparatorForMultiline() + wrapper.Wrap(body, 100) } else { body = " " + body } body = strings.Replace(body, "\r", "", -1) body = strings.Replace( body, config.Language.Continuation+"\n", strings.TrimRightFunc(config.Language.Continuation, unicode.IsSpace)+"\n", -1, ) comment := FormatComment(body, beenWrapped) fmt.Fprint(outputFile, comment) fmt.Printf("L%04d:\n%s\n", comments[nextComment].LineNum, comment) nextComment++ } fmt.Fprintln(outputFile, scanner.Text()) i++ } } } func GetReviewsPerPR(client *gitea.Client, prID int64) []int64 { reviews, _, err := client.ListPullReviews( config.Gitea.Owner, config.Gitea.Repository, prID, gitea.ListPullReviewsOptions{}, ) ExitOnError(err) result := make([]int64, 0) for _, review := range reviews { result = append(result, review.ID) } return result } func LoadConfig(filename string) { if filename == "" { filename = ".frag_review.yml" } configFile, err := os.Open(filename) ExitOnError(err) defer configFile.Close() decoder := yaml.NewDecoder(configFile) err = decoder.Decode(&config) ExitOnError(err) } func main() { token := os.Getenv("GITEA_TOKEN") LoadConfig(os.Getenv("FRAG_REVIEW_CONFIG")) client, err := gitea.NewClient(config.Gitea.InstanceURL, gitea.SetToken(token)) ExitOnError(err) if len(os.Args) < 2 { fmt.Fprintf(os.Stderr, "Usage: %s pr-id\n", os.Args[0]) os.Exit(1) } prID, err := strconv.ParseInt(os.Args[1], 10, 64) ExitOnError(err) reviews := GetReviewsPerPR(client, prID) splitComments := make(map[string]([]*gitea.PullReviewComment)) for _, reviewID := range reviews { comments := RetrieveComments(client, prID, reviewID) sort.Sort(ByLineNum(comments)) splitComments = SplitByFile(splitComments, comments) } wrapper := wrap.NewWrapper() wrapper.OutputLinePrefix = config.Language.Continuation PatchFiles(&wrapper, splitComments) }