summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBen Bridle <ben@derelict.engineering>2025-01-15 09:23:00 +1300
committerBen Bridle <ben@derelict.engineering>2025-01-15 09:23:05 +1300
commita9c45ef6de84df758194e3a9428faee8c69065ae (patch)
treee9080dac289cba8fba02a9643801b50375e902fa
parente96bbf19171452de9d269a98dc0f4171d1bf46b1 (diff)
downloadtoaster-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.rs57
-rw-r--r--src/generate_html.rs12
-rw-r--r--src/main.rs12
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);
+ }
+ }
}