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

Atom 1.0 support #1089

Closed
wants to merge 11 commits into from
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@ log = "0.4"
env_logger = "0.9"
rss = "2.0"
jsonfeed = "0.2"
atom_syndication = "0.11"
pulldown-cmark = {version="0.9", default-features = false}
engarde = { version = "0.1", path = "crates/engarde" }
regex = "1.6"
Expand Down
5 changes: 5 additions & 0 deletions crates/config/src/collection.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ pub struct Collection {
pub order: SortOrder,
pub rss: Option<crate::RelPath>,
pub jsonfeed: Option<crate::RelPath>,
pub atom: Option<crate::RelPath>,
pub publish_date_in_filename: bool,
pub default: Frontmatter,
}
Expand All @@ -27,6 +28,7 @@ impl From<PostCollection> for Collection {
order,
rss,
jsonfeed,
atom,
publish_date_in_filename,
default,
} = other;
Expand All @@ -38,6 +40,7 @@ impl From<PostCollection> for Collection {
order,
rss,
jsonfeed,
atom,
publish_date_in_filename,
default,
}
Expand Down Expand Up @@ -83,6 +86,7 @@ pub struct PostCollection {
pub order: SortOrder,
pub rss: Option<crate::RelPath>,
pub jsonfeed: Option<crate::RelPath>,
pub atom: Option<crate::RelPath>,
pub publish_date_in_filename: bool,
pub default: Frontmatter,
}
Expand All @@ -97,6 +101,7 @@ impl Default for PostCollection {
order: Default::default(),
rss: Default::default(),
jsonfeed: Default::default(),
atom: Default::default(),
publish_date_in_filename: true,
default: Default::default(),
}
Expand Down
8 changes: 8 additions & 0 deletions crates/config/src/frontmatter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,12 +21,16 @@ pub struct Frontmatter {
#[serde(skip_serializing_if = "Option::is_none")]
pub categories: Option<Vec<liquid_core::model::KString>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub authors: Option<Vec<liquid_core::model::KString>>,
Copy link
Member

Choose a reason for hiding this comment

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

Could you split this out into its own Issue / PR?

#[serde(skip_serializing_if = "Option::is_none")]
pub tags: Option<Vec<liquid_core::model::KString>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub excerpt_separator: Option<liquid_core::model::KString>,
#[serde(skip_serializing_if = "Option::is_none")]
pub published_date: Option<DateTime>,
#[serde(skip_serializing_if = "Option::is_none")]
pub updated_date: Option<DateTime>,
Copy link
Member

Choose a reason for hiding this comment

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

Could you split this out into its own issue / PR?

#197 exists but that is more about automatically inferring it

#[serde(skip_serializing_if = "Option::is_none")]
pub format: Option<SourceFormat>,
#[serde(skip_serializing_if = "Option::is_none")]
pub templated: Option<bool>,
Expand Down Expand Up @@ -99,11 +103,13 @@ impl Frontmatter {
slug,
title,
description,
authors,
excerpt,
categories,
tags,
excerpt_separator,
published_date,
updated_date,
format,
templated,
layout,
Expand All @@ -118,11 +124,13 @@ impl Frontmatter {
slug: slug.or_else(|| other.slug.clone()),
title: title.or_else(|| other.title.clone()),
description: description.or_else(|| other.description.clone()),
authors: authors.or_else(|| other.authors.clone()),
excerpt: excerpt.or_else(|| other.excerpt.clone()),
categories: categories.or_else(|| other.categories.clone()),
tags: tags.or_else(|| other.tags.clone()),
excerpt_separator: excerpt_separator.or_else(|| other.excerpt_separator.clone()),
published_date: published_date.or(other.published_date),
updated_date: updated_date.or(other.updated_date),
format: format.or(other.format),
templated: templated.or(other.templated),
layout: layout.or_else(|| other.layout.clone()),
Expand Down
67 changes: 67 additions & 0 deletions src/cobalt.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,16 @@ use std::collections::HashMap;
use std::fs;
use std::io::Write;
use std::path;
use std::time::SystemTime;

use failure::ResultExt;
use jsonfeed::Feed;
use log::debug;
use log::trace;
use log::warn;
use sitemap::writer::SiteMapWriter;
use vimwiki::vendor::chrono::DateTime;
use vimwiki::vendor::chrono::Utc;
Comment on lines +13 to +14
Copy link
Member

Choose a reason for hiding this comment

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

We shouldn't be relying on vimwiki like this. If we need chrono, we should use chrono.


use crate::cobalt_model;
use crate::cobalt_model::files;
Expand Down Expand Up @@ -142,6 +145,16 @@ pub fn build(config: Config) -> Result<()> {
context.site.base_url.as_deref(),
)?;
}
// check if we should creeate an atom file and create it!
if let Some(ref path) = context.posts.atom {
let path = path.to_path(&context.destination);
create_atom(
&path,
&context.posts,
&posts,
context.site.base_url.as_deref(),
)?;
}
if let Some(ref path) = context.site.sitemap {
let path = path.to_path(&context.destination);
create_sitemap(&path, &posts, &documents, context.site.base_url.as_deref())?;
Expand Down Expand Up @@ -506,6 +519,60 @@ fn create_jsonfeed(
Ok(())
}

fn create_atom(
path: &std::path::Path,
collection: &Collection,
documents: &[Document],
base_url: Option<&str>,
) -> Result<()> {
debug!("Creating Atom file at {}", path.display());

let title = &collection.title;
let description = collection.description.as_deref().unwrap_or("");
let link = base_url
.as_ref()
.ok_or_else(|| failure::err_msg("`base_url` is required for atom support"))?;

let entries: Result<Vec<atom_syndication::Entry>> =
documents.iter().map(|doc| doc.to_atom(link)).collect();
let entries = entries?;

// Assume the feed's "updated" is the same as the most recent
// updated/published post.
// If no entries exist, use the current time.
let feed_updated = entries
.iter()
.map(|entry| entry.updated)
.max()
.unwrap_or_else(|| {
atom_syndication::FixedDateTime::from(DateTime::<Utc>::from(SystemTime::now()))
});

// Build the feed object
let feed = atom_syndication::FeedBuilder::default()
.id(link.to_string())
.updated(feed_updated)
.subtitle(Some(atom_syndication::Text::plain(description.to_string())))
.links(vec![atom_syndication::Link {
href: link.to_string(),
..Default::default()
}])
.title(atom_syndication::Text::plain(title.to_string()))
.entries(entries)
.build();

// create target directories if any exist
if let Some(parent) = path.parent() {
fs::create_dir_all(parent)
.with_context(|_| failure::format_err!("Could not create {}", parent.display()))?;
}
Comment on lines +564 to +568
Copy link
Member

Choose a reason for hiding this comment

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

The other ones don't have this. Is there a reason to do it to one vs all?


let atom_string = feed.to_string();
files::write_document_file(atom_string, path)?;

Ok(())
}

fn create_sitemap(
path: &path::Path,
documents: &[Document],
Expand Down
3 changes: 3 additions & 0 deletions src/cobalt_model/collection.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ pub struct Collection {
pub order: SortOrder,
pub rss: Option<cobalt_config::RelPath>,
pub jsonfeed: Option<cobalt_config::RelPath>,
pub atom: Option<cobalt_config::RelPath>,
pub publish_date_in_filename: bool,
pub default: Frontmatter,
}
Expand Down Expand Up @@ -63,6 +64,7 @@ impl Collection {
order,
rss,
jsonfeed,
atom,
default,
publish_date_in_filename,
} = config;
Expand All @@ -87,6 +89,7 @@ impl Collection {
order,
rss,
jsonfeed,
atom,
publish_date_in_filename,
default,
};
Expand Down
6 changes: 6 additions & 0 deletions src/cobalt_model/frontmatter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,13 @@ pub struct Frontmatter {
pub slug: liquid::model::KString,
pub title: liquid::model::KString,
pub description: Option<liquid::model::KString>,
pub authors: Option<Vec<liquid::model::KString>>,
pub excerpt: Option<liquid::model::KString>,
pub categories: Vec<liquid::model::KString>,
pub tags: Option<Vec<liquid::model::KString>>,
pub excerpt_separator: liquid::model::KString,
pub published_date: Option<DateTime>,
pub updated_date: Option<DateTime>,
pub format: SourceFormat,
pub templated: bool,
pub layout: Option<liquid::model::KString>,
Expand All @@ -37,11 +39,13 @@ impl Frontmatter {
slug,
title,
description,
authors,
excerpt,
categories,
tags,
excerpt_separator,
published_date,
updated_date,
format,
templated,
layout,
Expand Down Expand Up @@ -73,11 +77,13 @@ impl Frontmatter {
slug: slug.ok_or_else(|| failure::err_msg("No slug"))?,
title: title.ok_or_else(|| failure::err_msg("No title"))?,
description,
authors,
excerpt,
categories: categories.unwrap_or_default(),
tags,
excerpt_separator: excerpt_separator.unwrap_or_else(|| "\n\n".into()),
published_date,
updated_date,
format: format.unwrap_or_default(),
#[cfg(feature = "preview_unstable")]
templated: templated.unwrap_or(false),
Expand Down
61 changes: 61 additions & 0 deletions src/document.rs
Original file line number Diff line number Diff line change
Expand Up @@ -258,6 +258,67 @@ impl Document {
}
}

// Metadata for generating Atom feeds
pub fn to_atom(&self, root_url: &str) -> Result<atom_syndication::Entry> {
let link = format!("{}/{}", root_url, &self.url_path);

// Try updated_date first; then try published_date
let updated = self.front
.updated_date
.or(self.front.published_date)
.map(|date|
atom_syndication::FixedDateTime::parse_from_rfc2822(&date.to_rfc2822()).unwrap()
)
.ok_or_else(|| failure::err_msg(
format!("Atom feed can only be generated if `published_date' or `updated_date' is added to the frontmatter of {}", self.url_path)
))?;

let authors = self
.front
.authors
.as_ref()
.ok_or_else(|| failure::err_msg(
format!("Atom feed requires authors. Please add it to the default frontmatter or the frontmatter of {}", self.url_path)
))?
.iter()
.map(|au| {
atom_syndication::PersonBuilder::default()
.name(au.to_string())
.build()
})
.collect();

let entry = atom_syndication::Entry {
id: link.clone(),
title: atom_syndication::Text::plain(self.front.title.to_string()),
updated,
summary: self
.description_to_str()
.map(|s| atom_syndication::Text::html(s)),
published: self.front.published_date.map(|date| {
atom_syndication::FixedDateTime::parse_from_rfc2822(&date.to_rfc2822()).unwrap()
}),
authors,
links: vec![atom_syndication::Link {
href: link.clone(),
..Default::default()
}],
categories: self
.front
.categories
.iter()
.map(|c| atom_syndication::Category {
term: c.as_str().to_owned(),
scheme: None,
label: None,
})
.collect(),
..Default::default()
};

Ok(entry)
}

pub fn to_sitemap<T: std::io::Write>(
&self,
root_url: &str,
Expand Down
12 changes: 12 additions & 0 deletions tests/cmd/atom.in/_cobalt.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
posts:
atom: atom.xml
default:
authors:
- Alice
- Bob
site:
title: "My blog!"
description: Blog description
base_url: "http://example.com"
syntax_highlight:
enabled: false
12 changes: 12 additions & 0 deletions tests/cmd/atom.in/_layouts/default.liquid
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<!DOCTYPE html>
<html>
<head>
<title>test</title>
</head>
<body>
<h1>{{ page.permalink }}</h1>

{{ page.content }}
</body>
</html>

10 changes: 10 additions & 0 deletions tests/cmd/atom.in/_layouts/posts.liquid
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<!DOCTYPE html>
<html>
<head>
<title>My blog - {{ page.title }}</title>
</head>
<body>
{{ page.content }}
</body>
</html>

8 changes: 8 additions & 0 deletions tests/cmd/atom.in/index.liquid
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
---
layout: default.liquid
---
This is my Index page!

{% for post in collections.posts.pages %}
<a href="{{post.permalink}}">{{ post.title }}</a>
{% endfor %}
11 changes: 11 additions & 0 deletions tests/cmd/atom.in/posts/my-fifth-blogpost.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
---
layout: posts.liquid

title: My fifth Blogpost!
published_date: 2016-02-16 10:00:00 +0100
---
# {{ page.title }}

Hey there this is my first blogpost and this is super awesome.

My Blog is lorem ipsum like, yes it is..
Loading