Commit 2d8df733 authored by Daniel Leidert's avatar Daniel Leidert

New upstream version 0.12.1

parent a93fa344
......@@ -18,3 +18,4 @@ Gemfile.lock
spec/dest
.bundle
spec/fixtures/.jekyll-metadata
spec/fixtures/.jekyll-cache
require: rubocop-jekyll
inherit_gem:
jekyll: .rubocop.yml
rubocop-jekyll: .rubocop.yml
AllCops:
TargetRubyVersion: 2.3
Include:
- lib/*.rb
- lib/**/*.rb
Exclude:
- .rubocop.yml
- .codeclimate.yml
- .travis.yml
- .gitignore
- .rspec
- .rubocop.yml
- .travis.yml
- Gemfile.lock
- CHANGELOG.md
- readme.md
- History.markdown
- LICENSE.txt
- README.md
- Readme.md
- ReadMe.md
- COPYING
- LICENSE
- test/**/*
- vendor/**/*
- features/**/*
- script/**/*
- spec/**/*
- vendor/**/*
Naming/MemoizedInstanceVariableName:
Exclude:
- lib/jekyll-feed/page-without-a-file.rb
language: ruby
cache: bundler
sudo: false
rvm:
- 2.5
- 2.4
- 2.3
- 2.2
- &latest_ruby 2.6
- 2.4
- 2.3
env:
global:
- NOKOGIRI_USE_SYSTEM_LIBRARIES=true
- NOKOGIRI_USE_SYSTEM_LIBRARIES=true
matrix:
- JEKYLL_VERSION="~> 3.8"
matrix:
include:
- rvm: *latest_ruby
env: JEKYLL_VERSION="~> 3.7.4"
- rvm: *latest_ruby
env: JEKYLL_VERSION=">= 4.0.0.pre.alpha1"
before_install:
- gem update --system
- gem install bundler
before_script: bundle update
script: script/cibuild
notifications:
......
......@@ -3,6 +3,9 @@
source "https://rubygems.org"
gemspec
if ENV["JEKYLL_VERSION"]
gem "jekyll", "~> #{ENV["JEKYLL_VERSION"]}"
gem "jekyll", ENV["JEKYLL_VERSION"] if ENV["JEKYLL_VERSION"]
install_if -> { Gem.win_platform? } do
gem "tzinfo", "~> 1.2"
gem "tzinfo-data"
end
## 0.12.1 / 2019-03-23
* Release: v0.12.0 (#271)
### Bug Fixes
* Re-introduce Ruby 2.3 support and test Jekyll 3.7+ (#272)
## 0.12.0 / 2019-03-21
* Allow Jekyll v4 (still alpha)
### Development Fixes
* style: fix offenses in specs (#248)
* dev: update CI and style settings (#258)
* Enable testing for Windows platform (#265)
## 0.11.0 / 2018-09-09
### Development Fixes
* Require Ruby 2.3 (#222)
* Refactor to remove redundant calls and variables (#240)
### Minor Enhancements
* Categories and collections (#228)
* Remove check for older version of Jekyll (#234)
## 0.10.0 / 2018-06-04
### Bug Fixes
* Escape image URL (#209)
### Development Fixes
* Rubocop 0.55 (#223)
* Bump Rubocop (#230)
### Minor Enhancements
* Support Typhoeus 1.0 (#232)
## 0.9.3 / 2018-02-04
* Define path with __dir (#187)
......@@ -7,6 +52,7 @@
* Fix: Add note about using plugins instead of gems key (#197)
* Add documentation for disabling smartify filter (#205)
* Use `https` in more places. (#165)
### Development Fixes
......@@ -14,15 +60,11 @@
* Test feeds that have a `site.lang` (#164)
* Test against Ruby 2.5 (#201)
## 0.9.3 / 2017-03-28
### Minor Enhancements
* fix <entry> template for posts with post.lang defined (#168)
### Documentation
* Use `https` in more places. (#165)
## 0.9.3 / 2017-03-28
## 0.9.1 / 2017-02-17
......
......@@ -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:
```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)
......
version: "{build}"
clone_depth: 5
build: off
environment:
NOKOGIRI_USE_SYSTEM_LIBRARIES: true
JEKYLL_VERSION: "~> 3.8"
matrix:
- RUBY_FOLDER_VER: "26"
JEKYLL_VERSION : "~> 3.7.4"
- RUBY_FOLDER_VER: "26"
JEKYLL_VERSION : ">= 4.0.0.pre.alpha1"
- RUBY_FOLDER_VER: "26"
- RUBY_FOLDER_VER: "24"
- RUBY_FOLDER_VER: "23"
install:
- SET PATH=C:\Ruby%RUBY_FOLDER_VER%-x64\bin;%PATH%
- bundle install --retry 5 --jobs=%NUMBER_OF_PROCESSORS% --clean --path vendor\bundle
test_script:
- ruby --version
- gem --version
- bundler --version
- bash ./script/test
cache:
# If one of the files after the right arrow changes, cache will be invalidated
- 'vendor\bundle -> appveyor.yml, Gemfile, jekyll-feed.gemspec'
......@@ -14,16 +14,17 @@ Gem::Specification.new do |spec|
spec.license = "MIT"
spec.files = `git ls-files -z`.split("\x0")
spec.executables = spec.files.grep(%r!^bin/!) { |f| File.basename(f) }
spec.test_files = spec.files.grep(%r!^(test|spec|features)/!)
spec.test_files = spec.files.grep(%r!^spec/!)
spec.require_paths = ["lib"]
spec.add_dependency "jekyll", "~> 3.3"
spec.required_ruby_version = ">= 2.3.0"
spec.add_development_dependency "bundler", "~> 1.15"
spec.add_dependency "jekyll", ">= 3.7", "< 5.0"
spec.add_development_dependency "bundler"
spec.add_development_dependency "nokogiri", "~> 1.6"
spec.add_development_dependency "rake", "~> 12.0"
spec.add_development_dependency "rspec", "~> 3.0"
spec.add_development_dependency "rubocop", "0.51"
spec.add_development_dependency "typhoeus", "~> 0.7"
spec.add_development_dependency "rubocop-jekyll", "~> 0.5"
spec.add_development_dependency "typhoeus", ">= 0.7", "< 2.0"
end
......@@ -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 %}
......@@ -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>
......@@ -72,9 +83,9 @@
{% assign post_image = post.image.path | default: post.image %}
{% if post_image %}
{% unless post_image contains "://" %}
{% assign post_image = post_image | absolute_url | xml_escape %}
{% assign post_image = post_image | absolute_url %}
{% endunless %}
<media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="{{ post_image }}" />
<media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="{{ post_image | xml_escape }}" />
{% endif %}
</entry>
{% endfor %}
......
......@@ -8,8 +8,15 @@ module JekyllFeed
# 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|
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
......@@ -18,40 +25,88 @@ module JekyllFeed
# 1. A '>', which closes an XML tag or
# 2. A '}', which closes a Liquid tag
# 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"]
else
"feed.xml"
MINIFY_REGEX = %r!(?<=>|})\s+!.freeze
# Returns the plugin's config or an empty hash if not set
def config
@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}"
return "#{prefix}/#{category}.xml" if category
collections.dig(collection, "path") || "#{prefix}.xml"
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_value do |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
def file_exists?(file_path)
if @site.respond_to?(:in_source_dir)
File.exist? @site.in_source_dir(file_path)
else
File.exist? Jekyll.sanitized_path(@site.source, file_path)
end
File.exist? @site.in_source_dir(file_path)
end
# Generates contents for a file
def content_for_file(file_path, file_source_path)
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.output
file
def make_page(file_path, collection: "posts", category: nil)
PageWithoutAFile.new(@site, __dir__, "", file_path).tap do |file|
file.content = feed_template
file.data.merge!(
"layout" => nil,
"sitemap" => false,
"xsl" => file_exists?("feed.xslt.xml"),
"collection" => collection,
"category" => category
)
file.output
end
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
......@@ -14,7 +14,7 @@ module JekyllFeed
private
def config
@context.registers[:site].config
@config ||= @context.registers[:site].config
end
def attributes
......@@ -27,11 +27,7 @@ module JekyllFeed
end
def path
if config["feed"] && config["feed"]["path"]
config["feed"]["path"]
else
"feed.xml"
end
config.dig("feed", "path") || "feed.xml"
end
def title
......
......@@ -2,6 +2,6 @@
module Jekyll
module Feed
VERSION = "0.9.3"
VERSION = "0.12.1"
end
end
---
category: news
---
Look at me! I'm a collection doc in a category!
......@@ -4,6 +4,6 @@ defaults:
-
scope:
path: ""
type: page
type: pages
values:
layout: some_default
---
excerpt: "Foo"
image: "/image.png"
category: news
---
# December the twelfth, actually.
---
image: "https://cdn.example.org/absolute.png"
image: https://cdn.example.org/absolute.png?h=188&w=250
category: news
---
March the second!
......@@ -3,6 +3,7 @@ tags:
- '"/><VADER>'
image:
path: "/object-image.png"
category: updates
---
March the fourth!
require 'spec_helper'
# frozen_string_literal: true
require "spec_helper"
describe(JekyllFeed) do
let(:overrides) { Hash.new }
let(:overrides) { {} }
let(:config) do
Jekyll.configuration(Jekyll::Utils.deep_merge_hashes({
"full_rebuild" => true,
"source" => source_dir,
"destination" => dest_dir,
"show_drafts" => true,
"url" => "http://example.org",
"name" => "My awesome site",
"author" => {
"name" => "Dr. Jekyll"
"source" => source_dir,
"destination" => dest_dir,
"show_drafts" => true,
"url" => "http://example.org",
"name" => "My awesome site",
"author" => {
"name" => "Dr. Jekyll",
},
"collections" => {
"collections" => {
"my_collection" => { "output" => true },
"other_things" => { "output" => false }
}
"other_things" => { "output" => false },
},
}, overrides))
end
let(:site) { Jekyll::Site.new(config) }
let(:contents) { File.read(dest_dir("feed.xml")) }
let(:context) { make_context(site: site) }
let(:context) { make_context(:site => site) }
let(:feed_meta) { Liquid::Template.parse("{% feed_meta %}").render!(context, {}) }
before(:each) do
site.process
end
it "has no layout" do
expect(contents).not_to match(/\ATHIS IS MY LAYOUT/)
expect(contents).not_to match(%r!\ATHIS IS MY LAYOUT!)
end
it "creates a feed.xml file" do
expect(Pathname.new(dest_dir("feed.xml"))).to exist
end
it "doesn't have multiple new lines or trailing whitespace" do
expect(contents).to_not match /\s+\n/
expect(contents).to_not match /\n{2,}/
expect(contents).to_not match %r!\s+\n!
expect(contents).to_not match %r!\n{2,}!
end
it "puts all the posts in the feed.xml file" do
expect(contents).to match /http:\/\/example\.org\/2014\/03\/04\/march-the-fourth\.html/
expect(contents).to match /http:\/\/example\.org\/2014\/03\/02\/march-the-second\.html/
expect(contents).to match /http:\/\/example\.org\/2013\/12\/12\/dec-the-second\.html/
expect(contents).to match "http://example.org/updates/2014/03/04/march-the-fourth.html"
expect(contents).to match "http://example.org/news/2014/03/02/march-the-second.html"
expect(contents).to match "http://example.org/news/2013/12/12/dec-the-second.html"
expect(contents).to match "http://example.org/2015/08/08/stuck-in-the-middle.html"
expect(contents).to_not match /http:\/\/example\.org\/2016\/02\/09\/a-draft\.html/
expect(contents).to_not match "http://example.org/2016/02/09/a-draft.html"
end
it "does not include assets or any static files that aren't .html" do
expect(contents).not_to match /http:\/\/example\.org\/images\/hubot\.png/
expect(contents).not_to match /http:\/\/example\.org\/feeds\/atom\.xml/
expect(contents).not_to match "http://example.org/images/hubot.png"
expect(contents).not_to match "http://example.org/feeds/atom.xml"
end
it "preserves linebreaks in preformatted text in posts" do
expect(contents).to match /Line 1\nLine 2\nLine 3/
expect(contents).to match "Line 1\nLine 2\nLine 3"
end
it "supports post author name as an object" do
expect(contents).to match /<author>\s*<name>Ben<\/name>\s*<email>ben@example.com<\/email>\s*<uri>http:\/\/ben.balter.com<\/uri>\s*<\/author>/
expect(contents).to match %r!<author>\s*<name>Ben</name>\s*<email>ben@example\.com</email>\s*<uri>http://ben\.balter\.com</uri>\s*</author>!
end
it "supports post author name as a string" do
expect(contents).to match /<author>\s*<name>Pat<\/name>\s*<\/author>/
expect(contents).to match %r!<author>\s*<name>Pat</name>\s*</author>!
end
it "does not output author tag no author is provided" do
expect(contents).not_to match /<author>\s*<name><\/name>\s*<\/author>/
expect(contents).not_to match %r!<author>\s*<name></name>\s*</author>!
end
it "does use author reference with data from _data/authors.yml" do
expect(contents).to match /<author>\s*<name>Garth<\/name>\s*<email>example@mail.com<\/email>\s*<uri>http:\/\/garthdb.com<\/uri>\s*<\/author>/
expect(contents).to match %r!<author>\s*<name>Garth</name>\s*<email>example@mail\.com</email>\s*<uri>http://garthdb\.com</uri>\s*</author>!
end
it "converts markdown posts to HTML" do
expect(contents).to match /&lt;p&gt;March the second!&lt;\/p&gt;/
expect(contents).to match %r!&lt;p&gt;March the second\!&lt;/p&gt;!
end
it "uses last_modified_at where available" do
expect(contents).to match /<updated>2015-05-12T13:27:59\+00:00<\/updated>/
expect(contents).to match %r!<updated>2015-05-12T13:27:59\+00:00</updated>!
end
it "replaces newlines in posts to spaces" do
expect(contents).to match %r!<title type="html">The plugin will properly strip newlines.</title>!
expect(contents).to match '<title type="html">The plugin will properly strip newlines.</title>'
end
it "renders Liquid inside posts" do
expect(contents).to match /Liquid is rendered\./
expect(contents).not_to match /Liquid is not rendered\./
expect(contents).to match "Liquid is rendered."
expect(contents).not_to match "Liquid is not rendered."
end
it "includes the item image" do
expect(contents).to include('<media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="http://example.org/image.png" />')
expect(contents).to include('<media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://cdn.example.org/absolute.png" />')
expect(contents).to include('<media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://cdn.example.org/absolute.png?h=188&amp;w=250" />')
expect(contents).to include('<media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="http://example.org/object-image.png" />')
end
......@@ -124,7 +125,7 @@ describe(JekyllFeed) do
it "includes item contents" do
post = feed.items.last
expect(post.title.content).to eql("Dec The Second")
expect(post.link.href).to eql("http://example.org/2013/12/12/dec-the-second.html")
expect(post.link.href).to eql("http://example.org/news/2013/12/12/dec-the-second.html")
expect(post.published.content).to eql(Time.parse("2013-12-12"))
end
......@@ -140,7 +141,7 @@ describe(JekyllFeed) do
context "with site.lang set" do
lang = "en_US"
let(:overrides) { {"lang" => lang} }
let(:overrides) { { "lang" => lang } }
it "outputs a valid feed" do
expect(feed.feed_type).to eql("atom")
expect(feed.feed_version).to eql("1.0")
......@@ -165,7 +166,7 @@ describe(JekyllFeed) do
context "with site.title set" do
let(:site_title) { "My Site Title" }
let(:overrides) { {"title" => site_title} }
let(:overrides) { { "title" => site_title } }
it "uses site.title for the title" do
expect(feed.title.content).to eql(site_title)
......@@ -174,7 +175,7 @@ describe(JekyllFeed) do
context "with site.name set" do
let(:site_name) { "My Site Name" }
let(:overrides) { {"name" => site_name} }
let(:overrides) { { "name" => site_name } }
it "uses site.name for the title" do
expect(feed.title.content).to eql(site_name)
......@@ -184,7 +185,7 @@ describe(JekyllFeed) do
context "with site.name and site.title set" do
let(:site_title) { "My Site Title" }
let(:site_name) { "My Site Name" }
let(:overrides) { {"title" => site_title, "name" => site_name} }
let(:overrides) { { "title" => site_title, "name" => site_name } }
it "uses site.title for the title, dropping site.name" do
expect(feed.title.content).to eql(site_title)
......@@ -204,22 +205,23 @@ describe(JekyllFeed) do
context "validation" do
it "validates" do
skip "Typhoeus couldn't find the 'libcurl' module on Windows" if Gem.win_platform?
# See https://validator.w3.org/docs/api.html
url = "https://validator.w3.org/feed/check.cgi?output=soap12"
response = Typhoeus.post(url, body: { rawdata: contents }, accept_encoding: "gzip")
response = Typhoeus.post(url, :body => { :rawdata => contents }, :accept_encoding => "gzip")
pending "Something went wrong with the W3 validator" unless response.success?
result = Nokogiri::XML(response.body)
result = Nokogiri::XML(response.body)
result.remove_namespaces!
result.css("warning").each do |warning|
# Quiet a warning that results from us passing the feed as a string
next if warning.css("text").text =~ /Self reference doesn't match document location/
next if warning.css("text").text =~ %r!Self reference doesn't match document location!
# Quiet expected warning that results from blank summary test case
next if warning.css("text").text =~ /(content|summary) should not be blank/
next if warning.css("text").text =~ %r!(content|summary) should not be blank!
# Quiet expected warning about multiple posts with same updated time
next if warning.css("text").text =~ /Two entries with the same value for atom:updated/
next if warning.css("text").text =~ %r!Two entries with the same value for atom:updated!
warn "Validation warning: #{warning.css("text").text} on line #{warning.css("line").text} column #{warning.css("column").text}"
end
......@@ -238,9 +240,9 @@ describe(JekyllFeed) do
end
it "correctly adds the baseurl to the posts" do
expect(contents).to match /http:\/\/example\.org\/bass\/2014\/03\/04\/march-the-fourth\.html/
expect(contents).to match /http:\/\/example\.org\/bass\/2014\/03\/02\/march-the-second\.html/
expect(contents).to match /http:\/\/example\.org\/bass\/2013\/12\/12\/dec-the-second\.html/
expect(contents).to match "http://example.org/bass/updates/2014/03/04/march-the-fourth.html"
expect(contents).to match "http://example.org/bass/news/2014/03/02/march-the-second.html"
expect(contents).to match "http://example.org/bass/news/2013/12/12/dec-the-second.html"