use regex::Regex; use std::{borrow::Cow, fmt::Display, str::FromStr}; #[cfg(feature = "proc-macro")] use proc_macro2::TokenStream; #[cfg(feature = "proc-macro")] use quote::{quote, ToTokens, TokenStreamExt}; #[derive(Clone, Copy, Debug)] pub struct Color(pub Option); #[cfg(feature = "proc-macro")] impl ToTokens for Color { fn to_tokens(&self, tokens: &mut TokenStream) { let value = match &self.0 { Some(val) => quote! { Some(#val) }, None => quote! { None }, }; tokens.append_all(quote! { ::pfetch_logo_parser::Color(#value) }); } } impl Display for Color { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self.0 { Some(color @ 0..=7) => write!(f, "\x1b[3{color}m"), Some(color) => write!(f, "\x1b[38;5;{color}m"), None => write!(f, "\x1b[39m"), } } } impl FromStr for Color { type Err = String; fn from_str(s: &str) -> std::result::Result { Ok(Color(s.parse::().ok())) } } #[derive(Clone, Debug)] pub struct LogoPart { pub color: Color, pub content: Cow<'static, str>, } #[cfg(feature = "proc-macro")] impl ToTokens for LogoPart { fn to_tokens(&self, tokens: &mut TokenStream) { let color = &self.color; let content = &self.content; tokens.append_all(quote! { ::pfetch_logo_parser::LogoPart { color: #color, content: ::std::borrow::Cow::Borrowed(#content), } }); } } #[derive(Clone, Debug)] pub struct Logo { pub primary_color: Color, pub secondary_color: Color, pub pattern: Cow<'static, str>, pub logo_parts: Cow<'static, [LogoPart]>, } #[cfg(feature = "proc-macro")] impl ToTokens for Logo { fn to_tokens(&self, tokens: &mut TokenStream) { let primary_color = &self.primary_color; let secondary_color = &self.secondary_color; let pattern = &self.pattern; let logo_parts = &self.logo_parts; tokens.append_all(quote! { ::pfetch_logo_parser::Logo { primary_color: #primary_color, secondary_color: #secondary_color, pattern: ::std::borrow::Cow::Borrowed(#pattern), logo_parts: ::std::borrow::Cow::Borrowed(&[#(#logo_parts),*]), } }); } } impl Display for Logo { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!( f, "{}", self.logo_parts .iter() .fold("".to_string(), |a, LogoPart { color, content }| a + &format!("{color}{content}")) ) } } /// Parses a logo in pfetch formant and returns wether it is the linux (tux) logo and the logo itself pub fn parse_logo(input: &str) -> Option<(bool, Logo)> { let input = input.trim().replace('\t', ""); if input.is_empty() { return None; } let regex = Regex::new(r"^\(?(.*)\)[\s\S]*read_ascii *(\d)? *(\d)?").unwrap(); let groups = regex.captures(&input).expect("Error while parsing logo"); let pattern = &groups[1]; let primary_color = match groups.get(2) { Some(color) => color.as_str().parse::().unwrap(), None => 7, }; let secondary_color = match groups.get(3) { Some(color) => color.as_str().parse::().unwrap(), None => (primary_color + 1) % 8, }; let logo = input .split_once("EOF\n") .expect("Could not find start of logo, make sure to include the `<<- EOF` and to use tabs for indentation") .1 .split_once("\nEOF") .expect("Could not find end of logo, make sure to include the closing EOF and to use tabs for indentation") .0; let mut logo_parts = vec![]; for logo_part in logo.split("${") { if let Some((new_color, rest)) = logo_part.split_once('}') { let new_color: u8 = new_color .get(1..) .and_then(|num| num.parse().ok()) .unwrap_or_else(|| panic!("Invalid color: {new_color}")); let rest = rest.replace("\\\\", "\\"); let rest = rest.replace("\\`", "`"); let lines = rest.split('\n').collect::>(); let last_index = lines.len() - 1; for (index, line) in lines.into_iter().enumerate() { let mut line = line.to_owned(); if index != last_index { line += "\n"; } logo_parts.push(LogoPart { color: Color(Some(new_color)), content: line.into(), }); } } else if !logo_part.is_empty() { let logo_part = logo_part.replace("\\\\", "\\"); logo_parts.push(LogoPart { color: Color(None), content: logo_part.into(), }); } } Some(( pattern == "[Ll]inux*", Logo { primary_color: Color(Some(primary_color)), secondary_color: Color(Some(secondary_color)), pattern: pattern.to_owned().into(), logo_parts: logo_parts.into(), }, )) }