Support for a convenient way of knowing if a value does or doesn't exist via `try_value()`, which can only fail if the conversion fails. Lastly, `string()` is a special case which doesn't fail as there is no conversion, and `boolean()` allows to obtain a plain boolean value if it was a valid boolean representation.
Unified · Split
| @@ -1,3 +1,4 @@ | |||
| 1 | use bstr::BStr; | ||
| 1 | use std::{ | 2 | use std::{ |
| 2 | borrow::Cow, | 3 | borrow::Cow, |
| 3 | collections::{HashMap, VecDeque}, | 4 | collections::{HashMap, VecDeque}, |
| @@ -441,6 +442,42 @@ impl<'event> GitConfig<'event> { · 437 unmodified lines | |||
| 441 | T::try_from(self.raw_value(section_name, subsection_name, key)?).map_err(lookup::Error::FailedConversion) | 442 | T::try_from(self.raw_value(section_name, subsection_name, key)?).map_err(lookup::Error::FailedConversion) |
| 442 | } | 443 | } |
| 443 | 444 | ||
| 445 | /// Like [`value()`][GitConfig::value()], but returning an `Option` if the value wasn't found. | ||
| 446 | pub fn try_value<T: TryFrom<Cow<'event, [u8]>>>( | ||
| 447 | &'event self, | ||
| 448 | section_name: &str, | ||
| 449 | subsection_name: Option<&str>, | ||
| 450 | key: &str, | ||
| 451 | ) -> Option<Result<T, T::Error>> { | ||
| 452 | self.raw_value(section_name, subsection_name, key).ok().map(T::try_from) | ||
| 453 | } | ||
| 454 | |||
| 455 | /// Like [`value()`][GitConfig::value()], but returning an `Option` if the string wasn't found. | ||
| 456 | /// | ||
| 457 | /// As strings perform no conversions, this will never fail. | ||
| 458 | pub fn string( | ||
| 459 | &'event self, | ||
| 460 | section_name: &str, | ||
| 461 | subsection_name: Option<&str>, | ||
| 462 | key: &str, | ||
| 463 | ) -> Option<Cow<'event, BStr>> { | ||
| 464 | self.raw_value(section_name, subsection_name, key) | ||
| 465 | .ok() | ||
| 466 | .map(|v| values::String::from(v).value) | ||
| 467 | } | ||
| 468 | |||
| 469 | /// Like [`value()`][GitConfig::value()], but returning an `Option` if the boolean wasn't found. | ||
| 470 | pub fn boolean( | ||
| 471 | &'event self, | ||
| 472 | section_name: &str, | ||
| 473 | subsection_name: Option<&str>, | ||
| 474 | key: &str, | ||
| 475 | ) -> Option<Result<bool, values::BooleanError>> { | ||
| 476 | self.raw_value(section_name, subsection_name, key) | ||
| 477 | .ok() | ||
| 478 | .map(|v| values::Boolean::try_from(v).map(|b| b.to_bool())) | ||
| 479 | } | ||
| 480 | |||
| 444 | /// Returns all interpreted values given a section, an optional subsection | 481 | /// Returns all interpreted values given a section, an optional subsection |
| 445 | /// and key. | 482 | /// and key. |
| 446 | /// | 483 | /// |
| @@ -2,7 +2,7 @@ · 1 unmodified line | |||
| 2 | 2 | ||
| 3 | use std::{borrow::Cow, convert::TryFrom, fmt::Display, str::FromStr}; | 3 | use std::{borrow::Cow, convert::TryFrom, fmt::Display, str::FromStr}; |
| 4 | 4 | ||
| 5 | use bstr::BStr; | 5 | use bstr::{BStr, BString}; |
| 6 | #[cfg(feature = "serde")] | 6 | #[cfg(feature = "serde")] |
| 7 | use serde::{Serialize, Serializer}; | 7 | use serde::{Serialize, Serializer}; |
| 8 | 8 | ||
| @@ -377,9 +377,9 @@ impl Boolean<'_> { · 368 unmodified lines | |||
| 377 | /// The error returned when creating `Boolean` from byte string. | 377 | /// The error returned when creating `Boolean` from byte string. |
| 378 | #[derive(Debug, PartialEq, thiserror::Error)] | 378 | #[derive(Debug, PartialEq, thiserror::Error)] |
| 379 | #[allow(missing_docs)] | 379 | #[allow(missing_docs)] |
| 380 | pub enum BooleanError { | 380 | #[error("Invalid boolean value: '{}'", .input)] |
| 381 | #[error("Invalid argument format")] | 381 | pub struct BooleanError { |
| 382 | InvalidFormat, | 382 | pub input: BString, |
| 383 | } | 383 | } |
| 384 | 384 | ||
| 385 | impl<'a> TryFrom<&'a [u8]> for Boolean<'a> { | 385 | impl<'a> TryFrom<&'a [u8]> for Boolean<'a> { |
| @@ -401,7 +401,7 @@ impl<'a> TryFrom<&'a [u8]> for Boolean<'a> { · 15 unmodified lines | |||
| 401 | )); | 401 | )); |
| 402 | } | 402 | } |
| 403 | 403 | ||
| 404 | Err(BooleanError::InvalidFormat) | 404 | Err(BooleanError { input: value.into() }) |
| 405 | } | 405 | } |
| 406 | } | 406 | } |
| 407 | 407 | ||
| @@ -512,7 +512,7 @@ impl<'a> TryFrom<&'a [u8]> for TrueVariant<'a> { · 104 unmodified lines | |||
| 512 | } else if value.is_empty() { | 512 | } else if value.is_empty() { |
| 513 | Ok(Self::Implicit) | 513 | Ok(Self::Implicit) |
| 514 | } else { | 514 | } else { |
| 515 | Err(BooleanError::InvalidFormat) | 515 | Err(BooleanError { input: value.into() }) |
| 516 | } | 516 | } |
| 517 | } | 517 | } |
| 518 | } | 518 | } |
| @@ -532,7 +532,7 @@ impl TryFrom<Vec<u8>> for TrueVariant<'_> { · 13 unmodified lines | |||
| 532 | } else if value.is_empty() { | 532 | } else if value.is_empty() { |
| 533 | Ok(Self::Implicit) | 533 | Ok(Self::Implicit) |
| 534 | } else { | 534 | } else { |
| 535 | Err(BooleanError::InvalidFormat) | 535 | Err(BooleanError { input: value.into() }) |
| 536 | } | 536 | } |
| 537 | } | 537 | } |
| 538 | } | 538 | } |
| @@ -23,11 +23,20 @@ fn get_value_for_all_provided_values() -> crate::Result { · 22 unmodified lines | |||
| 23 | file.value::<Boolean>("core", None, "bool-explicit")?, | 23 | file.value::<Boolean>("core", None, "bool-explicit")?, |
| 24 | Boolean::False(Cow::Borrowed("false")) | 24 | Boolean::False(Cow::Borrowed("false")) |
| 25 | ); | 25 | ); |
| 26 | assert_eq!(file.boolean("core", None, "bool-explicit").expect("exists")?, false); | ||
| 26 | 27 | ||
| 27 | assert_eq!( | 28 | assert_eq!( |
| 28 | file.value::<Boolean>("core", None, "bool-implicit")?, | 29 | file.value::<Boolean>("core", None, "bool-implicit")?, |
| 29 | Boolean::True(TrueVariant::Implicit) | 30 | Boolean::True(TrueVariant::Implicit) |
| 30 | ); | 31 | ); |
| 32 | assert_eq!( | ||
| 33 | file.try_value::<Boolean>("core", None, "bool-implicit") | ||
| 34 | .expect("exists")?, | ||
| 35 | Boolean::True(TrueVariant::Implicit) | ||
| 36 | ); | ||
| 37 | |||
| 38 | assert_eq!(file.boolean("core", None, "bool-implicit").expect("present")?, true); | ||
| 39 | assert_eq!(file.try_value::<String>("doesnt", None, "exist"), None); | ||
| 31 | 40 | ||
| 32 | assert_eq!( | 41 | assert_eq!( |
| 33 | file.value::<Integer>("core", None, "integer-no-prefix")?, | 42 | file.value::<Integer>("core", None, "integer-no-prefix")?, |
| @@ -69,6 +78,11 @@ fn get_value_for_all_provided_values() -> crate::Result { · 35 unmodified lines | |||
| 69 | } | 78 | } |
| 70 | ); | 79 | ); |
| 71 | 80 | ||
| 81 | assert_eq!( | ||
| 82 | file.string("core", None, "other").expect("present").as_ref(), | ||
| 83 | "hello world" | ||
| 84 | ); | ||
| 85 | |||
| 72 | let actual = file.value::<git_config::values::Path>("core", None, "location")?; | 86 | let actual = file.value::<git_config::values::Path>("core", None, "location")?; |
| 73 | assert_eq!( | 87 | assert_eq!( |
| 74 | &*actual, | 88 | &*actual, |