Compare commits

...

6 commits

8 changed files with 167 additions and 21 deletions

View file

@ -3,20 +3,21 @@ name = "git-config"
version = "0.1.0" version = "0.1.0"
authors = ["Edward Shen <code@eddie.sh>"] authors = ["Edward Shen <code@eddie.sh>"]
edition = "2018" edition = "2018"
exclude = ["fuzz/**/*", ".vscode/**/*"]
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies] [features]
nom = "6" serde = ["serde_crate"]
bstr = "0.2.15"
[dependencies.serde_crate] [dependencies]
package = "serde" bstr = "0.2.15"
optional = true nom = { version = "6", default_features = false, features = ["std"] }
version = " 1" serde_crate = { version = "1", package = "serde", optional = true}
[dev-dependencies] [dev-dependencies]
serde_derive = "1.0" serde_derive = "1.0"
[features] [profile.release]
serde = ["serde_crate"] lto = true
codegen-units = 1

4
fuzz/.gitignore vendored Normal file
View file

@ -0,0 +1,4 @@
target
corpus
artifacts

26
fuzz/Cargo.toml Normal file
View file

@ -0,0 +1,26 @@
[package]
name = "git-config-fuzz"
version = "0.0.0"
authors = ["Automatically generated"]
publish = false
edition = "2018"
[package.metadata]
cargo-fuzz = true
[dependencies]
libfuzzer-sys = "0.3"
[dependencies.git-config]
path = ".."
# Prevent this from interfering with workspaces
[workspace]
members = ["."]
[[bin]]
name = "parser"
path = "fuzz_targets/parser.rs"
test = false
doc = false

View file

@ -0,0 +1,8 @@
#![no_main]
use git_config::parser::Parser;
use libfuzzer_sys::fuzz_target;
fuzz_target!(|data: &[u8]| {
Parser::from_bytes(data);
});

View file

@ -200,7 +200,7 @@ impl<'a> GitConfig<'a> {
/// `d`, since the last valid config value is `a = d`: /// `d`, since the last valid config value is `a = d`:
/// ///
/// ``` /// ```
/// # use serde_git_config::config::GitConfig; /// # use git_config::config::GitConfig;
/// # use std::borrow::Cow; /// # use std::borrow::Cow;
/// # let git_config = GitConfig::from_str("[core]a=b\n[core]\na=c\na=d").unwrap(); /// # let git_config = GitConfig::from_str("[core]a=b\n[core]\na=c\na=d").unwrap();
/// assert_eq!(git_config.get_raw_value("core", None, "a"), Ok(&Cow::Borrowed("d".into()))); /// assert_eq!(git_config.get_raw_value("core", None, "a"), Ok(&Cow::Borrowed("d".into())));
@ -275,7 +275,7 @@ impl<'a> GitConfig<'a> {
/// `d`, since the last valid config value is `a = d`: /// `d`, since the last valid config value is `a = d`:
/// ///
/// ``` /// ```
/// # use serde_git_config::config::{GitConfig, GitConfigError}; /// # use git_config::config::{GitConfig, GitConfigError};
/// # use std::borrow::Cow; /// # use std::borrow::Cow;
/// # use bstr::BStr; /// # use bstr::BStr;
/// # let mut git_config = GitConfig::from_str("[core]a=b\n[core]\na=c\na=d").unwrap(); /// # let mut git_config = GitConfig::from_str("[core]a=b\n[core]\na=c\na=d").unwrap();
@ -354,7 +354,7 @@ impl<'a> GitConfig<'a> {
/// Attempting to get all values of `a` yields the following: /// Attempting to get all values of `a` yields the following:
/// ///
/// ``` /// ```
/// # use serde_git_config::config::GitConfig; /// # use git_config::config::GitConfig;
/// # use std::borrow::Cow; /// # use std::borrow::Cow;
/// # let git_config = GitConfig::from_str("[core]a=b\n[core]\na=c\na=d").unwrap(); /// # let git_config = GitConfig::from_str("[core]a=b\n[core]\na=c\na=d").unwrap();
/// assert_eq!( /// assert_eq!(
@ -419,7 +419,7 @@ impl<'a> GitConfig<'a> {
/// Attempting to get all values of `a` yields the following: /// Attempting to get all values of `a` yields the following:
/// ///
/// ``` /// ```
/// # use serde_git_config::config::{GitConfig, GitConfigError}; /// # use git_config::config::{GitConfig, GitConfigError};
/// # use std::borrow::Cow; /// # use std::borrow::Cow;
/// # use bstr::BStr; /// # use bstr::BStr;
/// # let mut git_config = GitConfig::from_str("[core]a=b\n[core]\na=c\na=d").unwrap(); /// # let mut git_config = GitConfig::from_str("[core]a=b\n[core]\na=c\na=d").unwrap();

View file

@ -1,12 +1,16 @@
#![forbid(unsafe_code)] #![forbid(unsafe_code)]
// Cargo.toml cannot have self-referential dependencies, so you can't just
// specify the actual serde crate when you define a feature called serde. We
// instead call the serde crate as serde_crate and then rename the crate to
// serde, to get around this in an intuitive manner.
#[cfg(feature = "serde")] #[cfg(feature = "serde")]
extern crate serde_crate as serde; extern crate serde_crate as serde;
// mod de; // mod de;
// mod ser;
pub mod config; pub mod config;
mod error; mod error;
// mod ser;
pub mod parser; pub mod parser;
pub mod values; pub mod values;

View file

@ -249,7 +249,7 @@ impl<'a> From<nom::Err<NomError<&'a [u8]>>> for ParserError<'a> {
/// 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 serde_git_config::parser::{Event, ParsedSectionHeader, parse_from_str}; /// # use git_config::parser::{Event, ParsedSectionHeader, parse_from_str};
/// # use std::borrow::Cow; /// # use std::borrow::Cow;
/// # let section_header = ParsedSectionHeader { /// # let section_header = ParsedSectionHeader {
/// # name: Cow::Borrowed("core".into()), /// # name: Cow::Borrowed("core".into()),
@ -288,7 +288,7 @@ impl<'a> From<nom::Err<NomError<&'a [u8]>>> for ParserError<'a> {
/// which means that the corresponding event won't appear either: /// which means that the corresponding event won't appear either:
/// ///
/// ``` /// ```
/// # use serde_git_config::parser::{Event, ParsedSectionHeader, parse_from_str}; /// # use git_config::parser::{Event, ParsedSectionHeader, parse_from_str};
/// # use std::borrow::Cow; /// # use std::borrow::Cow;
/// # use bstr::BStr; /// # use bstr::BStr;
/// # let section_header = ParsedSectionHeader { /// # let section_header = ParsedSectionHeader {
@ -323,7 +323,7 @@ impl<'a> From<nom::Err<NomError<&'a [u8]>>> for ParserError<'a> {
/// relevant event stream emitted is thus emitted as: /// relevant event stream emitted is thus emitted as:
/// ///
/// ``` /// ```
/// # use serde_git_config::parser::{Event, ParsedSectionHeader, parse_from_str}; /// # use git_config::parser::{Event, ParsedSectionHeader, parse_from_str};
/// # use std::borrow::Cow; /// # use std::borrow::Cow;
/// # let section_header = ParsedSectionHeader { /// # let section_header = ParsedSectionHeader {
/// # name: Cow::Borrowed("core".into()), /// # name: Cow::Borrowed("core".into()),
@ -360,7 +360,7 @@ impl<'a> From<nom::Err<NomError<&'a [u8]>>> for ParserError<'a> {
/// split value accordingly: /// split value accordingly:
/// ///
/// ``` /// ```
/// # use serde_git_config::parser::{Event, ParsedSectionHeader, parse_from_str}; /// # use git_config::parser::{Event, ParsedSectionHeader, parse_from_str};
/// # use std::borrow::Cow; /// # use std::borrow::Cow;
/// # let section_header = ParsedSectionHeader { /// # let section_header = ParsedSectionHeader {
/// # name: Cow::Borrowed("some-section".into()), /// # name: Cow::Borrowed("some-section".into()),
@ -391,9 +391,9 @@ pub struct Parser<'a> {
impl<'a> Parser<'a> { impl<'a> Parser<'a> {
/// Attempt to zero-copy parse the provided `&str`. On success, returns a /// Attempt to zero-copy parse the provided `&str`. On success, returns a
/// [`Parser`] that provides methods to accessing leading comments and sections /// [`Parser`] that provides methods to accessing leading comments and
/// of a `git-config` file and can be converted into an iterator of [`Event`] /// sections of a `git-config` file and can be converted into an iterator of
/// for higher level processing. /// [`Event`] for higher level processing.
/// ///
/// This function is identical to [`parse_from_str`]. /// This function is identical to [`parse_from_str`].
/// ///
@ -406,6 +406,20 @@ impl<'a> Parser<'a> {
parse_from_str(s) 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 /// Returns the leading events (any comments, whitespace, or newlines before
/// a section) from the parser. Consider [`Parser::take_frontmatter`] if /// a section) from the parser. Consider [`Parser::take_frontmatter`] if
/// you need an owned copy only once. If that function was called, then this /// you need an owned copy only once. If that function was called, then this
@ -471,6 +485,20 @@ 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> {
parse_from_bytes(input.as_bytes())
}
/// 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 parse_from_bytes(input: &[u8]) -> Result<Parser<'_>, ParserError> {
let (i, frontmatter) = many0(alt(( let (i, frontmatter) = many0(alt((
map(comment, Event::Comment), map(comment, Event::Comment),
map(take_spaces, |whitespace| { map(take_spaces, |whitespace| {

View file

@ -79,6 +79,15 @@ impl Display for Boolean<'_> {
} }
} }
impl Into<bool> for Boolean<'_> {
fn into(self) -> bool {
match self {
Boolean::True(_) => true,
Boolean::False(_) => false,
}
}
}
#[cfg(feature = "serde")] #[cfg(feature = "serde")]
impl Serialize for Boolean<'_> { impl Serialize for Boolean<'_> {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
@ -583,6 +592,72 @@ impl FromStr for ColorAttribute {
} }
} }
#[cfg(test)]
mod boolean {
use super::*;
#[test]
fn from_str_false() {
assert_eq!(
Boolean::from_str("no"),
Ok(Boolean::False(FalseVariant("no")))
);
assert_eq!(
Boolean::from_str("off"),
Ok(Boolean::False(FalseVariant("off")))
);
assert_eq!(
Boolean::from_str("false"),
Ok(Boolean::False(FalseVariant("false")))
);
assert_eq!(
Boolean::from_str("zero"),
Ok(Boolean::False(FalseVariant("zero")))
);
assert_eq!(
Boolean::from_str("\"\""),
Ok(Boolean::False(FalseVariant("\"\"")))
);
}
#[test]
fn from_str_true() {
assert_eq!(
Boolean::from_str("yes"),
Ok(Boolean::True(TrueVariant::Explicit("yes")))
);
assert_eq!(
Boolean::from_str("on"),
Ok(Boolean::True(TrueVariant::Explicit("on")))
);
assert_eq!(
Boolean::from_str("true"),
Ok(Boolean::True(TrueVariant::Explicit("true")))
);
assert_eq!(
Boolean::from_str("one"),
Ok(Boolean::True(TrueVariant::Explicit("one")))
);
}
#[test]
fn ignores_case() {
// Random subset
for word in &["no", "yes", "off", "true", "zero"] {
let first: bool = Boolean::from_str(word).unwrap().into();
let second: bool = Boolean::from_str(&word.to_uppercase()).unwrap().into();
assert_eq!(first, second);
}
}
#[test]
fn from_str_err() {
assert!(Boolean::from_str("yesn't").is_err());
assert!(Boolean::from_str("yesno").is_err());
assert!(Boolean::from_str("").is_err());
}
}
#[cfg(test)] #[cfg(test)]
mod integer { mod integer {
use super::*; use super::*;