improve how posts and pages are referenced internally

since most often, we need to get posts sorted in descending order by
date, we now just store the actual posts in a Vec, pre-sorted by date.
everything else that needs to reference posts now does so by the post's
index in that Vec.

pages are also changed to be stored/referenced in a similar manner, but
it is somewhat less important for pages since we don't do any real
enumeration of pages at all anywhere. but it's nice to be consistent.
This commit is contained in:
Gered 2023-06-28 16:59:10 -04:00
parent b144813654
commit 9d2af25c81

View file

@ -71,32 +71,31 @@ impl OldUrlMappings {
} }
} }
pub struct UrlsByTag { pub struct PostsByTag {
mapping: HashMap<Tag, Vec<UriPath>>, mapping: HashMap<Tag, Vec<usize>>,
} }
impl UrlsByTag { impl PostsByTag {
pub fn new() -> Self { pub fn new() -> Self {
UrlsByTag { mapping: HashMap::new() } PostsByTag { mapping: HashMap::new() }
} }
#[inline] #[inline]
pub fn get(&self, tag: &Tag) -> Option<&[UriPath]> { pub fn get(&self, tag: &Tag) -> Option<&[usize]> {
self.mapping.get(tag).map(|x| x.as_slice()) self.mapping.get(tag).map(|x| x.as_slice())
} }
pub fn add_mapping(&mut self, url: &UriPath, tag: &Tag) { pub fn add_mapping(&mut self, post_index: usize, tag: &Tag) {
if let Some(urls) = self.mapping.get_mut(tag) { if let Some(indices) = self.mapping.get_mut(tag) {
urls.push(url.clone()); indices.push(post_index);
} else { } else {
let urls = vec![url.clone()]; self.mapping.insert(tag.clone(), vec![post_index]);
self.mapping.insert(tag.clone(), urls);
} }
} }
pub fn add_mappings(&mut self, url: &UriPath, tags: &[Tag]) { pub fn add_mappings(&mut self, post_index: usize, tags: &[Tag]) {
for tag in tags.iter() { for tag in tags.iter() {
self.add_mapping(url, tag); self.add_mapping(post_index, tag);
} }
} }
} }
@ -173,84 +172,91 @@ pub enum Content<'a> {
} }
pub struct SiteContent { pub struct SiteContent {
pub pages: HashMap<UriPath, Page>, pub pages: Vec<Page>,
pub posts: HashMap<UriPath, Post>, pub posts: Vec<Post>,
pub pages_by_url: HashMap<UriPath, usize>,
pub posts_by_url: HashMap<UriPath, usize>,
pub old_url_mappings: OldUrlMappings, pub old_url_mappings: OldUrlMappings,
pub post_tag_mappings: UrlsByTag, pub post_tag_mappings: PostsByTag,
pub sorted_post_urls: Vec<UriPath>,
pub rss: RssMetadata, pub rss: RssMetadata,
} }
impl SiteContent { impl SiteContent {
pub fn new(pages_config: config::Pages, posts_config: config::Posts) -> Result<Self, SiteError> { pub fn new(pages_config: config::Pages, posts_config: config::Posts) -> Result<Self, SiteError> {
let mut old_url_mappings = OldUrlMappings::new(); let mut old_url_mappings = OldUrlMappings::new();
let mut post_tag_mappings = UrlsByTag::new(); let mut post_tag_mappings = PostsByTag::new();
let mut sorted_post_urls = Vec::<UriPath>::new();
// load pages // load pages
let mut pages = HashMap::<UriPath, Page>::new(); let mut pages = Vec::new();
for page_config in pages_config.pages.iter() { let mut pages_by_url = HashMap::new();
for (index, page_config) in pages_config.pages.iter().enumerate() {
let page = Page::try_from(page_config.clone())?; let page = Page::try_from(page_config.clone())?;
if let Some(old_urls) = &page_config.old_urls { if let Some(old_urls) = &page_config.old_urls {
old_url_mappings.add_mappings(old_urls, &page.url); old_url_mappings.add_mappings(old_urls, &page.url);
} }
pages.insert(page.url.clone(), page); pages_by_url.insert(page.url.clone(), index);
pages.push(page);
} }
// load posts // load posts, iterating over the config's list of posts in descending order by date so that
let mut posts = HashMap::<UriPath, Post>::new(); // our final post list is pre-sorted this way, as well as the post lists per tag
for post_config in posts_config.posts.iter() { let mut posts = Vec::new();
let mut posts_by_url = HashMap::new();
for (index, post_config) in posts_config.posts.iter().sorted_by(|a, b| b.date.cmp(&a.date)).enumerate() {
let post = Post::try_from(post_config.clone())?; let post = Post::try_from(post_config.clone())?;
if let Some(old_urls) = &post_config.old_urls { if let Some(old_urls) = &post_config.old_urls {
old_url_mappings.add_mappings(old_urls, &post.url); old_url_mappings.add_mappings(old_urls, &post.url);
} }
posts.insert(post.url.clone(), post); posts_by_url.insert(post.url.clone(), index);
} post_tag_mappings.add_mappings(index, &post.tags);
posts.push(post);
// build pre-sorted post urls table. as well, build the post url by tag mapping here so that
// the post urls for each tag will already be ordered by date
for post in posts.values().sorted_by(|a, b| b.date.cmp(&a.date)) {
sorted_post_urls.push(post.url.clone());
post_tag_mappings.add_mappings(&post.url, &post.tags);
} }
let rss = RssMetadata::from(posts_config.rss); let rss = RssMetadata::from(posts_config.rss);
Ok(SiteContent { pages, posts, old_url_mappings, post_tag_mappings, sorted_post_urls, rss }) Ok(SiteContent { pages, posts, pages_by_url, posts_by_url, old_url_mappings, post_tag_mappings, rss })
}
pub fn get_page_by_url(&self, url: &UriPath) -> Option<&Page> {
self.pages_by_url.get(url).map(|index| self.pages.get(*index).unwrap())
}
pub fn get_post_by_url(&self, url: &UriPath) -> Option<&Post> {
self.posts_by_url.get(url).map(|index| self.posts.get(*index).unwrap())
} }
pub fn get_content_at(&self, url: &UriPath) -> Option<Content> { pub fn get_content_at(&self, url: &UriPath) -> Option<Content> {
if let Some(new_url) = self.old_url_mappings.get(url) { if let Some(new_url) = self.old_url_mappings.get(url) {
Some(Content::Redirect(new_url.clone())) Some(Content::Redirect(new_url.clone()))
} else if let Some(post) = self.posts.get(url) { } else if let Some(post) = self.get_post_by_url(url) {
Some(Content::Post(post)) Some(Content::Post(post))
} else if let Some(page) = self.pages.get(url) { } else if let Some(page) = self.get_page_by_url(url) {
Some(Content::Page(page)) Some(Content::Page(page))
} else { } else {
None None
} }
} }
pub fn get_posts_ordered_by_date(&self) -> Vec<&Post> { pub fn get_posts_ordered_by_date(&self) -> &[Post] {
self.sorted_post_urls.iter().map(|post_url| self.posts.get(post_url).unwrap()).collect() self.posts.as_slice()
} }
pub fn get_posts_with_tag_ordered_by_date(&self, tag: &Tag) -> Vec<&Post> { pub fn get_posts_with_tag_ordered_by_date(&self, tag: &Tag) -> Vec<&Post> {
let mut posts = Vec::new(); let mut posts = Vec::new();
if let Some(post_urls) = self.post_tag_mappings.get(tag) { if let Some(post_indices) = self.post_tag_mappings.get(tag) {
for url in post_urls.iter() { for post_index in post_indices.iter() {
posts.push(self.posts.get(url).unwrap()) posts.push(self.posts.get(*post_index).unwrap())
} }
} }
posts posts
} }
pub fn get_latest_post(&self) -> Option<&Post> { pub fn get_latest_post(&self) -> Option<&Post> {
self.sorted_post_urls.first().map(|post_url| self.posts.get(post_url).unwrap()) self.posts.first()
} }
} }