bim 0.1.0

A content-first static site generator for blogs, digital gardens, and knowledge bases
Documentation
use crate::git::GitRepo;
use chrono::{DateTime, NaiveDate, TimeZone, Utc};
use regex::Regex;
use std::path::Path;
use std::sync::LazyLock;

static DATE_PREFIX_REGEX: LazyLock<Regex> =
    LazyLock::new(|| Regex::new(r"^(\d{4}-\d{2}-\d{2})-").unwrap());

pub fn extract_date(path: &Path, git_repo: Option<&GitRepo>) -> Option<DateTime<Utc>> {
    // Priority 1: Filename prefix (YYYY-MM-DD-)
    if let Some(date) = extract_date_from_filename(path) {
        return Some(date);
    }

    // Priority 2: Git last-modified time
    if let Some(repo) = git_repo {
        if let Some(date) = repo.last_modified(path) {
            return Some(date);
        }
    }

    // Priority 3: File modification time
    extract_date_from_mtime(path)
}

fn extract_date_from_filename(path: &Path) -> Option<DateTime<Utc>> {
    let filename = path.file_name()?.to_str()?;

    if let Some(captures) = DATE_PREFIX_REGEX.captures(filename) {
        let date_str = captures.get(1)?.as_str();
        if let Ok(date) = NaiveDate::parse_from_str(date_str, "%Y-%m-%d") {
            return Some(Utc.from_utc_datetime(&date.and_hms_opt(0, 0, 0)?));
        }
    }

    None
}

fn extract_date_from_mtime(path: &Path) -> Option<DateTime<Utc>> {
    let metadata = std::fs::metadata(path).ok()?;
    let modified = metadata.modified().ok()?;
    Some(DateTime::from(modified))
}

pub fn strip_date_prefix(filename: &str) -> &str {
    if let Some(captures) = DATE_PREFIX_REGEX.captures(filename) {
        let full_match = captures.get(0).unwrap();
        &filename[full_match.end()..]
    } else {
        filename
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_extract_date_from_filename() {
        let path = Path::new("2024-01-15-my-post.md");
        let date = extract_date_from_filename(path).unwrap();
        assert_eq!(date.format("%Y-%m-%d").to_string(), "2024-01-15");
    }

    #[test]
    fn test_extract_date_no_prefix() {
        let path = Path::new("my-post.md");
        assert!(extract_date_from_filename(path).is_none());
    }

    #[test]
    fn test_strip_date_prefix() {
        assert_eq!(strip_date_prefix("2024-01-15-my-post.md"), "my-post.md");
        assert_eq!(strip_date_prefix("my-post.md"), "my-post.md");
    }
}