mirror of
https://github.com/getzola/zola
synced 2024-12-12 05:12:29 +00:00
Added support for multiple feeds (i.e. generating both Atom and RSS) (#2477)
* Added support for multiple feeds * Implemented backwards-compatibility for feed config * Added a test for feed config backwards-compat, fixed bugs - Fixed language config merge bug found by a test - Adjusted two existing tests to fully check stuff related to multiple feeds - Added a new test for backwards-compatibility of the changes - Fixed bugs found by the newly added test * Renamed MightBeSingle to SingleOrVec * Made the multiple feeds config changes "loudly" backwards-incompatible * added #[serde(deny_unknown_fields)] to front-matter, fixed problems this found in tests
This commit is contained in:
parent
7f59792d50
commit
c054ed1b10
23 changed files with 163 additions and 128 deletions
|
@ -11,6 +11,10 @@
|
||||||
- Add `render = false` capability to pages
|
- Add `render = false` capability to pages
|
||||||
- Handle string dates in YAML front-matter
|
- Handle string dates in YAML front-matter
|
||||||
- Add support for fuse.js search format
|
- Add support for fuse.js search format
|
||||||
|
- Added support for generating multiple kinds of feeds at once
|
||||||
|
- Changed config options named `generate_feed` to `generate_feeds` (both in config.toml and in section front-matter)
|
||||||
|
- Changed config option `feed_filename: String` to `feed_filenames: Vec<String>`
|
||||||
|
- The config file no longer allows arbitrary fields outside the `[extra]` section
|
||||||
|
|
||||||
## 0.18.0 (2023-12-18)
|
## 0.18.0 (2023-12-18)
|
||||||
|
|
||||||
|
|
|
@ -8,17 +8,17 @@ use crate::config::search;
|
||||||
use crate::config::taxonomies;
|
use crate::config::taxonomies;
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
|
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
|
||||||
#[serde(default)]
|
#[serde(default, deny_unknown_fields)]
|
||||||
pub struct LanguageOptions {
|
pub struct LanguageOptions {
|
||||||
/// Title of the site. Defaults to None
|
/// Title of the site. Defaults to None
|
||||||
pub title: Option<String>,
|
pub title: Option<String>,
|
||||||
/// Description of the site. Defaults to None
|
/// Description of the site. Defaults to None
|
||||||
pub description: Option<String>,
|
pub description: Option<String>,
|
||||||
/// Whether to generate a feed for that language, defaults to `false`
|
/// Whether to generate feeds for that language, defaults to `false`
|
||||||
pub generate_feed: bool,
|
pub generate_feeds: bool,
|
||||||
/// The filename to use for feeds. Used to find the template, too.
|
/// The filenames to use for feeds. Used to find the templates, too.
|
||||||
/// Defaults to "atom.xml", with "rss.xml" also having a template provided out of the box.
|
/// Defaults to ["atom.xml"], with "rss.xml" also having a template provided out of the box.
|
||||||
pub feed_filename: String,
|
pub feed_filenames: Vec<String>,
|
||||||
pub taxonomies: Vec<taxonomies::TaxonomyConfig>,
|
pub taxonomies: Vec<taxonomies::TaxonomyConfig>,
|
||||||
/// Whether to generate search index for that language, defaults to `false`
|
/// Whether to generate search index for that language, defaults to `false`
|
||||||
pub build_search_index: bool,
|
pub build_search_index: bool,
|
||||||
|
@ -66,9 +66,10 @@ impl LanguageOptions {
|
||||||
merge_field!(self.title, other.title, "title");
|
merge_field!(self.title, other.title, "title");
|
||||||
merge_field!(self.description, other.description, "description");
|
merge_field!(self.description, other.description, "description");
|
||||||
merge_field!(
|
merge_field!(
|
||||||
self.feed_filename == "atom.xml",
|
self.feed_filenames.is_empty()
|
||||||
self.feed_filename,
|
|| self.feed_filenames == LanguageOptions::default().feed_filenames,
|
||||||
other.feed_filename,
|
self.feed_filenames,
|
||||||
|
other.feed_filenames,
|
||||||
"feed_filename"
|
"feed_filename"
|
||||||
);
|
);
|
||||||
merge_field!(self.taxonomies.is_empty(), self.taxonomies, other.taxonomies, "taxonomies");
|
merge_field!(self.taxonomies.is_empty(), self.taxonomies, other.taxonomies, "taxonomies");
|
||||||
|
@ -79,7 +80,7 @@ impl LanguageOptions {
|
||||||
"translations"
|
"translations"
|
||||||
);
|
);
|
||||||
|
|
||||||
self.generate_feed = self.generate_feed || other.generate_feed;
|
self.generate_feeds = self.generate_feeds || other.generate_feeds;
|
||||||
self.build_search_index = self.build_search_index || other.build_search_index;
|
self.build_search_index = self.build_search_index || other.build_search_index;
|
||||||
|
|
||||||
if self.search == search::Search::default() {
|
if self.search == search::Search::default() {
|
||||||
|
@ -101,8 +102,8 @@ impl Default for LanguageOptions {
|
||||||
LanguageOptions {
|
LanguageOptions {
|
||||||
title: None,
|
title: None,
|
||||||
description: None,
|
description: None,
|
||||||
generate_feed: false,
|
generate_feeds: false,
|
||||||
feed_filename: "atom.xml".to_string(),
|
feed_filenames: vec!["atom.xml".to_string()],
|
||||||
taxonomies: vec![],
|
taxonomies: vec![],
|
||||||
build_search_index: false,
|
build_search_index: false,
|
||||||
search: search::Search::default(),
|
search: search::Search::default(),
|
||||||
|
@ -129,8 +130,8 @@ mod tests {
|
||||||
let mut base_default_language_options = LanguageOptions {
|
let mut base_default_language_options = LanguageOptions {
|
||||||
title: Some("Site's title".to_string()),
|
title: Some("Site's title".to_string()),
|
||||||
description: None,
|
description: None,
|
||||||
generate_feed: true,
|
generate_feeds: true,
|
||||||
feed_filename: "atom.xml".to_string(),
|
feed_filenames: vec!["atom.xml".to_string()],
|
||||||
taxonomies: vec![],
|
taxonomies: vec![],
|
||||||
build_search_index: true,
|
build_search_index: true,
|
||||||
search: search::Search::default(),
|
search: search::Search::default(),
|
||||||
|
@ -140,8 +141,8 @@ mod tests {
|
||||||
let section_default_language_options = LanguageOptions {
|
let section_default_language_options = LanguageOptions {
|
||||||
title: None,
|
title: None,
|
||||||
description: Some("Site's description".to_string()),
|
description: Some("Site's description".to_string()),
|
||||||
generate_feed: false,
|
generate_feeds: false,
|
||||||
feed_filename: "rss.xml".to_string(),
|
feed_filenames: vec!["rss.xml".to_string()],
|
||||||
taxonomies: vec![],
|
taxonomies: vec![],
|
||||||
build_search_index: true,
|
build_search_index: true,
|
||||||
search: search::Search::default(),
|
search: search::Search::default(),
|
||||||
|
@ -156,8 +157,8 @@ mod tests {
|
||||||
let mut base_default_language_options = LanguageOptions {
|
let mut base_default_language_options = LanguageOptions {
|
||||||
title: Some("Site's title".to_string()),
|
title: Some("Site's title".to_string()),
|
||||||
description: Some("Duplicate site description".to_string()),
|
description: Some("Duplicate site description".to_string()),
|
||||||
generate_feed: true,
|
generate_feeds: true,
|
||||||
feed_filename: "".to_string(),
|
feed_filenames: vec![],
|
||||||
taxonomies: vec![],
|
taxonomies: vec![],
|
||||||
build_search_index: true,
|
build_search_index: true,
|
||||||
search: search::Search::default(),
|
search: search::Search::default(),
|
||||||
|
@ -167,8 +168,8 @@ mod tests {
|
||||||
let section_default_language_options = LanguageOptions {
|
let section_default_language_options = LanguageOptions {
|
||||||
title: None,
|
title: None,
|
||||||
description: Some("Site's description".to_string()),
|
description: Some("Site's description".to_string()),
|
||||||
generate_feed: false,
|
generate_feeds: false,
|
||||||
feed_filename: "Some feed_filename".to_string(),
|
feed_filenames: vec!["Some feed_filename".to_string()],
|
||||||
taxonomies: vec![],
|
taxonomies: vec![],
|
||||||
build_search_index: true,
|
build_search_index: true,
|
||||||
search: search::Search::default(),
|
search: search::Search::default(),
|
||||||
|
|
|
@ -30,7 +30,7 @@ pub enum Mode {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, Deserialize)]
|
#[derive(Clone, Debug, Deserialize)]
|
||||||
#[serde(default)]
|
#[serde(default, deny_unknown_fields)]
|
||||||
pub struct Config {
|
pub struct Config {
|
||||||
/// Base URL of the site, the only required config argument
|
/// Base URL of the site, the only required config argument
|
||||||
pub base_url: String,
|
pub base_url: String,
|
||||||
|
@ -49,13 +49,13 @@ pub struct Config {
|
||||||
/// The translations strings for the default language
|
/// The translations strings for the default language
|
||||||
translations: HashMap<String, String>,
|
translations: HashMap<String, String>,
|
||||||
|
|
||||||
/// Whether to generate a feed. Defaults to false.
|
/// Whether to generate feeds. Defaults to false.
|
||||||
pub generate_feed: bool,
|
pub generate_feeds: bool,
|
||||||
/// The number of articles to include in the feed. Defaults to including all items.
|
/// The number of articles to include in the feed. Defaults to including all items.
|
||||||
pub feed_limit: Option<usize>,
|
pub feed_limit: Option<usize>,
|
||||||
/// The filename to use for feeds. Used to find the template, too.
|
/// The filenames to use for feeds. Used to find the templates, too.
|
||||||
/// Defaults to "atom.xml", with "rss.xml" also having a template provided out of the box.
|
/// Defaults to ["atom.xml"], with "rss.xml" also having a template provided out of the box.
|
||||||
pub feed_filename: String,
|
pub feed_filenames: Vec<String>,
|
||||||
/// If set, files from static/ will be hardlinked instead of copied to the output dir.
|
/// If set, files from static/ will be hardlinked instead of copied to the output dir.
|
||||||
pub hard_link_static: bool,
|
pub hard_link_static: bool,
|
||||||
pub taxonomies: Vec<taxonomies::TaxonomyConfig>,
|
pub taxonomies: Vec<taxonomies::TaxonomyConfig>,
|
||||||
|
@ -109,7 +109,7 @@ pub struct SerializedConfig<'a> {
|
||||||
languages: HashMap<&'a String, &'a languages::LanguageOptions>,
|
languages: HashMap<&'a String, &'a languages::LanguageOptions>,
|
||||||
default_language: &'a str,
|
default_language: &'a str,
|
||||||
generate_feed: bool,
|
generate_feed: bool,
|
||||||
feed_filename: &'a str,
|
feed_filenames: &'a [String],
|
||||||
taxonomies: &'a [taxonomies::TaxonomyConfig],
|
taxonomies: &'a [taxonomies::TaxonomyConfig],
|
||||||
author: &'a Option<String>,
|
author: &'a Option<String>,
|
||||||
build_search_index: bool,
|
build_search_index: bool,
|
||||||
|
@ -183,12 +183,14 @@ impl Config {
|
||||||
|
|
||||||
/// Makes a url, taking into account that the base url might have a trailing slash
|
/// Makes a url, taking into account that the base url might have a trailing slash
|
||||||
pub fn make_permalink(&self, path: &str) -> String {
|
pub fn make_permalink(&self, path: &str) -> String {
|
||||||
let trailing_bit =
|
let trailing_bit = if path.ends_with('/')
|
||||||
if path.ends_with('/') || path.ends_with(&self.feed_filename) || path.is_empty() {
|
|| self.feed_filenames.iter().any(|feed_filename| path.ends_with(feed_filename))
|
||||||
""
|
|| path.is_empty()
|
||||||
} else {
|
{
|
||||||
"/"
|
""
|
||||||
};
|
} else {
|
||||||
|
"/"
|
||||||
|
};
|
||||||
|
|
||||||
// Index section with a base url that has a trailing slash
|
// Index section with a base url that has a trailing slash
|
||||||
if self.base_url.ends_with('/') && path == "/" {
|
if self.base_url.ends_with('/') && path == "/" {
|
||||||
|
@ -212,8 +214,8 @@ impl Config {
|
||||||
let mut base_language_options = languages::LanguageOptions {
|
let mut base_language_options = languages::LanguageOptions {
|
||||||
title: self.title.clone(),
|
title: self.title.clone(),
|
||||||
description: self.description.clone(),
|
description: self.description.clone(),
|
||||||
generate_feed: self.generate_feed,
|
generate_feeds: self.generate_feeds,
|
||||||
feed_filename: self.feed_filename.clone(),
|
feed_filenames: self.feed_filenames.clone(),
|
||||||
build_search_index: self.build_search_index,
|
build_search_index: self.build_search_index,
|
||||||
taxonomies: self.taxonomies.clone(),
|
taxonomies: self.taxonomies.clone(),
|
||||||
search: self.search.clone(),
|
search: self.search.clone(),
|
||||||
|
@ -320,8 +322,8 @@ impl Config {
|
||||||
description: &options.description,
|
description: &options.description,
|
||||||
languages: self.languages.iter().filter(|(k, _)| k.as_str() != lang).collect(),
|
languages: self.languages.iter().filter(|(k, _)| k.as_str() != lang).collect(),
|
||||||
default_language: &self.default_language,
|
default_language: &self.default_language,
|
||||||
generate_feed: options.generate_feed,
|
generate_feed: options.generate_feeds,
|
||||||
feed_filename: &options.feed_filename,
|
feed_filenames: &options.feed_filenames,
|
||||||
taxonomies: &options.taxonomies,
|
taxonomies: &options.taxonomies,
|
||||||
author: &self.author,
|
author: &self.author,
|
||||||
build_search_index: options.build_search_index,
|
build_search_index: options.build_search_index,
|
||||||
|
@ -369,9 +371,9 @@ impl Default for Config {
|
||||||
theme: None,
|
theme: None,
|
||||||
default_language: "en".to_string(),
|
default_language: "en".to_string(),
|
||||||
languages: HashMap::new(),
|
languages: HashMap::new(),
|
||||||
generate_feed: false,
|
generate_feeds: false,
|
||||||
feed_limit: None,
|
feed_limit: None,
|
||||||
feed_filename: "atom.xml".to_string(),
|
feed_filenames: vec!["atom.xml".to_string()],
|
||||||
hard_link_static: false,
|
hard_link_static: false,
|
||||||
taxonomies: Vec::new(),
|
taxonomies: Vec::new(),
|
||||||
author: None,
|
author: None,
|
||||||
|
@ -428,8 +430,8 @@ mod tests {
|
||||||
languages::LanguageOptions {
|
languages::LanguageOptions {
|
||||||
title: None,
|
title: None,
|
||||||
description: description_lang_section.clone(),
|
description: description_lang_section.clone(),
|
||||||
generate_feed: true,
|
generate_feeds: true,
|
||||||
feed_filename: config.feed_filename.clone(),
|
feed_filenames: config.feed_filenames.clone(),
|
||||||
taxonomies: config.taxonomies.clone(),
|
taxonomies: config.taxonomies.clone(),
|
||||||
build_search_index: false,
|
build_search_index: false,
|
||||||
search: search::Search::default(),
|
search: search::Search::default(),
|
||||||
|
@ -456,8 +458,8 @@ mod tests {
|
||||||
languages::LanguageOptions {
|
languages::LanguageOptions {
|
||||||
title: title_lang_section.clone(),
|
title: title_lang_section.clone(),
|
||||||
description: None,
|
description: None,
|
||||||
generate_feed: true,
|
generate_feeds: true,
|
||||||
feed_filename: config.feed_filename.clone(),
|
feed_filenames: config.feed_filenames.clone(),
|
||||||
taxonomies: config.taxonomies.clone(),
|
taxonomies: config.taxonomies.clone(),
|
||||||
build_search_index: false,
|
build_search_index: false,
|
||||||
search: search::Search::default(),
|
search: search::Search::default(),
|
||||||
|
@ -976,4 +978,16 @@ author = "person@example.com (Some Person)"
|
||||||
let config = Config::parse(config).unwrap();
|
let config = Config::parse(config).unwrap();
|
||||||
assert_eq!(config.author, Some("person@example.com (Some Person)".to_owned()))
|
assert_eq!(config.author, Some("person@example.com (Some Person)".to_owned()))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[should_panic]
|
||||||
|
fn test_backwards_incompatibility_for_feeds() {
|
||||||
|
let config = r#"
|
||||||
|
base_url = "example.com"
|
||||||
|
generate_feed = true
|
||||||
|
feed_filename = "test.xml"
|
||||||
|
"#;
|
||||||
|
|
||||||
|
Config::parse(config).unwrap();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,7 +12,7 @@ static DEFAULT_PAGINATE_PATH: &str = "page";
|
||||||
|
|
||||||
/// The front matter of every section
|
/// The front matter of every section
|
||||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||||
#[serde(default)]
|
#[serde(default, deny_unknown_fields)]
|
||||||
pub struct SectionFrontMatter {
|
pub struct SectionFrontMatter {
|
||||||
/// <title> of the page
|
/// <title> of the page
|
||||||
pub title: Option<String>,
|
pub title: Option<String>,
|
||||||
|
@ -69,7 +69,7 @@ pub struct SectionFrontMatter {
|
||||||
pub aliases: Vec<String>,
|
pub aliases: Vec<String>,
|
||||||
/// Whether to generate a feed for the current section
|
/// Whether to generate a feed for the current section
|
||||||
#[serde(skip_serializing)]
|
#[serde(skip_serializing)]
|
||||||
pub generate_feed: bool,
|
pub generate_feeds: bool,
|
||||||
/// Any extra parameter present in the front matter
|
/// Any extra parameter present in the front matter
|
||||||
pub extra: Map<String, Value>,
|
pub extra: Map<String, Value>,
|
||||||
}
|
}
|
||||||
|
@ -113,7 +113,7 @@ impl Default for SectionFrontMatter {
|
||||||
transparent: false,
|
transparent: false,
|
||||||
page_template: None,
|
page_template: None,
|
||||||
aliases: Vec::new(),
|
aliases: Vec::new(),
|
||||||
generate_feed: false,
|
generate_feeds: false,
|
||||||
extra: Map::new(),
|
extra: Map::new(),
|
||||||
draft: false,
|
draft: false,
|
||||||
}
|
}
|
||||||
|
|
|
@ -283,7 +283,7 @@ mod tests {
|
||||||
create_dir_all(path.join(&article_path).join("foo/baz/quux"))
|
create_dir_all(path.join(&article_path).join("foo/baz/quux"))
|
||||||
.expect("create nested temp dir");
|
.expect("create nested temp dir");
|
||||||
let mut f = File::create(article_path.join("_index.md")).unwrap();
|
let mut f = File::create(article_path.join("_index.md")).unwrap();
|
||||||
f.write_all(b"+++\nslug=\"hey\"\n+++\n").unwrap();
|
f.write_all(b"+++\n+++\n").unwrap();
|
||||||
File::create(article_path.join("example.js")).unwrap();
|
File::create(article_path.join("example.js")).unwrap();
|
||||||
File::create(article_path.join("graph.jpg")).unwrap();
|
File::create(article_path.join("graph.jpg")).unwrap();
|
||||||
File::create(article_path.join("fail.png")).unwrap();
|
File::create(article_path.join("fail.png")).unwrap();
|
||||||
|
|
|
@ -160,7 +160,7 @@ pub struct SerializingSection<'a> {
|
||||||
subsections: Vec<&'a str>,
|
subsections: Vec<&'a str>,
|
||||||
translations: Vec<TranslatedContent<'a>>,
|
translations: Vec<TranslatedContent<'a>>,
|
||||||
backlinks: Vec<BackLink<'a>>,
|
backlinks: Vec<BackLink<'a>>,
|
||||||
generate_feed: bool,
|
generate_feeds: bool,
|
||||||
transparent: bool,
|
transparent: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -220,7 +220,7 @@ impl<'a> SerializingSection<'a> {
|
||||||
reading_time: section.reading_time,
|
reading_time: section.reading_time,
|
||||||
assets: §ion.serialized_assets,
|
assets: §ion.serialized_assets,
|
||||||
lang: §ion.lang,
|
lang: §ion.lang,
|
||||||
generate_feed: section.meta.generate_feed,
|
generate_feeds: section.meta.generate_feeds,
|
||||||
transparent: section.meta.transparent,
|
transparent: section.meta.transparent,
|
||||||
pages,
|
pages,
|
||||||
subsections,
|
subsections,
|
||||||
|
|
|
@ -42,7 +42,7 @@ fn bench_render_feed(b: &mut test::Bencher) {
|
||||||
let public = &tmp_dir.path().join("public");
|
let public = &tmp_dir.path().join("public");
|
||||||
site.set_output_path(&public);
|
site.set_output_path(&public);
|
||||||
b.iter(|| {
|
b.iter(|| {
|
||||||
site.render_feed(
|
site.render_feeds(
|
||||||
site.library.read().unwrap().pages.values().collect(),
|
site.library.read().unwrap().pages.values().collect(),
|
||||||
None,
|
None,
|
||||||
&site.config.default_language,
|
&site.config.default_language,
|
||||||
|
|
|
@ -27,13 +27,13 @@ impl<'a> SerializedFeedTaxonomyItem<'a> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn render_feed(
|
pub fn render_feeds(
|
||||||
site: &Site,
|
site: &Site,
|
||||||
all_pages: Vec<&Page>,
|
all_pages: Vec<&Page>,
|
||||||
lang: &str,
|
lang: &str,
|
||||||
base_path: Option<&PathBuf>,
|
base_path: Option<&PathBuf>,
|
||||||
additional_context_fn: impl Fn(Context) -> Context,
|
additional_context_fn: impl Fn(Context) -> Context,
|
||||||
) -> Result<Option<String>> {
|
) -> Result<Option<Vec<String>>> {
|
||||||
let mut pages = all_pages.into_iter().filter(|p| p.meta.date.is_some()).collect::<Vec<_>>();
|
let mut pages = all_pages.into_iter().filter(|p| p.meta.date.is_some()).collect::<Vec<_>>();
|
||||||
|
|
||||||
// Don't generate a feed if none of the pages has a date
|
// Don't generate a feed if none of the pages has a date
|
||||||
|
@ -73,18 +73,23 @@ pub fn render_feed(
|
||||||
context.insert("config", &site.config.serialize(lang));
|
context.insert("config", &site.config.serialize(lang));
|
||||||
context.insert("lang", lang);
|
context.insert("lang", lang);
|
||||||
|
|
||||||
let feed_filename = &site.config.feed_filename;
|
let mut feeds = Vec::new();
|
||||||
let feed_url = if let Some(base) = base_path {
|
for feed_filename in &site.config.feed_filenames {
|
||||||
site.config.make_permalink(&base.join(feed_filename).to_string_lossy().replace('\\', "/"))
|
let mut context = context.clone();
|
||||||
} else {
|
|
||||||
site.config.make_permalink(feed_filename)
|
|
||||||
};
|
|
||||||
|
|
||||||
context.insert("feed_url", &feed_url);
|
let feed_url = if let Some(base) = base_path {
|
||||||
|
site.config
|
||||||
|
.make_permalink(&base.join(feed_filename).to_string_lossy().replace('\\', "/"))
|
||||||
|
} else {
|
||||||
|
site.config.make_permalink(feed_filename)
|
||||||
|
};
|
||||||
|
|
||||||
context = additional_context_fn(context);
|
context.insert("feed_url", &feed_url);
|
||||||
|
|
||||||
let feed = render_template(feed_filename, &site.tera, context, &site.config.theme)?;
|
context = additional_context_fn(context);
|
||||||
|
|
||||||
Ok(Some(feed))
|
feeds.push(render_template(feed_filename, &site.tera, context, &site.config.theme)?);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(Some(feeds))
|
||||||
}
|
}
|
|
@ -1,4 +1,4 @@
|
||||||
pub mod feed;
|
pub mod feeds;
|
||||||
pub mod link_checking;
|
pub mod link_checking;
|
||||||
mod minify;
|
mod minify;
|
||||||
pub mod sass;
|
pub mod sass;
|
||||||
|
@ -746,23 +746,23 @@ impl Site {
|
||||||
start = log_time(start, "Rendered sitemap");
|
start = log_time(start, "Rendered sitemap");
|
||||||
|
|
||||||
let library = self.library.read().unwrap();
|
let library = self.library.read().unwrap();
|
||||||
if self.config.generate_feed {
|
if self.config.generate_feeds {
|
||||||
let is_multilingual = self.config.is_multilingual();
|
let is_multilingual = self.config.is_multilingual();
|
||||||
let pages: Vec<_> = if is_multilingual {
|
let pages: Vec<_> = if is_multilingual {
|
||||||
library.pages.values().filter(|p| p.lang == self.config.default_language).collect()
|
library.pages.values().filter(|p| p.lang == self.config.default_language).collect()
|
||||||
} else {
|
} else {
|
||||||
library.pages.values().collect()
|
library.pages.values().collect()
|
||||||
};
|
};
|
||||||
self.render_feed(pages, None, &self.config.default_language, |c| c)?;
|
self.render_feeds(pages, None, &self.config.default_language, |c| c)?;
|
||||||
start = log_time(start, "Generated feed in default language");
|
start = log_time(start, "Generated feed in default language");
|
||||||
}
|
}
|
||||||
|
|
||||||
for (code, language) in &self.config.other_languages() {
|
for (code, language) in &self.config.other_languages() {
|
||||||
if !language.generate_feed {
|
if !language.generate_feeds {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
let pages: Vec<_> = library.pages.values().filter(|p| &p.lang == code).collect();
|
let pages: Vec<_> = library.pages.values().filter(|p| &p.lang == code).collect();
|
||||||
self.render_feed(pages, Some(&PathBuf::from(code)), code, |c| c)?;
|
self.render_feeds(pages, Some(&PathBuf::from(code)), code, |c| c)?;
|
||||||
start = log_time(start, "Generated feed in other language");
|
start = log_time(start, "Generated feed in other language");
|
||||||
}
|
}
|
||||||
self.render_themes_css()?;
|
self.render_themes_css()?;
|
||||||
|
@ -965,14 +965,16 @@ impl Site {
|
||||||
} else {
|
} else {
|
||||||
PathBuf::from(format!("{}/{}/{}", taxonomy.lang, taxonomy.slug, item.slug))
|
PathBuf::from(format!("{}/{}/{}", taxonomy.lang, taxonomy.slug, item.slug))
|
||||||
};
|
};
|
||||||
self.render_feed(
|
self.render_feeds(
|
||||||
item.pages.iter().map(|p| library.pages.get(p).unwrap()).collect(),
|
item.pages.iter().map(|p| library.pages.get(p).unwrap()).collect(),
|
||||||
Some(&tax_path),
|
Some(&tax_path),
|
||||||
&taxonomy.lang,
|
&taxonomy.lang,
|
||||||
|mut context: Context| {
|
|mut context: Context| {
|
||||||
context.insert("taxonomy", &taxonomy.kind);
|
context.insert("taxonomy", &taxonomy.kind);
|
||||||
context
|
context.insert(
|
||||||
.insert("term", &feed::SerializedFeedTaxonomyItem::from_item(item));
|
"term",
|
||||||
|
&feeds::SerializedFeedTaxonomyItem::from_item(item),
|
||||||
|
);
|
||||||
context
|
context
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
@ -1028,36 +1030,38 @@ impl Site {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Renders a feed for the given path and at the given path
|
/// Renders feeds for the given path and at the given path
|
||||||
/// If both arguments are `None`, it will render only the feed for the whole
|
/// If both arguments are `None`, it will render only the feeds for the whole
|
||||||
/// site at the root folder.
|
/// site at the root folder.
|
||||||
pub fn render_feed(
|
pub fn render_feeds(
|
||||||
&self,
|
&self,
|
||||||
all_pages: Vec<&Page>,
|
all_pages: Vec<&Page>,
|
||||||
base_path: Option<&PathBuf>,
|
base_path: Option<&PathBuf>,
|
||||||
lang: &str,
|
lang: &str,
|
||||||
additional_context_fn: impl Fn(Context) -> Context,
|
additional_context_fn: impl Fn(Context) -> Context,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
let feed = match feed::render_feed(self, all_pages, lang, base_path, additional_context_fn)?
|
let feeds =
|
||||||
{
|
match feeds::render_feeds(self, all_pages, lang, base_path, additional_context_fn)? {
|
||||||
Some(v) => v,
|
Some(v) => v,
|
||||||
None => return Ok(()),
|
None => return Ok(()),
|
||||||
};
|
};
|
||||||
let feed_filename = &self.config.feed_filename;
|
|
||||||
|
|
||||||
if let Some(base) = base_path {
|
for (feed, feed_filename) in feeds.into_iter().zip(self.config.feed_filenames.iter()) {
|
||||||
let mut components = Vec::new();
|
if let Some(base) = base_path {
|
||||||
for component in base.components() {
|
let mut components = Vec::new();
|
||||||
components.push(component.as_os_str().to_string_lossy());
|
for component in base.components() {
|
||||||
|
components.push(component.as_os_str().to_string_lossy());
|
||||||
|
}
|
||||||
|
self.write_content(
|
||||||
|
&components.iter().map(|x| x.as_ref()).collect::<Vec<_>>(),
|
||||||
|
feed_filename,
|
||||||
|
feed,
|
||||||
|
)?;
|
||||||
|
} else {
|
||||||
|
self.write_content(&[], feed_filename, feed)?;
|
||||||
}
|
}
|
||||||
self.write_content(
|
|
||||||
&components.iter().map(|x| x.as_ref()).collect::<Vec<_>>(),
|
|
||||||
feed_filename,
|
|
||||||
feed,
|
|
||||||
)?;
|
|
||||||
} else {
|
|
||||||
self.write_content(&[], feed_filename, feed)?;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1076,10 +1080,10 @@ impl Site {
|
||||||
output_path.push(component);
|
output_path.push(component);
|
||||||
}
|
}
|
||||||
|
|
||||||
if section.meta.generate_feed {
|
if section.meta.generate_feeds {
|
||||||
let library = &self.library.read().unwrap();
|
let library = &self.library.read().unwrap();
|
||||||
let pages = section.pages.iter().map(|k| library.pages.get(k).unwrap()).collect();
|
let pages = section.pages.iter().map(|k| library.pages.get(k).unwrap()).collect();
|
||||||
self.render_feed(
|
self.render_feeds(
|
||||||
pages,
|
pages,
|
||||||
Some(&PathBuf::from(§ion.path[1..])),
|
Some(&PathBuf::from(§ion.path[1..])),
|
||||||
§ion.lang,
|
§ion.lang,
|
||||||
|
|
|
@ -461,27 +461,34 @@ title = "A title"
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn can_get_feed_url_with_default_language() {
|
fn can_get_feed_urls_with_default_language() {
|
||||||
let config = Config::parse(CONFIG_DATA).unwrap();
|
let config = Config::parse(CONFIG_DATA).unwrap();
|
||||||
let dir = create_temp_dir();
|
let dir = create_temp_dir();
|
||||||
let static_fn =
|
let static_fn =
|
||||||
GetUrl::new(dir.path().to_path_buf(), config.clone(), HashMap::new(), PathBuf::new());
|
GetUrl::new(dir.path().to_path_buf(), config.clone(), HashMap::new(), PathBuf::new());
|
||||||
let mut args = HashMap::new();
|
for feed_filename in &config.feed_filenames {
|
||||||
args.insert("path".to_string(), to_value(config.feed_filename).unwrap());
|
let mut args = HashMap::new();
|
||||||
args.insert("lang".to_string(), to_value("fr").unwrap());
|
args.insert("path".to_string(), to_value(feed_filename).unwrap());
|
||||||
assert_eq!(static_fn.call(&args).unwrap(), "https://remplace-par-ton-url.fr/atom.xml");
|
args.insert("lang".to_string(), to_value("fr").unwrap());
|
||||||
|
assert_eq!(static_fn.call(&args).unwrap(), "https://remplace-par-ton-url.fr/atom.xml");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn can_get_feed_url_with_other_language() {
|
fn can_get_feed_urls_with_other_language() {
|
||||||
let config = Config::parse(CONFIG_DATA).unwrap();
|
let config = Config::parse(CONFIG_DATA).unwrap();
|
||||||
let dir = create_temp_dir();
|
let dir = create_temp_dir();
|
||||||
let static_fn =
|
let static_fn =
|
||||||
GetUrl::new(dir.path().to_path_buf(), config.clone(), HashMap::new(), PathBuf::new());
|
GetUrl::new(dir.path().to_path_buf(), config.clone(), HashMap::new(), PathBuf::new());
|
||||||
let mut args = HashMap::new();
|
for feed_filename in &config.feed_filenames {
|
||||||
args.insert("path".to_string(), to_value(config.feed_filename).unwrap());
|
let mut args = HashMap::new();
|
||||||
args.insert("lang".to_string(), to_value("en").unwrap());
|
args.insert("path".to_string(), to_value(feed_filename).unwrap());
|
||||||
assert_eq!(static_fn.call(&args).unwrap(), "https://remplace-par-ton-url.fr/en/atom.xml");
|
args.insert("lang".to_string(), to_value("en").unwrap());
|
||||||
|
assert_eq!(
|
||||||
|
static_fn.call(&args).unwrap(),
|
||||||
|
"https://remplace-par-ton-url.fr/en/atom.xml"
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|
|
@ -11,7 +11,7 @@ to your `config.toml`. For example:
|
||||||
|
|
||||||
```toml
|
```toml
|
||||||
[languages.fr]
|
[languages.fr]
|
||||||
generate_feed = true # there will be a feed for French content
|
generate_feeds = true # there will be a feed for French content
|
||||||
build_search_index = true
|
build_search_index = true
|
||||||
taxonomies = [
|
taxonomies = [
|
||||||
{name = "auteurs"},
|
{name = "auteurs"},
|
||||||
|
|
|
@ -106,11 +106,11 @@ transparent = false
|
||||||
# current one. This takes an array of paths, not URLs.
|
# current one. This takes an array of paths, not URLs.
|
||||||
aliases = []
|
aliases = []
|
||||||
|
|
||||||
# If set to "true", a feed file will be generated for this section at the
|
# If set to "true", feed files will be generated for this section at the
|
||||||
# section's root path. This is independent of the site-wide variable of the same
|
# section's root path. This is independent of the site-wide variable of the same
|
||||||
# name. The section feed will only include posts from that respective feed, and
|
# name. The section feed will only include posts from that respective feed, and
|
||||||
# not from any other sections, including sub-sections under that section.
|
# not from any other sections, including sub-sections under that section.
|
||||||
generate_feed = false
|
generate_feeds = false
|
||||||
|
|
||||||
# Your own data.
|
# Your own data.
|
||||||
[extra]
|
[extra]
|
||||||
|
|
|
@ -65,12 +65,12 @@ ignored_content = []
|
||||||
ignored_static = []
|
ignored_static = []
|
||||||
|
|
||||||
# When set to "true", a feed is automatically generated.
|
# When set to "true", a feed is automatically generated.
|
||||||
generate_feed = false
|
generate_feeds = false
|
||||||
|
|
||||||
# The filename to use for the feed. Used as the template filename, too.
|
# The filenames to use for the feeds. Used as the template filenames, too.
|
||||||
# Defaults to "atom.xml", which has a built-in template that renders an Atom 1.0 feed.
|
# Defaults to ["atom.xml"], which has a built-in template that renders an Atom 1.0 feed.
|
||||||
# There is also a built-in template "rss.xml" that renders an RSS 2.0 feed.
|
# There is also a built-in template "rss.xml" that renders an RSS 2.0 feed.
|
||||||
feed_filename = "atom.xml"
|
feed_filenames = ["atom.xml"]
|
||||||
|
|
||||||
# The number of articles to include in the feed. All items are included if
|
# The number of articles to include in the feed. All items are included if
|
||||||
# this limit is not set (the default).
|
# this limit is not set (the default).
|
||||||
|
@ -199,13 +199,13 @@ index_format = "elasticlunr_javascript"
|
||||||
|
|
||||||
# Additional languages definition
|
# Additional languages definition
|
||||||
# You can define language specific config values and translations:
|
# You can define language specific config values and translations:
|
||||||
# title, description, generate_feed, feed_filename, taxonomies, build_search_index
|
# title, description, generate_feeds, feed_filenames, taxonomies, build_search_index
|
||||||
# as well as its own search configuration and translations (see above for details on those)
|
# as well as its own search configuration and translations (see above for details on those)
|
||||||
[languages]
|
[languages]
|
||||||
# For example
|
# For example
|
||||||
# [languages.fr]
|
# [languages.fr]
|
||||||
# title = "Mon blog"
|
# title = "Mon blog"
|
||||||
# generate_feed = true
|
# generate_feeds = true
|
||||||
# taxonomies = [
|
# taxonomies = [
|
||||||
# {name = "auteurs"},
|
# {name = "auteurs"},
|
||||||
# {name = "tags"},
|
# {name = "tags"},
|
||||||
|
|
|
@ -4,13 +4,13 @@ weight = 50
|
||||||
aliases = ["/documentation/templates/rss/"]
|
aliases = ["/documentation/templates/rss/"]
|
||||||
+++
|
+++
|
||||||
|
|
||||||
If the site `config.toml` file sets `generate_feed = true`, then Zola will
|
If the site `config.toml` file sets `generate_feeds = true`, then Zola will
|
||||||
generate a feed file for the site, named according to the `feed_filename`
|
generate feed files for the site, named according to the `feed_filenames`
|
||||||
setting in `config.toml`, which defaults to `atom.xml`. Given the feed filename
|
setting in `config.toml`, which defaults to `atom.xml`. Given the feed filename
|
||||||
`atom.xml`, the generated file will live at `base_url/atom.xml`, based upon the
|
`atom.xml`, the generated file will live at `base_url/atom.xml`, based upon the
|
||||||
`atom.xml` file in the `templates` directory, or the built-in Atom template.
|
`atom.xml` file in the `templates` directory, or the built-in Atom template.
|
||||||
|
|
||||||
`feed_filename` can be set to any value, but built-in templates are provided
|
`feed_filenames` can be set to any value, but built-in templates are provided
|
||||||
for `atom.xml` (in the preferred Atom 1.0 format), and `rss.xml` (in the RSS
|
for `atom.xml` (in the preferred Atom 1.0 format), and `rss.xml` (in the RSS
|
||||||
2.0 format). If you choose a different filename (e.g. `feed.xml`), you will
|
2.0 format). If you choose a different filename (e.g. `feed.xml`), you will
|
||||||
need to provide a template yourself.
|
need to provide a template yourself.
|
||||||
|
@ -54,7 +54,7 @@ Feeds for taxonomy terms get two more variables, using types from the
|
||||||
- `term`: of type `TaxonomyTerm`, but without `term.pages` (use `pages` instead)
|
- `term`: of type `TaxonomyTerm`, but without `term.pages` (use `pages` instead)
|
||||||
|
|
||||||
You can also enable separate feeds for each section by setting the
|
You can also enable separate feeds for each section by setting the
|
||||||
`generate_feed` variable to true in the respective section's front matter.
|
`generate_feeds` variable to true in the respective section's front matter.
|
||||||
Section feeds will use the same template as indicated in the `config.toml` file.
|
Section feeds will use the same template as indicated in the `config.toml` file.
|
||||||
Section feeds, in addition to the five feed template variables, get the
|
Section feeds, in addition to the five feed template variables, get the
|
||||||
`section` variable from the [section
|
`section` variable from the [section
|
||||||
|
@ -85,4 +85,4 @@ In order to enable the tag feeds as well, you can overload the `block rss` using
|
||||||
Each tag page will refer to it's dedicated feed.
|
Each tag page will refer to it's dedicated feed.
|
||||||
|
|
||||||
[atom_rfc]: https://www.rfc-editor.org/rfc/rfc4287
|
[atom_rfc]: https://www.rfc-editor.org/rfc/rfc4287
|
||||||
[rss_spec]: https://www.rssboard.org/rss-specification#ltauthorgtSubelementOfLtitemgt
|
[rss_spec]: https://www.rssboard.org/rss-specification#ltauthorgtSubelementOfLtitemgt
|
||||||
|
|
|
@ -108,8 +108,8 @@ lang: String;
|
||||||
translations: Array<TranslatedContent>;
|
translations: Array<TranslatedContent>;
|
||||||
// All the pages/sections linking this page: their permalink and a title if there is one
|
// All the pages/sections linking this page: their permalink and a title if there is one
|
||||||
backlinks: Array<{permalink: String, title: String?}>;
|
backlinks: Array<{permalink: String, title: String?}>;
|
||||||
// Whether this section generates a feed or not. Taken from the front-matter if set
|
// Whether this section generates feeds or not. Taken from the front-matter if set
|
||||||
generate_feed: bool;
|
generate_feeds: bool;
|
||||||
// Whether this section is transparent. Taken from the front-matter if set
|
// Whether this section is transparent. Taken from the front-matter if set
|
||||||
transparent: bool;
|
transparent: bool;
|
||||||
```
|
```
|
||||||
|
|
|
@ -120,7 +120,7 @@ Here's a breakdown of the configuration settings tailored for this theme:
|
||||||
- **build_search_index**: If set to `true`, a search index will be built from the pages and section content for the `default_language`.
|
- **build_search_index**: If set to `true`, a search index will be built from the pages and section content for the `default_language`.
|
||||||
In this configuration and for this theme, it's disabled (`false`).
|
In this configuration and for this theme, it's disabled (`false`).
|
||||||
|
|
||||||
- **generate_feed**: Determines if an Atom feed (file `atom.xml`) is automatically generated.
|
- **generate_feeds**: Determines if an Atom feed (file `atom.xml`) is automatically generated.
|
||||||
It's set to `true`, meaning a feed will be generated.
|
It's set to `true`, meaning a feed will be generated.
|
||||||
|
|
||||||
- **taxonomies**: An array of taxonomies (classification systems) used for the site.
|
- **taxonomies**: An array of taxonomies (classification systems) used for the site.
|
||||||
|
@ -408,4 +408,4 @@ Just use the file `en.toml` as a template for your own translations.
|
||||||
This theme is under the MIT License.
|
This theme is under the MIT License.
|
||||||
For details, please refer to the LICENSE file.
|
For details, please refer to the LICENSE file.
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
title = "My Integration Testing site"
|
title = "My Integration Testing site"
|
||||||
base_url = "https://replace-this-with-your-url.com"
|
base_url = "https://replace-this-with-your-url.com"
|
||||||
compile_sass = true
|
compile_sass = true
|
||||||
generate_feed = true
|
generate_feeds = true
|
||||||
theme = "sample"
|
theme = "sample"
|
||||||
|
|
||||||
taxonomies = [
|
taxonomies = [
|
||||||
|
|
|
@ -5,5 +5,5 @@ template = "section_paginated.html"
|
||||||
insert_anchor_links = "left"
|
insert_anchor_links = "left"
|
||||||
sort_by = "date"
|
sort_by = "date"
|
||||||
aliases = ["another-old-url/index.html"]
|
aliases = ["another-old-url/index.html"]
|
||||||
generate_feed = true
|
generate_feeds = true
|
||||||
+++
|
+++
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
title = "Programming"
|
title = "Programming"
|
||||||
sort_by = "weight"
|
sort_by = "weight"
|
||||||
weight = 1
|
weight = 1
|
||||||
generate_feed = true
|
generate_feeds = true
|
||||||
|
|
||||||
[extra]
|
[extra]
|
||||||
we_have_extra = "variables"
|
we_have_extra = "variables"
|
||||||
|
|
|
@ -9,7 +9,7 @@ build_search_index = true
|
||||||
|
|
||||||
default_language = "en"
|
default_language = "en"
|
||||||
|
|
||||||
generate_feed = true
|
generate_feeds = true
|
||||||
|
|
||||||
taxonomies = [
|
taxonomies = [
|
||||||
{name = "authors", feed = true},
|
{name = "authors", feed = true},
|
||||||
|
@ -17,7 +17,7 @@ taxonomies = [
|
||||||
]
|
]
|
||||||
|
|
||||||
[languages.fr]
|
[languages.fr]
|
||||||
generate_feed = true
|
generate_feeds = true
|
||||||
taxonomies = [
|
taxonomies = [
|
||||||
{name = "auteurs", feed = true},
|
{name = "auteurs", feed = true},
|
||||||
{name = "tags"},
|
{name = "tags"},
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
+++
|
+++
|
||||||
title = "Mon blog"
|
title = "Mon blog"
|
||||||
sort_by = "date"
|
sort_by = "date"
|
||||||
insert_anchors = "right"
|
insert_anchor_links = "right"
|
||||||
+++
|
+++
|
||||||
|
|
||||||
[Dernières nouvelles](#news)
|
[Dernières nouvelles](#news)
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
+++
|
+++
|
||||||
title = "Il mio blog"
|
title = "Il mio blog"
|
||||||
sort_by = "date"
|
sort_by = "date"
|
||||||
insert_anchors = "right"
|
insert_anchor_links = "right"
|
||||||
+++
|
+++
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
+++
|
+++
|
||||||
title = "My blog"
|
title = "My blog"
|
||||||
sort_by = "date"
|
sort_by = "date"
|
||||||
insert_anchors = "left"
|
insert_anchor_links = "left"
|
||||||
+++
|
+++
|
||||||
|
|
Loading…
Reference in a new issue