feat: upgraded freedesktop parser

This commit is contained in:
imgurbot12 2024-07-10 01:26:53 -07:00
parent 129ac75287
commit 370ebfa5ec
3 changed files with 48 additions and 45 deletions

View file

@ -7,7 +7,7 @@ edition = "2021"
[dependencies] [dependencies]
clap = { version = "4.5.9", features = ["derive"] } clap = { version = "4.5.9", features = ["derive"] }
freedesktop-desktop-entry = "0.5.2" freedesktop-desktop-entry = "0.6.2"
itertools = "0.13.0" itertools = "0.13.0"
once_cell = "1.19.0" once_cell = "1.19.0"
rayon = "1.10.0" rayon = "1.10.0"

View file

@ -51,23 +51,23 @@ fn theme_inis(cfgdir: &PathBuf) -> Vec<String> {
} }
/// Parse FreeDesktop Theme-Name from Index File /// Parse FreeDesktop Theme-Name from Index File
fn get_theme_name(path: &PathBuf) -> Option<String> { fn get_theme_name(path: &PathBuf, locales: &[&str]) -> Option<String> {
let content = read_to_string(path).ok()?; let content = read_to_string(path).ok()?;
let config = DesktopEntry::decode(&path, &content).ok()?; let config = DesktopEntry::from_str(&path, &content, locales).ok()?;
config config
.groups .groups
.get(INDEX_MAIN) .get(INDEX_MAIN)
.and_then(|g| g.get(INDEX_NAME)) .and_then(|g| g.get(INDEX_NAME))
.map(|key| key.0.to_owned()) .map(|key| key.0.to_string())
} }
/// Determine XDG Icon Theme based on Preexisting Configuration Files /// Determine XDG Icon Theme based on Preexisting Configuration Files
pub fn active_themes(cfgdir: &PathBuf, icondirs: &Vec<PathBuf>) -> Vec<String> { pub fn active_themes(cfgdir: &PathBuf, icondirs: &Vec<PathBuf>, locales: &[&str]) -> Vec<String> {
let mut themes: Vec<String> = icondirs let mut themes: Vec<String> = icondirs
.iter() .iter()
.map(|d| d.join(DEFAULT_INDEX)) .map(|d| d.join(DEFAULT_INDEX))
.filter(|p| p.exists()) .filter(|p| p.exists())
.filter_map(|p| get_theme_name(&p)) .filter_map(|p| get_theme_name(&p, locales))
.collect(); .collect();
themes.extend(theme_inis(cfgdir)); themes.extend(theme_inis(cfgdir));
let default = DEFAULT_THEME.to_string(); let default = DEFAULT_THEME.to_string();
@ -82,7 +82,7 @@ pub enum ThemeError {
#[error("Failed to Read Index")] #[error("Failed to Read Index")]
FileError(#[from] std::io::Error), FileError(#[from] std::io::Error),
#[error("Failed to Parse Index")] #[error("Failed to Parse Index")]
IndexError(#[from] freedesktop_desktop_entry::DecodeError), IndexError(String),
#[error("No Such Group")] #[error("No Such Group")]
NoSuchGroup(&'static str), NoSuchGroup(&'static str),
#[error("No Such Key")] #[error("No Such Key")]
@ -138,11 +138,11 @@ fn sort_dirs(dirs: &mut Vec<PathPriority>) -> Vec<PathBuf> {
} }
/// Parse Theme Index and Sort Directories based on Size Preference /// Parse Theme Index and Sort Directories based on Size Preference
fn parse_index(spec: &ThemeSpec) -> Result<ThemeInfo, ThemeError> { fn parse_index(spec: &ThemeSpec, locales: &[&str]) -> Result<ThemeInfo, ThemeError> {
// parse file content // parse file content
let index = spec.root.join(INDEX_FILE); let index = spec.root.join(INDEX_FILE);
let content = read_to_string(&index)?; let config = DesktopEntry::from_path(index, locales)
let config = DesktopEntry::decode(&index, &content)?; .map_err(|e| ThemeError::IndexError(e.to_string()))?;
let main = config let main = config
.groups .groups
.get(INDEX_MAIN) .get(INDEX_MAIN)
@ -151,7 +151,8 @@ fn parse_index(spec: &ThemeSpec) -> Result<ThemeInfo, ThemeError> {
let name = main let name = main
.get(INDEX_NAME) .get(INDEX_NAME)
.ok_or_else(|| ThemeError::NoSuchKey(INDEX_NAME))? .ok_or_else(|| ThemeError::NoSuchKey(INDEX_NAME))?
.0; .0
.to_string();
// check if name in supported themes // check if name in supported themes
let index = spec let index = spec
.themes .themes
@ -179,7 +180,7 @@ fn parse_index(spec: &ThemeSpec) -> Result<ThemeInfo, ThemeError> {
.collect(); .collect();
Ok(ThemeInfo { Ok(ThemeInfo {
priority: index, priority: index,
name: name.to_owned(), name,
paths: sort_dirs(&mut directories), paths: sort_dirs(&mut directories),
}) })
} }
@ -238,15 +239,15 @@ impl IconSpec {
} }
} }
pub fn standard(cfg: &PathBuf, sizes: Vec<usize>) -> Self { pub fn standard(cfg: &PathBuf, sizes: Vec<usize>, locales: &[&str]) -> Self {
let icon_paths = crate::data_dirs("icons"); let icon_paths = crate::data_dirs("icons");
let themes = active_themes(cfg, &icon_paths); let themes = active_themes(cfg, &icon_paths, locales);
Self::new(icon_paths, themes, sizes) Self::new(icon_paths, themes, sizes)
} }
} }
/// Parse and Collect a list of Directories to Find Icons in Order of Preference /// Parse and Collect a list of Directories to Find Icons in Order of Preference
fn parse_themes(icons: IconSpec) -> Vec<PathBuf> { fn parse_themes(icons: IconSpec, locales: &[&str]) -> Vec<PathBuf> {
// retrieve supported theme information // retrieve supported theme information
let mut infos: Vec<ThemeInfo> = icons let mut infos: Vec<ThemeInfo> = icons
.paths .paths
@ -258,7 +259,7 @@ fn parse_themes(icons: IconSpec) -> Vec<PathBuf> {
// parse or guess index themes // parse or guess index themes
.filter_map(|icondir| { .filter_map(|icondir| {
let spec = ThemeSpec::new(&icondir, &icons.themes, &icons.sizes); let spec = ThemeSpec::new(&icondir, &icons.themes, &icons.sizes);
parse_index(&spec) parse_index(&spec, locales)
.map(|r| Ok(r)) .map(|r| Ok(r))
.unwrap_or_else(|_| guess_index(&spec)) .unwrap_or_else(|_| guess_index(&spec))
.ok() .ok()
@ -284,20 +285,24 @@ fn is_icon(fname: &str) -> bool {
} }
/// Collect Unique Icon Map based on Preffered Paths /// Collect Unique Icon Map based on Preffered Paths
pub fn collect_icons(spec: IconSpec) -> IconMap { pub fn collect_icons(spec: IconSpec, locales: &[&str]) -> IconMap {
let mut map = HashMap::new(); let mut map = HashMap::new();
for path in parse_themes(spec).into_iter() { for path in parse_themes(spec, locales).into_iter() {
let icons = WalkDir::new(path) let icons = WalkDir::new(path)
.follow_links(true) .follow_links(true)
.into_iter() .into_iter()
.filter_map(|e| e.ok()) .filter_map(|e| e.ok())
.filter(|e| e.file_type().is_file()); .filter(|e| e.file_type().is_file());
for icon in icons { for icon in icons {
let Some(fname) = icon.file_name().to_str() else { continue }; let Some(fname) = icon.file_name().to_str() else {
continue;
};
if !is_icon(&fname) { if !is_icon(&fname) {
continue; continue;
} }
let Some((name, _)) = fname.rsplit_once(".") else { continue }; let Some((name, _)) = fname.rsplit_once(".") else {
continue;
};
map.entry(name.to_owned()) map.entry(name.to_owned())
.or_insert_with(|| icon.path().to_owned()); .or_insert_with(|| icon.path().to_owned());
} }

View file

@ -1,4 +1,3 @@
use std::fs::read_to_string;
use std::path::PathBuf; use std::path::PathBuf;
use clap::Parser; use clap::Parser;
@ -50,28 +49,25 @@ fn fix_exec(exec: &str) -> String {
} }
/// Parse XDG Desktop Entry into RMenu Entry /// Parse XDG Desktop Entry into RMenu Entry
fn parse_desktop(path: &PathBuf, locale: Option<&str>) -> Option<Entry> { fn parse_desktop(path: PathBuf, locales: &[&str]) -> Option<Entry> {
let bytes = read_to_string(path).ok()?; let entry = DesktopEntry::from_path(path, locales).ok()?;
let entry = DesktopEntry::decode(&path, &bytes).ok()?; // hide `NoDisplay` entries
// no-display entries should not be shown
if entry.no_display() { if entry.no_display() {
return None; return None;
} }
// hide entries restricted by `OnlyShowIn`
// if an entry only is shown on a specific desktop, check whether desktop is set and matches, if let Ok(de) = std::env::var(XDG_CURRENT_DESKTOP_ENV) {
// otherwise return None
let de = std::env::var(XDG_CURRENT_DESKTOP_ENV);
if entry if entry
.only_show_in() .only_show_in()
.is_some_and(|e| !(de.is_ok() && de.unwrap() == e.to_string())) .is_some_and(|only| only.contains(&de.as_str()))
{ {
return None; return None;
};
} }
// parse desktop entry into rmenu entry
let name = entry.name(locale)?.to_string(); let name = entry.name(locales)?.to_string();
let icon = entry.icon().map(|i| i.to_string()); let icon = entry.icon().map(|i| i.to_string());
let comment = entry.comment(locale).map(|s| s.to_string()); let comment = entry.comment(locales).map(|s| s.to_string());
let terminal = entry.terminal(); let terminal = entry.terminal();
let mut actions = match entry.exec() { let mut actions = match entry.exec() {
Some(exec) => vec![Action { Some(exec) => vec![Action {
@ -84,12 +80,11 @@ fn parse_desktop(path: &PathBuf, locale: Option<&str>) -> Option<Entry> {
actions.extend( actions.extend(
entry entry
.actions() .actions()
.unwrap_or("") .unwrap_or_default()
.split(";")
.into_iter() .into_iter()
.filter(|a| a.len() > 0) .filter(|a| a.len() > 0)
.filter_map(|a| { .filter_map(|a| {
let name = entry.action_name(a, locale)?; let name = entry.action_name(a, locales)?;
let exec = entry.action_exec(a)?; let exec = entry.action_exec(a)?;
Some(Action { Some(Action {
name: name.to_string(), name: name.to_string(),
@ -124,17 +119,20 @@ struct Cli {
/// Only Allow Unique Desktop Entries /// Only Allow Unique Desktop Entries
#[clap(short, long)] #[clap(short, long)]
non_unique: bool, non_unique: bool,
/// Locale Override
#[clap(short, long, default_value = "en")]
locale: String,
} }
fn main() { fn main() {
let cli = Cli::parse(); let cli = Cli::parse();
let locale = Some("en"); let locales = &[cli.locale.as_str()];
let sizes = vec![64, 32, 96, 22, 128]; let sizes = vec![64, 32, 96, 22, 128];
// collect icons // collect icons
let cfg = config_dir(); let cfg = config_dir();
let spec = icons::IconSpec::standard(&cfg, sizes); let spec = icons::IconSpec::standard(&cfg, sizes, locales);
let icons = icons::collect_icons(spec); let icons = icons::collect_icons(spec, locales);
// collect applications // collect applications
let app_paths = data_dirs("applications"); let app_paths = data_dirs("applications");
@ -147,7 +145,7 @@ fn main() {
.and_then(|n| n.to_str()) .and_then(|n| n.to_str())
.map(|s| s.to_string()), .map(|s| s.to_string()),
}) })
.filter_map(|f| parse_desktop(&f, locale)) .filter_map(|f| parse_desktop(f, locales))
.map(|mut e| { .map(|mut e| {
e.icon = e.icon.and_then(|s| assign_icon(s, &icons)); e.icon = e.icon.and_then(|s| assign_icon(s, &icons));
e e