cargo_whatfeatures/printer/
workspace.rs1use super::{
2 deps::{GroupedDeps, SortedDeps},
3 labels,
4 tree::{Node, Printer},
5};
6use crate::{
7 features::{Dependency, Features, Workspace},
8 Options, Theme,
9};
10use std::{
11 collections::{BTreeMap, BTreeSet},
12 io::Write,
13};
14
15pub struct WorkspacePrinter<'a, W: ?Sized> {
16 writer: &'a mut W,
17 theme: Theme,
18 workspace: Workspace,
19 options: Options,
20}
21
22impl<'a, W: ?Sized> WorkspacePrinter<'a, W>
23where
24 W: Write,
25{
26 pub fn new(writer: &'a mut W, workspace: Workspace, options: Options) -> Self {
27 Self {
28 writer,
29 theme: options.theme,
30 workspace,
31 options,
32 }
33 }
34
35 pub fn print(self) -> std::io::Result<()> {
36 let mut list = self
37 .workspace
38 .map
39 .into_iter()
40 .map(|(_, v)| v)
41 .collect::<Vec<_>>();
42
43 list.sort_by(|l, r| l.name.cmp(&r.name));
44
45 let (options, theme) = (self.options, self.theme);
46
47 let mut nodes = list.iter().filter_map(|f| make_child_node(f, &options));
48
49 match list.len() {
50 0 => unreachable!("empty tree"),
51 1 => {
52 if let Some(node) = nodes.next() {
53 node.print(self.writer, &theme)
54 } else {
55 Err(std::io::Error::new(
56 std::io::ErrorKind::Other,
57 "An empty tree was returned by cargo-metdata",
58 ))
59 }
60 }
61 _ => {
62 let name = format!(
63 "workspace for {}",
64 theme.workspace.paint(&self.workspace.hint)
65 );
66 Node::new(name, nodes)
67 }
68 .print(self.writer, &theme),
69 }
70 }
71}
72
73fn make_child_node(features: &Features, options: &Options) -> Option<Node> {
74 let Options {
75 print_features, show_deps, verbose,
78 show_private,
79 theme,
80 } = *options;
81
82 if !features.published && !show_private {
83 return None;
84 }
85
86 let header = if features.published {
87 format!(
88 "{} = \"{}\"",
89 theme.name.paint(&features.name),
90 theme.version.paint(&features.version),
91 )
92 } else {
93 format!(
94 "{} = \"{}\" {}",
95 theme.name.paint(&features.name),
96 theme.version.paint(&features.version),
97 theme.is_not_published.paint("(restricted)")
98 )
99 };
100
101 let mut parent = Node::empty(header);
102
103 if print_features {
104 let node = make_features_node(features, &theme, verbose);
105 parent.add_child(node);
106 }
107
108 if verbose || (!print_features && show_deps) {
109 let node = make_opt_deps_node(features, &theme, verbose);
110 parent.add_child(node);
111 }
112
113 if show_deps {
114 let node = make_deps_node(features, &theme, verbose);
115 parent.add_child(node)
116 }
117
118 Some(parent)
119}
120
121fn make_opt_deps_node(features: &Features, theme: &Theme, verbose: bool) -> Node {
122 let sorted = SortedDeps::from_kind_map(features.optional_deps.clone());
123 if !sorted.normal.has_deps() {
124 let name = theme
125 .no_optional_deps
126 .paint(labels::NO_OPTIONAL_DEPENDENCIES);
127 return Node::empty(name);
128 }
129
130 build_features_tree(
131 theme.optional_deps.paint(labels::OPTIONAL_DEPENDENCIES),
132 sorted.normal,
133 theme,
134 verbose,
135 )
136}
137
138fn make_deps_node(features: &Features, theme: &Theme, verbose: bool) -> Node {
139 let sorted = SortedDeps::from_kind_map(features.required_deps.clone());
140 if !sorted.normal.has_deps() && !sorted.development.has_deps() && !sorted.build.has_deps() {
141 return Node::empty(
142 theme
143 .no_required_deps
144 .paint(labels::NO_REQUIRED_DEPENDENCIES),
145 );
146 }
147
148 let mut nodes = vec![];
149 if sorted.normal.has_deps() {
150 nodes.push(build_features_tree(
151 theme.normal_deps.paint(labels::NORMAL),
152 sorted.normal,
153 theme,
154 verbose,
155 ));
156 } else {
157 let name = theme.no_required_deps.paint(labels::NO_NORMAL_DEPENDENCIES);
158 nodes.push(Node::empty(name));
159 }
160
161 if sorted.development.has_deps() {
162 nodes.push(build_features_tree(
163 theme.dev_deps.paint(labels::DEVELOPMENT),
164 sorted.development,
165 theme,
166 verbose,
167 ));
168 } else {
169 let name = theme.no_dev_deps.paint(labels::NO_DEVELOPMENT_DEPENDENCIES);
170 nodes.push(Node::empty(name));
171 }
172
173 if sorted.build.has_deps() {
174 nodes.push(build_features_tree(
175 theme.build_deps.paint(labels::BUILD),
176 sorted.build,
177 theme,
178 verbose,
179 ));
180 } else {
181 let name = theme.no_build_deps.paint(labels::NO_BUILD_DEPENDENCIES);
182 nodes.push(Node::empty(name));
183 }
184
185 let name = theme.required_deps.paint(labels::REQUIRED_DEPENDENCIES);
186 Node::new(name, nodes)
187}
188
189fn make_features_node(features: &Features, theme: &Theme, verbose: bool) -> Node {
190 let mut sorted: BTreeMap<_, BTreeSet<_>> = features
191 .features
192 .iter()
193 .map(|(k, v)| (&*k, v.iter().collect()))
194 .collect();
195
196 if sorted.is_empty() {
197 return Node::empty(theme.no_features.paint(labels::NO_FEATURES));
198 }
199
200 let mut default = None;
201
202 let default_node = match sorted.remove(&"default".to_string()) {
203 Some(def) if !def.is_empty() => {
204 let node = Node::new(
205 theme.default.paint(labels::DEFAULT),
206 def.iter().map(|s| theme.feature_implies.paint(s)),
207 );
208 default.replace(def);
209 node
210 }
211 _ => Node::empty(theme.no_default_features.paint(labels::NO_DEFAULT_FEATURES)),
212 };
213
214 let iter = sorted.iter().map(|(k, v)| {
215 let (probably, other, features);
216 let k: &dyn std::fmt::Display = if k.starts_with('_') {
217 probably = theme.probably_internal.paint(k);
218 &probably
219 } else if default.as_ref().filter(|def| def.contains(k)).is_some() {
220 other = format!(
221 "{} ({})",
222 theme.feature_name.paint(k),
223 theme.default.paint("default")
224 );
225 &other
226 } else {
227 features = theme.feature_name.paint(k);
228 &features
229 };
230
231 if v.is_empty() || !verbose {
232 return Node::empty(k);
233 }
234
235 let children = v.iter().map(|s| {
236 let color = if s.starts_with('_') {
237 theme.probably_internal
238 } else {
239 theme.feature_implies
240 };
241 color.paint(s)
242 });
243 Node::new(k, children)
244 });
245
246 Node::new(
247 theme.features.paint(labels::FEATURES),
248 std::iter::once(default_node).chain(iter),
249 )
250}
251
252fn build_features_tree(
253 text: impl ToString,
254 deps: GroupedDeps,
255 theme: &Theme,
256 verbose: bool,
257) -> Node {
258 let format = |(target, deps)| {
259 Node::new(
260 format!("for {}", theme.target.paint(target)),
261 build_features(deps, theme, verbose),
262 )
263 };
264
265 let iter = deps
266 .with_targets
267 .into_iter()
268 .map(format)
269 .chain(build_features(deps.without_targets, theme, verbose));
270
271 Node::new(text, iter)
272}
273
274fn build_features<'a>(
275 deps: impl IntoIterator<Item = Dependency> + 'a,
276 theme: &'a Theme,
277 verbose: bool,
278) -> impl Iterator<Item = Node> + 'a {
279 let map = move |dep| {
280 let name = format_dep(&dep, theme);
281 if dep.features.is_empty() {
282 return Node::empty(name);
283 }
284
285 let enabled = theme
286 .has_enabled_features
287 .paint(labels::HAS_ENABLED_FEATURES);
288
289 let name = format!("{}{}", name, enabled);
290
291 if verbose {
292 let features = dep.features.into_iter().map(|s| theme.dep_feature.paint(s));
293 Node::new(name, features)
294 } else {
295 Node::empty(name)
296 }
297 };
298
299 deps.into_iter().map(map)
300}
301
302fn format_dep(dep: &Dependency, theme: &Theme) -> String {
303 if let Some(renamed) = dep.rename.as_deref() {
304 let renamed = format!("(renamed to {})", theme.renamed_target.paint(renamed));
305 return format!(
306 "{} = \"{}\" {}",
307 theme.name.paint(&dep.name),
308 theme.version.paint(&dep.req),
309 theme.renamed.paint(renamed).wrap()
310 );
311 }
312
313 format!(
314 "{} = \"{}\" ",
315 theme.name.paint(&dep.name),
316 theme.version.paint(&dep.req),
317 )
318}