From ea0f76d528e3218267b78c93e4256e6521071807 Mon Sep 17 00:00:00 2001 From: Edward Shen Date: Tue, 23 Feb 2021 19:47:24 -0500 Subject: [PATCH] Use BStr instead --- .vscode/settings.json | 12 + Cargo.toml | 1 + src/config.rs | 318 ++++++++--------- src/lib.rs | 3 + src/parser.rs | 568 ++++++++++++++---------------- src/test_util.rs | 73 ++++ tests/parser_integration_tests.rs | 72 ++-- 7 files changed, 552 insertions(+), 495 deletions(-) create mode 100644 .vscode/settings.json create mode 100644 src/test_util.rs diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..6ac041c --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,12 @@ +{ + "cSpell.words": [ + "Multivar", + "autocrlf", + "bstr", + "gitea", + "gpgsign", + "implicits", + "multivars", + "subname" + ] +} \ No newline at end of file diff --git a/Cargo.toml b/Cargo.toml index f931197..4229891 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,6 +9,7 @@ edition = "2018" [dependencies] serde = "1.0" nom = "6" +bstr = "0.2.15" [dev-dependencies] serde_derive = "1.0" \ No newline at end of file diff --git a/src/config.rs b/src/config.rs index 054df2a..cc07526 100644 --- a/src/config.rs +++ b/src/config.rs @@ -1,16 +1,16 @@ use crate::parser::{parse_from_str, Event, ParsedSectionHeader, Parser, ParserError}; -use serde::{ser::SerializeMap, Serialize, Serializer}; +use bstr::BStr; use std::collections::{HashMap, VecDeque}; use std::{borrow::Cow, fmt::Display}; #[derive(Debug, PartialEq, Eq)] pub enum GitConfigError<'a> { /// The requested section does not exist. - SectionDoesNotExist(&'a str), + SectionDoesNotExist(&'a BStr), /// The requested subsection does not exist. - SubSectionDoesNotExist(Option<&'a str>), + SubSectionDoesNotExist(Option<&'a BStr>), /// The key does not exist in the requested section. - KeyDoesNotExist(&'a str), + KeyDoesNotExist(&'a BStr), } /// High level `git-config` reader and writer. @@ -19,7 +19,7 @@ pub struct GitConfig<'a> { /// `git-config` file prohibits global values, this vec is limited to only /// comment, newline, and whitespace events. front_matter_events: Vec>, - section_lookup_tree: HashMap, Vec>>, + section_lookup_tree: HashMap, Vec>>, /// SectionId to section mapping. The value of this HashMap contains actual /// events sections: HashMap>>, @@ -45,7 +45,7 @@ struct SectionId(usize); #[derive(Debug, PartialEq, Eq)] enum LookupTreeNode<'a> { Terminal(Vec), - NonTerminal(HashMap, Vec>), + NonTerminal(HashMap, Vec>), } impl<'a> GitConfig<'a> { @@ -68,8 +68,8 @@ impl<'a> GitConfig<'a> { }; // Current section that we're building - let mut current_section_name: Option> = None; - let mut current_subsection_name: Option> = None; + let mut current_section_name: Option> = None; + let mut current_subsection_name: Option> = None; let mut maybe_section: Option>> = None; for event in parser.into_iter() { @@ -123,8 +123,8 @@ impl<'a> GitConfig<'a> { fn push_section( &mut self, - current_section_name: Option>, - current_subsection_name: Option>, + current_section_name: Option>, + current_subsection_name: Option>, maybe_section: &mut Option>>, ) { if let Some(section) = maybe_section.take() { @@ -203,7 +203,7 @@ impl<'a> GitConfig<'a> { /// # use serde_git_config::config::GitConfig; /// # use std::borrow::Cow; /// # let git_config = GitConfig::from_str("[core]a=b\n[core]\na=c\na=d").unwrap(); - /// assert_eq!(git_config.get_raw_value("core", None, "a"), Ok(&Cow::from("d"))); + /// assert_eq!(git_config.get_raw_value("core", None, "a"), Ok(&Cow::Borrowed("d".into()))); /// ``` /// /// Consider [`Self::get_raw_multi_value`] if you want to get all values of @@ -213,16 +213,20 @@ impl<'a> GitConfig<'a> { /// /// This function will return an error if the key is not in the requested /// section and subsection, or if the section and subsection do not exist. - pub fn get_raw_value<'b>( + pub fn get_raw_value<'b, S: Into<&'b BStr>>( &self, - section_name: &'b str, - subsection_name: Option<&'b str>, - key: &'b str, - ) -> Result<&Cow<'a, str>, GitConfigError<'b>> { + section_name: S, + subsection_name: Option, + key: S, + ) -> Result<&Cow<'a, BStr>, GitConfigError<'b>> { + let key = key.into(); // Note: cannot wrap around the raw_multi_value method because we need // to guarantee that the highest section id is used (so that we follow // the "last one wins" resolution strategy by `git-config`). - let section_id = self.get_section_id_by_name_and_subname(section_name, subsection_name)?; + let section_id = self.get_section_id_by_name_and_subname( + section_name.into(), + subsection_name.map(Into::into), + )?; // section_id is guaranteed to exist in self.sections, else we have a // violated invariant. @@ -273,11 +277,12 @@ impl<'a> GitConfig<'a> { /// ``` /// # use serde_git_config::config::{GitConfig, GitConfigError}; /// # use std::borrow::Cow; + /// # use bstr::BStr; /// # let mut git_config = GitConfig::from_str("[core]a=b\n[core]\na=c\na=d").unwrap(); /// let mut mut_value = git_config.get_raw_value_mut("core", None, "a")?; - /// assert_eq!(mut_value, &mut Cow::from("d")); - /// *mut_value = Cow::Borrowed("hello"); - /// assert_eq!(git_config.get_raw_value("core", None, "a"), Ok(&Cow::from("hello"))); + /// assert_eq!(mut_value, &mut Cow::::Borrowed("d".into())); + /// *mut_value = Cow::Borrowed("hello".into()); + /// assert_eq!(git_config.get_raw_value("core", None, "a"), Ok(&Cow::Borrowed("hello".into()))); /// # Ok::<(), GitConfigError>(()) /// ``` /// @@ -288,16 +293,20 @@ impl<'a> GitConfig<'a> { /// /// This function will return an error if the key is not in the requested /// section and subsection, or if the section and subsection do not exist. - pub fn get_raw_value_mut<'b>( + pub fn get_raw_value_mut<'b, S: Into<&'b BStr>>( &mut self, - section_name: &'b str, - subsection_name: Option<&'b str>, - key: &'b str, - ) -> Result<&mut Cow<'a, str>, GitConfigError<'b>> { + section_name: S, + subsection_name: Option, + key: S, + ) -> Result<&mut Cow<'a, BStr>, GitConfigError<'b>> { + let key = key.into(); // Note: cannot wrap around the raw_multi_value method because we need // to guarantee that the highest section id is used (so that we follow // the "last one wins" resolution strategy by `git-config`). - let section_id = self.get_section_id_by_name_and_subname(section_name, subsection_name)?; + let section_id = self.get_section_id_by_name_and_subname( + section_name.into(), + subsection_name.map(Into::into), + )?; // section_id is guaranteed to exist in self.sections, else we have a // violated invariant. @@ -320,8 +329,8 @@ impl<'a> GitConfig<'a> { fn get_section_id_by_name_and_subname<'b>( &'a self, - section_name: &'b str, - subsection_name: Option<&'b str>, + section_name: &'b BStr, + subsection_name: Option<&'b BStr>, ) -> Result> { self.get_section_ids_by_name_and_subname(section_name, subsection_name) .map(|vec| { @@ -350,7 +359,7 @@ impl<'a> GitConfig<'a> { /// # let git_config = GitConfig::from_str("[core]a=b\n[core]\na=c\na=d").unwrap(); /// assert_eq!( /// git_config.get_raw_multi_value("core", None, "a"), - /// Ok(vec![&Cow::from("b"), &Cow::from("c"), &Cow::from("d")]), + /// Ok(vec![&Cow::Borrowed("b".into()), &Cow::Borrowed("c".into()), &Cow::Borrowed("d".into())]), /// ); /// ``` /// @@ -362,14 +371,18 @@ impl<'a> GitConfig<'a> { /// This function will return an error if the key is not in any requested /// section and subsection, or if no instance of the section and subsections /// exist. - pub fn get_raw_multi_value<'b>( + pub fn get_raw_multi_value<'b, S: Into<&'b BStr>>( &'a self, - section_name: &'b str, - subsection_name: Option<&'b str>, - key: &'b str, - ) -> Result>, GitConfigError<'b>> { + section_name: S, + subsection_name: Option, + key: S, + ) -> Result>, GitConfigError<'b>> { + let key = key.into(); let mut values = vec![]; - for section_id in self.get_section_ids_by_name_and_subname(section_name, subsection_name)? { + for section_id in self.get_section_ids_by_name_and_subname( + section_name.into(), + subsection_name.map(Into::into), + )? { let mut found_key = false; // section_id is guaranteed to exist in self.sections, else we // have a violated invariant. @@ -408,14 +421,26 @@ impl<'a> GitConfig<'a> { /// ``` /// # use serde_git_config::config::{GitConfig, GitConfigError}; /// # use std::borrow::Cow; + /// # use bstr::BStr; /// # let mut git_config = GitConfig::from_str("[core]a=b\n[core]\na=c\na=d").unwrap(); - /// assert_eq!(git_config.get_raw_multi_value("core", None, "a")?, vec!["b", "c", "d"]); + /// assert_eq!( + /// git_config.get_raw_multi_value("core", None, "a")?, + /// vec![ + /// &Cow::::Borrowed("b".into()), + /// &Cow::::Borrowed("c".into()), + /// &Cow::::Borrowed("d".into()) + /// ] + /// ); /// for value in git_config.get_raw_multi_value_mut("core", None, "a")? { - /// *value = Cow::from("g"); + /// *value = Cow::Borrowed("g".into()); ///} /// assert_eq!( /// git_config.get_raw_multi_value("core", None, "a")?, - /// vec![&Cow::from("g"), &Cow::from("g"), &Cow::from("g")], + /// vec![ + /// &Cow::::Borrowed("g".into()), + /// &Cow::::Borrowed("g".into()), + /// &Cow::::Borrowed("g".into()) + /// ], /// ); /// # Ok::<(), GitConfigError>(()) /// ``` @@ -431,17 +456,21 @@ impl<'a> GitConfig<'a> { /// This function will return an error if the key is not in any requested /// section and subsection, or if no instance of the section and subsections /// exist. - pub fn get_raw_multi_value_mut<'b>( + pub fn get_raw_multi_value_mut<'b, S: Into<&'b BStr>>( &mut self, - section_name: &'b str, - subsection_name: Option<&'b str>, - key: &'b str, - ) -> Result>, GitConfigError<'b>> { + section_name: S, + subsection_name: Option, + key: S, + ) -> Result>, GitConfigError<'b>> { + let key = key.into(); let section_ids = self - .get_section_ids_by_name_and_subname(section_name, subsection_name)? + .get_section_ids_by_name_and_subname( + section_name.into(), + subsection_name.map(Into::into), + )? .to_vec(); let mut found_key = false; - let values: Vec<&mut Cow<'a, str>> = self + let values: Vec<&mut Cow<'a, BStr>> = self .sections .iter_mut() .filter_map(|(k, v)| { @@ -474,8 +503,8 @@ impl<'a> GitConfig<'a> { fn get_section_ids_by_name_and_subname<'b>( &'a self, - section_name: &'b str, - subsection_name: Option<&'b str>, + section_name: &'b BStr, + subsection_name: Option<&'b BStr>, ) -> Result<&[SectionId], GitConfigError<'b>> { let section_ids = self .section_lookup_tree @@ -488,7 +517,7 @@ impl<'a> GitConfig<'a> { if let Some(subsect_name) = subsection_name { for node in section_ids { if let LookupTreeNode::NonTerminal(subsection_lookup) = node { - maybe_ids = subsection_lookup.get(subsect_name); + maybe_ids = subsection_lookup.get(subsect_name.into()); break; } } @@ -505,12 +534,12 @@ impl<'a> GitConfig<'a> { .ok_or(GitConfigError::SubSectionDoesNotExist(subsection_name)) } - pub fn set_raw_value<'b>( + pub fn set_raw_value<'b, S: Into<&'b BStr>>( &mut self, - section_name: &'b str, - subsection_name: Option<&'b str>, - key: &'b str, - new_value: impl Into>, + section_name: S, + subsection_name: Option, + key: S, + new_value: impl Into>, ) -> Result<(), GitConfigError<'b>> { let value = self.get_raw_value_mut(section_name, subsection_name, key)?; *value = new_value.into(); @@ -530,12 +559,12 @@ impl<'a> GitConfig<'a> { /// todo: examples and errors /// /// [`get_raw_multi_value_mut`]: Self::get_raw_multi_value_mut - pub fn set_raw_multi_value<'b>( + pub fn set_raw_multi_value<'b, S: Into<&'b BStr>>( &mut self, - section_name: &'b str, - subsection_name: Option<&'b str>, - key: &'b str, - new_values: Vec>, + section_name: S, + subsection_name: Option, + key: S, + new_values: Vec>, ) -> Result<(), GitConfigError<'b>> { let values = self.get_raw_multi_value_mut(section_name, subsection_name, key)?; for (old, new) in values.into_iter().zip(new_values) { @@ -573,6 +602,7 @@ impl Display for GitConfig<'_> { #[cfg(test)] mod from_parser { use super::*; + use crate::test_util::*; #[test] fn parse_empty() { @@ -589,14 +619,7 @@ mod from_parser { let mut config = GitConfig::from_str("[core]\na=b\nc=d").unwrap(); let expected_separators = { let mut map = HashMap::new(); - map.insert( - SectionId(0), - ParsedSectionHeader { - name: "core".into(), - separator: None, - subsection_name: None, - }, - ); + map.insert(SectionId(0), section_header("core", None)); map }; assert_eq!(config.section_headers, expected_separators); @@ -604,7 +627,7 @@ mod from_parser { let expected_lookup_tree = { let mut tree = HashMap::new(); tree.insert( - Cow::Borrowed("core"), + Cow::Borrowed("core".into()), vec![LookupTreeNode::Terminal(vec![SectionId(0)])], ); tree @@ -615,14 +638,14 @@ mod from_parser { sections.insert( SectionId(0), vec![ - Event::Newline(Cow::Borrowed("\n")), - Event::Key(Cow::Borrowed("a")), + newline_event(), + name_event("a"), Event::KeyValueSeparator, - Event::Value(Cow::Borrowed("b")), - Event::Newline(Cow::Borrowed("\n")), - Event::Key(Cow::Borrowed("c")), + value_event("b"), + newline_event(), + name_event("c"), Event::KeyValueSeparator, - Event::Value(Cow::Borrowed("d")), + value_event("d"), ], ); sections @@ -636,14 +659,7 @@ mod from_parser { let mut config = GitConfig::from_str("[core.subsec]\na=b\nc=d").unwrap(); let expected_separators = { let mut map = HashMap::new(); - map.insert( - SectionId(0), - ParsedSectionHeader { - name: "core".into(), - separator: Some(".".into()), - subsection_name: Some("subsec".into()), - }, - ); + map.insert(SectionId(0), section_header("core", (".", "subsec"))); map }; assert_eq!(config.section_headers, expected_separators); @@ -651,9 +667,9 @@ mod from_parser { let expected_lookup_tree = { let mut tree = HashMap::new(); let mut inner_tree = HashMap::new(); - inner_tree.insert(Cow::Borrowed("subsec"), vec![SectionId(0)]); + inner_tree.insert(Cow::Borrowed("subsec".into()), vec![SectionId(0)]); tree.insert( - Cow::Borrowed("core"), + Cow::Borrowed("core".into()), vec![LookupTreeNode::NonTerminal(inner_tree)], ); tree @@ -664,14 +680,14 @@ mod from_parser { sections.insert( SectionId(0), vec![ - Event::Newline(Cow::Borrowed("\n")), - Event::Key(Cow::Borrowed("a")), + newline_event(), + name_event("a"), Event::KeyValueSeparator, - Event::Value(Cow::Borrowed("b")), - Event::Newline(Cow::Borrowed("\n")), - Event::Key(Cow::Borrowed("c")), + value_event("b"), + newline_event(), + name_event("c"), Event::KeyValueSeparator, - Event::Value(Cow::Borrowed("d")), + value_event("d"), ], ); sections @@ -685,22 +701,8 @@ mod from_parser { let mut config = GitConfig::from_str("[core]\na=b\nc=d\n[other]e=f").unwrap(); let expected_separators = { let mut map = HashMap::new(); - map.insert( - SectionId(0), - ParsedSectionHeader { - name: "core".into(), - separator: None, - subsection_name: None, - }, - ); - map.insert( - SectionId(1), - ParsedSectionHeader { - name: "other".into(), - separator: None, - subsection_name: None, - }, - ); + map.insert(SectionId(0), section_header("core", None)); + map.insert(SectionId(1), section_header("other", None)); map }; assert_eq!(config.section_headers, expected_separators); @@ -708,11 +710,11 @@ mod from_parser { let expected_lookup_tree = { let mut tree = HashMap::new(); tree.insert( - Cow::Borrowed("core"), + Cow::Borrowed("core".into()), vec![LookupTreeNode::Terminal(vec![SectionId(0)])], ); tree.insert( - Cow::Borrowed("other"), + Cow::Borrowed("other".into()), vec![LookupTreeNode::Terminal(vec![SectionId(1)])], ); tree @@ -723,24 +725,20 @@ mod from_parser { sections.insert( SectionId(0), vec![ - Event::Newline(Cow::Borrowed("\n")), - Event::Key(Cow::Borrowed("a")), + newline_event(), + name_event("a"), Event::KeyValueSeparator, - Event::Value(Cow::Borrowed("b")), - Event::Newline(Cow::Borrowed("\n")), - Event::Key(Cow::Borrowed("c")), + value_event("b"), + newline_event(), + name_event("c"), Event::KeyValueSeparator, - Event::Value(Cow::Borrowed("d")), - Event::Newline(Cow::Borrowed("\n")), + value_event("d"), + newline_event(), ], ); sections.insert( SectionId(1), - vec![ - Event::Key(Cow::Borrowed("e")), - Event::KeyValueSeparator, - Event::Value(Cow::Borrowed("f")), - ], + vec![name_event("e"), Event::KeyValueSeparator, value_event("f")], ); sections }; @@ -756,22 +754,8 @@ mod from_parser { let mut config = GitConfig::from_str("[core]\na=b\nc=d\n[core]e=f").unwrap(); let expected_separators = { let mut map = HashMap::new(); - map.insert( - SectionId(0), - ParsedSectionHeader { - name: "core".into(), - separator: None, - subsection_name: None, - }, - ); - map.insert( - SectionId(1), - ParsedSectionHeader { - name: "core".into(), - separator: None, - subsection_name: None, - }, - ); + map.insert(SectionId(0), section_header("core", None)); + map.insert(SectionId(1), section_header("core", None)); map }; assert_eq!(config.section_headers, expected_separators); @@ -779,7 +763,7 @@ mod from_parser { let expected_lookup_tree = { let mut tree = HashMap::new(); tree.insert( - Cow::Borrowed("core"), + Cow::Borrowed("core".into()), vec![LookupTreeNode::Terminal(vec![SectionId(0), SectionId(1)])], ); tree @@ -790,24 +774,20 @@ mod from_parser { sections.insert( SectionId(0), vec![ - Event::Newline(Cow::Borrowed("\n")), - Event::Key(Cow::Borrowed("a")), + newline_event(), + name_event("a"), Event::KeyValueSeparator, - Event::Value(Cow::Borrowed("b")), - Event::Newline(Cow::Borrowed("\n")), - Event::Key(Cow::Borrowed("c")), + value_event("b"), + newline_event(), + name_event("c"), Event::KeyValueSeparator, - Event::Value(Cow::Borrowed("d")), - Event::Newline(Cow::Borrowed("\n")), + value_event("d"), + newline_event(), ], ); sections.insert( SectionId(1), - vec![ - Event::Key(Cow::Borrowed("e")), - Event::KeyValueSeparator, - Event::Value(Cow::Borrowed("f")), - ], + vec![name_event("e"), Event::KeyValueSeparator, value_event("f")], ); sections }; @@ -828,11 +808,11 @@ mod get_raw_value { let config = GitConfig::from_str("[core]\na=b\nc=d").unwrap(); assert_eq!( config.get_raw_value("core", None, "a"), - Ok(&Cow::Borrowed("b")) + Ok(&Cow::Borrowed("b".into())) ); assert_eq!( config.get_raw_value("core", None, "c"), - Ok(&Cow::Borrowed("d")) + Ok(&Cow::Borrowed("d".into())) ); } @@ -841,7 +821,7 @@ mod get_raw_value { let config = GitConfig::from_str("[core]\na=b\na=d").unwrap(); assert_eq!( config.get_raw_value("core", None, "a"), - Ok(&Cow::Borrowed("d")) + Ok(&Cow::Borrowed("d".into())) ); } @@ -850,7 +830,7 @@ mod get_raw_value { let config = GitConfig::from_str("[core]\na=b\n[core]\na=d").unwrap(); assert_eq!( config.get_raw_value("core", None, "a"), - Ok(&Cow::Borrowed("d")) + Ok(&Cow::Borrowed("d".into())) ); } @@ -859,7 +839,7 @@ mod get_raw_value { let config = GitConfig::from_str("[core]\na=b\nc=d").unwrap(); assert_eq!( config.get_raw_value("foo", None, "a"), - Err(GitConfigError::SectionDoesNotExist("foo")) + Err(GitConfigError::SectionDoesNotExist("foo".into())) ); } @@ -868,7 +848,7 @@ mod get_raw_value { let config = GitConfig::from_str("[core]\na=b\nc=d").unwrap(); assert_eq!( config.get_raw_value("core", Some("a"), "a"), - Err(GitConfigError::SubSectionDoesNotExist(Some("a"))) + Err(GitConfigError::SubSectionDoesNotExist(Some("a".into()))) ); } @@ -877,7 +857,7 @@ mod get_raw_value { let config = GitConfig::from_str("[core]\na=b\nc=d").unwrap(); assert_eq!( config.get_raw_value("core", None, "aaaaaa"), - Err(GitConfigError::KeyDoesNotExist("aaaaaa")) + Err(GitConfigError::KeyDoesNotExist("aaaaaa".into())) ); } @@ -886,11 +866,11 @@ mod get_raw_value { let config = GitConfig::from_str("[core]a=b\n[core.a]a=c").unwrap(); assert_eq!( config.get_raw_value("core", None, "a"), - Ok(&Cow::Borrowed("b")) + Ok(&Cow::Borrowed("b".into())) ); assert_eq!( config.get_raw_value("core", Some("a"), "a"), - Ok(&Cow::Borrowed("c")) + Ok(&Cow::Borrowed("c".into())) ); } } @@ -913,7 +893,7 @@ mod get_raw_multi_value { let config = GitConfig::from_str("[core]\na=b\na=c").unwrap(); assert_eq!( config.get_raw_multi_value("core", None, "a").unwrap(), - vec!["b", "c"] + vec![&Cow::Borrowed("b"), &Cow::Borrowed("c")] ); } @@ -922,7 +902,11 @@ mod get_raw_multi_value { let config = GitConfig::from_str("[core]\na=b\na=c\n[core]a=d").unwrap(); assert_eq!( config.get_raw_multi_value("core", None, "a").unwrap(), - vec!["b", "c", "d"] + vec![ + &Cow::Borrowed("b"), + &Cow::Borrowed("c"), + &Cow::Borrowed("d") + ] ); } @@ -931,7 +915,7 @@ mod get_raw_multi_value { let config = GitConfig::from_str("[core]\na=b\nc=d").unwrap(); assert_eq!( config.get_raw_multi_value("foo", None, "a"), - Err(GitConfigError::SectionDoesNotExist("foo")) + Err(GitConfigError::SectionDoesNotExist("foo".into())) ); } @@ -940,7 +924,7 @@ mod get_raw_multi_value { let config = GitConfig::from_str("[core]\na=b\nc=d").unwrap(); assert_eq!( config.get_raw_multi_value("core", Some("a"), "a"), - Err(GitConfigError::SubSectionDoesNotExist(Some("a"))) + Err(GitConfigError::SubSectionDoesNotExist(Some("a".into()))) ); } @@ -949,7 +933,7 @@ mod get_raw_multi_value { let config = GitConfig::from_str("[core]\na=b\nc=d").unwrap(); assert_eq!( config.get_raw_multi_value("core", None, "aaaaaa"), - Err(GitConfigError::KeyDoesNotExist("aaaaaa")) + Err(GitConfigError::KeyDoesNotExist("aaaaaa".into())) ); } @@ -958,11 +942,11 @@ mod get_raw_multi_value { let config = GitConfig::from_str("[core]a=b\n[core.a]a=c").unwrap(); assert_eq!( config.get_raw_multi_value("core", None, "a").unwrap(), - vec!["b"] + vec![&Cow::Borrowed("b")] ); assert_eq!( config.get_raw_multi_value("core", Some("a"), "a").unwrap(), - vec!["c"] + vec![&Cow::Borrowed("c")] ); } @@ -971,7 +955,11 @@ mod get_raw_multi_value { let config = GitConfig::from_str("[core]\na=b\na=c\n[core]a=d\n[core]g=g").unwrap(); assert_eq!( config.get_raw_multi_value("core", None, "a").unwrap(), - vec!["b", "c", "d"] + vec![ + &Cow::Borrowed("b"), + &Cow::Borrowed("c"), + &Cow::Borrowed("d") + ] ); } } diff --git a/src/lib.rs b/src/lib.rs index ae2ce2a..d2384bf 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -10,3 +10,6 @@ pub mod values; // pub use de::{from_str, Deserializer}; pub use error::{Error, Result}; // pub use ser::{to_string, Serializer}; + +#[cfg(test)] +pub mod test_util; diff --git a/src/parser.rs b/src/parser.rs index 693ff37..4167e41 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -9,6 +9,7 @@ //! //! [`GitConfig`]: crate::config::GitConfig +use bstr::{BStr, ByteSlice}; use nom::bytes::complete::{escaped, tag, take_till, take_while}; use nom::character::complete::{char, none_of, one_of}; use nom::character::{is_newline, is_space}; @@ -17,7 +18,7 @@ use nom::error::{Error as NomError, ErrorKind}; use nom::sequence::delimited; use nom::IResult; use nom::{branch::alt, multi::many0}; -use std::borrow::{Borrow, Cow}; +use std::borrow::Cow; use std::fmt::Display; use std::iter::FusedIterator; @@ -42,26 +43,26 @@ pub enum Event<'a> { /// exists. SectionHeader(ParsedSectionHeader<'a>), /// A name to a value in a section. - Key(Cow<'a, str>), + Key(Cow<'a, BStr>), /// A completed value. This may be any string, including the empty string, /// if an implicit boolean value is used. Note that these values may contain /// spaces and any special character. This value is also unprocessed, so it /// it may contain double quotes that should be replaced. - Value(Cow<'a, str>), + Value(Cow<'a, BStr>), /// Represents any token used to signify a new line character. On Unix /// platforms, this is typically just `\n`, but can be any valid newline /// sequence. Multiple newlines (such as `\n\n`) will be merged as a single /// newline event. - Newline(Cow<'a, str>), + Newline(Cow<'a, BStr>), /// Any value that isn't completed. This occurs when the value is continued /// onto the next line. A Newline event is guaranteed after, followed by /// either a ValueDone, a Whitespace, or another ValueNotDone. - ValueNotDone(Cow<'a, str>), + ValueNotDone(Cow<'a, BStr>), /// The last line of a value which was continued onto another line. - ValueDone(Cow<'a, str>), + ValueDone(Cow<'a, BStr>), /// A continuous section of insignificant whitespace. Values with internal /// spaces will not be separated by this event. - Whitespace(Cow<'a, str>), + Whitespace(Cow<'a, BStr>), /// This event is emitted when the parser counters a valid `=` character /// separating the key and value. This event is necessary as it eliminates /// the ambiguity for whitespace events between a key and value event. @@ -103,19 +104,25 @@ impl Display for ParsedSection<'_> { } } +impl<'a> Into> for ParsedSectionHeader<'a> { + fn into(self) -> Event<'a> { + Event::SectionHeader(self) + } +} + /// A parsed section header, containing a name and optionally a subsection name. #[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug, Default)] pub struct ParsedSectionHeader<'a> { /// The name of the header. - pub name: Cow<'a, str>, + pub name: Cow<'a, BStr>, /// The separator used to determine if the section contains a subsection. /// This is either a period `.` or a string of whitespace. Note that /// reconstruction of subsection format is dependent on this value. If this /// is all whitespace, then the subsection name needs to be surrounded by /// quotes to have perfect reconstruction. - pub separator: Option>, + pub separator: Option>, /// The subsection name without quotes if any exist. - pub subsection_name: Option>, + pub subsection_name: Option>, } impl Display for ParsedSectionHeader<'_> { @@ -125,9 +132,10 @@ impl Display for ParsedSectionHeader<'_> { if let Some(v) = &self.separator { v.fmt(f)?; let subsection_name = self.subsection_name.as_ref().unwrap(); - match v.borrow() { - "." => subsection_name.fmt(f)?, - _ => write!(f, "\"{}\"", subsection_name)?, + if *v == b".".as_bstr() { + subsection_name.fmt(f)?; + } else { + write!(f, "\"{}\"", subsection_name)?; } } @@ -141,7 +149,7 @@ pub struct ParsedComment<'a> { /// The comment marker used. This is either a semicolon or octothorpe. pub comment_tag: char, /// The parsed comment. - pub comment: Cow<'a, str>, + pub comment: Cow<'a, BStr>, } impl Display for ParsedComment<'_> { @@ -155,15 +163,15 @@ impl Display for ParsedComment<'_> { #[derive(PartialEq, Debug)] pub enum ParserError<'a> { /// A parsing error occurred. - InvalidInput(nom::Err>), + InvalidInput(nom::Err>), /// The config was successfully parsed, but we had extraneous data after the /// config file. - ConfigHasExtraData(&'a str), + ConfigHasExtraData(&'a BStr), } #[doc(hidden)] -impl<'a> From>> for ParserError<'a> { - fn from(e: nom::Err>) -> Self { +impl<'a> From>> for ParserError<'a> { + fn from(e: nom::Err>) -> Self { Self::InvalidInput(e) } } @@ -245,20 +253,20 @@ impl<'a> From>> for ParserError<'a> { /// # use serde_git_config::parser::{Event, ParsedSectionHeader, parse_from_str}; /// # use std::borrow::Cow; /// # let section_header = ParsedSectionHeader { -/// # name: Cow::Borrowed("core"), +/// # name: Cow::Borrowed("core".into()), /// # separator: None, /// # subsection_name: None, /// # }; /// # let section_data = "[core]\n autocrlf = input"; /// # assert_eq!(parse_from_str(section_data).unwrap().into_vec(), vec![ /// Event::SectionHeader(section_header), -/// Event::Newline(Cow::Borrowed("\n")), -/// Event::Whitespace(Cow::Borrowed(" ")), -/// Event::Key(Cow::Borrowed("autocrlf")), -/// Event::Whitespace(Cow::Borrowed(" ")), +/// Event::Newline(Cow::Borrowed("\n".into())), +/// Event::Whitespace(Cow::Borrowed(" ".into())), +/// Event::Key(Cow::Borrowed("autocrlf".into())), +/// Event::Whitespace(Cow::Borrowed(" ".into())), /// Event::KeyValueSeparator, -/// Event::Whitespace(Cow::Borrowed(" ")), -/// Event::Value(Cow::Borrowed("input")), +/// Event::Whitespace(Cow::Borrowed(" ".into())), +/// Event::Value(Cow::Borrowed("input".into())), /// # ]); /// ``` /// @@ -283,18 +291,19 @@ impl<'a> From>> for ParserError<'a> { /// ``` /// # use serde_git_config::parser::{Event, ParsedSectionHeader, parse_from_str}; /// # use std::borrow::Cow; +/// # use bstr::BStr; /// # let section_header = ParsedSectionHeader { -/// # name: Cow::Borrowed("core"), +/// # name: Cow::Borrowed("core".into()), /// # separator: None, /// # subsection_name: None, /// # }; /// # let section_data = "[core]\n autocrlf"; /// # assert_eq!(parse_from_str(section_data).unwrap().into_vec(), vec![ /// Event::SectionHeader(section_header), -/// Event::Newline(Cow::Borrowed("\n")), -/// Event::Whitespace(Cow::Borrowed(" ")), -/// Event::Key(Cow::Borrowed("autocrlf")), -/// Event::Value(Cow::Borrowed("")), +/// Event::Newline(Cow::Borrowed("\n".into())), +/// Event::Whitespace(Cow::Borrowed(" ".into())), +/// Event::Key(Cow::Borrowed("autocrlf".into())), +/// Event::Value(Cow::Borrowed("".into())), /// # ]); /// ``` /// @@ -318,21 +327,21 @@ impl<'a> From>> for ParserError<'a> { /// # use serde_git_config::parser::{Event, ParsedSectionHeader, parse_from_str}; /// # use std::borrow::Cow; /// # let section_header = ParsedSectionHeader { -/// # name: Cow::Borrowed("core"), +/// # name: Cow::Borrowed("core".into()), /// # separator: None, /// # subsection_name: None, /// # }; /// # let section_data = "[core]\nautocrlf=true\"\"\nfilemode=fa\"lse\""; /// # assert_eq!(parse_from_str(section_data).unwrap().into_vec(), vec![ /// Event::SectionHeader(section_header), -/// Event::Newline(Cow::Borrowed("\n")), -/// Event::Key(Cow::Borrowed("autocrlf")), +/// Event::Newline(Cow::Borrowed("\n".into())), +/// Event::Key(Cow::Borrowed("autocrlf".into())), /// Event::KeyValueSeparator, -/// Event::Value(Cow::Borrowed(r#"true"""#)), -/// Event::Newline(Cow::Borrowed("\n")), -/// Event::Key(Cow::Borrowed("filemode")), +/// Event::Value(Cow::Borrowed(r#"true"""#.into())), +/// Event::Newline(Cow::Borrowed("\n".into())), +/// Event::Key(Cow::Borrowed("filemode".into())), /// Event::KeyValueSeparator, -/// Event::Value(Cow::Borrowed(r#"fa"lse""#)), +/// Event::Value(Cow::Borrowed(r#"fa"lse""#.into())), /// # ]); /// ``` /// @@ -355,19 +364,19 @@ impl<'a> From>> for ParserError<'a> { /// # use serde_git_config::parser::{Event, ParsedSectionHeader, parse_from_str}; /// # use std::borrow::Cow; /// # let section_header = ParsedSectionHeader { -/// # name: Cow::Borrowed("some-section"), +/// # name: Cow::Borrowed("some-section".into()), /// # separator: None, /// # subsection_name: None, /// # }; /// # let section_data = "[some-section]\nfile=a\\\n c"; /// # assert_eq!(parse_from_str(section_data).unwrap().into_vec(), vec![ /// Event::SectionHeader(section_header), -/// Event::Newline(Cow::Borrowed("\n")), -/// Event::Key(Cow::Borrowed("file")), +/// Event::Newline(Cow::Borrowed("\n".into())), +/// Event::Key(Cow::Borrowed("file".into())), /// Event::KeyValueSeparator, -/// Event::ValueNotDone(Cow::Borrowed("a")), -/// Event::Newline(Cow::Borrowed("\n")), -/// Event::ValueDone(Cow::Borrowed(" c")), +/// Event::ValueNotDone(Cow::Borrowed("a".into())), +/// Event::Newline(Cow::Borrowed("\n".into())), +/// Event::ValueDone(Cow::Borrowed(" c".into())), /// # ]); /// ``` /// @@ -466,14 +475,16 @@ pub fn parse_from_str(input: &str) -> Result, ParserError> { let (i, frontmatter) = many0(alt(( map(comment, |comment| Event::Comment(comment)), map(take_spaces, |whitespace| { - Event::Whitespace(whitespace.into()) + Event::Whitespace(Cow::Borrowed(whitespace.into())) }), - map(take_newline, |newline| Event::Newline(newline.into())), - )))(input)?; + map(take_newline, |newline| { + Event::Newline(Cow::Borrowed(newline.into())) + }), + )))(input.as_bytes())?; let (i, sections) = many0(section)(i)?; if !i.is_empty() { - return Err(ParserError::ConfigHasExtraData(i)); + return Err(ParserError::ConfigHasExtraData(i.into())); } Ok(Parser { @@ -482,29 +493,29 @@ pub fn parse_from_str(input: &str) -> Result, ParserError> { }) } -fn comment<'a>(i: &'a str) -> IResult<&'a str, ParsedComment<'a>> { +fn comment<'a>(i: &'a [u8]) -> IResult<&'a [u8], ParsedComment<'a>> { let (i, comment_tag) = one_of(";#")(i)?; - let (i, comment) = take_till(is_char_newline)(i)?; + let (i, comment) = take_till(|c| c == b'\n')(i)?; Ok(( i, ParsedComment { comment_tag, - comment: Cow::Borrowed(comment), + comment: Cow::Borrowed(comment.into()), }, )) } -fn section<'a>(i: &'a str) -> IResult<&'a str, ParsedSection<'a>> { +fn section<'a>(i: &'a [u8]) -> IResult<&'a [u8], ParsedSection<'a>> { let (i, section_header) = section_header(i)?; let (i, items) = many0(alt(( map(take_spaces, |space| { - vec![Event::Whitespace(Cow::Borrowed(space))] + vec![Event::Whitespace(Cow::Borrowed(space.into()))] }), map(take_newline, |newline| { - vec![Event::Newline(Cow::Borrowed(newline))] + vec![Event::Newline(Cow::Borrowed(newline.into()))] }), map(section_body, |(key, values)| { - let mut vec = vec![Event::Key(Cow::Borrowed(key))]; + let mut vec = vec![Event::Key(Cow::Borrowed(key.into()))]; vec.extend(values); vec }), @@ -519,22 +530,26 @@ fn section<'a>(i: &'a str) -> IResult<&'a str, ParsedSection<'a>> { )) } -fn section_header<'a>(i: &'a str) -> IResult<&'a str, ParsedSectionHeader<'a>> { +fn section_header<'a>(i: &'a [u8]) -> IResult<&'a [u8], ParsedSectionHeader<'a>> { let (i, _) = char('[')(i)?; // No spaces must be between section name and section start - let (i, name) = take_while(|c: char| c.is_ascii_alphanumeric() || c == '-' || c == '.')(i)?; + let (i, name) = take_while(|c: u8| c.is_ascii_alphanumeric() || c == b'-' || c == b'.')(i)?; - if let Ok((i, _)) = char::<_, NomError<&str>>(']')(i) { + if let Ok((i, _)) = char::<_, NomError<&[u8]>>(']')(i) { // Either section does not have a subsection or using deprecated // subsection syntax at this point. - let header = match name.rfind('.') { + let header = match name.rfind(&[b'.']) { Some(index) => ParsedSectionHeader { - name: Cow::Borrowed(&name[..index]), - separator: name.get(index..index + 1).map(Cow::Borrowed), - subsection_name: name.get(index + 1..).map(Cow::Borrowed), + name: Cow::Borrowed(name[..index].into()), + separator: name + .get(index..index + 1) + .map(|slice| Cow::Borrowed(slice.into())), + subsection_name: name + .get(index + 1..) + .map(|slice| Cow::Borrowed(slice.into())), }, None => ParsedSectionHeader { - name: Cow::Borrowed(name), + name: Cow::Borrowed(name.into()), separator: None, subsection_name: None, }, @@ -556,22 +571,24 @@ fn section_header<'a>(i: &'a str) -> IResult<&'a str, ParsedSectionHeader<'a>> { Ok(( i, ParsedSectionHeader { - name: Cow::Borrowed(name), - separator: Some(Cow::Borrowed(whitespace)), + name: Cow::Borrowed(name.into()), + separator: Some(Cow::Borrowed(whitespace.into())), // We know that there's some section name here, so if we get an // empty vec here then we actually parsed an empty section name. - subsection_name: subsection_name.or(Some("")).map(Cow::Borrowed), + subsection_name: subsection_name + .or(Some(b"")) + .map(|slice| Cow::Borrowed(slice.into())), }, )) } -fn section_body<'a>(i: &'a str) -> IResult<&'a str, (&'a str, Vec>)> { +fn section_body<'a>(i: &'a [u8]) -> IResult<&'a [u8], (&'a [u8], Vec>)> { // maybe need to check for [ here let (i, name) = config_name(i)?; let (i, whitespace) = opt(take_spaces)(i)?; let (i, value) = config_value(i)?; if let Some(whitespace) = whitespace { - let mut events = vec![Event::Whitespace(Cow::Borrowed(whitespace))]; + let mut events = vec![Event::Whitespace(Cow::Borrowed(whitespace.into()))]; events.extend(value); Ok((i, (name, events))) } else { @@ -581,7 +598,7 @@ fn section_body<'a>(i: &'a str) -> IResult<&'a str, (&'a str, Vec>)> { /// Parses the config name of a config pair. Assumes the input has already been /// trimmed of any leading whitespace. -fn config_name<'a>(i: &'a str) -> IResult<&'a str, &'a str> { +fn config_name<'a>(i: &'a [u8]) -> IResult<&'a [u8], &'a [u8]> { if i.is_empty() { return Err(nom::Err::Error(NomError { input: i, @@ -589,33 +606,33 @@ fn config_name<'a>(i: &'a str) -> IResult<&'a str, &'a str> { })); } - if !i.chars().nth(0).unwrap().is_alphabetic() { + if !(i[0] as char).is_alphabetic() { return Err(nom::Err::Error(NomError { input: i, code: ErrorKind::Alpha, })); } - take_while(|c: char| c.is_alphanumeric() || c == '-')(i) + take_while(|c: u8| (c as char).is_alphanumeric() || c == b'-')(i) } -fn config_value<'a>(i: &'a str) -> IResult<&'a str, Vec>> { +fn config_value<'a>(i: &'a [u8]) -> IResult<&'a [u8], Vec>> { if let (i, Some(_)) = opt(char('='))(i)? { let mut events = vec![]; events.push(Event::KeyValueSeparator); let (i, whitespace) = opt(take_spaces)(i)?; let (i, values) = value_impl(i)?; if let Some(whitespace) = whitespace { - events.push(Event::Whitespace(Cow::Borrowed(whitespace))); + events.push(Event::Whitespace(Cow::Borrowed(whitespace.into()))); } events.extend(values); Ok((i, events)) } else { - Ok((i, vec![Event::Value(Cow::Borrowed(""))])) + Ok((i, vec![Event::Value(Cow::Borrowed("".into()))])) } } -fn value_impl<'a>(i: &'a str) -> IResult<&'a str, Vec>> { +fn value_impl<'a>(i: &'a [u8]) -> IResult<&'a [u8], Vec>> { let mut events = vec![]; let mut parsed_index: usize = 0; let mut offset: usize = 0; @@ -626,7 +643,7 @@ fn value_impl<'a>(i: &'a str) -> IResult<&'a str, Vec>> { // Used to determine if we return a Value or Value{Not,}Done let mut partial_value_found = false; - for (index, c) in i.as_bytes().iter().enumerate() { + for (index, c) in i.iter().enumerate() { if was_prev_char_escape_char { was_prev_char_escape_char = false; match c { @@ -634,8 +651,10 @@ fn value_impl<'a>(i: &'a str) -> IResult<&'a str, Vec>> { // continuation. b'\n' => { partial_value_found = true; - events.push(Event::ValueNotDone(Cow::Borrowed(&i[offset..index - 1]))); - events.push(Event::Newline(Cow::Borrowed(&i[index..index + 1]))); + events.push(Event::ValueNotDone(Cow::Borrowed( + i[offset..index - 1].into(), + ))); + events.push(Event::Newline(Cow::Borrowed(i[index..index + 1].into()))); offset = index + 1; parsed_index = 0; } @@ -687,7 +706,7 @@ fn value_impl<'a>(i: &'a str) -> IResult<&'a str, Vec>> { let (i, remainder_value) = { let mut new_index = parsed_index; for index in (offset..parsed_index).rev() { - if !(i.as_bytes()[index] as char).is_whitespace() { + if !(i[index] as char).is_whitespace() { new_index = index + 1; break; } @@ -696,27 +715,27 @@ fn value_impl<'a>(i: &'a str) -> IResult<&'a str, Vec>> { }; if partial_value_found { - events.push(Event::ValueDone(Cow::Borrowed(remainder_value))); + events.push(Event::ValueDone(Cow::Borrowed(remainder_value.into()))); } else { - events.push(Event::Value(Cow::Borrowed(remainder_value))); + events.push(Event::Value(Cow::Borrowed(remainder_value.into()))); } Ok((i, events)) } -fn is_char_newline(c: char) -> bool { - c.is_ascii() && is_newline(c as u8) +fn take_spaces<'a>(i: &'a [u8]) -> IResult<&'a [u8], &'a [u8]> { + take_common(i, |c| (c as char).is_ascii() && is_space(c)) } -fn take_spaces<'a>(i: &'a str) -> IResult<&'a str, &'a str> { - take_common(i, |c: char| c.is_ascii() && is_space(c as u8)) -} - -fn take_newline<'a>(i: &'a str) -> IResult<&'a str, &'a str> { +fn take_newline<'a>(i: &'a [u8]) -> IResult<&'a [u8], &'a [u8]> { take_common(i, is_char_newline) } -fn take_common<'a, F: Fn(char) -> bool>(i: &'a str, f: F) -> IResult<&'a str, &'a str> { +fn is_char_newline(c: u8) -> bool { + (c as char).is_ascii() && is_newline(c) +} + +fn take_common<'a, F: Fn(u8) -> bool>(i: &'a [u8], f: F) -> IResult<&'a [u8], &'a [u8]> { let (i, v) = take_while(f)(i)?; if v.is_empty() { Err(nom::Err::Error(NomError { @@ -729,65 +748,36 @@ fn take_common<'a, F: Fn(char) -> bool>(i: &'a str, f: F) -> IResult<&'a str, &' } #[cfg(test)] -fn fully_consumed(t: T) -> (&'static str, T) { - ("", t) -} - -#[cfg(test)] -fn gen_section_header( - name: &str, - subsection: impl Into>, -) -> ParsedSectionHeader<'_> { - let name = Cow::Borrowed(name); - if let Some((separator, subsection_name)) = subsection.into() { - ParsedSectionHeader { - name, - separator: Some(separator).map(Cow::Borrowed), - subsection_name: Some(subsection_name).map(Cow::Borrowed), - } - } else { - ParsedSectionHeader { - name, - separator: None, - subsection_name: None, - } - } +fn fully_consumed(t: T) -> (&'static [u8], T) { + (&[], t) } #[cfg(test)] mod comments { use super::*; + use crate::test_util::comment as parsed_comment; #[test] fn semicolon() { assert_eq!( - comment("; this is a semicolon comment").unwrap(), - fully_consumed(ParsedComment { - comment_tag: ';', - comment: Cow::Borrowed(" this is a semicolon comment"), - }) + comment(b"; this is a semicolon comment").unwrap(), + fully_consumed(parsed_comment(';', " this is a semicolon comment")), ); } #[test] fn octothorpe() { assert_eq!( - comment("# this is an octothorpe comment").unwrap(), - fully_consumed(ParsedComment { - comment_tag: '#', - comment: Cow::Borrowed(" this is an octothorpe comment"), - }) + comment(b"# this is an octothorpe comment").unwrap(), + fully_consumed(parsed_comment('#', " this is an octothorpe comment")), ); } #[test] fn multiple_markers() { assert_eq!( - comment("###### this is an octothorpe comment").unwrap(), - fully_consumed(ParsedComment { - comment_tag: '#', - comment: Cow::Borrowed("##### this is an octothorpe comment"), - }) + comment(b"###### this is an octothorpe comment").unwrap(), + fully_consumed(parsed_comment('#', "##### this is an octothorpe comment")), ); } } @@ -795,70 +785,71 @@ mod comments { #[cfg(test)] mod section_headers { use super::*; + use crate::test_util::section_header as parsed_section_header; #[test] fn no_subsection() { assert_eq!( - section_header("[hello]").unwrap(), - fully_consumed(gen_section_header("hello", None)), + section_header(b"[hello]").unwrap(), + fully_consumed(parsed_section_header("hello", None)), ); } #[test] fn modern_subsection() { assert_eq!( - section_header(r#"[hello "world"]"#).unwrap(), - fully_consumed(gen_section_header("hello", (" ", "world"))), + section_header(br#"[hello "world"]"#).unwrap(), + fully_consumed(parsed_section_header("hello", (" ", "world"))), ); } #[test] fn escaped_subsection() { assert_eq!( - section_header(r#"[hello "foo\\bar\""]"#).unwrap(), - fully_consumed(gen_section_header("hello", (" ", r#"foo\\bar\""#))), + section_header(br#"[hello "foo\\bar\""]"#).unwrap(), + fully_consumed(parsed_section_header("hello", (" ", r#"foo\\bar\""#))), ); } #[test] fn deprecated_subsection() { assert_eq!( - section_header(r#"[hello.world]"#).unwrap(), - fully_consumed(gen_section_header("hello", (".", "world"))) + section_header(br#"[hello.world]"#).unwrap(), + fully_consumed(parsed_section_header("hello", (".", "world"))) ); } #[test] fn empty_legacy_subsection_name() { assert_eq!( - section_header(r#"[hello.]"#).unwrap(), - fully_consumed(gen_section_header("hello", (".", ""))) + section_header(br#"[hello.]"#).unwrap(), + fully_consumed(parsed_section_header("hello", (".", ""))) ); } #[test] fn empty_modern_subsection_name() { assert_eq!( - section_header(r#"[hello ""]"#).unwrap(), - fully_consumed(gen_section_header("hello", (" ", ""))) + section_header(br#"[hello ""]"#).unwrap(), + fully_consumed(parsed_section_header("hello", (" ", ""))) ); } #[test] fn newline_in_header() { - assert!(section_header("[hello\n]").is_err()) + assert!(section_header(b"[hello\n]").is_err()) } #[test] fn null_byte_in_header() { - assert!(section_header("[hello\0]").is_err()) + assert!(section_header(b"[hello\0]").is_err()) } #[test] fn right_brace_in_subsection_name() { assert_eq!( - section_header(r#"[hello "]"]"#).unwrap(), - fully_consumed(gen_section_header("hello", (" ", "]"))) + section_header(br#"[hello "]"]"#).unwrap(), + fully_consumed(parsed_section_header("hello", (" ", "]"))) ); } } @@ -869,46 +860,50 @@ mod config_name { #[test] fn just_name() { - assert_eq!(config_name("name").unwrap(), fully_consumed("name")); + assert_eq!( + config_name(b"name").unwrap(), + fully_consumed("name".as_bytes()) + ); } #[test] fn must_start_with_alphabetic() { - assert!(config_name("4aaa").is_err()); - assert!(config_name("-aaa").is_err()); + assert!(config_name(b"4aaa").is_err()); + assert!(config_name(b"-aaa").is_err()); } #[test] fn cannot_be_empty() { - assert!(config_name("").is_err()) + assert!(config_name(b"").is_err()) } } #[cfg(test)] mod section_body { use super::*; + use crate::test_util::{value_event, whitespace_event}; #[test] fn whitespace_is_not_ambigious() { assert_eq!( - section_body("a =b").unwrap().1, + section_body(b"a =b").unwrap().1, ( - "a", + "a".as_bytes(), vec![ - Event::Whitespace(Cow::from(" ")), + whitespace_event(" "), Event::KeyValueSeparator, - Event::Value(Cow::from("b")) + value_event("b") ] ) ); assert_eq!( - section_body("a= b").unwrap().1, + section_body(b"a= b").unwrap().1, ( - "a", + "a".as_bytes(), vec![ Event::KeyValueSeparator, - Event::Whitespace(Cow::from(" ")), - Event::Value(Cow::from("b")) + whitespace_event(" "), + value_event("b") ] ) ); @@ -918,99 +913,95 @@ mod section_body { #[cfg(test)] mod value_no_continuation { use super::*; + use crate::test_util::value_event; #[test] fn no_comment() { assert_eq!( - value_impl("hello").unwrap(), - fully_consumed(vec![Event::Value(Cow::from("hello"))]) + value_impl(b"hello").unwrap(), + fully_consumed(vec![value_event("hello")]) ); } #[test] fn no_comment_newline() { assert_eq!( - value_impl("hello\na").unwrap(), - ("\na", vec![Event::Value(Cow::from("hello"))]) + value_impl(b"hello\na").unwrap(), + ("\na".as_bytes(), vec![value_event("hello")]) ) } #[test] fn semicolon_comment_not_consumed() { assert_eq!( - value_impl("hello;world").unwrap(), - (";world", vec![Event::Value(Cow::from("hello")),]) + value_impl(b"hello;world").unwrap(), + (";world".as_bytes(), vec![value_event("hello"),]) ); } #[test] fn octothorpe_comment_not_consumed() { assert_eq!( - value_impl("hello#world").unwrap(), - ("#world", vec![Event::Value(Cow::from("hello")),]) + value_impl(b"hello#world").unwrap(), + ("#world".as_bytes(), vec![value_event("hello"),]) ); } #[test] fn values_with_extraneous_whitespace_without_comment() { assert_eq!( - value_impl("hello ").unwrap(), - (" ", vec![Event::Value(Cow::from("hello"))]) + value_impl(b"hello ").unwrap(), + (" ".as_bytes(), vec![value_event("hello")]) ); } #[test] fn values_with_extraneous_whitespace_before_comment() { assert_eq!( - value_impl("hello #world").unwrap(), - ( - " #world", - vec![Event::Value(Cow::from("hello"))] - ) + value_impl(b"hello #world").unwrap(), + (" #world".as_bytes(), vec![value_event("hello")]) ); assert_eq!( - value_impl("hello ;world").unwrap(), - ( - " ;world", - vec![Event::Value(Cow::from("hello"))] - ) + value_impl(b"hello ;world").unwrap(), + (" ;world".as_bytes(), vec![value_event("hello")]) ); } #[test] fn trans_escaped_comment_marker_not_consumed() { assert_eq!( - value_impl(r##"hello"#"world; a"##).unwrap(), - ("; a", vec![Event::Value(Cow::from(r##"hello"#"world"##))]) + value_impl(br##"hello"#"world; a"##).unwrap(), + ("; a".as_bytes(), vec![value_event(r##"hello"#"world"##)]) ); } #[test] fn complex_test() { assert_eq!( - value_impl(r#"value";";ahhhh"#).unwrap(), - (";ahhhh", vec![Event::Value(Cow::from(r#"value";""#))]) + value_impl(br#"value";";ahhhh"#).unwrap(), + (";ahhhh".as_bytes(), vec![value_event(r#"value";""#)]) ); } #[test] fn garbage_after_continution_is_err() { - assert!(value_impl("hello \\afwjdls").is_err()); + assert!(value_impl(b"hello \\afwjdls").is_err()); } } #[cfg(test)] mod value_continuation { use super::*; + use crate::test_util::{newline_event, value_done_event, value_not_done_event}; #[test] fn simple_continuation() { assert_eq!( - value_impl("hello\\\nworld").unwrap(), + value_impl(b"hello\\\nworld").unwrap(), fully_consumed(vec![ - Event::ValueNotDone(Cow::from("hello")), - Event::Newline(Cow::from("\n")), - Event::ValueDone(Cow::from("world")) + value_not_done_event("hello"), + newline_event(), + value_done_event("world") ]) ); } @@ -1018,11 +1009,11 @@ mod value_continuation { #[test] fn continuation_with_whitespace() { assert_eq!( - value_impl("hello\\\n world").unwrap(), + value_impl(b"hello\\\n world").unwrap(), fully_consumed(vec![ - Event::ValueNotDone(Cow::from("hello")), - Event::Newline(Cow::from("\n")), - Event::ValueDone(Cow::from(" world")) + value_not_done_event("hello"), + newline_event(), + value_done_event(" world") ]) ) } @@ -1030,15 +1021,15 @@ mod value_continuation { #[test] fn complex_continuation_with_leftover_comment() { assert_eq!( - value_impl("1 \"\\\"\\\na ; e \"\\\"\\\nd # \"b\t ; c").unwrap(), + value_impl(b"1 \"\\\"\\\na ; e \"\\\"\\\nd # \"b\t ; c").unwrap(), ( - " # \"b\t ; c", + " # \"b\t ; c".as_bytes(), vec![ - Event::ValueNotDone(Cow::from(r#"1 "\""#)), - Event::Newline(Cow::from("\n")), - Event::ValueNotDone(Cow::from(r#"a ; e "\""#)), - Event::Newline(Cow::from("\n")), - Event::ValueDone(Cow::from("d")), + value_not_done_event(r#"1 "\""#), + newline_event(), + value_not_done_event(r#"a ; e "\""#), + newline_event(), + value_done_event("d") ] ) ); @@ -1047,13 +1038,13 @@ mod value_continuation { #[test] fn quote_split_over_two_lines_with_leftover_comment() { assert_eq!( - value_impl("\"\\\n;\";a").unwrap(), + value_impl(b"\"\\\n;\";a").unwrap(), ( - ";a", + ";a".as_bytes(), vec![ - Event::ValueNotDone(Cow::from("\"")), - Event::Newline(Cow::from("\n")), - Event::ValueDone(Cow::from(";\"")), + value_not_done_event("\""), + newline_event(), + value_done_event(";\"") ] ) ) @@ -1063,13 +1054,17 @@ mod value_continuation { #[cfg(test)] mod section { use super::*; + use crate::test_util::{ + comment_event, name_event, newline_event, section_header as parsed_section_header, + value_done_event, value_event, value_not_done_event, whitespace_event, + }; #[test] fn empty_section() { assert_eq!( - section("[test]").unwrap(), + section(b"[test]").unwrap(), fully_consumed(ParsedSection { - section_header: gen_section_header("test", None), + section_header: parsed_section_header("test", None), events: vec![] }) ); @@ -1077,33 +1072,33 @@ mod section { #[test] fn simple_section() { - let section_data = r#"[hello] + let section_data = br#"[hello] a = b c d = "lol""#; assert_eq!( section(section_data).unwrap(), fully_consumed(ParsedSection { - section_header: gen_section_header("hello", None), + section_header: parsed_section_header("hello", None), events: vec![ - Event::Newline(Cow::from("\n")), - Event::Whitespace(Cow::from(" ")), - Event::Key(Cow::from("a")), - Event::Whitespace(Cow::from(" ")), + newline_event(), + whitespace_event(" "), + name_event("a"), + whitespace_event(" "), Event::KeyValueSeparator, - Event::Whitespace(Cow::Borrowed(" ")), - Event::Value(Cow::Borrowed("b")), - Event::Newline(Cow::Borrowed("\n")), - Event::Whitespace(Cow::Borrowed(" ")), - Event::Key(Cow::Borrowed("c")), - Event::Value(Cow::Borrowed("")), - Event::Newline(Cow::Borrowed("\n")), - Event::Whitespace(Cow::Borrowed(" ")), - Event::Key(Cow::Borrowed("d")), - Event::Whitespace(Cow::Borrowed(" ")), + whitespace_event(" "), + value_event("b"), + newline_event(), + whitespace_event(" "), + name_event("c"), + value_event(""), + newline_event(), + whitespace_event(" "), + name_event("d"), + whitespace_event(" "), Event::KeyValueSeparator, - Event::Whitespace(Cow::Borrowed(" ")), - Event::Value(Cow::Borrowed("\"lol\"")) + whitespace_event(" "), + value_event("\"lol\"") ] }) ) @@ -1112,21 +1107,17 @@ mod section { #[test] fn section_single_line() { assert_eq!( - section("[hello] c").unwrap(), + section(b"[hello] c").unwrap(), fully_consumed(ParsedSection { - section_header: gen_section_header("hello", None), - events: vec![ - Event::Whitespace(Cow::Borrowed(" ")), - Event::Key(Cow::Borrowed("c")), - Event::Value(Cow::Borrowed("")) - ] + section_header: parsed_section_header("hello", None), + events: vec![whitespace_event(" "), name_event("c"), value_event("")] }) ); } #[test] fn section_very_commented() { - let section_data = r#"[hello] ; commentA + let section_data = br#"[hello] ; commentA a = b # commentB ; commentC ; commentD @@ -1134,44 +1125,32 @@ mod section { assert_eq!( section(section_data).unwrap(), fully_consumed(ParsedSection { - section_header: gen_section_header("hello", None), + section_header: parsed_section_header("hello", None), events: vec![ - Event::Whitespace(Cow::Borrowed(" ")), - Event::Comment(ParsedComment { - comment_tag: ';', - comment: Cow::Borrowed(" commentA"), - }), - Event::Newline(Cow::Borrowed("\n")), - Event::Whitespace(Cow::Borrowed(" ")), - Event::Key(Cow::Borrowed("a")), - Event::Whitespace(Cow::Borrowed(" ")), + whitespace_event(" "), + comment_event(';', " commentA"), + newline_event(), + whitespace_event(" "), + name_event("a"), + whitespace_event(" "), Event::KeyValueSeparator, - Event::Whitespace(Cow::Borrowed(" ")), - Event::Value(Cow::Borrowed("b")), - Event::Whitespace(Cow::Borrowed(" ")), - Event::Comment(ParsedComment { - comment_tag: '#', - comment: Cow::Borrowed(" commentB"), - }), - Event::Newline(Cow::Borrowed("\n")), - Event::Whitespace(Cow::Borrowed(" ")), - Event::Comment(ParsedComment { - comment_tag: ';', - comment: Cow::Borrowed(" commentC"), - }), - Event::Newline(Cow::Borrowed("\n")), - Event::Whitespace(Cow::Borrowed(" ")), - Event::Comment(ParsedComment { - comment_tag: ';', - comment: Cow::Borrowed(" commentD"), - }), - Event::Newline(Cow::Borrowed("\n")), - Event::Whitespace(Cow::Borrowed(" ")), - Event::Key(Cow::Borrowed("c")), - Event::Whitespace(Cow::Borrowed(" ")), + whitespace_event(" "), + value_event("b"), + whitespace_event(" "), + comment_event('#', " commentB"), + newline_event(), + whitespace_event(" "), + comment_event(';', " commentC"), + newline_event(), + whitespace_event(" "), + comment_event(';', " commentD"), + newline_event(), + whitespace_event(" "), + name_event("c"), + whitespace_event(" "), Event::KeyValueSeparator, - Event::Whitespace(Cow::Borrowed(" ")), - Event::Value(Cow::Borrowed("d")), + whitespace_event(" "), + value_event("d"), ] }) ); @@ -1181,25 +1160,22 @@ mod section { fn complex_continuation() { // This test is absolute hell. Good luck if this fails. assert_eq!( - section("[section] a = 1 \"\\\"\\\na ; e \"\\\"\\\nd # \"b\t ; c").unwrap(), + section(b"[section] a = 1 \"\\\"\\\na ; e \"\\\"\\\nd # \"b\t ; c").unwrap(), fully_consumed(ParsedSection { - section_header: gen_section_header("section", None), + section_header: parsed_section_header("section", None), events: vec![ - Event::Whitespace(Cow::Borrowed(" ")), - Event::Key(Cow::Borrowed("a")), - Event::Whitespace(Cow::Borrowed(" ")), + whitespace_event(" "), + name_event("a"), + whitespace_event(" "), Event::KeyValueSeparator, - Event::Whitespace(Cow::Borrowed(" ")), - Event::ValueNotDone(Cow::Borrowed(r#"1 "\""#)), - Event::Newline(Cow::Borrowed("\n")), - Event::ValueNotDone(Cow::Borrowed(r#"a ; e "\""#)), - Event::Newline(Cow::Borrowed("\n")), - Event::ValueDone(Cow::Borrowed("d")), - Event::Whitespace(Cow::Borrowed(" ")), - Event::Comment(ParsedComment { - comment_tag: '#', - comment: Cow::Borrowed(" \"b\t ; c") - }) + whitespace_event(" "), + value_not_done_event(r#"1 "\""#), + newline_event(), + value_not_done_event(r#"a ; e "\""#), + newline_event(), + value_done_event("d"), + whitespace_event(" "), + comment_event('#', " \"b\t ; c"), ] }) ); @@ -1208,21 +1184,18 @@ mod section { #[test] fn quote_split_over_two_lines() { assert_eq!( - section("[section \"a\"] b =\"\\\n;\";a").unwrap(), + section(b"[section \"a\"] b =\"\\\n;\";a").unwrap(), fully_consumed(ParsedSection { - section_header: gen_section_header("section", (" ", "a")), + section_header: parsed_section_header("section", (" ", "a")), events: vec![ - Event::Whitespace(Cow::Borrowed(" ")), - Event::Key(Cow::Borrowed("b")), - Event::Whitespace(Cow::Borrowed(" ")), + whitespace_event(" "), + name_event("b"), + whitespace_event(" "), Event::KeyValueSeparator, - Event::ValueNotDone(Cow::Borrowed("\"")), - Event::Newline(Cow::Borrowed("\n")), - Event::ValueDone(Cow::Borrowed(";\"")), - Event::Comment(ParsedComment { - comment_tag: ';', - comment: Cow::Borrowed("a"), - }) + value_not_done_event("\""), + newline_event(), + value_done_event(";\""), + comment_event(';', "a"), ] }) ) @@ -1231,17 +1204,14 @@ mod section { #[test] fn section_handles_extranous_whitespace_before_comment() { assert_eq!( - section("[s]hello #world").unwrap(), + section(b"[s]hello #world").unwrap(), fully_consumed(ParsedSection { - section_header: gen_section_header("s", None), + section_header: parsed_section_header("s", None), events: vec![ - Event::Key(Cow::Borrowed("hello")), - Event::Whitespace(Cow::Borrowed(" ")), - Event::Value(Cow::Borrowed("")), - Event::Comment(ParsedComment { - comment_tag: '#', - comment: Cow::Borrowed("world"), - }), + name_event("hello"), + whitespace_event(" "), + value_event(""), + comment_event('#', "world"), ] }) ); diff --git a/src/test_util.rs b/src/test_util.rs new file mode 100644 index 0000000..cdcf1ca --- /dev/null +++ b/src/test_util.rs @@ -0,0 +1,73 @@ +use std::borrow::Cow; + +use crate::parser::{Event, ParsedComment, ParsedSectionHeader}; + +pub fn section_header_event( + name: &str, + subsection: impl Into>, +) -> Event<'_> { + Event::SectionHeader(section_header(name, subsection)) +} + +pub fn section_header( + name: &str, + subsection: impl Into>, +) -> ParsedSectionHeader<'_> { + let name = Cow::Borrowed(name.into()); + if let Some((separator, subsection_name)) = subsection.into() { + ParsedSectionHeader { + name, + separator: Some(Cow::Borrowed(separator.into())), + subsection_name: Some(Cow::Borrowed(subsection_name.into())), + } + } else { + ParsedSectionHeader { + name, + separator: None, + subsection_name: None, + } + } +} + +pub(crate) fn name_event(name: &'static str) -> Event<'static> { + Event::Key(Cow::Borrowed(name.into())) +} + +pub(crate) fn value_event(value: &'static str) -> Event<'static> { + Event::Value(Cow::Borrowed(value.into())) +} + +pub(crate) fn value_not_done_event(value: &'static str) -> Event<'static> { + Event::ValueNotDone(Cow::Borrowed(value.into())) +} + +pub(crate) fn value_done_event(value: &'static str) -> Event<'static> { + Event::ValueDone(Cow::Borrowed(value.into())) +} + +pub(crate) fn newline_event() -> Event<'static> { + newline_custom_event("\n") +} + +pub(crate) fn newline_custom_event(value: &'static str) -> Event<'static> { + Event::Newline(Cow::Borrowed(value.into())) +} + +pub(crate) fn whitespace_event(value: &'static str) -> Event<'static> { + Event::Whitespace(Cow::Borrowed(value.into())) +} + +pub(crate) fn separator_event() -> Event<'static> { + Event::KeyValueSeparator +} + +pub(crate) fn comment_event(tag: char, msg: &'static str) -> Event<'static> { + Event::Comment(comment(tag, msg)) +} + +pub(crate) fn comment(comment_tag: char, comment: &'static str) -> ParsedComment<'static> { + ParsedComment { + comment_tag, + comment: Cow::Borrowed(comment.into()), + } +} diff --git a/tests/parser_integration_tests.rs b/tests/parser_integration_tests.rs index 221aa91..c0aadb7 100644 --- a/tests/parser_integration_tests.rs +++ b/tests/parser_integration_tests.rs @@ -2,41 +2,51 @@ use std::borrow::Cow; use serde_git_config::parser::{parse_from_str, Event, ParsedSectionHeader}; -fn gen_section_header( +pub fn section_header_event( name: &str, subsection: impl Into>, ) -> Event<'_> { - let name = Cow::Borrowed(name); - Event::SectionHeader( - if let Some((separator, subsection_name)) = subsection.into() { - ParsedSectionHeader { - name, - separator: Some(Cow::Borrowed(separator)), - subsection_name: Some(Cow::Borrowed(subsection_name)), - } - } else { - ParsedSectionHeader { - name, - separator: None, - subsection_name: None, - } - }, - ) + Event::SectionHeader(section_header(name, subsection)) } + +pub fn section_header( + name: &str, + subsection: impl Into>, +) -> ParsedSectionHeader<'_> { + let name = Cow::Borrowed(name.into()); + if let Some((separator, subsection_name)) = subsection.into() { + ParsedSectionHeader { + name, + separator: Some(Cow::Borrowed(separator.into())), + subsection_name: Some(Cow::Borrowed(subsection_name.into())), + } + } else { + ParsedSectionHeader { + name, + separator: None, + subsection_name: None, + } + } +} + fn name(name: &'static str) -> Event<'static> { - Event::Key(Cow::Borrowed(name)) + Event::Key(Cow::Borrowed(name.into())) } fn value(value: &'static str) -> Event<'static> { - Event::Value(Cow::Borrowed(value)) + Event::Value(Cow::Borrowed(value.into())) } fn newline() -> Event<'static> { - Event::Newline(Cow::Borrowed("\n")) + newline_custom("\n") +} + +fn newline_custom(value: &'static str) -> Event<'static> { + Event::Newline(Cow::Borrowed(value.into())) } fn whitespace(value: &'static str) -> Event<'static> { - Event::Whitespace(Cow::Borrowed(value)) + Event::Whitespace(Cow::Borrowed(value.into())) } fn separator() -> Event<'static> { @@ -71,7 +81,7 @@ fn personal_config() { .unwrap() .into_vec(), vec![ - gen_section_header("user", None), + section_header_event("user", None), newline(), whitespace(" "), @@ -90,7 +100,7 @@ fn personal_config() { value("Foo Bar"), newline(), - gen_section_header("core", None), + section_header_event("core", None), newline(), whitespace(" "), @@ -101,7 +111,7 @@ fn personal_config() { value("input"), newline(), - gen_section_header("push", None), + section_header_event("push", None), newline(), whitespace(" "), @@ -112,7 +122,7 @@ fn personal_config() { value("simple"), newline(), - gen_section_header("commit", None), + section_header_event("commit", None), newline(), whitespace(" "), @@ -123,7 +133,7 @@ fn personal_config() { value("true"), newline(), - gen_section_header("gpg", None), + section_header_event("gpg", None), newline(), whitespace(" "), @@ -134,7 +144,7 @@ fn personal_config() { value("gpg"), newline(), - gen_section_header("url", (" ", "ssh://git@github.com/")), + section_header_event("url", (" ", "ssh://git@github.com/")), newline(), whitespace(" "), @@ -145,7 +155,7 @@ fn personal_config() { value("\"github://\""), newline(), - gen_section_header("url", (" ", "ssh://git@git.eddie.sh/edward/")), + section_header_event("url", (" ", "ssh://git@git.eddie.sh/edward/")), newline(), whitespace(" "), @@ -156,7 +166,7 @@ fn personal_config() { value("\"gitea://\""), newline(), - gen_section_header("pull", None), + section_header_event("pull", None), newline(), whitespace(" "), @@ -167,7 +177,7 @@ fn personal_config() { value("only"), newline(), - gen_section_header("init", None), + section_header_event("init", None), newline(), whitespace(" "), @@ -203,6 +213,6 @@ fn parse_whitespace() { fn newline_events_are_merged() { assert_eq!( parse_from_str("\n\n\n\n\n").unwrap().into_vec(), - vec![Event::Newline("\n\n\n\n\n".into())] + vec![newline_custom("\n\n\n\n\n")] ); }