pfetch_logo_parser/
lib.rs1use 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
108pub 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}