use std::{fs, io, path::Path};
use log::debug;
use serde::Deserialize;
use crate::{kebab, manifest::DocumentManifest, walk::Walker, Author, Date};
const DEFAULT_LOCALE: &str = "en_US";
#[derive(Clone, Debug, Default, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "kebab-case")]
pub enum DocumentType {
#[default]
Article,
Report,
Book,
}
impl AsRef<str> for DocumentType {
fn as_ref(&self) -> &str {
match self {
DocumentType::Article => "article",
DocumentType::Report => "report",
DocumentType::Book => "book",
}
}
}
pub struct Document {
pub title: String,
pub authors: Vec<Author>,
pub date: Date,
pub locale: String,
pub document_type: DocumentType,
pub(crate) texts: Vec<String>,
}
impl Default for Document {
fn default() -> Self {
Self {
title: "Document".into(),
locale: DEFAULT_LOCALE.into(),
texts: Default::default(),
authors: Default::default(),
date: Default::default(),
document_type: Default::default(),
}
}
}
impl Document {
pub fn title(&mut self, title: impl Into<String>) -> &mut Self {
self.title = title.into();
self
}
pub fn text(&mut self, text: impl Into<String>) -> &mut Self {
self.texts.push(text.into());
self
}
pub fn texts<S: Into<String>>(&mut self, texts: impl IntoIterator<Item = S>) -> &mut Self {
texts.into_iter().for_each(|text| {
self.text(text);
});
self
}
pub fn author(&mut self, author: Author) -> &mut Self {
self.authors.push(author);
self
}
pub fn authors(&mut self, authors: impl IntoIterator<Item = Author>) -> &mut Self {
self.authors.extend(authors);
self
}
pub fn document_type(&mut self, document_type: DocumentType) -> &mut Self {
self.document_type = document_type;
self
}
pub fn date(&mut self, date: Date) -> &mut Self {
self.date = date;
self
}
pub fn locale(&mut self, locale: impl Into<String>) -> &mut Self {
self.locale = locale.into();
self
}
pub fn from_path(path: impl AsRef<Path>) -> io::Result<Self> {
let path = fs::canonicalize(path)?;
let mut texts = Vec::new();
extend_texts(&path, &mut texts)?;
Ok(Self {
texts,
..Default::default()
})
}
pub fn filename(&self) -> String {
kebab(&self.title)
}
}
impl<S: Into<String>> From<S> for Document {
fn from(content: S) -> Self {
Self {
texts: vec![content.into()],
..Default::default()
}
}
}
impl TryFrom<DocumentManifest> for Document {
type Error = io::Error;
fn try_from(def: DocumentManifest) -> Result<Self, Self::Error> {
let mut texts = Vec::new();
for path in def.texts {
if path.is_dir() {
extend_texts(&path, &mut texts)?;
} else {
texts.push(fs::read_to_string(path)?);
}
}
Ok(Self {
texts,
date: def.date.map(|d| d.into()).unwrap_or_default(),
title: def.title.to_owned(),
authors: def.authors.clone().into_iter().map(Into::into).collect(),
locale: def.locale.clone().unwrap_or(DEFAULT_LOCALE.into()),
document_type: def.document_type,
})
}
}
fn extend_texts(path: impl AsRef<Path>, texts: &mut Vec<String>) -> io::Result<()> {
for path in Walker::new(path)?.filter_extensions(&["dj"]) {
debug!("Loading chapter from {path:?}...");
texts.push(fs::read_to_string(path)?);
}
Ok(())
}