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
366fn line_col_at_offset(node: &SyntaxNode, offset: rowan::TextSize) -> (usize, usize) {
369 let root = node.ancestors().last().unwrap_or_else(|| node.clone());
370 let mut line = 0;
371 let mut last_newline_offset = rowan::TextSize::from(0);
372
373 for element in root.preorder_with_tokens() {
374 if let rowan::WalkEvent::Enter(rowan::NodeOrToken::Token(token)) = element {
375 if token.text_range().start() >= offset {
376 break;
377 }
378
379 for (idx, _) in token.text().match_indices('\n') {
381 line += 1;
382 last_newline_offset =
383 token.text_range().start() + rowan::TextSize::from((idx + 1) as u32);
384 }
385 }
386 }
387
388 let column: usize = (offset - last_newline_offset).into();
389 (line, column)
390}
391
392#[derive(Debug, Clone, PartialEq, Eq, Hash)]
394pub struct Desktop(SyntaxNode);
395
396impl Desktop {
397 pub fn groups(&self) -> impl Iterator<Item = Group> {
399 self.0.children().filter_map(Group::cast)
400 }
401
402 pub fn get_group(&self, name: &str) -> Option<Group> {
404 self.groups().find(|g| g.name().as_deref() == Some(name))
405 }
406
407 pub fn syntax(&self) -> &SyntaxNode {
409 &self.0
410 }
411
412 pub fn text(&self) -> String {
414 self.0.text().to_string()
415 }
416
417 pub fn from_file(path: &Path) -> Result<Self, Error> {
419 let text = std::fs::read_to_string(path)?;
420 Self::from_str(&text)
421 }
422
423 pub fn line(&self) -> usize {
425 line_col_at_offset(&self.0, self.0.text_range().start()).0
426 }
427
428 pub fn column(&self) -> usize {
430 line_col_at_offset(&self.0, self.0.text_range().start()).1
431 }
432
433 pub fn line_col(&self) -> (usize, usize) {
436 line_col_at_offset(&self.0, self.0.text_range().start())
437 }
438}
439
440impl AstNode for Desktop {
441 type Language = Lang;
442
443 fn can_cast(kind: SyntaxKind) -> bool {
444 kind == SyntaxKind::ROOT
445 }
446
447 fn cast(node: SyntaxNode) -> Option<Self> {
448 if node.kind() == SyntaxKind::ROOT {
449 Some(Desktop(node))
450 } else {
451 None
452 }
453 }
454
455 fn syntax(&self) -> &SyntaxNode {
456 &self.0
457 }
458}
459
460impl FromStr for Desktop {
461 type Err = Error;
462
463 fn from_str(s: &str) -> Result<Self, Self::Err> {
464 let parsed = parse(s);
465 if !parsed.errors.is_empty() {
466 return Err(Error::ParseError(ParseError(parsed.errors)));
467 }
468 let node = SyntaxNode::new_root_mut(parsed.green_node);
469 Ok(Desktop::cast(node).expect("root node should be Desktop"))
470 }
471}
472
473impl std::fmt::Display for Desktop {
474 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
475 write!(f, "{}", self.0.text())
476 }
477}
478
479#[derive(Debug, Clone, PartialEq, Eq, Hash)]
481pub struct Group(SyntaxNode);
482
483impl Group {
484 pub fn name(&self) -> Option<String> {
486 let header = self
487 .0
488 .children()
489 .find(|n| n.kind() == SyntaxKind::GROUP_HEADER)?;
490 let value = header
491 .children_with_tokens()
492 .find(|e| e.kind() == SyntaxKind::VALUE)?;
493 Some(value.as_token()?.text().to_string())
494 }
495
496 pub fn entries(&self) -> impl Iterator<Item = Entry> {
498 self.0.children().filter_map(Entry::cast)
499 }
500
501 pub fn get(&self, key: &str) -> Option<String> {
503 self.entries()
504 .find(|e| e.key().as_deref() == Some(key) && e.locale().is_none())
505 .and_then(|e| e.value())
506 }
507
508 pub fn get_locale(&self, key: &str, locale: &str) -> Option<String> {
510 self.entries()
511 .find(|e| e.key().as_deref() == Some(key) && e.locale().as_deref() == Some(locale))
512 .and_then(|e| e.value())
513 }
514
515 pub fn get_locales(&self, key: &str) -> Vec<String> {
517 self.entries()
518 .filter(|e| e.key().as_deref() == Some(key) && e.locale().is_some())
519 .filter_map(|e| e.locale())
520 .collect()
521 }
522
523 pub fn get_all(&self, key: &str) -> Vec<(Option<String>, String)> {
525 self.entries()
526 .filter(|e| e.key().as_deref() == Some(key))
527 .filter_map(|e| {
528 let value = e.value()?;
529 Some((e.locale(), value))
530 })
531 .collect()
532 }
533
534 pub fn set(&mut self, key: &str, value: &str) {
536 let new_entry = Entry::new(key, value);
537
538 for entry in self.entries() {
540 if entry.key().as_deref() == Some(key) && entry.locale().is_none() {
541 self.0.splice_children(
542 entry.0.index()..entry.0.index() + 1,
543 vec![new_entry.0.into()],
544 );
545 return;
546 }
547 }
548
549 let insertion_index = self.0.children_with_tokens().count();
551 self.0
552 .splice_children(insertion_index..insertion_index, vec![new_entry.0.into()]);
553 }
554
555 pub fn set_locale(&mut self, key: &str, locale: &str, value: &str) {
557 let new_entry = Entry::new_localized(key, locale, value);
558
559 for entry in self.entries() {
561 if entry.key().as_deref() == Some(key) && entry.locale().as_deref() == Some(locale) {
562 self.0.splice_children(
563 entry.0.index()..entry.0.index() + 1,
564 vec![new_entry.0.into()],
565 );
566 return;
567 }
568 }
569
570 let insertion_index = self.0.children_with_tokens().count();
572 self.0
573 .splice_children(insertion_index..insertion_index, vec![new_entry.0.into()]);
574 }
575
576 pub fn remove(&mut self, key: &str) {
578 let entry_to_remove = self.0.children().find_map(|child| {
580 let entry = Entry::cast(child)?;
581 if entry.key().as_deref() == Some(key) && entry.locale().is_none() {
582 Some(entry)
583 } else {
584 None
585 }
586 });
587
588 if let Some(entry) = entry_to_remove {
589 entry.syntax().detach();
590 }
591 }
592
593 pub fn remove_locale(&mut self, key: &str, locale: &str) {
595 let entry_to_remove = self.0.children().find_map(|child| {
597 let entry = Entry::cast(child)?;
598 if entry.key().as_deref() == Some(key) && entry.locale().as_deref() == Some(locale) {
599 Some(entry)
600 } else {
601 None
602 }
603 });
604
605 if let Some(entry) = entry_to_remove {
606 entry.syntax().detach();
607 }
608 }
609
610 pub fn remove_all(&mut self, key: &str) {
612 let entries_to_remove: Vec<_> = self
614 .0
615 .children()
616 .filter_map(Entry::cast)
617 .filter(|e| e.key().as_deref() == Some(key))
618 .collect();
619
620 for entry in entries_to_remove {
621 entry.syntax().detach();
622 }
623 }
624
625 pub fn syntax(&self) -> &SyntaxNode {
627 &self.0
628 }
629
630 pub fn line(&self) -> usize {
632 line_col_at_offset(&self.0, self.0.text_range().start()).0
633 }
634
635 pub fn column(&self) -> usize {
637 line_col_at_offset(&self.0, self.0.text_range().start()).1
638 }
639
640 pub fn line_col(&self) -> (usize, usize) {
643 line_col_at_offset(&self.0, self.0.text_range().start())
644 }
645}
646
647impl AstNode for Group {
648 type Language = Lang;
649
650 fn can_cast(kind: SyntaxKind) -> bool {
651 kind == SyntaxKind::GROUP
652 }
653
654 fn cast(node: SyntaxNode) -> Option<Self> {
655 if node.kind() == SyntaxKind::GROUP {
656 Some(Group(node))
657 } else {
658 None
659 }
660 }
661
662 fn syntax(&self) -> &SyntaxNode {
663 &self.0
664 }
665}
666
667#[derive(Debug, Clone, PartialEq, Eq, Hash)]
669pub struct Entry(SyntaxNode);
670
671impl Entry {
672 pub fn new(key: &str, value: &str) -> Entry {
674 use rowan::GreenNodeBuilder;
675
676 let mut builder = GreenNodeBuilder::new();
677 builder.start_node(SyntaxKind::ENTRY.into());
678 builder.token(SyntaxKind::KEY.into(), key);
679 builder.token(SyntaxKind::EQUALS.into(), "=");
680 builder.token(SyntaxKind::VALUE.into(), value);
681 builder.token(SyntaxKind::NEWLINE.into(), "\n");
682 builder.finish_node();
683 Entry(SyntaxNode::new_root_mut(builder.finish()))
684 }
685
686 pub fn new_localized(key: &str, locale: &str, value: &str) -> Entry {
688 use rowan::GreenNodeBuilder;
689
690 let mut builder = GreenNodeBuilder::new();
691 builder.start_node(SyntaxKind::ENTRY.into());
692 builder.token(SyntaxKind::KEY.into(), key);
693 builder.token(SyntaxKind::LEFT_BRACKET.into(), "[");
694 builder.token(SyntaxKind::VALUE.into(), locale);
695 builder.token(SyntaxKind::RIGHT_BRACKET.into(), "]");
696 builder.token(SyntaxKind::EQUALS.into(), "=");
697 builder.token(SyntaxKind::VALUE.into(), value);
698 builder.token(SyntaxKind::NEWLINE.into(), "\n");
699 builder.finish_node();
700 Entry(SyntaxNode::new_root_mut(builder.finish()))
701 }
702
703 pub fn key(&self) -> Option<String> {
705 let key_token = self
706 .0
707 .children_with_tokens()
708 .find(|e| e.kind() == SyntaxKind::KEY)?;
709 Some(key_token.as_token()?.text().to_string())
710 }
711
712 pub fn value(&self) -> Option<String> {
714 let mut found_equals = false;
716 for element in self.0.children_with_tokens() {
717 match element.kind() {
718 SyntaxKind::EQUALS => found_equals = true,
719 SyntaxKind::VALUE if found_equals => {
720 return Some(element.as_token()?.text().to_string());
721 }
722 _ => {}
723 }
724 }
725 None
726 }
727
728 pub fn locale(&self) -> Option<String> {
730 let mut found_key = false;
732 let mut in_locale = false;
733 for element in self.0.children_with_tokens() {
734 match element.kind() {
735 SyntaxKind::KEY => found_key = true,
736 SyntaxKind::LEFT_BRACKET if found_key && !in_locale => in_locale = true,
737 SyntaxKind::VALUE if in_locale => {
738 return Some(element.as_token()?.text().to_string());
739 }
740 SyntaxKind::RIGHT_BRACKET if in_locale => in_locale = false,
741 SyntaxKind::EQUALS => break, _ => {}
743 }
744 }
745 None
746 }
747
748 pub fn syntax(&self) -> &SyntaxNode {
750 &self.0
751 }
752
753 pub fn line(&self) -> usize {
755 line_col_at_offset(&self.0, self.0.text_range().start()).0
756 }
757
758 pub fn column(&self) -> usize {
760 line_col_at_offset(&self.0, self.0.text_range().start()).1
761 }
762
763 pub fn line_col(&self) -> (usize, usize) {
766 line_col_at_offset(&self.0, self.0.text_range().start())
767 }
768}
769
770impl AstNode for Entry {
771 type Language = Lang;
772
773 fn can_cast(kind: SyntaxKind) -> bool {
774 kind == SyntaxKind::ENTRY
775 }
776
777 fn cast(node: SyntaxNode) -> Option<Self> {
778 if node.kind() == SyntaxKind::ENTRY {
779 Some(Entry(node))
780 } else {
781 None
782 }
783 }
784
785 fn syntax(&self) -> &SyntaxNode {
786 &self.0
787 }
788}
789
790#[cfg(test)]
791mod tests {
792 use super::*;
793
794 #[test]
795 fn test_parse_simple() {
796 let input = r#"[Desktop Entry]
797Name=Example
798Type=Application
799"#;
800 let desktop = Desktop::from_str(input).unwrap();
801 assert_eq!(desktop.groups().count(), 1);
802
803 let group = desktop.groups().nth(0).unwrap();
804 assert_eq!(group.name(), Some("Desktop Entry".to_string()));
805 assert_eq!(group.get("Name"), Some("Example".to_string()));
806 assert_eq!(group.get("Type"), Some("Application".to_string()));
807 }
808
809 #[test]
810 fn test_parse_with_comments() {
811 let input = r#"# Top comment
812[Desktop Entry]
813# Comment before name
814Name=Example
815Type=Application
816"#;
817 let desktop = Desktop::from_str(input).unwrap();
818 assert_eq!(desktop.groups().count(), 1);
819
820 let group = desktop.groups().nth(0).unwrap();
821 assert_eq!(group.get("Name"), Some("Example".to_string()));
822 }
823
824 #[test]
825 fn test_parse_multiple_groups() {
826 let input = r#"[Desktop Entry]
827Name=Example
828
829[Desktop Action Play]
830Name=Play
831Exec=example --play
832"#;
833 let desktop = Desktop::from_str(input).unwrap();
834 assert_eq!(desktop.groups().count(), 2);
835
836 let group1 = desktop.groups().nth(0).unwrap();
837 assert_eq!(group1.name(), Some("Desktop Entry".to_string()));
838
839 let group2 = desktop.groups().nth(1).unwrap();
840 assert_eq!(group2.name(), Some("Desktop Action Play".to_string()));
841 assert_eq!(group2.get("Name"), Some("Play".to_string()));
842 }
843
844 #[test]
845 fn test_parse_with_spaces() {
846 let input = "[Desktop Entry]\nName = Example Application\n";
847 let desktop = Desktop::from_str(input).unwrap();
848
849 let group = desktop.groups().nth(0).unwrap();
850 assert_eq!(group.get("Name"), Some("Example Application".to_string()));
851 }
852
853 #[test]
854 fn test_entry_locale() {
855 let input = "[Desktop Entry]\nName[de]=Beispiel\n";
856 let desktop = Desktop::from_str(input).unwrap();
857
858 let group = desktop.groups().nth(0).unwrap();
859 let entry = group.entries().nth(0).unwrap();
860 assert_eq!(entry.key(), Some("Name".to_string()));
861 assert_eq!(entry.locale(), Some("de".to_string()));
862 assert_eq!(entry.value(), Some("Beispiel".to_string()));
863 }
864
865 #[test]
866 fn test_lossless_roundtrip() {
867 let input = r#"# Comment
868[Desktop Entry]
869Name=Example
870Type=Application
871
872[Another Section]
873Key=Value
874"#;
875 let desktop = Desktop::from_str(input).unwrap();
876 let output = desktop.text();
877 assert_eq!(input, output);
878 }
879
880 #[test]
881 fn test_localized_query() {
882 let input = r#"[Desktop Entry]
883Name=Example Application
884Name[de]=Beispielanwendung
885Name[fr]=Application exemple
886Type=Application
887"#;
888 let desktop = Desktop::from_str(input).unwrap();
889 let group = desktop.groups().nth(0).unwrap();
890
891 assert_eq!(group.get("Name"), Some("Example Application".to_string()));
893
894 assert_eq!(
896 group.get_locale("Name", "de"),
897 Some("Beispielanwendung".to_string())
898 );
899 assert_eq!(
900 group.get_locale("Name", "fr"),
901 Some("Application exemple".to_string())
902 );
903 assert_eq!(group.get_locale("Name", "es"), None);
904
905 let locales = group.get_locales("Name");
907 assert_eq!(locales.len(), 2);
908 assert!(locales.contains(&"de".to_string()));
909 assert!(locales.contains(&"fr".to_string()));
910
911 let all = group.get_all("Name");
913 assert_eq!(all.len(), 3);
914 assert!(all.contains(&(None, "Example Application".to_string())));
915 assert!(all.contains(&(Some("de".to_string()), "Beispielanwendung".to_string())));
916 assert!(all.contains(&(Some("fr".to_string()), "Application exemple".to_string())));
917 }
918
919 #[test]
920 fn test_localized_set() {
921 let input = r#"[Desktop Entry]
922Name=Example
923Name[de]=Beispiel
924Type=Application
925"#;
926 let desktop = Desktop::from_str(input).unwrap();
927 {
928 let mut group = desktop.groups().nth(0).unwrap();
929 group.set_locale("Name", "de", "Neue Beispiel");
931 }
932
933 let group = desktop.groups().nth(0).unwrap();
935 assert_eq!(
936 group.get_locale("Name", "de"),
937 Some("Neue Beispiel".to_string())
938 );
939
940 assert_eq!(group.get("Name"), Some("Example".to_string()));
942 }
943
944 #[test]
945 fn test_localized_remove() {
946 let input = r#"[Desktop Entry]
947Name=Example
948Name[de]=Beispiel
949Name[fr]=Exemple
950Type=Application
951"#;
952 let desktop = Desktop::from_str(input).unwrap();
953 let mut group = desktop.groups().nth(0).unwrap();
954
955 group.remove_locale("Name", "de");
957 assert_eq!(group.get_locale("Name", "de"), None);
958 assert_eq!(group.get_locale("Name", "fr"), Some("Exemple".to_string()));
959 assert_eq!(group.get("Name"), Some("Example".to_string()));
960
961 group.remove("Name");
963 assert_eq!(group.get("Name"), None);
964 assert_eq!(group.get_locale("Name", "fr"), Some("Exemple".to_string()));
965 }
966
967 #[test]
968 fn test_localized_remove_all() {
969 let input = r#"[Desktop Entry]
970Name=Example
971Name[de]=Beispiel
972Name[fr]=Exemple
973Type=Application
974"#;
975 let desktop = Desktop::from_str(input).unwrap();
976 let mut group = desktop.groups().nth(0).unwrap();
977
978 group.remove_all("Name");
980 assert_eq!(group.get("Name"), None);
981 assert_eq!(group.get_locale("Name", "de"), None);
982 assert_eq!(group.get_locale("Name", "fr"), None);
983 assert_eq!(group.get_locales("Name").len(), 0);
984
985 assert_eq!(group.get("Type"), Some("Application".to_string()));
987 }
988
989 #[test]
990 fn test_get_distinguishes_localized() {
991 let input = r#"[Desktop Entry]
992Name[de]=Beispiel
993Type=Application
994"#;
995 let desktop = Desktop::from_str(input).unwrap();
996 let group = desktop.groups().nth(0).unwrap();
997
998 assert_eq!(group.get("Name"), None);
1000 assert_eq!(group.get_locale("Name", "de"), Some("Beispiel".to_string()));
1001 }
1002
1003 #[test]
1004 fn test_add_new_entry() {
1005 let input = r#"[Desktop Entry]
1006Name=Example
1007"#;
1008 let desktop = Desktop::from_str(input).unwrap();
1009 {
1010 let mut group = desktop.groups().nth(0).unwrap();
1011 group.set("Type", "Application");
1013 }
1014
1015 let group = desktop.groups().nth(0).unwrap();
1016 assert_eq!(group.get("Name"), Some("Example".to_string()));
1017 assert_eq!(group.get("Type"), Some("Application".to_string()));
1018 }
1019
1020 #[test]
1021 fn test_add_new_localized_entry() {
1022 let input = r#"[Desktop Entry]
1023Name=Example
1024"#;
1025 let desktop = Desktop::from_str(input).unwrap();
1026 {
1027 let mut group = desktop.groups().nth(0).unwrap();
1028 group.set_locale("Name", "de", "Beispiel");
1030 group.set_locale("Name", "fr", "Exemple");
1031 }
1032
1033 let group = desktop.groups().nth(0).unwrap();
1034 assert_eq!(group.get("Name"), Some("Example".to_string()));
1035 assert_eq!(group.get_locale("Name", "de"), Some("Beispiel".to_string()));
1036 assert_eq!(group.get_locale("Name", "fr"), Some("Exemple".to_string()));
1037 assert_eq!(group.get_locales("Name").len(), 2);
1038 }
1039
1040 #[test]
1041 fn test_line_col() {
1042 let text = r#"[Desktop Entry]
1043Name=Example Application
1044Type=Application
1045Exec=example
1046
1047[Desktop Action Play]
1048Name=Play
1049Exec=example --play
1050"#;
1051 let desktop = Desktop::from_str(text).unwrap();
1052
1053 assert_eq!(desktop.line(), 0);
1055 assert_eq!(desktop.column(), 0);
1056
1057 let groups: Vec<_> = desktop.groups().collect();
1059 assert_eq!(groups.len(), 2);
1060
1061 assert_eq!(groups[0].line(), 0);
1063 assert_eq!(groups[0].column(), 0);
1064
1065 assert_eq!(groups[1].line(), 5);
1067 assert_eq!(groups[1].column(), 0);
1068
1069 let entries: Vec<_> = groups[0].entries().collect();
1071 assert_eq!(entries[0].line(), 1); assert_eq!(entries[1].line(), 2); assert_eq!(entries[2].line(), 3); assert_eq!(entries[0].column(), 0); assert_eq!(entries[1].column(), 0); assert_eq!(groups[1].line_col(), (5, 0));
1081 assert_eq!(entries[0].line_col(), (1, 0));
1082
1083 let second_group_entries: Vec<_> = groups[1].entries().collect();
1085 assert_eq!(second_group_entries[0].line(), 6); assert_eq!(second_group_entries[1].line(), 7); }
1088}