diff --git a/techtree-manager/src/collect.rs b/techtree-manager/src/collect.rs index ef3eca1..9a2d6ba 100644 --- a/techtree-manager/src/collect.rs +++ b/techtree-manager/src/collect.rs @@ -24,7 +24,7 @@ pub async fn collect_tree(ctx: &crate::Context) -> anyhow::Result anyhow::Result anyhow::Result { - if has_completed_label { + if has_ultimate_label { + crate::tree::ElementStatus::UltimateGoal + } else if has_completed_label { crate::tree::ElementStatus::Completed } else if issue.assignee.is_some() - || issue - .assignees - .as_ref() - .map(|v| v.len() > 0) - .unwrap_or(false) + || issue.assignees.as_ref().is_some_and(|v| !v.is_empty()) { crate::tree::ElementStatus::Assigned } else { diff --git a/techtree-manager/src/issue.rs b/techtree-manager/src/issue.rs index 97cf9d9..51f33f0 100644 --- a/techtree-manager/src/issue.rs +++ b/techtree-manager/src/issue.rs @@ -57,14 +57,14 @@ pub async fn find_bot_comment( .rev() .find(|comment| comment.user.as_ref().and_then(|u| u.id) == Some(-2)); - Ok(maybe_bot_comment + maybe_bot_comment .map(|c| -> anyhow::Result<_> { Ok(BotCommentInfo { body: c.body.clone().unwrap_or("".to_owned()), id: c.id.context("Missing id for the bot comment")?, }) }) - .transpose()?) + .transpose() } /// Find existing bot comment or create a new one. @@ -115,12 +115,12 @@ pub async fn update_bot_comment_from_subtree( subtree: &crate::tree::Subtree<'_>, hash: &str, ) -> anyhow::Result<()> { - let mut mermaid = subtree.to_mermaid(&ctx.repo_url.to_string(), false); + let mut mermaid = subtree.to_mermaid(ctx.repo_url.as_ref(), false); // When the mermaid graph gets too big, regenerate a simplified version. if mermaid.len() > 4990 { log::info!("Mermaid graph is too big, generating simplified version..."); - mermaid = subtree.to_mermaid(&ctx.repo_url.to_string(), true); + mermaid = subtree.to_mermaid(ctx.repo_url.as_ref(), true); } let full_text = if mermaid.len() > 4990 { @@ -143,7 +143,7 @@ _Sorry, the partial techtree is still too big to be displayed for this element.. ) }; - update_bot_comment(&ctx, id, full_text).await?; + update_bot_comment(ctx, id, full_text).await?; Ok(()) } @@ -157,8 +157,7 @@ pub async fn remove_stale_label(ctx: &crate::Context, issue_number: u64) -> anyh let stale_label_id = labels .iter() - .filter(|l| l.name.as_deref() == Some("Stale")) - .next() + .find(|l| l.name.as_deref() == Some("Stale")) .map(|l| l.id.ok_or(anyhow::anyhow!("`Stale` label has no ID"))) .transpose()?; diff --git a/techtree-manager/src/main.rs b/techtree-manager/src/main.rs index eda6053..6a3c027 100644 --- a/techtree-manager/src/main.rs +++ b/techtree-manager/src/main.rs @@ -1,4 +1,7 @@ #![allow(dead_code)] +#![allow(clippy::useless_format)] +#![allow(clippy::needless_return)] +#![deny(clippy::as_conversions)] use anyhow::Context as _; use forgejo_api::Forgejo; @@ -69,7 +72,7 @@ async fn run() -> anyhow::Result<()> { .map(|token| -> Result<_, ()> { let mut repo_auth_url = repo_url.clone(); repo_auth_url.set_username("forgejo-actions")?; - repo_auth_url.set_password(Some(&token))?; + repo_auth_url.set_password(Some(token))?; Ok(repo_auth_url) }) .transpose() @@ -145,7 +148,9 @@ async fn run() -> anyhow::Result<()> { } 'issues: for issue in tree.iter_issues() { - let subtree = tree.subtree_for_issue(issue).expect("issue from tree not found in tree"); + let subtree = tree + .subtree_for_issue(issue) + .expect("issue from tree not found in tree"); let hash = subtree.stable_hash(); let (bot_comment, _is_new) = issue::find_or_make_bot_comment(&ctx, issue) diff --git a/techtree-manager/src/render.rs b/techtree-manager/src/render.rs index f18bebb..ca3682a 100644 --- a/techtree-manager/src/render.rs +++ b/techtree-manager/src/render.rs @@ -52,14 +52,14 @@ pub async fn render( std::fs::create_dir(&info.repo_dir).context("Failed creating directory for rendered graph")?; let dot_source = tree.to_dot(); - std::fs::write(&info.dot_file(), dot_source.as_bytes()) + std::fs::write(info.dot_file(), dot_source.as_bytes()) .context("Failed to write `dot` source file")?; Command::new("dot") .args(["-T", "svg"]) .arg("-o") - .arg(&info.svg_file()) - .arg(&info.dot_file()) + .arg(info.svg_file()) + .arg(info.dot_file()) .success() .context("Failed to generate svg graph from dot source")?; diff --git a/techtree-manager/src/tree.rs b/techtree-manager/src/tree.rs index 3ec271f..d64c78c 100644 --- a/techtree-manager/src/tree.rs +++ b/techtree-manager/src/tree.rs @@ -55,6 +55,7 @@ impl Element { let (color, background) = match (subtree_role, status) { (Some(SubtreeElementRole::ElementOfInterest), _) => ("black", "white"), (Some(SubtreeElementRole::Dependant), _) => ("gray", "gray"), + (_, ElementStatus::UltimateGoal) => ("black", "white"), (_, ElementStatus::Missing) => ( "#800", // Highlight root elements @@ -100,6 +101,7 @@ impl Element { let class = match (role, status) { (Some(SubtreeElementRole::ElementOfInterest), _) => "eoi", (Some(SubtreeElementRole::Dependant), _) => "dependant", + (_, ElementStatus::UltimateGoal) => "ultimate", (_, ElementStatus::Missing) => "dep_missing", (_, ElementStatus::Assigned) => "dep_assigned", (_, ElementStatus::Completed) => "dep_completed", @@ -116,6 +118,7 @@ fn mermaid_classes() -> String { r##" classDef eoi fill:#fff, stroke:#000, color:#000; classDef dependant fill:#fff, stroke:#888, color:#888; + classDef ultimate fill:#fff, stroke:#000, color:#000; classDef dep_missing fill:#fcc, stroke:#800, color:#000; classDef dep_assigned fill:#ffa, stroke:#a50, color:#000; classDef dep_completed fill:#afa, stroke:#080, color:#000; @@ -128,6 +131,7 @@ pub enum ElementStatus { Missing, Assigned, Completed, + UltimateGoal, } impl std::fmt::Display for ElementStatus { @@ -136,6 +140,7 @@ impl std::fmt::Display for ElementStatus { ElementStatus::Missing => "MISSING", ElementStatus::Assigned => "ASSIGNED", ElementStatus::Completed => "COMPLETED", + ElementStatus::UltimateGoal => "ULTIMATE GOAL", }) } } @@ -194,13 +199,24 @@ impl Tree { } pub fn iter_ultimate_elements<'a>(&'a self) -> impl Iterator + 'a { - self.graph.node_indices().filter(|index| { - // If there are no incoming edges, then this is an ultimate element! - self.graph - .neighbors_directed(*index, petgraph::Direction::Incoming) - .next() - .is_none() - }) + self.graph + .node_indices() + .filter(|index| { + self.graph.node_weight(*index).unwrap().status == ElementStatus::UltimateGoal + }) + .filter(|index| { + // Ultimate goal elements must not have any incoming dependency edges. Warn and + // ignore it, if one does. + let has_no_incoming = self.graph + .neighbors_directed(*index, petgraph::Direction::Incoming) + .next() + .is_none(); + if !has_no_incoming { + let el = self.graph.node_weight(*index).unwrap(); + log::warn!("Element #{} is marked \"Ultimate Goal\" but is depended on by others? Ignoring...", el.issue_number); + } + has_no_incoming + }) } pub fn get_element_role(&self, element: ElementIndex) -> ElementRole { @@ -263,7 +279,7 @@ impl Tree { for index in self.graph.node_indices() { mermaid.push_str(&self.graph[index].to_mermaid_node(index, None, repo_url, simple)); - mermaid.push_str("\n"); + mermaid.push('\n'); } for edge in self.graph.edge_references() { @@ -397,7 +413,7 @@ impl<'a> Subtree<'a> { repo_url, simple, )); - mermaid.push_str("\n"); + mermaid.push('\n'); } for edge in self.graph.edge_references() { diff --git a/techtree-manager/src/wiki.rs b/techtree-manager/src/wiki.rs index e61f931..d38a775 100644 --- a/techtree-manager/src/wiki.rs +++ b/techtree-manager/src/wiki.rs @@ -5,7 +5,7 @@ pub async fn update_wiki_from_tree( ctx: &crate::Context, tree: &crate::tree::Tree, ) -> anyhow::Result<()> { - let mermaid = tree.to_mermaid(&ctx.repo_url.to_string(), false); + let mermaid = tree.to_mermaid(ctx.repo_url.as_ref(), false); let wiki_text = format!( r##"This page is automatically updated to show the latest and greatest FAFO techtree: @@ -14,7 +14,7 @@ pub async fn update_wiki_from_tree( ``` "## ); - update_wiki_overview(&ctx, wiki_text) + update_wiki_overview(ctx, wiki_text) .await .context("Failed to update the techtree wiki page")?; Ok(())