dependency_analysis/
dependency_analysis.rs

1use ascii_dag::cycles::generic::roots::{RootFindable, find_leaves_fn, find_roots_fn};
2use ascii_dag::graph::DAG;
3use ascii_dag::layout::generic::impact::{
4    ImpactAnalyzable, compute_blast_radius_fn, compute_descendants_fn,
5};
6use ascii_dag::layout::generic::metrics::GraphMetrics;
7use std::collections::HashMap;
8
9fn main() {
10    println!("=== Dependency Analysis Tool ===\n");
11
12    example_simple_analysis();
13    example_trait_based_registry();
14    example_metrics_dashboard();
15}
16
17fn example_simple_analysis() {
18    println!("1. Simple Dependency Analysis");
19    println!("   Analyzing a package dependency graph\n");
20
21    // Define package dependencies
22    let get_deps = |pkg: &&str| match *pkg {
23        "app" => vec!["core", "ui"],
24        "ui" => vec!["core", "renderer"],
25        "renderer" => vec!["core"],
26        "core" => vec!["utils"],
27        "utils" => vec![],
28        _ => vec![],
29    };
30
31    let packages = ["app", "ui", "renderer", "core", "utils"];
32
33    // Find root packages (no dependencies)
34    let roots = find_roots_fn(&packages, get_deps);
35    println!("   📦 Root packages (can be built first):");
36    for root in &roots {
37        println!("      - {}", root);
38    }
39    println!();
40
41    // Find leaf packages (nothing depends on them)
42    let leaves = find_leaves_fn(&packages, get_deps);
43    println!("   🍃 Leaf packages (final outputs):");
44    for leaf in &leaves {
45        println!("      - {}", leaf);
46    }
47    println!();
48
49    // Impact analysis: What breaks if core changes?
50    let impacted = compute_descendants_fn(&packages, &"core", get_deps);
51    println!("   ⚠️  If 'core' changes, these packages need rebuilding:");
52    for pkg in &impacted {
53        println!("      - {}", pkg);
54    }
55    println!("   Impact: {} packages affected\n", impacted.len());
56
57    // Blast radius: Full dependency tree for ui
58    let (deps, impacts) = compute_blast_radius_fn(&packages, &"ui", get_deps);
59    println!("   💥 Blast radius for 'ui':");
60    println!("      Dependencies: {:?}", deps);
61    println!("      Impacts: {:?}", impacts);
62    println!();
63}
64
65fn example_trait_based_registry() {
66    println!("2. Trait-Based Error Registry");
67    println!("   Using traits for cleaner API\n");
68
69    #[derive(Debug, Clone)]
70    struct ErrorDef {
71        id: String,
72        message: String,
73        caused_by: Vec<String>,
74    }
75
76    struct ErrorRegistry {
77        errors: HashMap<String, ErrorDef>,
78    }
79
80    impl RootFindable for ErrorRegistry {
81        type Id = String;
82
83        fn get_all_ids(&self) -> Vec<String> {
84            self.errors.keys().cloned().collect()
85        }
86
87        fn get_dependencies(&self, id: &String) -> Vec<String> {
88            self.errors
89                .get(id)
90                .map(|e| e.caused_by.clone())
91                .unwrap_or_default()
92        }
93    }
94
95    impl ImpactAnalyzable for ErrorRegistry {
96        type Id = String;
97
98        fn get_all_ids(&self) -> Vec<String> {
99            self.errors.keys().cloned().collect()
100        }
101
102        fn get_dependencies(&self, id: &String) -> Vec<String> {
103            self.errors
104                .get(id)
105                .map(|e| e.caused_by.clone())
106                .unwrap_or_default()
107        }
108    }
109
110    // Create error registry
111    let mut errors = HashMap::new();
112    errors.insert(
113        "E001".to_string(),
114        ErrorDef {
115            id: "E001".to_string(),
116            message: "Invalid configuration".to_string(),
117            caused_by: vec![],
118        },
119    );
120    errors.insert(
121        "E002".to_string(),
122        ErrorDef {
123            id: "E002".to_string(),
124            message: "Database connection failed".to_string(),
125            caused_by: vec!["E001".to_string()],
126        },
127    );
128    errors.insert(
129        "E003".to_string(),
130        ErrorDef {
131            id: "E003".to_string(),
132            message: "Transaction rollback".to_string(),
133            caused_by: vec!["E002".to_string()],
134        },
135    );
136
137    let registry = ErrorRegistry { errors };
138
139    // Use trait methods
140    println!("   🔍 Root errors (primary causes):");
141    for root in registry.find_roots() {
142        println!("      - {}", root);
143    }
144    println!();
145
146    println!("   📊 Error E001 analysis:");
147    let impacted = registry.compute_descendants(&"E001".to_string());
148    println!("      Cascading errors: {}", impacted.len());
149    for err in &impacted {
150        println!("      - {}", err);
151    }
152    println!();
153}
154
155fn example_metrics_dashboard() {
156    println!("3. Dependency Graph Metrics");
157    println!("   Statistical analysis of the dependency structure\n");
158
159    // Build system example
160    let get_deps = |file: &&str| match *file {
161        "app.exe" => vec!["main.o", "utils.o", "io.o"],
162        "main.o" => vec!["main.c", "types.h"],
163        "utils.o" => vec!["utils.c", "types.h"],
164        "io.o" => vec!["io.c", "types.h"],
165        "main.c" => vec![],
166        "utils.c" => vec![],
167        "io.c" => vec![],
168        "types.h" => vec![],
169        _ => vec![],
170    };
171
172    let files = [
173        "app.exe", "main.o", "utils.o", "io.o", "main.c", "utils.c", "io.c", "types.h",
174    ];
175
176    let metrics = GraphMetrics::compute(&files, get_deps);
177
178    println!("   📈 Graph Statistics:");
179    println!("      Total files: {}", metrics.node_count());
180    println!("      Total dependencies: {}", metrics.edge_count());
181    println!("      Root files (sources): {}", metrics.root_count());
182    println!("      Leaf files (outputs): {}", metrics.leaf_count());
183    println!("      Max depth: {}", metrics.max_depth());
184    println!("      Max impact: {} files", metrics.max_descendants());
185    println!("      Avg dependencies: {:.2}", metrics.avg_dependencies());
186    println!("      Density: {:.2}%", metrics.density() * 100.0);
187    println!();
188
189    println!("   🔍 Graph Properties:");
190    println!("      Is tree: {}", metrics.is_tree());
191    println!("      Is forest: {}", metrics.is_forest());
192    println!("      Is sparse: {}", metrics.is_sparse());
193    println!("      Is dense: {}", metrics.is_dense());
194    println!();
195
196    // Visualize with DAG
197    println!("   📊 Visualization:");
198    let mut dag = DAG::new();
199    dag.add_node(1, "types.h");
200    dag.add_node(2, "main.c");
201    dag.add_node(3, "main.o");
202    dag.add_node(4, "app.exe");
203    dag.add_edge(2, 3);
204    dag.add_edge(1, 3);
205    dag.add_edge(3, 4);
206
207    println!("{}", dag.render());
208
209    // Find the most impactful file
210    let mut max_impact = 0;
211    let mut most_impactful = "";
212    for file in &files {
213        let impact = compute_descendants_fn(&files, file, get_deps).len();
214        if impact > max_impact {
215            max_impact = impact;
216            most_impactful = file;
217        }
218    }
219
220    println!(
221        "   ⚡ Most impactful file: '{}' (affects {} other files)",
222        most_impactful, max_impact
223    );
224    println!();
225}