natter/src/intermediate/convert.rs
2023-10-29 18:46:13 -04:00

171 lines
4.9 KiB
Rust

use std::path::Component;
use std::path::Path;
use std::path::PathBuf;
use crate::config::Config;
use crate::context::GlobalSettings;
use crate::context::RenderBlogPostPage;
use crate::context::RenderDocumentElement;
use crate::context::RenderRealFootnoteDefinition;
use crate::error::CustomError;
use super::BlogPost;
use super::BlogPostPage;
pub(crate) fn convert_blog_post_page_to_render_context<D: AsRef<Path>, F: AsRef<Path>>(
config: &Config,
output_directory: D,
output_file: F,
_post: &BlogPost,
page: &BlogPostPage,
) -> Result<RenderBlogPostPage, CustomError> {
let output_directory = output_directory.as_ref();
let output_file = output_file.as_ref();
let css_files = vec![get_web_path(
config,
output_directory,
output_file,
"main.css",
)?];
let js_files = vec![get_web_path(
config,
output_directory,
output_file,
"blog_post.js",
)?];
let global_settings = GlobalSettings::new(page.title.clone(), css_files, js_files);
let link_to_blog_post = get_web_path(
config,
output_directory,
output_file,
output_file.strip_prefix(output_directory)?,
)?;
let children = {
let mut children = Vec::new();
for child in page.children.iter() {
children.push(RenderDocumentElement::new(
config,
output_directory,
output_file,
child,
)?);
}
children
};
let footnotes = {
let mut ret = Vec::new();
for footnote in page.footnotes.iter() {
ret.push(RenderRealFootnoteDefinition::new(
config,
output_directory,
output_file,
footnote,
)?);
}
ret
};
let ret = RenderBlogPostPage::new(
global_settings,
page.title.clone(),
Some(link_to_blog_post),
children,
footnotes,
);
Ok(ret)
}
fn get_web_path<D: AsRef<Path>, F: AsRef<Path>, P: AsRef<Path>>(
config: &Config,
output_directory: D,
containing_file: F,
path_from_web_root: P,
) -> Result<String, CustomError> {
let path_from_web_root = path_from_web_root.as_ref();
if config.use_relative_paths() {
let output_directory = output_directory.as_ref();
let containing_file = containing_file.as_ref();
let containing_file_relative_to_output_directory =
containing_file.strip_prefix(output_directory)?;
let shared_stem = get_shared_steps(
containing_file_relative_to_output_directory
.parent()
.ok_or("File should exist in a folder.")?,
path_from_web_root
.parent()
.ok_or("File should exist in a folder.")?,
)
.collect::<PathBuf>();
// Subtracting 1 from the depth to "remove" the file name.
let depth_from_shared_stem = containing_file_relative_to_output_directory
.strip_prefix(&shared_stem)?
.components()
.count()
- 1;
let final_path = PathBuf::from("../".repeat(depth_from_shared_stem))
.join(path_from_web_root.strip_prefix(shared_stem)?);
let final_string = final_path
.as_path()
.to_str()
.map(str::to_string)
.ok_or("Path should be valid utf-8.")?;
Ok(final_string)
} else {
let web_root = config
.get_web_root()
.ok_or("Must either use_relative_paths or set the web_root in the config.")?;
let final_path = PathBuf::from(web_root).join(path_from_web_root);
let final_string = final_path
.as_path()
.to_str()
.map(str::to_string)
.ok_or("Path should be valid utf-8.")?;
Ok(final_string)
}
}
fn get_shared_steps<'a>(left: &'a Path, right: &'a Path) -> impl Iterator<Item = Component<'a>> {
let shared_stem = left
.components()
.zip(right.components())
.take_while(|(l, r)| l == r)
.map(|(l, _r)| l);
shared_stem
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_get_shared_steps() {
assert_eq!(
get_shared_steps(Path::new(""), Path::new("")).collect::<PathBuf>(),
PathBuf::from("")
);
assert_eq!(
get_shared_steps(Path::new("foo.txt"), Path::new("foo.txt")).collect::<PathBuf>(),
PathBuf::from("foo.txt")
);
assert_eq!(
get_shared_steps(Path::new("cat/foo.txt"), Path::new("dog/foo.txt"))
.collect::<PathBuf>(),
PathBuf::from("")
);
assert_eq!(
get_shared_steps(
Path::new("foo/bar/baz/lorem.txt"),
Path::new("foo/bar/ipsum/dolar.txt")
)
.collect::<PathBuf>(),
PathBuf::from("foo/bar")
);
}
}