summaryrefslogtreecommitdiff
path: root/src/collect_files.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/collect_files.rs')
-rw-r--r--src/collect_files.rs174
1 files changed, 106 insertions, 68 deletions
diff --git a/src/collect_files.rs b/src/collect_files.rs
index 88d065f..d9cfb37 100644
--- a/src/collect_files.rs
+++ b/src/collect_files.rs
@@ -11,13 +11,20 @@ pub struct Website {
}
pub struct Page {
- pub name: String, // Display name
- pub parent_url: String, // URL base for relative links
- pub file_url: String, // Safe file name, no extension
- pub full_url: String, // Safe full URL, no extension
+ pub name: String, // Display name of this page
+ pub name_url: String, // URL name for this page, no extension
+ pub full_url: String, // Full URL for this page, no extension
+ pub parents: Vec<String>, // Parent directory components, unsafe
+ pub parent_url: String, // Base URL for links in this page
pub source_path: PathBuf, // Absolute path to source file
pub document: MarkdownDocument, // File content parsed as markdown
- pub headings: Vec<String>, // Safe name of each document heading
+ pub headings: Vec<Heading>, // Ordered list of all headings in page
+}
+
+pub struct Heading {
+ pub name: String,
+ pub url: String,
+ pub level: Level,
}
pub struct StaticItem {
@@ -28,14 +35,12 @@ pub struct StaticItem {
impl Page {
- pub fn back_string(&self) -> String {
- let mut back = String::new();
- for c in self.full_url.chars() {
- if c == '/' {
- back.push_str("../");
- }
+ pub fn root(&self) -> String {
+ let mut root = String::new();
+ for _ in &self.parents {
+ root.push_str("../");
}
- return back;
+ return root;
}
}
@@ -59,9 +64,7 @@ impl Website {
fn collect_entry(&mut self, path: &Path, prefix: &Path) {
let entry = Entry::from_path(path).unwrap();
// Ignore dotted entries.
- if entry.name.starts_with('.') {
- return;
- }
+ if entry.name.starts_with('.') { return }
// Get name and extension.
let (mut name, extension) = entry.split_name();
if let Some((prefix, suffix)) = name.split_once(' ') {
@@ -69,18 +72,14 @@ impl Website {
name = suffix.to_string();
}
}
- let file_url = make_url_safe(&name);
+ let name_url = make_url_safe(&name);
// Generate parent URL, used only for files.
let source_path = entry.original_path.clone();
let relative_path = source_path.strip_prefix(prefix).unwrap_or_else(
- |_| error!("Path doesn't start with {:?}: {:?}", prefix, source_path));
- let mut parent_url = String::new();
- let mut components: Vec<_> = relative_path.components().collect();
- components.pop(); // Remove file segment.
- for c in &components {
- let segment = &make_url_safe(&c.as_os_str().to_string_lossy());
- parent_url.push_str(segment); parent_url.push('/')
- };
+ |_| error!("Path doesn't start with {prefix:?}: {source_path:?}"));
+ let mut parents: Vec<_> = relative_path.components()
+ .map(|c| c.as_os_str().to_string_lossy().to_string()).collect();
+ parents.pop(); // Remove file segment.
// Process each entry.
if entry.is_directory() {
@@ -98,34 +97,74 @@ impl Website {
let markdown = std::fs::read_to_string(&source_path).unwrap();
let document = MarkdownDocument::from_str(&markdown);
let headings = document.blocks.iter()
- .filter_map(|block| if let Block::Heading { line, .. } = block {
- Some(make_url_safe(&line.to_string()))
+ .filter_map(|block| if let Block::Heading { line, level } = block {
+ let name = line.to_string();
+ let url = make_url_safe(&name);
+ let level = level.to_owned();
+ Some(Heading { name, url, level })
} else {
None
}).collect();
- // Change name and path if this is an index file.
- let mut name = name;
- let mut file_url = file_url;
- let mut full_url = format!("{parent_url}{file_url}");
- if file_url == "+index" {
- if components.is_empty() {
+ if name_url == "+index" {
+ if parents.is_empty() {
// This is the index file for the whole site.
- name = String::from("Home");
- file_url = String::from("index");
- full_url = String::from("index");
+ self.pages.push(Page {
+ name: String::from("Home"),
+ name_url: String::from("index"),
+ full_url: String::from("index"),
+ parents,
+ parent_url: String::from(""),
+ source_path,
+ document,
+ headings,
+ });
} else {
// This is an index file for a directory.
- name = components[components.len()-1]
- .as_os_str().to_string_lossy().to_string();
- file_url = make_url_safe(&name);
- full_url = parent_url.strip_suffix('/').unwrap_or(&parent_url).to_string();
+ let name = parents[parents.len()-1].clone();
+ let name_url = make_url_safe(&name);
+ 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.pop();
+ parents.pop();
+ self.pages.push(Page {
+ name,
+ name_url,
+ full_url,
+ parents,
+ parent_url,
+ source_path,
+ document,
+ headings,
+ });
}
+ } else {
+ let mut full_url = String::new();
+ for parent in &parents {
+ full_url.push_str(&make_url_safe(parent));
+ full_url.push('/');
+ }
+ full_url.push_str(&name_url);
+ let mut parent_url = full_url.clone();
+ parent_url.push('/');
+ self.pages.push(Page {
+ name, name_url, full_url,
+ parents, parent_url,
+ source_path,
+ document, headings,
+ });
}
- self.pages.push(
- Page { name, parent_url, file_url, full_url, source_path, document, headings });
},
_ => {
- let full_url = format!("{parent_url}{file_url}.{extension}");
+ let mut parent_url = String::new();
+ for parent in &parents {
+ parent_url.push_str(&make_url_safe(parent));
+ parent_url.push('/');
+ }
+ let full_url = format!("{parent_url}{name_url}.{extension}");
self.static_files.push(StaticItem { full_url, source_path });
},
}
@@ -144,42 +183,41 @@ impl Website {
}
None => (path, None),
};
- let path = path.strip_suffix(&format!(".{ext}")).unwrap_or(path);
-
- // Attach parent of current page to given path.
- let directory = match from.parent_url.rsplit_once('/') {
- Some((parent, _)) => parent,
- None => &from.parent_url,
- };
- let full_path = match path.starts_with("/") {
- true => path.to_string(),
- false => format!("{directory}/{path}"),
- };
+ 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);
+ }
- // Remove relative portions of path.
- let segments: Vec<&str> = full_path.split("/")
- .filter(|seg| !seg.is_empty() && *seg != ".")
+ // Iteratively collapse ".." segments.
+ let mut segments: Vec<&str> = path.split('/')
+ .filter(|s| !s.is_empty() && *s != ".")
.collect();
- let mut reduced_segments: Vec<&str> = segments.windows(2)
- .filter(|w| w[1] != "..")
- .map(|w| w[1])
- .collect();
- // The first segment is always skipped by the previous step.
- if !segments.is_empty() && segments.get(1) != Some(&"..") {
- if segments[0] != ".." {
- reduced_segments.insert(0, segments[0]);
+ 'outer: loop {
+ for i in 0..(segments.len()-1) {
+ if segments[i] == ".." {
+ if i == 0 {
+ segments.remove(0);
+ } else {
+ segments.remove(i-1);
+ segments.remove(i-1);
+ }
+ continue 'outer;
+ }
}
+ break;
}
- let path = reduced_segments.join("/");
-
+ // Find page with this path in website.
+ let path = segments.join("/");
for page in &self.pages {
if page.full_url == path {
if let Some(heading) = heading {
- if !page.headings.contains(&make_url_safe(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);
}
}
- return Some(format!("{path}.{ext}"));
+ let root = from.root();
+ return Some(format!("{root}{path}.{ext}"));
}
}
return None;