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 serde::Serialize;
use serde::{ser::SerializeMap, Serialize, Serializer};
use std::collections::{HashMap, VecDeque};
use std::{borrow::Cow, fmt::Display};
@ -50,7 +50,9 @@ enum LookupTreeNode<'a> {
impl<'a> GitConfig<'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> {
Ok(Self::from_parser(parse_from_str(str)?))
}
@ -94,7 +96,8 @@ impl<'a> GitConfig<'a> {
e @ Event::Key(_)
| e @ Event::Value(_)
| e @ Event::ValueNotDone(_)
| e @ Event::ValueDone(_) => maybe_section
| e @ Event::ValueDone(_)
| e @ Event::KeyValueSeparator => maybe_section
.as_mut()
.expect("Got a section-only event before a section")
.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<'_> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
for front_matter in &self.front_matter_events {
@ -550,20 +559,8 @@ impl Display for GitConfig<'_> {
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)?,
}
event.fmt(f)?;
}
}
@ -571,6 +568,8 @@ impl Display for GitConfig<'_> {
}
}
// todo impl serialize
#[cfg(test)]
mod from_parser {
use super::*;
@ -618,9 +617,11 @@ mod from_parser {
vec![
Event::Newline(Cow::Borrowed("\n")),
Event::Key(Cow::Borrowed("a")),
Event::KeyValueSeparator,
Event::Value(Cow::Borrowed("b")),
Event::Newline(Cow::Borrowed("\n")),
Event::Key(Cow::Borrowed("c")),
Event::KeyValueSeparator,
Event::Value(Cow::Borrowed("d")),
],
);
@ -665,9 +666,11 @@ mod from_parser {
vec![
Event::Newline(Cow::Borrowed("\n")),
Event::Key(Cow::Borrowed("a")),
Event::KeyValueSeparator,
Event::Value(Cow::Borrowed("b")),
Event::Newline(Cow::Borrowed("\n")),
Event::Key(Cow::Borrowed("c")),
Event::KeyValueSeparator,
Event::Value(Cow::Borrowed("d")),
],
);
@ -722,9 +725,11 @@ mod from_parser {
vec![
Event::Newline(Cow::Borrowed("\n")),
Event::Key(Cow::Borrowed("a")),
Event::KeyValueSeparator,
Event::Value(Cow::Borrowed("b")),
Event::Newline(Cow::Borrowed("\n")),
Event::Key(Cow::Borrowed("c")),
Event::KeyValueSeparator,
Event::Value(Cow::Borrowed("d")),
Event::Newline(Cow::Borrowed("\n")),
],
@ -733,6 +738,7 @@ mod from_parser {
SectionId(1),
vec![
Event::Key(Cow::Borrowed("e")),
Event::KeyValueSeparator,
Event::Value(Cow::Borrowed("f")),
],
);
@ -786,9 +792,11 @@ mod from_parser {
vec![
Event::Newline(Cow::Borrowed("\n")),
Event::Key(Cow::Borrowed("a")),
Event::KeyValueSeparator,
Event::Value(Cow::Borrowed("b")),
Event::Newline(Cow::Borrowed("\n")),
Event::Key(Cow::Borrowed("c")),
Event::KeyValueSeparator,
Event::Value(Cow::Borrowed("d")),
Event::Newline(Cow::Borrowed("\n")),
],
@ -797,6 +805,7 @@ mod from_parser {
SectionId(1),
vec![
Event::Key(Cow::Borrowed("e")),
Event::KeyValueSeparator,
Event::Value(Cow::Borrowed("f")),
],
);
@ -983,7 +992,6 @@ mod display {
fn can_reconstruct_non_empty_config() {
let config = r#"[user]
email = code@eddie.sh
name = Edward Shen
[core]
autocrlf = input
[push]
@ -1003,4 +1011,35 @@ mod display {
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;
pub mod config;
mod error;

View file

@ -21,7 +21,17 @@ 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. 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)]
pub enum Event<'a> {
/// 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
/// spaces will not be separated by this event.
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<'_> {
@ -65,6 +79,7 @@ impl Display for Event<'_> {
Self::ValueNotDone(e) => e.fmt(f),
Self::ValueDone(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.
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)?;
@ -240,6 +256,7 @@ impl<'a> From<nom::Err<NomError<&'a str>>> for ParserError<'a> {
/// Event::Whitespace(Cow::Borrowed(" ")),
/// Event::Key(Cow::Borrowed("autocrlf")),
/// Event::Whitespace(Cow::Borrowed(" ")),
/// Event::KeyValueSeparator,
/// Event::Whitespace(Cow::Borrowed(" ")),
/// 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
/// 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
///
/// 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::Newline(Cow::Borrowed("\n")),
/// Event::Key(Cow::Borrowed("autocrlf")),
/// Event::KeyValueSeparator,
/// Event::Value(Cow::Borrowed(r#"true"""#)),
/// Event::Newline(Cow::Borrowed("\n")),
/// Event::Key(Cow::Borrowed("filemode")),
/// Event::KeyValueSeparator,
/// 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::Newline(Cow::Borrowed("\n")),
/// Event::Key(Cow::Borrowed("file")),
/// Event::KeyValueSeparator,
/// Event::ValueNotDone(Cow::Borrowed("a")),
/// Event::Newline(Cow::Borrowed("\n")),
/// 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>>> {
if let (i, Some(_)) = opt(char('='))(i)? {
let mut events = vec![];
events.push(Event::KeyValueSeparator);
let (i, whitespace) = opt(take_spaces)(i)?;
let (i, values) = value_impl(i)?;
if let Some(whitespace) = whitespace {
let mut events = vec![Event::Whitespace(Cow::Borrowed(whitespace))];
events.push(Event::Whitespace(Cow::Borrowed(whitespace)));
}
events.extend(values);
Ok((i, events))
} else {
Ok((i, values))
}
} else {
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)]
mod value_no_continuation {
use super::*;
@ -841,7 +923,7 @@ mod value_no_continuation {
fn no_comment() {
assert_eq!(
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() {
assert_eq!(
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() {
assert_eq!(
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() {
assert_eq!(
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() {
assert_eq!(
value_impl("hello ").unwrap(),
(
" ",
vec![Event::Value(Cow::Borrowed("hello"))]
)
(" ", vec![Event::Value(Cow::from("hello"))])
);
}
@ -886,14 +965,14 @@ mod value_no_continuation {
value_impl("hello #world").unwrap(),
(
" #world",
vec![Event::Value(Cow::Borrowed("hello"))]
vec![Event::Value(Cow::from("hello"))]
)
);
assert_eq!(
value_impl("hello ;world").unwrap(),
(
" ;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() {
assert_eq!(
value_impl(r##"hello"#"world; a"##).unwrap(),
(
"; a",
vec![Event::Value(Cow::Borrowed(r##"hello"#"world"##))]
)
("; a", vec![Event::Value(Cow::from(r##"hello"#"world"##))])
);
}
@ -913,7 +989,7 @@ mod value_no_continuation {
fn complex_test() {
assert_eq!(
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!(
value_impl("hello\\\nworld").unwrap(),
fully_consumed(vec![
Event::ValueNotDone(Cow::Borrowed("hello")),
Event::Newline(Cow::Borrowed("\n")),
Event::ValueDone(Cow::Borrowed("world"))
Event::ValueNotDone(Cow::from("hello")),
Event::Newline(Cow::from("\n")),
Event::ValueDone(Cow::from("world"))
])
);
}
@ -944,9 +1020,9 @@ mod value_continuation {
assert_eq!(
value_impl("hello\\\n world").unwrap(),
fully_consumed(vec![
Event::ValueNotDone(Cow::Borrowed("hello")),
Event::Newline(Cow::Borrowed("\n")),
Event::ValueDone(Cow::Borrowed(" world"))
Event::ValueNotDone(Cow::from("hello")),
Event::Newline(Cow::from("\n")),
Event::ValueDone(Cow::from(" world"))
])
)
}
@ -958,11 +1034,11 @@ mod value_continuation {
(
" # \"b\t ; c",
vec![
Event::ValueNotDone(Cow::Borrowed(r#"1 "\""#)),
Event::Newline(Cow::Borrowed("\n")),
Event::ValueNotDone(Cow::Borrowed(r#"a ; e "\""#)),
Event::Newline(Cow::Borrowed("\n")),
Event::ValueDone(Cow::Borrowed("d")),
Event::ValueNotDone(Cow::from(r#"1 "\""#)),
Event::Newline(Cow::from("\n")),
Event::ValueNotDone(Cow::from(r#"a ; e "\""#)),
Event::Newline(Cow::from("\n")),
Event::ValueDone(Cow::from("d")),
]
)
);
@ -975,9 +1051,9 @@ mod value_continuation {
(
";a",
vec![
Event::ValueNotDone(Cow::Borrowed("\"")),
Event::Newline(Cow::Borrowed("\n")),
Event::ValueDone(Cow::Borrowed(";\"")),
Event::ValueNotDone(Cow::from("\"")),
Event::Newline(Cow::from("\n")),
Event::ValueDone(Cow::from(";\"")),
]
)
)
@ -1010,10 +1086,11 @@ mod section {
fully_consumed(ParsedSection {
section_header: gen_section_header("hello", None),
events: vec![
Event::Newline(Cow::Borrowed("\n")),
Event::Whitespace(Cow::Borrowed(" ")),
Event::Key(Cow::Borrowed("a")),
Event::Whitespace(Cow::Borrowed(" ")),
Event::Newline(Cow::from("\n")),
Event::Whitespace(Cow::from(" ")),
Event::Key(Cow::from("a")),
Event::Whitespace(Cow::from(" ")),
Event::KeyValueSeparator,
Event::Whitespace(Cow::Borrowed(" ")),
Event::Value(Cow::Borrowed("b")),
Event::Newline(Cow::Borrowed("\n")),
@ -1024,6 +1101,7 @@ mod section {
Event::Whitespace(Cow::Borrowed(" ")),
Event::Key(Cow::Borrowed("d")),
Event::Whitespace(Cow::Borrowed(" ")),
Event::KeyValueSeparator,
Event::Whitespace(Cow::Borrowed(" ")),
Event::Value(Cow::Borrowed("\"lol\""))
]
@ -1067,6 +1145,7 @@ mod section {
Event::Whitespace(Cow::Borrowed(" ")),
Event::Key(Cow::Borrowed("a")),
Event::Whitespace(Cow::Borrowed(" ")),
Event::KeyValueSeparator,
Event::Whitespace(Cow::Borrowed(" ")),
Event::Value(Cow::Borrowed("b")),
Event::Whitespace(Cow::Borrowed(" ")),
@ -1090,6 +1169,7 @@ mod section {
Event::Whitespace(Cow::Borrowed(" ")),
Event::Key(Cow::Borrowed("c")),
Event::Whitespace(Cow::Borrowed(" ")),
Event::KeyValueSeparator,
Event::Whitespace(Cow::Borrowed(" ")),
Event::Value(Cow::Borrowed("d")),
]
@ -1108,6 +1188,7 @@ mod section {
Event::Whitespace(Cow::Borrowed(" ")),
Event::Key(Cow::Borrowed("a")),
Event::Whitespace(Cow::Borrowed(" ")),
Event::KeyValueSeparator,
Event::Whitespace(Cow::Borrowed(" ")),
Event::ValueNotDone(Cow::Borrowed(r#"1 "\""#)),
Event::Newline(Cow::Borrowed("\n")),
@ -1134,6 +1215,7 @@ mod section {
Event::Whitespace(Cow::Borrowed(" ")),
Event::Key(Cow::Borrowed("b")),
Event::Whitespace(Cow::Borrowed(" ")),
Event::KeyValueSeparator,
Event::ValueNotDone(Cow::Borrowed("\"")),
Event::Newline(Cow::Borrowed("\n")),
Event::ValueDone(Cow::Borrowed(";\"")),

View file

@ -16,9 +16,9 @@ impl<'a> Value<'a> {
return Self::Boolean(bool);
}
// if let Ok(int) = Integer::from_str(s) {
// return Self::Integer(int);
// }
if let Ok(int) = Integer::from_str(s) {
return Self::Integer(int);
}
// if let Ok(color) = Color::from_str(s) {
// return Self::Color(color);
@ -32,6 +32,8 @@ impl<'a> Value<'a> {
}
}
// todo display for value
impl Serialize for Value<'_> {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
@ -46,8 +48,6 @@ impl Serialize for Value<'_> {
}
}
// todo display for value
#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)]
pub enum Boolean<'a> {
True(TrueVariant<'a>),
@ -108,9 +108,10 @@ impl<'a> TrueVariant<'a> {
impl Display for TrueVariant<'_> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Explicit(v) => write!(f, "{}", v),
Self::Implicit => write!(f, "(implicit)"),
if let Self::Explicit(v) = self {
write!(f, "{}", v)
} else {
Ok(())
}
}
}
@ -163,12 +164,6 @@ pub struct Integer {
suffix: Option<IntegerSuffix>,
}
impl Integer {
pub fn from_str(s: &str) -> Result<Self, ()> {
todo!()
}
}
impl Display for Integer {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
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)]
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 {
foreground: 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)]
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))
}
fn separator() -> Event<'static> {
Event::KeyValueSeparator
}
#[test]
#[rustfmt::skip]
fn personal_config() {
let config = r#"[user]
email = code@eddie.sh
name = Edward Shen
name = Foo Bar
[core]
autocrlf = input
[push]
@ -73,6 +77,7 @@ fn personal_config() {
whitespace(" "),
name("email"),
whitespace(" "),
separator(),
whitespace(" "),
value("code@eddie.sh"),
newline(),
@ -80,8 +85,9 @@ fn personal_config() {
whitespace(" "),
name("name"),
whitespace(" "),
separator(),
whitespace(" "),
value("Edward Shen"),
value("Foo Bar"),
newline(),
gen_section_header("core", None),
@ -90,6 +96,7 @@ fn personal_config() {
whitespace(" "),
name("autocrlf"),
whitespace(" "),
separator(),
whitespace(" "),
value("input"),
newline(),
@ -100,6 +107,7 @@ fn personal_config() {
whitespace(" "),
name("default"),
whitespace(" "),
separator(),
whitespace(" "),
value("simple"),
newline(),
@ -110,6 +118,7 @@ fn personal_config() {
whitespace(" "),
name("gpgsign"),
whitespace(" "),
separator(),
whitespace(" "),
value("true"),
newline(),
@ -120,6 +129,7 @@ fn personal_config() {
whitespace(" "),
name("program"),
whitespace(" "),
separator(),
whitespace(" "),
value("gpg"),
newline(),
@ -130,6 +140,7 @@ fn personal_config() {
whitespace(" "),
name("insteadOf"),
whitespace(" "),
separator(),
whitespace(" "),
value("\"github://\""),
newline(),
@ -140,6 +151,7 @@ fn personal_config() {
whitespace(" "),
name("insteadOf"),
whitespace(" "),
separator(),
whitespace(" "),
value("\"gitea://\""),
newline(),
@ -150,6 +162,7 @@ fn personal_config() {
whitespace(" "),
name("ff"),
whitespace(" "),
separator(),
whitespace(" "),
value("only"),
newline(),
@ -160,6 +173,7 @@ fn personal_config() {
whitespace(" "),
name("defaultBranch"),
whitespace(" "),
separator(),
whitespace(" "),
value("master"),
]