vergen_pretty/header/
mod.rs1use crate::{Prefix, Pretty, Suffix};
12
13use anyhow::Result;
14use bon::Builder;
15use console::Style;
16#[cfg(feature = "color")]
17use rand::RngExt;
18use std::{collections::BTreeMap, io::Write};
19
20#[cfg(feature = "color")]
21fn from_u8(val: u8) -> Style {
22 let style = Style::new();
23 match val {
24 0 => style.green(),
25 1 => style.yellow(),
26 2 => style.blue(),
27 3 => style.magenta(),
28 4 => style.cyan(),
29 5 => style.white(),
30 _ => style.red(),
31 }
32}
33
34pub type Env = BTreeMap<&'static str, Option<&'static str>>;
36
37#[cfg_attr(feature = "color", doc = r"use vergen_pretty::Style;")]
44#[cfg_attr(feature = "color", doc = r" .style(Style::new().green())")]
49#[derive(Builder, Clone, Debug, Default, PartialEq)]
60pub struct Config {
61 #[cfg(feature = "color")]
62 #[builder(default = false)]
63 random_style: bool,
65 #[cfg(feature = "color")]
66 style: Option<Style>,
68 #[builder(into)]
70 prefix: Option<&'static str>,
71 env: Env,
73 #[builder(into)]
75 suffix: Option<&'static str>,
76}
77
78pub fn header<T>(config: &Config, writer: Option<&mut T>) -> Result<()>
103where
104 T: Write + ?Sized,
105{
106 if let Some(writer) = writer {
107 output_to_writer(writer, config)?;
108 }
109 trace(config);
110 Ok(())
111}
112
113#[cfg(feature = "color")]
114fn output_to_writer<T>(writer: &mut T, config: &Config) -> Result<()>
115where
116 T: Write + ?Sized,
117{
118 let app_style = get_style(config.random_style, config.style.clone());
119 Pretty::builder()
120 .env(config.env.clone())
121 .prefix(get_prefix(config.prefix, &app_style))
122 .suffix(get_suffix(config.suffix, &app_style))
123 .build()
124 .display(writer)?;
125 Ok(())
126}
127
128#[cfg(not(feature = "color"))]
129fn output_to_writer<T>(writer: &mut T, config: &Config) -> Result<()>
130where
131 T: Write + ?Sized,
132{
133 let app_style = get_style(false, None);
134 Pretty::builder()
135 .env(config.env.clone())
136 .prefix(get_prefix(config.prefix, &app_style))
137 .suffix(get_suffix(config.suffix, &app_style))
138 .build()
139 .display(writer)?;
140 Ok(())
141}
142
143#[cfg(all(feature = "trace", feature = "color"))]
144fn trace(config: &Config) {
145 let app_style = get_style(config.random_style, config.style.clone());
146 Pretty::builder()
147 .env(config.env.clone())
148 .prefix(get_prefix(config.prefix, &app_style))
149 .suffix(get_suffix(config.suffix, &app_style))
150 .build()
151 .trace();
152}
153
154#[cfg(all(feature = "trace", not(feature = "color")))]
155fn trace(config: &Config) {
156 let app_style = get_style(false, None);
157 Pretty::builder()
158 .env(config.env.clone())
159 .prefix(get_prefix(config.prefix, &app_style))
160 .suffix(get_suffix(config.suffix, &app_style))
161 .build()
162 .trace();
163}
164
165#[cfg(not(feature = "trace"))]
166fn trace(_config: &Config) {}
167
168#[cfg(feature = "color")]
169fn get_style(random_style: bool, style_opt: Option<Style>) -> Style {
170 if random_style {
171 let mut rng = rand::rng();
172 from_u8(rng.random_range(0..7))
173 } else if let Some(style) = style_opt {
174 style
175 } else {
176 Style::new()
177 }
178}
179
180#[cfg(not(feature = "color"))]
181#[allow(clippy::needless_pass_by_value)]
182fn get_style(_random_style: bool, _style_opt: Option<Style>) -> Style {
183 Style::new()
184}
185
186#[cfg(feature = "color")]
187fn get_prefix(prefix_opt: Option<&'static str>, app_style: &Style) -> Prefix {
188 if let Some(prefix) = prefix_opt {
189 Prefix::builder()
190 .lines(prefix.lines().map(str::to_string).collect())
191 .style(app_style.clone())
192 .build()
193 } else {
194 Prefix::builder().lines(vec![]).build()
195 }
196}
197
198#[cfg(not(feature = "color"))]
199fn get_prefix(prefix_opt: Option<&'static str>, _app_style: &Style) -> Prefix {
200 if let Some(prefix) = prefix_opt {
201 Prefix::builder()
202 .lines(prefix.lines().map(str::to_string).collect())
203 .build()
204 } else {
205 Prefix::builder().lines(vec![]).build()
206 }
207}
208
209#[cfg(feature = "color")]
210fn get_suffix(suffix_opt: Option<&'static str>, app_style: &Style) -> Suffix {
211 if let Some(suffix) = suffix_opt {
212 Suffix::builder()
213 .lines(suffix.lines().map(str::to_string).collect())
214 .style(app_style.clone())
215 .build()
216 } else {
217 Suffix::builder().lines(vec![]).build()
218 }
219}
220
221#[cfg(not(feature = "color"))]
222fn get_suffix(suffix_opt: Option<&'static str>, _app_style: &Style) -> Suffix {
223 if let Some(suffix) = suffix_opt {
224 Suffix::builder()
225 .lines(suffix.lines().map(str::to_string).collect())
226 .build()
227 } else {
228 Suffix::builder().lines(vec![]).build()
229 }
230}
231
232#[cfg(test)]
233mod test {
234 use super::Config;
235 #[cfg(feature = "color")]
236 use super::from_u8;
237 #[cfg(feature = "__vergen_test")]
238 use super::header;
239 use anyhow::Result;
240 #[cfg(feature = "color")]
241 use console::Style;
242 #[cfg(feature = "__vergen_test")]
243 use regex::Regex;
244 use std::io::Write;
245 #[cfg(feature = "__vergen_test")]
246 use std::sync::LazyLock;
247
248 #[cfg(feature = "__vergen_test")]
249 const HEADER_PREFIX: &str = r"██████╗ ██╗ ██╗██████╗ ██╗ ██╗
250██╔══██╗██║ ██║██╔══██╗██║ ██║
251██████╔╝██║ ██║██║ ██║██║ █╗ ██║
252██╔═══╝ ██║ ██║██║ ██║██║███╗██║
253██║ ╚██████╔╝██████╔╝╚███╔███╔╝
254╚═╝ ╚═════╝ ╚═════╝ ╚══╝╚══╝
255
2564a61736f6e204f7a696173
257";
258
259 #[cfg(feature = "__vergen_test")]
260 const HEADER_SUFFIX: &str = r"
2614a61736f6e204f7a696173
262";
263
264 #[cfg(feature = "__vergen_test")]
265 static BUILD_TIMESTAMP: LazyLock<Regex> =
266 LazyLock::new(|| Regex::new(r"Timestamp \( build\)").unwrap());
267 #[cfg(feature = "__vergen_test")]
268 static BUILD_SEMVER: LazyLock<Regex> =
269 LazyLock::new(|| Regex::new(r"Semver \( rustc\)").unwrap());
270 #[cfg(feature = "__vergen_test")]
271 static GIT_BRANCH: LazyLock<Regex> =
272 LazyLock::new(|| Regex::new(r"Branch \( git\)").unwrap());
273
274 #[test]
275 #[allow(clippy::clone_on_copy, clippy::redundant_clone)]
276 fn header_clone_works() {
277 let config = Config::default();
278 let another = config.clone();
279 assert_eq!(another, config);
280 }
281
282 #[test]
283 fn builder_debug_works() -> Result<()> {
284 let config = Config::default();
285 let mut buf = vec![];
286 write!(buf, "{config:?}")?;
287 assert!(!buf.is_empty());
288 Ok(())
289 }
290
291 #[test]
292 #[cfg(feature = "color")]
293 fn from_u8_works() {
294 assert_eq!(from_u8(0), Style::new().green());
295 assert_eq!(from_u8(1), Style::new().yellow());
296 assert_eq!(from_u8(2), Style::new().blue());
297 assert_eq!(from_u8(3), Style::new().magenta());
298 assert_eq!(from_u8(4), Style::new().cyan());
299 assert_eq!(from_u8(5), Style::new().white());
300 assert_eq!(from_u8(6), Style::new().red());
301 assert_eq!(from_u8(7), Style::new().red());
302 }
303
304 #[test]
305 #[cfg(feature = "__vergen_test")]
306 fn header_default() {
307 use super::Config;
308 use crate::vergen_pretty_env;
309
310 let mut buf = vec![];
311 let config = Config::builder().env(vergen_pretty_env!()).build();
312 assert!(header(&config, Some(&mut buf)).is_ok());
313 assert!(!buf.is_empty());
314 let header_str = String::from_utf8_lossy(&buf);
315 assert!(BUILD_TIMESTAMP.is_match(&header_str));
316 assert!(BUILD_SEMVER.is_match(&header_str));
317 assert!(GIT_BRANCH.is_match(&header_str));
318 }
319
320 #[test]
321 #[cfg(feature = "__vergen_test")]
322 fn header_no_writer() {
323 use super::Config;
324 use crate::vergen_pretty_env;
325
326 let buf: Vec<u8> = vec![];
327 let config = Config::builder().env(vergen_pretty_env!()).build();
328 assert!(header(&config, None::<&mut Vec<u8>>).is_ok());
329 assert!(buf.is_empty());
330 }
331
332 #[test]
333 #[cfg(feature = "__vergen_test")]
334 fn header_all() {
335 use super::Config;
336 use crate::vergen_pretty_env;
337
338 let mut buf = vec![];
339 let config = Config::builder()
340 .prefix(HEADER_PREFIX)
341 .env(vergen_pretty_env!())
342 .suffix(HEADER_SUFFIX)
343 .build();
344 assert!(header(&config, Some(&mut buf)).is_ok());
345 assert!(!buf.is_empty());
346 let header_str = String::from_utf8_lossy(&buf);
347 assert!(BUILD_TIMESTAMP.is_match(&header_str));
348 assert!(BUILD_SEMVER.is_match(&header_str));
349 assert!(GIT_BRANCH.is_match(&header_str));
350 }
351
352 #[test]
353 #[cfg(all(feature = "__vergen_test", feature = "color"))]
354 fn header_all_color_random() {
355 use super::Config;
356 use crate::vergen_pretty_env;
357
358 let mut buf = vec![];
359 let config = Config::builder()
360 .random_style(true)
361 .prefix(HEADER_PREFIX)
362 .env(vergen_pretty_env!())
363 .suffix(HEADER_SUFFIX)
364 .build();
365 assert!(header(&config, Some(&mut buf)).is_ok());
366 assert!(!buf.is_empty());
367 let header_str = String::from_utf8_lossy(&buf);
368 assert!(BUILD_TIMESTAMP.is_match(&header_str));
369 assert!(BUILD_SEMVER.is_match(&header_str));
370 assert!(GIT_BRANCH.is_match(&header_str));
371 }
372
373 #[test]
374 #[cfg(all(feature = "__vergen_test", feature = "color"))]
375 fn header_all_color_specific() {
376 use super::Config;
377 use crate::vergen_pretty_env;
378
379 let mut buf = vec![];
380 let config = Config::builder()
381 .style(Style::new().green())
382 .prefix(HEADER_PREFIX)
383 .env(vergen_pretty_env!())
384 .suffix(HEADER_SUFFIX)
385 .build();
386 assert!(header(&config, Some(&mut buf)).is_ok());
387 assert!(!buf.is_empty());
388 let header_str = String::from_utf8_lossy(&buf);
389 assert!(BUILD_TIMESTAMP.is_match(&header_str));
390 assert!(BUILD_SEMVER.is_match(&header_str));
391 assert!(GIT_BRANCH.is_match(&header_str));
392 }
393
394 #[test]
395 #[cfg(debug_assertions)]
396 #[cfg(feature = "__vergen_test")]
397 fn header_writes() {
398 use super::Config;
399 use crate::vergen_pretty_env;
400
401 let mut buf = vec![];
402 let config = Config::builder()
403 .prefix(HEADER_PREFIX)
404 .env(vergen_pretty_env!())
405 .build();
406 assert!(header(&config, Some(&mut buf)).is_ok());
407 assert!(!buf.is_empty());
408 let header_str = String::from_utf8_lossy(&buf);
409 assert!(BUILD_TIMESTAMP.is_match(&header_str));
410 assert!(BUILD_SEMVER.is_match(&header_str));
411 assert!(GIT_BRANCH.is_match(&header_str));
412 }
413
414 #[test]
415 #[cfg(not(debug_assertions))]
416 #[cfg(feature = "__vergen_test")]
417 fn header_writes() {
418 use super::Config;
419 use crate::vergen_pretty_env;
420
421 let mut buf = vec![];
422 let config = Config::builder()
423 .prefix(HEADER_PREFIX)
424 .env(vergen_pretty_env!())
425 .build();
426 assert!(header(&config, Some(&mut buf)).is_ok());
427 assert!(!buf.is_empty());
428 let header_str = String::from_utf8_lossy(&buf);
429 assert!(BUILD_TIMESTAMP.is_match(&header_str));
430 assert!(BUILD_SEMVER.is_match(&header_str));
431 assert!(GIT_BRANCH.is_match(&header_str));
432 }
433}