hakari/explain/
display.rs

1// Copyright (c) The cargo-guppy Contributors
2// SPDX-License-Identifier: MIT OR Apache-2.0
3
4use crate::explain::HakariExplain;
5use guppy::graph::{feature::StandardFeatures, DependencyDirection};
6use itertools::{Itertools, Position};
7use owo_colors::{OwoColorize, Style};
8use std::{collections::BTreeSet, fmt};
9use tabular::{Row, Table};
10
11/// A display formatter for [`HakariExplain`].
12///
13/// Requires the `cli-support` feature.
14#[derive(Clone, Debug)]
15pub struct HakariExplainDisplay<'g, 'a, 'explain> {
16    explain: &'explain HakariExplain<'g, 'a>,
17    styles: Box<Styles>,
18}
19
20impl<'g, 'a, 'explain> HakariExplainDisplay<'g, 'a, 'explain> {
21    pub(super) fn new(explain: &'explain HakariExplain<'g, 'a>) -> Self {
22        Self {
23            explain,
24            styles: Box::default(),
25        }
26    }
27
28    /// Adds ANSI color codes to the output.
29    pub fn colorize(&mut self) -> &mut Self {
30        self.styles.colorize();
31        self
32    }
33
34    fn display_platform_str(
35        &self,
36        platform_idx: Option<usize>,
37        f: &mut fmt::Formatter,
38    ) -> fmt::Result {
39        match platform_idx {
40            Some(idx) => {
41                let triple_str = self.explain.platforms[idx].triple_str();
42                write!(f, "{}", triple_str.style(self.styles.platform_style))
43            }
44            None => write!(f, "all"),
45        }
46    }
47}
48
49const DITTO_MARK: &str = "\"";
50
51impl fmt::Display for HakariExplainDisplay<'_, '_, '_> {
52    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
53        let mut table = Table::new("  {:^}  |  {:^}  {:^}  {:^}");
54        // header row
55        let row = Row::new()
56            .with_ansi_cell("package".style(self.styles.header_style))
57            .with_ansi_cell("include dev?".style(self.styles.header_style))
58            .with_ansi_cell("features".style(self.styles.header_style))
59            .with_ansi_cell("platform".style(self.styles.header_style));
60        table.add_row(row);
61
62        for (build_platform, explain_map) in self.explain.explain_maps() {
63            for (&features, inner) in explain_map {
64                let heading = format!(
65                    "\non the {} platform, feature set {} was built by:\n",
66                    build_platform.style(self.styles.build_platform_style),
67                    FeatureDisplay { features }.style(self.styles.feature_style),
68                );
69                table.add_heading(heading);
70
71                let package_set = self
72                    .explain
73                    .graph
74                    .resolve_ids(inner.workspace_packages.keys().copied())
75                    .expect("keys derived from package graph");
76
77                // Print output in reverse dependency order within the workspace.
78                for package_id in package_set.package_ids(DependencyDirection::Reverse) {
79                    let inner_value = &inner.workspace_packages[package_id];
80
81                    let name = inner_value.metadata.name();
82                    let name_display = name.style(self.styles.package_name_style);
83                    for (idx, (include_dev, standard_features, platform_idx)) in
84                        inner_value.sets.iter().enumerate()
85                    {
86                        let include_dev_display =
87                            include_dev.display_with(&self.styles.star_style, |include_dev, f| {
88                                match include_dev {
89                                    true => write!(f, "{}", "yes".style(self.styles.yes_style)),
90                                    false => write!(f, "{}", "no".style(self.styles.no_style)),
91                                }
92                            });
93                        let features_display = standard_features.display_with(
94                            &self.styles.star_style,
95                            |features, f| {
96                                let features_str = match features {
97                                    StandardFeatures::None => "none",
98                                    StandardFeatures::Default => "default",
99                                    StandardFeatures::All => "all",
100                                };
101                                write!(
102                                    f,
103                                    "{}",
104                                    features_str.style(self.styles.standard_features_style)
105                                )
106                            },
107                        );
108
109                        let platform_display = platform_idx
110                            .display_with(&self.styles.star_style, |&platform_idx, f| {
111                                self.display_platform_str(platform_idx, f)
112                            });
113
114                        let mut row = Row::new();
115                        if idx == 0 {
116                            row.add_ansi_cell(&name_display);
117                        } else {
118                            row.add_ansi_cell(DITTO_MARK.style(self.styles.ditto_style));
119                        }
120
121                        row.add_ansi_cell(include_dev_display)
122                            .add_ansi_cell(features_display)
123                            .add_ansi_cell(platform_display);
124                        table.add_row(row);
125                    }
126                }
127
128                for (idx, platform_idx) in inner.fixup_platforms.iter().enumerate() {
129                    let mut row = Row::new();
130                    if idx == 0 {
131                        row.add_ansi_cell("post-compute fixup");
132                    } else {
133                        row.add_ansi_cell(DITTO_MARK.style(self.styles.ditto_style));
134                    }
135
136                    let platform_display = platform_idx
137                        .display_with(&self.styles.star_style, |&platform_idx, f| {
138                            self.display_platform_str(platform_idx, f)
139                        });
140
141                    row.add_ansi_cell("-")
142                        .add_ansi_cell("-")
143                        .add_ansi_cell(platform_display);
144                    table.add_row(row);
145                }
146            }
147        }
148
149        writeln!(f, "{}", table)
150    }
151}
152
153#[derive(Clone, Debug, Default)]
154struct Styles {
155    build_platform_style: Style,
156    feature_style: Style,
157
158    header_style: Style,
159    package_name_style: Style,
160    ditto_style: Style,
161    star_style: Style,
162    yes_style: Style,
163    no_style: Style,
164    standard_features_style: Style,
165    platform_style: Style,
166}
167
168impl Styles {
169    fn colorize(&mut self) {
170        self.build_platform_style = Style::new().blue().bold();
171        self.feature_style = Style::new().purple().bold();
172
173        self.header_style = Style::new().bold();
174        self.package_name_style = Style::new().bold();
175        self.ditto_style = Style::new().dimmed();
176        self.star_style = Style::new().dimmed();
177        self.yes_style = Style::new().bright_green();
178        self.no_style = Style::new().bright_red();
179        self.standard_features_style = Style::new().bright_blue();
180        self.platform_style = Style::new().yellow();
181    }
182}
183
184#[derive(Clone, Debug)]
185struct FeatureDisplay<'g, 'a> {
186    features: &'a BTreeSet<&'g str>,
187}
188
189impl fmt::Display for FeatureDisplay<'_, '_> {
190    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
191        if self.features.is_empty() {
192            return write!(f, "(no features)");
193        }
194
195        for (position, feature) in self.features.iter().with_position() {
196            match position {
197                Position::First | Position::Middle => {
198                    write!(f, "{}, ", feature)?;
199                }
200                Position::Last | Position::Only => {
201                    write!(f, "{}", feature)?;
202                }
203            }
204        }
205        Ok(())
206    }
207}