From 97157905743c12a150042b477d2bb9641797c87f Mon Sep 17 00:00:00 2001 From: Edward Shen Date: Sat, 6 Mar 2021 01:23:23 -0500 Subject: [PATCH] implement case insensitivity for names --- LICENSE-APACHE | 1 + LICENSE-MIT | 1 + README.md | 18 +++ src/file.rs | 149 +++++++++++------- src/parser.rs | 112 ++++++++++--- src/test_util.rs | 4 +- .../file_integeration_test.rs | 30 ++++ .../parser_integration_tests.rs | 7 +- 8 files changed, 240 insertions(+), 82 deletions(-) create mode 120000 LICENSE-APACHE create mode 120000 LICENSE-MIT create mode 100644 README.md diff --git a/LICENSE-APACHE b/LICENSE-APACHE new file mode 120000 index 0000000..965b606 --- /dev/null +++ b/LICENSE-APACHE @@ -0,0 +1 @@ +../LICENSE-APACHE \ No newline at end of file diff --git a/LICENSE-MIT b/LICENSE-MIT new file mode 120000 index 0000000..76219eb --- /dev/null +++ b/LICENSE-MIT @@ -0,0 +1 @@ +../LICENSE-MIT \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..ece86f7 --- /dev/null +++ b/README.md @@ -0,0 +1,18 @@ +# git-config + +**git-config is a library for interacting with `git-config` files.** + +#### License + + +Licensed under either of Apache License, Version +2.0 or MIT license at your option. + + +
+ + +Unless you explicitly state otherwise, any contribution intentionally submitted +for inclusion in git-config by you, as defined in the Apache-2.0 license, shall +be dual licensed as above, without any additional terms or conditions. + \ No newline at end of file diff --git a/src/file.rs b/src/file.rs index ba3a755..0ac97e0 100644 --- a/src/file.rs +++ b/src/file.rs @@ -1,23 +1,25 @@ //! This module provides a high level wrapper around a single `git-config` file. -use crate::parser::{parse_from_bytes, Error, Event, ParsedSectionHeader, Parser}; -use crate::values::{normalize_bytes, normalize_cow, normalize_vec}; -use std::{borrow::Borrow, convert::TryFrom, ops::Deref}; -use std::{borrow::Cow, fmt::Display}; -use std::{ - collections::{HashMap, VecDeque}, - ops::DerefMut, +use crate::parser::{ + parse_from_bytes, Error, Event, Key, ParsedSectionHeader, Parser, SectionHeaderName, }; +use crate::values::{normalize_bytes, normalize_cow, normalize_vec}; +use std::borrow::Borrow; +use std::borrow::Cow; +use std::collections::{HashMap, VecDeque}; +use std::convert::TryFrom; +use std::fmt::Display; +use std::ops::{Deref, DerefMut}; /// All possible error types that may occur from interacting with [`GitConfig`]. -#[derive(PartialEq, Eq, Hash, Copy, Clone, PartialOrd, Ord, Debug)] +#[derive(PartialEq, Eq, Hash, Clone, PartialOrd, Ord, Debug)] pub enum GitConfigError<'a> { /// The requested section does not exist. - SectionDoesNotExist(&'a str), + SectionDoesNotExist(SectionHeaderName<'a>), /// The requested subsection does not exist. SubSectionDoesNotExist(Option<&'a str>), /// The key does not exist in the requested section. - KeyDoesNotExist(&'a str), + KeyDoesNotExist(Key<'a>), /// The conversion into the provided type for methods such as /// [`GitConfig::get_value`] failed. FailedConversion, @@ -68,7 +70,7 @@ enum LookupTreeNode<'a> { /// time. pub struct MutableValue<'borrow, 'lookup, 'event> { section: &'borrow mut Vec>, - key: &'lookup str, + key: Key<'lookup>, index: usize, size: usize, } @@ -109,7 +111,7 @@ impl MutableValue<'_, '_, '_> { latest_value .map(normalize_cow) .or_else(|| partial_value.map(normalize_vec)) - .ok_or(GitConfigError::KeyDoesNotExist(self.key)) + .ok_or(GitConfigError::KeyDoesNotExist(self.key.to_owned())) } /// Update the value to the provided one. This modifies the value such that @@ -131,8 +133,10 @@ impl MutableValue<'_, '_, '_> { self.section .insert(self.index, Event::Value(Cow::Owned(input))); self.section.insert(self.index, Event::KeyValueSeparator); - self.section - .insert(self.index, Event::Key(Cow::Owned(self.key.into()))); + self.section.insert( + self.index, + Event::Key(Key(Cow::Owned(self.key.0.to_string()))), + ); } /// Removes the value. Does nothing when called multiple times in @@ -193,7 +197,7 @@ impl DerefMut for Offset { #[derive(Eq, PartialEq, Debug)] pub struct MutableMultiValue<'borrow, 'lookup, 'event> { section: &'borrow mut HashMap>>, - key: &'lookup str, + key: Key<'lookup>, /// Each entry data struct provides sufficient information to index into /// [`Self::offsets`]. This layer of indirection is used for users to index /// into the offsets rather than leaking the internal data structures. @@ -244,7 +248,7 @@ impl<'lookup, 'event> MutableMultiValue<'_, 'lookup, 'event> { } if values.is_empty() { - return Err(GitConfigError::KeyDoesNotExist(self.key)); + return Err(GitConfigError::KeyDoesNotExist(self.key.to_owned())); } Ok(values) @@ -296,7 +300,7 @@ impl<'lookup, 'event> MutableMultiValue<'_, 'lookup, 'event> { offset_index, } = self.indices_and_sizes[index]; MutableMultiValue::set_value_inner( - self.key, + self.key.to_owned(), &mut self.offsets, self.section.get_mut(§ion_id).unwrap(), section_id, @@ -323,7 +327,7 @@ impl<'lookup, 'event> MutableMultiValue<'_, 'lookup, 'event> { ) in self.indices_and_sizes.iter().zip(input) { Self::set_value_inner( - self.key, + self.key.to_owned(), &mut self.offsets, self.section.get_mut(section_id).unwrap(), *section_id, @@ -350,7 +354,7 @@ impl<'lookup, 'event> MutableMultiValue<'_, 'lookup, 'event> { } in &self.indices_and_sizes { Self::set_value_inner( - self.key, + self.key.to_owned(), &mut self.offsets, self.section.get_mut(section_id).unwrap(), *section_id, @@ -373,7 +377,7 @@ impl<'lookup, 'event> MutableMultiValue<'_, 'lookup, 'event> { } in &self.indices_and_sizes { Self::set_value_inner( - self.key, + self.key.to_owned(), &mut self.offsets, self.section.get_mut(section_id).unwrap(), *section_id, @@ -384,7 +388,7 @@ impl<'lookup, 'event> MutableMultiValue<'_, 'lookup, 'event> { } fn set_value_inner<'a: 'event>( - key: &'lookup str, + key: Key<'lookup>, offsets: &mut HashMap>, section: &mut Vec>, section_id: SectionId, @@ -398,7 +402,7 @@ impl<'lookup, 'event> MutableMultiValue<'_, 'lookup, 'event> { MutableMultiValue::set_offset(offsets, section_id, offset_index, 3); section.insert(offset, Event::Value(input)); section.insert(offset, Event::KeyValueSeparator); - section.insert(offset, Event::Key(Cow::Owned(key.into()))); + section.insert(offset, Event::Key(Key(Cow::Owned(key.0.to_string())))); } /// Removes the value at the given index. Does nothing when called multiple @@ -530,7 +534,7 @@ pub struct GitConfig<'a> { /// `git-config` file prohibits global values, this vec is limited to only /// comment, newline, and whitespace events. frontmatter_events: Vec>, - section_lookup_tree: HashMap, Vec>>, + section_lookup_tree: HashMap, Vec>>, /// SectionId to section mapping. The value of this HashMap contains actual /// events. /// @@ -675,6 +679,7 @@ impl<'event> GitConfig<'event> { // the "last one wins" resolution strategy by `git-config`). let section_ids = self.get_section_ids_by_name_and_subname(section_name, subsection_name)?; + let key = Key(key.into()); for section_id in section_ids.iter().rev() { let mut found_key = false; @@ -728,6 +733,7 @@ impl<'event> GitConfig<'event> { ) -> Result, GitConfigError<'lookup>> { let section_ids = self.get_section_ids_by_name_and_subname(section_name, subsection_name)?; + let key = Key(key.into()); for section_id in section_ids.iter().rev() { let mut size = 0; @@ -814,7 +820,7 @@ impl<'event> GitConfig<'event> { subsection_name: Option<&'lookup str>, key: &'lookup str, ) -> Result>, GitConfigError<'lookup>> { - let key = key; + let key = Key(key.into()); 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; @@ -912,6 +918,7 @@ impl<'event> GitConfig<'event> { let section_ids = self .get_section_ids_by_name_and_subname(section_name, subsection_name)? .to_vec(); + let key = Key(key.into()); let mut offsets = HashMap::new(); let mut entries = vec![]; @@ -1103,26 +1110,23 @@ impl<'event> GitConfig<'event> { subsection_name: impl Into>>, ) { self.push_section( - Some(section_name.into()), + Some(SectionHeaderName(section_name.into())), subsection_name.into(), &mut Some(vec![]), ) } /// Removes the section, returning the events it had, if any. - pub fn remove_section( + pub fn remove_section<'lookup>( &mut self, - section_name: impl Into>, - subsection_name: impl Into>>, + section_name: &'lookup str, + subsection_name: impl Into>, ) -> Option> { - let mut section_ids = self - .get_section_ids_by_name_and_subname( - §ion_name.into(), - subsection_name.into().as_deref(), - ) - .ok()?; + let section_ids = + self.get_section_ids_by_name_and_subname(section_name, subsection_name.into()); + let section_ids = section_ids.ok()?.pop()?; - self.sections.remove(§ion_ids.pop()?) + self.sections.remove(§ion_ids) } } @@ -1131,7 +1135,7 @@ impl<'event> GitConfig<'event> { /// Used during initialization. fn push_section( &mut self, - current_section_name: Option>, + current_section_name: Option>, current_subsection_name: Option>, maybe_section: &mut Option>>, ) { @@ -1184,21 +1188,22 @@ impl<'event> GitConfig<'event> { /// Returns the mapping between section and subsection name to section ids. fn get_section_ids_by_name_and_subname<'lookup>( &self, - section_name: &'lookup str, + section_name: impl Into>, subsection_name: Option<&'lookup str>, ) -> Result, GitConfigError<'lookup>> { + let section_name = section_name.into(); let section_ids = self .section_lookup_tree - .get(section_name) + .get(§ion_name) .ok_or(GitConfigError::SectionDoesNotExist(section_name))?; let mut maybe_ids = None; // Don't simplify if and matches here -- the for loop currently needs // `n + 1` checks, while the if and matches will result in the for loop // needing `2n` checks. - if let Some(subsect_name) = subsection_name { + if let Some(subsection_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(subsection_name); break; } } @@ -1252,7 +1257,7 @@ impl<'a> From> for GitConfig<'a> { }; // Current section that we're building - let mut current_section_name: Option> = None; + let mut current_section_name: Option> = None; let mut current_subsection_name: Option> = None; let mut maybe_section: Option>> = None; @@ -1613,16 +1618,48 @@ mod mutable_multi_value { .get_raw_multi_value_mut("core", None, "a") .unwrap(); values.delete_all(); + assert!(values.get().is_err()); assert_eq!( git_config.to_string(), "[core]\n \n [core]\n \n ", ); } + + #[test] + fn partial_values_are_supported() { + let mut git_config = GitConfig::try_from( + r#"[core] + a=b\ +"100" + [core] + a=d\ +b + a=f\ +a"#, + ) + .unwrap(); + let mut values = git_config + .get_raw_multi_value_mut("core", None, "a") + .unwrap(); + + assert_eq!( + &*values.get().unwrap(), + vec![ + Cow::<[u8]>::Owned(b"b100".to_vec()), + Cow::<[u8]>::Borrowed(b"db"), + Cow::<[u8]>::Borrowed(b"fa"), + ] + ); + + values.delete_all(); + assert!(values.get().is_err()); + } } #[cfg(test)] mod from_parser { use super::{Cow, Event, GitConfig, HashMap, LookupTreeNode, SectionId, TryFrom}; + use crate::parser::SectionHeaderName; use crate::test_util::{name_event, newline_event, section_header, value_event}; #[test] @@ -1648,7 +1685,7 @@ mod from_parser { let expected_lookup_tree = { let mut tree = HashMap::new(); tree.insert( - Cow::Borrowed("core"), + SectionHeaderName(Cow::Borrowed("core")), vec![LookupTreeNode::Terminal(vec![SectionId(0)])], ); tree @@ -1677,10 +1714,10 @@ mod from_parser { #[test] fn parse_single_subsection() { - let mut config = GitConfig::try_from("[core.subsec]\na=b\nc=d").unwrap(); + let mut config = GitConfig::try_from("[core.sub]\na=b\nc=d").unwrap(); let expected_separators = { let mut map = HashMap::new(); - map.insert(SectionId(0), section_header("core", (".", "subsec"))); + map.insert(SectionId(0), section_header("core", (".", "sub"))); map }; assert_eq!(config.section_headers, expected_separators); @@ -1688,9 +1725,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("sub"), vec![SectionId(0)]); tree.insert( - Cow::Borrowed("core"), + SectionHeaderName(Cow::Borrowed("core")), vec![LookupTreeNode::NonTerminal(inner_tree)], ); tree @@ -1731,11 +1768,11 @@ mod from_parser { let expected_lookup_tree = { let mut tree = HashMap::new(); tree.insert( - Cow::Borrowed("core"), + SectionHeaderName(Cow::Borrowed("core")), vec![LookupTreeNode::Terminal(vec![SectionId(0)])], ); tree.insert( - Cow::Borrowed("other"), + SectionHeaderName(Cow::Borrowed("other")), vec![LookupTreeNode::Terminal(vec![SectionId(1)])], ); tree @@ -1784,7 +1821,7 @@ mod from_parser { let expected_lookup_tree = { let mut tree = HashMap::new(); tree.insert( - Cow::Borrowed("core"), + SectionHeaderName(Cow::Borrowed("core")), vec![LookupTreeNode::Terminal(vec![SectionId(0), SectionId(1)])], ); tree @@ -1823,6 +1860,7 @@ mod from_parser { #[cfg(test)] mod get_raw_value { use super::{Cow, GitConfig, GitConfigError, TryFrom}; + use crate::parser::{Key, SectionHeaderName}; #[test] fn single_section() { @@ -1860,7 +1898,9 @@ mod get_raw_value { let config = GitConfig::try_from("[core]\na=b\nc=d").unwrap(); assert_eq!( config.get_raw_value("foo", None, "a"), - Err(GitConfigError::SectionDoesNotExist("foo")) + Err(GitConfigError::SectionDoesNotExist(SectionHeaderName( + "foo".into() + ))) ); } @@ -1878,7 +1918,7 @@ mod get_raw_value { let config = GitConfig::try_from("[core]\na=b\nc=d").unwrap(); assert_eq!( config.get_raw_value("core", None, "aaaaaa"), - Err(GitConfigError::KeyDoesNotExist("aaaaaa")) + Err(GitConfigError::KeyDoesNotExist(Key("aaaaaa".into()))) ); } @@ -1918,6 +1958,7 @@ mod get_value { #[cfg(test)] mod get_raw_multi_value { use super::{Cow, GitConfig, GitConfigError, TryFrom}; + use crate::parser::{Key, SectionHeaderName}; #[test] fn single_value_is_identical_to_single_value_query() { @@ -1955,7 +1996,9 @@ mod get_raw_multi_value { let config = GitConfig::try_from("[core]\na=b\nc=d").unwrap(); assert_eq!( config.get_raw_multi_value("foo", None, "a"), - Err(GitConfigError::SectionDoesNotExist("foo")) + Err(GitConfigError::SectionDoesNotExist(SectionHeaderName( + "foo".into() + ))) ); } @@ -1973,7 +2016,7 @@ mod get_raw_multi_value { let config = GitConfig::try_from("[core]\na=b\nc=d").unwrap(); assert_eq!( config.get_raw_multi_value("core", None, "aaaaaa"), - Err(GitConfigError::KeyDoesNotExist("aaaaaa")) + Err(GitConfigError::KeyDoesNotExist(Key("aaaaaa".into()))) ); } diff --git a/src/parser.rs b/src/parser.rs index c7b7305..f37c8d7 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -18,9 +18,10 @@ use nom::error::{Error as NomError, ErrorKind}; use nom::multi::{many0, many1}; use nom::sequence::delimited; use nom::IResult; -use std::borrow::Cow; +use std::convert::TryFrom; +use std::fmt::Display; use std::iter::FusedIterator; -use std::{convert::TryFrom, fmt::Display}; +use std::{borrow::Cow, hash::Hash}; /// Syntactic events that occurs in the config. Despite all these variants /// holding a [`Cow`] instead over a simple reference, the parser will only emit @@ -42,7 +43,7 @@ pub enum Event<'a> { /// exists. SectionHeader(ParsedSectionHeader<'a>), /// A name to a value in a section. - Key(Cow<'a, str>), + Key(Key<'a>), /// 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 @@ -92,7 +93,8 @@ impl Display for Event<'_> { } Self::Comment(e) => e.fmt(f), Self::SectionHeader(e) => e.fmt(f), - Self::Key(e) | Self::Newline(e) | Self::Whitespace(e) => e.fmt(f), + Self::Key(e) => e.fmt(f), + Self::Newline(e) | Self::Whitespace(e) => e.fmt(f), Self::KeyValueSeparator => write!(f, "="), } } @@ -110,7 +112,8 @@ impl Into> for &Event<'_> { Event::Value(e) | Event::ValueNotDone(e) | Event::ValueDone(e) => e.to_vec(), Event::Comment(e) => e.into(), Event::SectionHeader(e) => e.into(), - Event::Key(e) | Event::Newline(e) | Event::Whitespace(e) => e.as_bytes().to_vec(), + Event::Key(e) => e.0.as_bytes().to_vec(), + Event::Newline(e) | Event::Whitespace(e) => e.as_bytes().to_vec(), Event::KeyValueSeparator => vec![b'='], } } @@ -143,7 +146,7 @@ impl Display for ParsedSection<'_> { #[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: SectionHeaderName<'a>, /// 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 @@ -154,6 +157,69 @@ pub struct ParsedSectionHeader<'a> { pub subsection_name: Option>, } +macro_rules! generate_case_insensitive { + ($name:ident, $inner_type:ty, $comment:literal) => { + /// Wrapper struct for $comment, since $comment are case-insensitive. + #[derive(Clone, Eq, Ord, Debug, Default)] + pub struct $name<'a>(pub $inner_type); + + impl PartialEq for $name<'_> { + fn eq(&self, other: &Self) -> bool { + self.0.eq_ignore_ascii_case(&other.0) + } + } + + impl Display for $name<'_> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + self.0.fmt(f) + } + } + + impl PartialOrd for $name<'_> { + fn partial_cmp(&self, other: &Self) -> Option { + self.0 + .to_ascii_lowercase() + .partial_cmp(&other.0.to_ascii_lowercase()) + } + } + + impl std::hash::Hash for $name<'_> { + fn hash(&self, state: &mut H) { + self.0.to_ascii_lowercase().hash(state) + } + } + + impl<'a> From<&'a str> for $name<'a> { + fn from(s: &'a str) -> Self { + Self(Cow::Borrowed(s)) + } + } + + impl<'a> From> for $name<'a> { + fn from(s: Cow<'a, str>) -> Self { + Self(s) + } + } + + impl<'a> std::ops::Deref for $name<'a> { + type Target = $inner_type; + + fn deref(&self) -> &Self::Target { + &self.0 + } + } + + impl<'a> std::ops::DerefMut for $name<'a> { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } + } + }; +} + +generate_case_insensitive!(SectionHeaderName, Cow<'a, str>, "section names"); +generate_case_insensitive!(Key, Cow<'a, str>, "keys"); + impl ParsedSectionHeader<'_> { /// Generates a byte representation of the value. This should be used when /// non-UTF-8 sequences are present or a UTF-8 representation can't be @@ -386,10 +452,10 @@ impl Display for ParserNode { /// non-significant events that occur in addition to the ones you may expect: /// /// ``` -/// # use git_config::parser::{Event, ParsedSectionHeader, parse_from_str}; +/// # use git_config::parser::{Event, ParsedSectionHeader, parse_from_str, SectionHeaderName, Key}; /// # use std::borrow::Cow; /// # let section_header = ParsedSectionHeader { -/// # name: Cow::Borrowed("core"), +/// # name: SectionHeaderName(Cow::Borrowed("core")), /// # separator: None, /// # subsection_name: None, /// # }; @@ -398,7 +464,7 @@ impl Display for ParserNode { /// Event::SectionHeader(section_header), /// Event::Newline(Cow::Borrowed("\n")), /// Event::Whitespace(Cow::Borrowed(" ")), -/// Event::Key(Cow::Borrowed("autocrlf")), +/// Event::Key(Key(Cow::Borrowed("autocrlf"))), /// Event::Whitespace(Cow::Borrowed(" ")), /// Event::KeyValueSeparator, /// Event::Whitespace(Cow::Borrowed(" ")), @@ -425,10 +491,10 @@ impl Display for ParserNode { /// which means that the corresponding event won't appear either: /// /// ``` -/// # use git_config::parser::{Event, ParsedSectionHeader, parse_from_str}; +/// # use git_config::parser::{Event, ParsedSectionHeader, parse_from_str, SectionHeaderName, Key}; /// # use std::borrow::Cow; /// # let section_header = ParsedSectionHeader { -/// # name: Cow::Borrowed("core"), +/// # name: SectionHeaderName(Cow::Borrowed("core")), /// # separator: None, /// # subsection_name: None, /// # }; @@ -437,7 +503,7 @@ impl Display for ParserNode { /// Event::SectionHeader(section_header), /// Event::Newline(Cow::Borrowed("\n")), /// Event::Whitespace(Cow::Borrowed(" ")), -/// Event::Key(Cow::Borrowed("autocrlf")), +/// Event::Key(Key(Cow::Borrowed("autocrlf"))), /// Event::Value(Cow::Borrowed(b"")), /// # ]); /// ``` @@ -459,10 +525,10 @@ impl Display for ParserNode { /// relevant event stream emitted is thus emitted as: /// /// ``` -/// # use git_config::parser::{Event, ParsedSectionHeader, parse_from_str}; +/// # use git_config::parser::{Event, ParsedSectionHeader, parse_from_str, SectionHeaderName, Key}; /// # use std::borrow::Cow; /// # let section_header = ParsedSectionHeader { -/// # name: Cow::Borrowed("core"), +/// # name: SectionHeaderName(Cow::Borrowed("core")), /// # separator: None, /// # subsection_name: None, /// # }; @@ -470,11 +536,11 @@ impl Display for ParserNode { /// # 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::Key(Key(Cow::Borrowed("autocrlf"))), /// Event::KeyValueSeparator, /// Event::Value(Cow::Borrowed(br#"true"""#)), /// Event::Newline(Cow::Borrowed("\n")), -/// Event::Key(Cow::Borrowed("filemode")), +/// Event::Key(Key(Cow::Borrowed("filemode"))), /// Event::KeyValueSeparator, /// Event::Value(Cow::Borrowed(br#"fa"lse""#)), /// # ]); @@ -496,10 +562,10 @@ impl Display for ParserNode { /// split value accordingly: /// /// ``` -/// # use git_config::parser::{Event, ParsedSectionHeader, parse_from_str}; +/// # use git_config::parser::{Event, ParsedSectionHeader, parse_from_str, SectionHeaderName, Key}; /// # use std::borrow::Cow; /// # let section_header = ParsedSectionHeader { -/// # name: Cow::Borrowed("some-section"), +/// # name: SectionHeaderName(Cow::Borrowed("some-section")), /// # separator: None, /// # subsection_name: None, /// # }; @@ -507,7 +573,7 @@ impl Display for ParserNode { /// # 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::Key(Key(Cow::Borrowed("file"))), /// Event::KeyValueSeparator, /// Event::ValueNotDone(Cow::Borrowed(b"a")), /// Event::Newline(Cow::Borrowed("\n")), @@ -772,12 +838,12 @@ fn section_header(i: &[u8]) -> IResult<&[u8], ParsedSectionHeader> { // subsection syntax at this point. let header = match memchr::memrchr(b'.', name.as_bytes()) { Some(index) => ParsedSectionHeader { - name: Cow::Borrowed(&name[..index]), + name: SectionHeaderName(Cow::Borrowed(&name[..index])), separator: name.get(index..=index).map(|slice| Cow::Borrowed(slice)), subsection_name: name.get(index + 1..).map(|slice| Cow::Borrowed(slice)), }, None => ParsedSectionHeader { - name: Cow::Borrowed(name), + name: SectionHeaderName(Cow::Borrowed(name)), separator: None, subsection_name: None, }, @@ -808,7 +874,7 @@ fn section_header(i: &[u8]) -> IResult<&[u8], ParsedSectionHeader> { Ok(( i, ParsedSectionHeader { - name: Cow::Borrowed(name), + name: SectionHeaderName(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. @@ -826,7 +892,7 @@ fn section_body<'a, 'b, 'c>( *node = ParserNode::ConfigName; let (i, name) = config_name(i)?; - items.push(Event::Key(Cow::Borrowed(name))); + items.push(Event::Key(Key(Cow::Borrowed(name)))); let (i, whitespace) = opt(take_spaces)(i)?; diff --git a/src/test_util.rs b/src/test_util.rs index 6397f02..06e3d5b 100644 --- a/src/test_util.rs +++ b/src/test_util.rs @@ -1,7 +1,7 @@ //! This module is only included for tests, and contains common unit test helper //! functions. -use crate::parser::{Event, ParsedComment, ParsedSectionHeader}; +use crate::parser::{Event, Key, ParsedComment, ParsedSectionHeader}; use std::borrow::Cow; pub fn section_header_event( @@ -32,7 +32,7 @@ pub fn section_header( } pub(crate) fn name_event(name: &'static str) -> Event<'static> { - Event::Key(Cow::Borrowed(name)) + Event::Key(Key(Cow::Borrowed(name))) } pub(crate) fn value_event(value: &'static str) -> Event<'static> { diff --git a/tests/integration_tests/file_integeration_test.rs b/tests/integration_tests/file_integeration_test.rs index 4291b3b..430ec48 100644 --- a/tests/integration_tests/file_integeration_test.rs +++ b/tests/integration_tests/file_integeration_test.rs @@ -97,3 +97,33 @@ fn get_value_looks_up_all_sections_before_failing() -> Result<(), Box Result<(), Box> { + let config = "[core] bool-implicit"; + let file = GitConfig::try_from(config)?; + assert!(file + .get_value::("core", None, "bool-implicit") + .is_ok()); + assert_eq!( + file.get_value::("core", None, "bool-implicit"), + file.get_value::("CORE", None, "bool-implicit") + ); + + Ok(()) +} + +#[test] +fn value_names_are_case_insensitive() -> Result<(), Box> { + let config = "[core] + a = true + A = false"; + let file = GitConfig::try_from(config)?; + assert_eq!(file.get_multi_value::("core", None, "a")?.len(), 2); + assert_eq!( + file.get_value::("core", None, "a"), + file.get_value::("core", None, "A") + ); + + Ok(()) +} diff --git a/tests/integration_tests/parser_integration_tests.rs b/tests/integration_tests/parser_integration_tests.rs index ee6d93e..d3d57ca 100644 --- a/tests/integration_tests/parser_integration_tests.rs +++ b/tests/integration_tests/parser_integration_tests.rs @@ -1,7 +1,6 @@ +use git_config::parser::{parse_from_str, Event, Key, ParsedSectionHeader, SectionHeaderName}; use std::borrow::Cow; -use git_config::parser::{parse_from_str, Event, ParsedSectionHeader}; - pub fn section_header_event( name: &str, subsection: impl Into>, @@ -13,7 +12,7 @@ pub fn section_header( name: &str, subsection: impl Into>, ) -> ParsedSectionHeader<'_> { - let name = Cow::Borrowed(name); + let name = SectionHeaderName(Cow::Borrowed(name)); if let Some((separator, subsection_name)) = subsection.into() { ParsedSectionHeader { name, @@ -30,7 +29,7 @@ pub fn section_header( } fn name(name: &'static str) -> Event<'static> { - Event::Key(Cow::Borrowed(name)) + Event::Key(Key(Cow::Borrowed(name))) } fn value(value: &'static str) -> Event<'static> {