Use traits instead of from_str

This commit is contained in:
Edward Shen 2021-02-26 21:44:58 -05:00
parent a50544b43a
commit 3e97f07b28
Signed by: edward
GPG key ID: 19182661E818369F
2 changed files with 146 additions and 147 deletions

View file

@ -1,9 +1,10 @@
use crate::parser::{parse_from_str, Event, ParsedSectionHeader, Parser, ParserError};
use crate::parser::{parse_from_bytes, Event, ParsedSectionHeader, Parser, ParserError};
use bstr::BStr;
use std::collections::{HashMap, VecDeque};
use std::convert::TryFrom;
use std::{borrow::Cow, fmt::Display};
#[derive(Debug, PartialEq, Eq)]
#[derive(PartialEq, Eq, Hash, Copy, Clone, PartialOrd, Ord, Debug)]
pub enum GitConfigError<'a> {
/// The requested section does not exist.
SectionDoesNotExist(&'a BStr),
@ -13,21 +14,6 @@ pub enum GitConfigError<'a> {
KeyDoesNotExist(&'a BStr),
}
/// High level `git-config` reader and writer.
pub struct GitConfig<'a> {
/// The list of events that occur before an actual section. Since a
/// `git-config` file prohibits global values, this vec is limited to only
/// comment, newline, and whitespace events.
front_matter_events: Vec<Event<'a>>,
section_lookup_tree: HashMap<Cow<'a, BStr>, Vec<LookupTreeNode<'a>>>,
/// SectionId to section mapping. The value of this HashMap contains actual
/// events
sections: HashMap<SectionId, Vec<Event<'a>>>,
section_headers: HashMap<SectionId, ParsedSectionHeader<'a>>,
section_id_counter: usize,
section_order: VecDeque<SectionId>,
}
/// The section ID is a monotonically increasing ID used to refer to sections.
/// This value does not imply any ordering between sections, as new sections
/// with higher section IDs may be in between lower ID sections.
@ -42,85 +28,28 @@ pub struct GitConfig<'a> {
#[derive(PartialEq, Eq, Hash, Copy, Clone, PartialOrd, Ord, Debug)]
struct SectionId(usize);
#[derive(Debug, PartialEq, Eq)]
#[derive(PartialEq, Eq, Clone, Debug)]
enum LookupTreeNode<'a> {
Terminal(Vec<SectionId>),
NonTerminal(HashMap<Cow<'a, BStr>, Vec<SectionId>>),
}
/// High level `git-config` reader and writer.
#[derive(PartialEq, Eq, Clone, Debug)]
pub struct GitConfig<'a> {
/// The list of events that occur before an actual section. Since a
/// `git-config` file prohibits global values, this vec is limited to only
/// comment, newline, and whitespace events.
front_matter_events: Vec<Event<'a>>,
section_lookup_tree: HashMap<Cow<'a, BStr>, Vec<LookupTreeNode<'a>>>,
/// SectionId to section mapping. The value of this HashMap contains actual
/// events
sections: HashMap<SectionId, Vec<Event<'a>>>,
section_headers: HashMap<SectionId, ParsedSectionHeader<'a>>,
section_id_counter: usize,
section_order: VecDeque<SectionId>,
}
impl<'a> GitConfig<'a> {
/// Convenience constructor. Attempts to parse the provided string into a
/// [`GitConfig`]. See [`parse_from_str`] for more information.
///
/// [`parse_from_str`]: crate::parser::parse_from_str
pub fn from_str(str: &'a str) -> Result<Self, ParserError> {
Ok(Self::from_parser(parse_from_str(str)?))
}
pub fn from_parser(parser: Parser<'a>) -> Self {
let mut new_self = Self {
front_matter_events: vec![],
sections: HashMap::new(),
section_lookup_tree: HashMap::new(),
section_headers: HashMap::new(),
section_id_counter: 0,
section_order: VecDeque::new(),
};
// Current section that we're building
let mut current_section_name: Option<Cow<'a, BStr>> = None;
let mut current_subsection_name: Option<Cow<'a, BStr>> = None;
let mut maybe_section: Option<Vec<Event<'a>>> = None;
for event in parser.into_iter() {
match event {
Event::SectionHeader(header) => {
new_self.push_section(
current_section_name,
current_subsection_name,
&mut maybe_section,
);
// Initialize new section
// We need to store the new, current id counter, so don't
// use new_section_id here and use the already incremented
// section id value.
new_self
.section_headers
.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::Value(_)
| e @ Event::ValueNotDone(_)
| e @ Event::ValueDone(_)
| e @ Event::KeyValueSeparator => maybe_section
.as_mut()
.expect("Got a section-only event before a section")
.push(e),
e @ Event::Comment(_) | e @ Event::Newline(_) | e @ Event::Whitespace(_) => {
match maybe_section {
Some(ref mut section) => section.push(e),
None => new_self.front_matter_events.push(e),
}
}
}
}
// The last section doesn't get pushed since we only push if there's a
// new section header, so we need to call push one more time.
new_self.push_section(
current_section_name,
current_subsection_name,
&mut maybe_section,
);
new_self
}
fn push_section(
&mut self,
current_section_name: Option<Cow<'a, BStr>>,
@ -574,9 +503,93 @@ impl<'a> GitConfig<'a> {
}
}
impl<'a> TryFrom<&'a str> for GitConfig<'a> {
type Error = ParserError<'a>;
/// Convenience constructor. Attempts to parse the provided string into a
/// [`GitConfig`]. See [`parse_from_str`] for more information.
///
/// [`parse_from_str`]: crate::parser::parse_from_str
fn try_from(s: &'a str) -> Result<Self, Self::Error> {
parse_from_bytes(s.as_bytes()).map(Self::from)
}
}
impl<'a> TryFrom<&'a BStr> for GitConfig<'a> {
type Error = ParserError<'a>;
/// Convenience constructor. Attempts to parse the provided byte string into
//// a [`GitConfig`]. See [`parse_from_bytes`] for more information.
///
/// [`parse_from_bytes`]: crate::parser::parse_from_bytes
fn try_from(value: &'a BStr) -> Result<Self, Self::Error> {
parse_from_bytes(value).map(Self::from)
}
}
impl<'a> From<Parser<'a>> for GitConfig<'a> {
fn from(p: Parser<'a>) -> Self {
Self::from_parser(p)
fn from(parser: Parser<'a>) -> Self {
let mut new_self = Self {
front_matter_events: vec![],
sections: HashMap::new(),
section_lookup_tree: HashMap::new(),
section_headers: HashMap::new(),
section_id_counter: 0,
section_order: VecDeque::new(),
};
// Current section that we're building
let mut current_section_name: Option<Cow<'a, BStr>> = None;
let mut current_subsection_name: Option<Cow<'a, BStr>> = None;
let mut maybe_section: Option<Vec<Event<'a>>> = None;
for event in parser.into_iter() {
match event {
Event::SectionHeader(header) => {
new_self.push_section(
current_section_name,
current_subsection_name,
&mut maybe_section,
);
// Initialize new section
// We need to store the new, current id counter, so don't
// use new_section_id here and use the already incremented
// section id value.
new_self
.section_headers
.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::Value(_)
| e @ Event::ValueNotDone(_)
| e @ Event::ValueDone(_)
| e @ Event::KeyValueSeparator => maybe_section
.as_mut()
.expect("Got a section-only event before a section")
.push(e),
e @ Event::Comment(_) | e @ Event::Newline(_) | e @ Event::Whitespace(_) => {
match maybe_section {
Some(ref mut section) => section.push(e),
None => new_self.front_matter_events.push(e),
}
}
}
}
// The last section doesn't get pushed since we only push if there's a
// new section header, so we need to call push one more time.
new_self.push_section(
current_section_name,
current_subsection_name,
&mut maybe_section,
);
new_self
}
}
@ -606,7 +619,7 @@ mod from_parser {
#[test]
fn parse_empty() {
let config = GitConfig::from_str("").unwrap();
let config = GitConfig::try_from("").unwrap();
assert!(config.section_headers.is_empty());
assert_eq!(config.section_id_counter, 0);
assert!(config.section_lookup_tree.is_empty());
@ -616,7 +629,7 @@ mod from_parser {
#[test]
fn parse_single_section() {
let mut config = GitConfig::from_str("[core]\na=b\nc=d").unwrap();
let mut config = GitConfig::try_from("[core]\na=b\nc=d").unwrap();
let expected_separators = {
let mut map = HashMap::new();
map.insert(SectionId(0), section_header("core", None));
@ -656,7 +669,7 @@ mod from_parser {
#[test]
fn parse_single_subsection() {
let mut config = GitConfig::from_str("[core.subsec]\na=b\nc=d").unwrap();
let mut config = GitConfig::try_from("[core.subsec]\na=b\nc=d").unwrap();
let expected_separators = {
let mut map = HashMap::new();
map.insert(SectionId(0), section_header("core", (".", "subsec")));
@ -698,7 +711,7 @@ mod from_parser {
#[test]
fn parse_multiple_sections() {
let mut config = GitConfig::from_str("[core]\na=b\nc=d\n[other]e=f").unwrap();
let mut config = GitConfig::try_from("[core]\na=b\nc=d\n[other]e=f").unwrap();
let expected_separators = {
let mut map = HashMap::new();
map.insert(SectionId(0), section_header("core", None));
@ -751,7 +764,7 @@ mod from_parser {
#[test]
fn parse_multiple_duplicate_sections() {
let mut config = GitConfig::from_str("[core]\na=b\nc=d\n[core]e=f").unwrap();
let mut config = GitConfig::try_from("[core]\na=b\nc=d\n[core]e=f").unwrap();
let expected_separators = {
let mut map = HashMap::new();
map.insert(SectionId(0), section_header("core", None));
@ -805,7 +818,7 @@ mod get_raw_value {
#[test]
fn single_section() {
let config = GitConfig::from_str("[core]\na=b\nc=d").unwrap();
let config = GitConfig::try_from("[core]\na=b\nc=d").unwrap();
assert_eq!(
config.get_raw_value("core", None, "a"),
Ok(&Cow::Borrowed("b".into()))
@ -818,7 +831,7 @@ mod get_raw_value {
#[test]
fn last_one_wins_respected_in_section() {
let config = GitConfig::from_str("[core]\na=b\na=d").unwrap();
let config = GitConfig::try_from("[core]\na=b\na=d").unwrap();
assert_eq!(
config.get_raw_value("core", None, "a"),
Ok(&Cow::Borrowed("d".into()))
@ -827,7 +840,7 @@ mod get_raw_value {
#[test]
fn last_one_wins_respected_across_section() {
let config = GitConfig::from_str("[core]\na=b\n[core]\na=d").unwrap();
let config = GitConfig::try_from("[core]\na=b\n[core]\na=d").unwrap();
assert_eq!(
config.get_raw_value("core", None, "a"),
Ok(&Cow::Borrowed("d".into()))
@ -836,7 +849,7 @@ mod get_raw_value {
#[test]
fn section_not_found() {
let config = GitConfig::from_str("[core]\na=b\nc=d").unwrap();
let config = GitConfig::try_from("[core]\na=b\nc=d").unwrap();
assert_eq!(
config.get_raw_value("foo", None, "a"),
Err(GitConfigError::SectionDoesNotExist("foo".into()))
@ -845,7 +858,7 @@ mod get_raw_value {
#[test]
fn subsection_not_found() {
let config = GitConfig::from_str("[core]\na=b\nc=d").unwrap();
let config = GitConfig::try_from("[core]\na=b\nc=d").unwrap();
assert_eq!(
config.get_raw_value("core", Some("a"), "a"),
Err(GitConfigError::SubSectionDoesNotExist(Some("a".into())))
@ -854,7 +867,7 @@ mod get_raw_value {
#[test]
fn key_not_found() {
let config = GitConfig::from_str("[core]\na=b\nc=d").unwrap();
let config = GitConfig::try_from("[core]\na=b\nc=d").unwrap();
assert_eq!(
config.get_raw_value("core", None, "aaaaaa"),
Err(GitConfigError::KeyDoesNotExist("aaaaaa".into()))
@ -863,7 +876,7 @@ mod get_raw_value {
#[test]
fn subsection_must_be_respected() {
let config = GitConfig::from_str("[core]a=b\n[core.a]a=c").unwrap();
let config = GitConfig::try_from("[core]a=b\n[core.a]a=c").unwrap();
assert_eq!(
config.get_raw_value("core", None, "a"),
Ok(&Cow::Borrowed("b".into()))
@ -881,7 +894,7 @@ mod get_raw_multi_value {
#[test]
fn single_value_is_identical_to_single_value_query() {
let config = GitConfig::from_str("[core]\na=b\nc=d").unwrap();
let config = GitConfig::try_from("[core]\na=b\nc=d").unwrap();
assert_eq!(
vec![config.get_raw_value("core", None, "a").unwrap()],
config.get_raw_multi_value("core", None, "a").unwrap()
@ -890,7 +903,7 @@ mod get_raw_multi_value {
#[test]
fn multi_value_in_section() {
let config = GitConfig::from_str("[core]\na=b\na=c").unwrap();
let config = GitConfig::try_from("[core]\na=b\na=c").unwrap();
assert_eq!(
config.get_raw_multi_value("core", None, "a").unwrap(),
vec![&Cow::Borrowed("b"), &Cow::Borrowed("c")]
@ -899,7 +912,7 @@ mod get_raw_multi_value {
#[test]
fn multi_value_across_sections() {
let config = GitConfig::from_str("[core]\na=b\na=c\n[core]a=d").unwrap();
let config = GitConfig::try_from("[core]\na=b\na=c\n[core]a=d").unwrap();
assert_eq!(
config.get_raw_multi_value("core", None, "a").unwrap(),
vec![
@ -912,7 +925,7 @@ mod get_raw_multi_value {
#[test]
fn section_not_found() {
let config = GitConfig::from_str("[core]\na=b\nc=d").unwrap();
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".into()))
@ -921,7 +934,7 @@ mod get_raw_multi_value {
#[test]
fn subsection_not_found() {
let config = GitConfig::from_str("[core]\na=b\nc=d").unwrap();
let config = GitConfig::try_from("[core]\na=b\nc=d").unwrap();
assert_eq!(
config.get_raw_multi_value("core", Some("a"), "a"),
Err(GitConfigError::SubSectionDoesNotExist(Some("a".into())))
@ -930,7 +943,7 @@ mod get_raw_multi_value {
#[test]
fn key_not_found() {
let config = GitConfig::from_str("[core]\na=b\nc=d").unwrap();
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".into()))
@ -939,7 +952,7 @@ mod get_raw_multi_value {
#[test]
fn subsection_must_be_respected() {
let config = GitConfig::from_str("[core]a=b\n[core.a]a=c").unwrap();
let config = GitConfig::try_from("[core]a=b\n[core.a]a=c").unwrap();
assert_eq!(
config.get_raw_multi_value("core", None, "a").unwrap(),
vec![&Cow::Borrowed("b")]
@ -952,7 +965,7 @@ mod get_raw_multi_value {
#[test]
fn non_relevant_subsection_is_ignored() {
let config = GitConfig::from_str("[core]\na=b\na=c\n[core]a=d\n[core]g=g").unwrap();
let config = GitConfig::try_from("[core]\na=b\na=c\n[core]a=d\n[core]g=g").unwrap();
assert_eq!(
config.get_raw_multi_value("core", None, "a").unwrap(),
vec![
@ -973,7 +986,7 @@ mod display {
let config = r#"
"#;
assert_eq!(GitConfig::from_str(config).unwrap().to_string(), config);
assert_eq!(GitConfig::try_from(config).unwrap().to_string(), config);
}
#[test]
@ -997,7 +1010,7 @@ mod display {
[init]
defaultBranch = master"#;
assert_eq!(GitConfig::from_str(config).unwrap().to_string(), config);
assert_eq!(GitConfig::try_from(config).unwrap().to_string(), config);
}
#[test]
@ -1012,7 +1025,7 @@ mod display {
[commit]
gpgsign"#;
assert_eq!(GitConfig::from_str(config).unwrap().to_string(), config);
assert_eq!(GitConfig::try_from(config).unwrap().to_string(), config);
}
#[test]
@ -1028,6 +1041,6 @@ mod display {
[init]
defaultBranch = master"#;
assert_eq!(GitConfig::from_str(config).unwrap().to_string(), config);
assert_eq!(GitConfig::try_from(config).unwrap().to_string(), config);
}
}

View file

@ -19,9 +19,9 @@ use nom::error::{Error as NomError, ErrorKind};
use nom::multi::{many0, many1};
use nom::sequence::delimited;
use nom::IResult;
use std::fmt::Display;
use std::iter::FusedIterator;
use std::{borrow::Cow, error::Error};
use std::{convert::TryFrom, fmt::Display};
/// Syntactic events that occurs in the config. Despite all these variants
/// holding a [`Cow`] instead over a simple reference, the parser will only emit
@ -446,36 +446,6 @@ pub struct Parser<'a> {
}
impl<'a> Parser<'a> {
/// Attempt to zero-copy parse the provided `&str`. On success, returns a
/// [`Parser`] that provides methods to accessing leading comments and
/// sections of a `git-config` file and can be converted into an iterator of
/// [`Event`] for higher level processing.
///
/// This function is identical to [`parse_from_str`].
///
/// # Errors
///
/// Returns an error if the string provided is not a valid `git-config`.
/// This generally is due to either invalid names or if there's extraneous
/// data succeeding valid `git-config` data.
pub fn from_str(s: &'a str) -> Result<Self, ParserError> {
parse_from_str(s)
}
/// Attempt to zero-copy parse the provided bytes. On success, returns a
/// [`Parser`] that provides methods to accessing leading comments and
/// sections of a `git-config` file and can be converted into an iterator of
/// [`Event`] for higher level processing.
///
/// # Errors
///
/// Returns an error if the string provided is not a valid `git-config`.
/// This generally is due to either invalid names or if there's extraneous
/// data succeeding valid `git-config` data.
pub fn from_bytes(s: impl Into<&'a BStr>) -> Result<Self, ParserError<'a>> {
parse_from_bytes(s.into())
}
/// Returns the leading events (any comments, whitespace, or newlines before
/// a section) from the parser. Consider [`Parser::take_frontmatter`] if
/// you need an owned copy only once. If that function was called, then this
@ -530,6 +500,22 @@ impl<'a> Parser<'a> {
}
}
impl<'a> TryFrom<&'a str> for Parser<'a> {
type Error = ParserError<'a>;
fn try_from(value: &'a str) -> Result<Self, Self::Error> {
parse_from_str(value)
}
}
impl<'a> TryFrom<&'a [u8]> for Parser<'a> {
type Error = ParserError<'a>;
fn try_from(value: &'a [u8]) -> Result<Self, Self::Error> {
parse_from_bytes(value)
}
}
/// Attempt to zero-copy parse the provided `&str`. On success, returns a
/// [`Parser`] that provides methods to accessing leading comments and sections
/// of a `git-config` file and can be converted into an iterator of [`Event`]