From 20e78c2b6cf81d001d2207a1cd0b10cf08c072b6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Felix=20H=C3=A4cker?= Date: Fri, 25 Jun 2021 21:56:51 +0200 Subject: [PATCH] Update renderer to include section/project details --- Cargo.lock | 1 + Cargo.toml | 3 +- src/bot.rs | 3 +- src/config.rs | 6 +-- src/render.rs | 130 +++++++++++++++++++++++++++++++++++++++++--------- 5 files changed, 114 insertions(+), 29 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 011b412..5545aa3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -736,6 +736,7 @@ dependencies = [ "matrix-sdk", "matrix-sdk-common", "pretty_env_logger", + "rand", "regex", "ruma", "serde", diff --git a/Cargo.toml b/Cargo.toml index 1f71a3a..a04a57b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,4 +18,5 @@ async-trait = "0.1" log = "0.4" pretty_env_logger = "0.4" chrono = "0.4" -regex = "1.5.4" +regex = "1.5" +rand = "0.8" diff --git a/src/bot.rs b/src/bot.rs index ac3e29c..2b7b6e2 100644 --- a/src/bot.rs +++ b/src/bot.rs @@ -472,8 +472,9 @@ impl EventCallback { let news_store = self.0.news_store.lock().unwrap(); let news = news_store.get_news(); + let config = self.0.config.clone(); - let r = render::render(news, editor, &bot); + let r = render::render(news, config, editor, &bot); format!("
{}
\n", r) }; diff --git a/src/config.rs b/src/config.rs index 4fc4061..c6ad060 100644 --- a/src/config.rs +++ b/src/config.rs @@ -4,20 +4,18 @@ use std::env; use std::fs::File; use std::io::Read; -#[derive(Serialize, Deserialize, Clone, Debug)] +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, Hash, Default)] pub struct Section { pub title: String, pub emoji: char, - pub order: u32, } -#[derive(Serialize, Deserialize, Clone, Debug)] +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, Hash, Default)] pub struct Project { pub title: String, pub description: String, pub repository: String, pub emoji: char, - pub order: u32, } #[derive(Deserialize, Clone, Debug)] diff --git a/src/render.rs b/src/render.rs index 2d92df1..15480bf 100644 --- a/src/render.rs +++ b/src/render.rs @@ -1,15 +1,52 @@ use matrix_sdk::RoomMember; +use rand::Rng; use regex::Regex; use ruma::UserId; +use std::collections::HashMap; +use std::collections::HashSet; use std::env; use std::fs::File; use std::io::Read; +use crate::config::Config; +use crate::config::{Project, Section}; use crate::store::News; use crate::utils; -pub fn render(news: Vec, editor: &RoomMember, bot: &UserId) -> String { +pub fn render(news_list: Vec, config: Config, editor: &RoomMember, bot: &UserId) -> String { + let mut section_map: HashMap> = HashMap::new(); + + // Sort news entries into sections + for news in news_list { + // skip not approved news + if news.approvals.is_empty() { + continue; + } + + // Filter out duplicated sections + // (eg. two editors are adding the same section to a news entry) + let mut sections = HashSet::new(); + for section in news.sections.values().collect::>() { + sections.insert(section); + } + + if sections.is_empty() { + // For news entries without a section + let todo_section = Section { + title: "TODO".into(), + emoji: '❔', + }; + insert_into_map(&mut section_map, &todo_section, news); + } else { + for section_emoji in sections { + let section = config.section_by_emoji(§ion_emoji).unwrap(); + insert_into_map(&mut section_map, §ion, news.clone()); + } + } + } + + // Load template file let path = match env::var("TEMPLATE_PATH") { Ok(val) => val, Err(_) => "./template.md".to_string(), @@ -20,29 +57,37 @@ pub fn render(news: Vec, editor: &RoomMember, bot: &UserId) -> String { file.read_to_string(&mut template) .expect("Unable to read template file"); - let mut report = String::new(); - for n in news { - // skip not approved news - if n.approvals.is_empty() { - continue; + // Generate actual report + let mut report_text = String::new(); + for (section, news) in section_map { + let mut section_text = format!("# {}\n", section.title); + + for n in news { + // Filter out duplicated project + // (eg. two editors are adding the same project description to a news entry) + let mut projects = HashSet::new(); + for project in n.projects.values().collect::>() { + projects.insert(project); + } + + if projects.is_empty() { + // For news entries without a project + let project = Project { + title: "TODO: Unknown project!".into(), + ..Default::default() + }; + let news_text = generate_news_text(&n, &project, bot); + section_text += &news_text; + } else { + for p in projects { + let project = config.project_by_emoji(&p).unwrap(); + let news_text = generate_news_text(&n, &project, bot); + section_text += &news_text; + } + } } - let section = "Section header (not implemented yet)"; - let user = format!( - "[{}](https://matrix.to/#/{})", - n.reporter_display_name, n.reporter_id - ); - - let message = prepare_message(n.message, bot); - - let section = format!( - "# {}\n\ - {} reports that\n\n\ - {}\n\n", - section, user, message - ); - - report = (report + §ion).to_string(); + report_text += §ion_text; } // Editor user name / link @@ -59,11 +104,43 @@ pub fn render(news: Vec, editor: &RoomMember, bot: &UserId) -> String { template = template.replace("{{today}}", &today.to_string()); template = template.replace("{{author}}", &author); - template = template.replace("{{report}}", &report); + template = template.replace("{{report}}", &report_text); template } +fn insert_into_map(section_map: &mut HashMap>, section: &Section, news: News) { + if let Some(entries) = section_map.get_mut(§ion) { + entries.insert(0, news); + } else { + let mut entries = Vec::new(); + entries.insert(0, news); + section_map.insert(section.clone(), entries); + } +} + +fn generate_news_text(news: &News, project: &Project, bot: &UserId) -> String { + let user = format!( + "[{}](https://matrix.to/#/{})", + news.reporter_display_name, news.reporter_id + ); + + let project_repo = format!("[{}]({})", project.title, project.repository); + let project_text = project.description.replace("{{project}}", &project_repo); + let verb = random_verb(); + let message = prepare_message(news.message.clone(), bot); + + let news_text = format!( + "### {}\n\n\ + {}\n\n\ + {} {}\n\n\ + {}\n\n", + project.title, project_text, user, verb, message + ); + + news_text +} + fn prepare_message(msg: String, bot: &UserId) -> String { let msg = msg.trim(); @@ -80,3 +157,10 @@ fn prepare_message(msg: String, bot: &UserId) -> String { // lists msg.replace("> -", "> *") } + +fn random_verb() -> String { + let mut rng = rand::thread_rng(); + let verbs = vec!["reports", "offers", "said", "announces", "reveals", "tells"]; + let id = rng.gen_range(0..verbs.len()); + verbs[id].to_string() +}