#![deny(missing_docs)]
#![deny(unsafe_code)]
use std::num::ParseIntError;
use std::path::PathBuf;
use thiserror::Error;
#[cfg(feature = "derive")]
pub use pkgsrc_kv_derive::Kv;
#[derive(Clone, Copy, Debug, Default, Eq, Hash, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct Span {
pub offset: usize,
pub len: usize,
}
impl From<Span> for std::ops::Range<usize> {
fn from(span: Span) -> Self {
span.offset..span.offset + span.len
}
}
#[derive(Clone, Debug, Eq, Hash, PartialEq)]
pub struct KvWarning {
pub variable: String,
pub value: String,
pub span: Span,
}
impl std::fmt::Display for KvWarning {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "invalid value {:?} for {}", self.value, self.variable)
}
}
#[derive(Debug, Error)]
pub enum KvError {
#[error("line is not in KEY=VALUE format")]
ParseLine(Span),
#[error("missing required field '{0}'")]
Incomplete(String),
#[error("unknown variable '{variable}'")]
UnknownVariable {
variable: String,
span: Span,
},
#[error("failed to parse integer")]
ParseInt {
#[source]
source: ParseIntError,
span: Span,
},
#[error("{message}")]
Parse {
message: String,
span: Span,
},
}
impl KvError {
#[must_use]
pub const fn span(&self) -> Option<Span> {
match self {
Self::ParseLine(span)
| Self::UnknownVariable { span, .. }
| Self::ParseInt { span, .. }
| Self::Parse { span, .. } => Some(*span),
Self::Incomplete(_) => None,
}
}
}
pub type Result<T> = std::result::Result<T, KvError>;
pub trait FromKv: Sized {
fn from_kv(value: &str, span: Span) -> Result<Self>;
}
impl FromKv for String {
fn from_kv(value: &str, _span: Span) -> Result<Self> {
Ok(value.to_string())
}
}
macro_rules! impl_fromkv_for_int {
($($t:ty),*) => {
$(
impl FromKv for $t {
fn from_kv(value: &str, span: Span) -> Result<Self> {
value.parse().map_err(|source: ParseIntError| KvError::ParseInt {
source,
span,
})
}
}
)*
};
}
impl_fromkv_for_int!(u8, u16, u32, u64, usize, i8, i16, i32, i64, isize);
impl FromKv for PathBuf {
fn from_kv(value: &str, _span: Span) -> Result<Self> {
Ok(Self::from(value))
}
}
impl FromKv for bool {
fn from_kv(value: &str, span: Span) -> Result<Self> {
match value.to_lowercase().as_str() {
"true" | "yes" | "1" => Ok(true),
"false" | "no" | "0" => Ok(false),
_ => Err(KvError::Parse {
message: format!("invalid boolean: {value}"),
span,
}),
}
}
}
#[doc(hidden)]
pub fn words_with_spans(
value: &str,
base: usize,
) -> impl Iterator<Item = (&str, Span)> {
let value_start = value.as_ptr() as usize;
value.split_whitespace().map(move |word| {
let offset = base + (word.as_ptr() as usize - value_start);
let span = Span {
offset,
len: word.len(),
};
(word, span)
})
}
impl<T: FromKv> FromKv for Vec<T> {
fn from_kv(value: &str, span: Span) -> Result<Self> {
words_with_spans(value, span.offset)
.map(|(word, word_span)| T::from_kv(word, word_span))
.collect()
}
}