use crate::error::MetadataError;
use crate::MetadataMap;
use alloc::string::{String, ToString};
mod date;
mod front_matter;
mod process;
mod scan;
#[cfg(all(
test,
feature = "std",
feature = "yaml",
feature = "toml",
feature = "json"
))]
use date::standardize_date;
use front_matter::parse_block;
#[cfg(all(
test,
feature = "std",
feature = "yaml",
feature = "toml",
feature = "json"
))]
use process::{generate_derived_fields, generate_slug};
pub use process::{
process_metadata, process_metadata_with, DateOrder, ProcessOptions,
};
use scan::Scan;
#[derive(Debug, Default, Clone)]
pub struct Metadata {
inner: MetadataMap,
}
impl Metadata {
pub fn new(data: MetadataMap) -> Self {
Metadata { inner: data }
}
pub fn get(&self, key: &str) -> Option<&String> {
self.inner.get(key)
}
pub fn insert(
&mut self,
key: String,
value: String,
) -> Option<String> {
self.inner.insert(key, value)
}
pub fn contains_key(&self, key: &str) -> bool {
self.inner.contains_key(key)
}
pub fn into_inner(self) -> MetadataMap {
self.inner
}
}
pub fn extract_metadata(
content: &str,
) -> Result<Metadata, MetadataError> {
extract_metadata_with_limits(content, &ParseLimits::default())
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[non_exhaustive]
pub struct ParseLimits {
pub max_block_bytes: usize,
pub max_depth: usize,
}
impl Default for ParseLimits {
fn default() -> Self {
Self {
max_block_bytes: 1024 * 1024,
max_depth: 64,
}
}
}
impl ParseLimits {
#[must_use]
pub const fn max_block_bytes(mut self, bytes: usize) -> Self {
self.max_block_bytes = bytes;
self
}
#[must_use]
pub const fn max_depth(mut self, depth: usize) -> Self {
self.max_depth = depth;
self
}
}
pub fn extract_metadata_with_limits(
content: &str,
limits: &ParseLimits,
) -> Result<Metadata, MetadataError> {
match scan::scan(content) {
Scan::Found { format, raw, .. } => parse_block(format, raw, limits),
Scan::Unterminated { format, offset } => {
Err(MetadataError::ExtractionError {
message: alloc::format!(
"{format} front matter opened at byte {offset} has no closing fence."
),
})
}
Scan::Unsupported(format) => {
Err(MetadataError::UnsupportedFormatError(format.to_string()))
}
Scan::None => Err(MetadataError::ExtractionError {
message: "No valid front matter found.".to_string(),
}),
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub enum FrontMatterFormat {
Yaml,
Toml,
Json,
}
impl FrontMatterFormat {
pub const fn as_str(self) -> &'static str {
match self {
Self::Yaml => "yaml",
Self::Toml => "toml",
Self::Json => "json",
}
}
}
impl core::fmt::Display for FrontMatterFormat {
fn fmt(
&self,
f: &mut core::fmt::Formatter<'_>,
) -> core::fmt::Result {
f.write_str(self.as_str())
}
}
pub fn detect_front_matter(
content: &str,
) -> Option<(FrontMatterFormat, &str, usize)> {
match scan::scan(content) {
Scan::Found {
format,
raw,
body_offset,
} => Some((format, raw, body_offset)),
_ => None,
}
}
pub fn extract_metadata_with_body(
content: &str,
) -> Result<(Metadata, &str), MetadataError> {
let metadata = extract_metadata(content)?;
let body_start = detect_front_matter(content)
.map_or(content.len(), |(_, _, start)| start);
let body = content[body_start..]
.strip_prefix("\r\n")
.or_else(|| content[body_start..].strip_prefix('\n'))
.unwrap_or(&content[body_start..]);
Ok((metadata, body))
}
pub fn extract_typed<T: serde::de::DeserializeOwned + 'static>(
content: &str,
) -> Result<T, MetadataError> {
let (format, raw, _) =
detect_front_matter(content).ok_or_else(|| {
MetadataError::ExtractionError {
message: "No valid front matter found.".to_string(),
}
})?;
front_matter::deserialize_block::<T>(
format,
raw,
&ParseLimits::default(),
)
}
pub fn extract_typed_borrowed<'a, T: serde::Deserialize<'a>>(
content: &'a str,
) -> Result<T, MetadataError> {
let (format, raw, _) =
detect_front_matter(content).ok_or_else(|| {
MetadataError::ExtractionError {
message: "No valid front matter found.".to_string(),
}
})?;
front_matter::deserialize_borrowed::<T>(
format,
raw,
&ParseLimits::default(),
)
}
#[cfg(all(
test,
feature = "std",
feature = "yaml",
feature = "toml",
feature = "json"
))]
mod tests;
#[cfg(all(
test,
feature = "std",
feature = "yaml",
feature = "toml",
feature = "json"
))]
mod coverage_tests;
#[cfg(all(
test,
feature = "std",
feature = "yaml",
feature = "toml",
feature = "json"
))]
mod typed_api_tests;