From 9ea018e115f237a879bd7909c70fb761d65c72db Mon Sep 17 00:00:00 2001 From: Rouven Himmelstein Date: Wed, 3 Apr 2024 16:25:24 +0200 Subject: [PATCH 1/8] feat(lib): Allow adding / appending duplicate entries --- src/lib.rs | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/lib.rs b/src/lib.rs index 0f8cd08..7684630 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -365,6 +365,20 @@ impl<'a> SectionSetter<'a> { self } + /// Add (append) key-value pair in this section + pub fn add(&'a mut self, key: K, value: V) -> &'a mut SectionSetter<'a> + where + K: Into, + V: Into, + { + self.ini + .entry(self.section_name.clone()) + .or_insert_with(Default::default) + .append(key, value); + + self + } + /// Delete the first entry in this section with `key` pub fn delete>(&'a mut self, key: &K) -> &'a mut SectionSetter<'a> { for prop in self.ini.section_all_mut(self.section_name.as_ref()) { From d520f117c677b4e422e244963d40b2df3700a73d Mon Sep 17 00:00:00 2001 From: Rouven Himmelstein Date: Wed, 3 Apr 2024 16:34:39 +0200 Subject: [PATCH 2/8] feat(tests): Add tests for duplicate properties --- src/lib.rs | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/src/lib.rs b/src/lib.rs index 7684630..4eb9476 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -2344,6 +2344,25 @@ bar = f assert_eq!(None, iter.next()); } + #[test] + fn duplicate_properties() { + // Test duplicate properties in a section + let mut ini = Ini::new(); + ini.with_section(Some("foo")) + .add("a", "1") + .add("a", "2"); + + let sec = ini.section(Some("foo")).unwrap(); + assert_eq!(sec.get("a"), Some("1")); + assert_eq!(sec.get_all("a").collect::>(), vec!["1", "2"]); + + // Test string representation + let mut buf = Vec::new(); + ini.write_to(&mut buf).unwrap(); + let ini_str = String::from_utf8(buf).unwrap(); + assert_eq!(ini_str, "[foo]\na=1\na=2\n"); + } + #[test] fn new_has_empty_general_section() { let mut ini = Ini::new(); From 2027043a89310ad7aebbcc67bf79a5f3af703918 Mon Sep 17 00:00:00 2001 From: Rouven Himmelstein Date: Thu, 4 Apr 2024 21:51:27 +0200 Subject: [PATCH 3/8] feat(tests): Add more unit tests --- src/lib.rs | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/lib.rs b/src/lib.rs index 4eb9476..883c691 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -2356,7 +2356,21 @@ bar = f assert_eq!(sec.get("a"), Some("1")); assert_eq!(sec.get_all("a").collect::>(), vec!["1", "2"]); + // Test add with unique keys + let mut ini = Ini::new(); + ini.with_section(Some("foo")) + .add("a", "1") + .add("b", "2"); + + let sec = ini.section(Some("foo")).unwrap(); + assert_eq!(sec.get("a"), Some("1")); + assert_eq!(sec.get("b"), Some("2")); + // Test string representation + let mut ini = Ini::new(); + ini.with_section(Some("foo")) + .add("a", "1") + .add("a", "2"); let mut buf = Vec::new(); ini.write_to(&mut buf).unwrap(); let ini_str = String::from_utf8(buf).unwrap(); From c1de98a156ea40642756306559d2a8438c8f85ca Mon Sep 17 00:00:00 2001 From: Rouven Himmelstein Date: Thu, 4 Apr 2024 21:52:17 +0200 Subject: [PATCH 4/8] chore(tests): Rename test --- src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib.rs b/src/lib.rs index 883c691..0f017ba 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -2345,7 +2345,7 @@ bar = f } #[test] - fn duplicate_properties() { + fn add_properties_api() { // Test duplicate properties in a section let mut ini = Ini::new(); ini.with_section(Some("foo")) From a3984643debf72b01963f545f9689e32a0864cbc Mon Sep 17 00:00:00 2001 From: Rouven Himmelstein Date: Sun, 7 Apr 2024 10:26:54 +0200 Subject: [PATCH 5/8] fix: new feature tests on windows --- src/lib.rs | 100 ++++++++++++++++++++++++++++------------------------- 1 file changed, 52 insertions(+), 48 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 0f017ba..aa2a0f3 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -353,9 +353,9 @@ impl<'a> SectionSetter<'a> { /// Set (replace) key-value pair in this section (all with the same name) pub fn set(&'a mut self, key: K, value: V) -> &'a mut SectionSetter<'a> - where - K: Into, - V: Into, + where + K: Into, + V: Into, { self.ini .entry(self.section_name.clone()) @@ -367,9 +367,9 @@ impl<'a> SectionSetter<'a> { /// Add (append) key-value pair in this section pub fn add(&'a mut self, key: K, value: V) -> &'a mut SectionSetter<'a> - where - K: Into, - V: Into, + where + K: Into, + V: Into, { self.ini .entry(self.section_name.clone()) @@ -440,18 +440,18 @@ impl Properties { /// Insert (key, value) pair by replace pub fn insert(&mut self, k: K, v: V) - where - K: Into, - V: Into, + where + K: Into, + V: Into, { self.data.insert(property_insert_key!(k.into()), v.into()); } /// Append key with (key, value) pair pub fn append(&mut self, k: K, v: V) - where - K: Into, - V: Into, + where + K: Into, + V: Into, { self.data.append(property_insert_key!(k.into()), v.into()); } @@ -462,7 +462,7 @@ impl Properties { } /// Get all values associate with the key - pub fn get_all>(&self, s: S) -> impl DoubleEndedIterator { + pub fn get_all>(&self, s: S) -> impl DoubleEndedIterator { self.data.get_all(property_get_key!(s.as_ref())).map(|v| v.as_str()) } @@ -472,7 +472,7 @@ impl Properties { } /// Remove the property with all values with the same key - pub fn remove_all>(&mut self, s: S) -> impl DoubleEndedIterator + '_ { + pub fn remove_all>(&mut self, s: S) -> impl DoubleEndedIterator + '_ { self.data.remove_all(property_get_key!(s.as_ref())) } @@ -674,8 +674,8 @@ impl Ini { /// Set with a specified section, `None` is for the general section pub fn with_section(&mut self, section: Option) -> SectionSetter - where - S: Into, + where + S: Into, { SectionSetter::new(self, section.map(Into::into)) } @@ -699,32 +699,32 @@ impl Ini { /// Get a immutable section pub fn section(&self, name: Option) -> Option<&Properties> - where - S: Into, + where + S: Into, { self.sections.get(§ion_key!(name)) } /// Get a mutable section pub fn section_mut(&mut self, name: Option) -> Option<&mut Properties> - where - S: Into, + where + S: Into, { self.sections.get_mut(§ion_key!(name)) } /// Get all sections immutable with the same key - pub fn section_all(&self, name: Option) -> impl DoubleEndedIterator - where - S: Into, + pub fn section_all(&self, name: Option) -> impl DoubleEndedIterator + where + S: Into, { self.sections.get_all(§ion_key!(name)) } /// Get all sections mutable with the same key - pub fn section_all_mut(&mut self, name: Option) -> impl DoubleEndedIterator - where - S: Into, + pub fn section_all_mut(&mut self, name: Option) -> impl DoubleEndedIterator + where + S: Into, { self.sections.get_all_mut(§ion_key!(name)) } @@ -747,14 +747,14 @@ impl Ini { } /// Iterate with sections - pub fn sections(&self) -> impl DoubleEndedIterator> { + pub fn sections(&self) -> impl DoubleEndedIterator> { self.sections.keys().map(|s| s.as_ref().map(AsRef::as_ref)) } /// Set key-value to a section pub fn set_to(&mut self, section: Option, key: String, value: String) - where - S: Into, + where + S: Into, { self.with_section(section).set(key, value); } @@ -770,8 +770,8 @@ impl Ini { /// assert_eq!(ini.get_from(Some("sec"), "abc"), Some("def")); /// ``` pub fn get_from<'a, S>(&'a self, section: Option, key: &str) -> Option<&'a str> - where - S: Into, + where + S: Into, { self.sections.get(§ion_key!(section)).and_then(|prop| prop.get(key)) } @@ -787,16 +787,16 @@ impl Ini { /// assert_eq!(ini.get_from_or(Some("sec"), "key", "default"), "default"); /// ``` pub fn get_from_or<'a, S>(&'a self, section: Option, key: &str, default: &'a str) -> &'a str - where - S: Into, + where + S: Into, { self.get_from(section, key).unwrap_or(default) } /// Get the first mutable value from the sections with key pub fn get_from_mut<'a, S>(&'a mut self, section: Option, key: &str) -> Option<&'a mut str> - where - S: Into, + where + S: Into, { self.sections .get_mut(§ion_key!(section)) @@ -805,8 +805,8 @@ impl Ini { /// Delete the first section with key, return the properties if it exists pub fn delete(&mut self, section: Option) -> Option - where - S: Into, + where + S: Into, { let key = section_key!(section); self.sections.remove(&key) @@ -814,8 +814,8 @@ impl Ini { /// Delete the key from the section, return the value if key exists or None pub fn delete_from(&mut self, section: Option, key: &str) -> Option - where - S: Into, + where + S: Into, { self.section_mut(section).and_then(|prop| prop.remove(key)) } @@ -1512,7 +1512,7 @@ impl<'a> Parser<'a> { self.bump(); self.parse_str_until(&[Some('"')], false).and_then(|s| { self.bump(); // Eats the last " - // Parse until EOL + // Parse until EOL self.parse_str_until_eol(cfg!(feature = "inline-comment")) .map(|x| s + &x) }) @@ -1521,7 +1521,7 @@ impl<'a> Parser<'a> { self.bump(); self.parse_str_until(&[Some('\'')], false).and_then(|s| { self.bump(); // Eats the last ' - // Parse until EOL + // Parse until EOL self.parse_str_until_eol(cfg!(feature = "inline-comment")) .map(|x| s + &x) }) @@ -2080,7 +2080,7 @@ Exec = \"/path/to/exe with space\" arg ..ParseOption::default() }, ) - .unwrap(); + .unwrap(); let sec = opt.section(Some("Desktop Entry")).unwrap(); assert_eq!(&sec["Exec"], "\"/path/to/exe with space\" arg"); } @@ -2213,7 +2213,7 @@ a3 = n3 ..Default::default() }, ) - .unwrap(); + .unwrap(); assert_eq!( "[Section1]\nKey1=Value\nKey2=Value\n\n[Section2]\nKey1=Value\nKey2=Value\n", @@ -2230,7 +2230,7 @@ a3 = n3 ..Default::default() }, ) - .unwrap(); + .unwrap(); assert_eq!( "[Section1]\r\nKey1=Value\r\nKey2=Value\r\n\r\n[Section2]\r\nKey1=Value\r\nKey2=Value\r\n", @@ -2247,7 +2247,7 @@ a3 = n3 ..Default::default() }, ) - .unwrap(); + .unwrap(); if cfg!(windows) { assert_eq!( @@ -2286,7 +2286,7 @@ a3 = n3 ..Default::default() }, ) - .unwrap(); + .unwrap(); // Test different line endings in Windows and Unix if cfg!(windows) { @@ -2374,7 +2374,11 @@ bar = f let mut buf = Vec::new(); ini.write_to(&mut buf).unwrap(); let ini_str = String::from_utf8(buf).unwrap(); - assert_eq!(ini_str, "[foo]\na=1\na=2\n"); + let expected = r#"[foo] +a=1 +a=2 +"#; + assert_eq!(ini_str, expected); } #[test] @@ -2628,7 +2632,7 @@ x3 = nb vec![ ("x2".to_owned(), "nc".to_owned()), ("x1".to_owned(), "na".to_owned()), - ("x3".to_owned(), "nb".to_owned()) + ("x3".to_owned(), "nb".to_owned()), ] ); } From 8ef4126993a30772db3ce796389cbb577b11e940 Mon Sep 17 00:00:00 2001 From: Rouven Himmelstein Date: Tue, 9 Apr 2024 08:01:11 +0200 Subject: [PATCH 6/8] Revert "fix: new feature tests on windows" This reverts commit a3984643debf72b01963f545f9689e32a0864cbc. --- src/lib.rs | 100 +++++++++++++++++++++++++---------------------------- 1 file changed, 48 insertions(+), 52 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index aa2a0f3..0f017ba 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -353,9 +353,9 @@ impl<'a> SectionSetter<'a> { /// Set (replace) key-value pair in this section (all with the same name) pub fn set(&'a mut self, key: K, value: V) -> &'a mut SectionSetter<'a> - where - K: Into, - V: Into, + where + K: Into, + V: Into, { self.ini .entry(self.section_name.clone()) @@ -367,9 +367,9 @@ impl<'a> SectionSetter<'a> { /// Add (append) key-value pair in this section pub fn add(&'a mut self, key: K, value: V) -> &'a mut SectionSetter<'a> - where - K: Into, - V: Into, + where + K: Into, + V: Into, { self.ini .entry(self.section_name.clone()) @@ -440,18 +440,18 @@ impl Properties { /// Insert (key, value) pair by replace pub fn insert(&mut self, k: K, v: V) - where - K: Into, - V: Into, + where + K: Into, + V: Into, { self.data.insert(property_insert_key!(k.into()), v.into()); } /// Append key with (key, value) pair pub fn append(&mut self, k: K, v: V) - where - K: Into, - V: Into, + where + K: Into, + V: Into, { self.data.append(property_insert_key!(k.into()), v.into()); } @@ -462,7 +462,7 @@ impl Properties { } /// Get all values associate with the key - pub fn get_all>(&self, s: S) -> impl DoubleEndedIterator { + pub fn get_all>(&self, s: S) -> impl DoubleEndedIterator { self.data.get_all(property_get_key!(s.as_ref())).map(|v| v.as_str()) } @@ -472,7 +472,7 @@ impl Properties { } /// Remove the property with all values with the same key - pub fn remove_all>(&mut self, s: S) -> impl DoubleEndedIterator + '_ { + pub fn remove_all>(&mut self, s: S) -> impl DoubleEndedIterator + '_ { self.data.remove_all(property_get_key!(s.as_ref())) } @@ -674,8 +674,8 @@ impl Ini { /// Set with a specified section, `None` is for the general section pub fn with_section(&mut self, section: Option) -> SectionSetter - where - S: Into, + where + S: Into, { SectionSetter::new(self, section.map(Into::into)) } @@ -699,32 +699,32 @@ impl Ini { /// Get a immutable section pub fn section(&self, name: Option) -> Option<&Properties> - where - S: Into, + where + S: Into, { self.sections.get(§ion_key!(name)) } /// Get a mutable section pub fn section_mut(&mut self, name: Option) -> Option<&mut Properties> - where - S: Into, + where + S: Into, { self.sections.get_mut(§ion_key!(name)) } /// Get all sections immutable with the same key - pub fn section_all(&self, name: Option) -> impl DoubleEndedIterator - where - S: Into, + pub fn section_all(&self, name: Option) -> impl DoubleEndedIterator + where + S: Into, { self.sections.get_all(§ion_key!(name)) } /// Get all sections mutable with the same key - pub fn section_all_mut(&mut self, name: Option) -> impl DoubleEndedIterator - where - S: Into, + pub fn section_all_mut(&mut self, name: Option) -> impl DoubleEndedIterator + where + S: Into, { self.sections.get_all_mut(§ion_key!(name)) } @@ -747,14 +747,14 @@ impl Ini { } /// Iterate with sections - pub fn sections(&self) -> impl DoubleEndedIterator> { + pub fn sections(&self) -> impl DoubleEndedIterator> { self.sections.keys().map(|s| s.as_ref().map(AsRef::as_ref)) } /// Set key-value to a section pub fn set_to(&mut self, section: Option, key: String, value: String) - where - S: Into, + where + S: Into, { self.with_section(section).set(key, value); } @@ -770,8 +770,8 @@ impl Ini { /// assert_eq!(ini.get_from(Some("sec"), "abc"), Some("def")); /// ``` pub fn get_from<'a, S>(&'a self, section: Option, key: &str) -> Option<&'a str> - where - S: Into, + where + S: Into, { self.sections.get(§ion_key!(section)).and_then(|prop| prop.get(key)) } @@ -787,16 +787,16 @@ impl Ini { /// assert_eq!(ini.get_from_or(Some("sec"), "key", "default"), "default"); /// ``` pub fn get_from_or<'a, S>(&'a self, section: Option, key: &str, default: &'a str) -> &'a str - where - S: Into, + where + S: Into, { self.get_from(section, key).unwrap_or(default) } /// Get the first mutable value from the sections with key pub fn get_from_mut<'a, S>(&'a mut self, section: Option, key: &str) -> Option<&'a mut str> - where - S: Into, + where + S: Into, { self.sections .get_mut(§ion_key!(section)) @@ -805,8 +805,8 @@ impl Ini { /// Delete the first section with key, return the properties if it exists pub fn delete(&mut self, section: Option) -> Option - where - S: Into, + where + S: Into, { let key = section_key!(section); self.sections.remove(&key) @@ -814,8 +814,8 @@ impl Ini { /// Delete the key from the section, return the value if key exists or None pub fn delete_from(&mut self, section: Option, key: &str) -> Option - where - S: Into, + where + S: Into, { self.section_mut(section).and_then(|prop| prop.remove(key)) } @@ -1512,7 +1512,7 @@ impl<'a> Parser<'a> { self.bump(); self.parse_str_until(&[Some('"')], false).and_then(|s| { self.bump(); // Eats the last " - // Parse until EOL + // Parse until EOL self.parse_str_until_eol(cfg!(feature = "inline-comment")) .map(|x| s + &x) }) @@ -1521,7 +1521,7 @@ impl<'a> Parser<'a> { self.bump(); self.parse_str_until(&[Some('\'')], false).and_then(|s| { self.bump(); // Eats the last ' - // Parse until EOL + // Parse until EOL self.parse_str_until_eol(cfg!(feature = "inline-comment")) .map(|x| s + &x) }) @@ -2080,7 +2080,7 @@ Exec = \"/path/to/exe with space\" arg ..ParseOption::default() }, ) - .unwrap(); + .unwrap(); let sec = opt.section(Some("Desktop Entry")).unwrap(); assert_eq!(&sec["Exec"], "\"/path/to/exe with space\" arg"); } @@ -2213,7 +2213,7 @@ a3 = n3 ..Default::default() }, ) - .unwrap(); + .unwrap(); assert_eq!( "[Section1]\nKey1=Value\nKey2=Value\n\n[Section2]\nKey1=Value\nKey2=Value\n", @@ -2230,7 +2230,7 @@ a3 = n3 ..Default::default() }, ) - .unwrap(); + .unwrap(); assert_eq!( "[Section1]\r\nKey1=Value\r\nKey2=Value\r\n\r\n[Section2]\r\nKey1=Value\r\nKey2=Value\r\n", @@ -2247,7 +2247,7 @@ a3 = n3 ..Default::default() }, ) - .unwrap(); + .unwrap(); if cfg!(windows) { assert_eq!( @@ -2286,7 +2286,7 @@ a3 = n3 ..Default::default() }, ) - .unwrap(); + .unwrap(); // Test different line endings in Windows and Unix if cfg!(windows) { @@ -2374,11 +2374,7 @@ bar = f let mut buf = Vec::new(); ini.write_to(&mut buf).unwrap(); let ini_str = String::from_utf8(buf).unwrap(); - let expected = r#"[foo] -a=1 -a=2 -"#; - assert_eq!(ini_str, expected); + assert_eq!(ini_str, "[foo]\na=1\na=2\n"); } #[test] @@ -2632,7 +2628,7 @@ x3 = nb vec![ ("x2".to_owned(), "nc".to_owned()), ("x1".to_owned(), "na".to_owned()), - ("x3".to_owned(), "nb".to_owned()), + ("x3".to_owned(), "nb".to_owned()) ] ); } From f255b0dcf16079479be3162f36e030f1914fba51 Mon Sep 17 00:00:00 2001 From: Rouven Himmelstein Date: Tue, 9 Apr 2024 08:02:12 +0200 Subject: [PATCH 7/8] fix: broken test on windows --- src/lib.rs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/lib.rs b/src/lib.rs index 0f017ba..70f8fb6 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -2374,7 +2374,11 @@ bar = f let mut buf = Vec::new(); ini.write_to(&mut buf).unwrap(); let ini_str = String::from_utf8(buf).unwrap(); - assert_eq!(ini_str, "[foo]\na=1\na=2\n"); + let expected = r#"[foo] +a=1 +a=2 +"#; + assert_eq!(ini_str, expected); } #[test] From a529a9092d99b4e78e70e77ba052203accb4ae76 Mon Sep 17 00:00:00 2001 From: Rouven Himmelstein Date: Fri, 12 Apr 2024 07:09:25 +0200 Subject: [PATCH 8/8] fix: broken test on windows (hopefully) --- src/lib.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 70f8fb6..4f11166 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -2374,11 +2374,11 @@ bar = f let mut buf = Vec::new(); ini.write_to(&mut buf).unwrap(); let ini_str = String::from_utf8(buf).unwrap(); - let expected = r#"[foo] -a=1 -a=2 -"#; - assert_eq!(ini_str, expected); + if cfg!(windows) { + assert_eq!(ini_str, "[foo]\r\na=1\r\na=2\r\n"); + } else { + assert_eq!(ini_str, "[foo]\na=1\na=2\n"); + } } #[test]