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

Added support for multiple feeds (i.e. generating both Atom and RSS) #2477

Open
wants to merge 5 commits into
base: next
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,10 @@
- Fix resizing for images with EXIF orientation
- Add MIME type to get_image_metadata
- Fix hot loading for config.toml in some cases
- 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 (front-matter is unaffected)

## 0.18.0 (2023-12-18)

Expand Down
41 changes: 21 additions & 20 deletions components/config/src/config/languages.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,17 +8,17 @@ use crate::config::search;
use crate::config::taxonomies;

#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[serde(default)]
#[serde(default, deny_unknown_fields)]
pub struct LanguageOptions {
/// Title of the site. Defaults to None
pub title: Option<String>,
/// Description of the site. Defaults to None
pub description: Option<String>,
/// Whether to generate a feed for that language, defaults to `false`
pub generate_feed: bool,
/// The filename to use for feeds. Used to find the template, too.
/// Defaults to "atom.xml", with "rss.xml" also having a template provided out of the box.
pub feed_filename: String,
/// Whether to generate feeds for that language, defaults to `false`
pub generate_feeds: bool,
LunarEclipse363 marked this conversation as resolved.
Show resolved Hide resolved
/// 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.
pub feed_filenames: Vec<String>,
LunarEclipse363 marked this conversation as resolved.
Show resolved Hide resolved
pub taxonomies: Vec<taxonomies::TaxonomyConfig>,
/// Whether to generate search index for that language, defaults to `false`
pub build_search_index: bool,
Expand Down Expand Up @@ -66,9 +66,10 @@ impl LanguageOptions {
merge_field!(self.title, other.title, "title");
merge_field!(self.description, other.description, "description");
merge_field!(
self.feed_filename == "atom.xml",
self.feed_filename,
other.feed_filename,
self.feed_filenames.is_empty()
|| self.feed_filenames == LanguageOptions::default().feed_filenames,
self.feed_filenames,
other.feed_filenames,
"feed_filename"
);
merge_field!(self.taxonomies.is_empty(), self.taxonomies, other.taxonomies, "taxonomies");
Expand All @@ -79,7 +80,7 @@ impl LanguageOptions {
"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;

if self.search == search::Search::default() {
Expand All @@ -101,8 +102,8 @@ impl Default for LanguageOptions {
LanguageOptions {
title: None,
description: None,
generate_feed: false,
feed_filename: "atom.xml".to_string(),
generate_feeds: false,
feed_filenames: vec!["atom.xml".to_string()],
taxonomies: vec![],
build_search_index: false,
search: search::Search::default(),
Expand All @@ -129,8 +130,8 @@ mod tests {
let mut base_default_language_options = LanguageOptions {
title: Some("Site's title".to_string()),
description: None,
generate_feed: true,
feed_filename: "atom.xml".to_string(),
generate_feeds: true,
feed_filenames: vec!["atom.xml".to_string()],
taxonomies: vec![],
build_search_index: true,
search: search::Search::default(),
Expand All @@ -140,8 +141,8 @@ mod tests {
let section_default_language_options = LanguageOptions {
title: None,
description: Some("Site's description".to_string()),
generate_feed: false,
feed_filename: "rss.xml".to_string(),
generate_feeds: false,
feed_filenames: vec!["rss.xml".to_string()],
taxonomies: vec![],
build_search_index: true,
search: search::Search::default(),
Expand All @@ -156,8 +157,8 @@ mod tests {
let mut base_default_language_options = LanguageOptions {
title: Some("Site's title".to_string()),
description: Some("Duplicate site description".to_string()),
generate_feed: true,
feed_filename: "".to_string(),
generate_feeds: true,
feed_filenames: vec![],
taxonomies: vec![],
build_search_index: true,
search: search::Search::default(),
Expand All @@ -167,8 +168,8 @@ mod tests {
let section_default_language_options = LanguageOptions {
title: None,
description: Some("Site's description".to_string()),
generate_feed: false,
feed_filename: "Some feed_filename".to_string(),
generate_feeds: false,
feed_filenames: vec!["Some feed_filename".to_string()],
taxonomies: vec![],
build_search_index: true,
search: search::Search::default(),
Expand Down
60 changes: 37 additions & 23 deletions components/config/src/config/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ pub enum Mode {
}

#[derive(Clone, Debug, Deserialize)]
#[serde(default)]
#[serde(default, deny_unknown_fields)]
pub struct Config {
/// Base URL of the site, the only required config argument
pub base_url: String,
Expand All @@ -49,13 +49,13 @@ pub struct Config {
/// The translations strings for the default language
translations: HashMap<String, String>,

/// Whether to generate a feed. Defaults to false.
pub generate_feed: bool,
/// Whether to generate feeds. Defaults to false.
pub generate_feeds: bool,
/// The number of articles to include in the feed. Defaults to including all items.
pub feed_limit: Option<usize>,
/// The filename to use for feeds. Used to find the template, too.
/// Defaults to "atom.xml", with "rss.xml" also having a template provided out of the box.
pub feed_filename: String,
/// 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.
pub feed_filenames: Vec<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>,
Expand Down Expand Up @@ -109,7 +109,7 @@ pub struct SerializedConfig<'a> {
languages: HashMap<&'a String, &'a languages::LanguageOptions>,
default_language: &'a str,
generate_feed: bool,
feed_filename: &'a str,
feed_filenames: &'a [String],
taxonomies: &'a [taxonomies::TaxonomyConfig],
author: &'a Option<String>,
build_search_index: bool,
Expand Down Expand Up @@ -183,12 +183,14 @@ impl Config {

/// 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 =
if path.ends_with('/') || path.ends_with(&self.feed_filename) || path.is_empty() {
""
} else {
"/"
};
let trailing_bit = if path.ends_with('/')
|| self.feed_filenames.iter().any(|feed_filename| path.ends_with(feed_filename))
|| path.is_empty()
{
""
} else {
"/"
};

// Index section with a base url that has a trailing slash
if self.base_url.ends_with('/') && path == "/" {
Expand All @@ -212,8 +214,8 @@ impl Config {
let mut base_language_options = languages::LanguageOptions {
title: self.title.clone(),
description: self.description.clone(),
generate_feed: self.generate_feed,
feed_filename: self.feed_filename.clone(),
generate_feeds: self.generate_feeds,
feed_filenames: self.feed_filenames.clone(),
build_search_index: self.build_search_index,
taxonomies: self.taxonomies.clone(),
search: self.search.clone(),
Expand Down Expand Up @@ -320,8 +322,8 @@ impl Config {
description: &options.description,
languages: self.languages.iter().filter(|(k, _)| k.as_str() != lang).collect(),
default_language: &self.default_language,
generate_feed: options.generate_feed,
feed_filename: &options.feed_filename,
generate_feed: options.generate_feeds,
feed_filenames: &options.feed_filenames,
taxonomies: &options.taxonomies,
author: &self.author,
build_search_index: options.build_search_index,
Expand Down Expand Up @@ -369,9 +371,9 @@ impl Default for Config {
theme: None,
default_language: "en".to_string(),
languages: HashMap::new(),
generate_feed: false,
generate_feeds: false,
feed_limit: None,
feed_filename: "atom.xml".to_string(),
feed_filenames: vec!["atom.xml".to_string()],
hard_link_static: false,
taxonomies: Vec::new(),
author: None,
Expand Down Expand Up @@ -428,8 +430,8 @@ mod tests {
languages::LanguageOptions {
title: None,
description: description_lang_section.clone(),
generate_feed: true,
feed_filename: config.feed_filename.clone(),
generate_feeds: true,
feed_filenames: config.feed_filenames.clone(),
taxonomies: config.taxonomies.clone(),
build_search_index: false,
search: search::Search::default(),
Expand All @@ -456,8 +458,8 @@ mod tests {
languages::LanguageOptions {
title: title_lang_section.clone(),
description: None,
generate_feed: true,
feed_filename: config.feed_filename.clone(),
generate_feeds: true,
feed_filenames: config.feed_filenames.clone(),
taxonomies: config.taxonomies.clone(),
build_search_index: false,
search: search::Search::default(),
Expand Down Expand Up @@ -976,4 +978,16 @@ author = "[email protected] (Some Person)"
let config = Config::parse(config).unwrap();
assert_eq!(config.author, Some("[email protected] (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();
}
}
28 changes: 27 additions & 1 deletion components/config/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ mod config;
pub mod highlighting;
mod theme;

use std::path::Path;
use std::{marker::PhantomData, path::Path};

pub use crate::config::{
languages::LanguageOptions,
Expand All @@ -14,9 +14,35 @@ pub use crate::config::{
Config,
};
use errors::Result;
use serde::Deserialize;

/// Get and parse the config.
/// If it doesn't succeed, exit
pub fn get_config(filename: &Path) -> Result<Config> {
Config::from_file(filename)
}

/// This is used to print an error message for deprecated fields
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub struct Deprecated<T> {
_type: PhantomData<T>,
}

impl<T> Deprecated<T> {
pub fn new() -> Self {
Self { _type: PhantomData }
}
}

pub trait DeprecationReason {
const REASON: &'static str;
}

impl<'de, T: DeprecationReason> Deserialize<'de> for Deprecated<T> {
fn deserialize<D>(_deserializer: D) -> std::prelude::v1::Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
Err(serde::de::Error::custom(format!("Failed to parse a deprecated option: {}", T::REASON)))
}
}
16 changes: 14 additions & 2 deletions components/content/src/front_matter/section.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use libs::tera::{Map, Value};
use serde::{Deserialize, Serialize};

use config::{Deprecated, DeprecationReason};
use errors::Result;
use utils::de::fix_toml_dates;
use utils::types::InsertAnchor;
Expand Down Expand Up @@ -67,13 +68,23 @@ pub struct SectionFrontMatter {
/// redirect to this
#[serde(skip_serializing)]
pub aliases: Vec<String>,
/// Deprecated
#[serde(skip_serializing)]
pub generate_feed: Deprecated<DeprecatedGenerateFeed>,
Copy link
Collaborator

Choose a reason for hiding this comment

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

👍

/// Whether to generate a feed for the current section
#[serde(skip_serializing)]
pub generate_feed: bool,
pub generate_feeds: bool,
/// Any extra parameter present in the front matter
pub extra: Map<String, Value>,
}

#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub struct DeprecatedGenerateFeed {}

impl DeprecationReason for DeprecatedGenerateFeed {
const REASON: &'static str = "generate_feed is deprecated, please use generate_feeds instead";
}

impl SectionFrontMatter {
pub fn parse(raw: &RawFrontMatter) -> Result<SectionFrontMatter> {
let mut f: SectionFrontMatter = raw.deserialize()?;
Expand Down Expand Up @@ -113,7 +124,8 @@ impl Default for SectionFrontMatter {
transparent: false,
page_template: None,
aliases: Vec::new(),
generate_feed: false,
generate_feed: Deprecated::new(),
generate_feeds: false,
extra: Map::new(),
draft: false,
}
Expand Down
4 changes: 2 additions & 2 deletions components/content/src/ser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -160,7 +160,7 @@ pub struct SerializingSection<'a> {
subsections: Vec<&'a str>,
translations: Vec<TranslatedContent<'a>>,
backlinks: Vec<BackLink<'a>>,
generate_feed: bool,
generate_feeds: bool,
transparent: bool,
}

Expand Down Expand Up @@ -220,7 +220,7 @@ impl<'a> SerializingSection<'a> {
reading_time: section.reading_time,
assets: &section.serialized_assets,
lang: &section.lang,
generate_feed: section.meta.generate_feed,
generate_feeds: section.meta.generate_feeds,
transparent: section.meta.transparent,
pages,
subsections,
Expand Down
2 changes: 1 addition & 1 deletion components/site/benches/site.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ fn bench_render_feed(b: &mut test::Bencher) {
let public = &tmp_dir.path().join("public");
site.set_output_path(&public);
b.iter(|| {
site.render_feed(
site.render_feeds(
site.library.read().unwrap().pages.values().collect(),
None,
&site.config.default_language,
Expand Down