interactions.rs 5.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169
  1. use crate::github::{GithubClient, Issue};
  2. use std::fmt::Write;
  3. use tracing as log;
  4. pub struct ErrorComment<'a> {
  5. issue: &'a Issue,
  6. message: String,
  7. }
  8. impl<'a> ErrorComment<'a> {
  9. pub fn new<T>(issue: &'a Issue, message: T) -> ErrorComment<'a>
  10. where
  11. T: Into<String>,
  12. {
  13. ErrorComment {
  14. issue,
  15. message: message.into(),
  16. }
  17. }
  18. pub async fn post(&self, client: &GithubClient) -> anyhow::Result<()> {
  19. let mut body = String::new();
  20. writeln!(body, "**Error**: {}", self.message)?;
  21. writeln!(body)?;
  22. writeln!(
  23. body,
  24. "Please let **`@rust-lang/release`** know if you're having trouble with this bot."
  25. )?;
  26. self.issue.post_comment(client, &body).await
  27. }
  28. }
  29. pub struct PingComment<'a> {
  30. issue: &'a Issue,
  31. users: &'a [&'a str],
  32. }
  33. impl<'a> PingComment<'a> {
  34. pub fn new(issue: &'a Issue, users: &'a [&str]) -> PingComment<'a> {
  35. PingComment { issue, users }
  36. }
  37. pub async fn post(&self, client: &GithubClient) -> anyhow::Result<()> {
  38. let mut body = String::new();
  39. for user in self.users {
  40. write!(body, "@{} ", user)?;
  41. }
  42. self.issue.post_comment(client, &body).await
  43. }
  44. }
  45. pub struct EditIssueBody<'a> {
  46. issue: &'a Issue,
  47. id: &'static str,
  48. }
  49. static START_BOT: &str = "<!-- TRIAGEBOT_START -->\n\n";
  50. static END_BOT: &str = "<!-- TRIAGEBOT_END -->";
  51. fn normalize_body(body: &str) -> String {
  52. str::replace(body, "\r\n", "\n")
  53. }
  54. impl<'a> EditIssueBody<'a> {
  55. pub fn new(issue: &'a Issue, id: &'static str) -> EditIssueBody<'a> {
  56. EditIssueBody { issue, id }
  57. }
  58. fn get_current(&self) -> Option<String> {
  59. let self_issue_body = normalize_body(&self.issue.body);
  60. let start_section = self.start_section();
  61. let end_section = self.end_section();
  62. if self_issue_body.contains(START_BOT) {
  63. if self_issue_body.contains(&start_section) {
  64. let start_idx = self_issue_body.find(&start_section).unwrap();
  65. let end_idx = self_issue_body.find(&end_section).unwrap();
  66. let current =
  67. String::from(&self_issue_body[start_idx..(end_idx + end_section.len())]);
  68. Some(current)
  69. } else {
  70. None
  71. }
  72. } else {
  73. None
  74. }
  75. }
  76. pub fn current_data<T: serde::de::DeserializeOwned>(&self) -> Option<T> {
  77. let all = self.get_current()?;
  78. let start = self.data_section_start();
  79. let end = self.data_section_end();
  80. let start_idx = all.find(&start).unwrap();
  81. let end_idx = all.find(&end).unwrap();
  82. let text = &all[(start_idx + start.len())..end_idx];
  83. Some(serde_json::from_str(text).unwrap_or_else(|e| {
  84. panic!("deserializing data {:?} failed: {:?}", text, e);
  85. }))
  86. }
  87. fn start_section(&self) -> String {
  88. format!("<!-- TRIAGEBOT_{}_START -->\n", self.id)
  89. }
  90. fn end_section(&self) -> String {
  91. format!("\n<!-- TRIAGEBOT_{}_END -->\n", self.id)
  92. }
  93. fn data_section_start(&self) -> String {
  94. format!("\n<!-- TRIAGEBOT_{}_DATA_START$$", self.id)
  95. }
  96. fn data_section_end(&self) -> String {
  97. format!("$$TRIAGEBOT_{}_DATA_END -->\n", self.id)
  98. }
  99. fn data_section<T>(&self, data: T) -> String
  100. where
  101. T: serde::Serialize,
  102. {
  103. format!(
  104. "{}{}{}",
  105. self.data_section_start(),
  106. serde_json::to_string(&data).unwrap(),
  107. self.data_section_end()
  108. )
  109. }
  110. pub async fn apply<T>(&self, client: &GithubClient, text: String, data: T) -> anyhow::Result<()>
  111. where
  112. T: serde::Serialize,
  113. {
  114. let mut current_body = normalize_body(&self.issue.body.clone());
  115. let start_section = self.start_section();
  116. let end_section = self.end_section();
  117. let bot_section = format!(
  118. "{}{}{}{}",
  119. start_section,
  120. text,
  121. self.data_section(data),
  122. end_section
  123. );
  124. let empty_bot_section = format!("{}{}", start_section, end_section);
  125. let all_new = format!("\n\n{}{}{}", START_BOT, bot_section, END_BOT);
  126. if current_body.contains(START_BOT) {
  127. if current_body.contains(&start_section) {
  128. let start_idx = current_body.find(&start_section).unwrap();
  129. let end_idx = current_body.find(&end_section).unwrap();
  130. current_body.replace_range(start_idx..(end_idx + end_section.len()), &bot_section);
  131. if current_body.contains(&all_new) && bot_section == empty_bot_section {
  132. let start_idx = current_body.find(&all_new).unwrap();
  133. let end_idx = start_idx + all_new.len();
  134. current_body.replace_range(start_idx..end_idx, "");
  135. }
  136. self.issue.edit_body(&client, &current_body).await?;
  137. } else {
  138. let end_idx = current_body.find(&END_BOT).unwrap();
  139. current_body.insert_str(end_idx, &bot_section);
  140. self.issue.edit_body(&client, &current_body).await?;
  141. }
  142. } else {
  143. let new_body = format!("{}{}", current_body, all_new);
  144. self.issue.edit_body(&client, &new_body).await?;
  145. }
  146. Ok(())
  147. }
  148. }