use std::collections::HashMap;
use std::path::{Path, PathBuf};
use crate::error::ScrapsResult;
use crate::service::tera_render::{render_to_file, user_template_glob};
use crate::usecase::build::model::backlinks_map::BacklinksMap;
use crate::usecase::build::model::html::HtmlMetadata;
use crate::usecase::build::model::scrap_detail::{ScrapDetail, ScrapDetails};
use crate::usecase::build::model::scrap_graph::{MAX_NODES, ScrapGraph};
use crate::usecase::build::model::site_nav::SiteNav;
use rayon::prelude::*;
use scraps_libs::model::base_url::BaseUrl;
use scraps_libs::model::file::ScrapFileStem;
use scraps_libs::model::key::ScrapKey;
use scraps_libs::model::scrap::Scrap;
use tera::Tera;
use tracing::{Level, span};
use crate::usecase::build::html::templates;
use super::serde::link_scraps::LinkScrapsTera;
use super::serde::scrap_detail::ScrapDetailTera;
use super::serde::scrap_graph::ScrapGraphTera;
use super::serde::tag::TagTera;
pub struct ScrapRender {
tera: Tera,
output_scraps_dir_path: PathBuf,
}
impl ScrapRender {
pub fn new(static_dir_path: &Path, output_dir_path: &Path) -> ScrapsResult<ScrapRender> {
let tera = templates::scrap(&user_template_glob(static_dir_path, "*.html"))?;
Ok(ScrapRender {
tera,
output_scraps_dir_path: output_dir_path.join("scraps"),
})
}
pub fn run(
&self,
base_url: &BaseUrl,
metadata: &HtmlMetadata,
scrap_details: &ScrapDetails,
backlinks_map: &BacklinksMap,
scraps_by_key: &HashMap<ScrapKey, &Scrap>,
site_nav: &SiteNav,
) -> ScrapsResult<()> {
let mut shell = templates::context(base_url, metadata);
templates::insert_site_nav(&mut shell, "", site_nav, backlinks_map);
scrap_details
.as_slice()
.par_iter()
.try_for_each(|scrap_detail| {
let _span = span!(Level::INFO, "generate_html_scrap").entered();
self.render_page(&shell, scrap_detail, backlinks_map, scraps_by_key)
})
}
fn render_page(
&self,
shell: &tera::Context,
scrap_detail: &ScrapDetail,
backlinks_map: &BacklinksMap,
scraps_by_key: &HashMap<ScrapKey, &Scrap>,
) -> ScrapsResult<()> {
let mut context = shell.clone();
let scrap = scrap_detail.scrap();
context.insert("scrap", &ScrapDetailTera::from(scrap_detail));
let scrap_tags = scrap
.tags()
.iter()
.map(|tag| TagTera::new(tag, backlinks_map))
.collect::<Vec<_>>();
context.insert("scrap_tags", &scrap_tags);
let linked_scraps = backlinks_map.get(&scrap.self_key());
context.insert("linked_scraps", &LinkScrapsTera::new(linked_scraps));
let outbound_scraps = scrap
.links()
.iter()
.filter_map(|key| scraps_by_key.get(key).copied())
.collect::<Vec<_>>();
context.insert("outbound_scraps", &LinkScrapsTera::new(&outbound_scraps));
if let Some(graph) = ScrapGraph::new(
&scrap.title().to_string(),
linked_scraps,
&outbound_scraps,
MAX_NODES,
) {
context.insert("scrap_graph", &ScrapGraphTera::from(&graph));
}
let file_path = self
.output_scraps_dir_path
.join(format!("{}.html", ScrapFileStem::from(scrap.self_key())));
render_to_file(&self.tera, "__builtins/scrap.html", &context, &file_path)
}
}
#[cfg(test)]
mod tests {
use std::fs;
use url::Url;
use crate::usecase::build::model::backlinks_map::BacklinksMap;
use crate::usecase::build::model::html::HtmlMetadata;
use scraps_libs::lang::LangCode;
use scraps_libs::model::base_url::BaseUrl;
use scraps_libs::model::scrap::Scrap;
use scraps_libs::model::tags::Tags;
use super::*;
#[test]
fn it_run() {
let base_url = &BaseUrl::new(Url::parse("http://localhost:1112/").unwrap()).unwrap();
let metadata = HtmlMetadata::new(
&LangCode::default(),
"Scrap",
&Some("Scrap Wiki".to_string()),
&Some(Url::parse("https://github.io/image.png").unwrap()),
);
let test_resource_path =
PathBuf::from("tests/resource/build/html/render/it_render_scrap_htmls");
let static_dir_path = test_resource_path.join("static");
let output_dir_path = test_resource_path.join("_site");
let commited_ts1 = None;
let scrap1 = &Scrap::new("scrap 1", &None, "# header1");
let scrap2 = &Scrap::new(
"scrap 2",
&Some("Context".into()),
"[[scrap 1]] #[[design]]",
);
let scraps = vec![scrap1.to_owned(), scrap2.to_owned()];
let scrap_texts = scraps
.iter()
.map(|scrap| (scrap.self_key(), scrap.md_text().to_string()))
.collect();
let backlinks_map = BacklinksMap::new(&scraps);
let site_nav = SiteNav::new(
scraps.len(),
Tags::new(&scraps),
true,
chrono_tz::UTC,
false,
);
let scraps_by_key: HashMap<_, _> = scraps
.iter()
.map(|scrap| (scrap.self_key(), scrap))
.collect();
let scrap1_html_path = output_dir_path.join("scraps/scrap-1.html");
let scrap2_html_path = output_dir_path.join("scraps/context/scrap-2.html");
let scrap_details = ScrapDetails::new(vec![
ScrapDetail::new(scrap1, &commited_ts1, base_url, &scrap_texts),
ScrapDetail::new(scrap2, &commited_ts1, base_url, &scrap_texts),
]);
let render = ScrapRender::new(&static_dir_path, &output_dir_path).unwrap();
render
.run(
base_url,
&metadata,
&scrap_details,
&backlinks_map,
&scraps_by_key,
&site_nav,
)
.unwrap();
let result2 = fs::read_to_string(scrap1_html_path).unwrap();
assert!(result2.contains(">backlinks · 1"));
assert!(!result2.contains(">links ·"));
assert!(!result2.contains("corsproxy"));
let result4 = fs::read_to_string(scrap2_html_path).unwrap();
assert!(result4.contains(">links · 1"));
assert!(result4.contains("tags/design.html"));
assert!(result4.contains("#[["));
}
#[test]
fn it_draws_the_neighbourhood_only_when_there_is_one() {
let base_url = &BaseUrl::new(Url::parse("http://localhost:1112/").unwrap()).unwrap();
let metadata = HtmlMetadata::new(&LangCode::default(), "Scrap", &None, &None);
let linked = &Scrap::new("linked", &None, "");
let linking = &Scrap::new("linking", &None, "[[linked]]");
let alone = &Scrap::new("alone", &None, "no wiki links here");
let scraps = vec![linked.to_owned(), linking.to_owned(), alone.to_owned()];
let scrap_texts = scraps
.iter()
.map(|scrap| (scrap.self_key(), scrap.md_text().to_string()))
.collect();
let backlinks_map = BacklinksMap::new(&scraps);
let scraps_by_key: HashMap<_, _> = scraps
.iter()
.map(|scrap| (scrap.self_key(), scrap))
.collect();
let site_nav = SiteNav::new(
scraps.len(),
Tags::new(&scraps),
false,
chrono_tz::UTC,
false,
);
let scrap_details = ScrapDetails::new(
[linked, linking, alone]
.into_iter()
.map(|scrap| ScrapDetail::new(scrap, &None, base_url, &scrap_texts))
.collect(),
);
let project = crate::test_fixtures::TempScrapProject::new();
let render = ScrapRender::new(&project.static_dir, &project.output_dir).unwrap();
render
.run(
base_url,
&metadata,
&scrap_details,
&backlinks_map,
&scraps_by_key,
&site_nav,
)
.unwrap();
let read = |stem: &str| {
fs::read_to_string(project.output_dir.join(format!("scraps/{stem}.html"))).unwrap()
};
let backlinked = read("linked");
assert!(backlinked.contains("<svg class=\"scrap-graph\""));
assert!(backlinked.contains("marker-start=\"url(#scrap-graph-arrow)\""));
assert!(!backlinked.contains("marker-end=\"url(#scrap-graph-arrow)\""));
let outbound = read("linking");
assert!(outbound.contains("marker-end=\"url(#scrap-graph-arrow)\""));
assert!(!outbound.contains("marker-start=\"url(#scrap-graph-arrow)\""));
assert!(!read("alone").contains("scrap-graph"));
}
}