refactor: outsource pfetch logo parser code to separate crate

This makes the parse_logo() function accessible at compile-time and
runtime
This commit is contained in:
Adrian Groh 2023-07-05 19:00:25 +02:00
parent 7a898206fa
commit e09cc31f7d
Signed by: Gobidev
GPG Key ID: 3AA3153E98B0D771
8 changed files with 215 additions and 127 deletions

18
Cargo.lock generated
View File

@ -757,12 +757,22 @@ dependencies = [
"globset", "globset",
"libmacchina", "libmacchina",
"pfetch-extractor", "pfetch-extractor",
"pfetch-logo-parser",
"which", "which",
] ]
[[package]] [[package]]
name = "pfetch-extractor" name = "pfetch-extractor"
version = "0.1.5" version = "0.1.5"
dependencies = [
"pfetch-logo-parser",
"proc-macro2",
"quote",
]
[[package]]
name = "pfetch-logo-parser"
version = "0.1.0"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
@ -827,9 +837,9 @@ dependencies = [
[[package]] [[package]]
name = "regex" name = "regex"
version = "1.8.1" version = "1.8.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "af83e617f331cc6ae2da5443c602dfa5af81e517212d9d611a5b3ba1777b5370" checksum = "d0ab3ca65655bb1e41f2a8c8cd662eb4fb035e67c3f78da1d61dffe89d07300f"
dependencies = [ dependencies = [
"aho-corasick 1.0.1", "aho-corasick 1.0.1",
"memchr", "memchr",
@ -838,9 +848,9 @@ dependencies = [
[[package]] [[package]]
name = "regex-syntax" name = "regex-syntax"
version = "0.7.1" version = "0.7.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a5996294f19bd3aae0453a862ad728f60e6600695733dd5df01da90c54363a3c" checksum = "436b050e76ed2903236f032a59761c1eb99e1b0aead2c257922771dab1fc8c78"
[[package]] [[package]]
name = "rpm-pkg-count" name = "rpm-pkg-count"

View File

@ -1,5 +1,5 @@
[workspace] [workspace]
members = ["pfetch-extractor"] members = ["pfetch-extractor", "pfetch-logo-parser"]
[package] [package]
name = "pfetch" name = "pfetch"
@ -15,6 +15,7 @@ categories = ["command-line-utilities"]
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies] [dependencies]
pfetch-logo-parser = { path = "./pfetch-logo-parser", version = "0.1.0" }
pfetch-extractor = { path = "./pfetch-extractor", version = "0.1.5" } pfetch-extractor = { path = "./pfetch-extractor", version = "0.1.5" }
globset = "0.4.10" globset = "0.4.10"
dotenvy = "0.15.6" dotenvy = "0.15.6"

View File

@ -1,11 +1,11 @@
[package] [package]
name = "pfetch-extractor" name = "pfetch-extractor"
version = "0.1.5" version = "0.1.5"
edition = "2021"
authors = ["Gobidev"] authors = ["Gobidev"]
description = "A rust proc-macro to extract pfetch logos at compile time" edition = "2021"
license = "MIT"
keywords = ["pfetch"] keywords = ["pfetch"]
license = "MIT"
description = "A rust proc-macro to extract pfetch logos at compile time"
[lib] [lib]
proc-macro = true proc-macro = true
@ -13,6 +13,6 @@ proc-macro = true
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies] [dependencies]
pfetch-logo-parser = { version = "0.1.0", path = "../pfetch-logo-parser", features = ["proc-macro"] }
proc-macro2 = "1.0.50" proc-macro2 = "1.0.50"
quote = "1.0.23" quote = "1.0.23"
regex = "1.7.1"

View File

@ -1,6 +1,5 @@
use proc_macro::TokenStream; use proc_macro::TokenStream;
use quote::quote; use quote::quote;
use regex::Regex;
#[proc_macro] #[proc_macro]
pub fn parse_logos(_input: TokenStream) -> TokenStream { pub fn parse_logos(_input: TokenStream) -> TokenStream {
@ -18,7 +17,7 @@ pub fn parse_logos(_input: TokenStream) -> TokenStream {
let logos = raw_logos let logos = raw_logos
.split(";;\n") .split(";;\n")
.filter_map(|raw_logo| { .filter_map(|raw_logo| {
let (is_tux, logo) = parse_logo(raw_logo)?; let (is_tux, logo) = pfetch_logo_parser::parse_logo(raw_logo)?;
if is_tux { if is_tux {
tux = Some(logo.clone()); tux = Some(logo.clone());
} }
@ -30,69 +29,3 @@ pub fn parse_logos(_input: TokenStream) -> TokenStream {
quote! { (#tux, [#(#logos),*]) }.into() quote! { (#tux, [#(#logos),*]) }.into()
} }
fn parse_logo(input: &str) -> Option<(bool, proc_macro2::TokenStream)> {
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 logos.sh");
let pattern = &groups[1];
let primary_color = match groups.get(2) {
Some(color) => color.as_str().parse::<u8>().unwrap(),
None => 7,
};
let secondary_color = match groups.get(3) {
Some(color) => color.as_str().parse::<u8>().unwrap(),
None => (primary_color + 1) % 8,
};
let logo = input
.split_once("EOF\n")
.expect("Could not find start of logo")
.1
.split_once("\nEOF")
.expect("Could not find end of logo")
.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::<Vec<_>>();
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(quote! { (Color(Some(#new_color)), #line) });
}
} else if !logo_part.is_empty() {
let logo_part = logo_part.replace("\\\\", "\\");
logo_parts.push(quote! { (Color(None), #logo_part) });
}
}
Some((
pattern == "[Ll]inux*",
quote! {
Logo {
primary_color: Color(Some(#primary_color)),
secondary_color: Color(Some(#secondary_color)),
pattern: #pattern,
logo_parts: &[#(#logo_parts),*],
}
},
))
}

View File

@ -0,0 +1,14 @@
[package]
name = "pfetch-logo-parser"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[features]
proc-macro = ["dep:proc-macro2", "dep:quote"]
[dependencies]
regex = "1.8.4"
proc-macro2 = { version = "1.0.50", optional = true }
quote = { version = "1.0.23", optional = true }

View File

@ -0,0 +1,170 @@
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<u8>);
#[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<Self, Self::Err> {
Ok(Color(s.parse::<u8>().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()
.map(|LogoPart { color, content }| format!("{color}{content}"))
.collect::<String>()
)
}
}
/// 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::<u8>().unwrap(),
None => 7,
};
let secondary_color = match groups.get(3) {
Some(color) => color.as_str().parse::<u8>().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::<Vec<_>>();
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(),
},
))
}

View File

@ -1,4 +1,4 @@
use std::{env, fmt::Display, fs, io::Result, process::Command, str::FromStr}; use std::{env, fs, io::Result, process::Command};
use glob::glob; use glob::glob;
use globset::Glob; use globset::Glob;
@ -6,48 +6,7 @@ use libmacchina::{
traits::GeneralReadout as _, traits::KernelReadout as _, traits::MemoryReadout as _, traits::GeneralReadout as _, traits::KernelReadout as _, traits::MemoryReadout as _,
traits::PackageReadout as _, GeneralReadout, KernelReadout, MemoryReadout, PackageReadout, traits::PackageReadout as _, GeneralReadout, KernelReadout, MemoryReadout, PackageReadout,
}; };
use pfetch_extractor::parse_logos; use pfetch_logo_parser::Logo;
#[derive(Clone, Copy, Debug)]
pub struct Color(pub Option<u8>);
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<Self, Self::Err> {
Ok(Color(s.parse::<u8>().ok()))
}
}
pub struct Logo {
pub primary_color: Color,
pub secondary_color: Color,
pub pattern: &'static str,
pub logo_parts: &'static [(Color, &'static str)],
}
impl Display for Logo {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"{}",
self.logo_parts
.iter()
.map(|(color, part)| format!("{color}{part}"))
.collect::<String>()
)
}
}
#[derive(Debug)] #[derive(Debug)]
pub enum PackageManager { pub enum PackageManager {
@ -353,7 +312,7 @@ pub fn host(general_readout: &GeneralReadout) -> Option<String> {
} }
pub fn logo(logo_name: &str) -> Logo { pub fn logo(logo_name: &str) -> Logo {
let (tux, logos) = parse_logos!(); let (tux, logos) = pfetch_extractor::parse_logos!();
logos logos
.into_iter() .into_iter()
.find(|logo| { .find(|logo| {

View File

@ -2,6 +2,7 @@ use libmacchina::{
traits::GeneralReadout as _, traits::KernelReadout as _, traits::MemoryReadout as _, traits::GeneralReadout as _, traits::KernelReadout as _, traits::MemoryReadout as _,
traits::PackageReadout as _, GeneralReadout, KernelReadout, MemoryReadout, PackageReadout, traits::PackageReadout as _, GeneralReadout, KernelReadout, MemoryReadout, PackageReadout,
}; };
use pfetch_logo_parser::{Color, Logo, LogoPart};
use std::{env, fmt::Display, str::FromStr}; use std::{env, fmt::Display, str::FromStr};
#[derive(Debug, PartialEq)] #[derive(Debug, PartialEq)]
@ -53,11 +54,11 @@ impl FromStr for PfetchInfo {
} }
} }
fn pfetch(info: Vec<(pfetch::Color, String, String)>, logo: pfetch::Logo, logo_enabled: bool) { fn pfetch(info: Vec<(Color, String, String)>, logo: Logo, logo_enabled: bool) {
let raw_logo = if logo_enabled { let raw_logo = if logo_enabled {
logo.logo_parts logo.logo_parts
.iter() .iter()
.map(|(_, part)| *part) .map(|LogoPart { content, .. }| content.as_ref())
.collect::<String>() .collect::<String>()
} else { } else {
"".to_string() "".to_string()
@ -126,7 +127,7 @@ fn pfetch(info: Vec<(pfetch::Color, String, String)>, logo: pfetch::Logo, logo_e
), ),
color2 = match dotenvy::var("PF_COL2") { color2 = match dotenvy::var("PF_COL2") {
Ok(newcolor) => { Ok(newcolor) => {
match pfetch::Color::from_str(&newcolor) { match Color::from_str(&newcolor) {
Ok(newcolor) => format!("{newcolor}"), Ok(newcolor) => format!("{newcolor}"),
Err(_) => "".to_string(), Err(_) => "".to_string(),
} }
@ -266,7 +267,7 @@ fn main() {
// color overrides // color overrides
if let Ok(newcolor) = dotenvy::var("PF_COL1") { if let Ok(newcolor) = dotenvy::var("PF_COL1") {
if let Ok(newcolor) = pfetch::Color::from_str(&newcolor) { if let Ok(newcolor) = Color::from_str(&newcolor) {
logo.primary_color = newcolor; logo.primary_color = newcolor;
} }
} }
@ -274,12 +275,12 @@ fn main() {
if let Ok(newcolor) = dotenvy::var("PF_COL3") { if let Ok(newcolor) = dotenvy::var("PF_COL3") {
if newcolor == "COL1" { if newcolor == "COL1" {
logo.secondary_color = logo.primary_color; logo.secondary_color = logo.primary_color;
} else if let Ok(newcolor) = pfetch::Color::from_str(&newcolor) { } else if let Ok(newcolor) = Color::from_str(&newcolor) {
logo.secondary_color = newcolor; logo.secondary_color = newcolor;
} }
} }
let gathered_pfetch_info: Vec<(pfetch::Color, String, String)> = enabled_pf_info let gathered_pfetch_info: Vec<(Color, String, String)> = enabled_pf_info
.iter() .iter()
.filter_map(|info| match info { .filter_map(|info| match info {
PfetchInfo::Os => Some((logo.primary_color, info.to_string(), os.clone())), PfetchInfo::Os => Some((logo.primary_color, info.to_string(), os.clone())),