use std::ffi::OsStr; use std::path::PathBuf; use std::sync::Arc; use std::sync::Mutex; use super::stylesheet::Stylesheet; use super::walk_fs::walk_fs; use super::walk_fs::WalkAction; use crate::cli::parameters::BuildArgs; use crate::command::build::render::SiteRenderer; use crate::config::Config; use crate::error::CustomError; use crate::intermediate::get_org_files; use crate::intermediate::BlogPost; use crate::intermediate::IPage; use crate::intermediate::IntermediateContext; use crate::intermediate::PageInput; use crate::intermediate::Registry; use include_dir::include_dir; use include_dir::Dir; use tokio::fs::DirEntry; static DEFAULT_STYLESHEETS: Dir = include_dir!("$CARGO_MANIFEST_DIR/default_environment/stylesheet"); pub(crate) async fn build_site(args: BuildArgs) -> Result<(), CustomError> { let config = Config::load_from_file(args.config).await?; let blog_posts = load_blog_posts(&config).await?; let stylesheets = load_stylesheets().await?; let pages = load_pages(&config).await?; let renderer = SiteRenderer::new( get_output_directory(&config).await?, blog_posts, stylesheets, pages, ); renderer.render_blog_posts(&config).await?; renderer.render_blog_stream(&config).await?; renderer.render_pages(&config).await?; renderer.render_stylesheets().await?; renderer.copy_static_files(&config).await?; Ok(()) } /// Delete everything inside the output directory and return the path to that directory. async fn get_output_directory(config: &Config) -> Result { let output_directory = config.get_output_directory(); if !output_directory.exists() { tokio::fs::create_dir(&output_directory).await?; } else { let mut existing_entries = tokio::fs::read_dir(&output_directory).await?; while let Some(entry) = existing_entries.next_entry().await? { let file_type = entry.file_type().await?; if file_type.is_dir() { tokio::fs::remove_dir_all(entry.path()).await?; } else { tokio::fs::remove_file(entry.path()).await?; } } } Ok(output_directory) } async fn filter_to_highest_folders_containing_org_files( entry: &DirEntry, ) -> Result { let file_type = entry.file_type().await?; if !file_type.is_dir() { return Ok(WalkAction::Halt); } let mut entries = tokio::fs::read_dir(entry.path()).await?; while let Some(entry) = entries.next_entry().await? { let entry_type = entry.file_type().await?; if !entry_type.is_file() { continue; } match entry.path().extension().and_then(OsStr::to_str) { Some("org") => { return Ok(WalkAction::HaltAndCapture); } _ => {} } } Ok(WalkAction::Recurse) } async fn get_post_directories(config: &Config) -> Result, CustomError> { let top_level_org_folders = walk_fs( config.get_posts_directory(), filter_to_highest_folders_containing_org_files, ) .await?; let mut ret = Vec::new(); if !config.get_posts_directory().exists() { return Ok(ret); } let mut entries = tokio::fs::read_dir(config.get_posts_directory()).await?; while let Some(entry) = entries.next_entry().await? { let file_type = entry.file_type().await?; if file_type.is_dir() { ret.push(entry.path()); } } Ok(ret) } async fn load_blog_posts(config: &Config) -> Result, CustomError> { let root_directory = config.get_root_directory().to_owned(); let post_directories = get_post_directories(config).await?; let load_jobs = post_directories .into_iter() .map(|path| tokio::spawn(BlogPost::load_blog_post(root_directory.clone(), path))); let mut blog_posts = Vec::new(); for job in load_jobs { blog_posts.push(job.await??); } Ok(blog_posts) } async fn load_pages(config: &Config) -> Result, CustomError> { let pages_source = config .get_root_directory() .join(config.get_relative_path_to_pages()); if !pages_source.exists() { return Ok(Vec::new()); } let page_files = get_org_files(&pages_source)?; let org_files = { let mut ret = Vec::new(); for page in page_files { ret.push(page.await??); } ret }; let parsed_org_files = { let mut ret = Vec::new(); for (path, contents) in org_files.iter() { let parsed = organic::parser::parse_file(contents.as_str(), Some(path)) .map_err(|_| CustomError::Static("Failed to parse org-mode document."))?; ret.push((path, contents, parsed)); } ret }; let pages = { let mut ret = Vec::new(); for (real_path, _contents, parsed_document) in parsed_org_files.iter() { let mut registry = Registry::new(); // Assign IDs to the targets organic::types::AstNode::from(parsed_document) .iter_all_ast_nodes() .for_each(|node| { if let organic::types::AstNode::Target(target) = node { registry.get_target(target.value); } }); let registry = Arc::new(Mutex::new(registry)); let intermediate_context = IntermediateContext::new(registry)?; let relative_to_pages_dir_path = real_path.strip_prefix(&pages_source)?; ret.push( IPage::new( intermediate_context, PageInput::new(relative_to_pages_dir_path, parsed_document), ) .await?, ); } ret }; Ok(pages) } async fn load_stylesheets() -> Result, CustomError> { let sources: Vec<_> = DEFAULT_STYLESHEETS .files() .filter(|f| f.path().extension() == Some(OsStr::new("css"))) .collect(); let mut ret = Vec::with_capacity(sources.len()); for entry in sources { let path = entry.path().to_path_buf(); let contents = String::from_utf8(entry.contents().to_vec())?; let stylesheet = Stylesheet::new(path, contents).await?; ret.push(stylesheet); } Ok(ret) }