More stuff
This commit is contained in:
+48
-48
@@ -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
|
||||
}
|
||||
|
||||
@@ -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 +1,2 @@
|
||||
pub mod desktop;
|
||||
mod inish;
|
||||
|
||||
Reference in New Issue
Block a user