use crate::directive::{Processed, Toc}; use crate::html::parse; use crate::name::Name; use crate::parser::WikitextParser; use crate::site::Site; use crate::util::get_mtime; use crate::wikitext::Snippet; use html_page::{Document, Element, Tag}; use log::{info, trace}; use std::path::{Path, PathBuf}; use std::time::SystemTime; #[derive(Debug, thiserror::Error)] pub enum PageError { #[error("could not read file: {0}")] FileRead(PathBuf, #[source] std::io::Error), #[error("could not convert input text from {0} to UTF-8")] Utf8(PathBuf, #[source] std::string::FromUtf8Error), #[error(transparent)] Util(#[from] crate::util::UtilError), #[error(transparent)] Wikitext(#[from] crate::wikitext::WikitextError), #[error(transparent)] Html(#[from] crate::html::HtmlError), #[error(transparent)] Parser(#[from] crate::parser::ParserError), } #[derive(Debug, Clone)] pub struct Page { meta: PageMeta, unprocessed: UnprocessedPage, } impl Page { pub fn new(meta: PageMeta, unprocessed: UnprocessedPage) -> Self { Self { meta, unprocessed } } pub fn meta(&self) -> &PageMeta { &self.meta } pub fn markdown(&self, site: &mut Site) -> Result { self.unprocessed.process(site) } } #[derive(Debug, Eq, PartialEq)] pub struct WikitextPage { meta: PageMeta, wikitext: String, } impl WikitextPage { pub fn new(meta: PageMeta, wikitext: String) -> Self { Self { meta, wikitext } } pub fn read(name: &Name) -> Result { info!("input file: {}", name); let src = name.source_path(); let data = std::fs::read(src).map_err(|e| PageError::FileRead(src.into(), e))?; let wikitext = String::from_utf8(data).map_err(|e| PageError::Utf8(src.into(), e))?; let mtime = get_mtime(src)?; let meta = MetaBuilder::default() .name(name.clone()) .mtime(mtime) .build(); Ok(Self::new(meta, wikitext)) } pub fn meta(&self) -> &PageMeta { &self.meta } pub fn meta_mut(&mut self) -> &mut PageMeta { &mut self.meta } pub fn wikitext(&self) -> &str { &self.wikitext } } #[derive(Debug, Clone)] pub struct UnprocessedPage { meta: PageMeta, snippets: Vec, } impl UnprocessedPage { pub fn new(meta: PageMeta, parser: &mut WikitextParser) -> Result { Ok(Self { meta, snippets: Self::snippets(parser)?, }) } pub fn meta(&self) -> &PageMeta { &self.meta } fn snippets(parser: &mut WikitextParser) -> Result, PageError> { let mut snippets = vec![]; while let Some(snippet) = parser.parse()? { snippets.push(snippet); } Ok(snippets) } pub fn prepare(&self, site: &mut Site) -> Result<(), PageError> { trace!("UnprocessedPage: preparing snippets"); for snippet in self.snippets.iter() { snippet.prepare(site)?; } Ok(()) } pub fn process(&self, site: &mut Site) -> Result { let mut meta = self.meta.clone(); let mut processed = vec![]; trace!("UnprocessedPage: processing snippets"); for snippet in self.snippets.iter() { processed.push(snippet.process(site, &mut meta)?); } let page_text = processed .iter() .filter_map(|p| match p { Processed::Markdown(s) => Some(s.as_str()), _ => None, }) .collect::>() .join(""); let body = parse(&page_text)?; let mut m = String::new(); for p in processed { match p { Processed::Markdown(s) => m.push_str(&s), Processed::Toc(levels) => m.push_str(&Toc::post_process(&body, levels)), } } Ok(MarkdownPage::new(m, meta)) } } #[derive(Debug, Clone, Eq, PartialEq)] pub struct MarkdownPage { meta: PageMeta, markdown: String, } impl MarkdownPage { fn new(markdown: String, meta: PageMeta) -> Self { Self { markdown, meta } } pub fn markdown(&self) -> &str { &self.markdown } pub fn meta(&self) -> &PageMeta { &self.meta } pub fn body_to_html(&self) -> Result { let mut html = Document::default(); html.push_children(&parse(self.markdown())?); Ok(html) } pub fn to_html(&self) -> Result { let mut html = Document::default(); let title = Element::new(Tag::Title).with_text(self.meta.title()); html.push_to_head(&title); html.push_children(&parse(self.markdown())?); Ok(html) } } #[derive(Debug, Clone, Eq, PartialEq)] pub struct PageMeta { name: Name, title: Option, mtime: SystemTime, links_to: Vec, } impl PageMeta { fn new(name: Name, title: Option, mtime: SystemTime) -> Self { trace!( "PageMeta: name={:?} title={:?} mtime={:?}", name, title, mtime, ); Self { name, title, mtime, links_to: vec![], } } pub fn destination_filename(&self) -> PathBuf { self.name.destination_path().into() } pub fn set_title(&mut self, title: String) { trace!("PageMeta::set_title: title={:?}", title); self.title = Some(title); } pub fn title(&self) -> &str { if let Some(title) = &self.title { title } else { self.name.page_name() } } pub fn path(&self) -> &Path { self.name.page_path() } pub fn mtime(&self) -> SystemTime { self.mtime } pub fn set_mtime(&mut self, mtime: SystemTime) { self.mtime = mtime; } pub fn add_link(&mut self, path: &Path) { self.links_to.push(path.into()); } pub fn links_to(&self) -> &[PathBuf] { &self.links_to } } #[derive(Debug, Default)] pub struct MetaBuilder { name: Option, title: Option, mtime: Option, } impl MetaBuilder { pub fn build(self) -> PageMeta { PageMeta::new( self.name.expect("name set on MetaBuilder"), self.title, self.mtime.expect("mtime set on MetaBuilder"), ) } pub fn name(mut self, name: Name) -> Self { self.name = Some(name); self } pub fn title(mut self, title: String) -> Self { self.title = Some(title); self } pub fn mtime(mut self, mtime: SystemTime) -> Self { self.mtime = Some(mtime); self } }