diff options
author | Ben Bridle <ben@derelict.engineering> | 2025-01-15 09:23:00 +1300 |
---|---|---|
committer | Ben Bridle <ben@derelict.engineering> | 2025-01-15 09:23:05 +1300 |
commit | a9c45ef6de84df758194e3a9428faee8c69065ae (patch) | |
tree | e9080dac289cba8fba02a9643801b50375e902fa | |
parent | e96bbf19171452de9d269a98dc0f4171d1bf46b1 (diff) | |
download | toaster-a9c45ef6de84df758194e3a9428faee8c69065ae.zip |
Implement redirects
Source files with extension .redirect will be converted into redirect
pages linking to the internal-style URL in each file.
-rw-r--r-- | src/collect_files.rs | 57 | ||||
-rw-r--r-- | src/generate_html.rs | 12 | ||||
-rw-r--r-- | src/main.rs | 12 |
3 files changed, 75 insertions, 6 deletions
diff --git a/src/collect_files.rs b/src/collect_files.rs index 9c7ac24..b0e24ed 100644 --- a/src/collect_files.rs +++ b/src/collect_files.rs @@ -9,6 +9,7 @@ pub struct Website { pub name: String, pub config: HashMap<String, String>, pub pages: Vec<Page>, + pub redirects: Vec<Redirect>, pub static_files: Vec<StaticItem>, pub static_dirs: Vec<StaticItem>, } @@ -31,10 +32,30 @@ pub struct Heading { } pub struct StaticItem { - pub full_url: String, // Safe full URL, with extension - pub source_path: PathBuf, // Absolute path to source file + pub full_url: String, // Safe full URL, with extension + pub source_path: PathBuf, // Absolute path to source file +} + +pub struct Redirect { + pub name: String, // Display name of this redirect + pub full_url: String, // Safe full URL, no extension + pub parents: Vec<String>, // Parent directory components, unsafe + pub parent_url: String, // Base URL for relative redirects + pub redirect: String, // Page to redirect to, as an internal link } +pub trait LinkFrom { + fn name(&self) -> &str; + fn parent_url(&self) -> &str; + fn parents(&self) -> &[String]; + fn root(&self) -> String { + let mut root = String::new(); + for _ in self.parents() { + root.push_str("../"); + } + return root; + } +} impl Page { @@ -47,12 +68,24 @@ impl Page { } } +impl LinkFrom for Page { + fn name(&self) -> &str { &self.name } + fn parent_url(&self) -> &str { &self.parent_url } + fn parents(&self) -> &[String] { &self.parents } +} + +impl LinkFrom for Redirect { + fn name(&self) -> &str { &self.name } + fn parent_url(&self) -> &str { &self.parent_url } + fn parents(&self) -> &[String] { &self.parents } +} impl Website { pub fn from_path(path: &Path) -> Self { let mut new = Self { pages: Vec::new(), + redirects: Vec::new(), static_files: Vec::new(), static_dirs: Vec::new(), name: match Entry::from_path(path) { @@ -189,6 +222,18 @@ impl Website { }); } }, + "redirect" => { + let mut full_url = String::new(); + for parent in &parents { + full_url.push_str(&make_url_safe(parent)); + full_url.push('/'); + } + let parent_url = full_url.clone(); + full_url.push_str(&name_url); + let redirect = std::fs::read_to_string(&source_path) + .unwrap().trim().to_string(); + self.redirects.push(Redirect { name, full_url, parents, parent_url, redirect }); + } _ => { let mut parent_url = String::new(); for parent in &parents { @@ -205,7 +250,7 @@ impl Website { // Ext is extension without a dot. // Checks if a relative link to an internal page name can be reached from // the current page, and returns a resolved absolute link to the page with extension. - pub fn has_page(&self, from: &Page, path: &str, ext: &str) -> Option<String> { + pub fn has_page(&self, from: &impl LinkFrom, path: &str, ext: &str) -> Option<String> { // Remove heading fragment and file extension. let (path, heading) = match path.rsplit_once('#') { Some((path, heading)) => match heading.is_empty() { @@ -217,7 +262,7 @@ impl Website { let mut path = path.strip_suffix(&format!(".{ext}")).unwrap_or(path).to_string(); // Attach parent if not an absolute path. if !path.starts_with('/') { - path = format!("{}{path}", from.parent_url); + path = format!("{}{path}", from.parent_url()); } // Iteratively collapse ".." segments. @@ -239,12 +284,12 @@ impl Website { break; } // Find page with this path in website. - let path = segments.join("/"); + let path = make_url_safe(&segments.join("/")); for page in &self.pages { if page.full_url == path { if let Some(heading) = heading { if !page.headings.iter().any(|h| h.url == make_url_safe(heading)) { - warn!("Page {:?} contains link to nonexistent heading {heading:?} on page {path:?}", from.name); + warn!("Page {:?} contains link to nonexistent heading {heading:?} on page {path:?}", from.name()); } } let root = from.root(); diff --git a/src/generate_html.rs b/src/generate_html.rs index 42b2c21..e98b661 100644 --- a/src/generate_html.rs +++ b/src/generate_html.rs @@ -50,6 +50,18 @@ pub fn generate_html(document: &MarkdownDocument, page: &Page, website: &Website } +pub fn generate_html_redirect(path: &str) -> String { + let path = sanitize_text(path); + format!("\ +<!DOCTYPE html> +<head> +<title>Redirect</title> +<meta http-equiv='refresh' content='0; url={path}'> +</head> +<html>") +} + + pub fn get_html_head(page: &Page, website: &Website) -> String { let root = page.root(); website.get_config("html.head") diff --git a/src/main.rs b/src/main.rs index a41f801..a742cfe 100644 --- a/src/main.rs +++ b/src/main.rs @@ -121,6 +121,18 @@ fn main() { error!("Failed to copy static directory {:?} to {:?}", static_dir.source_path, destination)); } + + for redirect in &website.redirects { + let mut destination = destination.clone(); + destination.push(&redirect.full_url); + if let Some(path) = website.has_page(redirect, &redirect.redirect, "html") { + if args.html { + write_file(&generate_html_redirect(&path), &destination, "html"); + } + } else { + warn!("Redirect {:?} links to nonexistent page {:?}", redirect.name, redirect.redirect); + } + } } |