1 package org.openjdk.skara.bots.mlbridge;
  2 
  3 import org.openjdk.skara.email.Email;
  4 import org.openjdk.skara.host.*;
  5 import org.openjdk.skara.vcs.*;
  6 
  7 import java.net.URI;
  8 import java.time.format.DateTimeFormatter;
  9 import java.util.Arrays;
 10 import java.util.regex.Pattern;
 11 import java.util.stream.Collectors;
 12 
 13 class ArchiveMessages {
 14     private static String formatCommit(Commit commit) {
 15         var ret = new StringBuilder();
 16         var message = commit.message();
 17         if (message.size() == 0) {
 18             ret.append("<no commit message found>");
 19         } else {
 20             var abbrev = commit.hash().abbreviate();
 21             ret.append(" - ").append(abbrev).append(": ").append(message.get(0).strip());
 22         }
 23         return ret.toString();
 24     }
 25 
 26     private static final String infoSeparator = "----------------";
 27 
 28     private static final Pattern commentPattern = Pattern.compile("<!--.*?-->",
 29                                                                   Pattern.DOTALL | Pattern.MULTILINE);
 30     private static final Pattern cutoffPattern = Pattern.compile("(.*?)<!-- Anything below this marker will be .*? -->",
 31                                                                  Pattern.DOTALL | Pattern.MULTILINE);
 32     private static String filterComments(String body) {
 33         var cutoffMatcher = cutoffPattern.matcher(body);
 34         if (cutoffMatcher.find()) {
 35             body = cutoffMatcher.group(1);
 36         }
 37 
 38         var commentMatcher = commentPattern.matcher(body);
 39         body = commentMatcher.replaceAll("");
 40 
 41         body = MarkdownToText.removeFormatting(body);
 42         return body.strip();
 43     }
 44 
 45     private static String quoteBody(String body) {
 46         return Arrays.stream(body.strip().split("\\R"))
 47                      .map(line -> line.length() > 0 ? line.charAt(0) == '>' ? ">" + line : "> " + line : "> ")
 48                      .collect(Collectors.joining("\n"));
 49     }
 50 
 51     private static String replyFooter(PullRequestInstance prInstance) {
 52         return "PR: " + prInstance.pr().getWebUrl();
 53     }
 54 
 55     // When changing this, ensure that the PR pattern in the notifier still matches
 56     static String composeConversation(PullRequestInstance prInstance, URI webrev) {
 57         var commitMessages = prInstance.formatCommitMessages(prInstance.baseHash(), prInstance.headHash(), ArchiveMessages::formatCommit);
 58         var filteredBody = filterComments(prInstance.pr().getBody());
 59         if (filteredBody.isEmpty()) {
 60             filteredBody = prInstance.pr().getTitle().strip();
 61         }
 62         return filteredBody + "\n\n" +
 63                 infoSeparator + "\n\n" +
 64                 "Commits:\n" +
 65                 commitMessages + "\n\n" +
 66                 "Changes: " + prInstance.changeUrl() + "\n" +
 67                 " Webrev: " + webrev.toString() + "\n" +
 68                 "  Stats: " + prInstance.stats(prInstance.baseHash(), prInstance.headHash()) + "\n" +
 69                 "  Patch: " + prInstance.diffUrl() + "\n" +
 70                 "  Fetch: " + prInstance.fetchCommand() + "\n\n" +
 71                 replyFooter(prInstance);
 72     }
 73 
 74     static String composeRebaseComment(PullRequestInstance prInstance, URI fullWebrev) {
 75         var commitMessages = prInstance.formatCommitMessages(prInstance.baseHash(), prInstance.headHash(), ArchiveMessages::formatCommit);
 76         return "The pull request has been updated with a complete new set of changes (possibly due to a rebase).\n\n" +
 77                 infoSeparator + "\n\n" +
 78                 "Commits:\n" +
 79                 commitMessages + "\n\n" +
 80                 "Changes: " + prInstance.changeUrl() + "\n" +
 81                 " Webrev: " + fullWebrev.toString() + "\n" +
 82                 "  Stats: " + prInstance.stats(prInstance.baseHash(), prInstance.headHash()) + "\n" +
 83                 "  Patch: " + prInstance.diffUrl() + "\n" +
 84                 "  Fetch: " + prInstance.fetchCommand() + "\n\n" +
 85                 replyFooter(prInstance);    }
 86 
 87     static String composeIncrementalComment(Hash lastHead, PullRequestInstance prInstance, URI fullWebrev, URI incrementalWebrev) {
 88         var newCommitMessages = prInstance.formatCommitMessages(lastHead, prInstance.headHash(), ArchiveMessages::formatCommit);
 89         return "The pull request has been updated with additional changes.\n\n" +
 90                 infoSeparator + "\n\n" +
 91                 "Added commits:\n" +
 92                 newCommitMessages + "\n\n" +
 93                 "Changes:\n" +
 94                 "  - all: " + prInstance.pr().getWebUrl() + "/files\n" +
 95                 "  - new: " + prInstance.changeUrl(lastHead, prInstance.headHash()) + "\n\n" +
 96                 "Webrevs:\n" +
 97                 " - full: " + fullWebrev.toString() + "\n" +
 98                 " - incr: " + incrementalWebrev.toString() + "\n\n" +
 99                 "  Stats: " + prInstance.stats(lastHead, prInstance.headHash()) + "\n" +
100                 "  Patch: " + prInstance.diffUrl() + "\n" +
101                 "  Fetch: " + prInstance.fetchCommand() + "\n\n" +
102                 replyFooter(prInstance);
103     }
104 
105     private static String filterParentBody(Email parent, PullRequestInstance prInstance) {
106         var parentFooter = ArchiveMessages.replyFooter(prInstance);
107         var filteredParentBody = parent.body().strip();
108         if (filteredParentBody.endsWith(parentFooter)) {
109             return filteredParentBody.substring(0, filteredParentBody.length() - parentFooter.length()).strip();
110         } else {
111             return filteredParentBody;
112         }
113     }
114 
115     static String composeReply(Email parent, String body, PullRequestInstance prInstance) {
116         return "On " + parent.date().format(DateTimeFormatter.RFC_1123_DATE_TIME) + ", " + parent.author().toString() + " wrote:\n" +
117                 "\n" +
118                 quoteBody(filterParentBody(parent, prInstance)) +
119                 "\n\n" +
120                 filterComments(body) +
121                 "\n\n" +
122                 replyFooter(prInstance);
123     }
124 
125     static String composeCombinedReply(Email parent, String body, PullRequestInstance prInstance) {
126         return filterParentBody(parent, prInstance) +
127                 "\n\n" +
128                 filterComments(body) +
129                 "\n\n" +
130                 replyFooter(prInstance);
131     }
132 
133     static String reviewCommentBody(String body, Review.Verdict verdict, String user, String role) {
134         var result = new StringBuilder(filterComments(body));
135         if (verdict != Review.Verdict.NONE) {
136             if (result.length() > 0) {
137                 result.append("\n\n");
138                 result.append(infoSeparator);
139                 result.append("\n\n");
140             }
141             if (verdict == Review.Verdict.APPROVED) {
142                 result.append("Approved");
143             } else {
144                 result.append("Disapproved");
145             }
146             result.append(" by ");
147             result.append(user);
148             result.append(" (");
149             result.append(role);
150             result.append(").");
151         }
152         return result.toString();
153     }
154 }