implement case insensitivity for names
This commit is contained in:
parent
6167914277
commit
9715790574
8 changed files with 240 additions and 82 deletions
1
LICENSE-APACHE
Symbolic link
1
LICENSE-APACHE
Symbolic link
|
@ -0,0 +1 @@
|
||||||
|
../LICENSE-APACHE
|
1
LICENSE-MIT
Symbolic link
1
LICENSE-MIT
Symbolic link
|
@ -0,0 +1 @@
|
||||||
|
../LICENSE-MIT
|
18
README.md
Normal file
18
README.md
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
# git-config
|
||||||
|
|
||||||
|
**git-config is a library for interacting with `git-config` files.**
|
||||||
|
|
||||||
|
#### License
|
||||||
|
|
||||||
|
<sup>
|
||||||
|
Licensed under either of <a href="LICENSE-APACHE">Apache License, Version
|
||||||
|
2.0</a> or <a href="LICENSE-MIT">MIT license</a> at your option.
|
||||||
|
</sup>
|
||||||
|
|
||||||
|
<br>
|
||||||
|
|
||||||
|
<sub>
|
||||||
|
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.
|
||||||
|
</sub>
|
149
src/file.rs
149
src/file.rs
|
@ -1,23 +1,25 @@
|
||||||
//! This module provides a high level wrapper around a single `git-config` file.
|
//! 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::parser::{
|
||||||
use crate::values::{normalize_bytes, normalize_cow, normalize_vec};
|
parse_from_bytes, Error, Event, Key, ParsedSectionHeader, Parser, SectionHeaderName,
|
||||||
use std::{borrow::Borrow, convert::TryFrom, ops::Deref};
|
|
||||||
use std::{borrow::Cow, fmt::Display};
|
|
||||||
use std::{
|
|
||||||
collections::{HashMap, VecDeque},
|
|
||||||
ops::DerefMut,
|
|
||||||
};
|
};
|
||||||
|
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`].
|
/// 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> {
|
pub enum GitConfigError<'a> {
|
||||||
/// The requested section does not exist.
|
/// The requested section does not exist.
|
||||||
SectionDoesNotExist(&'a str),
|
SectionDoesNotExist(SectionHeaderName<'a>),
|
||||||
/// The requested subsection does not exist.
|
/// The requested subsection does not exist.
|
||||||
SubSectionDoesNotExist(Option<&'a str>),
|
SubSectionDoesNotExist(Option<&'a str>),
|
||||||
/// The key does not exist in the requested section.
|
/// 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
|
/// The conversion into the provided type for methods such as
|
||||||
/// [`GitConfig::get_value`] failed.
|
/// [`GitConfig::get_value`] failed.
|
||||||
FailedConversion,
|
FailedConversion,
|
||||||
|
@ -68,7 +70,7 @@ enum LookupTreeNode<'a> {
|
||||||
/// time.
|
/// time.
|
||||||
pub struct MutableValue<'borrow, 'lookup, 'event> {
|
pub struct MutableValue<'borrow, 'lookup, 'event> {
|
||||||
section: &'borrow mut Vec<Event<'event>>,
|
section: &'borrow mut Vec<Event<'event>>,
|
||||||
key: &'lookup str,
|
key: Key<'lookup>,
|
||||||
index: usize,
|
index: usize,
|
||||||
size: usize,
|
size: usize,
|
||||||
}
|
}
|
||||||
|
@ -109,7 +111,7 @@ impl MutableValue<'_, '_, '_> {
|
||||||
latest_value
|
latest_value
|
||||||
.map(normalize_cow)
|
.map(normalize_cow)
|
||||||
.or_else(|| partial_value.map(normalize_vec))
|
.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
|
/// Update the value to the provided one. This modifies the value such that
|
||||||
|
@ -131,8 +133,10 @@ impl MutableValue<'_, '_, '_> {
|
||||||
self.section
|
self.section
|
||||||
.insert(self.index, Event::Value(Cow::Owned(input)));
|
.insert(self.index, Event::Value(Cow::Owned(input)));
|
||||||
self.section.insert(self.index, Event::KeyValueSeparator);
|
self.section.insert(self.index, Event::KeyValueSeparator);
|
||||||
self.section
|
self.section.insert(
|
||||||
.insert(self.index, Event::Key(Cow::Owned(self.key.into())));
|
self.index,
|
||||||
|
Event::Key(Key(Cow::Owned(self.key.0.to_string()))),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Removes the value. Does nothing when called multiple times in
|
/// Removes the value. Does nothing when called multiple times in
|
||||||
|
@ -193,7 +197,7 @@ impl DerefMut for Offset {
|
||||||
#[derive(Eq, PartialEq, Debug)]
|
#[derive(Eq, PartialEq, Debug)]
|
||||||
pub struct MutableMultiValue<'borrow, 'lookup, 'event> {
|
pub struct MutableMultiValue<'borrow, 'lookup, 'event> {
|
||||||
section: &'borrow mut HashMap<SectionId, Vec<Event<'event>>>,
|
section: &'borrow mut HashMap<SectionId, Vec<Event<'event>>>,
|
||||||
key: &'lookup str,
|
key: Key<'lookup>,
|
||||||
/// Each entry data struct provides sufficient information to index into
|
/// Each entry data struct provides sufficient information to index into
|
||||||
/// [`Self::offsets`]. This layer of indirection is used for users to index
|
/// [`Self::offsets`]. This layer of indirection is used for users to index
|
||||||
/// into the offsets rather than leaking the internal data structures.
|
/// into the offsets rather than leaking the internal data structures.
|
||||||
|
@ -244,7 +248,7 @@ impl<'lookup, 'event> MutableMultiValue<'_, 'lookup, 'event> {
|
||||||
}
|
}
|
||||||
|
|
||||||
if values.is_empty() {
|
if values.is_empty() {
|
||||||
return Err(GitConfigError::KeyDoesNotExist(self.key));
|
return Err(GitConfigError::KeyDoesNotExist(self.key.to_owned()));
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(values)
|
Ok(values)
|
||||||
|
@ -296,7 +300,7 @@ impl<'lookup, 'event> MutableMultiValue<'_, 'lookup, 'event> {
|
||||||
offset_index,
|
offset_index,
|
||||||
} = self.indices_and_sizes[index];
|
} = self.indices_and_sizes[index];
|
||||||
MutableMultiValue::set_value_inner(
|
MutableMultiValue::set_value_inner(
|
||||||
self.key,
|
self.key.to_owned(),
|
||||||
&mut self.offsets,
|
&mut self.offsets,
|
||||||
self.section.get_mut(§ion_id).unwrap(),
|
self.section.get_mut(§ion_id).unwrap(),
|
||||||
section_id,
|
section_id,
|
||||||
|
@ -323,7 +327,7 @@ impl<'lookup, 'event> MutableMultiValue<'_, 'lookup, 'event> {
|
||||||
) in self.indices_and_sizes.iter().zip(input)
|
) in self.indices_and_sizes.iter().zip(input)
|
||||||
{
|
{
|
||||||
Self::set_value_inner(
|
Self::set_value_inner(
|
||||||
self.key,
|
self.key.to_owned(),
|
||||||
&mut self.offsets,
|
&mut self.offsets,
|
||||||
self.section.get_mut(section_id).unwrap(),
|
self.section.get_mut(section_id).unwrap(),
|
||||||
*section_id,
|
*section_id,
|
||||||
|
@ -350,7 +354,7 @@ impl<'lookup, 'event> MutableMultiValue<'_, 'lookup, 'event> {
|
||||||
} in &self.indices_and_sizes
|
} in &self.indices_and_sizes
|
||||||
{
|
{
|
||||||
Self::set_value_inner(
|
Self::set_value_inner(
|
||||||
self.key,
|
self.key.to_owned(),
|
||||||
&mut self.offsets,
|
&mut self.offsets,
|
||||||
self.section.get_mut(section_id).unwrap(),
|
self.section.get_mut(section_id).unwrap(),
|
||||||
*section_id,
|
*section_id,
|
||||||
|
@ -373,7 +377,7 @@ impl<'lookup, 'event> MutableMultiValue<'_, 'lookup, 'event> {
|
||||||
} in &self.indices_and_sizes
|
} in &self.indices_and_sizes
|
||||||
{
|
{
|
||||||
Self::set_value_inner(
|
Self::set_value_inner(
|
||||||
self.key,
|
self.key.to_owned(),
|
||||||
&mut self.offsets,
|
&mut self.offsets,
|
||||||
self.section.get_mut(section_id).unwrap(),
|
self.section.get_mut(section_id).unwrap(),
|
||||||
*section_id,
|
*section_id,
|
||||||
|
@ -384,7 +388,7 @@ impl<'lookup, 'event> MutableMultiValue<'_, 'lookup, 'event> {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn set_value_inner<'a: 'event>(
|
fn set_value_inner<'a: 'event>(
|
||||||
key: &'lookup str,
|
key: Key<'lookup>,
|
||||||
offsets: &mut HashMap<SectionId, Vec<Offset>>,
|
offsets: &mut HashMap<SectionId, Vec<Offset>>,
|
||||||
section: &mut Vec<Event<'event>>,
|
section: &mut Vec<Event<'event>>,
|
||||||
section_id: SectionId,
|
section_id: SectionId,
|
||||||
|
@ -398,7 +402,7 @@ impl<'lookup, 'event> MutableMultiValue<'_, 'lookup, 'event> {
|
||||||
MutableMultiValue::set_offset(offsets, section_id, offset_index, 3);
|
MutableMultiValue::set_offset(offsets, section_id, offset_index, 3);
|
||||||
section.insert(offset, Event::Value(input));
|
section.insert(offset, Event::Value(input));
|
||||||
section.insert(offset, Event::KeyValueSeparator);
|
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
|
/// 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
|
/// `git-config` file prohibits global values, this vec is limited to only
|
||||||
/// comment, newline, and whitespace events.
|
/// comment, newline, and whitespace events.
|
||||||
frontmatter_events: Vec<Event<'a>>,
|
frontmatter_events: Vec<Event<'a>>,
|
||||||
section_lookup_tree: HashMap<Cow<'a, str>, Vec<LookupTreeNode<'a>>>,
|
section_lookup_tree: HashMap<SectionHeaderName<'a>, Vec<LookupTreeNode<'a>>>,
|
||||||
/// SectionId to section mapping. The value of this HashMap contains actual
|
/// SectionId to section mapping. The value of this HashMap contains actual
|
||||||
/// events.
|
/// events.
|
||||||
///
|
///
|
||||||
|
@ -675,6 +679,7 @@ impl<'event> GitConfig<'event> {
|
||||||
// the "last one wins" resolution strategy by `git-config`).
|
// the "last one wins" resolution strategy by `git-config`).
|
||||||
let section_ids =
|
let section_ids =
|
||||||
self.get_section_ids_by_name_and_subname(section_name, subsection_name)?;
|
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() {
|
for section_id in section_ids.iter().rev() {
|
||||||
let mut found_key = false;
|
let mut found_key = false;
|
||||||
|
@ -728,6 +733,7 @@ impl<'event> GitConfig<'event> {
|
||||||
) -> Result<MutableValue<'_, 'lookup, 'event>, GitConfigError<'lookup>> {
|
) -> Result<MutableValue<'_, 'lookup, 'event>, GitConfigError<'lookup>> {
|
||||||
let section_ids =
|
let section_ids =
|
||||||
self.get_section_ids_by_name_and_subname(section_name, subsection_name)?;
|
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() {
|
for section_id in section_ids.iter().rev() {
|
||||||
let mut size = 0;
|
let mut size = 0;
|
||||||
|
@ -814,7 +820,7 @@ impl<'event> GitConfig<'event> {
|
||||||
subsection_name: Option<&'lookup str>,
|
subsection_name: Option<&'lookup str>,
|
||||||
key: &'lookup str,
|
key: &'lookup str,
|
||||||
) -> Result<Vec<Cow<'_, [u8]>>, GitConfigError<'lookup>> {
|
) -> Result<Vec<Cow<'_, [u8]>>, GitConfigError<'lookup>> {
|
||||||
let key = key;
|
let key = Key(key.into());
|
||||||
let mut values = vec![];
|
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, subsection_name)? {
|
||||||
let mut found_key = false;
|
let mut found_key = false;
|
||||||
|
@ -912,6 +918,7 @@ impl<'event> GitConfig<'event> {
|
||||||
let section_ids = self
|
let section_ids = self
|
||||||
.get_section_ids_by_name_and_subname(section_name, subsection_name)?
|
.get_section_ids_by_name_and_subname(section_name, subsection_name)?
|
||||||
.to_vec();
|
.to_vec();
|
||||||
|
let key = Key(key.into());
|
||||||
|
|
||||||
let mut offsets = HashMap::new();
|
let mut offsets = HashMap::new();
|
||||||
let mut entries = vec![];
|
let mut entries = vec![];
|
||||||
|
@ -1103,26 +1110,23 @@ impl<'event> GitConfig<'event> {
|
||||||
subsection_name: impl Into<Option<Cow<'event, str>>>,
|
subsection_name: impl Into<Option<Cow<'event, str>>>,
|
||||||
) {
|
) {
|
||||||
self.push_section(
|
self.push_section(
|
||||||
Some(section_name.into()),
|
Some(SectionHeaderName(section_name.into())),
|
||||||
subsection_name.into(),
|
subsection_name.into(),
|
||||||
&mut Some(vec![]),
|
&mut Some(vec![]),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Removes the section, returning the events it had, if any.
|
/// Removes the section, returning the events it had, if any.
|
||||||
pub fn remove_section(
|
pub fn remove_section<'lookup>(
|
||||||
&mut self,
|
&mut self,
|
||||||
section_name: impl Into<Cow<'event, str>>,
|
section_name: &'lookup str,
|
||||||
subsection_name: impl Into<Option<Cow<'event, str>>>,
|
subsection_name: impl Into<Option<&'lookup str>>,
|
||||||
) -> Option<Vec<Event>> {
|
) -> Option<Vec<Event>> {
|
||||||
let mut section_ids = self
|
let section_ids =
|
||||||
.get_section_ids_by_name_and_subname(
|
self.get_section_ids_by_name_and_subname(section_name, subsection_name.into());
|
||||||
§ion_name.into(),
|
let section_ids = section_ids.ok()?.pop()?;
|
||||||
subsection_name.into().as_deref(),
|
|
||||||
)
|
|
||||||
.ok()?;
|
|
||||||
|
|
||||||
self.sections.remove(§ion_ids.pop()?)
|
self.sections.remove(§ion_ids)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1131,7 +1135,7 @@ impl<'event> GitConfig<'event> {
|
||||||
/// Used during initialization.
|
/// Used during initialization.
|
||||||
fn push_section(
|
fn push_section(
|
||||||
&mut self,
|
&mut self,
|
||||||
current_section_name: Option<Cow<'event, str>>,
|
current_section_name: Option<SectionHeaderName<'event>>,
|
||||||
current_subsection_name: Option<Cow<'event, str>>,
|
current_subsection_name: Option<Cow<'event, str>>,
|
||||||
maybe_section: &mut Option<Vec<Event<'event>>>,
|
maybe_section: &mut Option<Vec<Event<'event>>>,
|
||||||
) {
|
) {
|
||||||
|
@ -1184,21 +1188,22 @@ impl<'event> GitConfig<'event> {
|
||||||
/// Returns the mapping between section and subsection name to section ids.
|
/// Returns the mapping between section and subsection name to section ids.
|
||||||
fn get_section_ids_by_name_and_subname<'lookup>(
|
fn get_section_ids_by_name_and_subname<'lookup>(
|
||||||
&self,
|
&self,
|
||||||
section_name: &'lookup str,
|
section_name: impl Into<SectionHeaderName<'lookup>>,
|
||||||
subsection_name: Option<&'lookup str>,
|
subsection_name: Option<&'lookup str>,
|
||||||
) -> Result<Vec<SectionId>, GitConfigError<'lookup>> {
|
) -> Result<Vec<SectionId>, GitConfigError<'lookup>> {
|
||||||
|
let section_name = section_name.into();
|
||||||
let section_ids = self
|
let section_ids = self
|
||||||
.section_lookup_tree
|
.section_lookup_tree
|
||||||
.get(section_name)
|
.get(§ion_name)
|
||||||
.ok_or(GitConfigError::SectionDoesNotExist(section_name))?;
|
.ok_or(GitConfigError::SectionDoesNotExist(section_name))?;
|
||||||
let mut maybe_ids = None;
|
let mut maybe_ids = None;
|
||||||
// Don't simplify if and matches here -- the for loop currently needs
|
// 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
|
// `n + 1` checks, while the if and matches will result in the for loop
|
||||||
// needing `2n` checks.
|
// needing `2n` checks.
|
||||||
if let Some(subsect_name) = subsection_name {
|
if let Some(subsection_name) = subsection_name {
|
||||||
for node in section_ids {
|
for node in section_ids {
|
||||||
if let LookupTreeNode::NonTerminal(subsection_lookup) = node {
|
if let LookupTreeNode::NonTerminal(subsection_lookup) = node {
|
||||||
maybe_ids = subsection_lookup.get(subsect_name);
|
maybe_ids = subsection_lookup.get(subsection_name);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1252,7 +1257,7 @@ impl<'a> From<Parser<'a>> for GitConfig<'a> {
|
||||||
};
|
};
|
||||||
|
|
||||||
// Current section that we're building
|
// Current section that we're building
|
||||||
let mut current_section_name: Option<Cow<'a, str>> = None;
|
let mut current_section_name: Option<SectionHeaderName<'_>> = None;
|
||||||
let mut current_subsection_name: Option<Cow<'a, str>> = None;
|
let mut current_subsection_name: Option<Cow<'a, str>> = None;
|
||||||
let mut maybe_section: Option<Vec<Event<'a>>> = None;
|
let mut maybe_section: Option<Vec<Event<'a>>> = None;
|
||||||
|
|
||||||
|
@ -1613,16 +1618,48 @@ mod mutable_multi_value {
|
||||||
.get_raw_multi_value_mut("core", None, "a")
|
.get_raw_multi_value_mut("core", None, "a")
|
||||||
.unwrap();
|
.unwrap();
|
||||||
values.delete_all();
|
values.delete_all();
|
||||||
|
assert!(values.get().is_err());
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
git_config.to_string(),
|
git_config.to_string(),
|
||||||
"[core]\n \n [core]\n \n ",
|
"[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)]
|
#[cfg(test)]
|
||||||
mod from_parser {
|
mod from_parser {
|
||||||
use super::{Cow, Event, GitConfig, HashMap, LookupTreeNode, SectionId, TryFrom};
|
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};
|
use crate::test_util::{name_event, newline_event, section_header, value_event};
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -1648,7 +1685,7 @@ mod from_parser {
|
||||||
let expected_lookup_tree = {
|
let expected_lookup_tree = {
|
||||||
let mut tree = HashMap::new();
|
let mut tree = HashMap::new();
|
||||||
tree.insert(
|
tree.insert(
|
||||||
Cow::Borrowed("core"),
|
SectionHeaderName(Cow::Borrowed("core")),
|
||||||
vec![LookupTreeNode::Terminal(vec![SectionId(0)])],
|
vec![LookupTreeNode::Terminal(vec![SectionId(0)])],
|
||||||
);
|
);
|
||||||
tree
|
tree
|
||||||
|
@ -1677,10 +1714,10 @@ mod from_parser {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn parse_single_subsection() {
|
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 expected_separators = {
|
||||||
let mut map = HashMap::new();
|
let mut map = HashMap::new();
|
||||||
map.insert(SectionId(0), section_header("core", (".", "subsec")));
|
map.insert(SectionId(0), section_header("core", (".", "sub")));
|
||||||
map
|
map
|
||||||
};
|
};
|
||||||
assert_eq!(config.section_headers, expected_separators);
|
assert_eq!(config.section_headers, expected_separators);
|
||||||
|
@ -1688,9 +1725,9 @@ mod from_parser {
|
||||||
let expected_lookup_tree = {
|
let expected_lookup_tree = {
|
||||||
let mut tree = HashMap::new();
|
let mut tree = HashMap::new();
|
||||||
let mut inner_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(
|
tree.insert(
|
||||||
Cow::Borrowed("core"),
|
SectionHeaderName(Cow::Borrowed("core")),
|
||||||
vec![LookupTreeNode::NonTerminal(inner_tree)],
|
vec![LookupTreeNode::NonTerminal(inner_tree)],
|
||||||
);
|
);
|
||||||
tree
|
tree
|
||||||
|
@ -1731,11 +1768,11 @@ mod from_parser {
|
||||||
let expected_lookup_tree = {
|
let expected_lookup_tree = {
|
||||||
let mut tree = HashMap::new();
|
let mut tree = HashMap::new();
|
||||||
tree.insert(
|
tree.insert(
|
||||||
Cow::Borrowed("core"),
|
SectionHeaderName(Cow::Borrowed("core")),
|
||||||
vec![LookupTreeNode::Terminal(vec![SectionId(0)])],
|
vec![LookupTreeNode::Terminal(vec![SectionId(0)])],
|
||||||
);
|
);
|
||||||
tree.insert(
|
tree.insert(
|
||||||
Cow::Borrowed("other"),
|
SectionHeaderName(Cow::Borrowed("other")),
|
||||||
vec![LookupTreeNode::Terminal(vec![SectionId(1)])],
|
vec![LookupTreeNode::Terminal(vec![SectionId(1)])],
|
||||||
);
|
);
|
||||||
tree
|
tree
|
||||||
|
@ -1784,7 +1821,7 @@ mod from_parser {
|
||||||
let expected_lookup_tree = {
|
let expected_lookup_tree = {
|
||||||
let mut tree = HashMap::new();
|
let mut tree = HashMap::new();
|
||||||
tree.insert(
|
tree.insert(
|
||||||
Cow::Borrowed("core"),
|
SectionHeaderName(Cow::Borrowed("core")),
|
||||||
vec![LookupTreeNode::Terminal(vec![SectionId(0), SectionId(1)])],
|
vec![LookupTreeNode::Terminal(vec![SectionId(0), SectionId(1)])],
|
||||||
);
|
);
|
||||||
tree
|
tree
|
||||||
|
@ -1823,6 +1860,7 @@ mod from_parser {
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod get_raw_value {
|
mod get_raw_value {
|
||||||
use super::{Cow, GitConfig, GitConfigError, TryFrom};
|
use super::{Cow, GitConfig, GitConfigError, TryFrom};
|
||||||
|
use crate::parser::{Key, SectionHeaderName};
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn single_section() {
|
fn single_section() {
|
||||||
|
@ -1860,7 +1898,9 @@ mod get_raw_value {
|
||||||
let config = GitConfig::try_from("[core]\na=b\nc=d").unwrap();
|
let config = GitConfig::try_from("[core]\na=b\nc=d").unwrap();
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
config.get_raw_value("foo", None, "a"),
|
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();
|
let config = GitConfig::try_from("[core]\na=b\nc=d").unwrap();
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
config.get_raw_value("core", None, "aaaaaa"),
|
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)]
|
#[cfg(test)]
|
||||||
mod get_raw_multi_value {
|
mod get_raw_multi_value {
|
||||||
use super::{Cow, GitConfig, GitConfigError, TryFrom};
|
use super::{Cow, GitConfig, GitConfigError, TryFrom};
|
||||||
|
use crate::parser::{Key, SectionHeaderName};
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn single_value_is_identical_to_single_value_query() {
|
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();
|
let config = GitConfig::try_from("[core]\na=b\nc=d").unwrap();
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
config.get_raw_multi_value("foo", None, "a"),
|
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();
|
let config = GitConfig::try_from("[core]\na=b\nc=d").unwrap();
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
config.get_raw_multi_value("core", None, "aaaaaa"),
|
config.get_raw_multi_value("core", None, "aaaaaa"),
|
||||||
Err(GitConfigError::KeyDoesNotExist("aaaaaa"))
|
Err(GitConfigError::KeyDoesNotExist(Key("aaaaaa".into())))
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
112
src/parser.rs
112
src/parser.rs
|
@ -18,9 +18,10 @@ use nom::error::{Error as NomError, ErrorKind};
|
||||||
use nom::multi::{many0, many1};
|
use nom::multi::{many0, many1};
|
||||||
use nom::sequence::delimited;
|
use nom::sequence::delimited;
|
||||||
use nom::IResult;
|
use nom::IResult;
|
||||||
use std::borrow::Cow;
|
use std::convert::TryFrom;
|
||||||
|
use std::fmt::Display;
|
||||||
use std::iter::FusedIterator;
|
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
|
/// Syntactic events that occurs in the config. Despite all these variants
|
||||||
/// holding a [`Cow`] instead over a simple reference, the parser will only emit
|
/// holding a [`Cow`] instead over a simple reference, the parser will only emit
|
||||||
|
@ -42,7 +43,7 @@ pub enum Event<'a> {
|
||||||
/// exists.
|
/// exists.
|
||||||
SectionHeader(ParsedSectionHeader<'a>),
|
SectionHeader(ParsedSectionHeader<'a>),
|
||||||
/// A name to a value in a section.
|
/// 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,
|
/// 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
|
/// 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
|
/// 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::Comment(e) => e.fmt(f),
|
||||||
Self::SectionHeader(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, "="),
|
Self::KeyValueSeparator => write!(f, "="),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -110,7 +112,8 @@ impl Into<Vec<u8>> for &Event<'_> {
|
||||||
Event::Value(e) | Event::ValueNotDone(e) | Event::ValueDone(e) => e.to_vec(),
|
Event::Value(e) | Event::ValueNotDone(e) | Event::ValueDone(e) => e.to_vec(),
|
||||||
Event::Comment(e) => e.into(),
|
Event::Comment(e) => e.into(),
|
||||||
Event::SectionHeader(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'='],
|
Event::KeyValueSeparator => vec![b'='],
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -143,7 +146,7 @@ impl Display for ParsedSection<'_> {
|
||||||
#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug, Default)]
|
#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug, Default)]
|
||||||
pub struct ParsedSectionHeader<'a> {
|
pub struct ParsedSectionHeader<'a> {
|
||||||
/// The name of the header.
|
/// 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.
|
/// The separator used to determine if the section contains a subsection.
|
||||||
/// This is either a period `.` or a string of whitespace. Note that
|
/// This is either a period `.` or a string of whitespace. Note that
|
||||||
/// reconstruction of subsection format is dependent on this value. If this
|
/// reconstruction of subsection format is dependent on this value. If this
|
||||||
|
@ -154,6 +157,69 @@ pub struct ParsedSectionHeader<'a> {
|
||||||
pub subsection_name: Option<Cow<'a, str>>,
|
pub subsection_name: Option<Cow<'a, str>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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<std::cmp::Ordering> {
|
||||||
|
self.0
|
||||||
|
.to_ascii_lowercase()
|
||||||
|
.partial_cmp(&other.0.to_ascii_lowercase())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::hash::Hash for $name<'_> {
|
||||||
|
fn hash<H: std::hash::Hasher>(&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<Cow<'a, str>> 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<'_> {
|
impl ParsedSectionHeader<'_> {
|
||||||
/// Generates a byte representation of the value. This should be used when
|
/// 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
|
/// 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:
|
/// 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;
|
/// # use std::borrow::Cow;
|
||||||
/// # let section_header = ParsedSectionHeader {
|
/// # let section_header = ParsedSectionHeader {
|
||||||
/// # name: Cow::Borrowed("core"),
|
/// # name: SectionHeaderName(Cow::Borrowed("core")),
|
||||||
/// # separator: None,
|
/// # separator: None,
|
||||||
/// # subsection_name: None,
|
/// # subsection_name: None,
|
||||||
/// # };
|
/// # };
|
||||||
|
@ -398,7 +464,7 @@ impl Display for ParserNode {
|
||||||
/// Event::SectionHeader(section_header),
|
/// Event::SectionHeader(section_header),
|
||||||
/// Event::Newline(Cow::Borrowed("\n")),
|
/// Event::Newline(Cow::Borrowed("\n")),
|
||||||
/// Event::Whitespace(Cow::Borrowed(" ")),
|
/// Event::Whitespace(Cow::Borrowed(" ")),
|
||||||
/// Event::Key(Cow::Borrowed("autocrlf")),
|
/// Event::Key(Key(Cow::Borrowed("autocrlf"))),
|
||||||
/// Event::Whitespace(Cow::Borrowed(" ")),
|
/// Event::Whitespace(Cow::Borrowed(" ")),
|
||||||
/// Event::KeyValueSeparator,
|
/// Event::KeyValueSeparator,
|
||||||
/// Event::Whitespace(Cow::Borrowed(" ")),
|
/// Event::Whitespace(Cow::Borrowed(" ")),
|
||||||
|
@ -425,10 +491,10 @@ impl Display for ParserNode {
|
||||||
/// which means that the corresponding event won't appear either:
|
/// 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;
|
/// # use std::borrow::Cow;
|
||||||
/// # let section_header = ParsedSectionHeader {
|
/// # let section_header = ParsedSectionHeader {
|
||||||
/// # name: Cow::Borrowed("core"),
|
/// # name: SectionHeaderName(Cow::Borrowed("core")),
|
||||||
/// # separator: None,
|
/// # separator: None,
|
||||||
/// # subsection_name: None,
|
/// # subsection_name: None,
|
||||||
/// # };
|
/// # };
|
||||||
|
@ -437,7 +503,7 @@ impl Display for ParserNode {
|
||||||
/// Event::SectionHeader(section_header),
|
/// Event::SectionHeader(section_header),
|
||||||
/// Event::Newline(Cow::Borrowed("\n")),
|
/// Event::Newline(Cow::Borrowed("\n")),
|
||||||
/// Event::Whitespace(Cow::Borrowed(" ")),
|
/// Event::Whitespace(Cow::Borrowed(" ")),
|
||||||
/// Event::Key(Cow::Borrowed("autocrlf")),
|
/// Event::Key(Key(Cow::Borrowed("autocrlf"))),
|
||||||
/// Event::Value(Cow::Borrowed(b"")),
|
/// Event::Value(Cow::Borrowed(b"")),
|
||||||
/// # ]);
|
/// # ]);
|
||||||
/// ```
|
/// ```
|
||||||
|
@ -459,10 +525,10 @@ impl Display for ParserNode {
|
||||||
/// relevant event stream emitted is thus emitted as:
|
/// 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;
|
/// # use std::borrow::Cow;
|
||||||
/// # let section_header = ParsedSectionHeader {
|
/// # let section_header = ParsedSectionHeader {
|
||||||
/// # name: Cow::Borrowed("core"),
|
/// # name: SectionHeaderName(Cow::Borrowed("core")),
|
||||||
/// # separator: None,
|
/// # separator: None,
|
||||||
/// # subsection_name: None,
|
/// # subsection_name: None,
|
||||||
/// # };
|
/// # };
|
||||||
|
@ -470,11 +536,11 @@ impl Display for ParserNode {
|
||||||
/// # assert_eq!(parse_from_str(section_data).unwrap().into_vec(), vec![
|
/// # assert_eq!(parse_from_str(section_data).unwrap().into_vec(), vec![
|
||||||
/// Event::SectionHeader(section_header),
|
/// Event::SectionHeader(section_header),
|
||||||
/// Event::Newline(Cow::Borrowed("\n")),
|
/// Event::Newline(Cow::Borrowed("\n")),
|
||||||
/// Event::Key(Cow::Borrowed("autocrlf")),
|
/// Event::Key(Key(Cow::Borrowed("autocrlf"))),
|
||||||
/// Event::KeyValueSeparator,
|
/// Event::KeyValueSeparator,
|
||||||
/// Event::Value(Cow::Borrowed(br#"true"""#)),
|
/// Event::Value(Cow::Borrowed(br#"true"""#)),
|
||||||
/// Event::Newline(Cow::Borrowed("\n")),
|
/// Event::Newline(Cow::Borrowed("\n")),
|
||||||
/// Event::Key(Cow::Borrowed("filemode")),
|
/// Event::Key(Key(Cow::Borrowed("filemode"))),
|
||||||
/// Event::KeyValueSeparator,
|
/// Event::KeyValueSeparator,
|
||||||
/// Event::Value(Cow::Borrowed(br#"fa"lse""#)),
|
/// Event::Value(Cow::Borrowed(br#"fa"lse""#)),
|
||||||
/// # ]);
|
/// # ]);
|
||||||
|
@ -496,10 +562,10 @@ impl Display for ParserNode {
|
||||||
/// split value accordingly:
|
/// 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;
|
/// # use std::borrow::Cow;
|
||||||
/// # let section_header = ParsedSectionHeader {
|
/// # let section_header = ParsedSectionHeader {
|
||||||
/// # name: Cow::Borrowed("some-section"),
|
/// # name: SectionHeaderName(Cow::Borrowed("some-section")),
|
||||||
/// # separator: None,
|
/// # separator: None,
|
||||||
/// # subsection_name: None,
|
/// # subsection_name: None,
|
||||||
/// # };
|
/// # };
|
||||||
|
@ -507,7 +573,7 @@ impl Display for ParserNode {
|
||||||
/// # assert_eq!(parse_from_str(section_data).unwrap().into_vec(), vec![
|
/// # assert_eq!(parse_from_str(section_data).unwrap().into_vec(), vec![
|
||||||
/// Event::SectionHeader(section_header),
|
/// Event::SectionHeader(section_header),
|
||||||
/// Event::Newline(Cow::Borrowed("\n")),
|
/// Event::Newline(Cow::Borrowed("\n")),
|
||||||
/// Event::Key(Cow::Borrowed("file")),
|
/// Event::Key(Key(Cow::Borrowed("file"))),
|
||||||
/// Event::KeyValueSeparator,
|
/// Event::KeyValueSeparator,
|
||||||
/// Event::ValueNotDone(Cow::Borrowed(b"a")),
|
/// Event::ValueNotDone(Cow::Borrowed(b"a")),
|
||||||
/// Event::Newline(Cow::Borrowed("\n")),
|
/// Event::Newline(Cow::Borrowed("\n")),
|
||||||
|
@ -772,12 +838,12 @@ fn section_header(i: &[u8]) -> IResult<&[u8], ParsedSectionHeader> {
|
||||||
// subsection syntax at this point.
|
// subsection syntax at this point.
|
||||||
let header = match memchr::memrchr(b'.', name.as_bytes()) {
|
let header = match memchr::memrchr(b'.', name.as_bytes()) {
|
||||||
Some(index) => ParsedSectionHeader {
|
Some(index) => ParsedSectionHeader {
|
||||||
name: Cow::Borrowed(&name[..index]),
|
name: SectionHeaderName(Cow::Borrowed(&name[..index])),
|
||||||
separator: name.get(index..=index).map(|slice| Cow::Borrowed(slice)),
|
separator: name.get(index..=index).map(|slice| Cow::Borrowed(slice)),
|
||||||
subsection_name: name.get(index + 1..).map(|slice| Cow::Borrowed(slice)),
|
subsection_name: name.get(index + 1..).map(|slice| Cow::Borrowed(slice)),
|
||||||
},
|
},
|
||||||
None => ParsedSectionHeader {
|
None => ParsedSectionHeader {
|
||||||
name: Cow::Borrowed(name),
|
name: SectionHeaderName(Cow::Borrowed(name)),
|
||||||
separator: None,
|
separator: None,
|
||||||
subsection_name: None,
|
subsection_name: None,
|
||||||
},
|
},
|
||||||
|
@ -808,7 +874,7 @@ fn section_header(i: &[u8]) -> IResult<&[u8], ParsedSectionHeader> {
|
||||||
Ok((
|
Ok((
|
||||||
i,
|
i,
|
||||||
ParsedSectionHeader {
|
ParsedSectionHeader {
|
||||||
name: Cow::Borrowed(name),
|
name: SectionHeaderName(Cow::Borrowed(name)),
|
||||||
separator: Some(Cow::Borrowed(whitespace)),
|
separator: Some(Cow::Borrowed(whitespace)),
|
||||||
// We know that there's some section name here, so if we get an
|
// 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.
|
// empty vec here then we actually parsed an empty section name.
|
||||||
|
@ -826,7 +892,7 @@ fn section_body<'a, 'b, 'c>(
|
||||||
*node = ParserNode::ConfigName;
|
*node = ParserNode::ConfigName;
|
||||||
let (i, name) = config_name(i)?;
|
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)?;
|
let (i, whitespace) = opt(take_spaces)(i)?;
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
//! This module is only included for tests, and contains common unit test helper
|
//! This module is only included for tests, and contains common unit test helper
|
||||||
//! functions.
|
//! functions.
|
||||||
|
|
||||||
use crate::parser::{Event, ParsedComment, ParsedSectionHeader};
|
use crate::parser::{Event, Key, ParsedComment, ParsedSectionHeader};
|
||||||
use std::borrow::Cow;
|
use std::borrow::Cow;
|
||||||
|
|
||||||
pub fn section_header_event(
|
pub fn section_header_event(
|
||||||
|
@ -32,7 +32,7 @@ pub fn section_header(
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn name_event(name: &'static str) -> Event<'static> {
|
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> {
|
pub(crate) fn value_event(value: &'static str) -> Event<'static> {
|
||||||
|
|
|
@ -97,3 +97,33 @@ fn get_value_looks_up_all_sections_before_failing() -> Result<(), Box<dyn std::e
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn section_names_are_case_insensitive() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
|
let config = "[core] bool-implicit";
|
||||||
|
let file = GitConfig::try_from(config)?;
|
||||||
|
assert!(file
|
||||||
|
.get_value::<Boolean>("core", None, "bool-implicit")
|
||||||
|
.is_ok());
|
||||||
|
assert_eq!(
|
||||||
|
file.get_value::<Boolean>("core", None, "bool-implicit"),
|
||||||
|
file.get_value::<Boolean>("CORE", None, "bool-implicit")
|
||||||
|
);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn value_names_are_case_insensitive() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
|
let config = "[core]
|
||||||
|
a = true
|
||||||
|
A = false";
|
||||||
|
let file = GitConfig::try_from(config)?;
|
||||||
|
assert_eq!(file.get_multi_value::<Boolean>("core", None, "a")?.len(), 2);
|
||||||
|
assert_eq!(
|
||||||
|
file.get_value::<Boolean>("core", None, "a"),
|
||||||
|
file.get_value::<Boolean>("core", None, "A")
|
||||||
|
);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
|
use git_config::parser::{parse_from_str, Event, Key, ParsedSectionHeader, SectionHeaderName};
|
||||||
use std::borrow::Cow;
|
use std::borrow::Cow;
|
||||||
|
|
||||||
use git_config::parser::{parse_from_str, Event, ParsedSectionHeader};
|
|
||||||
|
|
||||||
pub fn section_header_event(
|
pub fn section_header_event(
|
||||||
name: &str,
|
name: &str,
|
||||||
subsection: impl Into<Option<(&'static str, &'static str)>>,
|
subsection: impl Into<Option<(&'static str, &'static str)>>,
|
||||||
|
@ -13,7 +12,7 @@ pub fn section_header(
|
||||||
name: &str,
|
name: &str,
|
||||||
subsection: impl Into<Option<(&'static str, &'static str)>>,
|
subsection: impl Into<Option<(&'static str, &'static str)>>,
|
||||||
) -> ParsedSectionHeader<'_> {
|
) -> ParsedSectionHeader<'_> {
|
||||||
let name = Cow::Borrowed(name);
|
let name = SectionHeaderName(Cow::Borrowed(name));
|
||||||
if let Some((separator, subsection_name)) = subsection.into() {
|
if let Some((separator, subsection_name)) = subsection.into() {
|
||||||
ParsedSectionHeader {
|
ParsedSectionHeader {
|
||||||
name,
|
name,
|
||||||
|
@ -30,7 +29,7 @@ pub fn section_header(
|
||||||
}
|
}
|
||||||
|
|
||||||
fn name(name: &'static str) -> Event<'static> {
|
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> {
|
fn value(value: &'static str) -> Event<'static> {
|
||||||
|
|
Reference in a new issue