[+] Read from distro data in rust

This commit is contained in:
2026-04-07 06:00:01 +00:00
parent 5ff0a8d49a
commit e9b65287a3
3 changed files with 120 additions and 149 deletions
+111 -77
View File
@@ -7,16 +7,27 @@ use anyhow::{Context, Result};
use fs_extra::dir::CopyOptions;
use heck::ToUpperCamelCase;
use indexmap::IndexMap;
use regex::Regex;
use serde::Deserialize;
use unicode_normalization::UnicodeNormalization as _;
#[derive(Debug)]
struct AsciiDistro {
pattern: String,
color: String,
foreground: Vec<u8>,
background: Option<u8>,
art: String,
}
#[derive(Deserialize, Debug)]
struct DistroHeader {
#[serde(rename = "match")]
pattern: String,
color: serde_json::Value,
foreground: Option<Vec<u8>>,
background: Option<u8>,
}
impl AsciiDistro {
fn friendly_name(&self) -> String {
self.pattern
@@ -37,31 +48,26 @@ fn main() -> Result<()> {
let dir = PathBuf::from(env::var_os("CARGO_WORKSPACE_DIR").unwrap_or_else(|| env::var_os("CARGO_MANIFEST_DIR").unwrap()));
let o = PathBuf::from(env::var_os("OUT_DIR").unwrap());
for file in &["neofetch", "hyfetch/data"] {
let src = anything_that_exist(&[
&dir.join(file),
&dir.join("../../").join(file),
]).context("couldn't find neofetch")?;
let dst = o.join(file);
println!("cargo:rerun-if-changed={}", src.display());
// Copy either file or directory
if src.is_dir() {
let opt = CopyOptions { overwrite: true, copy_inside: true, ..CopyOptions::default() };
println!("copying {} to {}", src.display(), dst.display());
fs_extra::dir::copy(&src, &dst, &opt)?;
}
else { fs::copy(&src, &dst)?; }
}
let data_dir = anything_that_exist(&[
&dir.join("hyfetch/data"),
&dir.join("../../hyfetch/data"),
]).context("couldn't find hyfetch/data")?;
let dst_data = o.join("hyfetch/data");
fs::create_dir_all(&dst_data)?;
// Copy hyfetch/data
let opt = CopyOptions { overwrite: true, copy_inside: true, ..CopyOptions::default() };
fs_extra::dir::copy(&data_dir, &dst_data, &opt)?;
preset_codegen(&o.join("hyfetch/data/presets.json"), &o.join("presets.rs"))?;
export_distros(&o.join("neofetch"), &o)?;
export_distros(&data_dir.join("distros"), &o)?;
Ok(())
}
fn export_distros(neofetch_path: &Path, out_path: &Path) -> Result<()>
fn export_distros(distro_dir: &Path, out_path: &Path) -> Result<()>
{
let distros = parse_ascii_distros(neofetch_path)?;
let distros = parse_ascii_distros(distro_dir)?;
let mut variants = IndexMap::with_capacity(distros.len());
for distro in &distros {
@@ -164,6 +170,66 @@ impl Distro {
None
}
pub fn color(&self) -> &str {
match self {
"###,
);
for (variant, AsciiDistro { color, .. }) in &variants {
write!(buf, r###"
Self::{variant} => {color:?},
"###, color = color)?;
}
buf.push_str(
r###"
}
}
pub fn foreground(&self) -> &[u8] {
match self {
"###,
);
for (variant, AsciiDistro { foreground, .. }) in &variants {
if foreground.is_empty() {
write!(buf, r###"
Self::{variant} => &[],
"###)?;
} else {
write!(buf, r###"
Self::{variant} => &{:?},
"###, foreground)?;
}
}
buf.push_str(
r###"
}
}
pub fn background(&self) -> Option<u8> {
match self {
"###,
);
for (variant, AsciiDistro { background, .. }) in &variants {
if let Some(b) = background {
write!(buf, r###"
Self::{variant} => Some({b}),
"###)?;
} else {
write!(buf, r###"
Self::{variant} => None,
"###)?;
}
}
buf.push_str(
r###"
}
}
pub fn ascii_art(&self) -> &str {
let art = match self {
"###,
@@ -191,67 +257,35 @@ impl Distro {
Ok(())
}
/// Parses ascii distros from neofetch script.
fn parse_ascii_distros(neofetch_path: &Path) -> Result<Vec<AsciiDistro>>
fn parse_ascii_distros(distro_dir: &Path) -> Result<Vec<AsciiDistro>>
{
let nf = {
let nf = fs::read_to_string(neofetch_path)?;
let mut distros = Vec::new();
let mut paths: Vec<_> = fs::read_dir(distro_dir)?
.filter_map(|e| e.ok())
.map(|e| e.path())
.collect();
paths.sort();
// Get the content of "get_distro_ascii" function
let (_, nf) = nf
.split_once("get_distro_ascii() {\n")
.context("couldn't find get_distro_ascii function")?;
let (nf, _) = nf
.split_once("\n}\n")
.context("couldn't find end of get_distro_ascii function")?;
let mut nf = nf.replace('\t', &" ".repeat(4));
// Remove trailing spaces
while nf.contains(" \n") {
nf = nf.replace(" \n", "\n");
for path in paths {
if path.extension().and_then(|s| s.to_str()) == Some("ascii") {
let content = fs::read_to_string(&path)?;
let (header_line, art) = content.split_once('\n').context("invalid distro file")?;
let header: DistroHeader = serde_json::from_str(header_line)?;
let color = match header.color {
serde_json::Value::String(s) => s,
serde_json::Value::Number(n) => n.to_string(),
_ => "7".to_owned(),
};
distros.push(AsciiDistro {
pattern: header.pattern,
color,
foreground: header.foreground.unwrap_or_default(),
background: header.background,
art: art.to_owned(),
});
}
nf
};
let case_re = Regex::new(r"case .*? in\n")?;
let eof_re = Regex::new(r"EOF[ \n]*?;;")?;
// Split by blocks
let mut blocks = Vec::new();
for b in case_re.split(&nf) {
blocks.extend(eof_re.split(b).map(|sub| sub.trim()));
}
// Parse blocks
fn parse_block(block: &str) -> Option<AsciiDistro> {
let (block, art) = block.split_once("'EOF'\n")?;
// Join \
//
// > A <backslash> that is not quoted shall preserve the literal value of the
// > following character, with the exception of a <newline>. If a <newline>
// > follows the <backslash>, the shell shall interpret this as line
// > continuation. The <backslash> and <newline> shall be removed before
// > splitting the input into tokens. Since the escaped <newline> is removed
// > entirely from the input and is not replaced by any white space, it cannot
// > serve as a token separator.
// See https://pubs.opengroup.org/onlinepubs/9699919799/utilities/V3_chap02.html#tag_18_02_01
let block = block.replace("\\\n", "");
// Get case pattern
let pattern = block
.split('\n')
.next()
.and_then(|pattern| pattern.trim().strip_suffix(')'))?;
// Unescape backslashes here because backslashes are escaped in neofetch
// for printf
let art = art.replace(r"\\", r"\");
Some(AsciiDistro { pattern: pattern.to_owned(), art })
}
Ok(blocks.iter().filter_map(|block| parse_block(block)).collect())
Ok(distros)
}
// Preset parsing
+1 -7
View File
@@ -372,12 +372,7 @@ impl NormalizedAsciiArt {
// Line starts with neofetch color code
last = Some(&line[m.span()]);
},
Some(_) => {
new.push_str(last.context(
"failed to find neofetch color code from a previous line",
)?);
},
None => {
_ => {
new.push_str(last.unwrap_or(NEOFETCH_COLOR_PATTERNS[0]));
},
}
@@ -385,7 +380,6 @@ impl NormalizedAsciiArt {
// Get the last placeholder for the next line
if let Some(m) = matches.last() {
last.context("non-space character seen before first color code")?;
last = Some(&line[m.span()]);
}
+8 -65
View File
@@ -241,27 +241,27 @@ pub fn get_distro_ascii<S>(distro: Option<S>, backend: Backend) -> Result<RawAsc
where
S: AsRef<str> + fmt::Debug,
{
let distro: Cow<_> = if let Some(distro) = distro.as_ref() {
let distro_name: Cow<_> = if let Some(distro) = distro.as_ref() {
distro.as_ref().into()
} else {
get_distro_name(backend)
.context("failed to get distro name")?
.into()
};
debug!(%distro, "distro name");
debug!(%distro_name, "distro name");
// Try new codegen-based detection method
if let Some(distro) = Distro::detect(&distro) {
if let Some(distro) = Distro::detect(&distro_name) {
let asc = distro.ascii_art().to_owned();
let fg = ascii_foreground(&distro);
return Ok(RawAsciiArt { asc, fg });
}
debug!(%distro, "could not find a match for distro; falling back to neofetch");
debug!(%distro_name, "could not find a match for distro; falling back to neofetch");
// Old detection method that calls neofetch
let asc = run_neofetch_command_piped(&["print_ascii", "--ascii_distro", distro.as_ref()])
let asc = run_neofetch_command_piped(&["print_ascii", "--ascii_distro", distro_name.as_ref()])
.context("failed to get ascii art from neofetch")?;
// Unescape backslashes here because backslashes are escaped in neofetch for
@@ -728,65 +728,8 @@ fn run_macchina(asc: String, args: Option<&Vec<String>>) -> Result<()> {
/// Gets the color indices that should be considered as foreground, for a
/// particular distro's ascii art.
fn ascii_foreground(distro: &Distro) -> Vec<NeofetchAsciiIndexedColor> {
let fg: Vec<u8> = match distro {
Distro::Anarchy => vec![2],
Distro::Android => vec![2],
Distro::Antergos => vec![1],
Distro::ArchStrike => vec![2],
Distro::Arkane => vec![1],
Distro::Asahi => vec![5],
Distro::Astra_Linux => vec![2],
Distro::BlackArch => vec![3],
Distro::CelOS => vec![3],
Distro::Chapeau => vec![2],
Distro::Chrom => vec![5],
Distro::Clear_Linux_OS => vec![2],
Distro::Container_Linux_by_CoreOS => vec![3],
Distro::CRUX => vec![3],
Distro::EuroLinux => vec![2],
Distro::eweOS => vec![3],
Distro::Fedora => vec![2],
Distro::Fedora_Sericea => vec![2],
Distro::Fedora_Silverblue => vec![2],
Distro::GalliumOS => vec![2],
Distro::Gentoo => vec![1],
Distro::HarDClanZ => vec![2],
Distro::Kibojoe => vec![3],
Distro::KrassOS => vec![2],
Distro::Kubuntu => vec![2],
Distro::Linux => vec![1],
Distro::LinuxFromScratch => vec![1, 3],
Distro::Lubuntu => vec![2],
Distro::openEuler => vec![2],
Distro::orchid => vec![1],
Distro::Panwah => vec![1],
Distro::Peppermint => vec![2],
Distro::PNM_Linux => vec![2],
Distro::Pop__OS => vec![2],
Distro::Reborn_OS => vec![1],
Distro::SalentOS => vec![4],
Distro::Septor => vec![2],
Distro::Ubuntu_Cinnamon => vec![2],
Distro::Ubuntu_Kylin => vec![2],
Distro::Ubuntu_MATE => vec![2],
Distro::Ubuntu_old => vec![2],
Distro::Ubuntu_Studio => vec![2],
Distro::Ubuntu_Sway => vec![2],
Distro::Ultramarine_Linux => vec![2],
Distro::Univention => vec![2],
Distro::uwuntu => vec![2],
Distro::Vanilla => vec![2],
Distro::VNux => vec![3, 5],
Distro::Void => vec![2],
Distro::Xray_OS => vec![2, 3],
Distro::Xubuntu => vec![2],
_ => Vec::new(),
};
fg.into_iter()
.map(|fore| {
fore.try_into()
.expect("`fore` should be a valid neofetch color index")
})
distro.foreground()
.iter()
.map(|&f| f.try_into().expect("neofetch color index should be valid"))
.collect()
}