Add function to visualise dependency tree via graphviz

This commit is contained in:
RunasSudo 2025-05-24 00:40:27 +10:00
parent fa76461926
commit fa7381fce5
Signed by: RunasSudo
GPG Key ID: 7234E476BF21C61A
2 changed files with 65 additions and 0 deletions

View File

@ -19,6 +19,7 @@
use chrono::NaiveDate; use chrono::NaiveDate;
use libdrcr::db::DbConnection; use libdrcr::db::DbConnection;
use libdrcr::reporting::builders::register_dynamic_builders; use libdrcr::reporting::builders::register_dynamic_builders;
use libdrcr::reporting::calculator::{steps_as_graphviz, steps_for_targets};
use libdrcr::reporting::generate_report; use libdrcr::reporting::generate_report;
use libdrcr::reporting::steps::{ use libdrcr::reporting::steps::{
register_lookup_fns, AllTransactionsExceptEarningsToEquity, register_lookup_fns, AllTransactionsExceptEarningsToEquity,
@ -43,6 +44,21 @@ fn main() {
register_lookup_fns(&mut context); register_lookup_fns(&mut context);
register_dynamic_builders(&mut context); register_dynamic_builders(&mut context);
// Print Graphviz
let targets: Vec<Box<dyn ReportingStep>> = vec![
Box::new(CalculateIncomeTax {}),
Box::new(AllTransactionsIncludingEarningsToEquity {
args: DateArgs {
date: NaiveDate::from_ymd_opt(2025, 6, 30).unwrap(),
},
}),
];
let (sorted_steps, dependencies) = steps_for_targets(targets, &context).unwrap();
println!("Graphviz:");
println!("{}", steps_as_graphviz(&sorted_steps, &dependencies));
// Get income statement // Get income statement
let targets: Vec<Box<dyn ReportingStep>> = vec![ let targets: Vec<Box<dyn ReportingStep>> = vec![

View File

@ -321,3 +321,52 @@ pub fn steps_for_targets(
Ok((sorted_steps, dependencies)) Ok((sorted_steps, dependencies))
} }
/// Generate graphviz code representing the dependency tree
///
/// Useful for debugging or visualisation. Can be compiled using e.g. `dot -Tpdf -O output.gv`.
pub fn steps_as_graphviz(
steps: &Vec<Box<dyn ReportingStep>>,
dependencies: &ReportingGraphDependencies,
) -> String {
let mut result = String::from("strict digraph drcr {\n");
// Output all steps
for step in steps.iter() {
let step_display_name = step.to_string();
if step_display_name.contains("{") {
// Bodge: Detect dynamic step builders
result.push_str(&format!(
"\"{}\" [shape=box, style=dashed, label=\"{}\"];\n",
step.id(),
step_display_name
));
} else {
result.push_str(&format!("\"{}\" [shape=box];\n", step.id()));
}
// Output the products of the step
for product_kind in step.id().product_kinds.iter() {
result.push_str(&format!(
"\"{}\" -> \"{}\";\n",
step.id(),
ReportingProductId {
name: step.id().name,
kind: *product_kind,
args: step.id().args
}
));
}
}
// Output all dependencies
for dependency in dependencies.vec().iter() {
result.push_str(&format!(
"\"{}\" -> \"{}\";\n",
dependency.product, dependency.step
));
}
result.push_str("}");
result
}