2023-12-17 12:43:47 -05:00
|
|
|
use std::ffi::OsStr;
|
2023-10-22 14:40:59 -04:00
|
|
|
use std::path::PathBuf;
|
2023-12-23 16:37:19 -05:00
|
|
|
use std::sync::Arc;
|
|
|
|
use std::sync::Mutex;
|
2023-10-22 14:40:59 -04:00
|
|
|
|
2023-12-17 12:43:47 -05:00
|
|
|
use super::stylesheet::Stylesheet;
|
2023-10-20 19:13:22 -04:00
|
|
|
use crate::cli::parameters::BuildArgs;
|
2023-10-22 16:10:41 -04:00
|
|
|
use crate::command::build::render::SiteRenderer;
|
2023-10-20 19:13:22 -04:00
|
|
|
use crate::config::Config;
|
2023-10-22 13:44:03 -04:00
|
|
|
use crate::error::CustomError;
|
2023-12-23 16:37:19 -05:00
|
|
|
use crate::intermediate::get_org_files;
|
2023-10-27 13:05:34 -04:00
|
|
|
use crate::intermediate::BlogPost;
|
2023-12-23 16:37:19 -05:00
|
|
|
use crate::intermediate::IPage;
|
|
|
|
use crate::intermediate::IntermediateContext;
|
|
|
|
use crate::intermediate::PageInput;
|
|
|
|
use crate::intermediate::Registry;
|
2024-10-19 17:26:37 -04:00
|
|
|
use crate::walk_fs::walk_fs;
|
|
|
|
use crate::walk_fs::WalkAction;
|
|
|
|
use crate::walk_fs::WalkFsFilterResult;
|
2023-12-17 12:43:47 -05:00
|
|
|
use include_dir::include_dir;
|
|
|
|
use include_dir::Dir;
|
2024-10-18 21:15:23 -04:00
|
|
|
use tokio::fs::DirEntry;
|
2023-12-17 12:43:47 -05:00
|
|
|
|
|
|
|
static DEFAULT_STYLESHEETS: Dir =
|
|
|
|
include_dir!("$CARGO_MANIFEST_DIR/default_environment/stylesheet");
|
2023-10-20 19:13:22 -04:00
|
|
|
|
2023-10-22 13:44:03 -04:00
|
|
|
pub(crate) async fn build_site(args: BuildArgs) -> Result<(), CustomError> {
|
2023-10-20 20:16:22 -04:00
|
|
|
let config = Config::load_from_file(args.config).await?;
|
2023-10-22 16:01:42 -04:00
|
|
|
let blog_posts = load_blog_posts(&config).await?;
|
2023-12-17 12:43:47 -05:00
|
|
|
let stylesheets = load_stylesheets().await?;
|
2023-12-23 16:37:19 -05:00
|
|
|
let pages = load_pages(&config).await?;
|
2023-12-17 13:46:47 -05:00
|
|
|
let renderer = SiteRenderer::new(
|
|
|
|
get_output_directory(&config).await?,
|
|
|
|
blog_posts,
|
|
|
|
stylesheets,
|
2023-12-23 16:37:19 -05:00
|
|
|
pages,
|
2023-12-17 13:46:47 -05:00
|
|
|
);
|
2023-10-23 21:51:15 -04:00
|
|
|
renderer.render_blog_posts(&config).await?;
|
2023-12-17 15:23:40 -05:00
|
|
|
renderer.render_blog_stream(&config).await?;
|
2023-12-23 16:37:19 -05:00
|
|
|
renderer.render_pages(&config).await?;
|
2023-12-17 13:46:47 -05:00
|
|
|
renderer.render_stylesheets().await?;
|
2023-12-23 15:45:23 -05:00
|
|
|
renderer.copy_static_files(&config).await?;
|
2023-10-22 16:01:42 -04:00
|
|
|
|
2023-10-22 13:44:03 -04:00
|
|
|
Ok(())
|
2023-10-20 20:16:22 -04:00
|
|
|
}
|
2023-10-22 14:40:59 -04:00
|
|
|
|
|
|
|
/// Delete everything inside the output directory and return the path to that directory.
|
|
|
|
async fn get_output_directory(config: &Config) -> Result<PathBuf, CustomError> {
|
|
|
|
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)
|
|
|
|
}
|
2023-10-22 14:49:08 -04:00
|
|
|
|
2024-10-19 17:14:05 -04:00
|
|
|
async fn filter_to_highest_folders_containing_org_files(entry: &DirEntry) -> WalkFsFilterResult {
|
2024-10-19 16:15:23 -04:00
|
|
|
let file_type = entry.file_type().await?;
|
2024-10-19 16:25:54 -04:00
|
|
|
if !file_type.is_dir() {
|
|
|
|
return Ok(WalkAction::Halt);
|
|
|
|
}
|
2024-10-19 16:15:23 -04:00
|
|
|
let mut entries = tokio::fs::read_dir(entry.path()).await?;
|
2024-10-19 16:25:54 -04:00
|
|
|
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) {
|
2024-10-19 16:38:29 -04:00
|
|
|
Some(ext) if ext.eq_ignore_ascii_case("org") => {
|
2024-10-19 16:25:54 -04:00
|
|
|
return Ok(WalkAction::HaltAndCapture);
|
|
|
|
}
|
|
|
|
_ => {}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
Ok(WalkAction::Recurse)
|
2024-10-18 21:15:23 -04:00
|
|
|
}
|
|
|
|
|
2023-10-22 14:49:08 -04:00
|
|
|
async fn get_post_directories(config: &Config) -> Result<Vec<PathBuf>, CustomError> {
|
2024-10-19 16:35:45 -04:00
|
|
|
if !config.get_posts_directory().exists() {
|
|
|
|
return Ok(Vec::new());
|
|
|
|
}
|
|
|
|
|
2024-10-19 16:25:54 -04:00
|
|
|
let top_level_org_folders = walk_fs(
|
2024-10-19 16:15:23 -04:00
|
|
|
config.get_posts_directory(),
|
|
|
|
filter_to_highest_folders_containing_org_files,
|
|
|
|
)
|
|
|
|
.await?;
|
2024-10-19 16:35:45 -04:00
|
|
|
Ok(top_level_org_folders
|
|
|
|
.into_iter()
|
|
|
|
.map(|entry| entry.path())
|
|
|
|
.collect())
|
2023-10-22 14:49:08 -04:00
|
|
|
}
|
2023-10-22 16:01:42 -04:00
|
|
|
|
|
|
|
async fn load_blog_posts(config: &Config) -> Result<Vec<BlogPost>, CustomError> {
|
|
|
|
let root_directory = config.get_root_directory().to_owned();
|
2024-10-19 16:55:38 -04:00
|
|
|
let posts_directory = config.get_posts_directory();
|
2023-12-23 06:38:23 -05:00
|
|
|
let post_directories = get_post_directories(config).await?;
|
2024-10-19 16:55:38 -04:00
|
|
|
let load_jobs = post_directories.into_iter().map(|path| {
|
|
|
|
tokio::spawn(BlogPost::load_blog_post(
|
|
|
|
root_directory.clone(),
|
|
|
|
posts_directory.clone(),
|
|
|
|
path,
|
|
|
|
))
|
|
|
|
});
|
2023-10-22 16:01:42 -04:00
|
|
|
let mut blog_posts = Vec::new();
|
|
|
|
for job in load_jobs {
|
|
|
|
blog_posts.push(job.await??);
|
|
|
|
}
|
|
|
|
Ok(blog_posts)
|
|
|
|
}
|
2023-12-17 12:43:47 -05:00
|
|
|
|
2023-12-23 16:37:19 -05:00
|
|
|
async fn load_pages(config: &Config) -> Result<Vec<IPage>, CustomError> {
|
|
|
|
let pages_source = config
|
|
|
|
.get_root_directory()
|
|
|
|
.join(config.get_relative_path_to_pages());
|
|
|
|
if !pages_source.exists() {
|
|
|
|
return Ok(Vec::new());
|
|
|
|
}
|
2024-10-19 17:26:37 -04:00
|
|
|
let page_files = get_org_files(&pages_source).await?;
|
2023-12-23 16:37:19 -05:00
|
|
|
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)
|
|
|
|
}
|
|
|
|
|
2023-12-17 12:43:47 -05:00
|
|
|
async fn load_stylesheets() -> Result<Vec<Stylesheet>, 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)
|
|
|
|
}
|