Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,11 @@ categories = [ "os::unix-apis" ]
keywords = [ "freedesktop", "desktop", "entry" ]

[dependencies]
dirs = "5.0.1"
gettext-rs = { version = "0.7", features = ["gettext-system"]}
memchr = "2"
textdistance = "1.0.2"
strsim = "0.11.1"
thiserror = "1"
xdg = "2.4.0"
log = "0.4.21"
11 changes: 9 additions & 2 deletions src/decoder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,10 @@ impl<'a> DesktopEntry<'a> {
}

/// Return an owned [`DesktopEntry`]
pub fn from_path<L>(path: PathBuf, locales_filter: Option<&[L]>) -> Result<DesktopEntry<'static>, DecodeError>
pub fn from_path<L>(
path: PathBuf,
locales_filter: Option<&[L]>,
) -> Result<DesktopEntry<'static>, DecodeError>
where
L: AsRef<str>,
{
Expand Down Expand Up @@ -125,7 +128,11 @@ fn process_line<'buf, 'local_ref, 'res: 'local_ref + 'buf, F, L>(
let locale = &key[start + 1..key.len() - 1];

match locales_filter {
Some(locales_filter) if !locales_filter.iter().any(|l| l.as_ref() == locale) => return,
Some(locales_filter)
if !locales_filter.iter().any(|l| l.as_ref() == locale) =>
{
return
}
_ => (),
}

Expand Down
1 change: 1 addition & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
mod decoder;
mod iter;

pub mod matching;
pub use decoder::DecodeError;

pub use self::iter::Iter;
Expand Down
124 changes: 124 additions & 0 deletions src/matching.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
// Copyright 2021 System76 <[email protected]>
// SPDX-License-Identifier: MPL-2.0

use crate::DesktopEntry;

impl<'a> DesktopEntry<'a> {
/// The returned value is between 0.0 and 1.0 (higher value means more similar).
/// You can use the `additional_haystack_values` parameter to add relevant string that are not part of the desktop entry.
pub fn match_query<Q, L>(
&'a self,
query: Q,
locales: &'a [L],
additional_haystack_values: &'a [&'a str],
) -> f64
where
Q: AsRef<str>,
L: AsRef<str>,
{
#[inline]
fn add_value(v: &mut Vec<String>, value: &str, is_multiple: bool) {
if is_multiple {
value.split(';').for_each(|e| v.push(e.to_lowercase()));
} else {
v.push(value.to_lowercase());
}
}

// (field name, is separated by ";")
let fields = [
("Name", false),
("GenericName", false),
("Comment", false),
("Categories", true),
("Keywords", true),
];

let mut normalized_values: Vec<String> = Vec::new();

normalized_values.extend(
additional_haystack_values
.iter()
.map(|val| val.to_lowercase()),
);

let desktop_entry_group = self.groups.get("Desktop Entry");

for field in fields {
if let Some(group) = desktop_entry_group {
if let Some((default_value, locale_map)) = group.get(field.0) {
add_value(&mut normalized_values, default_value, field.1);

let mut at_least_one_locale = false;

for locale in locales {
match locale_map.get(locale.as_ref()) {
Some(value) => {
add_value(&mut normalized_values, value, field.1);
at_least_one_locale = true;
}
None => {
if let Some(pos) = locale.as_ref().find('_') {
if let Some(value) = locale_map.get(&locale.as_ref()[..pos]) {
add_value(&mut normalized_values, value, field.1);
at_least_one_locale = true;
}
}
}
}
}

if !at_least_one_locale {
if let Some(domain) = &self.ubuntu_gettext_domain {
let gettext_value = crate::dgettext(domain, default_value);
if !gettext_value.is_empty() {
add_value(&mut normalized_values, &gettext_value, false);
}
}
}
}
}
}

let query = query.as_ref().to_lowercase();

let query_espaced = query.split_ascii_whitespace().collect::<Vec<_>>();

normalized_values
.into_iter()
.map(|de_field| {
let jaro_score = strsim::jaro_winkler(&query, &de_field);

if query_espaced.iter().any(|query| de_field.contains(*query)) {
// provide a bonus if the query is contained in the de field
(jaro_score + 0.1).clamp(0.61, 1.)
} else {
jaro_score
}
})
.max_by(|e1, e2| e1.total_cmp(e2))
.unwrap_or(0.0)
}
}

/// Return the corresponding [`DesktopEntry`] that match the given appid.
pub fn find_entry_from_appid<'a, I>(entries: I, appid: &str) -> Option<&'a DesktopEntry<'a>>
where
I: Iterator<Item = &'a DesktopEntry<'a>>,
{
let normalized_appid = appid.to_lowercase();

entries.into_iter().find(|e| {
if e.appid.to_lowercase() == normalized_appid {
return true;
}

if let Some(field) = e.startup_wm_class() {
if field.to_lowercase() == normalized_appid {
return true;
}
}

false
})
}