Skip to content

Commit e3106c1

Browse files
authored
feat: owned variants, matching, benchmarks, and translation improvements
1 parent 8b130a6 commit e3106c1

12 files changed

+921
-102
lines changed

.vscode/settings.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
{
2+
"rust-analyzer.cargo.features": [
3+
]
4+
}

Cargo.toml

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,18 +2,19 @@
22
name = "freedesktop-desktop-entry"
33
version = "0.5.2"
44
authors = ["Michael Aaron Murphy <[email protected]>"]
5-
edition = "2018"
5+
edition = "2021"
66
homepage = "https://github.com/pop-os/freedesktop-desktop-entry"
77
description = "Freedesktop Desktop Entry Specification"
88
license = "MPL-2.0"
99
readme = "README.md"
1010
categories = [ "os::unix-apis" ]
1111
keywords = [ "freedesktop", "desktop", "entry" ]
12-
maintenance = { status = "passively-maintained" }
1312

1413
[dependencies]
15-
dirs = "3"
14+
dirs = "5.0.1"
1615
gettext-rs = { version = "0.7", features = ["gettext-system"]}
1716
memchr = "2"
17+
textdistance = "1.0.2"
18+
strsim = "0.11.1"
1819
thiserror = "1"
1920
xdg = "2.4.0"

examples/example.rs renamed to examples/all_files.rs

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,17 @@
33

44
use std::fs;
55

6-
use freedesktop_desktop_entry::{default_paths, DesktopEntry, Iter, PathSource};
6+
use freedesktop_desktop_entry::{
7+
default_paths, get_languages_from_env, DesktopEntry, Iter, PathSource,
8+
};
79

810
fn main() {
11+
let locale = get_languages_from_env();
12+
913
for path in Iter::new(default_paths()) {
1014
let path_src = PathSource::guess_from(&path);
1115
if let Ok(bytes) = fs::read_to_string(&path) {
12-
if let Ok(entry) = DesktopEntry::decode(&path, &bytes) {
16+
if let Ok(entry) = DesktopEntry::decode_from_str(&path, &bytes, &locale) {
1317
println!("{:?}: {}\n---\n{}", path_src, path.display(), entry);
1418
}
1519
}

examples/bench.rs

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
// Copyright 2021 System76 <[email protected]>
2+
// SPDX-License-Identifier: MPL-2.0
3+
4+
use std::{fs, time::Duration};
5+
6+
use freedesktop_desktop_entry::{default_paths, get_languages_from_env, DesktopEntry, Iter};
7+
8+
use std::time::Instant;
9+
10+
fn main() {
11+
let it = 500;
12+
13+
bench_borrowed(it);
14+
bench_owned(it);
15+
bench_owned_optimized(it);
16+
}
17+
18+
fn bench_borrowed(it: u32) {
19+
let mut total_time = Duration::ZERO;
20+
21+
for _ in 0..it {
22+
let locale = get_languages_from_env();
23+
let paths = Iter::new(default_paths());
24+
25+
let now = Instant::now();
26+
27+
for path in paths {
28+
if let Ok(bytes) = fs::read_to_string(&path) {
29+
if let Ok(_entry) = DesktopEntry::decode_from_str(&path, &bytes, &locale) {}
30+
}
31+
}
32+
33+
total_time += now.elapsed();
34+
}
35+
36+
println!("bench_borrowed: {:.2?}", total_time / it);
37+
}
38+
39+
fn bench_owned(it: u32) {
40+
let mut total_time = Duration::ZERO;
41+
42+
for _ in 0..it {
43+
let locale = get_languages_from_env();
44+
let paths = Iter::new(default_paths());
45+
46+
let now = Instant::now();
47+
48+
for path in paths {
49+
if let Ok(_entry) = DesktopEntry::decode_from_path(path, &locale) {}
50+
}
51+
52+
total_time += now.elapsed();
53+
}
54+
55+
println!("bench_owned: {:.2?}", total_time / it);
56+
}
57+
58+
fn bench_owned_optimized(it: u32) {
59+
let mut total_time = Duration::ZERO;
60+
61+
for _ in 0..it {
62+
let locale = get_languages_from_env();
63+
let paths = Iter::new(default_paths());
64+
65+
let now = Instant::now();
66+
67+
let _ = DesktopEntry::decode_from_paths(paths, &locale)
68+
.filter_map(|e| e.ok())
69+
.collect::<Vec<_>>();
70+
71+
total_time += now.elapsed();
72+
}
73+
74+
println!("bench_owned_optimized: {:.2?}", total_time / it);
75+
}

examples/specific_file.rs

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
use std::path::Path;
2+
3+
use freedesktop_desktop_entry::DesktopEntry;
4+
5+
fn main() {
6+
let path = Path::new("tests/org.mozilla.firefox.desktop");
7+
let locales = &["fr", "en"];
8+
9+
// if let Ok(bytes) = fs::read_to_string(path) {
10+
// if let Ok(entry) = DesktopEntry::decode_from_str(path, &bytes, locales) {
11+
// println!("{}\n---\n{}", path.display(), entry);
12+
// }
13+
// }
14+
15+
if let Ok(entry) = DesktopEntry::decode_from_path(path.to_path_buf(), locales) {
16+
println!("{}\n---\n{}", path.display(), entry);
17+
}
18+
}

src/decoder.rs

Lines changed: 190 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,190 @@
1+
use std::{
2+
borrow::Cow,
3+
collections::BTreeMap,
4+
fs::File,
5+
io::{self, BufRead},
6+
path::{Path, PathBuf},
7+
};
8+
9+
use crate::DesktopEntry;
10+
use crate::{Groups, LocaleMap};
11+
use thiserror::Error;
12+
13+
#[derive(Debug, Error)]
14+
pub enum DecodeError {
15+
#[error("path does not contain a valid app ID")]
16+
AppID,
17+
#[error(transparent)]
18+
Io(#[from] std::io::Error),
19+
}
20+
21+
impl<'a> DesktopEntry<'a> {
22+
pub fn decode_from_str<L>(
23+
path: &'a Path,
24+
input: &'a str,
25+
locales: &[L],
26+
) -> Result<DesktopEntry<'a>, DecodeError>
27+
where
28+
L: AsRef<str>,
29+
{
30+
let appid = get_app_id(path)?;
31+
32+
let mut groups = Groups::new();
33+
let mut active_group = Cow::Borrowed("");
34+
let mut ubuntu_gettext_domain = None;
35+
36+
for line in input.lines() {
37+
process_line(
38+
line,
39+
&mut groups,
40+
&mut active_group,
41+
&mut ubuntu_gettext_domain,
42+
locales,
43+
Cow::Borrowed,
44+
)
45+
}
46+
47+
Ok(DesktopEntry {
48+
appid: Cow::Borrowed(appid),
49+
groups,
50+
path: Cow::Borrowed(path),
51+
ubuntu_gettext_domain,
52+
})
53+
}
54+
55+
pub fn decode_from_paths<'i, 'l: 'i, L>(
56+
paths: impl Iterator<Item = PathBuf> + 'i,
57+
locales: &'l [L],
58+
) -> impl Iterator<Item = Result<DesktopEntry<'static>, DecodeError>> + 'i
59+
where
60+
L: AsRef<str>,
61+
{
62+
let mut buf = String::new();
63+
64+
paths.map(move |path| decode_from_path_with_buf(path, locales, &mut buf))
65+
}
66+
67+
/// Return an owned [`DesktopEntry`]
68+
pub fn decode_from_path<L>(
69+
path: PathBuf,
70+
locales: &[L],
71+
) -> Result<DesktopEntry<'static>, DecodeError>
72+
where
73+
L: AsRef<str>,
74+
{
75+
let mut buf = String::new();
76+
decode_from_path_with_buf(path, locales, &mut buf)
77+
}
78+
}
79+
80+
fn get_app_id<P: AsRef<Path> + ?Sized>(path: &P) -> Result<&str, DecodeError> {
81+
let appid = path
82+
.as_ref()
83+
.file_stem()
84+
.ok_or(DecodeError::AppID)?
85+
.to_str()
86+
.ok_or(DecodeError::AppID)?;
87+
Ok(appid)
88+
}
89+
90+
#[inline]
91+
fn process_line<'buf, 'local_ref, 'res: 'local_ref + 'buf, F, L>(
92+
line: &'buf str,
93+
groups: &'local_ref mut Groups<'res>,
94+
active_group: &'local_ref mut Cow<'res, str>,
95+
ubuntu_gettext_domain: &'local_ref mut Option<Cow<'res, str>>,
96+
locales_filter: &[L],
97+
convert_to_cow: F,
98+
) where
99+
F: Fn(&'buf str) -> Cow<'res, str>,
100+
L: AsRef<str>,
101+
{
102+
let line = line.trim();
103+
if line.is_empty() || line.starts_with('#') {
104+
return;
105+
}
106+
107+
let line_bytes = line.as_bytes();
108+
109+
if line_bytes[0] == b'[' {
110+
if let Some(end) = memchr::memrchr(b']', &line_bytes[1..]) {
111+
*active_group = convert_to_cow(&line[1..end + 1]);
112+
}
113+
} else if let Some(delimiter) = memchr::memchr(b'=', line_bytes) {
114+
let key = &line[..delimiter];
115+
let value = &line[delimiter + 1..];
116+
117+
// if locale
118+
if key.as_bytes()[key.len() - 1] == b']' {
119+
if let Some(start) = memchr::memchr(b'[', key.as_bytes()) {
120+
let key_name = &key[..start];
121+
let locale = &key[start + 1..key.len() - 1];
122+
123+
if !locales_filter.iter().any(|l| l.as_ref() == locale) {
124+
return;
125+
}
126+
127+
groups
128+
.entry(active_group.clone())
129+
.or_default()
130+
.entry(convert_to_cow(key_name))
131+
.or_insert_with(|| (Cow::Borrowed(""), LocaleMap::new()))
132+
.1
133+
.insert(convert_to_cow(locale), convert_to_cow(value));
134+
135+
return;
136+
}
137+
}
138+
139+
if key == "X-Ubuntu-Gettext-Domain" {
140+
*ubuntu_gettext_domain = Some(convert_to_cow(value));
141+
return;
142+
}
143+
144+
groups
145+
.entry(active_group.clone())
146+
.or_default()
147+
.entry(convert_to_cow(key))
148+
.or_insert_with(|| (Cow::Borrowed(""), BTreeMap::new()))
149+
.0 = convert_to_cow(value);
150+
}
151+
}
152+
153+
#[inline]
154+
fn decode_from_path_with_buf<L>(
155+
path: PathBuf,
156+
locales: &[L],
157+
buf: &mut String,
158+
) -> Result<DesktopEntry<'static>, DecodeError>
159+
where
160+
L: AsRef<str>,
161+
{
162+
let file = File::open(&path)?;
163+
164+
let appid = get_app_id(&path)?;
165+
166+
let mut groups = Groups::new();
167+
let mut active_group = Cow::Borrowed("");
168+
let mut ubuntu_gettext_domain = None;
169+
170+
let mut reader = io::BufReader::new(file);
171+
172+
while reader.read_line(buf)? != 0 {
173+
process_line(
174+
buf,
175+
&mut groups,
176+
&mut active_group,
177+
&mut ubuntu_gettext_domain,
178+
locales,
179+
|s| Cow::Owned(s.to_owned()),
180+
);
181+
buf.clear();
182+
}
183+
184+
Ok(DesktopEntry {
185+
appid: Cow::Owned(appid.to_owned()),
186+
groups,
187+
path: Cow::Owned(path),
188+
ubuntu_gettext_domain,
189+
})
190+
}

0 commit comments

Comments
 (0)