cargo_whatfeatures/printer/
workspace.rs

1use 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, // not -n
76        show_deps,      // -d
77        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}