1use std::collections::BTreeMap;
2
3use serde::Serialize;
4use tinytemplate::TinyTemplate;
5
6use crate::{coloring::NodeColoringScheme, error::TemplateError, graph::Graph};
7
8pub trait Templating {
10 type Context;
12 type Value;
14
15 fn node(
17 &self,
18 graph: &Graph,
19 index: usize,
20 value: Self::Value,
21 context: Self::Context,
22 ) -> (String, String);
23
24 fn edge(&self, graph: &Graph, source: usize, target: usize) -> (String, String);
26}
27
28#[derive(Debug, Default)]
52pub struct TemplateOptions {
53 pub node_label_template: Option<String>,
54 pub node_tooltip_template: Option<String>,
55 pub edge_label_template: Option<String>,
56 pub edge_tooltip_template: Option<String>,
57}
58
59pub struct Template<'a>(pub(crate) TinyTemplate<'a>);
63
64impl<'a> Template<'a> {
65 pub fn new(template_options: &'a TemplateOptions) -> Result<Self, TemplateError> {
75 let mut template = TinyTemplate::new();
76 template.add_template(
77 "node_label",
78 template_options
79 .node_label_template
80 .as_deref()
81 .unwrap_or("{short}"),
82 )?;
83 template.add_template(
84 "node_tooltip",
85 template_options
86 .node_tooltip_template
87 .as_deref()
88 .unwrap_or("{full}\n{size_binary}\n{features}"),
89 )?;
90 template.add_template(
91 "edge_label",
92 template_options
93 .edge_label_template
94 .as_deref()
95 .unwrap_or("{features}"),
96 )?;
97 template.add_template(
98 "edge_tooltip",
99 template_options
100 .edge_tooltip_template
101 .as_deref()
102 .unwrap_or("{source} -> {target}"),
103 )?;
104 Ok(Template(template))
105 }
106}
107
108impl<'a> Templating for Template<'a> {
109 type Context = Option<NodeColoringScheme>;
110 type Value = Option<usize>;
111
112 fn node(
113 &self,
114 graph: &Graph,
115 index: usize,
116 value: Self::Value,
117 context: Self::Context,
118 ) -> (String, String) {
119 #[derive(Serialize)]
120 struct NodeContext<'a> {
121 short: &'a str,
122 extra: &'a str,
123 full: &'a str,
124 size: usize,
125 size_binary: String,
126 size_decimal: String,
127 scheme: Option<&'static str>,
128 value: Option<usize>,
129 value_binary: Option<String>,
130 value_decimal: Option<String>,
131 features: String,
132 }
133
134 let node = graph.node_weight(index);
135 let size = graph.size(index).unwrap_or_default();
136
137 let context = NodeContext {
138 short: node.short(),
139 extra: node.extra(),
140 full: node.full(),
141 size,
142 size_binary: humansize::format_size(size, humansize::BINARY),
143 size_decimal: humansize::format_size(size, humansize::DECIMAL),
144 scheme: context.map(Into::into),
145 value,
146 value_binary: value.map(|v| humansize::format_size(v, humansize::BINARY)),
147 value_decimal: value.map(|v| humansize::format_size(v, humansize::DECIMAL)),
148 features: features(&node.features),
149 };
150
151 let label = self
152 .0
153 .render("node_label", &context)
154 .unwrap_or_else(|e| e.to_string());
155 let tooltip = self
156 .0
157 .render("node_tooltip", &context)
158 .unwrap_or_else(|e| e.to_string());
159 (label, tooltip)
160 }
161
162 fn edge(&self, graph: &Graph, source: usize, target: usize) -> (String, String) {
163 #[derive(Serialize)]
164 struct EdgeContext<'a> {
165 source: &'a str,
166 target: &'a str,
167 features: String,
168 }
169
170 let edge = graph.edge_weight(source, target);
171 let source = graph.node_weight(source);
172 let target = graph.node_weight(target);
173
174 let context = EdgeContext {
175 source: source.short(),
176 target: target.short(),
177 features: features(&edge.features),
178 };
179
180 let label = self
181 .0
182 .render("edge_label", &context)
183 .unwrap_or_else(|e| e.to_string());
184 let tooltip = self
185 .0
186 .render("edge_tooltip", &context)
187 .unwrap_or_else(|e| e.to_string());
188 (label, tooltip)
189 }
190}
191
192fn features(features: &BTreeMap<String, Vec<String>>) -> String {
193 features
194 .iter()
195 .map(|(f, d)| {
196 if d.is_empty() {
197 f.clone()
198 } else {
199 format!("{f}({})", d.join(","))
200 }
201 })
202 .collect::<Vec<String>>()
203 .join(",\n")
204}