ralph/queue/graph/
build.rs1use crate::contracts::QueueFile;
17use std::collections::HashMap;
18
19use super::types::{DependencyGraph, TaskNode};
20
21pub fn build_graph(active: &QueueFile, done: Option<&QueueFile>) -> DependencyGraph {
22 let mut nodes: HashMap<String, TaskNode> = HashMap::new();
23
24 let mut dependents_map: HashMap<String, Vec<String>> = HashMap::new();
25 let mut blocked_by_map: HashMap<String, Vec<String>> = HashMap::new();
26 let mut related_by_map: HashMap<String, Vec<String>> = HashMap::new();
27 let mut duplicated_by_map: HashMap<String, Vec<String>> = HashMap::new();
28
29 let all_tasks = active
30 .tasks
31 .iter()
32 .chain(done.iter().flat_map(|d| d.tasks.iter()));
33
34 for task in all_tasks {
35 let task_id = task.id.trim().to_string();
36
37 for dep_id in &task.depends_on {
38 let dep_id = dep_id.trim().to_string();
39 if dep_id.is_empty() {
40 continue;
41 }
42 dependents_map
43 .entry(dep_id)
44 .or_default()
45 .push(task_id.clone());
46 }
47
48 for blocked_id in &task.blocks {
49 let blocked_id = blocked_id.trim().to_string();
50 if blocked_id.is_empty() {
51 continue;
52 }
53 blocked_by_map
54 .entry(blocked_id)
55 .or_default()
56 .push(task_id.clone());
57 }
58
59 for related_id in &task.relates_to {
60 let related_id = related_id.trim().to_string();
61 if related_id.is_empty() {
62 continue;
63 }
64 related_by_map
65 .entry(related_id)
66 .or_default()
67 .push(task_id.clone());
68 }
69
70 if let Some(duplicates_id) = &task.duplicates {
71 let duplicates_id = duplicates_id.trim().to_string();
72 if duplicates_id.is_empty() {
73 continue;
74 }
75 duplicated_by_map
76 .entry(duplicates_id)
77 .or_default()
78 .push(task_id.clone());
79 }
80 }
81
82 let all_tasks = active
83 .tasks
84 .iter()
85 .chain(done.iter().flat_map(|d| d.tasks.iter()));
86
87 for task in all_tasks {
88 let task_id = task.id.trim().to_string();
89
90 let dependencies: Vec<String> = task
91 .depends_on
92 .iter()
93 .map(|s| s.trim().to_string())
94 .filter(|s| !s.is_empty())
95 .collect();
96
97 let blocks: Vec<String> = task
98 .blocks
99 .iter()
100 .map(|s| s.trim().to_string())
101 .filter(|s| !s.is_empty())
102 .collect();
103
104 let relates_to: Vec<String> = task
105 .relates_to
106 .iter()
107 .map(|s| s.trim().to_string())
108 .filter(|s| !s.is_empty())
109 .collect();
110
111 let duplicates = task
112 .duplicates
113 .as_ref()
114 .map(|s| s.trim().to_string())
115 .filter(|s| !s.is_empty());
116
117 nodes.insert(
118 task_id.clone(),
119 TaskNode {
120 task: task.clone(),
121 dependencies,
122 dependents: dependents_map.get(&task_id).cloned().unwrap_or_default(),
123 blocks,
124 blocked_by: blocked_by_map.get(&task_id).cloned().unwrap_or_default(),
125 relates_to,
126 related_by: related_by_map.get(&task_id).cloned().unwrap_or_default(),
127 duplicates,
128 duplicated_by: duplicated_by_map.get(&task_id).cloned().unwrap_or_default(),
129 },
130 );
131 }
132
133 let roots: Vec<String> = nodes
134 .iter()
135 .filter(|(_, n)| n.dependents.is_empty())
136 .map(|(id, _)| id.clone())
137 .collect();
138
139 let leaves: Vec<String> = nodes
140 .iter()
141 .filter(|(_, n)| n.dependencies.is_empty())
142 .map(|(id, _)| id.clone())
143 .collect();
144
145 DependencyGraph::from_parts(nodes, roots, leaves)
146}