1#[cfg(any(test, feature = "indexedlog-backend"))]
9use std::cmp::Ordering;
10#[cfg(any(test, feature = "indexedlog-backend"))]
11use std::io::Write;
12
13use anyhow::Result;
14use nonblocking::non_blocking_result;
15pub use renderdag::Ancestor;
17pub use renderdag::GraphRow;
18pub use renderdag::GraphRowRenderer;
19pub use renderdag::Renderer;
20pub use renderdag::*;
21
22#[cfg(any(test, feature = "indexedlog-backend"))]
23use crate::ops::IdConvert;
24use crate::set::SyncSetQuery;
25#[cfg(any(test, feature = "indexedlog-backend"))]
26use crate::Dag;
27use crate::DagAlgorithm;
28#[cfg(any(test, feature = "indexedlog-backend"))]
29use crate::Group;
30#[cfg(any(test, feature = "indexedlog-backend"))]
31use crate::IdSpan;
32#[cfg(any(test, feature = "indexedlog-backend"))]
33use crate::Level;
34use crate::Set;
35use crate::Vertex;
36
37pub fn render_dag(
39 dag: &(impl DagAlgorithm + ?Sized),
40 get_message: impl Fn(&Vertex) -> Option<String>,
41) -> Result<String> {
42 let mut renderer = GraphRowRenderer::new().output().build_box_drawing();
43
44 let mut out = String::new();
45 let next_rows = dag_to_renderer_next_rows(dag, None)?;
46 for (node, parents) in next_rows {
47 let mut name = format!("{:?}", &node);
48 let message = get_message(&node).unwrap_or_default();
49 let row = if name.len() == 1 {
50 renderer.next_row(node, parents, name, message)
51 } else {
52 if !message.is_empty() {
53 name += &format!(" {}", message);
54 }
55 renderer.next_row(node, parents, String::from("o"), name)
56 };
57 out.push_str(&row);
58 }
59
60 let output = format!(
61 "\n{}",
62 out.trim_end()
63 .lines()
64 .map(|l| format!(" {}", l))
65 .collect::<Vec<_>>()
66 .join("\n")
67 );
68 Ok(output)
69}
70
71pub fn render_dag_structured(
76 dag: &dyn DagAlgorithm,
77 subset: Option<Set>,
78) -> Result<Vec<GraphRow<Vertex>>> {
79 let mut renderer = GraphRowRenderer::new();
80 let next_rows = dag_to_renderer_next_rows(dag, subset)?;
81 let mut out = Vec::with_capacity(next_rows.len());
82 for (node, parents) in next_rows {
83 let name = String::from_utf8_lossy(node.as_ref()).into_owned();
84 let message = Default::default();
85 let row = renderer.next_row(node, parents, name, message);
86 out.push(row);
87 }
88 Ok(out)
89}
90
91fn dag_to_renderer_next_rows(
96 dag: &(impl DagAlgorithm + ?Sized),
97 subset: Option<Set>,
98) -> Result<Vec<(Vertex, Vec<Ancestor<Vertex>>)>> {
99 let subset = match subset {
100 None => non_blocking_result(dag.all())?,
101 Some(set) => set,
102 };
103 let iter: Vec<_> = subset.iter()?.collect::<crate::Result<_>>()?;
104 let subdag = non_blocking_result(dag.subdag(subset))?;
105
106 let mut out = Vec::with_capacity(iter.len());
107 for node in iter {
108 let direct_parents = non_blocking_result(dag.parent_names(node.clone()))?;
109 let subdag_parents = non_blocking_result(subdag.parent_names(node.clone()))?;
110 let mut parents: Vec<Ancestor<Vertex>> = subdag_parents
111 .iter()
112 .cloned()
113 .map(|p| {
114 if direct_parents.contains(&p) {
115 Ancestor::Parent(p)
116 } else {
117 Ancestor::Ancestor(p)
118 }
119 })
120 .collect();
121 if parents.len() < direct_parents.len() {
122 let subdag_ancestors =
123 non_blocking_result(dag.ancestors(Set::from_static_names(subdag_parents.clone())))?;
124 for p in &direct_parents {
125 if subdag_parents.contains(p) {
126 continue;
127 }
128 let direct_reachable = non_blocking_result(dag.ancestors(p.into()))?;
130 if direct_reachable
131 .intersection(&subdag_ancestors)
132 .is_empty()?
133 {
134 parents.push(Ancestor::Anonymous)
135 }
136 }
137 }
138 out.push((node, parents));
139 }
140
141 Ok(out)
142}
143
144#[cfg(any(test, feature = "indexedlog-backend"))]
145pub fn render_segment_dag(
146 mut out: impl Write,
147 dag: &Dag,
148 level: Level,
149 group: Group,
150) -> Result<()> {
151 let mut renderer = renderdag::GraphRowRenderer::new()
152 .output()
153 .build_box_drawing();
154 let segs = dag.dag.next_segments(group.min_id(), level)?;
155
156 for seg in segs.iter().rev() {
157 let mut parents = vec![];
158 for parent_id in seg.parents()? {
159 let parent_span: IdSpan = parent_id.into();
161 let parent_idx = segs.binary_search_by(|s| {
162 let span = s.span().unwrap();
163 if span.contains(parent_id) {
164 Ordering::Equal
165 } else {
166 span.cmp(&parent_span)
167 }
168 });
169
170 if let Ok(parent_idx) = parent_idx {
171 parents.push(Ancestor::Parent(&segs[parent_idx]));
172 } else {
173 parents.push(Ancestor::Anonymous);
175 }
176 }
177
178 let span = seg.span()?;
179 let get_hex = |id| -> String {
180 non_blocking_result(dag.vertex_name(id))
181 .map(|s| format!("{:.12?}", s))
182 .unwrap_or_default()
183 };
184 let name = format!(
185 "{}({})-{}({})",
186 get_hex(span.low),
187 span.low,
188 get_hex(span.high),
189 span.high,
190 );
191 let row = renderer.next_row(seg, parents, String::from("o"), name);
192 write!(out, "{}", row)?;
193 }
194
195 Ok(())
196}