Merge pull request #34

Support custom logos at runtime
This commit is contained in:
Adrian Groh 2023-07-07 16:33:26 +02:00 committed by GitHub
commit eb3c0f0f2c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 340 additions and 132 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

@ -89,12 +89,11 @@ value.
## Configuration ## Configuration
Like the original `pfetch`, `pfetch-rs` is configured through environment Like the original `pfetch`, `pfetch-rs` is configured through environment
variables. Your existing config will probably still work, the only difference is variables. Your existing config will probably still work, the main difference is
how padding is configured. how padding is configured.
If you want to display a custom logo, you will have to download the source code, If you want to display a custom logo, use the `PF_CUSTOM_LOGOS` option, an
make your changes to `./pfetch-extractor/logos.sh` and build the binary with example for a custom logos file can be found below.
`cargo b --release`.
```sh ```sh
# Which information to display. # Which information to display.
@ -110,11 +109,16 @@ PF_INFO="ascii"
# Example: Only Information. # Example: Only Information.
PF_INFO="title os host kernel uptime pkgs memory" PF_INFO="title os host kernel uptime pkgs memory"
# A file containing environment variables to source before running pfetch. # A file containing environment variables to source before running pfetch
# Default: unset # Default: unset
# Valid: A shell script # Valid: A shell script
PF_SOURCE="" PF_SOURCE=""
# A file containing pfetch logos to overwrite default logos or add new logos
# Default: unset
# Valid: Path to a file containing pfetch logos (example below)
PF_CUSTOM_LOGOS="~/.config/pfetch_logos"
# Separator between info name and info data. # Separator between info name and info data.
# Default: unset # Default: unset
# Valid: string # Valid: string
@ -166,3 +170,48 @@ HOSTNAME=""
# Skip package managers that take "long" to query package count (like nix) # Skip package managers that take "long" to query package count (like nix)
PF_FAST_PKG_COUNT=1 PF_FAST_PKG_COUNT=1
``` ```
A file containing custom pfetch logos could look like this (also found under
`custom_logos_example`). This will turn the Arch Linux logo red, the Debian Logo
blue and the Fedora logo yellow:
```
[Aa]rch*)
read_ascii 1 <<- EOF
${c1} /\\
${c1} / \\
${c1} /\\ \\
${c1} / \\
${c1} / ,, \\
${c1} / | | -\\
${c1} /_-'' ''-_\\
EOF
;;
[Dd]ebian*)
read_ascii 4 <<- EOF
${c4} _____
${c4} / __ \\
${c4}| / |
${c4}| \\___-
${c4}-_
${c4} --_
EOF
;;
[Ff]edora*)
read_ascii 3 <<- EOF
${c3},'''''.
${c3}| ,. |
${c3}| | '_'
${c3} ,....| |..
${c3}.' ,_;| ..'
${c3}| | | |
${c3}| ',_,' |
${c3} '. ,'
${c3}'''''
EOF
```
_Note: Make sure to use tabs for indentation and separate logos with `;;`, as
seen above. You only need to add the logos you want to overwrite/add, the
default logos will stay available. The included logos can be found at
`./pfetch-extractor/logos.sh`._

34
custom_logos_example Normal file
View File

@ -0,0 +1,34 @@
[Aa]rch*)
read_ascii 1 <<- EOF
${c1} /\\
${c1} / \\
${c1} /\\ \\
${c1} / \\
${c1} / ,, \\
${c1} / | | -\\
${c1} /_-'' ''-_\\
EOF
;;
[Dd]ebian*)
read_ascii 4 <<- EOF
${c4} _____
${c4} / __ \\
${c4}| / |
${c4}| \\___-
${c4}-_
${c4} --_
EOF
;;
[Ff]edora*)
read_ascii 3 <<- EOF
${c3},'''''.
${c3}| ,. |
${c3}| | '_'
${c3} ,....| |..
${c3}.' ,_;| ..'
${c3}| | | |
${c3}| ',_,' |
${c3} '. ,'
${c3}'''''
EOF

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,17 @@
[package]
name = "pfetch-logo-parser"
version = "0.1.0"
edition = "2021"
authors = ["Gobidev"]
license = "MIT"
description = "A parser for pfetch logos"
# 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,19 @@
Copyright (c) 2023 Adrian Groh
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

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::{collections::VecDeque, 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::{parse_logo, 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 {
@ -352,8 +311,23 @@ pub fn host(general_readout: &GeneralReadout) -> Option<String> {
} }
} }
fn parse_custom_logos(filename: &str) -> Vec<Option<Logo>> {
let file_contents = fs::read_to_string(filename).expect("Could not open custom logo file");
file_contents
.split(";;")
.map(|raw_logo| parse_logo(raw_logo).map(|(_, logo)| logo))
.collect::<Vec<_>>()
}
pub fn logo(logo_name: &str) -> Logo { pub fn logo(logo_name: &str) -> Logo {
let (tux, logos) = parse_logos!(); let (tux, included_logos) = pfetch_extractor::parse_logos!();
let mut logos: VecDeque<_> = included_logos.into();
if let Ok(filename) = dotenvy::var("PF_CUSTOM_LOGOS") {
// insert custom logos in front of incuded logos
for custom_logo in parse_custom_logos(&filename).into_iter().flatten() {
logos.insert(0, custom_logo.clone());
}
};
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())),