diff --git a/src/config.rs b/src/config.rs index e7bf53d..4dca16a 100644 --- a/src/config.rs +++ b/src/config.rs @@ -1,6 +1,7 @@ -use std::collections::HashMap; - use crate::parser::{parse_from_str, Event, Parser, ParserError}; +use serde::Serialize; +use std::borrow::Cow; +use std::collections::{HashMap, VecDeque}; #[derive(Debug, PartialEq, Eq)] pub enum GitConfigError<'a> { @@ -12,25 +13,24 @@ pub enum GitConfigError<'a> { KeyDoesNotExist(&'a str), } -// TODO: Use linked list for section ordering? - /// High level `git-config` reader and writer. pub struct GitConfig<'a> { /// The list of events that occur before an actual section. Since 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<&'a str, Vec>>, + section_lookup_tree: HashMap, Vec>>, /// SectionId to section mapping. The value of this HashMap contains actual /// events sections: HashMap>>, - section_header_separators: HashMap>, + section_header_separators: HashMap>>, section_id_counter: usize, + section_order: VecDeque, } -/// The section ID is a monotonically increasing ID used to refer to sections, -/// and implies ordering between sections in the original `git-config` file, -/// such that a section with id 0 always is before a section with id 1. +/// The section ID is a monotonically increasing ID used to refer to sections. +/// This value does not imply any ordering between sections, as new sections +/// with higher section IDs may be in between lower ID sections. /// /// We need to use a section id because `git-config` permits sections with /// identical names. As a result, we can't simply use the section name as a key @@ -45,7 +45,7 @@ struct SectionId(usize); #[derive(Debug, PartialEq, Eq)] enum LookupTreeNode<'a> { Terminal(Vec), - NonTerminal(HashMap<&'a str, Vec>), + NonTerminal(HashMap, Vec>), } impl<'a> GitConfig<'a> { @@ -62,19 +62,20 @@ impl<'a> GitConfig<'a> { section_lookup_tree: HashMap::new(), section_header_separators: HashMap::new(), section_id_counter: 0, + section_order: VecDeque::new(), }; // Current section that we're building - let mut current_section_name: Option<&str> = None; - let mut current_subsection_name: Option<&str> = 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() { match event { Event::SectionHeader(header) => { new_self.push_section( - &mut current_section_name, - &mut current_subsection_name, + current_section_name, + current_subsection_name, &mut maybe_section, ); @@ -109,8 +110,8 @@ impl<'a> GitConfig<'a> { // The last section doesn't get pushed since we only push if there's a // new section header, so we need to call push one more time. new_self.push_section( - &mut current_section_name, - &mut current_subsection_name, + current_section_name, + current_subsection_name, &mut maybe_section, ); @@ -119,12 +120,12 @@ impl<'a> GitConfig<'a> { fn push_section( &mut self, - current_section_name: &mut Option<&'a str>, - current_subsection_name: &mut Option<&'a str>, + current_section_name: Option>, + current_subsection_name: Option>, maybe_section: &mut Option>>, ) { - let new_section_id = SectionId(self.section_id_counter); if let Some(section) = maybe_section.take() { + let new_section_id = SectionId(self.section_id_counter); self.sections.insert(new_section_id, section); let lookup = self .section_lookup_tree @@ -137,7 +138,10 @@ impl<'a> GitConfig<'a> { if let LookupTreeNode::NonTerminal(subsection) = node { found_node = true; subsection - .entry(subsection_name) + // Despite the clone `push_section` is always called + // with a Cow::Borrowed, so this is effectively a + // copy. + .entry(subsection_name.clone()) .or_default() .push(new_section_id); break; @@ -145,7 +149,7 @@ impl<'a> GitConfig<'a> { } if !found_node { let mut map = HashMap::new(); - map.insert(*subsection_name, vec![new_section_id]); + map.insert(subsection_name, vec![new_section_id]); lookup.push(LookupTreeNode::NonTerminal(map)); } } else { @@ -160,6 +164,7 @@ impl<'a> GitConfig<'a> { lookup.push(LookupTreeNode::Terminal(vec![new_section_id])) } } + self.section_order.push_back(new_section_id); self.section_id_counter += 1; } } @@ -186,8 +191,9 @@ 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("d")); + /// assert_eq!(git_config.get_raw_value("core", None, "a"), Ok(&Cow::from("d"))); /// ``` /// /// Consider [`Self::get_raw_multi_value`] if you want to get all values for @@ -202,7 +208,7 @@ impl<'a> GitConfig<'a> { section_name: &'b str, subsection_name: Option<&'b str>, key: &'b str, - ) -> Result<&'a str, GitConfigError<'b>> { + ) -> Result<&Cow<'a, str>, GitConfigError<'b>> { // 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`). @@ -218,7 +224,7 @@ impl<'a> GitConfig<'a> { Event::Key(event_key) if *event_key == key => found_key = true, Event::Value(v) if found_key => { found_key = false; - latest_value = Some(*v); + latest_value = Some(v); } _ => (), } @@ -249,11 +255,12 @@ impl<'a> GitConfig<'a> { /// /// ``` /// # use serde_git_config::config::{GitConfig, GitConfigError}; + /// # use std::borrow::Cow; /// # 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 "d"); - /// *mut_value = "hello"; - /// assert_eq!(git_config.get_raw_value("core", None, "a"), Ok("hello")); + /// 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"))); /// # Ok::<(), GitConfigError>(()) /// ``` /// @@ -269,7 +276,7 @@ impl<'a> GitConfig<'a> { section_name: &'b str, subsection_name: Option<&'b str>, key: &'b str, - ) -> Result<&mut &'a str, GitConfigError<'b>> { + ) -> Result<&mut Cow<'a, str>, GitConfigError<'b>> { // 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`). @@ -322,8 +329,12 @@ 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_multi_value("core", None, "a"), Ok(vec!["b", "c", "d"])); + /// assert_eq!( + /// git_config.get_raw_multi_value("core", None, "a"), + /// Ok(vec![&Cow::from("b"), &Cow::from("c"), &Cow::from("d")]), + /// ); /// ``` /// /// Consider [`Self::get_raw_value`] if you want to get the resolved single @@ -339,7 +350,7 @@ impl<'a> GitConfig<'a> { section_name: &'b str, subsection_name: Option<&'b str>, key: &'b str, - ) -> Result, GitConfigError<'b>> { + ) -> Result>, GitConfigError<'b>> { let mut values = vec![]; for section_id in self.get_section_ids_by_name_and_subname(section_name, subsection_name)? { let mut found_key = false; @@ -349,7 +360,7 @@ impl<'a> GitConfig<'a> { match event { Event::Key(event_key) if *event_key == key => found_key = true, Event::Value(v) if found_key => { - values.push(*v); + values.push(v); found_key = false; } _ => (), @@ -379,18 +390,25 @@ impl<'a> GitConfig<'a> { /// /// ``` /// # use serde_git_config::config::{GitConfig, GitConfigError}; + /// # use std::borrow::Cow; /// # 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"]); /// for value in git_config.get_raw_multi_value_mut("core", None, "a")? { - /// *value = "g"; + /// *value = Cow::from("g"); ///} - /// assert_eq!(git_config.get_raw_multi_value("core", None, "a")?, vec!["g", "g", "g"]); + /// assert_eq!( + /// git_config.get_raw_multi_value("core", None, "a")?, + /// vec![&Cow::from("g"), &Cow::from("g"), &Cow::from("g")], + /// ); /// # Ok::<(), GitConfigError>(()) /// ``` /// /// Consider [`Self::get_raw_value`] if you want to get the resolved single /// value for a given key, if your key does not support multi-valued values. /// + /// Note that this operation is relatively expensive, requiring a full + /// traversal of the config. + /// /// # Errors /// /// This function will return an error if the key is not in any requested @@ -401,12 +419,12 @@ impl<'a> GitConfig<'a> { section_name: &'b str, subsection_name: Option<&'b str>, key: &'b str, - ) -> Result, GitConfigError<'b>> { + ) -> Result>, GitConfigError<'b>> { let section_ids = self .get_section_ids_by_name_and_subname(section_name, subsection_name)? .to_vec(); let mut found_key = false; - let values: Vec<&mut &'a str> = self + let values: Vec<&mut Cow<'a, str>> = self .sections .iter_mut() .filter_map(|(k, v)| { @@ -469,6 +487,18 @@ impl<'a> GitConfig<'a> { .map(Vec::as_slice) .ok_or(GitConfigError::SubSectionDoesNotExist(subsection_name)) } + + pub fn set_raw_value<'b>( + &mut self, + section_name: &'b str, + subsection_name: Option<&'b str>, + key: &'b str, + new_value: impl Into>, + ) -> Result<(), GitConfigError<'b>> { + let value = self.get_raw_value_mut(section_name, subsection_name, key)?; + *value = new_value.into(); + Ok(()) + } } #[cfg(test)] @@ -482,11 +512,12 @@ mod from_parser { assert_eq!(config.section_id_counter, 0); assert!(config.section_lookup_tree.is_empty()); assert!(config.sections.is_empty()); + assert!(config.section_order.is_empty()); } #[test] fn parse_single_section() { - let config = GitConfig::from_str("[core]\na=b\nc=d").unwrap(); + let mut config = GitConfig::from_str("[core]\na=b\nc=d").unwrap(); let expected_separators = { let mut map = HashMap::new(); map.insert(SectionId(0), None); @@ -496,7 +527,10 @@ mod from_parser { assert_eq!(config.section_id_counter, 1); let expected_lookup_tree = { let mut tree = HashMap::new(); - tree.insert("core", vec![LookupTreeNode::Terminal(vec![SectionId(0)])]); + tree.insert( + Cow::Borrowed("core"), + vec![LookupTreeNode::Terminal(vec![SectionId(0)])], + ); tree }; assert_eq!(config.section_lookup_tree, expected_lookup_tree); @@ -505,25 +539,26 @@ mod from_parser { sections.insert( SectionId(0), vec![ - Event::Newline("\n"), - Event::Key("a"), - Event::Value("b"), - Event::Newline("\n"), - Event::Key("c"), - Event::Value("d"), + Event::Newline(Cow::Borrowed("\n")), + Event::Key(Cow::Borrowed("a")), + Event::Value(Cow::Borrowed("b")), + Event::Newline(Cow::Borrowed("\n")), + Event::Key(Cow::Borrowed("c")), + Event::Value(Cow::Borrowed("d")), ], ); sections }; assert_eq!(config.sections, expected_sections); + assert_eq!(config.section_order.make_contiguous(), &[SectionId(0)]); } #[test] fn parse_single_subsection() { - let config = GitConfig::from_str("[core.subsec]\na=b\nc=d").unwrap(); + 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), Some(".")); + map.insert(SectionId(0), Some(Cow::Borrowed("."))); map }; assert_eq!(config.section_header_separators, expected_separators); @@ -531,8 +566,11 @@ mod from_parser { let expected_lookup_tree = { let mut tree = HashMap::new(); let mut inner_tree = HashMap::new(); - inner_tree.insert("subsec", vec![SectionId(0)]); - tree.insert("core", vec![LookupTreeNode::NonTerminal(inner_tree)]); + inner_tree.insert(Cow::Borrowed("subsec"), vec![SectionId(0)]); + tree.insert( + Cow::Borrowed("core"), + vec![LookupTreeNode::NonTerminal(inner_tree)], + ); tree }; assert_eq!(config.section_lookup_tree, expected_lookup_tree); @@ -541,60 +579,23 @@ mod from_parser { sections.insert( SectionId(0), vec![ - Event::Newline("\n"), - Event::Key("a"), - Event::Value("b"), - Event::Newline("\n"), - Event::Key("c"), - Event::Value("d"), + Event::Newline(Cow::Borrowed("\n")), + Event::Key(Cow::Borrowed("a")), + Event::Value(Cow::Borrowed("b")), + Event::Newline(Cow::Borrowed("\n")), + Event::Key(Cow::Borrowed("c")), + Event::Value(Cow::Borrowed("d")), ], ); sections }; assert_eq!(config.sections, expected_sections); + assert_eq!(config.section_order.make_contiguous(), &[SectionId(0)]); } #[test] fn parse_multiple_sections() { - let 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), None); - map.insert(SectionId(1), None); - map - }; - assert_eq!(config.section_header_separators, expected_separators); - assert_eq!(config.section_id_counter, 2); - let expected_lookup_tree = { - let mut tree = HashMap::new(); - tree.insert("core", vec![LookupTreeNode::Terminal(vec![SectionId(0)])]); - tree.insert("other", vec![LookupTreeNode::Terminal(vec![SectionId(1)])]); - tree - }; - assert_eq!(config.section_lookup_tree, expected_lookup_tree); - let expected_sections = { - let mut sections = HashMap::new(); - sections.insert( - SectionId(0), - vec![ - Event::Newline("\n"), - Event::Key("a"), - Event::Value("b"), - Event::Newline("\n"), - Event::Key("c"), - Event::Value("d"), - Event::Newline("\n"), - ], - ); - sections.insert(SectionId(1), vec![Event::Key("e"), Event::Value("f")]); - sections - }; - assert_eq!(config.sections, expected_sections); - } - - #[test] - fn parse_multiple_duplicate_sections() { - let config = GitConfig::from_str("[core]\na=b\nc=d\n[core]e=f").unwrap(); + 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), None); @@ -606,7 +607,61 @@ mod from_parser { let expected_lookup_tree = { let mut tree = HashMap::new(); tree.insert( - "core", + Cow::Borrowed("core"), + vec![LookupTreeNode::Terminal(vec![SectionId(0)])], + ); + tree.insert( + Cow::Borrowed("other"), + vec![LookupTreeNode::Terminal(vec![SectionId(1)])], + ); + tree + }; + assert_eq!(config.section_lookup_tree, expected_lookup_tree); + let expected_sections = { + let mut sections = HashMap::new(); + sections.insert( + SectionId(0), + vec![ + Event::Newline(Cow::Borrowed("\n")), + Event::Key(Cow::Borrowed("a")), + Event::Value(Cow::Borrowed("b")), + Event::Newline(Cow::Borrowed("\n")), + Event::Key(Cow::Borrowed("c")), + Event::Value(Cow::Borrowed("d")), + Event::Newline(Cow::Borrowed("\n")), + ], + ); + sections.insert( + SectionId(1), + vec![ + Event::Key(Cow::Borrowed("e")), + Event::Value(Cow::Borrowed("f")), + ], + ); + sections + }; + assert_eq!(config.sections, expected_sections); + assert_eq!( + config.section_order.make_contiguous(), + &[SectionId(0), SectionId(1)] + ); + } + + #[test] + fn parse_multiple_duplicate_sections() { + 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), None); + map.insert(SectionId(1), None); + map + }; + assert_eq!(config.section_header_separators, expected_separators); + assert_eq!(config.section_id_counter, 2); + let expected_lookup_tree = { + let mut tree = HashMap::new(); + tree.insert( + Cow::Borrowed("core"), vec![LookupTreeNode::Terminal(vec![SectionId(0), SectionId(1)])], ); tree @@ -617,19 +672,29 @@ mod from_parser { sections.insert( SectionId(0), vec![ - Event::Newline("\n"), - Event::Key("a"), - Event::Value("b"), - Event::Newline("\n"), - Event::Key("c"), - Event::Value("d"), - Event::Newline("\n"), + Event::Newline(Cow::Borrowed("\n")), + Event::Key(Cow::Borrowed("a")), + Event::Value(Cow::Borrowed("b")), + Event::Newline(Cow::Borrowed("\n")), + Event::Key(Cow::Borrowed("c")), + Event::Value(Cow::Borrowed("d")), + Event::Newline(Cow::Borrowed("\n")), + ], + ); + sections.insert( + SectionId(1), + vec![ + Event::Key(Cow::Borrowed("e")), + Event::Value(Cow::Borrowed("f")), ], ); - sections.insert(SectionId(1), vec![Event::Key("e"), Event::Value("f")]); sections }; assert_eq!(config.sections, expected_sections); + assert_eq!( + config.section_order.make_contiguous(), + &[SectionId(0), SectionId(1)] + ); } } @@ -640,20 +705,32 @@ mod get_raw_value { #[test] fn single_section() { let config = GitConfig::from_str("[core]\na=b\nc=d").unwrap(); - assert_eq!(config.get_raw_value("core", None, "a"), Ok("b")); - assert_eq!(config.get_raw_value("core", None, "c"), Ok("d")); + assert_eq!( + config.get_raw_value("core", None, "a"), + Ok(&Cow::Borrowed("b")) + ); + assert_eq!( + config.get_raw_value("core", None, "c"), + Ok(&Cow::Borrowed("d")) + ); } #[test] fn last_one_wins_respected_in_section() { let config = GitConfig::from_str("[core]\na=b\na=d").unwrap(); - assert_eq!(config.get_raw_value("core", None, "a"), Ok("d")); + assert_eq!( + config.get_raw_value("core", None, "a"), + Ok(&Cow::Borrowed("d")) + ); } #[test] fn last_one_wins_respected_across_section() { let config = GitConfig::from_str("[core]\na=b\n[core]\na=d").unwrap(); - assert_eq!(config.get_raw_value("core", None, "a"), Ok("d")); + assert_eq!( + config.get_raw_value("core", None, "a"), + Ok(&Cow::Borrowed("d")) + ); } #[test] @@ -686,8 +763,14 @@ mod get_raw_value { #[test] fn subsection_must_be_respected() { let config = GitConfig::from_str("[core]a=b\n[core.a]a=c").unwrap(); - assert_eq!(config.get_raw_value("core", None, "a"), Ok("b")); - assert_eq!(config.get_raw_value("core", Some("a"), "a"), Ok("c")); + assert_eq!( + config.get_raw_value("core", None, "a"), + Ok(&Cow::Borrowed("b")) + ); + assert_eq!( + config.get_raw_value("core", Some("a"), "a"), + Ok(&Cow::Borrowed("c")) + ); } } diff --git a/src/lib.rs b/src/lib.rs index ae2ce2a..a967901 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,5 +1,3 @@ -#![forbid(unsafe_code)] - // mod de; pub mod config; mod error; diff --git a/src/parser.rs b/src/parser.rs index 92f6fc9..37267bd 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -17,7 +17,7 @@ use nom::error::{Error as NomError, ErrorKind}; use nom::sequence::delimited; use nom::IResult; use nom::{branch::alt, multi::many0}; -use std::iter::FusedIterator; +use std::{borrow::Cow, iter::FusedIterator}; /// Syntactic events that occurs in the config. #[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)] @@ -30,25 +30,25 @@ pub enum Event<'a> { /// exists. SectionHeader(ParsedSectionHeader<'a>), /// A name to a value in a section. - Key(&'a str), + Key(Cow<'a, str>), /// 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(&'a str), + Value(Cow<'a, str>), /// 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. - Newline(&'a str), + Newline(Cow<'a, str>), /// 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(&'a str), + ValueNotDone(Cow<'a, str>), /// The last line of a value which was continued onto another line. - ValueDone(&'a str), + ValueDone(Cow<'a, str>), /// A continuous section of insignificant whitespace. Values with internal /// spaces will not be separated by this event. - Whitespace(&'a str), + Whitespace(Cow<'a, str>), } /// A parsed section containing the header and the section events. @@ -61,27 +61,27 @@ pub struct ParsedSection<'a> { } /// A parsed section header, containing a name and optionally a subsection name. -#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug, Default)] +#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug, Default)] pub struct ParsedSectionHeader<'a> { /// The name of the header. - pub name: &'a str, + pub name: Cow<'a, str>, /// 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<&'a str>, + pub separator: Option>, /// The subsection name without quotes if any exist. - pub subsection_name: Option<&'a str>, + pub subsection_name: Option>, } /// A parsed comment event containing the comment marker and comment. -#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug, Default)] +#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug, Default)] pub struct ParsedComment<'a> { /// The comment marker used. This is either a semicolon or octothorpe. pub comment_tag: char, /// The parsed comment. - pub comment: &'a str, + pub comment: Cow<'a, str>, } /// The various parsing failure reasons. @@ -176,20 +176,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: "core", +/// # name: Cow::Borrowed("core"), /// # 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("\n"), -/// Event::Whitespace(" "), -/// Event::Key("autocrlf"), -/// Event::Whitespace(" "), -/// Event::Whitespace(" "), -/// Event::Value("input"), +/// Event::Newline(Cow::Borrowed("\n")), +/// Event::Whitespace(Cow::Borrowed(" ")), +/// Event::Key(Cow::Borrowed("autocrlf")), +/// Event::Whitespace(Cow::Borrowed(" ")), +/// Event::Whitespace(Cow::Borrowed(" ")), +/// Event::Value(Cow::Borrowed("input")), /// # ]); /// ``` /// @@ -216,20 +217,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: "core", +/// # name: Cow::Borrowed("core"), /// # 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("\n"), -/// Event::Key("autocrlf"), -/// Event::Value(r#"true"""#), -/// Event::Newline("\n"), -/// Event::Key("filemode"), -/// Event::Value(r#"fa"lse""#), +/// Event::Newline(Cow::Borrowed("\n")), +/// Event::Key(Cow::Borrowed("autocrlf")), +/// Event::Value(Cow::Borrowed(r#"true"""#)), +/// Event::Newline(Cow::Borrowed("\n")), +/// Event::Key(Cow::Borrowed("filemode")), +/// Event::Value(Cow::Borrowed(r#"fa"lse""#)), /// # ]); /// ``` /// @@ -250,19 +252,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: "some-section", +/// # name: Cow::Borrowed("some-section"), /// # 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("\n"), -/// Event::Key("file"), -/// Event::ValueNotDone("a"), -/// Event::Newline("\n"), -/// Event::ValueDone(" c"), +/// Event::Newline(Cow::Borrowed("\n")), +/// Event::Key(Cow::Borrowed("file")), +/// Event::ValueNotDone(Cow::Borrowed("a")), +/// Event::Newline(Cow::Borrowed("\n")), +/// Event::ValueDone(Cow::Borrowed(" c")), /// # ]); /// ``` /// @@ -380,7 +383,7 @@ fn comment<'a>(i: &'a str) -> IResult<&'a str, ParsedComment<'a>> { i, ParsedComment { comment_tag, - comment, + comment: Cow::Borrowed(comment), }, )) } @@ -388,10 +391,14 @@ fn comment<'a>(i: &'a str) -> IResult<&'a str, ParsedComment<'a>> { fn section<'a>(i: &'a str) -> IResult<&'a str, ParsedSection<'a>> { let (i, section_header) = section_header(i)?; let (i, items) = many0(alt(( - map(take_spaces, |space| vec![Event::Whitespace(space)]), - map(take_newline, |newline| vec![Event::Newline(newline)]), + map(take_spaces, |space| { + vec![Event::Whitespace(Cow::Borrowed(space))] + }), + map(take_newline, |newline| { + vec![Event::Newline(Cow::Borrowed(newline))] + }), map(section_body, |(key, values)| { - let mut vec = vec![Event::Key(key)]; + let mut vec = vec![Event::Key(Cow::Borrowed(key))]; vec.extend(values); vec }), @@ -416,12 +423,12 @@ fn section_header<'a>(i: &'a str) -> IResult<&'a str, ParsedSectionHeader<'a>> { // subsection syntax at this point. let header = match name.rfind('.') { Some(index) => ParsedSectionHeader { - name: &name[..index], - separator: name.get(index..index + 1), - subsection_name: name.get(index + 1..), + name: Cow::Borrowed(&name[..index]), + separator: name.get(index..index + 1).map(Cow::Borrowed), + subsection_name: name.get(index + 1..).map(Cow::Borrowed), }, None => ParsedSectionHeader { - name: name, + name: Cow::Borrowed(name), separator: None, subsection_name: None, }, @@ -443,11 +450,11 @@ fn section_header<'a>(i: &'a str) -> IResult<&'a str, ParsedSectionHeader<'a>> { Ok(( i, ParsedSectionHeader { - name: name, - separator: Some(whitespace), + name: Cow::Borrowed(name), + separator: Some(Cow::Borrowed(whitespace)), // 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("")), + subsection_name: subsection_name.or(Some("")).map(Cow::Borrowed), }, )) } @@ -458,7 +465,7 @@ fn section_body<'a>(i: &'a str) -> IResult<&'a str, (&'a str, Vec>)> { let (i, whitespace) = opt(take_spaces)(i)?; let (i, value) = config_value(i)?; if let Some(whitespace) = whitespace { - let mut events = vec![Event::Whitespace(whitespace)]; + let mut events = vec![Event::Whitespace(Cow::Borrowed(whitespace))]; events.extend(value); Ok((i, (name, events))) } else { @@ -491,14 +498,14 @@ fn config_value<'a>(i: &'a str) -> IResult<&'a str, Vec>> { let (i, whitespace) = opt(take_spaces)(i)?; let (i, values) = value_impl(i)?; if let Some(whitespace) = whitespace { - let mut events = vec![Event::Whitespace(whitespace)]; + let mut events = vec![Event::Whitespace(Cow::Borrowed(whitespace))]; events.extend(values); Ok((i, events)) } else { Ok((i, values)) } } else { - Ok((i, vec![Event::Value("")])) + Ok((i, vec![Event::Value(Cow::Borrowed(""))])) } } @@ -521,8 +528,8 @@ fn value_impl<'a>(i: &'a str) -> IResult<&'a str, Vec>> { // continuation. b'\n' => { partial_value_found = true; - events.push(Event::ValueNotDone(&i[offset..index - 1])); - events.push(Event::Newline(&i[index..index + 1])); + events.push(Event::ValueNotDone(Cow::Borrowed(&i[offset..index - 1]))); + events.push(Event::Newline(Cow::Borrowed(&i[index..index + 1]))); offset = index + 1; parsed_index = 0; } @@ -583,9 +590,9 @@ fn value_impl<'a>(i: &'a str) -> IResult<&'a str, Vec>> { }; if partial_value_found { - events.push(Event::ValueDone(remainder_value)); + events.push(Event::ValueDone(Cow::Borrowed(remainder_value))); } else { - events.push(Event::Value(remainder_value)); + events.push(Event::Value(Cow::Borrowed(remainder_value))); } Ok((i, events)) @@ -625,11 +632,12 @@ 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), - subsection_name: Some(subsection_name), + separator: Some(separator).map(Cow::Borrowed), + subsection_name: Some(subsection_name).map(Cow::Borrowed), } } else { ParsedSectionHeader { @@ -650,7 +658,7 @@ mod comments { comment("; this is a semicolon comment").unwrap(), fully_consumed(ParsedComment { comment_tag: ';', - comment: " this is a semicolon comment", + comment: Cow::Borrowed(" this is a semicolon comment"), }) ); } @@ -661,7 +669,7 @@ mod comments { comment("# this is an octothorpe comment").unwrap(), fully_consumed(ParsedComment { comment_tag: '#', - comment: " this is an octothorpe comment", + comment: Cow::Borrowed(" this is an octothorpe comment"), }) ); } @@ -672,7 +680,7 @@ mod comments { comment("###### this is an octothorpe comment").unwrap(), fully_consumed(ParsedComment { comment_tag: '#', - comment: "##### this is an octothorpe comment", + comment: Cow::Borrowed("##### this is an octothorpe comment"), }) ); } @@ -778,7 +786,7 @@ mod value_no_continuation { fn no_comment() { assert_eq!( value_impl("hello").unwrap(), - fully_consumed(vec![Event::Value("hello")]) + fully_consumed(vec![Event::Value(Cow::Borrowed("hello"))]) ); } @@ -786,7 +794,7 @@ mod value_no_continuation { fn no_comment_newline() { assert_eq!( value_impl("hello\na").unwrap(), - ("\na", vec![Event::Value("hello")]) + ("\na", vec![Event::Value(Cow::Borrowed("hello"))]) ) } @@ -794,7 +802,7 @@ mod value_no_continuation { fn semicolon_comment_not_consumed() { assert_eq!( value_impl("hello;world").unwrap(), - (";world", vec![Event::Value("hello"),]) + (";world", vec![Event::Value(Cow::Borrowed("hello")),]) ); } @@ -802,7 +810,7 @@ mod value_no_continuation { fn octothorpe_comment_not_consumed() { assert_eq!( value_impl("hello#world").unwrap(), - ("#world", vec![Event::Value("hello"),]) + ("#world", vec![Event::Value(Cow::Borrowed("hello")),]) ); } @@ -810,7 +818,10 @@ mod value_no_continuation { fn values_with_extraneous_whitespace_without_comment() { assert_eq!( value_impl("hello ").unwrap(), - (" ", vec![Event::Value("hello")]) + ( + " ", + vec![Event::Value(Cow::Borrowed("hello"))] + ) ); } @@ -818,11 +829,17 @@ mod value_no_continuation { fn values_with_extraneous_whitespace_before_comment() { assert_eq!( value_impl("hello #world").unwrap(), - (" #world", vec![Event::Value("hello"),]) + ( + " #world", + vec![Event::Value(Cow::Borrowed("hello"))] + ) ); assert_eq!( value_impl("hello ;world").unwrap(), - (" ;world", vec![Event::Value("hello"),]) + ( + " ;world", + vec![Event::Value(Cow::Borrowed("hello"))] + ) ); } @@ -830,7 +847,10 @@ mod value_no_continuation { fn trans_escaped_comment_marker_not_consumed() { assert_eq!( value_impl(r##"hello"#"world; a"##).unwrap(), - ("; a", vec![Event::Value(r##"hello"#"world"##)]) + ( + "; a", + vec![Event::Value(Cow::Borrowed(r##"hello"#"world"##))] + ) ); } @@ -838,7 +858,7 @@ mod value_no_continuation { fn complex_test() { assert_eq!( value_impl(r#"value";";ahhhh"#).unwrap(), - (";ahhhh", vec![Event::Value(r#"value";""#)]) + (";ahhhh", vec![Event::Value(Cow::Borrowed(r#"value";""#))]) ); } @@ -857,9 +877,9 @@ mod value_continuation { assert_eq!( value_impl("hello\\\nworld").unwrap(), fully_consumed(vec![ - Event::ValueNotDone("hello"), - Event::Newline("\n"), - Event::ValueDone("world") + Event::ValueNotDone(Cow::Borrowed("hello")), + Event::Newline(Cow::Borrowed("\n")), + Event::ValueDone(Cow::Borrowed("world")) ]) ); } @@ -869,9 +889,9 @@ mod value_continuation { assert_eq!( value_impl("hello\\\n world").unwrap(), fully_consumed(vec![ - Event::ValueNotDone("hello"), - Event::Newline("\n"), - Event::ValueDone(" world") + Event::ValueNotDone(Cow::Borrowed("hello")), + Event::Newline(Cow::Borrowed("\n")), + Event::ValueDone(Cow::Borrowed(" world")) ]) ) } @@ -883,11 +903,11 @@ mod value_continuation { ( " # \"b\t ; c", vec![ - Event::ValueNotDone(r#"1 "\""#), - Event::Newline("\n"), - Event::ValueNotDone(r#"a ; e "\""#), - Event::Newline("\n"), - Event::ValueDone("d"), + 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")), ] ) ); @@ -900,9 +920,9 @@ mod value_continuation { ( ";a", vec![ - Event::ValueNotDone("\""), - Event::Newline("\n"), - Event::ValueDone(";\""), + Event::ValueNotDone(Cow::Borrowed("\"")), + Event::Newline(Cow::Borrowed("\n")), + Event::ValueDone(Cow::Borrowed(";\"")), ] ) ) @@ -935,22 +955,22 @@ mod section { fully_consumed(ParsedSection { section_header: gen_section_header("hello", None), events: vec![ - Event::Newline("\n"), - Event::Whitespace(" "), - Event::Key("a"), - Event::Whitespace(" "), - Event::Whitespace(" "), - Event::Value("b"), - Event::Newline("\n"), - Event::Whitespace(" "), - Event::Key("c"), - Event::Value(""), - Event::Newline("\n"), - Event::Whitespace(" "), - Event::Key("d"), - Event::Whitespace(" "), - Event::Whitespace(" "), - Event::Value("\"lol\"") + Event::Newline(Cow::Borrowed("\n")), + Event::Whitespace(Cow::Borrowed(" ")), + Event::Key(Cow::Borrowed("a")), + Event::Whitespace(Cow::Borrowed(" ")), + 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(" ")), + Event::Whitespace(Cow::Borrowed(" ")), + Event::Value(Cow::Borrowed("\"lol\"")) ] }) ) @@ -962,7 +982,11 @@ mod section { section("[hello] c").unwrap(), fully_consumed(ParsedSection { section_header: gen_section_header("hello", None), - events: vec![Event::Whitespace(" "), Event::Key("c"), Event::Value("")] + events: vec![ + Event::Whitespace(Cow::Borrowed(" ")), + Event::Key(Cow::Borrowed("c")), + Event::Value(Cow::Borrowed("")) + ] }) ); } @@ -979,40 +1003,40 @@ mod section { fully_consumed(ParsedSection { section_header: gen_section_header("hello", None), events: vec![ - Event::Whitespace(" "), + Event::Whitespace(Cow::Borrowed(" ")), Event::Comment(ParsedComment { comment_tag: ';', - comment: " commentA", + comment: Cow::Borrowed(" commentA"), }), - Event::Newline("\n"), - Event::Whitespace(" "), - Event::Key("a"), - Event::Whitespace(" "), - Event::Whitespace(" "), - Event::Value("b"), - Event::Whitespace(" "), + Event::Newline(Cow::Borrowed("\n")), + Event::Whitespace(Cow::Borrowed(" ")), + Event::Key(Cow::Borrowed("a")), + Event::Whitespace(Cow::Borrowed(" ")), + Event::Whitespace(Cow::Borrowed(" ")), + Event::Value(Cow::Borrowed("b")), + Event::Whitespace(Cow::Borrowed(" ")), Event::Comment(ParsedComment { comment_tag: '#', - comment: " commentB", + comment: Cow::Borrowed(" commentB"), }), - Event::Newline("\n"), - Event::Whitespace(" "), + Event::Newline(Cow::Borrowed("\n")), + Event::Whitespace(Cow::Borrowed(" ")), Event::Comment(ParsedComment { comment_tag: ';', - comment: " commentC", + comment: Cow::Borrowed(" commentC"), }), - Event::Newline("\n"), - Event::Whitespace(" "), + Event::Newline(Cow::Borrowed("\n")), + Event::Whitespace(Cow::Borrowed(" ")), Event::Comment(ParsedComment { comment_tag: ';', - comment: " commentD", + comment: Cow::Borrowed(" commentD"), }), - Event::Newline("\n"), - Event::Whitespace(" "), - Event::Key("c"), - Event::Whitespace(" "), - Event::Whitespace(" "), - Event::Value("d"), + Event::Newline(Cow::Borrowed("\n")), + Event::Whitespace(Cow::Borrowed(" ")), + Event::Key(Cow::Borrowed("c")), + Event::Whitespace(Cow::Borrowed(" ")), + Event::Whitespace(Cow::Borrowed(" ")), + Event::Value(Cow::Borrowed("d")), ] }) ); @@ -1026,19 +1050,19 @@ mod section { fully_consumed(ParsedSection { section_header: gen_section_header("section", None), events: vec![ - Event::Whitespace(" "), - Event::Key("a"), - Event::Whitespace(" "), - Event::Whitespace(" "), - Event::ValueNotDone(r#"1 "\""#), - Event::Newline("\n"), - Event::ValueNotDone(r#"a ; e "\""#), - Event::Newline("\n"), - Event::ValueDone("d"), - Event::Whitespace(" "), + Event::Whitespace(Cow::Borrowed(" ")), + Event::Key(Cow::Borrowed("a")), + Event::Whitespace(Cow::Borrowed(" ")), + 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: " \"b\t ; c" + comment: Cow::Borrowed(" \"b\t ; c") }) ] }) @@ -1052,15 +1076,15 @@ mod section { fully_consumed(ParsedSection { section_header: gen_section_header("section", (" ", "a")), events: vec![ - Event::Whitespace(" "), - Event::Key("b"), - Event::Whitespace(" "), - Event::ValueNotDone("\""), - Event::Newline("\n"), - Event::ValueDone(";\""), + Event::Whitespace(Cow::Borrowed(" ")), + Event::Key(Cow::Borrowed("b")), + Event::Whitespace(Cow::Borrowed(" ")), + Event::ValueNotDone(Cow::Borrowed("\"")), + Event::Newline(Cow::Borrowed("\n")), + Event::ValueDone(Cow::Borrowed(";\"")), Event::Comment(ParsedComment { comment_tag: ';', - comment: "a", + comment: Cow::Borrowed("a"), }) ] }) @@ -1074,12 +1098,12 @@ mod section { fully_consumed(ParsedSection { section_header: gen_section_header("s", None), events: vec![ - Event::Key("hello"), - Event::Whitespace(" "), - Event::Value(""), + Event::Key(Cow::Borrowed("hello")), + Event::Whitespace(Cow::Borrowed(" ")), + Event::Value(Cow::Borrowed("")), Event::Comment(ParsedComment { comment_tag: '#', - comment: "world", + comment: Cow::Borrowed("world"), }), ] }) diff --git a/tests/parser_integration_tests.rs b/tests/parser_integration_tests.rs index 46569b2..80bc425 100644 --- a/tests/parser_integration_tests.rs +++ b/tests/parser_integration_tests.rs @@ -1,15 +1,18 @@ +use std::borrow::Cow; + use serde_git_config::parser::{parse_from_str, Event, ParsedSectionHeader}; fn gen_section_header( 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(separator), - subsection_name: Some(subsection_name), + separator: Some(Cow::Borrowed(separator)), + subsection_name: Some(Cow::Borrowed(subsection_name)), } } else { ParsedSectionHeader { @@ -21,19 +24,19 @@ fn gen_section_header( ) } fn name(name: &'static str) -> Event<'static> { - Event::Key(name) + Event::Key(Cow::Borrowed(name)) } fn value(value: &'static str) -> Event<'static> { - Event::Value(value) + Event::Value(Cow::Borrowed(value)) } fn newline() -> Event<'static> { - Event::Newline("\n") + Event::Newline(Cow::Borrowed("\n")) } fn whitespace(value: &'static str) -> Event<'static> { - Event::Whitespace(value) + Event::Whitespace(Cow::Borrowed(value)) } #[test]