diff --git a/.vscode/settings.json b/.vscode/settings.json index 6ac041c..8afb651 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -3,6 +3,7 @@ "Multivar", "autocrlf", "bstr", + "combinator", "gitea", "gpgsign", "implicits", diff --git a/src/lib.rs b/src/lib.rs index 6607b8a..20c41bc 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,5 +1,14 @@ #![forbid(unsafe_code)] +//! # git_config +//! +//! This crate is a high performance `git-config` file reader and writer. It +//! exposes a high level API to parse, read, and write [`git-config` files], +//! which are loosely based on the [INI file format]. +//! +//! [`git-config` files]: https://git-scm.com/docs/git-config#_configuration_file +//! [INI file format]: https://en.wikipedia.org/wiki/INI_file + // 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 diff --git a/src/parser.rs b/src/parser.rs index f4ba895..bef9b11 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -790,6 +790,13 @@ fn config_value<'a, 'b>(i: &'a [u8], events: &'b mut Vec>) -> IResult< } } +/// Handles parsing of known-to-be values. This function handles both single +/// line values as well as values that are continuations. +/// +/// # Errors +/// +/// Returns an error if an invalid escape was used, if there was an unfinished +/// quote, or there was an escape but there is nothing left to escape. fn value_impl<'a, 'b>(i: &'a [u8], events: &'b mut Vec>) -> IResult<&'a [u8], ()> { let mut parsed_index: usize = 0; let mut offset: usize = 0; diff --git a/src/values.rs b/src/values.rs index c6d4abe..097ccfa 100644 --- a/src/values.rs +++ b/src/values.rs @@ -1,6 +1,6 @@ //! Rust containers for valid `git-config` types. -use std::{borrow::Cow, fmt::Display, str::FromStr}; +use std::{borrow::Cow, convert::TryFrom, fmt::Display, str::FromStr}; use bstr::BStr; #[cfg(feature = "serde")] @@ -27,7 +27,7 @@ impl<'a> Value<'a> { impl<'a> From<&'a str> for Value<'a> { fn from(s: &'a str) -> Self { - if let Ok(bool) = Boolean::from_str(s) { + if let Ok(bool) = Boolean::try_from(s) { return Self::Boolean(bool); } @@ -66,9 +66,11 @@ pub enum Boolean<'a> { False(&'a str), } -impl<'a> Boolean<'a> { - pub fn from_str(value: &'a str) -> Result { - if let Ok(v) = TrueVariant::from_str(value) { +impl<'a> TryFrom<&'a str> for Boolean<'a> { + type Error = (); + + fn try_from(value: &'a str) -> Result { + if let Ok(v) = TrueVariant::try_from(value) { return Ok(Self::True(v)); } @@ -89,7 +91,7 @@ impl Display for Boolean<'_> { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { Boolean::True(v) => v.fmt(f), - Boolean::False(v) => v.fmt(f), + Boolean::False(v) => write!(f, "{}", v), } } } @@ -126,8 +128,10 @@ pub enum TrueVariant<'a> { Implicit, } -impl<'a> TrueVariant<'a> { - pub fn from_str(value: &'a str) -> Result, ()> { +impl<'a> TryFrom<&'a str> for TrueVariant<'a> { + type Error = (); + + fn try_from(value: &'a str) -> Result { if value.eq_ignore_ascii_case("yes") || value.eq_ignore_ascii_case("on") || value.eq_ignore_ascii_case("true") @@ -221,14 +225,15 @@ impl FromStr for Integer { } #[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)] -enum IntegerSuffix { +pub enum IntegerSuffix { Kilo, Mega, Giga, } impl IntegerSuffix { - fn bitwise_offset(&self) -> usize { + /// Returns the number of bits that the suffix shifts left by. + pub fn bitwise_offset(&self) -> usize { match self { Self::Kilo => 10, Self::Mega => 20, @@ -579,29 +584,29 @@ mod boolean { #[test] fn from_str_false() { - assert_eq!(Boolean::from_str("no"), Ok(Boolean::False("no"))); - assert_eq!(Boolean::from_str("off"), Ok(Boolean::False("off"))); - assert_eq!(Boolean::from_str("false"), Ok(Boolean::False("false"))); - assert_eq!(Boolean::from_str("zero"), Ok(Boolean::False("zero"))); - assert_eq!(Boolean::from_str("\"\""), Ok(Boolean::False("\"\""))); + assert_eq!(Boolean::try_from("no"), Ok(Boolean::False("no"))); + assert_eq!(Boolean::try_from("off"), Ok(Boolean::False("off"))); + assert_eq!(Boolean::try_from("false"), Ok(Boolean::False("false"))); + assert_eq!(Boolean::try_from("zero"), Ok(Boolean::False("zero"))); + assert_eq!(Boolean::try_from("\"\""), Ok(Boolean::False("\"\""))); } #[test] fn from_str_true() { assert_eq!( - Boolean::from_str("yes"), + Boolean::try_from("yes"), Ok(Boolean::True(TrueVariant::Explicit("yes"))) ); assert_eq!( - Boolean::from_str("on"), + Boolean::try_from("on"), Ok(Boolean::True(TrueVariant::Explicit("on"))) ); assert_eq!( - Boolean::from_str("true"), + Boolean::try_from("true"), Ok(Boolean::True(TrueVariant::Explicit("true"))) ); assert_eq!( - Boolean::from_str("one"), + Boolean::try_from("one"), Ok(Boolean::True(TrueVariant::Explicit("one"))) ); } @@ -610,17 +615,17 @@ mod boolean { 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(); + let first: bool = Boolean::try_from(*word).unwrap().into(); + let second: bool = Boolean::try_from(&*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()); + assert!(Boolean::try_from("yesn't").is_err()); + assert!(Boolean::try_from("yesno").is_err()); + assert!(Boolean::try_from("").is_err()); } }