Skip to main content

pfetch_logo_parser/
lib.rs

1use regex::Regex;
2
3use std::{borrow::Cow, fmt::Display, str::FromStr};
4
5#[cfg(feature = "proc-macro")]
6use proc_macro2::TokenStream;
7#[cfg(feature = "proc-macro")]
8use quote::{quote, ToTokens, TokenStreamExt};
9
10#[derive(Clone, Copy, Debug)]
11pub struct Color(pub Option<u8>);
12
13#[cfg(feature = "proc-macro")]
14impl ToTokens for Color {
15    fn to_tokens(&self, tokens: &mut TokenStream) {
16        let value = match &self.0 {
17            Some(val) => quote! { Some(#val) },
18            None => quote! { None },
19        };
20        tokens.append_all(quote! {
21            ::pfetch_logo_parser::Color(#value)
22        });
23    }
24}
25
26impl Display for Color {
27    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
28        match self.0 {
29            Some(color @ 0..=7) => write!(f, "\x1b[3{color}m"),
30            Some(color) => write!(f, "\x1b[38;5;{color}m"),
31            None => write!(f, "\x1b[39m"),
32        }
33    }
34}
35
36impl FromStr for Color {
37    type Err = String;
38
39    fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
40        if s.is_empty() {
41            return Err("No string given".to_string());
42        }
43        Ok(Color(s.parse::<u8>().ok()))
44    }
45}
46
47#[derive(Clone, Debug)]
48pub struct LogoPart {
49    pub color: Color,
50    pub content: Cow<'static, str>,
51}
52
53#[cfg(feature = "proc-macro")]
54impl ToTokens for LogoPart {
55    fn to_tokens(&self, tokens: &mut TokenStream) {
56        let color = &self.color;
57        let content = &self.content;
58        tokens.append_all(quote! {
59            ::pfetch_logo_parser::LogoPart {
60                color: #color,
61                content: ::std::borrow::Cow::Borrowed(#content),
62            }
63        });
64    }
65}
66
67#[derive(Clone, Debug)]
68pub struct Logo {
69    pub primary_color: Color,
70    pub secondary_color: Color,
71    pub pattern: Cow<'static, str>,
72    pub logo_parts: Cow<'static, [LogoPart]>,
73}
74
75#[cfg(feature = "proc-macro")]
76impl ToTokens for Logo {
77    fn to_tokens(&self, tokens: &mut TokenStream) {
78        let primary_color = &self.primary_color;
79        let secondary_color = &self.secondary_color;
80        let pattern = &self.pattern;
81        let logo_parts = &self.logo_parts;
82
83        tokens.append_all(quote! {
84            ::pfetch_logo_parser::Logo {
85                primary_color: #primary_color,
86                secondary_color: #secondary_color,
87                pattern: ::std::borrow::Cow::Borrowed(#pattern),
88                logo_parts: ::std::borrow::Cow::Borrowed(&[#(#logo_parts),*]),
89            }
90        });
91    }
92}
93
94impl Display for Logo {
95    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
96        write!(
97            f,
98            "{}",
99            self.logo_parts
100                .iter()
101                .fold("".to_string(), |a, LogoPart { color, content }| a
102                    + &if !f.alternate() {
103                        format!("{color}{content}")
104                    } else {
105                        format!("{content}")
106                    })
107        )
108    }
109}
110
111/// Parses a logo in pfetch formant and returns wether it is the linux (tux) logo and the logo itself
112pub fn parse_logo(input: &str) -> Option<(bool, Logo)> {
113    let input = input.trim().replace('\t', "");
114    if input.is_empty() {
115        return None;
116    }
117    let regex = Regex::new(r"^\(?(.*)\)[\s\S]*read_ascii *(\d)?").unwrap();
118
119    let groups = regex.captures(&input).expect("Error while parsing logo");
120
121    let pattern = &groups[1];
122    let primary_color = match groups.get(2) {
123        Some(color) => color.as_str().parse::<u8>().unwrap(),
124        None => 7,
125    };
126    let secondary_color = (primary_color + 1) % 8;
127    let logo = input
128        .split_once("EOF\n")
129        .expect("Could not find start of logo, make sure to include the `<<- EOF` and to use tabs for indentation")
130        .1
131        .split_once("\nEOF")
132        .expect("Could not find end of logo, make sure to include the closing EOF and to use tabs for indentation")
133        .0;
134
135    let mut logo_parts = vec![];
136    for logo_part in logo.split("${") {
137        if let Some((new_color, rest)) = logo_part.split_once('}') {
138            let new_color: u8 = new_color
139                .get(1..)
140                .and_then(|num| num.parse().ok())
141                .unwrap_or_else(|| panic!("Invalid color: {new_color}"));
142            let rest = rest.replace("\\\\", "\\");
143            let rest = rest.replace("\\`", "`");
144            let lines = rest.split('\n').collect::<Vec<_>>();
145            let last_index = lines.len() - 1;
146            for (index, line) in lines.into_iter().enumerate() {
147                let mut line = line.to_owned();
148                if index != last_index {
149                    line += "\n";
150                }
151                logo_parts.push(LogoPart {
152                    color: Color(Some(new_color)),
153                    content: line.into(),
154                });
155            }
156        } else if !logo_part.is_empty() {
157            let logo_part = logo_part.replace("\\\\", "\\");
158            logo_parts.push(LogoPart {
159                color: Color(None),
160                content: logo_part.into(),
161            });
162        }
163    }
164
165    Some((
166        pattern == "[Ll]inux*",
167        Logo {
168            primary_color: Color(Some(primary_color)),
169            secondary_color: Color(Some(secondary_color)),
170            pattern: pattern.to_owned().into(),
171            logo_parts: logo_parts.into(),
172        },
173    ))
174}