Build taxonomies as pages are added (#1841)
This commit is contained in:
parent
77eb9fef9b
commit
bad0127c9f
@ -26,7 +26,6 @@ pub struct LanguageOptions {
|
||||
pub search: search::Search,
|
||||
/// A toml crate `Table` with String key representing term and value
|
||||
/// another `String` representing its translation.
|
||||
///
|
||||
/// Use `get_translation()` method for translating key into different languages.
|
||||
pub translations: HashMap<String, String>,
|
||||
}
|
||||
|
||||
@ -15,6 +15,7 @@ use serde::{Deserialize, Serialize};
|
||||
use crate::theme::Theme;
|
||||
use errors::{anyhow, bail, Result};
|
||||
use utils::fs::read_file;
|
||||
use utils::slugs::slugify_paths;
|
||||
|
||||
// We want a default base url for tests
|
||||
static DEFAULT_BASE_URL: &str = "http://a-website.com";
|
||||
@ -55,7 +56,6 @@ pub struct Config {
|
||||
pub feed_filename: String,
|
||||
/// If set, files from static/ will be hardlinked instead of copied to the output dir.
|
||||
pub hard_link_static: bool,
|
||||
|
||||
pub taxonomies: Vec<taxonomies::TaxonomyConfig>,
|
||||
|
||||
/// Whether to compile the `sass` directory and output the css files into the static folder
|
||||
@ -124,6 +124,7 @@ impl Config {
|
||||
}
|
||||
|
||||
config.add_default_language();
|
||||
config.slugify_taxonomies();
|
||||
|
||||
if !config.ignored_content.is_empty() {
|
||||
// Convert the file glob strings into a compiled glob set matcher. We want to do this once,
|
||||
@ -149,6 +150,7 @@ impl Config {
|
||||
pub fn default_for_test() -> Self {
|
||||
let mut config = Config::default();
|
||||
config.add_default_language();
|
||||
config.slugify_taxonomies();
|
||||
config
|
||||
}
|
||||
|
||||
@ -168,6 +170,14 @@ impl Config {
|
||||
Ok(config)
|
||||
}
|
||||
|
||||
pub fn slugify_taxonomies(&mut self) {
|
||||
for (_, lang_options) in self.languages.iter_mut() {
|
||||
for tax_def in lang_options.taxonomies.iter_mut() {
|
||||
tax_def.slug = slugify_paths(&tax_def.name, self.slugify.taxonomies);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Makes a url, taking into account that the base url might have a trailing slash
|
||||
pub fn make_permalink(&self, path: &str) -> String {
|
||||
let trailing_bit =
|
||||
@ -283,6 +293,14 @@ impl Config {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn has_taxonomy(&self, name: &str, lang: &str) -> bool {
|
||||
if let Some(lang_options) = self.languages.get(lang) {
|
||||
lang_options.taxonomies.iter().any(|t| t.name == name)
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
pub fn serialize(&self, lang: &str) -> SerializedConfig {
|
||||
let options = &self.languages[lang];
|
||||
|
||||
|
||||
@ -5,6 +5,8 @@ use serde::{Deserialize, Serialize};
|
||||
pub struct TaxonomyConfig {
|
||||
/// The name used in the URL, usually the plural
|
||||
pub name: String,
|
||||
/// The slug according to the config slugification strategy
|
||||
pub slug: String,
|
||||
/// If this is set, the list of individual taxonomy term page will be paginated
|
||||
/// by this much
|
||||
pub paginate_by: Option<usize>,
|
||||
@ -19,6 +21,7 @@ impl Default for TaxonomyConfig {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
name: String::new(),
|
||||
slug: String::new(),
|
||||
paginate_by: None,
|
||||
paginate_path: None,
|
||||
render: true,
|
||||
|
||||
@ -1,27 +1,40 @@
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
use config::Config;
|
||||
use errors::Result;
|
||||
use libs::ahash::{AHashMap, AHashSet};
|
||||
|
||||
use crate::ser::TranslatedContent;
|
||||
use crate::sorting::sort_pages;
|
||||
use crate::taxonomies::{find_taxonomies, Taxonomy};
|
||||
use crate::taxonomies::{Taxonomy, TaxonomyFound};
|
||||
use crate::{Page, Section, SortBy};
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub struct Library {
|
||||
pub pages: AHashMap<PathBuf, Page>,
|
||||
pub sections: AHashMap<PathBuf, Section>,
|
||||
pub taxonomies: Vec<Taxonomy>,
|
||||
// aliases -> files, so we can easily check for conflicts
|
||||
pub reverse_aliases: AHashMap<String, AHashSet<PathBuf>>,
|
||||
pub translations: AHashMap<PathBuf, AHashSet<PathBuf>>,
|
||||
// A mapping of {lang -> <slug, {term -> vec<paths>}>>}
|
||||
taxonomies_def: AHashMap<String, AHashMap<String, AHashMap<String, Vec<PathBuf>>>>,
|
||||
// So we don't need to pass the Config when adding a page to know how to slugify and we only
|
||||
// slugify once
|
||||
taxo_name_to_slug: AHashMap<String, String>,
|
||||
}
|
||||
|
||||
impl Library {
|
||||
pub fn new() -> Self {
|
||||
Self::default()
|
||||
pub fn new(config: &Config) -> Self {
|
||||
let mut lib = Self::default();
|
||||
|
||||
for (lang, options) in &config.languages {
|
||||
let mut taxas = AHashMap::new();
|
||||
for tax_def in &options.taxonomies {
|
||||
taxas.insert(tax_def.slug.clone(), AHashMap::new());
|
||||
lib.taxo_name_to_slug.insert(tax_def.name.clone(), tax_def.slug.clone());
|
||||
}
|
||||
lib.taxonomies_def.insert(lang.to_string(), taxas);
|
||||
}
|
||||
lib
|
||||
}
|
||||
|
||||
fn insert_reverse_aliases(&mut self, file_path: &Path, entries: Vec<String>) {
|
||||
@ -60,6 +73,25 @@ impl Library {
|
||||
let mut entries = vec![page.path.clone()];
|
||||
entries.extend(page.meta.aliases.to_vec());
|
||||
self.insert_reverse_aliases(&file_path, entries);
|
||||
|
||||
for (taxa_name, terms) in &page.meta.taxonomies {
|
||||
for term in terms {
|
||||
// Safe unwraps as we create all lang/taxa and we validated that they are correct
|
||||
// before getting there
|
||||
let taxa_def = self
|
||||
.taxonomies_def
|
||||
.get_mut(&page.lang)
|
||||
.expect("lang not found")
|
||||
.get_mut(&self.taxo_name_to_slug[taxa_name])
|
||||
.expect("taxa not found");
|
||||
|
||||
if !taxa_def.contains_key(term) {
|
||||
taxa_def.insert(term.to_string(), Vec::new());
|
||||
}
|
||||
taxa_def.get_mut(term).unwrap().push(page.file.path.clone());
|
||||
}
|
||||
}
|
||||
|
||||
self.pages.insert(file_path, page);
|
||||
}
|
||||
|
||||
@ -71,10 +103,29 @@ impl Library {
|
||||
self.sections.insert(file_path, section);
|
||||
}
|
||||
|
||||
/// Separate from `populate_sections` as it's called _before_ markdown the pages/sections
|
||||
pub fn populate_taxonomies(&mut self, config: &Config) -> Result<()> {
|
||||
self.taxonomies = find_taxonomies(config, &self.pages)?;
|
||||
Ok(())
|
||||
/// This is called _before_ rendering the markdown the pages/sections
|
||||
pub fn find_taxonomies(&self, config: &Config) -> Vec<Taxonomy> {
|
||||
let mut taxonomies = Vec::new();
|
||||
|
||||
for (lang, taxonomies_data) in &self.taxonomies_def {
|
||||
for (taxa_slug, terms_pages) in taxonomies_data {
|
||||
let taxo_config = &config.languages[lang]
|
||||
.taxonomies
|
||||
.iter()
|
||||
.find(|t| &t.slug == taxa_slug)
|
||||
.expect("taxo should exist");
|
||||
let mut taxo_found = TaxonomyFound::new(taxa_slug.to_string(), lang, taxo_config);
|
||||
for (term, page_path) in terms_pages {
|
||||
taxo_found
|
||||
.terms
|
||||
.insert(term, page_path.iter().map(|p| &self.pages[p]).collect());
|
||||
}
|
||||
|
||||
taxonomies.push(Taxonomy::new(taxo_found, config));
|
||||
}
|
||||
}
|
||||
|
||||
taxonomies
|
||||
}
|
||||
|
||||
/// Sort all sections pages according to sorting method given
|
||||
@ -280,21 +331,19 @@ impl Library {
|
||||
pub fn find_sections_by_path(&self, paths: &[PathBuf]) -> Vec<&Section> {
|
||||
paths.iter().map(|p| &self.sections[p]).collect()
|
||||
}
|
||||
|
||||
pub fn find_taxonomies(&self, config: &Config) -> Result<Vec<Taxonomy>> {
|
||||
find_taxonomies(config, &self.pages)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::FileInfo;
|
||||
use config::LanguageOptions;
|
||||
use config::{LanguageOptions, TaxonomyConfig};
|
||||
use std::collections::HashMap;
|
||||
use utils::slugs::SlugifyStrategy;
|
||||
|
||||
#[test]
|
||||
fn can_find_collisions_with_paths() {
|
||||
let mut library = Library::new();
|
||||
let mut library = Library::default();
|
||||
let mut section = Section { path: "hello".to_owned(), ..Default::default() };
|
||||
section.file.path = PathBuf::from("hello.md");
|
||||
library.insert_section(section.clone());
|
||||
@ -311,7 +360,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn can_find_collisions_with_aliases() {
|
||||
let mut library = Library::new();
|
||||
let mut library = Library::default();
|
||||
let mut section = Section { path: "hello".to_owned(), ..Default::default() };
|
||||
section.file.path = PathBuf::from("hello.md");
|
||||
library.insert_section(section.clone());
|
||||
@ -378,7 +427,7 @@ mod tests {
|
||||
fn can_populate_sections() {
|
||||
let mut config = Config::default_for_test();
|
||||
config.languages.insert("fr".to_owned(), LanguageOptions::default());
|
||||
let mut library = Library::new();
|
||||
let mut library = Library::default();
|
||||
let sections = vec![
|
||||
("content/_index.md", "en", 0, false, SortBy::None),
|
||||
("content/_index.fr.md", "fr", 0, false, SortBy::None),
|
||||
@ -514,4 +563,152 @@ mod tests {
|
||||
assert!(translations[0].title.is_some());
|
||||
assert!(translations[1].title.is_some());
|
||||
}
|
||||
|
||||
macro_rules! taxonomies {
|
||||
($config:expr, [$($page:expr),+]) => {{
|
||||
let mut library = Library::new(&$config);
|
||||
$(
|
||||
library.insert_page($page);
|
||||
)+
|
||||
library.find_taxonomies(&$config)
|
||||
}};
|
||||
}
|
||||
|
||||
fn create_page_w_taxa(path: &str, lang: &str, taxo: Vec<(&str, Vec<&str>)>) -> Page {
|
||||
let mut page = Page::default();
|
||||
page.file.path = PathBuf::from(path);
|
||||
page.lang = lang.to_owned();
|
||||
let mut taxonomies = HashMap::new();
|
||||
for (name, terms) in taxo {
|
||||
taxonomies.insert(name.to_owned(), terms.iter().map(|t| t.to_string()).collect());
|
||||
}
|
||||
page.meta.taxonomies = taxonomies;
|
||||
page
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn can_make_taxonomies() {
|
||||
let mut config = Config::default_for_test();
|
||||
config.languages.get_mut("en").unwrap().taxonomies = vec![
|
||||
TaxonomyConfig { name: "categories".to_string(), ..TaxonomyConfig::default() },
|
||||
TaxonomyConfig { name: "tags".to_string(), ..TaxonomyConfig::default() },
|
||||
TaxonomyConfig { name: "authors".to_string(), ..TaxonomyConfig::default() },
|
||||
];
|
||||
config.slugify_taxonomies();
|
||||
|
||||
let page1 = create_page_w_taxa(
|
||||
"a.md",
|
||||
"en",
|
||||
vec![("tags", vec!["rust", "db"]), ("categories", vec!["tutorials"])],
|
||||
);
|
||||
let page2 = create_page_w_taxa(
|
||||
"b.md",
|
||||
"en",
|
||||
vec![("tags", vec!["rust", "js"]), ("categories", vec!["others"])],
|
||||
);
|
||||
let page3 = create_page_w_taxa(
|
||||
"c.md",
|
||||
"en",
|
||||
vec![("tags", vec!["js"]), ("authors", vec!["Vincent Prouillet"])],
|
||||
);
|
||||
let taxonomies = taxonomies!(config, [page1, page2, page3]);
|
||||
|
||||
let tags = taxonomies.iter().find(|t| t.kind.name == "tags").unwrap();
|
||||
assert_eq!(tags.len(), 3);
|
||||
assert_eq!(tags.items[0].name, "db");
|
||||
assert_eq!(tags.items[0].permalink, "http://a-website.com/tags/db/");
|
||||
assert_eq!(tags.items[0].pages.len(), 1);
|
||||
assert_eq!(tags.items[1].name, "js");
|
||||
assert_eq!(tags.items[1].permalink, "http://a-website.com/tags/js/");
|
||||
assert_eq!(tags.items[1].pages.len(), 2);
|
||||
assert_eq!(tags.items[2].name, "rust");
|
||||
assert_eq!(tags.items[2].permalink, "http://a-website.com/tags/rust/");
|
||||
assert_eq!(tags.items[2].pages.len(), 2);
|
||||
|
||||
let categories = taxonomies.iter().find(|t| t.kind.name == "categories").unwrap();
|
||||
assert_eq!(categories.items.len(), 2);
|
||||
assert_eq!(categories.items[0].name, "others");
|
||||
assert_eq!(categories.items[0].permalink, "http://a-website.com/categories/others/");
|
||||
assert_eq!(categories.items[0].pages.len(), 1);
|
||||
|
||||
let authors = taxonomies.iter().find(|t| t.kind.name == "authors").unwrap();
|
||||
assert_eq!(authors.items.len(), 1);
|
||||
assert_eq!(authors.items[0].permalink, "http://a-website.com/authors/vincent-prouillet/");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn can_make_multiple_language_taxonomies() {
|
||||
let mut config = Config::default_for_test();
|
||||
config.slugify.taxonomies = SlugifyStrategy::Safe;
|
||||
config.languages.insert("fr".to_owned(), LanguageOptions::default());
|
||||
config.languages.get_mut("en").unwrap().taxonomies = vec![
|
||||
TaxonomyConfig { name: "categories".to_string(), ..TaxonomyConfig::default() },
|
||||
TaxonomyConfig { name: "tags".to_string(), ..TaxonomyConfig::default() },
|
||||
];
|
||||
config.languages.get_mut("fr").unwrap().taxonomies = vec![
|
||||
TaxonomyConfig { name: "catégories".to_string(), ..TaxonomyConfig::default() },
|
||||
TaxonomyConfig { name: "tags".to_string(), ..TaxonomyConfig::default() },
|
||||
];
|
||||
config.slugify_taxonomies();
|
||||
|
||||
let page1 = create_page_w_taxa("a.md", "en", vec![("categories", vec!["rust"])]);
|
||||
let page2 = create_page_w_taxa("b.md", "en", vec![("tags", vec!["rust"])]);
|
||||
let page3 = create_page_w_taxa("c.md", "fr", vec![("catégories", vec!["rust"])]);
|
||||
let taxonomies = taxonomies!(config, [page1, page2, page3]);
|
||||
|
||||
let categories = taxonomies.iter().find(|t| t.kind.name == "categories").unwrap();
|
||||
assert_eq!(categories.len(), 1);
|
||||
assert_eq!(categories.items[0].permalink, "http://a-website.com/categories/rust/");
|
||||
let tags = taxonomies.iter().find(|t| t.kind.name == "tags" && t.lang == "en").unwrap();
|
||||
assert_eq!(tags.len(), 1);
|
||||
assert_eq!(tags.items[0].permalink, "http://a-website.com/tags/rust/");
|
||||
let fr_categories = taxonomies.iter().find(|t| t.kind.name == "catégories").unwrap();
|
||||
assert_eq!(fr_categories.len(), 1);
|
||||
assert_eq!(fr_categories.items[0].permalink, "http://a-website.com/fr/catégories/rust/");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn taxonomies_with_unic_are_grouped_with_default_slugify_strategy() {
|
||||
let mut config = Config::default_for_test();
|
||||
config.languages.get_mut("en").unwrap().taxonomies = vec![
|
||||
TaxonomyConfig { name: "test-taxonomy".to_string(), ..TaxonomyConfig::default() },
|
||||
TaxonomyConfig { name: "test taxonomy".to_string(), ..TaxonomyConfig::default() },
|
||||
TaxonomyConfig { name: "test-taxonomy ".to_string(), ..TaxonomyConfig::default() },
|
||||
TaxonomyConfig { name: "Test-Taxonomy ".to_string(), ..TaxonomyConfig::default() },
|
||||
];
|
||||
config.slugify_taxonomies();
|
||||
let page1 = create_page_w_taxa("a.md", "en", vec![("test-taxonomy", vec!["Ecole"])]);
|
||||
let page2 = create_page_w_taxa("b.md", "en", vec![("test taxonomy", vec!["École"])]);
|
||||
let page3 = create_page_w_taxa("c.md", "en", vec![("test-taxonomy ", vec!["ecole"])]);
|
||||
let page4 = create_page_w_taxa("d.md", "en", vec![("Test-Taxonomy ", vec!["école"])]);
|
||||
let taxonomies = taxonomies!(config, [page1, page2, page3, page4]);
|
||||
assert_eq!(taxonomies.len(), 1);
|
||||
|
||||
let tax = &taxonomies[0];
|
||||
// under the default slugify strategy all of the provided terms should be the same
|
||||
assert_eq!(tax.items.len(), 1);
|
||||
let term1 = &tax.items[0];
|
||||
assert_eq!(term1.name, "Ecole");
|
||||
assert_eq!(term1.slug, "ecole");
|
||||
assert_eq!(term1.permalink, "http://a-website.com/test-taxonomy/ecole/");
|
||||
assert_eq!(term1.pages.len(), 4);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn taxonomies_with_unic_are_not_grouped_with_safe_slugify_strategy() {
|
||||
let mut config = Config::default_for_test();
|
||||
config.slugify.taxonomies = SlugifyStrategy::Safe;
|
||||
config.languages.get_mut("en").unwrap().taxonomies =
|
||||
vec![TaxonomyConfig { name: "test".to_string(), ..TaxonomyConfig::default() }];
|
||||
config.slugify_taxonomies();
|
||||
let page1 = create_page_w_taxa("a.md", "en", vec![("test", vec!["Ecole"])]);
|
||||
let page2 = create_page_w_taxa("b.md", "en", vec![("test", vec!["École"])]);
|
||||
let page3 = create_page_w_taxa("c.md", "en", vec![("test", vec!["ecole"])]);
|
||||
let page4 = create_page_w_taxa("d.md", "en", vec![("test", vec!["école"])]);
|
||||
let taxonomies = taxonomies!(config, [page1, page2, page3, page4]);
|
||||
assert_eq!(taxonomies.len(), 1);
|
||||
let tax = &taxonomies[0];
|
||||
// under the safe slugify strategy all terms should be distinct
|
||||
assert_eq!(tax.items.len(), 4);
|
||||
}
|
||||
}
|
||||
|
||||
@ -285,7 +285,7 @@ mod tests {
|
||||
num_pages: usize,
|
||||
paginate_reversed: bool,
|
||||
) -> (Section, Library) {
|
||||
let mut library = Library::new();
|
||||
let mut library = Library::default();
|
||||
for i in 1..=num_pages {
|
||||
let mut page = Page::default();
|
||||
page.meta.title = Some(i.to_string());
|
||||
|
||||
@ -4,7 +4,7 @@ use std::path::PathBuf;
|
||||
use serde::Serialize;
|
||||
|
||||
use config::{Config, TaxonomyConfig};
|
||||
use errors::{bail, Context as ErrorContext, Result};
|
||||
use errors::{Context as ErrorContext, Result};
|
||||
use libs::ahash::AHashMap;
|
||||
use libs::tera::{Context, Tera};
|
||||
use utils::slugs::slugify_paths;
|
||||
@ -125,7 +125,7 @@ pub struct Taxonomy {
|
||||
}
|
||||
|
||||
impl Taxonomy {
|
||||
fn new(tax_found: TaxonomyFound, config: &Config) -> Self {
|
||||
pub(crate) fn new(tax_found: TaxonomyFound, config: &Config) -> Self {
|
||||
let mut sorted_items = vec![];
|
||||
let slug = tax_found.slug;
|
||||
for (name, pages) in tax_found.terms {
|
||||
@ -231,7 +231,7 @@ impl Taxonomy {
|
||||
|
||||
/// Only used while building the taxonomies
|
||||
#[derive(Debug, PartialEq)]
|
||||
struct TaxonomyFound<'a> {
|
||||
pub(crate) struct TaxonomyFound<'a> {
|
||||
pub lang: &'a str,
|
||||
pub slug: String,
|
||||
pub config: &'a TaxonomyConfig,
|
||||
@ -243,220 +243,3 @@ impl<'a> TaxonomyFound<'a> {
|
||||
Self { slug, lang, config, terms: AHashMap::new() }
|
||||
}
|
||||
}
|
||||
|
||||
pub fn find_taxonomies(config: &Config, pages: &AHashMap<PathBuf, Page>) -> Result<Vec<Taxonomy>> {
|
||||
let mut taxonomies_def = AHashMap::new();
|
||||
let mut taxonomies_slug = AHashMap::new();
|
||||
|
||||
for (code, options) in &config.languages {
|
||||
let mut taxo_lang_def = AHashMap::new();
|
||||
for t in &options.taxonomies {
|
||||
let slug = slugify_paths(&t.name, config.slugify.taxonomies);
|
||||
taxonomies_slug.insert(&t.name, slug.clone());
|
||||
taxo_lang_def.insert(slug.clone(), TaxonomyFound::new(slug, code, t));
|
||||
}
|
||||
taxonomies_def.insert(code, taxo_lang_def);
|
||||
}
|
||||
|
||||
for (_, page) in pages {
|
||||
for (name, terms) in &page.meta.taxonomies {
|
||||
let slug = taxonomies_slug.get(name);
|
||||
let mut exists = slug.is_some();
|
||||
if let Some(s) = slug {
|
||||
if !taxonomies_def[&page.lang].contains_key(s) {
|
||||
exists = false;
|
||||
}
|
||||
}
|
||||
if !exists {
|
||||
bail!(
|
||||
"Page `{}` has taxonomy `{}` which is not defined in config.toml",
|
||||
page.file.path.display(),
|
||||
name
|
||||
);
|
||||
}
|
||||
let slug = slug.unwrap();
|
||||
|
||||
let taxonomy_found = taxonomies_def.get_mut(&page.lang).unwrap().get_mut(slug).unwrap();
|
||||
for term in terms {
|
||||
taxonomy_found.terms.entry(term).or_insert_with(Vec::new).push(page);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// And now generates the actual taxonomies
|
||||
let mut taxonomies = vec![];
|
||||
for (_, vals) in taxonomies_def {
|
||||
for (_, tax_found) in vals {
|
||||
taxonomies.push(Taxonomy::new(tax_found, config));
|
||||
}
|
||||
}
|
||||
|
||||
Ok(taxonomies)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use config::LanguageOptions;
|
||||
use std::collections::HashMap;
|
||||
use utils::slugs::SlugifyStrategy;
|
||||
|
||||
macro_rules! taxonomies {
|
||||
($config:expr, [$($page:expr),+]) => {{
|
||||
let mut pages = AHashMap::new();
|
||||
$(
|
||||
pages.insert($page.file.path.clone(), $page.clone());
|
||||
)+
|
||||
find_taxonomies(&$config, &pages).unwrap()
|
||||
}};
|
||||
}
|
||||
|
||||
fn create_page(path: &str, lang: &str, taxo: Vec<(&str, Vec<&str>)>) -> Page {
|
||||
let mut page = Page::default();
|
||||
page.file.path = PathBuf::from(path);
|
||||
page.lang = lang.to_owned();
|
||||
let mut taxonomies = HashMap::new();
|
||||
for (name, terms) in taxo {
|
||||
taxonomies.insert(name.to_owned(), terms.iter().map(|t| t.to_string()).collect());
|
||||
}
|
||||
page.meta.taxonomies = taxonomies;
|
||||
page
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn errors_on_unknown_taxonomy() {
|
||||
let config = Config::default_for_test();
|
||||
let page1 = create_page("unknown/taxo.md", "en", vec![("tags", vec!["rust", "db"])]);
|
||||
let mut pages = AHashMap::new();
|
||||
pages.insert(page1.file.path.clone(), page1);
|
||||
let taxonomies = find_taxonomies(&config, &pages);
|
||||
assert!(taxonomies.is_err());
|
||||
let err = taxonomies.unwrap_err();
|
||||
assert_eq!(
|
||||
err.to_string(),
|
||||
"Page `unknown/taxo.md` has taxonomy `tags` which is not defined in config.toml"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn can_make_taxonomies() {
|
||||
let mut config = Config::default_for_test();
|
||||
config.languages.get_mut("en").unwrap().taxonomies = vec![
|
||||
TaxonomyConfig { name: "categories".to_string(), ..TaxonomyConfig::default() },
|
||||
TaxonomyConfig { name: "tags".to_string(), ..TaxonomyConfig::default() },
|
||||
TaxonomyConfig { name: "authors".to_string(), ..TaxonomyConfig::default() },
|
||||
];
|
||||
|
||||
let page1 = create_page(
|
||||
"a.md",
|
||||
"en",
|
||||
vec![("tags", vec!["rust", "db"]), ("categories", vec!["tutorials"])],
|
||||
);
|
||||
let page2 = create_page(
|
||||
"b.md",
|
||||
"en",
|
||||
vec![("tags", vec!["rust", "js"]), ("categories", vec!["others"])],
|
||||
);
|
||||
let page3 = create_page(
|
||||
"c.md",
|
||||
"en",
|
||||
vec![("tags", vec!["js"]), ("authors", vec!["Vincent Prouillet"])],
|
||||
);
|
||||
let taxonomies = taxonomies!(config, [page1, page2, page3]);
|
||||
|
||||
let tags = taxonomies.iter().find(|t| t.kind.name == "tags").unwrap();
|
||||
assert_eq!(tags.len(), 3);
|
||||
assert_eq!(tags.items[0].name, "db");
|
||||
assert_eq!(tags.items[0].permalink, "http://a-website.com/tags/db/");
|
||||
assert_eq!(tags.items[0].pages.len(), 1);
|
||||
assert_eq!(tags.items[1].name, "js");
|
||||
assert_eq!(tags.items[1].permalink, "http://a-website.com/tags/js/");
|
||||
assert_eq!(tags.items[1].pages.len(), 2);
|
||||
assert_eq!(tags.items[2].name, "rust");
|
||||
assert_eq!(tags.items[2].permalink, "http://a-website.com/tags/rust/");
|
||||
assert_eq!(tags.items[2].pages.len(), 2);
|
||||
|
||||
let categories = taxonomies.iter().find(|t| t.kind.name == "categories").unwrap();
|
||||
assert_eq!(categories.items.len(), 2);
|
||||
assert_eq!(categories.items[0].name, "others");
|
||||
assert_eq!(categories.items[0].permalink, "http://a-website.com/categories/others/");
|
||||
assert_eq!(categories.items[0].pages.len(), 1);
|
||||
|
||||
let authors = taxonomies.iter().find(|t| t.kind.name == "authors").unwrap();
|
||||
assert_eq!(authors.items.len(), 1);
|
||||
assert_eq!(authors.items[0].permalink, "http://a-website.com/authors/vincent-prouillet/");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn can_make_multiple_language_taxonomies() {
|
||||
let mut config = Config::default_for_test();
|
||||
config.slugify.taxonomies = SlugifyStrategy::Safe;
|
||||
config.languages.insert("fr".to_owned(), LanguageOptions::default());
|
||||
config.languages.get_mut("en").unwrap().taxonomies = vec![
|
||||
TaxonomyConfig { name: "categories".to_string(), ..TaxonomyConfig::default() },
|
||||
TaxonomyConfig { name: "tags".to_string(), ..TaxonomyConfig::default() },
|
||||
];
|
||||
config.languages.get_mut("fr").unwrap().taxonomies = vec![
|
||||
TaxonomyConfig { name: "catégories".to_string(), ..TaxonomyConfig::default() },
|
||||
TaxonomyConfig { name: "tags".to_string(), ..TaxonomyConfig::default() },
|
||||
];
|
||||
|
||||
let page1 = create_page("a.md", "en", vec![("categories", vec!["rust"])]);
|
||||
let page2 = create_page("b.md", "en", vec![("tags", vec!["rust"])]);
|
||||
let page3 = create_page("c.md", "fr", vec![("catégories", vec!["rust"])]);
|
||||
let taxonomies = taxonomies!(config, [page1, page2, page3]);
|
||||
|
||||
let categories = taxonomies.iter().find(|t| t.kind.name == "categories").unwrap();
|
||||
assert_eq!(categories.len(), 1);
|
||||
assert_eq!(categories.items[0].permalink, "http://a-website.com/categories/rust/");
|
||||
let tags = taxonomies.iter().find(|t| t.kind.name == "tags" && t.lang == "en").unwrap();
|
||||
assert_eq!(tags.len(), 1);
|
||||
assert_eq!(tags.items[0].permalink, "http://a-website.com/tags/rust/");
|
||||
let fr_categories = taxonomies.iter().find(|t| t.kind.name == "catégories").unwrap();
|
||||
assert_eq!(fr_categories.len(), 1);
|
||||
assert_eq!(fr_categories.items[0].permalink, "http://a-website.com/fr/catégories/rust/");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn taxonomies_with_unic_are_grouped_with_default_slugify_strategy() {
|
||||
let mut config = Config::default_for_test();
|
||||
config.languages.get_mut("en").unwrap().taxonomies = vec![
|
||||
TaxonomyConfig { name: "test-taxonomy".to_string(), ..TaxonomyConfig::default() },
|
||||
TaxonomyConfig { name: "test taxonomy".to_string(), ..TaxonomyConfig::default() },
|
||||
TaxonomyConfig { name: "test-taxonomy ".to_string(), ..TaxonomyConfig::default() },
|
||||
TaxonomyConfig { name: "Test-Taxonomy ".to_string(), ..TaxonomyConfig::default() },
|
||||
];
|
||||
let page1 = create_page("a.md", "en", vec![("test-taxonomy", vec!["Ecole"])]);
|
||||
let page2 = create_page("b.md", "en", vec![("test taxonomy", vec!["École"])]);
|
||||
let page3 = create_page("c.md", "en", vec![("test-taxonomy ", vec!["ecole"])]);
|
||||
let page4 = create_page("d.md", "en", vec![("Test-Taxonomy ", vec!["école"])]);
|
||||
let taxonomies = taxonomies!(config, [page1, page2, page3, page4]);
|
||||
assert_eq!(taxonomies.len(), 1);
|
||||
|
||||
let tax = &taxonomies[0];
|
||||
// under the default slugify strategy all of the provided terms should be the same
|
||||
assert_eq!(tax.items.len(), 1);
|
||||
let term1 = &tax.items[0];
|
||||
assert_eq!(term1.name, "Ecole");
|
||||
assert_eq!(term1.slug, "ecole");
|
||||
assert_eq!(term1.permalink, "http://a-website.com/test-taxonomy/ecole/");
|
||||
assert_eq!(term1.pages.len(), 4);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn taxonomies_with_unic_are_not_grouped_with_safe_slugify_strategy() {
|
||||
let mut config = Config::default_for_test();
|
||||
config.slugify.taxonomies = SlugifyStrategy::Safe;
|
||||
config.languages.get_mut("en").unwrap().taxonomies =
|
||||
vec![TaxonomyConfig { name: "test".to_string(), ..TaxonomyConfig::default() }];
|
||||
let page1 = create_page("a.md", "en", vec![("test", vec!["Ecole"])]);
|
||||
let page2 = create_page("b.md", "en", vec![("test", vec!["École"])]);
|
||||
let page3 = create_page("c.md", "en", vec![("test", vec!["ecole"])]);
|
||||
let page4 = create_page("d.md", "en", vec![("test", vec!["école"])]);
|
||||
let taxonomies = taxonomies!(config, [page1, page2, page3, page4]);
|
||||
assert_eq!(taxonomies.len(), 1);
|
||||
let tax = &taxonomies[0];
|
||||
// under the safe slugify strategy all terms should be distinct
|
||||
assert_eq!(tax.items.len(), 4);
|
||||
}
|
||||
}
|
||||
|
||||
@ -99,7 +99,7 @@ impl Site {
|
||||
permalinks: HashMap::new(),
|
||||
include_drafts: false,
|
||||
// We will allocate it properly later on
|
||||
library: Arc::new(RwLock::new(Library::new())),
|
||||
library: Arc::new(RwLock::new(Library::default())),
|
||||
build_mode: BuildMode::Disk,
|
||||
shortcode_definitions,
|
||||
};
|
||||
@ -164,7 +164,7 @@ impl Site {
|
||||
pub fn load(&mut self) -> Result<()> {
|
||||
let base_path = self.base_path.to_string_lossy().replace('\\', "/");
|
||||
|
||||
self.library = Arc::new(RwLock::new(Library::new()));
|
||||
self.library = Arc::new(RwLock::new(Library::new(&self.config)));
|
||||
let mut pages_insert_anchors = HashMap::new();
|
||||
|
||||
// not the most elegant loop, but this is necessary to use skip_current_dir
|
||||
@ -397,6 +397,16 @@ impl Site {
|
||||
/// Add a page to the site
|
||||
/// The `render` parameter is used in the serve command with --fast, when rebuilding a page.
|
||||
pub fn add_page(&mut self, mut page: Page, render_md: bool) -> Result<()> {
|
||||
for (taxa_name, _) in &page.meta.taxonomies {
|
||||
if !self.config.has_taxonomy(taxa_name, &page.lang) {
|
||||
bail!(
|
||||
"Page `{}` has taxonomy `{}` which is not defined in config.toml",
|
||||
page.file.path.display(),
|
||||
taxa_name
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
self.permalinks.insert(page.file.relative.clone(), page.permalink.clone());
|
||||
if render_md {
|
||||
let insert_anchor =
|
||||
@ -490,7 +500,7 @@ impl Site {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
self.taxonomies = self.library.read().unwrap().find_taxonomies(&self.config)?;
|
||||
self.taxonomies = self.library.read().unwrap().find_taxonomies(&self.config);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@ -2,10 +2,12 @@ mod common;
|
||||
|
||||
use std::collections::HashMap;
|
||||
use std::env;
|
||||
use std::path::Path;
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
use common::{build_site, build_site_with_setup};
|
||||
use config::TaxonomyConfig;
|
||||
use content::Page;
|
||||
use libs::ahash::AHashMap;
|
||||
use site::sitemap;
|
||||
use site::Site;
|
||||
|
||||
@ -98,6 +100,21 @@ fn can_parse_site() {
|
||||
assert_eq!(Some(&prog_section.meta.extra), sitemap_entry.extra);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn errors_on_unknown_taxonomies() {
|
||||
let (mut site, _, _) = build_site("test_site");
|
||||
let mut page = Page::default();
|
||||
page.file.path = PathBuf::from("unknown/taxo.md");
|
||||
page.meta.taxonomies.insert("wrong".to_string(), vec![]);
|
||||
let res = site.add_page(page, false);
|
||||
assert!(res.is_err());
|
||||
let err = res.unwrap_err();
|
||||
assert_eq!(
|
||||
err.to_string(),
|
||||
"Page `unknown/taxo.md` has taxonomy `wrong` which is not defined in config.toml"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn can_build_site_without_live_reload() {
|
||||
let (_, _tmp_dir, public) = build_site("test_site");
|
||||
@ -268,8 +285,11 @@ fn can_build_site_with_taxonomies() {
|
||||
let (site, _tmp_dir, public) = build_site_with_setup("test_site", |mut site| {
|
||||
site.load().unwrap();
|
||||
{
|
||||
let mut library = site.library.write().unwrap();
|
||||
for (i, (_, page)) in library.pages.iter_mut().enumerate() {
|
||||
let library = &mut *site.library.write().unwrap();
|
||||
let mut pages = vec![];
|
||||
|
||||
let pages_data = std::mem::replace(&mut library.pages, AHashMap::new());
|
||||
for (i, (_, mut page)) in pages_data.into_iter().enumerate() {
|
||||
page.meta.taxonomies = {
|
||||
let mut taxonomies = HashMap::new();
|
||||
taxonomies.insert(
|
||||
@ -278,6 +298,10 @@ fn can_build_site_with_taxonomies() {
|
||||
);
|
||||
taxonomies
|
||||
};
|
||||
pages.push(page);
|
||||
}
|
||||
for p in pages {
|
||||
library.insert_page(p);
|
||||
}
|
||||
}
|
||||
site.populate_taxonomies().unwrap();
|
||||
@ -543,6 +567,7 @@ fn can_build_site_with_pagination_for_taxonomy() {
|
||||
let (_, _tmp_dir, public) = build_site_with_setup("test_site", |mut site| {
|
||||
site.config.languages.get_mut("en").unwrap().taxonomies.push(TaxonomyConfig {
|
||||
name: "tags".to_string(),
|
||||
slug: "tags".to_string(),
|
||||
paginate_by: Some(2),
|
||||
paginate_path: None,
|
||||
render: true,
|
||||
@ -550,9 +575,11 @@ fn can_build_site_with_pagination_for_taxonomy() {
|
||||
});
|
||||
site.load().unwrap();
|
||||
{
|
||||
let mut library = site.library.write().unwrap();
|
||||
let library = &mut *site.library.write().unwrap();
|
||||
let mut pages = vec![];
|
||||
|
||||
for (i, (_, page)) in library.pages.iter_mut().enumerate() {
|
||||
let pages_data = std::mem::replace(&mut library.pages, AHashMap::new());
|
||||
for (i, (_, mut page)) in pages_data.into_iter().enumerate() {
|
||||
page.meta.taxonomies = {
|
||||
let mut taxonomies = HashMap::new();
|
||||
taxonomies.insert(
|
||||
@ -561,6 +588,10 @@ fn can_build_site_with_pagination_for_taxonomy() {
|
||||
);
|
||||
taxonomies
|
||||
};
|
||||
pages.push(page);
|
||||
}
|
||||
for p in pages {
|
||||
library.insert_page(p);
|
||||
}
|
||||
}
|
||||
site.populate_taxonomies().unwrap();
|
||||
|
||||
@ -190,12 +190,13 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn can_get_taxonomy() {
|
||||
let mut config = Config::default();
|
||||
let mut config = Config::default_for_test();
|
||||
config.slugify.taxonomies = SlugifyStrategy::On;
|
||||
let taxo_config = TaxonomyConfig { name: "tags".to_string(), ..TaxonomyConfig::default() };
|
||||
let taxo_config_fr =
|
||||
TaxonomyConfig { name: "tags".to_string(), ..TaxonomyConfig::default() };
|
||||
let library = Arc::new(RwLock::new(Library::new()));
|
||||
config.slugify_taxonomies();
|
||||
let library = Arc::new(RwLock::new(Library::new(&config)));
|
||||
let tag = TaxonomyTerm::new("Programming", &config.default_language, "tags", &[], &config);
|
||||
let tag_fr = TaxonomyTerm::new("Programmation", "fr", "tags", &[], &config);
|
||||
let tags = Taxonomy {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user