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 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
111pub 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}