Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Issue 2378 lang level feed config #2400

Open
wants to merge 10 commits into
base: next
Choose a base branch
from
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
# Changelog

## 0.20.0 (unreleased)

- Fix some of YAML date parsing
- Fix feed generation for languages not working in some cases (it was taking the value from the root of the config for
feed_filenames)
- Ignore `.bck` files in `zola serve`

## 0.19.1 (2024-06-24)

- Fix `config.generate_feeds` being still serialized as `config.generate_feed`. Both are available for now
Expand Down
4 changes: 1 addition & 3 deletions components/site/src/feeds.rs
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ pub fn render_feeds(
context.insert("lang", lang);

let mut feeds = Vec::new();
for feed_filename in &site.config.feed_filenames {
for feed_filename in &site.config.languages[lang].feed_filenames {
let mut context = context.clone();

let feed_url = if let Some(base) = base_path {
Expand All @@ -85,9 +85,7 @@ pub fn render_feeds(
};

context.insert("feed_url", &feed_url);

context = additional_context_fn(context);

feeds.push(render_template(feed_filename, &site.tera, context, &site.config.theme)?);
}

Expand Down
258 changes: 236 additions & 22 deletions components/site/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -744,27 +744,7 @@ impl Site {
start = log_time(start, "Rendered orphan pages");
self.render_sitemap()?;
start = log_time(start, "Rendered sitemap");

let library = self.library.read().unwrap();
if self.config.generate_feeds {
let is_multilingual = self.config.is_multilingual();
let pages: Vec<_> = if is_multilingual {
library.pages.values().filter(|p| p.lang == self.config.default_language).collect()
} else {
library.pages.values().collect()
};
self.render_feeds(pages, None, &self.config.default_language, |c| c)?;
start = log_time(start, "Generated feed in default language");
}

for (code, language) in &self.config.other_languages() {
if !language.generate_feeds {
continue;
}
let pages: Vec<_> = library.pages.values().filter(|p| &p.lang == code).collect();
self.render_feeds(pages, Some(&PathBuf::from(code)), code, |c| c)?;
start = log_time(start, "Generated feed in other language");
}
self.render_all_feeds()?;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This name is not accurate since there are also section feeds. render_site_feeds?

self.render_themes_css()?;
start = log_time(start, "Rendered themes css");
self.render_404()?;
Expand All @@ -784,6 +764,41 @@ impl Site {
Ok(())
}

pub fn render_all_feeds(&self) -> Result<()> {
let mut start = Instant::now();
let library = self.library.read().unwrap();

for (code, language) in &self.config.languages {
let is_default_language = code == &self.config.default_language;

let skip_default_language_feed_generation =
is_default_language && !self.config.generate_feeds && !language.generate_feeds;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should that ambiguity be resolved when loading the config? Eg error if the value at the top level and in a default language level differs.
This way you can now just do something like self.config.languages.iter().filter(|x| x.generate_feeds) for the loop

let skip_other_language_feed_generation =
!is_default_language && !language.generate_feeds;
if skip_default_language_feed_generation || skip_other_language_feed_generation {
continue;
}

let pages: Vec<_> = if is_default_language && !self.config.is_multilingual() {
library.pages.values().collect()
} else {
library.pages.values().filter(|p| &p.lang == code).collect()
};

let code_path = PathBuf::from(code);
let base_path = if is_default_language { None } else { Some(&code_path) };

self.render_feeds(pages, base_path, code, |c| c)?;
let debug_message = if is_default_language {
"Generated feed in default language"
} else {
"Generated feed in other language"
};
start = log_time(start, debug_message);
}
Ok(())
}

pub fn render_themes_css(&self) -> Result<()> {
let themes = &self.config.markdown.highlight_themes_css;

Expand Down Expand Up @@ -1046,7 +1061,9 @@ impl Site {
None => return Ok(()),
};

for (feed, feed_filename) in feeds.into_iter().zip(self.config.feed_filenames.iter()) {
for (feed, feed_filename) in
feeds.into_iter().zip(self.config.languages[lang].feed_filenames.iter())
{
if let Some(base) = base_path {
let mut components = Vec::new();
for component in base.components() {
Expand Down Expand Up @@ -1209,3 +1226,200 @@ fn log_time(start: Instant, message: &str) -> Instant {
}
now
}

#[cfg(test)]
mod tests {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can those be moved in the tests folders? This file is already long enough without tests

use super::*;
use config::Config;
use content::{FileInfo, Library, Page};
use tempfile::{tempdir, TempDir};

fn create_page(title: &str, file_path: &str, lang: &str, config: &Config) -> Page {
let mut page = Page { lang: lang.to_owned(), ..Page::default() };
page.file = FileInfo::new_page(
Path::new(format!("/test/base/path/{}", file_path).as_str()),
&PathBuf::new(),
);
page.meta.title = Some(title.to_string());
page.meta.date = Some("2016-03-01".to_string());
page.meta.weight = Some(1);
page.lang = lang.to_string();
page.file.find_language(&config.default_language, &config.other_languages_codes()).unwrap();
page.permalink = config.make_permalink(file_path);
page
}

fn create_site_from_config_and_pages(
config_raw: &str,
pages: &[(&str, &str, &str)],
) -> (TempDir, Site) {
let config = Config::parse(config_raw).unwrap();
let mut library = Library::default();
for (t, f, l) in pages {
library.insert_page(create_page(t, f, l, &config));
}

let tmp_dir = tempdir().unwrap();
let path = tmp_dir.path();
let public_dir = path.join("public");
let site = Site {
config: config.clone(),
library: Arc::new(RwLock::new(library)),
base_path: path.into(),
tera: load_tera(path, &config).unwrap(),
imageproc: Arc::new(Mutex::new(imageproc::Processor::new(path.to_path_buf(), &config))),
live_reload: None,
output_path: public_dir.clone(),
content_path: path.into(),
sass_path: path.into(),
static_path: path.into(),
templates_path: path.into(),
taxonomies: vec![],
permalinks: HashMap::new(),
include_drafts: false,
build_mode: BuildMode::Disk,
shortcode_definitions: HashMap::new(),
};
site.render_all_feeds().unwrap();
(tmp_dir, site)
}

#[test]
fn can_render_feed_for_single_language_with_global_feed_flag() {
let config_raw = r#"
title = "My site"
base_url = "https://replace-this-with-your-url.com"
generate_feeds = true

"#;
let pages = vec![("My En Article", "content/my-article.md", "en")];

let (tmp_dir, site) = create_site_from_config_and_pages(config_raw, &pages);
let public_dir = site.output_path;

assert!(tmp_dir.path().exists());
assert!(public_dir.exists());
assert!(public_dir.join("atom.xml").exists());
assert!(std::fs::read_to_string(public_dir.join("atom.xml"))
.unwrap()
.contains("My En Article"));
}

#[test]
fn can_render_feed_for_multi_language_with_language_level_feed_flag() {
let config_raw = r#"
base_url = "https://replace-this-with-your-url.com"
default_language = "en"

[languages.en]
title = "My English site"
generate_feeds = true

[languages.fr]
title = "My French site"
generate_feeds = false

"#;
let pages = vec![
("My En Article", "content/my-article.md", "en"),
("My Fr Article", "content/my-article.fr.md", "fr"),
];

let (tmp_dir, site) = create_site_from_config_and_pages(config_raw, &pages);
let public_dir = site.output_path;

assert!(tmp_dir.path().exists());
assert!(public_dir.exists());
assert!(public_dir.join("atom.xml").exists());
assert!(std::fs::read_to_string(public_dir.join("atom.xml"))
.unwrap()
.contains("My En Article"));
assert!(!std::fs::read_to_string(public_dir.join("atom.xml"))
.unwrap()
.contains("My Fr Article"));
}

#[test]
fn can_render_feed_for_multi_language_with_language_level_feed_flag_and_feed_files() {
let config_raw = r#"
base_url = "https://replace-this-with-your-url.com"
default_language = "en"

[languages.en]
title = "My English site"
generate_feeds = true

[languages.fr]
title = "My French site"
generate_feeds = true
feed_filenames = ["rss.xml"]

"#;
let pages = vec![
("My En Article", "content/my-article.md", "en"),
("My Fr Article", "content/my-article.fr.md", "fr"),
];

let (tmp_dir, site) = create_site_from_config_and_pages(config_raw, &pages);
let public_dir = site.output_path;

assert!(tmp_dir.path().exists());
assert!(public_dir.exists());
assert!(public_dir.join("atom.xml").exists());
assert!(public_dir.join("fr").join("rss.xml").exists());
assert!(std::fs::read_to_string(public_dir.join("atom.xml"))
.unwrap()
.contains("My En Article"));
assert!(!std::fs::read_to_string(public_dir.join("atom.xml"))
.unwrap()
.contains("My Fr Article"));
assert!(!std::fs::read_to_string(public_dir.join("fr").join("rss.xml"))
.unwrap()
.contains("My En Article"));
assert!(std::fs::read_to_string(public_dir.join("fr").join("rss.xml"))
.unwrap()
.contains("My Fr Article"));
}

#[test]
fn can_render_feed_for_multi_language_with_language_level_feed_flag_preferred_for_default() {
let config_raw = r#"
base_url = "https://replace-this-with-your-url.com"
default_language = "en"
generate_feeds = false

[languages.en]
title = "My English site"
generate_feeds = true

[languages.fr]
title = "My French site"
generate_feeds = true

"#;
let pages = vec![
("My En Article", "content/my-article.md", "en"),
("My Fr Article", "content/my-article.fr.md", "fr"),
];

let (tmp_dir, site) = create_site_from_config_and_pages(config_raw, &pages);
let public_dir = site.output_path;

assert!(tmp_dir.path().exists());
assert!(public_dir.exists());
assert!(public_dir.join("atom.xml").exists());
assert!(public_dir.join("fr").join("atom.xml").exists());
assert!(std::fs::read_to_string(public_dir.join("atom.xml"))
.unwrap()
.contains("My En Article"));
assert!(!std::fs::read_to_string(public_dir.join("atom.xml"))
.unwrap()
.contains("My Fr Article"));
assert!(!std::fs::read_to_string(public_dir.join("fr").join("atom.xml"))
.unwrap()
.contains("My En Article"));
assert!(std::fs::read_to_string(public_dir.join("fr").join("atom.xml"))
.unwrap()
.contains("My Fr Article"));
}
}
Loading