freedesktop_file_parser/
lib.rs

1pub mod internal_structs;
2pub mod parser;
3pub mod structs;
4
5pub use parser::parse;
6pub use structs::*;
7
8#[cfg(test)]
9mod tests {
10    use super::*;
11
12    #[test]
13    fn test_basic_valid_entry() {
14        let content = r#"
15[Desktop Entry]
16Name=Firefox
17Exec=firefox %U
18Type=Application
19Categories=Network;WebBrowser;
20"#;
21        let f = parse(content).unwrap();
22        let entry = f.entry;
23
24        assert_eq!(entry.name.default, "Firefox");
25
26        match entry.entry_type {
27            EntryType::Application(fields) => {
28                assert_eq!(fields.exec.unwrap(), "firefox %U");
29                assert_eq!(fields.categories.unwrap(), vec!["Network", "WebBrowser"]);
30            }
31            _ => panic!("Entry type is not Application"),
32        }
33    }
34
35    #[test]
36    fn test_startup_wm_class() {
37        let content = r#"
38[Desktop Entry]
39Name=Spotify
40Exec=spotify %U
41Type=Application
42Categories=Audio;Music;Player;AudioVideo;
43StartupWMClass=spotify
44"#;
45        let f = parse(content).unwrap();
46        let entry = f.entry;
47
48        match entry.entry_type {
49            EntryType::Application(fields) => {
50                assert_eq!(fields.startup_wm_class.unwrap(), "spotify");
51            }
52            _ => panic!("Entry type is not Application"),
53        }
54    }
55
56    #[test]
57    fn test_localized_strings() {
58        let content = r#"
59[Desktop Entry]
60Name=Text Editor
61Name[es]=Editor de texto
62Name[fr]=Éditeur de texte
63Name[de]=Texteditor
64GenericName=Text Editor
65GenericName[es]=Editor
66Comment=Edit text files
67Comment[fr]=Éditer des fichiers texte
68Exec=gedit %F
69Type=Application
70"#;
71        let f = parse(content).unwrap();
72        let entry = f.entry;
73
74        let name = entry.name;
75        assert_eq!(name.default, "Text Editor");
76        assert_eq!(name.variants.get("es").unwrap(), "Editor de texto");
77        assert_eq!(name.variants.get("fr").unwrap(), "Éditeur de texte");
78        assert_eq!(name.variants.get("de").unwrap(), "Texteditor");
79
80        let generic_name = entry.generic_name.unwrap();
81        assert_eq!(generic_name.default, "Text Editor");
82        assert_eq!(generic_name.variants.get("es").unwrap(), "Editor");
83
84        let comment = entry.comment.unwrap();
85        assert_eq!(comment.default, "Edit text files");
86        assert_eq!(
87            comment.variants.get("fr").unwrap(),
88            "Éditer des fichiers texte"
89        );
90    }
91
92    #[test]
93    fn test_desktop_actions() {
94        let content = r#"
95[Desktop Entry]
96Name=Firefox
97Exec=firefox %U
98Type=Application
99Actions=new-window;new-private-window;
100
101[Desktop Action new-window]
102Name=New Window
103Name[es]=Nueva ventana
104Exec=firefox --new-window
105Icon=firefox-new-window
106
107[Desktop Action new-private-window]
108Name=New Private Window
109Exec=firefox --private-window
110"#;
111        let f = parse(content).unwrap();
112        let actions = f.actions;
113
114        assert_eq!(actions.len(), 2);
115
116        assert_eq!(
117            actions.get("new-window").unwrap().name.default,
118            "New Window"
119        );
120        assert_eq!(
121            actions
122                .get("new-window")
123                .unwrap()
124                .name
125                .variants
126                .get("es")
127                .unwrap(),
128            "Nueva ventana"
129        );
130        assert_eq!(
131            actions.get("new-window").unwrap().exec.as_ref().unwrap(),
132            "firefox --new-window"
133        );
134        assert_eq!(
135            actions
136                .get("new-window")
137                .unwrap()
138                .icon
139                .as_ref()
140                .unwrap()
141                .content,
142            "firefox-new-window"
143        );
144
145        assert_eq!(
146            actions.get("new-private-window").unwrap().name.default,
147            "New Private Window"
148        );
149        assert_eq!(
150            actions
151                .get("new-private-window")
152                .unwrap()
153                .exec
154                .as_ref()
155                .unwrap(),
156            "firefox --private-window"
157        );
158        assert!(actions.get("new-private-window").unwrap().icon.is_none());
159    }
160
161    #[test]
162    fn test_boolean_values() {
163        let content = r#"
164[Desktop Entry]
165Name=Test App
166Exec=test
167Type=Application
168Terminal=true
169NoDisplay=false
170Hidden=true
171DBusActivatable=true
172StartupNotify=true
173PrefersNonDefaultGPU=true
174SingleMainWindow=true
175"#;
176        let f = parse(content).unwrap();
177        let entry = f.entry;
178
179        assert_eq!(entry.no_display.unwrap(), false);
180        assert_eq!(entry.hidden.unwrap(), true);
181        assert_eq!(entry.dbus_activatable.unwrap(), true);
182
183        match entry.entry_type {
184            EntryType::Application(fields) => {
185                assert_eq!(fields.terminal.unwrap(), true);
186                assert_eq!(fields.startup_notify.unwrap(), true);
187                assert_eq!(fields.prefers_non_default_gpu.unwrap(), true);
188                assert_eq!(fields.single_main_window.unwrap(), true);
189            }
190            _ => panic!("Type not Application"),
191        }
192    }
193
194    #[test]
195    fn test_list_values() {
196        let content = r#"
197[Desktop Entry]
198Name=Test App
199Exec=test
200Type=Application
201Categories=Development;IDE;Programming;
202MimeType=text/plain;application/x-python;
203OnlyShowIn=GNOME;KDE;
204NotShowIn=XFCE;
205Keywords=development;coding;
206Keywords[es]=desarrollo;programación;
207Implements=org.freedesktop.Application;
208"#;
209        let f = parse(content).unwrap();
210        let entry = f.entry;
211        assert_eq!(entry.only_show_in.unwrap(), vec!["GNOME", "KDE"]);
212        assert_eq!(entry.not_show_in.unwrap(), vec!["XFCE"]);
213
214        match entry.entry_type {
215            EntryType::Application(fields) => {
216                let keywords = fields.keywords.unwrap();
217                assert_eq!(keywords.default, vec!["development", "coding"]);
218                assert_eq!(
219                    keywords.variants.get("es").unwrap(),
220                    &vec!["desarrollo", "programación"]
221                );
222
223                assert_eq!(
224                    fields.categories.unwrap(),
225                    vec!["Development", "IDE", "Programming"]
226                );
227                assert_eq!(
228                    fields.mime_type.unwrap(),
229                    vec!["text/plain", "application/x-python"]
230                );
231                assert_eq!(
232                    fields.implements.unwrap(),
233                    vec!["org.freedesktop.Application"]
234                );
235            }
236            _ => panic!("Entry type is not Application"),
237        }
238    }
239
240    #[test]
241    fn test_entry_types() {
242        let app_content = "[Desktop Entry]\nType=Application\nName=Test\nExec=test";
243        let link_content = "[Desktop Entry]\nType=Link\nName=Test\nURL=https://example.com";
244        let dir_content = "[Desktop Entry]\nType=Directory\nName=Test";
245        let unknown_content = "[Desktop Entry]\nType=CustomType\nName=Test";
246
247        let f = parse(app_content).unwrap();
248        let app_entry = f.entry;
249        let f = parse(link_content).unwrap();
250        let link_entry = f.entry;
251        let f = parse(dir_content).unwrap();
252        let dir_entry = f.entry;
253        let f = parse(unknown_content).unwrap();
254        let unknown_entry = f.entry;
255
256        assert!(matches!(app_entry.entry_type, EntryType::Application(_)));
257        assert!(matches!(link_entry.entry_type, EntryType::Link(_)));
258        assert!(matches!(dir_entry.entry_type, EntryType::Directory));
259        assert!(matches!(unknown_entry.entry_type, EntryType::Unknown));
260    }
261
262    #[test]
263    fn test_icon_string() {
264        let content = r#"
265[Desktop Entry]
266Name=Test App
267Exec=test
268Type=Application
269Icon=test-icon
270"#;
271        let f = parse(content).unwrap();
272        let entry = f.entry;
273        let icon = entry.icon.unwrap();
274        assert_eq!(icon.content, "test-icon");
275        // Note: We can't effectively test get_icon_path() here without mocking the filesystem
276    }
277
278    #[test]
279    #[should_panic]
280    fn test_missing_required_fields() {
281        let content = r#"
282[Desktop Entry]
283Exec=test
284Type=Application
285"#;
286        parse(content).unwrap();
287    }
288}