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