use super::renderer::BuildRenderer;
use crate::error::ScrapsResult;
use crate::usecase::progress::{Progress, Stage};
use rayon::iter::IntoParallelIterator;
use rayon::prelude::*;
use scraps_libs::{
html::{self, EmbedMode},
model::{scrap::Scrap, tags::Tags},
};
use tracing::{span, Level};
use super::model::{
backlinks_map::BacklinksMap,
build_config::BuildConfig,
scrap_detail::{ScrapDetail, ScrapDetails},
site_nav::SiteNav,
};
use std::collections::HashMap;
pub struct BuildUsecase;
impl BuildUsecase {
pub fn new() -> BuildUsecase {
BuildUsecase
}
pub fn execute<PG: Progress, BR: BuildRenderer>(
&self,
scraps_with_ts: &[(Scrap, Option<i64>)],
readme_text: &Option<String>,
progress: &PG,
renderer: &BR,
config: &BuildConfig,
) -> ScrapsResult<usize> {
let BuildConfig {
base_url,
timezone,
html_metadata,
css_metadata,
list_view_configs,
} = config;
let timezone = *timezone;
progress.start_stage(&Stage::ReadScraps);
let span_read_scraps = span!(Level::INFO, "read_scraps").entered();
let scrap_texts: HashMap<_, _> = scraps_with_ts
.iter()
.map(|(scrap, _)| (scrap.self_key(), scrap.md_text().to_string()))
.collect();
let readme_content = readme_text
.as_ref()
.map(|text| html::to_content(text, base_url, EmbedMode::Expand(&scrap_texts)));
let scrap_details = scraps_with_ts
.into_par_iter()
.map(|(scrap, commited_ts)| {
ScrapDetail::new(scrap, commited_ts, base_url, &scrap_texts)
})
.collect::<Vec<ScrapDetail>>();
let scrap_details = ScrapDetails::new(&scrap_details);
let scraps = scrap_details.to_scraps();
let backlinks_map = BacklinksMap::new(&scraps);
let scraps_by_key: HashMap<_, _> = scraps
.iter()
.map(|scrap| (scrap.self_key(), scrap.clone()))
.collect();
let site_nav = SiteNav::new(
scraps.len(),
Tags::new(&scraps),
list_view_configs.build_search_index,
timezone,
readme_content.is_some(),
);
span_read_scraps.exit();
progress.complete_stage(&Stage::ReadScraps, &scrap_details.len());
progress.start_stage(&Stage::GenerateHtml);
let span_generate_html_indexes = span!(Level::INFO, "generate_html_indexes").entered();
let index_page_count = renderer.render_index(
base_url,
html_metadata,
list_view_configs,
&scrap_details,
&backlinks_map,
&site_nav,
)?;
span_generate_html_indexes.exit();
if let Some(readme) = &readme_content {
let span_generate_html_about = span!(Level::INFO, "generate_html_about").entered();
renderer.render_about(base_url, html_metadata, readme, &backlinks_map, &site_nav)?;
span_generate_html_about.exit();
}
let span_generate_html_scraps = span!(Level::INFO, "generate_html_scraps").entered();
scrap_details
.to_vec()
.into_par_iter()
.try_for_each(|scrap_detail| {
let _span_generate_html_scrap = span!(Level::INFO, "generate_html_scrap").entered();
renderer.render_scrap(
base_url,
html_metadata,
&scrap_detail,
&backlinks_map,
&scraps_by_key,
&site_nav,
)
})?;
span_generate_html_scraps.exit();
let span_generate_html_scraps_index =
span!(Level::INFO, "generate_html_scraps_index").entered();
renderer.render_scraps_index(
base_url,
html_metadata,
&scrap_details,
&backlinks_map,
&site_nav,
)?;
span_generate_html_scraps_index.exit();
let span_generate_html_tags_index =
span!(Level::INFO, "generate_html_tags_index").entered();
renderer.render_tags_index(base_url, html_metadata, &scraps, &backlinks_map, &site_nav)?;
span_generate_html_tags_index.exit();
let span_generate_html_tags = span!(Level::INFO, "generate_html_tags").entered();
site_nav.tags.iter().par_bridge().try_for_each(|tag| {
let _span_render_tag = span!(Level::INFO, "generate_html_tag").entered();
renderer.render_tag(base_url, html_metadata, tag, &backlinks_map, &site_nav)
})?;
span_generate_html_tags.exit();
progress.complete_stage(&Stage::GenerateHtml, &{
index_page_count + scrap_details.len() + 1 + site_nav.tags.len()
});
progress.start_stage(&Stage::GenerateCss);
let span_generate_css = span!(Level::INFO, "generate_css").entered();
renderer.render_css(css_metadata)?;
span_generate_css.exit();
progress.complete_stage(&Stage::GenerateCss, &1);
if list_view_configs.build_search_index {
progress.start_stage(&Stage::GenerateJson);
let _span_generate_json_search_index =
span!(Level::INFO, "generate_json_search_index").entered();
renderer.render_search_index(base_url, &scraps)?;
progress.complete_stage(&Stage::GenerateJson, &1);
}
Ok(scraps.len())
}
}
#[cfg(test)]
mod tests {
use crate::usecase::build::model::{
color_scheme::ColorScheme, css::CssMetadata, html::HtmlMetadata,
list_view_configs::ListViewConfigs, paging::Paging,
};
use crate::usecase::build::renderer::tests::BuildRendererTest;
use crate::usecase::progress::tests::ProgressTest;
use super::*;
use scraps_libs::lang::LangCode;
use scraps_libs::model::base_url::BaseUrl;
use url::Url;
fn build_config(build_search_index: bool) -> BuildConfig {
BuildConfig {
base_url: BaseUrl::new(Url::parse("http://localhost:1112/").unwrap()).unwrap(),
timezone: chrono_tz::UTC,
html_metadata: HtmlMetadata::new(
&LangCode::default(),
"Scrap",
&Some("Scrap Wiki".to_string()),
&Some(Url::parse("https://github.io/image.png").unwrap()),
),
css_metadata: CssMetadata::new(&ColorScheme::OsSetting),
list_view_configs: ListViewConfigs::new(&build_search_index, &Paging::Not),
}
}
#[test]
fn it_run() {
let scraps_with_ts = vec![
(
Scrap::new("test1", &None, concat!("# header1\n", "## header2\n")),
Some(0i64),
),
(Scrap::new("test2", &None, "[[test1]]\n"), Some(0i64)),
];
let readme_text = Some("# README\n".to_string());
let progress = ProgressTest::new();
let renderer = BuildRendererTest::new();
let usecase = BuildUsecase::new();
let result = usecase
.execute(
&scraps_with_ts,
&readme_text,
&progress,
&renderer,
&build_config(true),
)
.unwrap();
assert_eq!(result, 2);
}
#[test]
fn it_run_when_build_search_index_is_false() {
let scraps_with_ts = vec![
(
Scrap::new("test1", &None, concat!("# header1\n", "## header2\n")),
Some(0i64),
),
(Scrap::new("test2", &None, "[[test1]]\n"), Some(0i64)),
];
let progress = ProgressTest::new();
let renderer = BuildRendererTest::new();
let usecase = BuildUsecase::new();
let result = usecase
.execute(
&scraps_with_ts,
&None,
&progress,
&renderer,
&build_config(false),
)
.unwrap();
assert_eq!(result, 2);
}
}