1use crate::internal_structs::vec_to_map;
2use std::{cell::RefCell, collections::HashMap, fmt, rc::Rc};
3
4use crate::{
5 internal_structs::{
6 DesktopActionInternal, DesktopEntryInternal, Header, LocaleStringInternal,
7 LocaleStringListInternal,
8 },
9 structs::ParseError,
10 DesktopFile, IconString,
11};
12
13#[derive(Debug)]
14enum LineType {
15 Header,
16 ValPair,
17}
18
19#[derive(Debug)]
20enum EntryType {
21 Entry(Rc<RefCell<DesktopEntryInternal>>),
22 Action(usize),
23}
24
25#[derive(Debug)]
26struct Line<'a> {
27 content: Vec<Character<'a>>,
28 line_number: usize,
29}
30
31impl<'a> Line<'a> {
32 pub fn from_data(line: &'a str, line_number: usize) -> Self {
33 let content: Vec<Character<'a>> = line
34 .trim_end()
35 .char_indices()
36 .map(|(col_number, ch)| Character {
37 content: &line[col_number..col_number + ch.len_utf8()],
38 line_number,
39 col_number,
40 })
41 .filter(|ch| !(ch.col_number == 0 && ch.content == " "))
42 .collect();
43
44 Self {
45 content,
46 line_number,
47 }
48 }
49
50 pub fn line_type(&self) -> LineType {
51 if self.content[0].content == "[" {
52 LineType::Header
53 } else {
54 LineType::ValPair
55 }
56 }
57}
58impl<'a> fmt::Display for Line<'a> {
59 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
60 for ch in &self.content {
61 write!(f, "{}", ch.content)?;
62 }
63 Ok(())
64 }
65}
66
67#[derive(Debug, Clone)]
68struct Character<'a> {
69 content: &'a str,
70 line_number: usize,
71 col_number: usize,
72}
73
74fn filter_lines(input: &str) -> Vec<Line> {
75 input
76 .split("\n")
77 .enumerate()
78 .filter(|element| !element.1.is_empty() && !element.1.trim().starts_with("#"))
79 .map(|(num, l)| Line::from_data(l, num))
80 .collect()
81}
82
83fn parse_header(input: &Line) -> Result<Header, ParseError> {
84 enum HeaderParseState {
85 Idle,
86 Content,
87 }
88
89 let mut state = HeaderParseState::Idle;
90 let mut result = String::new();
91
92 for (ind, ch) in input.content.iter().enumerate() {
93 match state {
94 HeaderParseState::Idle => match ch.content {
95 "[" => {
96 state = HeaderParseState::Content;
97 }
98 _ => {
99 return Err(ParseError::InternalError {
100 msg: "line is mis-classified as a header".into(),
101 row: ch.line_number,
102 col: ch.col_number,
103 });
104 }
105 },
106 HeaderParseState::Content => match ch.content {
107 "]" => {
108 if ind != input.content.len() - 1 {
109 return Err(ParseError::Syntax {
110 msg: "nothing is expected after \"]\"".to_string(),
111 row: ch.line_number,
112 col: ch.col_number,
113 });
114 }
115 }
116 "[" => {
117 return Err(ParseError::UnacceptableCharacter {
118 ch: ch.content.to_string(),
119 row: ch.line_number,
120 col: ch.col_number,
121 msg: format!("\"{}\" is not accepted in header", ch.content),
122 });
123 }
124 _ => {
125 if ch.content.chars().next().unwrap().is_control() {
126 return Err(ParseError::UnacceptableCharacter {
127 ch: ch.content.to_string(),
128 row: ch.line_number,
129 col: ch.col_number,
130 msg: "none".to_string(),
131 });
132 }
133 result.push_str(ch.content);
134 }
135 },
136 }
137 }
138
139 if result == "Desktop Entry" {
140 Ok(Header::DesktopEntry)
141 } else if let Some(remain) = result.strip_prefix("Desktop Action ") {
142 Ok(Header::DesktopAction {
143 name: remain.to_string(),
144 })
145 } else {
146 Ok(Header::Other { name: result })
147 }
148}
149
150#[derive(Clone)]
152struct LinePart {
153 key: String,
154 locale: Option<String>,
155 value: String,
156 line_number: usize,
157}
158
159fn split_into_parts(line: &Line) -> Result<LinePart, ParseError> {
160 #[cfg(test)]
161 println!("This line is: {:?}", line.to_string());
162
163 enum State {
164 Key,
166 KeyLocale,
168 LocaleToValue,
170 Value,
172 }
173
174 let mut result = LinePart {
175 key: "".into(),
176 locale: None,
177 value: "".into(),
178 line_number: line.line_number,
179 };
180
181 let mut state = State::Key;
182 let mut key_has_space = false;
183
184 for ch in line.content.iter() {
185 match state {
186 State::Key => match ch.content {
187 "[" => {
188 state = State::KeyLocale;
189 result.locale = Some("".into())
190 }
191
192 "=" => state = State::Value,
193
194 " " => key_has_space = true,
195
196 "A" | "B" | "C" | "D" | "E" | "F" | "G" | "H" | "I" | "J" | "K" | "L" | "M"
197 | "N" | "O" | "P" | "Q" | "R" | "S" | "T" | "U" | "V" | "W" | "X" | "Y" | "Z"
198 | "a" | "b" | "c" | "d" | "e" | "f" | "g" | "h" | "i" | "j" | "k" | "l" | "m"
199 | "n" | "o" | "p" | "q" | "r" | "s" | "t" | "u" | "v" | "w" | "x" | "y" | "z"
200 | "1" | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9" | "0" | "-" => {
201 if !key_has_space {
202 result.key.push_str(ch.content)
203 } else {
204 return Err(ParseError::Syntax {
205 msg: "Keys shouldn't have characters other than A-Za-z0-9-".into(),
206 row: ch.line_number,
207 col: ch.col_number,
208 });
209 }
210 }
211
212 _ => {
213 return Err(ParseError::Syntax {
214 msg: "Keys shouldn't have characters other than A-Za-z0-9-".into(),
215 row: ch.line_number,
216 col: ch.col_number,
217 })
218 }
219 },
220
221 State::KeyLocale => match ch.content {
222 "]" => state = State::LocaleToValue,
223
224 _ => {
225 if let Some(ref mut str) = result.locale {
226 str.push_str(ch.content);
227 }
228 }
229 },
230
231 State::LocaleToValue => match ch.content {
232 "=" => state = State::Value,
233
234 _ => {
235 return Err(ParseError::Syntax {
236 msg: "Expect \"=\" after \"=\"".into(),
237 row: ch.line_number,
238 col: ch.col_number,
239 });
240 }
241 },
242
243 State::Value => result.value.push_str(ch.content),
244 }
245 }
246
247 result.value = result.value.trim_start().to_string();
248 result.key = result.key.trim_end().to_string();
249
250 Ok(result)
251}
252
253fn set_locale_str(parts: LinePart, str: &mut LocaleStringInternal) -> Result<(), ParseError> {
254 match parts.locale {
257 Some(locale) => {
258 if str.variants.contains_key(&locale) {
259 return Err(ParseError::RepetitiveKey {
260 key: parts.key,
261 row: parts.line_number,
262 col: 0,
263 });
264 }
265 str.variants.insert(locale, parts.value);
266 }
267 None => {
268 if str.default.is_none() {
269 str.default = Some(parts.value);
270 } else {
271 return Err(ParseError::RepetitiveKey {
272 key: parts.key,
273 row: parts.line_number,
274 col: 0,
275 });
276 }
277 }
278 }
279
280 Ok(())
281}
282
283fn set_optional_locale_str(
284 parts: LinePart,
285 opt: &mut Option<LocaleStringInternal>,
286) -> Result<(), ParseError> {
287 match opt {
288 Some(str) => set_locale_str(parts, str),
289
290 None => {
291 {
292 let mut inner = LocaleStringInternal::default();
293
294 set_locale_str(parts, &mut inner)?;
295
296 *opt = Some(inner);
297 };
298 Ok(())
299 }
300 }
301}
302
303fn set_bool(parts: LinePart, val: &mut bool) -> Result<(), ParseError> {
304 *val = parts
305 .value
306 .parse::<bool>()
307 .map_err(|_| ParseError::Syntax {
308 msg: "Property's value needs to be bool".into(),
309 row: parts.line_number,
310 col: 0,
311 })?;
312 Ok(())
313}
314
315fn set_optional_bool(parts: LinePart, opt: &mut Option<bool>) -> Result<(), ParseError> {
316 match opt {
318 Some(_) => {
319 return Err(ParseError::RepetitiveKey {
320 key: parts.key,
321 row: parts.line_number,
322 col: 0,
323 });
324 }
325 None => {
326 let mut res = false;
327 set_bool(parts, &mut res)?;
328 *opt = Some(res);
329 }
330 }
331
332 Ok(())
333}
334
335fn set_optional_list(parts: LinePart, opt: &mut Option<Vec<String>>) -> Result<(), ParseError> {
336 if !opt.is_none() {
337 return Err(ParseError::RepetitiveKey {
338 key: parts.key,
339 row: parts.line_number,
340 col: 0,
341 });
342 }
343
344 *opt = Some({
345 let mut res = parts
346 .value
347 .split(";")
348 .map(|s| s.to_string())
349 .collect::<Vec<String>>();
350
351 if let Some(val) = res.last() {
352 if val.is_empty() {
353 res.pop();
354 }
355 }
356
357 res
358 });
359 Ok(())
360}
361
362fn set_optional_str(parts: LinePart, opt: &mut Option<String>) -> Result<(), ParseError> {
363 if !opt.is_none() {
364 return Err(ParseError::RepetitiveKey {
365 key: parts.key,
366 row: parts.line_number,
367 col: 0,
368 });
369 }
370
371 *opt = Some(parts.value);
372 Ok(())
373}
374
375fn set_optional_icon_str(parts: LinePart, opt: &mut Option<IconString>) -> Result<(), ParseError> {
376 if !opt.is_none() {
377 return Err(ParseError::RepetitiveKey {
378 key: parts.key,
379 row: parts.line_number,
380 col: 0,
381 });
382 }
383
384 *opt = Some(IconString {
385 content: parts.value,
386 });
387 Ok(())
388}
389
390fn fill_entry_val(entry: &mut DesktopEntryInternal, parts: LinePart) -> Result<(), ParseError> {
391 match parts.key.as_str() {
392 "Type" => {
393 if entry.entry_type.is_some() {
394 return Err(ParseError::RepetitiveKey {
395 key: "Type".into(),
396 row: parts.line_number,
397 col: 0,
398 });
399 }
400
401 entry.entry_type = Some(crate::internal_structs::EntryTypeInternal::from(
402 parts.value.as_str(),
403 ));
404 }
405 "Version" => set_optional_str(parts, &mut entry.version)?,
406 "Name" => set_optional_locale_str(parts, &mut entry.name)?,
407 "GenericName" => set_optional_locale_str(parts, &mut entry.generic_name)?,
408 "NoDisplay" => set_optional_bool(parts, &mut entry.no_display)?,
409 "Comment" => set_optional_locale_str(parts, &mut entry.comment)?,
410 "Icon" => set_optional_icon_str(parts, &mut entry.icon)?,
411 "Hidden" => set_optional_bool(parts, &mut entry.hidden)?,
412 "OnlyShowIn" => set_optional_list(parts, &mut entry.only_show_in)?,
413 "NotShowIn" => set_optional_list(parts, &mut entry.not_show_in)?,
414 "DBusActivatable" => set_optional_bool(parts, &mut entry.dbus_activatable)?,
415 "TryExec" => set_optional_str(parts, &mut entry.try_exec)?,
416 "Exec" => set_optional_str(parts, &mut entry.exec)?,
417 "Path" => set_optional_str(parts, &mut entry.path)?,
418 "Terminal" => set_optional_bool(parts, &mut entry.terminal)?,
419 "Actions" => set_optional_list(parts, &mut entry.actions)?,
420 "MimeType" => set_optional_list(parts, &mut entry.mime_type)?,
421 "Categories" => set_optional_list(parts, &mut entry.categories)?,
422 "Implements" => set_optional_list(parts, &mut entry.implements)?,
423 "Keywords" => {
424 let mut split = parts
425 .value
426 .split(";")
427 .map(|str| str.to_string())
428 .collect::<Vec<String>>();
429
430 if let Some(val) = split.last() {
431 if val.is_empty() {
432 split.pop();
433 }
434 }
435
436 match entry.keywords {
437 Some(ref mut kwds) => match parts.locale {
438 Some(locale) => {
439 if kwds.variants.contains_key(&locale) {
440 return Err(ParseError::RepetitiveKey {
441 key: "Keywords".into(),
442 row: parts.line_number,
443 col: 0,
444 });
445 }
446
447 kwds.variants.insert(locale, split);
448 }
449 None => {
450 if kwds.default.is_some() {
451 return Err(ParseError::RepetitiveKey {
452 key: "Keywords".into(),
453 row: parts.line_number,
454 col: 0,
455 });
456 }
457
458 kwds.default = Some(split);
459 }
460 },
461 None => {
462 let mut res = LocaleStringListInternal::default();
463 match parts.locale {
464 Some(locale) => {
465 res.variants.insert(locale, split);
466 }
467 None => {
468 res.default = Some(split);
469 }
470 }
471
472 entry.keywords = Some(res);
473 }
474 }
475 }
476 "StartupNotify" => set_optional_bool(parts, &mut entry.startup_notify)?,
477 "StartupWMClass" => set_optional_str(parts, &mut entry.startup_wm_class)?,
478 "URL" => set_optional_str(parts, &mut entry.url)?,
479 "PrefersNonDefaultGPU" => set_optional_bool(parts, &mut entry.prefers_non_default_gpu)?,
480 "SingleMainWindow" => set_optional_bool(parts, &mut entry.single_main_window)?,
481
482 _ => {}
483 }
484
485 Ok(())
486}
487
488fn process_entry_val_pair(line: &Line, entry: &mut DesktopEntryInternal) -> Result<(), ParseError> {
489 let parts = split_into_parts(line)?;
490
491 fill_entry_val(entry, parts)
492}
493
494fn fill_action_val(action: &mut DesktopActionInternal, parts: LinePart) -> Result<(), ParseError> {
495 match parts.key.as_str() {
496 "Name" => set_optional_locale_str(parts, &mut action.name)?,
497 "Exec" => set_optional_str(parts, &mut action.exec)?,
498 "Icon" => set_optional_icon_str(parts, &mut action.icon)?,
499 _ => {}
500 }
501
502 Ok(())
503}
504
505fn process_action_val_pair(
506 line: &Line,
507 action: &mut DesktopActionInternal,
508) -> Result<(), ParseError> {
509 let parts = split_into_parts(line)?;
510
511 fill_action_val(action, parts)
512}
513
514pub fn parse(input: &str) -> Result<DesktopFile, ParseError> {
534 let mut lines = filter_lines(input);
535 let result_entry = Rc::new(RefCell::new(DesktopEntryInternal::default()));
536
537 let mut is_entry_found = false;
538 let mut is_first_entry = true;
539
540 let mut result_actions: Vec<DesktopActionInternal> = vec![];
541 let mut current_target = EntryType::Entry(result_entry.clone());
542
543 for line in lines.iter_mut() {
544 match current_target {
545 EntryType::Entry(ref entry) => match line.line_type() {
546 LineType::Header => {
547 match parse_header(line)? {
548 Header::DesktopEntry => {
549 if is_entry_found {
550 return Err(ParseError::RepetitiveEntry {
551 msg: "none".into(),
552 row: line.line_number,
553 col: 0,
554 });
555 } else {
556 is_entry_found = true;
557 }
558
559 if !is_first_entry {
560 return Err(ParseError::InternalError { msg: "it should be able to return error when entry is not in the first header".into(), row: line.line_number, col: 0 });
561 } else {
562 is_first_entry = false;
563 }
564 }
565 Header::DesktopAction { name } => {
566 if !is_entry_found {
567 return Err(ParseError::InternalError { msg: "it should be able to return error when an action appears before an entry".into(), row: line.line_number, col: 0 });
568 }
569
570 if is_first_entry {
571 return Err(ParseError::FormatError {
572 msg: "none".into(),
573 row: line.line_number,
574 col: 0,
575 });
576 }
577
578 result_actions.push(DesktopActionInternal {
579 ref_name: name,
580 ..Default::default()
581 });
582
583 current_target = EntryType::Action(result_actions.len() - 1);
584 }
585 _ => {}
586 };
587 }
588 LineType::ValPair => {
589 process_entry_val_pair(line, &mut entry.borrow_mut())?;
590 }
591 },
592
593 EntryType::Action(index) => match line.line_type() {
594 LineType::Header => match parse_header(line)? {
595 Header::DesktopEntry => {
596 return Err(ParseError::RepetitiveEntry {
597 msg: "There should only be one entry on top".into(),
598 row: line.line_number,
599 col: 0,
600 });
601 }
602 Header::DesktopAction { name } => {
603 result_actions.push(DesktopActionInternal {
604 ref_name: name,
605 ..Default::default()
606 });
607 current_target = EntryType::Action(result_actions.len() - 1)
608 }
609 _ => {}
610 },
611 LineType::ValPair => {
612 let target = &mut result_actions[index];
613 process_action_val_pair(line, target)?;
614 }
615 },
616 }
617 }
618
619 let mut entry = result_entry.take();
620 let actions = match entry.actions {
621 Some(ref mut d) => vec_to_map(result_actions, d)?,
622 None => HashMap::new(),
623 };
624
625 Ok(DesktopFile {
626 entry: entry.try_into()?,
627 actions,
628 })
629}
630
631#[cfg(test)]
632mod tests {
633 use super::*;
634
635 #[test]
636 fn filter_lines_test() {
637 let res = filter_lines("aaa你好 \n\n\n aaaa\n #sadas")
638 .iter()
639 .map(|l| l.to_string())
640 .collect::<Vec<_>>();
641
642 println!("{:?}", res);
643 assert_eq!(vec!["aaa你好", "aaaa"], res);
644 }
645
646 #[test]
647 fn test_clense() {
648 let content = r#"
649Name = a
650Type = Application
651 "#;
652
653 let l = filter_lines(content);
654 let parts = split_into_parts(&l[0]).unwrap();
655 assert_eq!(parts.key, "Name".to_string());
656 assert_eq!(parts.value, "a".to_string());
657 }
658}