1use 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#[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 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 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 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}