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

Categories and collections #228

Merged
merged 9 commits into from
Sep 6, 2018
42 changes: 42 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,48 @@ Want to style what your feed looks like in the browser? Simply add an XSLT at `/

Great question. In short, Atom is a better format. Think of it like RSS 3.0. For more information, see [this discussion on why we chose Atom over RSS 2.0](https://github.com/jekyll/jekyll-rss-feed/issues/2).

## Categories

Jekyll Feed can generate feeds for each category. Simply define which categories you'd like feeds for in your config:

```yml
feed:
categories:
- news
- updates
```

## Collections

Jekyll Feed can generate feeds for collections other than the Posts collection. This works best for chronological collections (e.g., collections with dates in the filenames). Simply define which collections you'd like feeds for in your config:

```yml
feed:
collections:
- changes
```

By default, collection feeds will be outputted to `/feed/<COLLECTION>.xml`. If you'd like to customize the output path, specify a collection's custom path as follows:
Copy link
Member

Choose a reason for hiding this comment

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

IMHO ..will be output to reads better..

Choose a reason for hiding this comment

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

Or active voice: "The default output-path for collection feeds is /feed/<COLLECTION>.xml"


```yml
feed:
collections:
changes:
path: "/changes.xml"
```

Finally, collections can also have category feeds which are outputted as `/feed/<COLLECTION>/<CATEGORY>.xml`. Specify categories like so:

```yml
feed:
collections:
changes:
path: "/changes.xml"
categories:
- news
- updates
```

## Contributing

1. Fork it (https://github.com/jekyll/jekyll-feed/fork)
Expand Down
23 changes: 17 additions & 6 deletions lib/jekyll-feed/feed.xml
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,20 @@
<link href="{{ page.url | absolute_url }}" rel="self" type="application/atom+xml" />
<link href="{{ '/' | absolute_url }}" rel="alternate" type="text/html" {% if site.lang %}hreflang="{{ site.lang }}" {% endif %}/>
<updated>{{ site.time | date_to_xmlschema }}</updated>
<id>{{ '/' | absolute_url | xml_escape }}</id>
<id>{{ page.url | absolute_url | xml_escape }}</id>

{% if site.title %}
<title type="html">{{ site.title | smartify | xml_escape }}</title>
{% elsif site.name %}
<title type="html">{{ site.name | smartify | xml_escape }}</title>
{% assign title = site.title | default: site.name %}
{% if page.collection != "posts" %}
{% assign collection = page.collection | capitalize %}
{% assign title = title | append: " | " | append: collection %}
{% endif %}
{% if page.category %}
{% assign category = page.category | capitalize %}
{% assign title = title | append: " | " | append: category %}
{% endif %}

{% if title %}
<title type="html">{{ title | smartify | xml_escape }}</title>
{% endif %}

{% if site.description %}
Expand All @@ -31,7 +39,10 @@
</author>
{% endif %}

{% assign posts = site.posts | where_exp: "post", "post.draft != true" %}
{% assign posts = site[page.collection] | where_exp: "post", "post.draft != true" | sort: "date" | reverse %}
{% if page.category %}
{% assign posts = posts | where: "category",page.category %}
{% endif %}
{% for post in posts limit: 10 %}
<entry{% if post.lang %}{{" "}}xml:lang="{{ post.lang }}"{% endif %}>
<title type="html">{{ post.title | smartify | strip_html | normalize_whitespace | xml_escape }}</title>
Expand Down
88 changes: 75 additions & 13 deletions lib/jekyll-feed/generator.rb
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,14 @@ class Generator < Jekyll::Generator
# Main plugin action, called by Jekyll-core
def generate(site)
@site = site
return if file_exists?(feed_path)
@site.pages << content_for_file(feed_path, feed_source_path)
collections.each do |name, meta|
Jekyll.logger.info "Jekyll Feed:", "Generating feed for #{name}"
(meta["categories"] + [nil]).each do |category|
ashmaroli marked this conversation as resolved.
Show resolved Hide resolved
path = feed_path(:collection => name, :category => category)
next if file_exists?(path)
@site.pages << make_page(path, :collection => name, :category => category)
end
end
end

private
Expand All @@ -20,18 +26,59 @@ def generate(site)
# We will strip all of this whitespace to minify the template
MINIFY_REGEX = %r!(?<=>|})\s+!

# Path to feed from config, or feed.xml for default
def feed_path
if @site.config["feed"] && @site.config["feed"]["path"]
@site.config["feed"]["path"]
# Returns the plugin's config or an empty hash if not set
def config
@site.config["feed"] || {}
end

# Determines the destination path of a given feed
#
# collection - the name of a collection, e.g., "posts"
# category - a category within that collection, e.g., "news"
#
# Will return "feed.xml", or the config-specified default feed for posts
# Will return `/feed/category.xml` for post categories
# WIll return `/feed/collection.xml` for other collections
# Will return `/feed/collection/category.xml` for other collection categories
def feed_path(collection: "posts", category: nil)
prefix = collection == "posts" ? "/feed" : "feed/#{collection}"
Copy link
Member

Choose a reason for hiding this comment

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

why is there a leading slash for /feed.xml but not for feed/#{collection}.xml .. also, the comments above actually states the opposite..

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Good catch. That is an oversight. Will push up a fix.

if category
"#{prefix}/#{category}.xml"
elsif collections[collection]["path"]
Copy link
Member

Choose a reason for hiding this comment

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

We should do nil checking here in case collections is nil or collections[collection] is nil

collections[collection]["path"]
else
"feed.xml"
"#{prefix}.xml"
end
end

# Returns a hash representing all collections to be processed and their metadata
# in the form of { collection_name => { categories = [...], path = "..." } }
def collections
return @collections if defined?(@collections)

@collections = if config["collections"].is_a?(Array)
config["collections"].map { |c| [c, {}] }.to_h
elsif config["collections"].is_a?(Hash)
config["collections"]
else
{}
end

@collections = normalize_posts_meta(@collections)
@collections.each do |_name, meta|
meta["categories"] = (meta["categories"] || []).to_set
end

@collections
end

# Path to feed.xml template file
def feed_source_path
File.expand_path "feed.xml", __dir__
@feed_source_path ||= File.expand_path "feed.xml", __dir__
end

def feed_template
@feed_template ||= File.read(feed_source_path).gsub(MINIFY_REGEX, "")
end

# Checks if a file already exists in the site source
Expand All @@ -44,14 +91,29 @@ def file_exists?(file_path)
end

# Generates contents for a file
def content_for_file(file_path, file_source_path)

def make_page(file_path, collection: "posts", category: nil)
file = PageWithoutAFile.new(@site, __dir__, "", file_path)
file.content = File.read(file_source_path).gsub(MINIFY_REGEX, "")
file.data["layout"] = nil
file.data["sitemap"] = false
file.data["xsl"] = file_exists?("feed.xslt.xml")
file.content = feed_template
file.data.merge!({
"layout" => nil,
"sitemap" => false,
"xsl" => file_exists?("feed.xslt.xml"),
"collection" => collection,
"category" => category,
})
file.output
file
end

# Special case the "posts" collection, which, for ease of use and backwards
# compatability, can be configured via top-level keys or directly as a collection
def normalize_posts_meta(hash)
hash["posts"] ||= {}
hash["posts"]["path"] ||= config["path"]
hash["posts"]["categories"] ||= config["categories"]
config["path"] ||= hash["posts"]["path"]
hash
end
end
end
4 changes: 4 additions & 0 deletions spec/fixtures/_collection/2018-01-01-collection-doc.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
---
---

Look at me! I'm a collection!
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
category: news
---

Look at me! I'm a collection doc in a category!
1 change: 1 addition & 0 deletions spec/fixtures/_posts/2013-12-12-dec-the-second.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
---
excerpt: "Foo"
image: "/image.png"
category: news
---

# December the twelfth, actually.
1 change: 1 addition & 0 deletions spec/fixtures/_posts/2014-03-02-march-the-second.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
---
image: https://cdn.example.org/absolute.png?h=188&w=250
category: news
---

March the second!
1 change: 1 addition & 0 deletions spec/fixtures/_posts/2014-03-04-march-the-fourth.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ tags:
- '"/><VADER>'
image:
path: "/object-image.png"
category: updates
---

March the fourth!
Loading