Skip to main content

vergen_pretty/header/
mod.rs

1// Copyright (c) 2022 pud developers
2//
3// Licensed under the Apache License, Version 2.0
4// <LICENSE-APACHE or https://www.apache.org/licenses/LICENSE-2.0> or the MIT
5// license <LICENSE-MIT or https://opensource.org/licenses/MIT>, at your
6// option. All files in the project carrying such notice may not be copied,
7// modified, or distributed except according to those terms.
8
9// Header
10
11use 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
34/// Environment tree type alias
35pub type Env = BTreeMap<&'static str, Option<&'static str>>;
36
37/// Convenience configuration around [`crate::Pretty`] to ease output generation.
38///
39/// # Example
40/// ```
41/// # use anyhow::Result;
42/// # use vergen_pretty::{Config, header, vergen_pretty_env};
43#[cfg_attr(feature = "color", doc = r"use vergen_pretty::Style;")]
44/// #
45/// # pub fn main() -> Result<()> {
46/// let mut buf = vec![];
47/// let config = Config::builder()
48#[cfg_attr(feature = "color", doc = r"    .style(Style::new().green())")]
49///     .prefix("HEADER_PREFIX")
50///     .env(vergen_pretty_env!())
51///     .suffix("HEADER_SUFFIX")
52///     .build();
53/// assert!(header(&config, Some(&mut buf)).is_ok());
54/// assert!(!buf.is_empty());
55/// #     Ok(())
56/// # }
57/// ```
58///
59#[derive(Builder, Clone, Debug, Default, PartialEq)]
60pub struct Config {
61    #[cfg(feature = "color")]
62    #[builder(default = false)]
63    /// Use a random [`Style`] color for the output
64    random_style: bool,
65    #[cfg(feature = "color")]
66    /// Use the given [`Style`] for the output (mutually exclusive with `random_style`)
67    style: Option<Style>,
68    /// An optional prefix string
69    #[builder(into)]
70    prefix: Option<&'static str>,
71    /// The vergen env (generated with the [`vergen_pretty_env`](crate::vergen_pretty_env) macro)
72    env: Env,
73    /// An optional suffix string
74    #[builder(into)]
75    suffix: Option<&'static str>,
76}
77
78/// Generate a pretty header based off your emitted `vergen` variables.
79///
80/// # Example
81/// ```
82/// # use anyhow::Result;
83/// # use vergen_pretty::{Config, header, vergen_pretty_env};
84/// #
85/// # pub fn main() -> Result<()> {
86/// let mut buf = vec![];
87/// let config = Config::builder()
88///     .prefix("HEADER_PREFIX")
89///     .env(vergen_pretty_env!())
90///     .suffix("HEADER_SUFFIX")
91///     .build();
92/// assert!(header(&config, Some(&mut buf)).is_ok());
93/// assert!(!buf.is_empty());
94/// #     Ok(())
95/// # }
96/// ```
97///
98/// # Errors
99///
100/// The errors are generally passed up from [`Pretty`] builder.
101///
102pub 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}