1use crate::lex::{lex, SyntaxKind};
29use rowan::ast::AstNode;
30use rowan::{GreenNode, GreenNodeBuilder};
31use std::path::Path;
32use std::str::FromStr;
33
34#[derive(Debug, Clone, PartialEq, Eq, Hash)]
36pub struct PositionedParseError {
37 pub message: String,
39 pub range: rowan::TextRange,
41 pub code: Option<String>,
43}
44
45impl std::fmt::Display for PositionedParseError {
46 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
47 write!(f, "{}", self.message)
48 }
49}
50
51impl std::error::Error for PositionedParseError {}
52
53#[derive(Debug, Clone, PartialEq, Eq, Hash)]
55pub struct ParseError(pub Vec<String>);
56
57impl std::fmt::Display for ParseError {
58 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
59 for err in &self.0 {
60 writeln!(f, "{}", err)?;
61 }
62 Ok(())
63 }
64}
65
66impl std::error::Error for ParseError {}
67
68#[derive(Debug)]
70pub enum Error {
71 ParseError(ParseError),
73
74 IoError(std::io::Error),
76}
77
78impl std::fmt::Display for Error {
79 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
80 match &self {
81 Error::ParseError(err) => write!(f, "{}", err),
82 Error::IoError(err) => write!(f, "{}", err),
83 }
84 }
85}
86
87impl From<ParseError> for Error {
88 fn from(err: ParseError) -> Self {
89 Self::ParseError(err)
90 }
91}
92
93impl From<std::io::Error> for Error {
94 fn from(err: std::io::Error) -> Self {
95 Self::IoError(err)
96 }
97}
98
99impl std::error::Error for Error {}
100
101#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
103pub enum Lang {}
104
105impl rowan::Language for Lang {
106 type Kind = SyntaxKind;
107
108 fn kind_from_raw(raw: rowan::SyntaxKind) -> Self::Kind {
109 unsafe { std::mem::transmute::<u16, SyntaxKind>(raw.0) }
110 }
111
112 fn kind_to_raw(kind: Self::Kind) -> rowan::SyntaxKind {
113 kind.into()
114 }
115}
116
117pub(crate) struct Parse {
119 pub(crate) green_node: GreenNode,
120 pub(crate) errors: Vec<String>,
121 pub(crate) positioned_errors: Vec<PositionedParseError>,
122}
123
124pub(crate) fn parse(text: &str) -> Parse {
126 struct Parser<'a> {
127 tokens: Vec<(SyntaxKind, &'a str)>,
128 builder: GreenNodeBuilder<'static>,
129 errors: Vec<String>,
130 positioned_errors: Vec<PositionedParseError>,
131 pos: usize,
132 }
133
134 impl<'a> Parser<'a> {
135 fn current(&self) -> Option<SyntaxKind> {
136 if self.pos < self.tokens.len() {
137 Some(self.tokens[self.tokens.len() - 1 - self.pos].0)
138 } else {
139 None
140 }
141 }
142
143 fn bump(&mut self) {
144 if self.pos < self.tokens.len() {
145 let (kind, text) = self.tokens[self.tokens.len() - 1 - self.pos];
146 self.builder.token(kind.into(), text);
147 self.pos += 1;
148 }
149 }
150
151 fn skip_ws(&mut self) {
152 while self.current() == Some(SyntaxKind::WHITESPACE) {
153 self.bump();
154 }
155 }
156
157 fn skip_blank_lines(&mut self) {
158 while let Some(kind) = self.current() {
159 match kind {
160 SyntaxKind::NEWLINE => {
161 self.builder.start_node(SyntaxKind::BLANK_LINE.into());
162 self.bump();
163 self.builder.finish_node();
164 }
165 SyntaxKind::WHITESPACE => {
166 if self.pos + 1 < self.tokens.len()
168 && self.tokens[self.tokens.len() - 2 - self.pos].0
169 == SyntaxKind::NEWLINE
170 {
171 self.builder.start_node(SyntaxKind::BLANK_LINE.into());
172 self.bump(); self.bump(); self.builder.finish_node();
175 } else {
176 break;
177 }
178 }
179 _ => break,
180 }
181 }
182 }
183
184 fn parse_group_header(&mut self) {
185 self.builder.start_node(SyntaxKind::GROUP_HEADER.into());
186
187 if self.current() == Some(SyntaxKind::LEFT_BRACKET) {
189 self.bump();
190 } else {
191 self.errors
192 .push("expected '[' at start of group header".to_string());
193 }
194
195 if self.current() == Some(SyntaxKind::VALUE) {
197 self.bump();
198 } else {
199 self.errors
200 .push("expected section name in group header".to_string());
201 }
202
203 if self.current() == Some(SyntaxKind::RIGHT_BRACKET) {
205 self.bump();
206 } else {
207 self.errors
208 .push("expected ']' at end of group header".to_string());
209 }
210
211 if self.current() == Some(SyntaxKind::NEWLINE) {
213 self.bump();
214 }
215
216 self.builder.finish_node();
217 }
218
219 fn parse_entry(&mut self) {
220 self.builder.start_node(SyntaxKind::ENTRY.into());
221
222 if self.current() == Some(SyntaxKind::COMMENT) {
224 self.bump();
225 if self.current() == Some(SyntaxKind::NEWLINE) {
226 self.bump();
227 }
228 self.builder.finish_node();
229 return;
230 }
231
232 if self.current() == Some(SyntaxKind::KEY) {
234 self.bump();
235 } else {
236 self.errors
237 .push(format!("expected key, got {:?}", self.current()));
238 }
239
240 self.skip_ws();
241
242 if self.current() == Some(SyntaxKind::LEFT_BRACKET) {
247 self.bump();
248 self.skip_ws();
251 if self.current() == Some(SyntaxKind::VALUE) {
252 self.bump();
253 }
254 if self.current() == Some(SyntaxKind::RIGHT_BRACKET) {
255 self.bump();
256 }
257 self.skip_ws();
258 }
259
260 if self.current() == Some(SyntaxKind::EQUALS) {
262 self.bump();
263 } else {
264 self.errors.push("expected '=' after key".to_string());
265 }
266
267 self.skip_ws();
268
269 if self.current() == Some(SyntaxKind::VALUE) {
271 self.bump();
272 }
273
274 if self.current() == Some(SyntaxKind::NEWLINE) {
276 self.bump();
277 }
278
279 self.builder.finish_node();
280 }
281
282 fn parse_group(&mut self) {
283 self.builder.start_node(SyntaxKind::GROUP.into());
284
285 self.parse_group_header();
287
288 while let Some(kind) = self.current() {
290 match kind {
291 SyntaxKind::LEFT_BRACKET => break, SyntaxKind::KEY | SyntaxKind::COMMENT => self.parse_entry(),
293 SyntaxKind::NEWLINE | SyntaxKind::WHITESPACE => {
294 self.skip_blank_lines();
295 }
296 _ => {
297 self.errors
298 .push(format!("unexpected token in group: {:?}", kind));
299 self.bump();
300 }
301 }
302 }
303
304 self.builder.finish_node();
305 }
306
307 fn parse_file(&mut self) {
308 self.builder.start_node(SyntaxKind::ROOT.into());
309
310 while let Some(kind) = self.current() {
312 match kind {
313 SyntaxKind::COMMENT => {
314 self.builder.start_node(SyntaxKind::ENTRY.into());
315 self.bump();
316 if self.current() == Some(SyntaxKind::NEWLINE) {
317 self.bump();
318 }
319 self.builder.finish_node();
320 }
321 SyntaxKind::NEWLINE | SyntaxKind::WHITESPACE => {
322 self.skip_blank_lines();
323 }
324 _ => break,
325 }
326 }
327
328 while self.current().is_some() {
330 if self.current() == Some(SyntaxKind::LEFT_BRACKET) {
331 self.parse_group();
332 } else {
333 self.errors
334 .push(format!("expected group header, got {:?}", self.current()));
335 self.bump();
336 }
337 }
338
339 self.builder.finish_node();
340 }
341 }
342
343 let mut tokens: Vec<_> = lex(text).collect();
344 tokens.reverse();
345
346 let mut parser = Parser {
347 tokens,
348 builder: GreenNodeBuilder::new(),
349 errors: Vec::new(),
350 positioned_errors: Vec::new(),
351 pos: 0,
352 };
353
354 parser.parse_file();
355
356 Parse {
357 green_node: parser.builder.finish(),
358 errors: parser.errors,
359 positioned_errors: parser.positioned_errors,
360 }
361}
362
363type SyntaxNode = rowan::SyntaxNode<Lang>;
365
366#[derive(Debug, Clone, PartialEq, Eq, Hash)]
368pub struct Desktop(SyntaxNode);
369
370impl Desktop {
371 pub fn groups(&self) -> impl Iterator<Item = Group> {
373 self.0.children().filter_map(Group::cast)
374 }
375
376 pub fn get_group(&self, name: &str) -> Option<Group> {
378 self.groups().find(|g| g.name().as_deref() == Some(name))
379 }
380
381 pub fn syntax(&self) -> &SyntaxNode {
383 &self.0
384 }
385
386 pub fn text(&self) -> String {
388 self.0.text().to_string()
389 }
390
391 pub fn from_file(path: &Path) -> Result<Self, Error> {
393 let text = std::fs::read_to_string(path)?;
394 Self::from_str(&text)
395 }
396}
397
398impl AstNode for Desktop {
399 type Language = Lang;
400
401 fn can_cast(kind: SyntaxKind) -> bool {
402 kind == SyntaxKind::ROOT
403 }
404
405 fn cast(node: SyntaxNode) -> Option<Self> {
406 if node.kind() == SyntaxKind::ROOT {
407 Some(Desktop(node))
408 } else {
409 None
410 }
411 }
412
413 fn syntax(&self) -> &SyntaxNode {
414 &self.0
415 }
416}
417
418impl FromStr for Desktop {
419 type Err = Error;
420
421 fn from_str(s: &str) -> Result<Self, Self::Err> {
422 let parsed = parse(s);
423 if !parsed.errors.is_empty() {
424 return Err(Error::ParseError(ParseError(parsed.errors)));
425 }
426 let node = SyntaxNode::new_root_mut(parsed.green_node);
427 Ok(Desktop::cast(node).expect("root node should be Desktop"))
428 }
429}
430
431impl std::fmt::Display for Desktop {
432 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
433 write!(f, "{}", self.0.text())
434 }
435}
436
437#[derive(Debug, Clone, PartialEq, Eq, Hash)]
439pub struct Group(SyntaxNode);
440
441impl Group {
442 pub fn name(&self) -> Option<String> {
444 let header = self
445 .0
446 .children()
447 .find(|n| n.kind() == SyntaxKind::GROUP_HEADER)?;
448 let value = header
449 .children_with_tokens()
450 .find(|e| e.kind() == SyntaxKind::VALUE)?;
451 Some(value.as_token()?.text().to_string())
452 }
453
454 pub fn entries(&self) -> impl Iterator<Item = Entry> {
456 self.0.children().filter_map(Entry::cast)
457 }
458
459 pub fn get(&self, key: &str) -> Option<String> {
461 self.entries()
462 .find(|e| e.key().as_deref() == Some(key) && e.locale().is_none())
463 .and_then(|e| e.value())
464 }
465
466 pub fn get_locale(&self, key: &str, locale: &str) -> Option<String> {
468 self.entries()
469 .find(|e| e.key().as_deref() == Some(key) && e.locale().as_deref() == Some(locale))
470 .and_then(|e| e.value())
471 }
472
473 pub fn get_locales(&self, key: &str) -> Vec<String> {
475 self.entries()
476 .filter(|e| e.key().as_deref() == Some(key) && e.locale().is_some())
477 .filter_map(|e| e.locale())
478 .collect()
479 }
480
481 pub fn get_all(&self, key: &str) -> Vec<(Option<String>, String)> {
483 self.entries()
484 .filter(|e| e.key().as_deref() == Some(key))
485 .filter_map(|e| {
486 let value = e.value()?;
487 Some((e.locale(), value))
488 })
489 .collect()
490 }
491
492 pub fn set(&mut self, key: &str, value: &str) {
494 let new_entry = Entry::new(key, value);
495
496 for entry in self.entries() {
498 if entry.key().as_deref() == Some(key) && entry.locale().is_none() {
499 self.0.splice_children(
500 entry.0.index()..entry.0.index() + 1,
501 vec![new_entry.0.into()],
502 );
503 return;
504 }
505 }
506
507 let insertion_index = self.0.children_with_tokens().count();
509 self.0
510 .splice_children(insertion_index..insertion_index, vec![new_entry.0.into()]);
511 }
512
513 pub fn set_locale(&mut self, key: &str, locale: &str, value: &str) {
515 let new_entry = Entry::new_localized(key, locale, value);
516
517 for entry in self.entries() {
519 if entry.key().as_deref() == Some(key) && entry.locale().as_deref() == Some(locale) {
520 self.0.splice_children(
521 entry.0.index()..entry.0.index() + 1,
522 vec![new_entry.0.into()],
523 );
524 return;
525 }
526 }
527
528 let insertion_index = self.0.children_with_tokens().count();
530 self.0
531 .splice_children(insertion_index..insertion_index, vec![new_entry.0.into()]);
532 }
533
534 pub fn remove(&mut self, key: &str) {
536 let entry_to_remove = self.0.children().find_map(|child| {
538 let entry = Entry::cast(child)?;
539 if entry.key().as_deref() == Some(key) && entry.locale().is_none() {
540 Some(entry)
541 } else {
542 None
543 }
544 });
545
546 if let Some(entry) = entry_to_remove {
547 entry.syntax().detach();
548 }
549 }
550
551 pub fn remove_locale(&mut self, key: &str, locale: &str) {
553 let entry_to_remove = self.0.children().find_map(|child| {
555 let entry = Entry::cast(child)?;
556 if entry.key().as_deref() == Some(key) && entry.locale().as_deref() == Some(locale) {
557 Some(entry)
558 } else {
559 None
560 }
561 });
562
563 if let Some(entry) = entry_to_remove {
564 entry.syntax().detach();
565 }
566 }
567
568 pub fn remove_all(&mut self, key: &str) {
570 let entries_to_remove: Vec<_> = self
572 .0
573 .children()
574 .filter_map(Entry::cast)
575 .filter(|e| e.key().as_deref() == Some(key))
576 .collect();
577
578 for entry in entries_to_remove {
579 entry.syntax().detach();
580 }
581 }
582
583 pub fn syntax(&self) -> &SyntaxNode {
585 &self.0
586 }
587}
588
589impl AstNode for Group {
590 type Language = Lang;
591
592 fn can_cast(kind: SyntaxKind) -> bool {
593 kind == SyntaxKind::GROUP
594 }
595
596 fn cast(node: SyntaxNode) -> Option<Self> {
597 if node.kind() == SyntaxKind::GROUP {
598 Some(Group(node))
599 } else {
600 None
601 }
602 }
603
604 fn syntax(&self) -> &SyntaxNode {
605 &self.0
606 }
607}
608
609#[derive(Debug, Clone, PartialEq, Eq, Hash)]
611pub struct Entry(SyntaxNode);
612
613impl Entry {
614 pub fn new(key: &str, value: &str) -> Entry {
616 use rowan::GreenNodeBuilder;
617
618 let mut builder = GreenNodeBuilder::new();
619 builder.start_node(SyntaxKind::ENTRY.into());
620 builder.token(SyntaxKind::KEY.into(), key);
621 builder.token(SyntaxKind::EQUALS.into(), "=");
622 builder.token(SyntaxKind::VALUE.into(), value);
623 builder.token(SyntaxKind::NEWLINE.into(), "\n");
624 builder.finish_node();
625 Entry(SyntaxNode::new_root_mut(builder.finish()))
626 }
627
628 pub fn new_localized(key: &str, locale: &str, value: &str) -> Entry {
630 use rowan::GreenNodeBuilder;
631
632 let mut builder = GreenNodeBuilder::new();
633 builder.start_node(SyntaxKind::ENTRY.into());
634 builder.token(SyntaxKind::KEY.into(), key);
635 builder.token(SyntaxKind::LEFT_BRACKET.into(), "[");
636 builder.token(SyntaxKind::VALUE.into(), locale);
637 builder.token(SyntaxKind::RIGHT_BRACKET.into(), "]");
638 builder.token(SyntaxKind::EQUALS.into(), "=");
639 builder.token(SyntaxKind::VALUE.into(), value);
640 builder.token(SyntaxKind::NEWLINE.into(), "\n");
641 builder.finish_node();
642 Entry(SyntaxNode::new_root_mut(builder.finish()))
643 }
644
645 pub fn key(&self) -> Option<String> {
647 let key_token = self
648 .0
649 .children_with_tokens()
650 .find(|e| e.kind() == SyntaxKind::KEY)?;
651 Some(key_token.as_token()?.text().to_string())
652 }
653
654 pub fn value(&self) -> Option<String> {
656 let mut found_equals = false;
658 for element in self.0.children_with_tokens() {
659 match element.kind() {
660 SyntaxKind::EQUALS => found_equals = true,
661 SyntaxKind::VALUE if found_equals => {
662 return Some(element.as_token()?.text().to_string());
663 }
664 _ => {}
665 }
666 }
667 None
668 }
669
670 pub fn locale(&self) -> Option<String> {
672 let mut found_key = false;
674 let mut in_locale = false;
675 for element in self.0.children_with_tokens() {
676 match element.kind() {
677 SyntaxKind::KEY => found_key = true,
678 SyntaxKind::LEFT_BRACKET if found_key && !in_locale => in_locale = true,
679 SyntaxKind::VALUE if in_locale => {
680 return Some(element.as_token()?.text().to_string());
681 }
682 SyntaxKind::RIGHT_BRACKET if in_locale => in_locale = false,
683 SyntaxKind::EQUALS => break, _ => {}
685 }
686 }
687 None
688 }
689
690 pub fn syntax(&self) -> &SyntaxNode {
692 &self.0
693 }
694}
695
696impl AstNode for Entry {
697 type Language = Lang;
698
699 fn can_cast(kind: SyntaxKind) -> bool {
700 kind == SyntaxKind::ENTRY
701 }
702
703 fn cast(node: SyntaxNode) -> Option<Self> {
704 if node.kind() == SyntaxKind::ENTRY {
705 Some(Entry(node))
706 } else {
707 None
708 }
709 }
710
711 fn syntax(&self) -> &SyntaxNode {
712 &self.0
713 }
714}
715
716#[cfg(test)]
717mod tests {
718 use super::*;
719
720 #[test]
721 fn test_parse_simple() {
722 let input = r#"[Desktop Entry]
723Name=Example
724Type=Application
725"#;
726 let desktop = Desktop::from_str(input).unwrap();
727 assert_eq!(desktop.groups().count(), 1);
728
729 let group = desktop.groups().nth(0).unwrap();
730 assert_eq!(group.name(), Some("Desktop Entry".to_string()));
731 assert_eq!(group.get("Name"), Some("Example".to_string()));
732 assert_eq!(group.get("Type"), Some("Application".to_string()));
733 }
734
735 #[test]
736 fn test_parse_with_comments() {
737 let input = r#"# Top comment
738[Desktop Entry]
739# Comment before name
740Name=Example
741Type=Application
742"#;
743 let desktop = Desktop::from_str(input).unwrap();
744 assert_eq!(desktop.groups().count(), 1);
745
746 let group = desktop.groups().nth(0).unwrap();
747 assert_eq!(group.get("Name"), Some("Example".to_string()));
748 }
749
750 #[test]
751 fn test_parse_multiple_groups() {
752 let input = r#"[Desktop Entry]
753Name=Example
754
755[Desktop Action Play]
756Name=Play
757Exec=example --play
758"#;
759 let desktop = Desktop::from_str(input).unwrap();
760 assert_eq!(desktop.groups().count(), 2);
761
762 let group1 = desktop.groups().nth(0).unwrap();
763 assert_eq!(group1.name(), Some("Desktop Entry".to_string()));
764
765 let group2 = desktop.groups().nth(1).unwrap();
766 assert_eq!(group2.name(), Some("Desktop Action Play".to_string()));
767 assert_eq!(group2.get("Name"), Some("Play".to_string()));
768 }
769
770 #[test]
771 fn test_parse_with_spaces() {
772 let input = "[Desktop Entry]\nName = Example Application\n";
773 let desktop = Desktop::from_str(input).unwrap();
774
775 let group = desktop.groups().nth(0).unwrap();
776 assert_eq!(group.get("Name"), Some("Example Application".to_string()));
777 }
778
779 #[test]
780 fn test_entry_locale() {
781 let input = "[Desktop Entry]\nName[de]=Beispiel\n";
782 let desktop = Desktop::from_str(input).unwrap();
783
784 let group = desktop.groups().nth(0).unwrap();
785 let entry = group.entries().nth(0).unwrap();
786 assert_eq!(entry.key(), Some("Name".to_string()));
787 assert_eq!(entry.locale(), Some("de".to_string()));
788 assert_eq!(entry.value(), Some("Beispiel".to_string()));
789 }
790
791 #[test]
792 fn test_lossless_roundtrip() {
793 let input = r#"# Comment
794[Desktop Entry]
795Name=Example
796Type=Application
797
798[Another Section]
799Key=Value
800"#;
801 let desktop = Desktop::from_str(input).unwrap();
802 let output = desktop.text();
803 assert_eq!(input, output);
804 }
805
806 #[test]
807 fn test_localized_query() {
808 let input = r#"[Desktop Entry]
809Name=Example Application
810Name[de]=Beispielanwendung
811Name[fr]=Application exemple
812Type=Application
813"#;
814 let desktop = Desktop::from_str(input).unwrap();
815 let group = desktop.groups().nth(0).unwrap();
816
817 assert_eq!(group.get("Name"), Some("Example Application".to_string()));
819
820 assert_eq!(
822 group.get_locale("Name", "de"),
823 Some("Beispielanwendung".to_string())
824 );
825 assert_eq!(
826 group.get_locale("Name", "fr"),
827 Some("Application exemple".to_string())
828 );
829 assert_eq!(group.get_locale("Name", "es"), None);
830
831 let locales = group.get_locales("Name");
833 assert_eq!(locales.len(), 2);
834 assert!(locales.contains(&"de".to_string()));
835 assert!(locales.contains(&"fr".to_string()));
836
837 let all = group.get_all("Name");
839 assert_eq!(all.len(), 3);
840 assert!(all.contains(&(None, "Example Application".to_string())));
841 assert!(all.contains(&(Some("de".to_string()), "Beispielanwendung".to_string())));
842 assert!(all.contains(&(Some("fr".to_string()), "Application exemple".to_string())));
843 }
844
845 #[test]
846 fn test_localized_set() {
847 let input = r#"[Desktop Entry]
848Name=Example
849Name[de]=Beispiel
850Type=Application
851"#;
852 let desktop = Desktop::from_str(input).unwrap();
853 {
854 let mut group = desktop.groups().nth(0).unwrap();
855 group.set_locale("Name", "de", "Neue Beispiel");
857 }
858
859 let group = desktop.groups().nth(0).unwrap();
861 assert_eq!(
862 group.get_locale("Name", "de"),
863 Some("Neue Beispiel".to_string())
864 );
865
866 assert_eq!(group.get("Name"), Some("Example".to_string()));
868 }
869
870 #[test]
871 fn test_localized_remove() {
872 let input = r#"[Desktop Entry]
873Name=Example
874Name[de]=Beispiel
875Name[fr]=Exemple
876Type=Application
877"#;
878 let desktop = Desktop::from_str(input).unwrap();
879 let mut group = desktop.groups().nth(0).unwrap();
880
881 group.remove_locale("Name", "de");
883 assert_eq!(group.get_locale("Name", "de"), None);
884 assert_eq!(group.get_locale("Name", "fr"), Some("Exemple".to_string()));
885 assert_eq!(group.get("Name"), Some("Example".to_string()));
886
887 group.remove("Name");
889 assert_eq!(group.get("Name"), None);
890 assert_eq!(group.get_locale("Name", "fr"), Some("Exemple".to_string()));
891 }
892
893 #[test]
894 fn test_localized_remove_all() {
895 let input = r#"[Desktop Entry]
896Name=Example
897Name[de]=Beispiel
898Name[fr]=Exemple
899Type=Application
900"#;
901 let desktop = Desktop::from_str(input).unwrap();
902 let mut group = desktop.groups().nth(0).unwrap();
903
904 group.remove_all("Name");
906 assert_eq!(group.get("Name"), None);
907 assert_eq!(group.get_locale("Name", "de"), None);
908 assert_eq!(group.get_locale("Name", "fr"), None);
909 assert_eq!(group.get_locales("Name").len(), 0);
910
911 assert_eq!(group.get("Type"), Some("Application".to_string()));
913 }
914
915 #[test]
916 fn test_get_distinguishes_localized() {
917 let input = r#"[Desktop Entry]
918Name[de]=Beispiel
919Type=Application
920"#;
921 let desktop = Desktop::from_str(input).unwrap();
922 let group = desktop.groups().nth(0).unwrap();
923
924 assert_eq!(group.get("Name"), None);
926 assert_eq!(group.get_locale("Name", "de"), Some("Beispiel".to_string()));
927 }
928
929 #[test]
930 fn test_add_new_entry() {
931 let input = r#"[Desktop Entry]
932Name=Example
933"#;
934 let desktop = Desktop::from_str(input).unwrap();
935 {
936 let mut group = desktop.groups().nth(0).unwrap();
937 group.set("Type", "Application");
939 }
940
941 let group = desktop.groups().nth(0).unwrap();
942 assert_eq!(group.get("Name"), Some("Example".to_string()));
943 assert_eq!(group.get("Type"), Some("Application".to_string()));
944 }
945
946 #[test]
947 fn test_add_new_localized_entry() {
948 let input = r#"[Desktop Entry]
949Name=Example
950"#;
951 let desktop = Desktop::from_str(input).unwrap();
952 {
953 let mut group = desktop.groups().nth(0).unwrap();
954 group.set_locale("Name", "de", "Beispiel");
956 group.set_locale("Name", "fr", "Exemple");
957 }
958
959 let group = desktop.groups().nth(0).unwrap();
960 assert_eq!(group.get("Name"), Some("Example".to_string()));
961 assert_eq!(group.get_locale("Name", "de"), Some("Beispiel".to_string()));
962 assert_eq!(group.get_locale("Name", "fr"), Some("Exemple".to_string()));
963 assert_eq!(group.get_locales("Name").len(), 2);
964 }
965}