gitconfig writing to string
This commit is contained in:
parent
7f7a7e073d
commit
f10afb7894
3 changed files with 254 additions and 41 deletions
178
src/config.rs
178
src/config.rs
|
@ -1,7 +1,7 @@
|
||||||
use crate::parser::{parse_from_str, Event, Parser, ParserError};
|
use crate::parser::{parse_from_str, Event, ParsedSectionHeader, Parser, ParserError};
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
use std::borrow::Cow;
|
|
||||||
use std::collections::{HashMap, VecDeque};
|
use std::collections::{HashMap, VecDeque};
|
||||||
|
use std::{borrow::Cow, fmt::Display};
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Eq)]
|
#[derive(Debug, PartialEq, Eq)]
|
||||||
pub enum GitConfigError<'a> {
|
pub enum GitConfigError<'a> {
|
||||||
|
@ -23,7 +23,7 @@ pub struct GitConfig<'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
|
||||||
sections: HashMap<SectionId, Vec<Event<'a>>>,
|
sections: HashMap<SectionId, Vec<Event<'a>>>,
|
||||||
section_header_separators: HashMap<SectionId, Option<Cow<'a, str>>>,
|
section_headers: HashMap<SectionId, ParsedSectionHeader<'a>>,
|
||||||
section_id_counter: usize,
|
section_id_counter: usize,
|
||||||
section_order: VecDeque<SectionId>,
|
section_order: VecDeque<SectionId>,
|
||||||
}
|
}
|
||||||
|
@ -60,7 +60,7 @@ impl<'a> GitConfig<'a> {
|
||||||
front_matter_events: vec![],
|
front_matter_events: vec![],
|
||||||
sections: HashMap::new(),
|
sections: HashMap::new(),
|
||||||
section_lookup_tree: HashMap::new(),
|
section_lookup_tree: HashMap::new(),
|
||||||
section_header_separators: HashMap::new(),
|
section_headers: HashMap::new(),
|
||||||
section_id_counter: 0,
|
section_id_counter: 0,
|
||||||
section_order: VecDeque::new(),
|
section_order: VecDeque::new(),
|
||||||
};
|
};
|
||||||
|
@ -80,16 +80,16 @@ impl<'a> GitConfig<'a> {
|
||||||
);
|
);
|
||||||
|
|
||||||
// Initialize new section
|
// Initialize new section
|
||||||
let (name, subname) = (header.name, header.subsection_name);
|
|
||||||
maybe_section = Some(vec![]);
|
|
||||||
current_section_name = Some(name);
|
|
||||||
current_subsection_name = subname;
|
|
||||||
// We need to store the new, current id counter, so don't
|
// We need to store the new, current id counter, so don't
|
||||||
// use new_section_id here and use the already incremented
|
// use new_section_id here and use the already incremented
|
||||||
// section id value.
|
// section id value.
|
||||||
new_self
|
new_self
|
||||||
.section_header_separators
|
.section_headers
|
||||||
.insert(SectionId(new_self.section_id_counter), header.separator);
|
.insert(SectionId(new_self.section_id_counter), header.clone());
|
||||||
|
let (name, subname) = (header.name, header.subsection_name);
|
||||||
|
maybe_section = Some(vec![]);
|
||||||
|
current_section_name = Some(name);
|
||||||
|
current_subsection_name = subname;
|
||||||
}
|
}
|
||||||
e @ Event::Key(_)
|
e @ Event::Key(_)
|
||||||
| e @ Event::Value(_)
|
| e @ Event::Value(_)
|
||||||
|
@ -513,6 +513,62 @@ impl<'a> GitConfig<'a> {
|
||||||
*value = new_value.into();
|
*value = new_value.into();
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Sets a multivar in a given section, optional subsection, and key value.
|
||||||
|
///
|
||||||
|
/// This internally zips together the new values and the existing values.
|
||||||
|
/// As a result, if more new values are provided than the current amount of
|
||||||
|
/// multivars, then the latter values are not applied. If there are less
|
||||||
|
/// new values than old ones then the remaining old values are unmodified.
|
||||||
|
///
|
||||||
|
/// If you need finer control over which values of the multivar are set,
|
||||||
|
/// consider using [`get_raw_multi_value_mut`].
|
||||||
|
///
|
||||||
|
/// todo: examples and errors
|
||||||
|
///
|
||||||
|
/// [`get_raw_multi_value_mut`]: Self::get_raw_multi_value_mut
|
||||||
|
pub fn set_raw_multi_value<'b>(
|
||||||
|
&mut self,
|
||||||
|
section_name: &'b str,
|
||||||
|
subsection_name: Option<&'b str>,
|
||||||
|
key: &'b str,
|
||||||
|
new_values: Vec<Cow<'a, str>>,
|
||||||
|
) -> Result<(), GitConfigError<'b>> {
|
||||||
|
let values = self.get_raw_multi_value_mut(section_name, subsection_name, key)?;
|
||||||
|
for (old, new) in values.into_iter().zip(new_values) {
|
||||||
|
*old = new;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for GitConfig<'_> {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
for front_matter in &self.front_matter_events {
|
||||||
|
front_matter.fmt(f)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
for section_id in &self.section_order {
|
||||||
|
self.section_headers.get(section_id).unwrap().fmt(f)?;
|
||||||
|
let mut found_key = false;
|
||||||
|
for event in self.sections.get(section_id).unwrap() {
|
||||||
|
match event {
|
||||||
|
Event::Key(k) => {
|
||||||
|
found_key = true;
|
||||||
|
k.fmt(f)?;
|
||||||
|
}
|
||||||
|
Event::Whitespace(w) if found_key => {
|
||||||
|
found_key = false;
|
||||||
|
w.fmt(f)?;
|
||||||
|
write!(f, "=")?;
|
||||||
|
}
|
||||||
|
e => e.fmt(f)?,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
@ -522,7 +578,7 @@ mod from_parser {
|
||||||
#[test]
|
#[test]
|
||||||
fn parse_empty() {
|
fn parse_empty() {
|
||||||
let config = GitConfig::from_str("").unwrap();
|
let config = GitConfig::from_str("").unwrap();
|
||||||
assert!(config.section_header_separators.is_empty());
|
assert!(config.section_headers.is_empty());
|
||||||
assert_eq!(config.section_id_counter, 0);
|
assert_eq!(config.section_id_counter, 0);
|
||||||
assert!(config.section_lookup_tree.is_empty());
|
assert!(config.section_lookup_tree.is_empty());
|
||||||
assert!(config.sections.is_empty());
|
assert!(config.sections.is_empty());
|
||||||
|
@ -534,10 +590,17 @@ mod from_parser {
|
||||||
let mut 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 expected_separators = {
|
||||||
let mut map = HashMap::new();
|
let mut map = HashMap::new();
|
||||||
map.insert(SectionId(0), None);
|
map.insert(
|
||||||
|
SectionId(0),
|
||||||
|
ParsedSectionHeader {
|
||||||
|
name: "core".into(),
|
||||||
|
separator: None,
|
||||||
|
subsection_name: None,
|
||||||
|
},
|
||||||
|
);
|
||||||
map
|
map
|
||||||
};
|
};
|
||||||
assert_eq!(config.section_header_separators, expected_separators);
|
assert_eq!(config.section_headers, expected_separators);
|
||||||
assert_eq!(config.section_id_counter, 1);
|
assert_eq!(config.section_id_counter, 1);
|
||||||
let expected_lookup_tree = {
|
let expected_lookup_tree = {
|
||||||
let mut tree = HashMap::new();
|
let mut tree = HashMap::new();
|
||||||
|
@ -572,10 +635,17 @@ mod from_parser {
|
||||||
let mut 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 expected_separators = {
|
||||||
let mut map = HashMap::new();
|
let mut map = HashMap::new();
|
||||||
map.insert(SectionId(0), Some(Cow::Borrowed(".")));
|
map.insert(
|
||||||
|
SectionId(0),
|
||||||
|
ParsedSectionHeader {
|
||||||
|
name: "core".into(),
|
||||||
|
separator: Some(".".into()),
|
||||||
|
subsection_name: Some("subsec".into()),
|
||||||
|
},
|
||||||
|
);
|
||||||
map
|
map
|
||||||
};
|
};
|
||||||
assert_eq!(config.section_header_separators, expected_separators);
|
assert_eq!(config.section_headers, expected_separators);
|
||||||
assert_eq!(config.section_id_counter, 1);
|
assert_eq!(config.section_id_counter, 1);
|
||||||
let expected_lookup_tree = {
|
let expected_lookup_tree = {
|
||||||
let mut tree = HashMap::new();
|
let mut tree = HashMap::new();
|
||||||
|
@ -612,11 +682,25 @@ mod from_parser {
|
||||||
let mut config = GitConfig::from_str("[core]\na=b\nc=d\n[other]e=f").unwrap();
|
let mut config = GitConfig::from_str("[core]\na=b\nc=d\n[other]e=f").unwrap();
|
||||||
let expected_separators = {
|
let expected_separators = {
|
||||||
let mut map = HashMap::new();
|
let mut map = HashMap::new();
|
||||||
map.insert(SectionId(0), None);
|
map.insert(
|
||||||
map.insert(SectionId(1), None);
|
SectionId(0),
|
||||||
|
ParsedSectionHeader {
|
||||||
|
name: "core".into(),
|
||||||
|
separator: None,
|
||||||
|
subsection_name: None,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
map.insert(
|
||||||
|
SectionId(1),
|
||||||
|
ParsedSectionHeader {
|
||||||
|
name: "other".into(),
|
||||||
|
separator: None,
|
||||||
|
subsection_name: None,
|
||||||
|
},
|
||||||
|
);
|
||||||
map
|
map
|
||||||
};
|
};
|
||||||
assert_eq!(config.section_header_separators, expected_separators);
|
assert_eq!(config.section_headers, expected_separators);
|
||||||
assert_eq!(config.section_id_counter, 2);
|
assert_eq!(config.section_id_counter, 2);
|
||||||
let expected_lookup_tree = {
|
let expected_lookup_tree = {
|
||||||
let mut tree = HashMap::new();
|
let mut tree = HashMap::new();
|
||||||
|
@ -666,11 +750,25 @@ mod from_parser {
|
||||||
let mut 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[core]e=f").unwrap();
|
||||||
let expected_separators = {
|
let expected_separators = {
|
||||||
let mut map = HashMap::new();
|
let mut map = HashMap::new();
|
||||||
map.insert(SectionId(0), None);
|
map.insert(
|
||||||
map.insert(SectionId(1), None);
|
SectionId(0),
|
||||||
|
ParsedSectionHeader {
|
||||||
|
name: "core".into(),
|
||||||
|
separator: None,
|
||||||
|
subsection_name: None,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
map.insert(
|
||||||
|
SectionId(1),
|
||||||
|
ParsedSectionHeader {
|
||||||
|
name: "core".into(),
|
||||||
|
separator: None,
|
||||||
|
subsection_name: None,
|
||||||
|
},
|
||||||
|
);
|
||||||
map
|
map
|
||||||
};
|
};
|
||||||
assert_eq!(config.section_header_separators, expected_separators);
|
assert_eq!(config.section_headers, expected_separators);
|
||||||
assert_eq!(config.section_id_counter, 2);
|
assert_eq!(config.section_id_counter, 2);
|
||||||
let expected_lookup_tree = {
|
let expected_lookup_tree = {
|
||||||
let mut tree = HashMap::new();
|
let mut tree = HashMap::new();
|
||||||
|
@ -868,3 +966,41 @@ mod get_raw_multi_value {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod display {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn can_reconstruct_empty_config() {
|
||||||
|
let config = r#"
|
||||||
|
|
||||||
|
"#;
|
||||||
|
assert_eq!(GitConfig::from_str(config).unwrap().to_string(), config);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn can_reconstruct_non_empty_config() {
|
||||||
|
let config = r#"[user]
|
||||||
|
email = code@eddie.sh
|
||||||
|
name = Edward Shen
|
||||||
|
[core]
|
||||||
|
autocrlf = input
|
||||||
|
[push]
|
||||||
|
default = simple
|
||||||
|
[commit]
|
||||||
|
gpgsign = true
|
||||||
|
[gpg]
|
||||||
|
program = gpg
|
||||||
|
[url "ssh://git@github.com/"]
|
||||||
|
insteadOf = "github://"
|
||||||
|
[url "ssh://git@git.eddie.sh/edward/"]
|
||||||
|
insteadOf = "gitea://"
|
||||||
|
[pull]
|
||||||
|
ff = only
|
||||||
|
[init]
|
||||||
|
defaultBranch = master"#;
|
||||||
|
|
||||||
|
assert_eq!(GitConfig::from_str(config).unwrap().to_string(), config);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -17,7 +17,9 @@ use nom::error::{Error as NomError, ErrorKind};
|
||||||
use nom::sequence::delimited;
|
use nom::sequence::delimited;
|
||||||
use nom::IResult;
|
use nom::IResult;
|
||||||
use nom::{branch::alt, multi::many0};
|
use nom::{branch::alt, multi::many0};
|
||||||
use std::{borrow::Cow, iter::FusedIterator};
|
use std::borrow::{Borrow, Cow};
|
||||||
|
use std::fmt::Display;
|
||||||
|
use std::iter::FusedIterator;
|
||||||
|
|
||||||
/// Syntactic events that occurs in the config.
|
/// Syntactic events that occurs in the config.
|
||||||
#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)]
|
#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)]
|
||||||
|
@ -38,7 +40,8 @@ pub enum Event<'a> {
|
||||||
Value(Cow<'a, str>),
|
Value(Cow<'a, str>),
|
||||||
/// Represents any token used to signify a new line character. On Unix
|
/// Represents any token used to signify a new line character. On Unix
|
||||||
/// platforms, this is typically just `\n`, but can be any valid newline
|
/// platforms, this is typically just `\n`, but can be any valid newline
|
||||||
/// sequence.
|
/// sequence. Multiple newlines (such as `\n\n`) will be merged as a single
|
||||||
|
/// newline event.
|
||||||
Newline(Cow<'a, str>),
|
Newline(Cow<'a, str>),
|
||||||
/// Any value that isn't completed. This occurs when the value is continued
|
/// 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
|
/// onto the next line. A Newline event is guaranteed after, followed by
|
||||||
|
@ -51,6 +54,21 @@ pub enum Event<'a> {
|
||||||
Whitespace(Cow<'a, str>),
|
Whitespace(Cow<'a, str>),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Display for Event<'_> {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
match self {
|
||||||
|
Self::Comment(e) => e.fmt(f),
|
||||||
|
Self::SectionHeader(e) => e.fmt(f),
|
||||||
|
Self::Key(e) => e.fmt(f),
|
||||||
|
Self::Value(e) => e.fmt(f),
|
||||||
|
Self::Newline(e) => e.fmt(f),
|
||||||
|
Self::ValueNotDone(e) => e.fmt(f),
|
||||||
|
Self::ValueDone(e) => e.fmt(f),
|
||||||
|
Self::Whitespace(e) => e.fmt(f),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// A parsed section containing the header and the section events.
|
/// A parsed section containing the header and the section events.
|
||||||
#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug, Default)]
|
#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug, Default)]
|
||||||
pub struct ParsedSection<'a> {
|
pub struct ParsedSection<'a> {
|
||||||
|
@ -59,6 +77,15 @@ pub struct ParsedSection<'a> {
|
||||||
/// The syntactic events found in this section.
|
/// The syntactic events found in this section.
|
||||||
pub events: Vec<Event<'a>>,
|
pub events: Vec<Event<'a>>,
|
||||||
}
|
}
|
||||||
|
impl Display for ParsedSection<'_> {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
write!(f, "{}", self.section_header)?;
|
||||||
|
for event in &self.events {
|
||||||
|
event.fmt(f)?;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// A parsed section header, containing a name and optionally a subsection name.
|
/// A parsed section header, containing a name and optionally a subsection name.
|
||||||
#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug, Default)]
|
#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug, Default)]
|
||||||
|
@ -75,6 +102,23 @@ pub struct ParsedSectionHeader<'a> {
|
||||||
pub subsection_name: Option<Cow<'a, str>>,
|
pub subsection_name: Option<Cow<'a, str>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Display for ParsedSectionHeader<'_> {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
write!(f, "[{}", self.name)?;
|
||||||
|
|
||||||
|
if let Some(v) = &self.separator {
|
||||||
|
v.fmt(f)?;
|
||||||
|
let subsection_name = self.subsection_name.as_ref().unwrap();
|
||||||
|
match v.borrow() {
|
||||||
|
"." => subsection_name.fmt(f)?,
|
||||||
|
_ => write!(f, "\"{}\"", subsection_name)?,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
write!(f, "]")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// A parsed comment event containing the comment marker and comment.
|
/// A parsed comment event containing the comment marker and comment.
|
||||||
#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug, Default)]
|
#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug, Default)]
|
||||||
pub struct ParsedComment<'a> {
|
pub struct ParsedComment<'a> {
|
||||||
|
@ -84,6 +128,13 @@ pub struct ParsedComment<'a> {
|
||||||
pub comment: Cow<'a, str>,
|
pub comment: Cow<'a, str>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Display for ParsedComment<'_> {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
self.comment_tag.fmt(f)?;
|
||||||
|
self.comment.fmt(f)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// The various parsing failure reasons.
|
/// The various parsing failure reasons.
|
||||||
#[derive(PartialEq, Debug)]
|
#[derive(PartialEq, Debug)]
|
||||||
pub enum ParserError<'a> {
|
pub enum ParserError<'a> {
|
||||||
|
@ -275,7 +326,7 @@ impl<'a> From<nom::Err<NomError<&'a str>>> for ParserError<'a> {
|
||||||
/// [`FromStr`]: std::str::FromStr
|
/// [`FromStr`]: std::str::FromStr
|
||||||
#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug, Default)]
|
#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug, Default)]
|
||||||
pub struct Parser<'a> {
|
pub struct Parser<'a> {
|
||||||
init_comments: Vec<ParsedComment<'a>>,
|
frontmatter: Vec<Event<'a>>,
|
||||||
sections: Vec<ParsedSection<'a>>,
|
sections: Vec<ParsedSection<'a>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -296,20 +347,21 @@ impl<'a> Parser<'a> {
|
||||||
parse_from_str(s)
|
parse_from_str(s)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the leading comments (any comments before a section) from the
|
/// Returns the leading events (any comments, whitespace, or newlines before
|
||||||
/// parser. Consider [`Parser::take_leading_comments`] if you need an owned
|
/// a section) from the parser. Consider [`Parser::take_frontmatter`] if
|
||||||
/// copy only once. If that function was called, then this will always
|
/// you need an owned copy only once. If that function was called, then this
|
||||||
/// return an empty slice.
|
/// will always return an empty slice.
|
||||||
pub fn leading_comments(&self) -> &[ParsedComment<'a>] {
|
pub fn frontmatter(&self) -> &[Event<'a>] {
|
||||||
&self.init_comments
|
&self.frontmatter
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Takes the leading comments (any comments before a section) from the
|
/// Takes the leading events (any comments, whitespace, or newlines before
|
||||||
/// parser. Subsequent calls will return an empty vec. Consider
|
/// a section) from the parser. Subsequent calls will return an empty vec.
|
||||||
/// [`Parser::leading_comments`] if you only need a reference to the comments.
|
/// Consider [`Parser::frontmatter`] if you only need a reference to the
|
||||||
pub fn take_leading_comments(&mut self) -> Vec<ParsedComment<'a>> {
|
/// frontmatter
|
||||||
|
pub fn take_frontmatter(&mut self) -> Vec<Event<'a>> {
|
||||||
let mut to_return = vec![];
|
let mut to_return = vec![];
|
||||||
std::mem::swap(&mut self.init_comments, &mut to_return);
|
std::mem::swap(&mut self.frontmatter, &mut to_return);
|
||||||
to_return
|
to_return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -345,10 +397,7 @@ impl<'a> Parser<'a> {
|
||||||
.chain(section.events)
|
.chain(section.events)
|
||||||
})
|
})
|
||||||
.flatten();
|
.flatten();
|
||||||
self.init_comments
|
self.frontmatter.into_iter().chain(section_iter)
|
||||||
.into_iter()
|
|
||||||
.map(Event::Comment)
|
|
||||||
.chain(section_iter)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -363,7 +412,13 @@ impl<'a> Parser<'a> {
|
||||||
/// This generally is due to either invalid names or if there's extraneous
|
/// This generally is due to either invalid names or if there's extraneous
|
||||||
/// data succeeding valid `git-config` data.
|
/// data succeeding valid `git-config` data.
|
||||||
pub fn parse_from_str(input: &str) -> Result<Parser<'_>, ParserError> {
|
pub fn parse_from_str(input: &str) -> Result<Parser<'_>, ParserError> {
|
||||||
let (i, comments) = many0(comment)(input)?;
|
let (i, frontmatter) = many0(alt((
|
||||||
|
map(comment, |comment| Event::Comment(comment)),
|
||||||
|
map(take_spaces, |whitespace| {
|
||||||
|
Event::Whitespace(whitespace.into())
|
||||||
|
}),
|
||||||
|
map(take_newline, |newline| Event::Newline(newline.into())),
|
||||||
|
)))(input)?;
|
||||||
let (i, sections) = many0(section)(i)?;
|
let (i, sections) = many0(section)(i)?;
|
||||||
|
|
||||||
if !i.is_empty() {
|
if !i.is_empty() {
|
||||||
|
@ -371,7 +426,7 @@ pub fn parse_from_str(input: &str) -> Result<Parser<'_>, ParserError> {
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(Parser {
|
Ok(Parser {
|
||||||
init_comments: comments,
|
frontmatter,
|
||||||
sections,
|
sections,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -170,3 +170,25 @@ fn personal_config() {
|
||||||
fn parse_empty() {
|
fn parse_empty() {
|
||||||
assert_eq!(parse_from_str("").unwrap().into_vec(), vec![]);
|
assert_eq!(parse_from_str("").unwrap().into_vec(), vec![]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn parse_whitespace() {
|
||||||
|
assert_eq!(
|
||||||
|
parse_from_str("\n \n \n").unwrap().into_vec(),
|
||||||
|
vec![
|
||||||
|
newline(),
|
||||||
|
whitespace(" "),
|
||||||
|
newline(),
|
||||||
|
whitespace(" "),
|
||||||
|
newline(),
|
||||||
|
]
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn newline_events_are_merged() {
|
||||||
|
assert_eq!(
|
||||||
|
parse_from_str("\n\n\n\n\n").unwrap().into_vec(),
|
||||||
|
vec![Event::Newline("\n\n\n\n\n".into())]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
Reference in a new issue