gdritter repos xdg-desktop / b9ebece
Basic library for parsing XDG desktop files Needs more comments, more tests, and a better, richer API Getty Ritter 5 years ago
5 changed file(s) with 529 addition(s) and 0 deletion(s). Collapse all Expand all
1
2 /target/
3 **/*.rs.bk
4 Cargo.lock
1 [package]
2 name = "xdg-desktop"
3 version = "0.1.0"
4 authors = ["Getty Ritter <gettylefou@gmail.com>"]
5
6 [dependencies]
7 regex = "1.0"
8 failure = "0.1.1"
9 lazy_static = "1.0"
1 #[macro_use] extern crate failure;
2 #[macro_use] extern crate lazy_static;
3 extern crate regex;
4
5 use failure::Error;
6
7 use std::collections::HashMap;
8 use std::io::{Read};
9
10 #[derive(Debug)]
11 pub struct DesktopEntry {
12 pub info: DesktopEntryInfo,
13 pub typ: EntryType,
14 }
15
16 #[derive(Debug)]
17 pub enum EntryType {
18 Application(ApplicationInfo),
19 Link(String),
20 Directory,
21 }
22
23 #[derive(Debug)]
24 pub struct ApplicationInfo {
25 pub dbus_activatable: bool,
26 pub try_exec: Option<String>,
27 pub exec: Option<String>,
28 pub path: Option<String>,
29 pub terminal: bool,
30 pub actions: Vec<Action>,
31 pub mime_type: Vec<String>,
32 pub categories: Vec<String>,
33 pub implements: Vec<String>,
34 pub keywords: Vec<String>,
35 pub startup_notify: bool,
36 pub startup_wm_class: Option<String>,
37 }
38
39 #[derive(Debug)]
40 pub struct Action {
41 pub name: String,
42 pub icon: Option<String>,
43 pub exec: Option<String>,
44 }
45
46 impl Action {
47 fn from_hashmap(bag: &mut HashMap<String, String>) -> Result<Action, Error> {
48 let name = bag.remove("Name")
49 .ok_or(format_err!("No Name!"))?;
50 let icon = bag.remove("Icon");
51 let exec = bag.remove("Exec");
52
53 Ok(Action { name, icon, exec })
54 }
55 }
56
57 impl ApplicationInfo {
58 fn from_hashmap(
59 bag: &mut HashMap<String, String>,
60 rest: &mut parser::Bag,
61 ) ->
62 Result<ApplicationInfo, Error>
63 {
64 let exec = bag.remove("Exec");
65 let path = bag.remove("Path");
66 let try_exec = bag.remove("TryExec");
67
68
69 let actions = bag.remove("Actions")
70 .map_or_else(|| Vec::new(), parser::list_of_strings);
71 let actions: Result<Vec<Action>, Error> =
72 actions.into_iter().map(|a: String| {
73 let key = format!("Desktop Action {}", a);
74 let mut b = rest.remove(&key)
75 .ok_or(format_err!("No action named {}", key))?;
76 let r: Result<Action, Error> = Action::from_hashmap(&mut b);
77 r
78 }).collect();
79 let actions = actions?;
80
81 let mime_type = bag.remove("MimeType")
82 .map_or_else(|| Vec::new(), parser::list_of_strings);
83 let categories = bag.remove("Categories")
84 .map_or_else(|| Vec::new(), parser::list_of_strings);
85 let implements = bag.remove("Implements")
86 .map_or_else(|| Vec::new(), parser::list_of_strings);
87 let keywords = bag.remove("Keywords")
88 .map_or_else(|| Vec::new(), parser::list_of_strings);
89
90 let terminal = bag.remove("Terminal")
91 .map_or_else(|| Ok(false), parser::to_bool)?;
92 let dbus_activatable = bag.remove("DbusActivatable")
93 .map_or_else(|| Ok(false), parser::to_bool)?;
94
95 let startup_notify = bag.remove("StartupNotify")
96 .map_or_else(|| Ok(false), parser::to_bool)?;
97 let startup_wm_class = None;
98
99 Ok(ApplicationInfo {
100 dbus_activatable,
101 try_exec,
102 exec,
103 path,
104 terminal,
105 actions,
106 mime_type,
107 categories,
108 implements,
109 keywords,
110 startup_notify,
111 startup_wm_class,
112 })
113 }
114 }
115
116 #[derive(Debug)]
117 pub struct DesktopEntryInfo {
118 pub version: Option<String>,
119 pub name: String,
120 pub generic_name: Option<String>,
121 pub no_display: bool,
122 pub comment: Option<String>,
123 pub icon: Option<String>,
124 pub hidden: bool,
125 pub only_show_in: Vec<String>,
126 pub not_show_in: Vec<String>,
127 }
128
129
130 mod parser {
131 use std::collections::HashMap;
132 use std::io::{BufRead, BufReader, Read};
133 use std::mem;
134
135 use failure::Error;
136 use regex::Regex;
137
138 pub type Bag = HashMap<String, HashMap<String, String>>;
139
140 pub fn bags_from_file<R: Read>(reader: &mut R) -> Result<Bag, Error> {
141 lazy_static! {
142 static ref SECTION: Regex =
143 Regex::new(r"\[([A-Za-z0-9 -]*)\]").unwrap();
144 static ref KV: Regex =
145 Regex::new(r"([A-Za-z0-9-]*(\[[A-Za-z@_]*\])?)=(.*)").unwrap();
146 }
147
148 let reader = BufReader::new(reader);
149 let mut bags = HashMap::new();
150 let mut current_section = None;
151 let mut current_bag = HashMap::new();
152
153 for ln in reader.lines() {
154 let ln = ln?;
155
156 if let Some(kv) = KV.captures(&ln) {
157 current_bag.insert(kv[1].to_owned(), kv[3].to_owned());
158 } else if let Some(s) = SECTION.captures(&ln) {
159 if let Some(name) = current_section.take() {
160 let bag = mem::replace(&mut current_bag, HashMap::new());
161 bags.insert(name, bag);
162 }
163 current_section = Some(s[1].to_owned());
164 }
165 }
166
167 if let Some(name) = current_section.take() {
168 bags.insert(name, current_bag);
169 }
170
171 Ok(bags)
172 }
173
174 pub fn list_of_strings(str: String) -> Vec<String> {
175 str.split(";").filter(|s| s.len() > 0).map(|s| s.to_owned()).collect()
176 }
177
178 pub fn to_bool(str: String) -> Result<bool, Error> {
179 match str.as_ref() {
180 "true" => Ok(true),
181 "false" => Ok(false),
182 _ => Err(format_err!("Invalid value for boolean field: {}", str)),
183 }
184 }
185 }
186
187 impl DesktopEntryInfo {
188 pub fn from_bag(bags: &mut parser::Bag) -> Result<DesktopEntryInfo, Error> {
189 let bag = bags.get_mut("Desktop Entry")
190 .ok_or(format_err!("No Desktop Entry"))?;
191
192 let version = bag.remove("Version");
193
194 let name = bag.remove("Name")
195 .ok_or(format_err!("No name"))?
196 .to_owned();
197 let generic_name = bag.remove("Generic Name");
198 let comment = bag.remove("Comment");
199 let icon = bag.remove("Icon");
200
201 let no_display = bag.remove("No Display")
202 .map_or_else(|| Ok(false), parser::to_bool)?;
203 let hidden = bag.remove("Hidden")
204 .map_or_else(|| Ok(false), parser::to_bool)?;
205
206 let only_show_in = bag.remove("OnlyShowIn")
207 .map_or_else(|| Vec::new(), parser::list_of_strings);
208 let not_show_in = bag.remove("NotShowIn")
209 .map_or_else(|| Vec::new(), parser::list_of_strings);
210
211 let info = DesktopEntryInfo {
212 version,
213 name,
214 generic_name,
215 no_display,
216 comment,
217 icon,
218 hidden,
219 only_show_in,
220 not_show_in,
221 };
222
223 Ok(info)
224 }
225 }
226
227 impl DesktopEntry {
228 pub fn from_file<R: Read>(reader: &mut R) -> Result<DesktopEntry, Error> {
229 let mut bags = parser::bags_from_file(reader)?;
230 DesktopEntry::from_bags(&mut bags)
231 }
232
233 fn from_bags(bags: &mut parser::Bag) -> Result<DesktopEntry, Error> {
234 let info = DesktopEntryInfo::from_bag(bags)?;
235 let mut bag = bags.remove("Desktop Entry")
236 .ok_or(format_err!("No Desktop Entry"))?;
237 let typ = bag.remove("Type")
238 .ok_or(format_err!("No Type"))?;
239 let typ = match typ.as_ref() {
240 "Application" => {
241 let app = ApplicationInfo::from_hashmap(&mut bag, bags)?;
242 EntryType::Application(app)
243 }
244 "Link" => {
245 let url = bag.remove("URL")
246 .ok_or(format_err!("No URL"))?;
247 EntryType::Link(url)
248 }
249 "Directory" => EntryType::Directory,
250 r => Err(format_err!("Bad type: {}", r))?,
251 };
252 Ok(DesktopEntry { info, typ })
253 }
254 }
255
256
257 #[cfg(test)]
258 mod tests {
259 #[test]
260 fn firefox_example() {
261 let firefox_example = include_str!("../test_cases/firefox.desktop");
262 let mut f = ::std::io::Cursor::new(firefox_example);
263 let mut entry = ::parser::bags_from_file(&mut f).unwrap();
264 assert_eq!(
265 entry["Desktop Entry"]["Name"],
266 "Firefox".to_owned(),
267 );
268
269 let info = ::DesktopEntryInfo::from_bag(&mut entry).unwrap();
270 println!("Got: {:#?}", info);
271 assert_eq!(
272 info.name,
273 "Firefox".to_owned(),
274 );
275 }
276
277 #[test]
278 fn steam_example() {
279 let steam_example = include_str!("../test_cases/steam.desktop");
280 let mut f = ::std::io::Cursor::new(steam_example);
281 let entry = ::DesktopEntry::from_file(&mut f).unwrap();
282 println!("got {:#?}", entry);
283 assert_eq!(
284 entry.info.name,
285 "Steam".to_owned(),
286 );
287 }
288 }
1 [Desktop Entry]
2 Name=Firefox
3 Name[bn]=ফায়ারফক্স
4 Name[eo]=Fajrovulpo
5 Name[fi]=Firefox
6 Name[pa]=ਫਾਇਰਫੋਕਸ
7 Name[tg]=Рӯбоҳи оташин
8 GenericName=Web Browser
9 GenericName[af]=Web Blaaier
10 GenericName[ar]=متصفح ويب
11 GenericName[az]=Veb Səyyahı
12 GenericName[bg]=Браузър
13 GenericName[bn]=ওয়েব ব্রাউজার
14 GenericName[br]=Furcher ar Gwiad
15 GenericName[bs]=WWW Preglednik
16 GenericName[ca]=Fullejador web
17 GenericName[cs]=WWW prohlížeč
18 GenericName[cy]=Porydd Gwe
19 GenericName[da]=Browser
20 GenericName[de]=Web-Browser
21 GenericName[el]=Περιηγητής Ιστού
22 GenericName[eo]=TTT-legilo
23 GenericName[es]=Navegador web
24 GenericName[et]=Veebilehitseja
25 GenericName[eu]=Web arakatzailea
26 GenericName[fa]=مرورگر وب
27 GenericName[fi]=WWW-selain
28 GenericName[fo]=Alnótsfar
29 GenericName[fr]=Navigateur web
30 GenericName[gl]=Navegador Web
31 GenericName[he]=דפדפן אינטרנט
32 GenericName[hi]=वेब ब्राउज़र
33 GenericName[hr]=Web preglednik
34 GenericName[hu]=Webböngésző
35 GenericName[is]=Vafri
36 GenericName[it]=Browser Web
37 GenericName[ja]=ウェブブラウザ
38 GenericName[ko]=웹 브라우저
39 GenericName[lo]=ເວັບບຣາວເຊີ
40 GenericName[lt]=Žiniatinklio naršyklė
41 GenericName[lv]=Web Pārlūks
42 GenericName[mk]=Прелистувач на Интернет
43 GenericName[mn]=Веб-Хөтөч
44 GenericName[nb]=Nettleser
45 GenericName[nds]=Nettkieker
46 GenericName[nl]=Webbrowser
47 GenericName[nn]=Nettlesar
48 GenericName[nso]=Seinyakisi sa Web
49 GenericName[pa]=ਵੈਬ ਝਲਕਾਰਾ
50 GenericName[pl]=Przeglądarka WWW
51 GenericName[pt]=Navegador Web
52 GenericName[pt_BR]=Navegador Web
53 GenericName[ro]=Navigator de web
54 GenericName[ru]=Веб-браузер
55 GenericName[se]=Fierpmádatlogan
56 GenericName[sk]=Webový prehliadač
57 GenericName[sl]=Spletni brskalnik
58 GenericName[sr]=Веб претраживач
59 GenericName[sr@Latn]=Veb pretraživač
60 GenericName[ss]=Ibrawuza yeWeb
61 GenericName[sv]=Webbläsare
62 GenericName[ta]=வலை உலாவி
63 GenericName[tg]=Тафсиргари вэб
64 GenericName[th]=เว็บบราวเซอร์
65 GenericName[tr]=Web Tarayıcı
66 GenericName[uk]=Навігатор Тенет
67 GenericName[uz]=Веб-браузер
68 GenericName[ven]=Buronza ya Webu
69 GenericName[vi]=Trình duyệt Web
70 GenericName[wa]=Betchteu waibe
71 GenericName[xh]=Umkhangeli zincwadi we Web
72 GenericName[zh_CN]=网页浏览器
73 GenericName[zh_TW]=網頁瀏覽器
74 GenericName[zu]=Umcingi we-Web
75 Comment=Browse the World Wide Web
76 Comment[ar]=تصفح الشبكة العنكبوتية العالمية
77 Comment[ast]=Restola pela Rede
78 Comment[bn]=ইন্টারনেট ব্রাউজ করুন
79 Comment[ca]=Navegueu per la web
80 Comment[cs]=Prohlížení stránek World Wide Webu
81 Comment[da]=Surf på internettet
82 Comment[de]=Im Internet surfen
83 Comment[el]=Μπορείτε να περιηγηθείτε στο διαδίκτυο (Web)
84 Comment[es]=Navegue por la web
85 Comment[et]=Lehitse veebi
86 Comment[fa]=صفحات شبکه جهانی اینترنت را مرور نمایید
87 Comment[fi]=Selaa Internetin WWW-sivuja
88 Comment[fr]=Naviguer sur le Web
89 Comment[gl]=Navegar pola rede
90 Comment[he]=גלישה ברחבי האינטרנט
91 Comment[hr]=Pretražite web
92 Comment[hu]=A világháló böngészése
93 Comment[it]=Esplora il web
94 Comment[ja]=ウェブを閲覧します
95 Comment[ko]=웹을 돌아 다닙니다
96 Comment[ku]=Li torê bigere
97 Comment[lt]=Naršykite internete
98 Comment[nb]=Surf på nettet
99 Comment[nl]=Verken het internet
100 Comment[nn]=Surf på nettet
101 Comment[no]=Surf på nettet
102 Comment[pl]=Przeglądanie stron WWW
103 Comment[pt]=Navegue na Internet
104 Comment[pt_BR]=Navegue na Internet
105 Comment[ro]=Navigați pe Internet
106 Comment[ru]=Доступ в Интернет
107 Comment[sk]=Prehliadanie internetu
108 Comment[sl]=Brskajte po spletu
109 Comment[sv]=Surfa på webben
110 Comment[ug]=دۇنيادىكى توربەتلەرنى كۆرگىلى بولىدۇ
111 Comment[uk]=Перегляд сторінок Інтернету
112 Comment[vi]=Để duyệt các trang web
113 Comment[zh_CN]=浏览互联网
114 Comment[zh_TW]=瀏覽網際網路
115 Exec=firefox %u
116 Icon=firefox
117 Terminal=false
118 Type=Application
119 MimeType=text/html;text/xml;application/xhtml+xml;application/vnd.mozilla.xul+xml;text/mml;x-scheme-handler/http;x-scheme-handler/https;
120 StartupNotify=true
121 Categories=Network;WebBrowser;
1 [Desktop Entry]
2 Name=Steam
3 Comment=Application for managing and playing games on Steam
4 Exec=/usr/bin/steam %U
5 Icon=steam
6 Terminal=false
7 Type=Application
8 Categories=Network;FileTransfer;Game;
9 MimeType=x-scheme-handler/steam;
10 Actions=Store;Community;Library;Servers;Screenshots;News;Settings;BigPicture;Friends;
11
12 [Desktop Action Store]
13 Name=Store
14 Name[de]=Shop
15 Name[es]=Tienda
16 Name[fr]=Magasin
17 Name[it]=Negozio
18 Name[pt]=Loja
19 Name[ru]=Магазин
20 Name[zh_CN]=商店
21 Name[zh_TW]=商店
22 Exec=steam steam://store
23
24 [Desktop Action Community]
25 Name=Community
26 Name[es]=Comunidad
27 Name[fr]=Communauté
28 Name[it]=Comunità
29 Name[pt]=Comunidade
30 Name[ru]=Сообщество
31 Name[zh_CN]=社区
32 Name[zh_TW]=社群
33 Exec=steam steam://url/SteamIDControlPage
34
35 [Desktop Action Library]
36 Name=Library
37 Name[de]=Bibliothek
38 Name[es]=Biblioteca
39 Name[fr]=Bibliothèque
40 Name[it]=Libreria
41 Name[pt]=Biblioteca
42 Name[ru]=Библиотека
43 Name[zh_CN]=库
44 Name[zh_TW]=遊戲庫
45 Exec=steam steam://open/games
46
47 [Desktop Action Servers]
48 Name=Servers
49 Name[de]=Server
50 Name[es]=Servidores
51 Name[fr]=Serveurs
52 Name[it]=Server
53 Name[pt]=Servidores
54 Name[ru]=Серверы
55 Name[zh_CN]=服务器
56 Name[zh_TW]=伺服器
57 Exec=steam steam://open/servers
58
59 [Desktop Action Screenshots]
60 Name=Screenshots
61 Name[es]=Capturas
62 Name[fr]=Captures d'écran
63 Name[it]=Screenshot
64 Name[ru]=Скриншоты
65 Name[zh_CN]=截图
66 Name[zh_TW]=螢幕擷圖
67 Exec=steam steam://open/screenshots
68
69 [Desktop Action News]
70 Name=News
71 Name[de]=Neuigkeiten
72 Name[es]=Noticias
73 Name[fr]=Actualités
74 Name[it]=Notizie
75 Name[pt]=Notícias
76 Name[ru]=Новости
77 Name[zh_CN]=新闻
78 Name[zh_TW]=新聞
79 Exec=steam steam://open/news
80
81 [Desktop Action Settings]
82 Name=Settings
83 Name[de]=Einstellungen
84 Name[es]=Parámetros
85 Name[fr]=Paramètres
86 Name[it]=Impostazioni
87 Name[pt]=Configurações
88 Name[ru]=Настройки
89 Name[zh_CN]=设置
90 Name[zh_TW]=設定
91 Exec=steam steam://open/settings
92
93 [Desktop Action BigPicture]
94 Name=Big Picture
95 Exec=steam steam://open/bigpicture
96
97 [Desktop Action Friends]
98 Name=Friends
99 Name[de]=Freunde
100 Name[es]=Amigos
101 Name[fr]=Amis
102 Name[it]=Amici
103 Name[pt]=Amigos
104 Name[ru]=Друзья
105 Name[zh_CN]=好友
106 Name[zh_TW]=好友
107 Exec=steam steam://open/friends