From be7f305a6b1a94dfbc3ae5fb48972d53602b3982 Mon Sep 17 00:00:00 2001 From: Rahix Date: Tue, 9 Dec 2025 16:48:42 +0100 Subject: [PATCH 1/3] Fix clippy lints --- techtree-manager/src/collect.rs | 10 +++------- techtree-manager/src/issue.rs | 13 ++++++------- techtree-manager/src/main.rs | 9 +++++++-- techtree-manager/src/render.rs | 6 +++--- techtree-manager/src/tree.rs | 4 ++-- techtree-manager/src/wiki.rs | 4 ++-- 6 files changed, 23 insertions(+), 23 deletions(-) diff --git a/techtree-manager/src/collect.rs b/techtree-manager/src/collect.rs index ef3eca1..0660eb0 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 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..19a8a5f 100644 --- a/techtree-manager/src/tree.rs +++ b/techtree-manager/src/tree.rs @@ -263,7 +263,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 +397,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(()) From 8b0aef0599cd1ee692def5a644d91c091c9319fd Mon Sep 17 00:00:00 2001 From: Rahix Date: Fri, 23 Jan 2026 14:48:49 +0100 Subject: [PATCH 2/3] Treat elements with "Ultimate Goal" label specially Mainly restyle them to appear different from other techtree elements. Also ignore any element status for these, because it is meaningless (they can never be reached). --- techtree-manager/src/collect.rs | 8 +++++++- techtree-manager/src/tree.rs | 5 +++++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/techtree-manager/src/collect.rs b/techtree-manager/src/collect.rs index 0660eb0..9a2d6ba 100644 --- a/techtree-manager/src/collect.rs +++ b/techtree-manager/src/collect.rs @@ -128,9 +128,15 @@ fn element_from_issue(issue: &forgejo_api::structs::Issue) -> 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().is_some_and(|v| !v.is_empty()) diff --git a/techtree-manager/src/tree.rs b/techtree-manager/src/tree.rs index 19a8a5f..303b75d 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", }) } } From 81ce471d0e960d7f0dc30c9889e7e35a6aa80b6b Mon Sep 17 00:00:00 2001 From: Rahix Date: Fri, 23 Jan 2026 14:55:54 +0100 Subject: [PATCH 3/3] Only place real "Ultimate Goal" elements at the bottom of the graph "Ultimate Goal" elements are only counted as such if they fulfil the following requirements: - They have the "Ultimate Goal" issue label - They are not depended on by any other elements --- techtree-manager/src/tree.rs | 25 ++++++++++++++++++------- 1 file changed, 18 insertions(+), 7 deletions(-) diff --git a/techtree-manager/src/tree.rs b/techtree-manager/src/tree.rs index 303b75d..d64c78c 100644 --- a/techtree-manager/src/tree.rs +++ b/techtree-manager/src/tree.rs @@ -199,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 {