Add key-value delimination event

This commit is contained in:
Edward Shen 2021-02-23 11:30:48 -05:00
parent f10afb7894
commit f82d32953e
Signed by: edward
GPG key ID: 19182661E818369F
5 changed files with 343 additions and 75 deletions

View file

@ -1,5 +1,5 @@
use crate::parser::{parse_from_str, Event, ParsedSectionHeader, Parser, ParserError}; use crate::parser::{parse_from_str, Event, ParsedSectionHeader, Parser, ParserError};
use serde::Serialize; use serde::{ser::SerializeMap, Serialize, Serializer};
use std::collections::{HashMap, VecDeque}; use std::collections::{HashMap, VecDeque};
use std::{borrow::Cow, fmt::Display}; use std::{borrow::Cow, fmt::Display};
@ -50,7 +50,9 @@ enum LookupTreeNode<'a> {
impl<'a> GitConfig<'a> { impl<'a> GitConfig<'a> {
/// Convenience constructor. Attempts to parse the provided string into a /// Convenience constructor. Attempts to parse the provided string into a
/// [`GitConfig`]. /// [`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> { pub fn from_str(str: &'a str) -> Result<Self, ParserError> {
Ok(Self::from_parser(parse_from_str(str)?)) Ok(Self::from_parser(parse_from_str(str)?))
} }
@ -94,7 +96,8 @@ impl<'a> GitConfig<'a> {
e @ Event::Key(_) e @ Event::Key(_)
| e @ Event::Value(_) | e @ Event::Value(_)
| e @ Event::ValueNotDone(_) | e @ Event::ValueNotDone(_)
| e @ Event::ValueDone(_) => maybe_section | e @ Event::ValueDone(_)
| e @ Event::KeyValueSeparator => maybe_section
.as_mut() .as_mut()
.expect("Got a section-only event before a section") .expect("Got a section-only event before a section")
.push(e), .push(e),
@ -542,6 +545,12 @@ impl<'a> GitConfig<'a> {
} }
} }
impl<'a> From<Parser<'a>> for GitConfig<'a> {
fn from(p: Parser<'a>) -> Self {
Self::from_parser(p)
}
}
impl Display for GitConfig<'_> { impl Display for GitConfig<'_> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
for front_matter in &self.front_matter_events { for front_matter in &self.front_matter_events {
@ -550,20 +559,8 @@ impl Display for GitConfig<'_> {
for section_id in &self.section_order { for section_id in &self.section_order {
self.section_headers.get(section_id).unwrap().fmt(f)?; self.section_headers.get(section_id).unwrap().fmt(f)?;
let mut found_key = false;
for event in self.sections.get(section_id).unwrap() { for event in self.sections.get(section_id).unwrap() {
match event { event.fmt(f)?;
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)?,
}
} }
} }
@ -571,6 +568,8 @@ impl Display for GitConfig<'_> {
} }
} }
// todo impl serialize
#[cfg(test)] #[cfg(test)]
mod from_parser { mod from_parser {
use super::*; use super::*;
@ -618,9 +617,11 @@ mod from_parser {
vec![ vec![
Event::Newline(Cow::Borrowed("\n")), Event::Newline(Cow::Borrowed("\n")),
Event::Key(Cow::Borrowed("a")), Event::Key(Cow::Borrowed("a")),
Event::KeyValueSeparator,
Event::Value(Cow::Borrowed("b")), Event::Value(Cow::Borrowed("b")),
Event::Newline(Cow::Borrowed("\n")), Event::Newline(Cow::Borrowed("\n")),
Event::Key(Cow::Borrowed("c")), Event::Key(Cow::Borrowed("c")),
Event::KeyValueSeparator,
Event::Value(Cow::Borrowed("d")), Event::Value(Cow::Borrowed("d")),
], ],
); );
@ -665,9 +666,11 @@ mod from_parser {
vec![ vec![
Event::Newline(Cow::Borrowed("\n")), Event::Newline(Cow::Borrowed("\n")),
Event::Key(Cow::Borrowed("a")), Event::Key(Cow::Borrowed("a")),
Event::KeyValueSeparator,
Event::Value(Cow::Borrowed("b")), Event::Value(Cow::Borrowed("b")),
Event::Newline(Cow::Borrowed("\n")), Event::Newline(Cow::Borrowed("\n")),
Event::Key(Cow::Borrowed("c")), Event::Key(Cow::Borrowed("c")),
Event::KeyValueSeparator,
Event::Value(Cow::Borrowed("d")), Event::Value(Cow::Borrowed("d")),
], ],
); );
@ -722,9 +725,11 @@ mod from_parser {
vec![ vec![
Event::Newline(Cow::Borrowed("\n")), Event::Newline(Cow::Borrowed("\n")),
Event::Key(Cow::Borrowed("a")), Event::Key(Cow::Borrowed("a")),
Event::KeyValueSeparator,
Event::Value(Cow::Borrowed("b")), Event::Value(Cow::Borrowed("b")),
Event::Newline(Cow::Borrowed("\n")), Event::Newline(Cow::Borrowed("\n")),
Event::Key(Cow::Borrowed("c")), Event::Key(Cow::Borrowed("c")),
Event::KeyValueSeparator,
Event::Value(Cow::Borrowed("d")), Event::Value(Cow::Borrowed("d")),
Event::Newline(Cow::Borrowed("\n")), Event::Newline(Cow::Borrowed("\n")),
], ],
@ -733,6 +738,7 @@ mod from_parser {
SectionId(1), SectionId(1),
vec![ vec![
Event::Key(Cow::Borrowed("e")), Event::Key(Cow::Borrowed("e")),
Event::KeyValueSeparator,
Event::Value(Cow::Borrowed("f")), Event::Value(Cow::Borrowed("f")),
], ],
); );
@ -786,9 +792,11 @@ mod from_parser {
vec![ vec![
Event::Newline(Cow::Borrowed("\n")), Event::Newline(Cow::Borrowed("\n")),
Event::Key(Cow::Borrowed("a")), Event::Key(Cow::Borrowed("a")),
Event::KeyValueSeparator,
Event::Value(Cow::Borrowed("b")), Event::Value(Cow::Borrowed("b")),
Event::Newline(Cow::Borrowed("\n")), Event::Newline(Cow::Borrowed("\n")),
Event::Key(Cow::Borrowed("c")), Event::Key(Cow::Borrowed("c")),
Event::KeyValueSeparator,
Event::Value(Cow::Borrowed("d")), Event::Value(Cow::Borrowed("d")),
Event::Newline(Cow::Borrowed("\n")), Event::Newline(Cow::Borrowed("\n")),
], ],
@ -797,6 +805,7 @@ mod from_parser {
SectionId(1), SectionId(1),
vec![ vec![
Event::Key(Cow::Borrowed("e")), Event::Key(Cow::Borrowed("e")),
Event::KeyValueSeparator,
Event::Value(Cow::Borrowed("f")), Event::Value(Cow::Borrowed("f")),
], ],
); );
@ -983,7 +992,6 @@ mod display {
fn can_reconstruct_non_empty_config() { fn can_reconstruct_non_empty_config() {
let config = r#"[user] let config = r#"[user]
email = code@eddie.sh email = code@eddie.sh
name = Edward Shen
[core] [core]
autocrlf = input autocrlf = input
[push] [push]
@ -1003,4 +1011,35 @@ mod display {
assert_eq!(GitConfig::from_str(config).unwrap().to_string(), config); assert_eq!(GitConfig::from_str(config).unwrap().to_string(), config);
} }
#[test]
fn can_reconstruct_configs_with_implicits() {
let config = r#"[user]
email
name
[core]
autocrlf
[push]
default
[commit]
gpgsign"#;
assert_eq!(GitConfig::from_str(config).unwrap().to_string(), config);
}
#[test]
fn can_reconstruct_configs_without_whitespace_in_middle() {
let config = r#"[core]
autocrlf=input
[push]
default=simple
[commit]
gpgsign=true
[pull]
ff = only
[init]
defaultBranch = master"#;
assert_eq!(GitConfig::from_str(config).unwrap().to_string(), config);
}
} }

View file

@ -1,3 +1,5 @@
#![forbid(unsafe_code)]
// mod de; // mod de;
pub mod config; pub mod config;
mod error; mod error;

View file

@ -21,7 +21,17 @@ use std::borrow::{Borrow, Cow};
use std::fmt::Display; use std::fmt::Display;
use std::iter::FusedIterator; use std::iter::FusedIterator;
/// Syntactic events that occurs in the config. /// Syntactic events that occurs in the config. Despite all these variants
/// holding a [`Cow`] instead over a [`&str`], the parser will only emit
/// borrowed `Cow` variants.
///
/// The `Cow` smart pointer is used here for ease of inserting events in a
/// middle of an Event iterator. This is used, for example, in the [`GitConfig`]
/// struct when adding values.
///
/// [`Cow`]: std::borrow::Cow
/// [`&str`]: std::str
/// [`GitConfig`]: crate::config::GitConfig
#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)] #[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)]
pub enum Event<'a> { pub enum Event<'a> {
/// A comment with a comment tag and the comment itself. Note that the /// A comment with a comment tag and the comment itself. Note that the
@ -52,6 +62,10 @@ pub enum Event<'a> {
/// A continuous section of insignificant whitespace. Values with internal /// A continuous section of insignificant whitespace. Values with internal
/// spaces will not be separated by this event. /// spaces will not be separated by this event.
Whitespace(Cow<'a, str>), Whitespace(Cow<'a, str>),
/// This event is emitted when the parser counters a valid `=` character
/// separating the key and value. This event is necessary as it eliminates
/// the ambiguity for whitespace events between a key and value event.
KeyValueSeparator,
} }
impl Display for Event<'_> { impl Display for Event<'_> {
@ -65,6 +79,7 @@ impl Display for Event<'_> {
Self::ValueNotDone(e) => e.fmt(f), Self::ValueNotDone(e) => e.fmt(f),
Self::ValueDone(e) => e.fmt(f), Self::ValueDone(e) => e.fmt(f),
Self::Whitespace(e) => e.fmt(f), Self::Whitespace(e) => e.fmt(f),
Self::KeyValueSeparator => write!(f, "="),
} }
} }
} }
@ -77,6 +92,7 @@ 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<'_> { impl Display for ParsedSection<'_> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.section_header)?; write!(f, "{}", self.section_header)?;
@ -240,6 +256,7 @@ impl<'a> From<nom::Err<NomError<&'a str>>> for ParserError<'a> {
/// Event::Whitespace(Cow::Borrowed(" ")), /// Event::Whitespace(Cow::Borrowed(" ")),
/// Event::Key(Cow::Borrowed("autocrlf")), /// Event::Key(Cow::Borrowed("autocrlf")),
/// Event::Whitespace(Cow::Borrowed(" ")), /// Event::Whitespace(Cow::Borrowed(" ")),
/// Event::KeyValueSeparator,
/// Event::Whitespace(Cow::Borrowed(" ")), /// Event::Whitespace(Cow::Borrowed(" ")),
/// Event::Value(Cow::Borrowed("input")), /// Event::Value(Cow::Borrowed("input")),
/// # ]); /// # ]);
@ -250,6 +267,37 @@ impl<'a> From<nom::Err<NomError<&'a str>>> for ParserError<'a> {
/// equal sign. So if the config instead had `autocrlf=input`, those whitespace /// equal sign. So if the config instead had `autocrlf=input`, those whitespace
/// events would no longer be present. /// events would no longer be present.
/// ///
/// ## `KeyValueSeparator` event is not guaranteed to emit
///
/// Consider the following `git-config` example:
///
/// ```text
/// [core]
/// autocrlf
/// ```
///
/// This is a valid config with a `autocrlf` key having an implicit `true`
/// value. This means that there is not a `=` separating the key and value,
/// which means that the corresponding event won't appear either:
///
/// ```
/// # use serde_git_config::parser::{Event, ParsedSectionHeader, parse_from_str};
/// # use std::borrow::Cow;
/// # let section_header = ParsedSectionHeader {
/// # name: Cow::Borrowed("core"),
/// # separator: None,
/// # subsection_name: None,
/// # };
/// # let section_data = "[core]\n autocrlf";
/// # assert_eq!(parse_from_str(section_data).unwrap().into_vec(), vec![
/// Event::SectionHeader(section_header),
/// Event::Newline(Cow::Borrowed("\n")),
/// Event::Whitespace(Cow::Borrowed(" ")),
/// Event::Key(Cow::Borrowed("autocrlf")),
/// Event::Value(Cow::Borrowed("")),
/// # ]);
/// ```
///
/// ## Quoted values are not unquoted /// ## Quoted values are not unquoted
/// ///
/// Consider the following `git-config` example: /// Consider the following `git-config` example:
@ -279,9 +327,11 @@ impl<'a> From<nom::Err<NomError<&'a str>>> for ParserError<'a> {
/// 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(Cow::Borrowed("autocrlf")),
/// Event::KeyValueSeparator,
/// Event::Value(Cow::Borrowed(r#"true"""#)), /// Event::Value(Cow::Borrowed(r#"true"""#)),
/// Event::Newline(Cow::Borrowed("\n")), /// Event::Newline(Cow::Borrowed("\n")),
/// Event::Key(Cow::Borrowed("filemode")), /// Event::Key(Cow::Borrowed("filemode")),
/// Event::KeyValueSeparator,
/// Event::Value(Cow::Borrowed(r#"fa"lse""#)), /// Event::Value(Cow::Borrowed(r#"fa"lse""#)),
/// # ]); /// # ]);
/// ``` /// ```
@ -314,6 +364,7 @@ impl<'a> From<nom::Err<NomError<&'a str>>> for ParserError<'a> {
/// 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(Cow::Borrowed("file")),
/// Event::KeyValueSeparator,
/// Event::ValueNotDone(Cow::Borrowed("a")), /// Event::ValueNotDone(Cow::Borrowed("a")),
/// Event::Newline(Cow::Borrowed("\n")), /// Event::Newline(Cow::Borrowed("\n")),
/// Event::ValueDone(Cow::Borrowed(" c")), /// Event::ValueDone(Cow::Borrowed(" c")),
@ -550,15 +601,15 @@ fn config_name<'a>(i: &'a str) -> IResult<&'a str, &'a str> {
fn config_value<'a>(i: &'a str) -> IResult<&'a str, Vec<Event<'a>>> { fn config_value<'a>(i: &'a str) -> IResult<&'a str, Vec<Event<'a>>> {
if let (i, Some(_)) = opt(char('='))(i)? { if let (i, Some(_)) = opt(char('='))(i)? {
let mut events = vec![];
events.push(Event::KeyValueSeparator);
let (i, whitespace) = opt(take_spaces)(i)?; let (i, whitespace) = opt(take_spaces)(i)?;
let (i, values) = value_impl(i)?; let (i, values) = value_impl(i)?;
if let Some(whitespace) = whitespace { if let Some(whitespace) = whitespace {
let mut events = vec![Event::Whitespace(Cow::Borrowed(whitespace))]; events.push(Event::Whitespace(Cow::Borrowed(whitespace)));
}
events.extend(values); events.extend(values);
Ok((i, events)) Ok((i, events))
} else {
Ok((i, values))
}
} else { } else {
Ok((i, vec![Event::Value(Cow::Borrowed(""))])) Ok((i, vec![Event::Value(Cow::Borrowed(""))]))
} }
@ -833,6 +884,37 @@ mod config_name {
} }
} }
#[cfg(test)]
mod section_body {
use super::*;
#[test]
fn whitespace_is_not_ambigious() {
assert_eq!(
section_body("a =b").unwrap().1,
(
"a",
vec![
Event::Whitespace(Cow::from(" ")),
Event::KeyValueSeparator,
Event::Value(Cow::from("b"))
]
)
);
assert_eq!(
section_body("a= b").unwrap().1,
(
"a",
vec![
Event::KeyValueSeparator,
Event::Whitespace(Cow::from(" ")),
Event::Value(Cow::from("b"))
]
)
);
}
}
#[cfg(test)] #[cfg(test)]
mod value_no_continuation { mod value_no_continuation {
use super::*; use super::*;
@ -841,7 +923,7 @@ mod value_no_continuation {
fn no_comment() { fn no_comment() {
assert_eq!( assert_eq!(
value_impl("hello").unwrap(), value_impl("hello").unwrap(),
fully_consumed(vec![Event::Value(Cow::Borrowed("hello"))]) fully_consumed(vec![Event::Value(Cow::from("hello"))])
); );
} }
@ -849,7 +931,7 @@ mod value_no_continuation {
fn no_comment_newline() { fn no_comment_newline() {
assert_eq!( assert_eq!(
value_impl("hello\na").unwrap(), value_impl("hello\na").unwrap(),
("\na", vec![Event::Value(Cow::Borrowed("hello"))]) ("\na", vec![Event::Value(Cow::from("hello"))])
) )
} }
@ -857,7 +939,7 @@ mod value_no_continuation {
fn semicolon_comment_not_consumed() { fn semicolon_comment_not_consumed() {
assert_eq!( assert_eq!(
value_impl("hello;world").unwrap(), value_impl("hello;world").unwrap(),
(";world", vec![Event::Value(Cow::Borrowed("hello")),]) (";world", vec![Event::Value(Cow::from("hello")),])
); );
} }
@ -865,7 +947,7 @@ mod value_no_continuation {
fn octothorpe_comment_not_consumed() { fn octothorpe_comment_not_consumed() {
assert_eq!( assert_eq!(
value_impl("hello#world").unwrap(), value_impl("hello#world").unwrap(),
("#world", vec![Event::Value(Cow::Borrowed("hello")),]) ("#world", vec![Event::Value(Cow::from("hello")),])
); );
} }
@ -873,10 +955,7 @@ mod value_no_continuation {
fn values_with_extraneous_whitespace_without_comment() { fn values_with_extraneous_whitespace_without_comment() {
assert_eq!( assert_eq!(
value_impl("hello ").unwrap(), value_impl("hello ").unwrap(),
( (" ", vec![Event::Value(Cow::from("hello"))])
" ",
vec![Event::Value(Cow::Borrowed("hello"))]
)
); );
} }
@ -886,14 +965,14 @@ mod value_no_continuation {
value_impl("hello #world").unwrap(), value_impl("hello #world").unwrap(),
( (
" #world", " #world",
vec![Event::Value(Cow::Borrowed("hello"))] vec![Event::Value(Cow::from("hello"))]
) )
); );
assert_eq!( assert_eq!(
value_impl("hello ;world").unwrap(), value_impl("hello ;world").unwrap(),
( (
" ;world", " ;world",
vec![Event::Value(Cow::Borrowed("hello"))] vec![Event::Value(Cow::from("hello"))]
) )
); );
} }
@ -902,10 +981,7 @@ mod value_no_continuation {
fn trans_escaped_comment_marker_not_consumed() { fn trans_escaped_comment_marker_not_consumed() {
assert_eq!( assert_eq!(
value_impl(r##"hello"#"world; a"##).unwrap(), value_impl(r##"hello"#"world; a"##).unwrap(),
( ("; a", vec![Event::Value(Cow::from(r##"hello"#"world"##))])
"; a",
vec![Event::Value(Cow::Borrowed(r##"hello"#"world"##))]
)
); );
} }
@ -913,7 +989,7 @@ mod value_no_continuation {
fn complex_test() { fn complex_test() {
assert_eq!( assert_eq!(
value_impl(r#"value";";ahhhh"#).unwrap(), value_impl(r#"value";";ahhhh"#).unwrap(),
(";ahhhh", vec![Event::Value(Cow::Borrowed(r#"value";""#))]) (";ahhhh", vec![Event::Value(Cow::from(r#"value";""#))])
); );
} }
@ -932,9 +1008,9 @@ mod value_continuation {
assert_eq!( assert_eq!(
value_impl("hello\\\nworld").unwrap(), value_impl("hello\\\nworld").unwrap(),
fully_consumed(vec![ fully_consumed(vec![
Event::ValueNotDone(Cow::Borrowed("hello")), Event::ValueNotDone(Cow::from("hello")),
Event::Newline(Cow::Borrowed("\n")), Event::Newline(Cow::from("\n")),
Event::ValueDone(Cow::Borrowed("world")) Event::ValueDone(Cow::from("world"))
]) ])
); );
} }
@ -944,9 +1020,9 @@ mod value_continuation {
assert_eq!( assert_eq!(
value_impl("hello\\\n world").unwrap(), value_impl("hello\\\n world").unwrap(),
fully_consumed(vec![ fully_consumed(vec![
Event::ValueNotDone(Cow::Borrowed("hello")), Event::ValueNotDone(Cow::from("hello")),
Event::Newline(Cow::Borrowed("\n")), Event::Newline(Cow::from("\n")),
Event::ValueDone(Cow::Borrowed(" world")) Event::ValueDone(Cow::from(" world"))
]) ])
) )
} }
@ -958,11 +1034,11 @@ mod value_continuation {
( (
" # \"b\t ; c", " # \"b\t ; c",
vec![ vec![
Event::ValueNotDone(Cow::Borrowed(r#"1 "\""#)), Event::ValueNotDone(Cow::from(r#"1 "\""#)),
Event::Newline(Cow::Borrowed("\n")), Event::Newline(Cow::from("\n")),
Event::ValueNotDone(Cow::Borrowed(r#"a ; e "\""#)), Event::ValueNotDone(Cow::from(r#"a ; e "\""#)),
Event::Newline(Cow::Borrowed("\n")), Event::Newline(Cow::from("\n")),
Event::ValueDone(Cow::Borrowed("d")), Event::ValueDone(Cow::from("d")),
] ]
) )
); );
@ -975,9 +1051,9 @@ mod value_continuation {
( (
";a", ";a",
vec![ vec![
Event::ValueNotDone(Cow::Borrowed("\"")), Event::ValueNotDone(Cow::from("\"")),
Event::Newline(Cow::Borrowed("\n")), Event::Newline(Cow::from("\n")),
Event::ValueDone(Cow::Borrowed(";\"")), Event::ValueDone(Cow::from(";\"")),
] ]
) )
) )
@ -1010,10 +1086,11 @@ mod section {
fully_consumed(ParsedSection { fully_consumed(ParsedSection {
section_header: gen_section_header("hello", None), section_header: gen_section_header("hello", None),
events: vec![ events: vec![
Event::Newline(Cow::Borrowed("\n")), Event::Newline(Cow::from("\n")),
Event::Whitespace(Cow::Borrowed(" ")), Event::Whitespace(Cow::from(" ")),
Event::Key(Cow::Borrowed("a")), Event::Key(Cow::from("a")),
Event::Whitespace(Cow::Borrowed(" ")), Event::Whitespace(Cow::from(" ")),
Event::KeyValueSeparator,
Event::Whitespace(Cow::Borrowed(" ")), Event::Whitespace(Cow::Borrowed(" ")),
Event::Value(Cow::Borrowed("b")), Event::Value(Cow::Borrowed("b")),
Event::Newline(Cow::Borrowed("\n")), Event::Newline(Cow::Borrowed("\n")),
@ -1024,6 +1101,7 @@ mod section {
Event::Whitespace(Cow::Borrowed(" ")), Event::Whitespace(Cow::Borrowed(" ")),
Event::Key(Cow::Borrowed("d")), Event::Key(Cow::Borrowed("d")),
Event::Whitespace(Cow::Borrowed(" ")), Event::Whitespace(Cow::Borrowed(" ")),
Event::KeyValueSeparator,
Event::Whitespace(Cow::Borrowed(" ")), Event::Whitespace(Cow::Borrowed(" ")),
Event::Value(Cow::Borrowed("\"lol\"")) Event::Value(Cow::Borrowed("\"lol\""))
] ]
@ -1067,6 +1145,7 @@ mod section {
Event::Whitespace(Cow::Borrowed(" ")), Event::Whitespace(Cow::Borrowed(" ")),
Event::Key(Cow::Borrowed("a")), Event::Key(Cow::Borrowed("a")),
Event::Whitespace(Cow::Borrowed(" ")), Event::Whitespace(Cow::Borrowed(" ")),
Event::KeyValueSeparator,
Event::Whitespace(Cow::Borrowed(" ")), Event::Whitespace(Cow::Borrowed(" ")),
Event::Value(Cow::Borrowed("b")), Event::Value(Cow::Borrowed("b")),
Event::Whitespace(Cow::Borrowed(" ")), Event::Whitespace(Cow::Borrowed(" ")),
@ -1090,6 +1169,7 @@ mod section {
Event::Whitespace(Cow::Borrowed(" ")), Event::Whitespace(Cow::Borrowed(" ")),
Event::Key(Cow::Borrowed("c")), Event::Key(Cow::Borrowed("c")),
Event::Whitespace(Cow::Borrowed(" ")), Event::Whitespace(Cow::Borrowed(" ")),
Event::KeyValueSeparator,
Event::Whitespace(Cow::Borrowed(" ")), Event::Whitespace(Cow::Borrowed(" ")),
Event::Value(Cow::Borrowed("d")), Event::Value(Cow::Borrowed("d")),
] ]
@ -1108,6 +1188,7 @@ mod section {
Event::Whitespace(Cow::Borrowed(" ")), Event::Whitespace(Cow::Borrowed(" ")),
Event::Key(Cow::Borrowed("a")), Event::Key(Cow::Borrowed("a")),
Event::Whitespace(Cow::Borrowed(" ")), Event::Whitespace(Cow::Borrowed(" ")),
Event::KeyValueSeparator,
Event::Whitespace(Cow::Borrowed(" ")), Event::Whitespace(Cow::Borrowed(" ")),
Event::ValueNotDone(Cow::Borrowed(r#"1 "\""#)), Event::ValueNotDone(Cow::Borrowed(r#"1 "\""#)),
Event::Newline(Cow::Borrowed("\n")), Event::Newline(Cow::Borrowed("\n")),
@ -1134,6 +1215,7 @@ mod section {
Event::Whitespace(Cow::Borrowed(" ")), Event::Whitespace(Cow::Borrowed(" ")),
Event::Key(Cow::Borrowed("b")), Event::Key(Cow::Borrowed("b")),
Event::Whitespace(Cow::Borrowed(" ")), Event::Whitespace(Cow::Borrowed(" ")),
Event::KeyValueSeparator,
Event::ValueNotDone(Cow::Borrowed("\"")), Event::ValueNotDone(Cow::Borrowed("\"")),
Event::Newline(Cow::Borrowed("\n")), Event::Newline(Cow::Borrowed("\n")),
Event::ValueDone(Cow::Borrowed(";\"")), Event::ValueDone(Cow::Borrowed(";\"")),

View file

@ -16,9 +16,9 @@ impl<'a> Value<'a> {
return Self::Boolean(bool); return Self::Boolean(bool);
} }
// if let Ok(int) = Integer::from_str(s) { if let Ok(int) = Integer::from_str(s) {
// return Self::Integer(int); return Self::Integer(int);
// } }
// if let Ok(color) = Color::from_str(s) { // if let Ok(color) = Color::from_str(s) {
// return Self::Color(color); // return Self::Color(color);
@ -32,6 +32,8 @@ impl<'a> Value<'a> {
} }
} }
// todo display for value
impl Serialize for Value<'_> { impl Serialize for Value<'_> {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where where
@ -46,8 +48,6 @@ impl Serialize for Value<'_> {
} }
} }
// todo display for value
#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)] #[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)]
pub enum Boolean<'a> { pub enum Boolean<'a> {
True(TrueVariant<'a>), True(TrueVariant<'a>),
@ -108,9 +108,10 @@ impl<'a> TrueVariant<'a> {
impl Display for TrueVariant<'_> { impl Display for TrueVariant<'_> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self { if let Self::Explicit(v) = self {
Self::Explicit(v) => write!(f, "{}", v), write!(f, "{}", v)
Self::Implicit => write!(f, "(implicit)"), } else {
Ok(())
} }
} }
} }
@ -163,12 +164,6 @@ pub struct Integer {
suffix: Option<IntegerSuffix>, suffix: Option<IntegerSuffix>,
} }
impl Integer {
pub fn from_str(s: &str) -> Result<Self, ()> {
todo!()
}
}
impl Display for Integer { impl Display for Integer {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.value)?; write!(f, "{}", self.value)?;
@ -193,7 +188,34 @@ impl Serialize for Integer {
} }
} }
// todo from str for integer impl FromStr for Integer {
type Err = String;
fn from_str<'a>(s: &'a str) -> Result<Self, Self::Err> {
if let Ok(value) = s.parse() {
return Ok(Self {
value,
suffix: None,
});
}
// Assume we have a prefix at this point.
if s.len() <= 1 {
return Err(s.to_string());
}
let (number, suffix) = s.split_at(s.len() - 1);
if let (Ok(value), Ok(suffix)) = (number.parse(), suffix.parse()) {
Ok(Self {
value,
suffix: Some(suffix),
})
} else {
Err(s.to_string())
}
}
}
#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)] #[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)]
enum IntegerSuffix { enum IntegerSuffix {
@ -248,7 +270,7 @@ impl FromStr for IntegerSuffix {
} }
} }
#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)] #[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug, Default)]
pub struct Color { pub struct Color {
foreground: Option<ColorValue>, foreground: Option<ColorValue>,
background: Option<ColorValue>, background: Option<ColorValue>,
@ -283,7 +305,54 @@ impl Serialize for Color {
} }
} }
// impl fromstr for color pub enum FromColorErr {
TooManyColorValues,
InvalidColorOption,
}
impl FromStr for Color {
type Err = FromColorErr;
fn from_str(s: &str) -> Result<Self, Self::Err> {
enum ColorItem {
Value(ColorValue),
Attr(ColorAttribute),
}
let items = s.split_whitespace().filter_map(|s| {
if s.is_empty() {
return None;
}
Some(
ColorValue::from_str(s)
.map(ColorItem::Value)
.or_else(|_| ColorAttribute::from_str(s).map(ColorItem::Attr)),
)
});
let mut new_self = Self::default();
for item in items {
match item {
Ok(item) => match item {
ColorItem::Value(v) => {
if new_self.foreground.is_none() {
new_self.foreground = Some(v);
} else if new_self.background.is_none() {
new_self.background = Some(v);
} else {
return Err(FromColorErr::TooManyColorValues);
}
}
ColorItem::Attr(a) => new_self.attributes.push(a),
},
Err(_) => return Err(FromColorErr::InvalidColorOption),
}
}
Ok(new_self)
}
}
#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)] #[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)]
enum ColorValue { enum ColorValue {
@ -484,3 +553,65 @@ impl FromStr for ColorAttribute {
} }
} }
} }
#[cfg(test)]
mod integer {
use super::*;
#[test]
fn from_str_no_suffix() {
assert_eq!(
Integer::from_str("1").unwrap(),
Integer {
value: 1,
suffix: None
}
);
assert_eq!(
Integer::from_str("-1").unwrap(),
Integer {
value: -1,
suffix: None
}
);
}
#[test]
fn from_str_with_suffix() {
assert_eq!(
Integer::from_str("1k").unwrap(),
Integer {
value: 1,
suffix: Some(IntegerSuffix::Kilo),
}
);
assert_eq!(
Integer::from_str("1m").unwrap(),
Integer {
value: 1,
suffix: Some(IntegerSuffix::Mega),
}
);
assert_eq!(
Integer::from_str("1g").unwrap(),
Integer {
value: 1,
suffix: Some(IntegerSuffix::Giga),
}
);
}
#[test]
fn invalid_from_str() {
assert!(Integer::from_str("").is_err());
assert!(Integer::from_str("-").is_err());
assert!(Integer::from_str("k").is_err());
assert!(Integer::from_str("m").is_err());
assert!(Integer::from_str("g").is_err());
assert!(Integer::from_str("123123123123123123123123").is_err());
assert!(Integer::from_str("gg").is_err());
}
}

View file

@ -39,12 +39,16 @@ fn whitespace(value: &'static str) -> Event<'static> {
Event::Whitespace(Cow::Borrowed(value)) Event::Whitespace(Cow::Borrowed(value))
} }
fn separator() -> Event<'static> {
Event::KeyValueSeparator
}
#[test] #[test]
#[rustfmt::skip] #[rustfmt::skip]
fn personal_config() { fn personal_config() {
let config = r#"[user] let config = r#"[user]
email = code@eddie.sh email = code@eddie.sh
name = Edward Shen name = Foo Bar
[core] [core]
autocrlf = input autocrlf = input
[push] [push]
@ -73,6 +77,7 @@ fn personal_config() {
whitespace(" "), whitespace(" "),
name("email"), name("email"),
whitespace(" "), whitespace(" "),
separator(),
whitespace(" "), whitespace(" "),
value("code@eddie.sh"), value("code@eddie.sh"),
newline(), newline(),
@ -80,8 +85,9 @@ fn personal_config() {
whitespace(" "), whitespace(" "),
name("name"), name("name"),
whitespace(" "), whitespace(" "),
separator(),
whitespace(" "), whitespace(" "),
value("Edward Shen"), value("Foo Bar"),
newline(), newline(),
gen_section_header("core", None), gen_section_header("core", None),
@ -90,6 +96,7 @@ fn personal_config() {
whitespace(" "), whitespace(" "),
name("autocrlf"), name("autocrlf"),
whitespace(" "), whitespace(" "),
separator(),
whitespace(" "), whitespace(" "),
value("input"), value("input"),
newline(), newline(),
@ -100,6 +107,7 @@ fn personal_config() {
whitespace(" "), whitespace(" "),
name("default"), name("default"),
whitespace(" "), whitespace(" "),
separator(),
whitespace(" "), whitespace(" "),
value("simple"), value("simple"),
newline(), newline(),
@ -110,6 +118,7 @@ fn personal_config() {
whitespace(" "), whitespace(" "),
name("gpgsign"), name("gpgsign"),
whitespace(" "), whitespace(" "),
separator(),
whitespace(" "), whitespace(" "),
value("true"), value("true"),
newline(), newline(),
@ -120,6 +129,7 @@ fn personal_config() {
whitespace(" "), whitespace(" "),
name("program"), name("program"),
whitespace(" "), whitespace(" "),
separator(),
whitespace(" "), whitespace(" "),
value("gpg"), value("gpg"),
newline(), newline(),
@ -130,6 +140,7 @@ fn personal_config() {
whitespace(" "), whitespace(" "),
name("insteadOf"), name("insteadOf"),
whitespace(" "), whitespace(" "),
separator(),
whitespace(" "), whitespace(" "),
value("\"github://\""), value("\"github://\""),
newline(), newline(),
@ -140,6 +151,7 @@ fn personal_config() {
whitespace(" "), whitespace(" "),
name("insteadOf"), name("insteadOf"),
whitespace(" "), whitespace(" "),
separator(),
whitespace(" "), whitespace(" "),
value("\"gitea://\""), value("\"gitea://\""),
newline(), newline(),
@ -150,6 +162,7 @@ fn personal_config() {
whitespace(" "), whitespace(" "),
name("ff"), name("ff"),
whitespace(" "), whitespace(" "),
separator(),
whitespace(" "), whitespace(" "),
value("only"), value("only"),
newline(), newline(),
@ -160,6 +173,7 @@ fn personal_config() {
whitespace(" "), whitespace(" "),
name("defaultBranch"), name("defaultBranch"),
whitespace(" "), whitespace(" "),
separator(),
whitespace(" "), whitespace(" "),
value("master"), value("master"),
] ]