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]
clap = { version = "4.5.9", features = ["derive"] }
freedesktop-desktop-entry = "0.5.2"
freedesktop-desktop-entry = "0.6.2"
itertools = "0.13.0"
once_cell = "1.19.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
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 config = DesktopEntry::decode(&path, &content).ok()?;
let config = DesktopEntry::from_str(&path, &content, locales).ok()?;
config
.groups
.get(INDEX_MAIN)
.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
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
.iter()
.map(|d| d.join(DEFAULT_INDEX))
.filter(|p| p.exists())
.filter_map(|p| get_theme_name(&p))
.filter_map(|p| get_theme_name(&p, locales))
.collect();
themes.extend(theme_inis(cfgdir));
let default = DEFAULT_THEME.to_string();
@ -82,7 +82,7 @@ pub enum ThemeError {
#[error("Failed to Read Index")]
FileError(#[from] std::io::Error),
#[error("Failed to Parse Index")]
IndexError(#[from] freedesktop_desktop_entry::DecodeError),
IndexError(String),
#[error("No Such Group")]
NoSuchGroup(&'static str),
#[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
fn parse_index(spec: &ThemeSpec) -> Result<ThemeInfo, ThemeError> {
fn parse_index(spec: &ThemeSpec, locales: &[&str]) -> Result<ThemeInfo, ThemeError> {
// parse file content
let index = spec.root.join(INDEX_FILE);
let content = read_to_string(&index)?;
let config = DesktopEntry::decode(&index, &content)?;
let config = DesktopEntry::from_path(index, locales)
.map_err(|e| ThemeError::IndexError(e.to_string()))?;
let main = config
.groups
.get(INDEX_MAIN)
@ -151,7 +151,8 @@ fn parse_index(spec: &ThemeSpec) -> Result<ThemeInfo, ThemeError> {
let name = main
.get(INDEX_NAME)
.ok_or_else(|| ThemeError::NoSuchKey(INDEX_NAME))?
.0;
.0
.to_string();
// check if name in supported themes
let index = spec
.themes
@ -179,7 +180,7 @@ fn parse_index(spec: &ThemeSpec) -> Result<ThemeInfo, ThemeError> {
.collect();
Ok(ThemeInfo {
priority: index,
name: name.to_owned(),
name,
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 themes = active_themes(cfg, &icon_paths);
let themes = active_themes(cfg, &icon_paths, locales);
Self::new(icon_paths, themes, sizes)
}
}
/// 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
let mut infos: Vec<ThemeInfo> = icons
.paths
@ -258,7 +259,7 @@ fn parse_themes(icons: IconSpec) -> Vec<PathBuf> {
// parse or guess index themes
.filter_map(|icondir| {
let spec = ThemeSpec::new(&icondir, &icons.themes, &icons.sizes);
parse_index(&spec)
parse_index(&spec, locales)
.map(|r| Ok(r))
.unwrap_or_else(|_| guess_index(&spec))
.ok()
@ -284,20 +285,24 @@ fn is_icon(fname: &str) -> bool {
}
/// 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();
for path in parse_themes(spec).into_iter() {
for path in parse_themes(spec, locales).into_iter() {
let icons = WalkDir::new(path)
.follow_links(true)
.into_iter()
.filter_map(|e| e.ok())
.filter(|e| e.file_type().is_file());
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) {
continue;
}
let Some((name, _)) = fname.rsplit_once(".") else { continue };
let Some((name, _)) = fname.rsplit_once(".") else {
continue;
};
map.entry(name.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 clap::Parser;
@ -50,28 +49,25 @@ fn fix_exec(exec: &str) -> String {
}
/// Parse XDG Desktop Entry into RMenu Entry
fn parse_desktop(path: &PathBuf, locale: Option<&str>) -> Option<Entry> {
let bytes = read_to_string(path).ok()?;
let entry = DesktopEntry::decode(&path, &bytes).ok()?;
// no-display entries should not be shown
fn parse_desktop(path: PathBuf, locales: &[&str]) -> Option<Entry> {
let entry = DesktopEntry::from_path(path, locales).ok()?;
// hide `NoDisplay` entries
if entry.no_display() {
return None;
}
// if an entry only is shown on a specific desktop, check whether desktop is set and matches,
// otherwise return None
let de = std::env::var(XDG_CURRENT_DESKTOP_ENV);
if entry
.only_show_in()
.is_some_and(|e| !(de.is_ok() && de.unwrap() == e.to_string()))
{
return None;
// hide entries restricted by `OnlyShowIn`
if let Ok(de) = std::env::var(XDG_CURRENT_DESKTOP_ENV) {
if entry
.only_show_in()
.is_some_and(|only| only.contains(&de.as_str()))
{
return None;
};
}
let name = entry.name(locale)?.to_string();
// parse desktop entry into rmenu entry
let name = entry.name(locales)?.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 mut actions = match entry.exec() {
Some(exec) => vec![Action {
@ -84,12 +80,11 @@ fn parse_desktop(path: &PathBuf, locale: Option<&str>) -> Option<Entry> {
actions.extend(
entry
.actions()
.unwrap_or("")
.split(";")
.unwrap_or_default()
.into_iter()
.filter(|a| a.len() > 0)
.filter_map(|a| {
let name = entry.action_name(a, locale)?;
let name = entry.action_name(a, locales)?;
let exec = entry.action_exec(a)?;
Some(Action {
name: name.to_string(),
@ -124,17 +119,20 @@ struct Cli {
/// Only Allow Unique Desktop Entries
#[clap(short, long)]
non_unique: bool,
/// Locale Override
#[clap(short, long, default_value = "en")]
locale: String,
}
fn main() {
let cli = Cli::parse();
let locale = Some("en");
let locales = &[cli.locale.as_str()];
let sizes = vec![64, 32, 96, 22, 128];
// collect icons
let cfg = config_dir();
let spec = icons::IconSpec::standard(&cfg, sizes);
let icons = icons::collect_icons(spec);
let spec = icons::IconSpec::standard(&cfg, sizes, locales);
let icons = icons::collect_icons(spec, locales);
// collect applications
let app_paths = data_dirs("applications");
@ -147,7 +145,7 @@ fn main() {
.and_then(|n| n.to_str())
.map(|s| s.to_string()),
})
.filter_map(|f| parse_desktop(&f, locale))
.filter_map(|f| parse_desktop(f, locales))
.map(|mut e| {
e.icon = e.icon.and_then(|s| assign_icon(s, &icons));
e