More stuff

This commit is contained in:
Kenny Levinsen
2023-02-02 23:52:59 +01:00
parent 7593cd48a4
commit b49b9e9764
26 changed files with 2022 additions and 803 deletions
+48 -48
View File
@@ -1,12 +1,16 @@
use ini::{Ini, ParseOption};
use itertools::Itertools;
use std::cmp::Ordering;
use std::env;
use std::error::Error;
use std::io::Error as io_error;
use std::io::ErrorKind;
use std::{
cmp::Ordering,
collections::HashSet,
env,
error::Error,
fs::read_to_string,
io::{Error as io_error, ErrorKind},
};
use walkdir::WalkDir;
use crate::utils::inish;
#[derive(Clone, Debug, Eq, Hash)]
pub struct Desktop {
pub entry_type: String,
@@ -21,23 +25,18 @@ pub struct Desktop {
impl Desktop {
fn parse(f: &str) -> Result<Desktop, Box<dyn Error>> {
let file = Ini::load_from_file_opt(
f,
ParseOption {
enabled_quote: false,
enabled_escape: false,
},
)?;
match file.section(Some("Desktop Entry")) {
Some(desktop) => Ok(Desktop {
entry_type: desktop.get("Type").unwrap_or(&"".to_string()).to_string(),
name: desktop.get("Name").unwrap_or(&"".to_string()).to_string(),
term: desktop.get("Terminal").unwrap_or(&"".to_string()) == "true",
no_display: desktop.get("NoDisplay").unwrap_or(&"".to_string()) == "true",
hidden: desktop.get("Hidden").unwrap_or(&"".to_string()) == "true",
exec: desktop.get("Exec").map(|x| x.to_string()),
url: desktop.get("URL").map(|x| x.to_string()),
keywords: desktop
let s = read_to_string(f)?;
let config = inish::parse(&s)?;
match config.get("Desktop Entry") {
Some(section) => Ok(Desktop {
entry_type: section.get("Type").unwrap_or(&"").to_string(),
name: section.get("Name").unwrap_or(&"").to_string(),
term: section.get("Terminal").unwrap_or(&"") == &"true",
no_display: section.get("NoDisplay").unwrap_or(&"") == &"true",
hidden: section.get("Hidden").unwrap_or(&"") == &"true",
exec: section.get("Exec").map(|x| x.to_string()),
url: section.get("URL").map(|x| x.to_string()),
keywords: section
.get("Keywords")
.map(|x| {
x.split(';')
@@ -53,20 +52,6 @@ impl Desktop {
))),
}
}
fn parse_dir(d: &str) -> Result<Vec<Desktop>, Box<dyn Error>> {
let mut files: Vec<Desktop> = Vec::with_capacity(16);
for entry in WalkDir::new(d) {
let entry = entry?;
let path = entry.path();
if let Ok(d) = Desktop::parse(path.to_str().unwrap()) {
files.push(d)
}
}
Ok(files)
}
}
impl Ord for Desktop {
@@ -99,14 +84,29 @@ pub fn load_desktop_files() -> Vec<Desktop> {
None => "/usr/local/share:/usr/share".to_string(),
};
std::iter::once(xdg_data_home.as_str())
.chain(xdg_data_dirs.split(':'))
.map(|p| Desktop::parse_dir(&format!("{}/applications", p)))
.filter_map(Result::ok)
.flatten()
.filter(|d| {
!d.hidden && !d.no_display && (d.entry_type == "Application" || d.entry_type == "Link")
})
.unique_by(|x| x.name.clone())
.collect()
let dirs = std::iter::once(xdg_data_home.as_str()).chain(xdg_data_dirs.split(':'));
let mut desktop: Vec<Desktop> = Vec::new();
let mut seen: HashSet<String> = HashSet::new();
for dir in dirs {
for entry in WalkDir::new(format!("{}/applications", dir)) {
if let Ok(entry) = entry {
if let Ok(d) = Desktop::parse(entry.path().to_str().unwrap()) {
if d.hidden
|| d.no_display
|| (d.entry_type != "Application" && d.entry_type != "Link")
{
continue;
}
if !seen.contains(&d.name) {
seen.insert(d.name.to_string());
desktop.push(d);
}
}
}
}
}
desktop
}
+134
View File
@@ -0,0 +1,134 @@
use std::{collections::HashMap, error::Error};
fn parse_field<'a>(line: &'a str) -> Result<(&'a str, &'a str), Box<dyn Error>> {
let split = match line.find('=') {
Some(v) => v,
None => return Err("expected equals sign on line, but found none".into()),
};
let (key, value) = line.split_at(split);
let (key, value) = (key.trim(), value.trim_matches('=').trim());
Ok((key, value))
}
type InishSection<'a> = HashMap<&'a str, &'a str>;
type Inish<'a> = HashMap<&'a str, InishSection<'a>>;
pub fn parse<'a>(s: &'a str) -> Result<Inish<'a>, Box<dyn Error>> {
let mut sections: Inish<'a> = HashMap::new();
let mut current_section = HashMap::new();
let mut current_section_name = "";
for line in s.lines() {
let line = line.trim();
let mut chars = line.chars();
let start = chars.next();
let end = chars.last();
match (start, end) {
(Some('#'), _) => continue,
(Some('['), Some(']')) => {
sections.insert(current_section_name, current_section);
current_section = HashMap::new();
let len = line.bytes().count();
current_section_name = &line[1..len - 1].trim();
}
(Some('['), v) => {
return Err(format!(
"expected Some(']') to terminate section name, but got {:?}",
v
)
.into());
}
_ if line.is_empty() => continue,
_ => {
let (key, value) = parse_field(line)?;
current_section.insert(key, value);
}
}
}
sections.insert(current_section_name, current_section);
Ok(sections)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn sections() {
let config = parse(
"
[section_a]
[section_b]
",
)
.expect("config didn't parse");
if !config.contains_key("section_a") {
panic!("named section did not exist");
}
if !config.contains_key("section_b") {
panic!("named section did not exist");
}
if !config.contains_key("") {
panic!("unnamed section did not exist");
}
}
#[test]
fn fields() {
let config = parse(
"
field_outside = 'hello'
[section_a]
field_a = 1234
",
)
.expect("config didn't parse");
let section_a = match config.get("section_a") {
Some(v) => v,
None => panic!("named section did not exist"),
};
assert_eq!(section_a.get("field_a"), Some(&"1234"));
let section_unnamed = match config.get("") {
Some(v) => v,
None => panic!("unnamed section did not exist"),
};
assert_eq!(section_unnamed.get("field_outside"), Some(&"'hello'"));
}
#[test]
fn whitespaces() {
let config = parse(
"
field outside = 'hello'
[ section a ]
field a = 1234 ",
)
.expect("config didn't parse");
let section_a = match config.get("section a") {
Some(v) => v,
None => panic!("named section did not exist"),
};
assert_eq!(section_a.get("field a"), Some(&"1234"));
let section_unnamed = match config.get("") {
Some(v) => v,
None => panic!("unnamed section did not exist"),
};
assert_eq!(section_unnamed.get("field outside"), Some(&"'hello'"));
}
}
+1
View File
@@ -1 +1,2 @@
pub mod desktop;
mod inish;