1 use std::borrow::Cow;
2 use std::collections::HashSet;
3 use std::hash::{Hash, Hasher};
4 
5 use serde_derive::Serialize;
6 
7 use config::Config;
8 use library::{Library, Taxonomy};
9 use std::cmp::Ordering;
10 use tera::{Map, Value};
11 
12 /// The sitemap only needs links, potentially date and extra for pages in case of updates
13 /// for examples so we trim down all entries to only that
14 #[derive(Debug, Serialize)]
15 pub struct SitemapEntry<'a> {
16     pub permalink: Cow<'a, str>,
17     pub updated: Option<String>,
18     pub extra: Option<&'a Map<String, Value>>,
19 }
20 
21 // Hash/Eq is not implemented for tera::Map but in our case we only care about the permalink
22 // when comparing/hashing so we implement it manually
23 impl<'a> Hash for SitemapEntry<'a> {
hash<H: Hasher>(&self, state: &mut H)24     fn hash<H: Hasher>(&self, state: &mut H) {
25         self.permalink.hash(state);
26     }
27 }
28 impl<'a> PartialEq for SitemapEntry<'a> {
eq(&self, other: &SitemapEntry) -> bool29     fn eq(&self, other: &SitemapEntry) -> bool {
30         self.permalink == other.permalink
31     }
32 }
33 impl<'a> Eq for SitemapEntry<'a> {}
34 
35 impl<'a> SitemapEntry<'a> {
new(permalink: Cow<'a, str>, updated: Option<String>) -> Self36     pub fn new(permalink: Cow<'a, str>, updated: Option<String>) -> Self {
37         SitemapEntry { permalink, updated, extra: None }
38     }
39 
add_extra(&mut self, extra: &'a Map<String, Value>)40     pub fn add_extra(&mut self, extra: &'a Map<String, Value>) {
41         self.extra = Some(extra);
42     }
43 }
44 
45 impl<'a> PartialOrd for SitemapEntry<'a> {
partial_cmp(&self, other: &SitemapEntry) -> Option<Ordering>46     fn partial_cmp(&self, other: &SitemapEntry) -> Option<Ordering> {
47         Some(self.permalink.as_ref().cmp(other.permalink.as_ref()))
48     }
49 }
50 
51 impl<'a> Ord for SitemapEntry<'a> {
cmp(&self, other: &SitemapEntry) -> Ordering52     fn cmp(&self, other: &SitemapEntry) -> Ordering {
53         self.permalink.as_ref().cmp(other.permalink.as_ref())
54     }
55 }
56 
57 /// Finds out all the links to put in a sitemap from the pages/sections/taxonomies
58 /// There are no duplicate permalinks in the output vec
find_entries<'a>( library: &'a Library, taxonomies: &'a [Taxonomy], config: &'a Config, ) -> Vec<SitemapEntry<'a>>59 pub fn find_entries<'a>(
60     library: &'a Library,
61     taxonomies: &'a [Taxonomy],
62     config: &'a Config,
63 ) -> Vec<SitemapEntry<'a>> {
64     let pages = library
65         .pages_values()
66         .iter()
67         .map(|p| {
68             let mut entry = SitemapEntry::new(
69                 Cow::Borrowed(&p.permalink),
70                 p.meta.updated.clone().or_else(|| p.meta.date.clone()),
71             );
72             entry.add_extra(&p.meta.extra);
73             entry
74         })
75         .collect::<Vec<_>>();
76 
77     let mut sections = library
78         .sections_values()
79         .iter()
80         .filter(|s| s.meta.render)
81         .map(|s| {
82             let mut entry = SitemapEntry::new(Cow::Borrowed(&s.permalink), None);
83             entry.add_extra(&s.meta.extra);
84             entry
85         })
86         .collect::<Vec<_>>();
87 
88     for section in library.sections_values().iter() {
89         if let Some(paginate_by) = section.paginate_by() {
90             let number_pagers = (section.pages.len() as f64 / paginate_by as f64).ceil() as isize;
91             for i in 1..=number_pagers {
92                 let permalink =
93                     format!("{}{}/{}/", section.permalink, section.meta.paginate_path, i);
94                 sections.push(SitemapEntry::new(Cow::Owned(permalink), None))
95             }
96         }
97     }
98 
99     let mut taxonomies_entries = vec![];
100     for taxonomy in taxonomies {
101         let name = &taxonomy.kind.name;
102         let mut terms = vec![SitemapEntry::new(Cow::Owned(config.make_permalink(name)), None)];
103         for item in &taxonomy.items {
104             terms.push(SitemapEntry::new(
105                 Cow::Owned(config.make_permalink(&format!("{}/{}", name, item.slug))),
106                 None,
107             ));
108 
109             if taxonomy.kind.is_paginated() {
110                 let number_pagers = (item.pages.len() as f64
111                     / taxonomy.kind.paginate_by.unwrap() as f64)
112                     .ceil() as isize;
113                 for i in 1..=number_pagers {
114                     let permalink = config.make_permalink(&format!(
115                         "{}/{}/{}/{}",
116                         name,
117                         item.slug,
118                         taxonomy.kind.paginate_path(),
119                         i
120                     ));
121                     terms.push(SitemapEntry::new(Cow::Owned(permalink), None))
122                 }
123             }
124         }
125 
126         taxonomies_entries.push(terms);
127     }
128 
129     let mut all_sitemap_entries = HashSet::new();
130     for p in pages {
131         all_sitemap_entries.insert(p);
132     }
133     for s in sections {
134         all_sitemap_entries.insert(s);
135     }
136     for terms in taxonomies_entries {
137         for term in terms {
138             all_sitemap_entries.insert(term);
139         }
140     }
141 
142     let mut entries = all_sitemap_entries.into_iter().collect::<Vec<_>>();
143     entries.sort();
144     entries
145 }
146