use super::date::standardize_date;
use super::Metadata;
use crate::error::MetadataError;
use alloc::string::{String, ToString};
use alloc::vec;
use alloc::vec::Vec;
#[derive(Debug, Clone, PartialEq, Eq)]
#[non_exhaustive]
pub struct ProcessOptions {
pub required_fields: Vec<String>,
pub derive_slug: bool,
pub date_order: DateOrder,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
#[non_exhaustive]
pub enum DateOrder {
#[default]
DayFirst,
MonthFirst,
}
impl Default for ProcessOptions {
fn default() -> Self {
Self {
required_fields: vec![
"title".to_string(),
"date".to_string(),
],
derive_slug: true,
date_order: DateOrder::DayFirst,
}
}
}
impl ProcessOptions {
#[must_use]
pub fn required_fields<I, S>(mut self, fields: I) -> Self
where
I: IntoIterator<Item = S>,
S: Into<String>,
{
self.required_fields =
fields.into_iter().map(Into::into).collect();
self
}
#[must_use]
pub const fn derive_slug(mut self, on: bool) -> Self {
self.derive_slug = on;
self
}
#[must_use]
pub const fn date_order(mut self, order: DateOrder) -> Self {
self.date_order = order;
self
}
}
pub fn process_metadata_with(
metadata: &Metadata,
options: &ProcessOptions,
) -> Result<Metadata, MetadataError> {
let mut processed = metadata.clone();
if let Some(date) = processed.get("date").cloned() {
let standardized_date =
standardize_date(&date, options.date_order)?;
processed.insert("date".to_string(), standardized_date);
}
for field in &options.required_fields {
if !processed.contains_key(field) {
return Err(MetadataError::MissingFieldError(
field.clone(),
));
}
}
if options.derive_slug {
generate_derived_fields(&mut processed);
}
Ok(processed)
}
pub fn process_metadata(
metadata: &Metadata,
) -> Result<Metadata, MetadataError> {
process_metadata_with(metadata, &ProcessOptions::default())
}
pub(super) fn generate_derived_fields(metadata: &mut Metadata) {
if !metadata.contains_key("slug") {
if let Some(title) = metadata.get("title") {
let slug = generate_slug(title);
metadata.insert("slug".to_string(), slug);
}
}
}
pub(super) fn generate_slug(title: &str) -> String {
let mut slug = String::with_capacity(title.len());
let mut pending_separator = false;
for ch in title.chars().flat_map(char::to_lowercase) {
if ch.is_alphanumeric() {
if pending_separator && !slug.is_empty() {
slug.push('-');
}
pending_separator = false;
slug.push(ch);
} else {
pending_separator = true;
}
}
slug
}