Commit a93fa344 authored by Daniel Leidert's avatar Daniel Leidert

Merge branch 'dleidert/ruby-jekyll-feed-upstream' into upstream

parents 4f01de0d 5cbea4ad
inherit_gem:
jekyll: .rubocop.yml
AllCops:
TargetRubyVersion: 2.3
Include:
- lib/*.rb
Exclude:
- .rubocop.yml
- .codeclimate.yml
- .travis.yml
- .gitignore
- .rspec
- Gemfile.lock
- CHANGELOG.md
- readme.md
- README.md
- Readme.md
- ReadMe.md
- COPYING
- LICENSE
- test/**/*
- vendor/**/*
- features/**/*
- script/**/*
- spec/**/*
language: ruby
rvm:
- 2.1
- 2.2
- 2.0
- 1.9
script: "script/cibuild"
before_script: bundle update
cache: bundler
sudo: false
matrix:
exclude:
- rvm: 1.9
env: JEKYLL_VERSION=3.0.0.beta5
- env: JEKYLL_VERSION=2.4
rvm: 2.1
- rvm: 2.2
env: JEKYLL_VERSION=2.4
rvm:
- 2.5
- 2.4
- 2.3
- 2.2
env:
matrix:
- ""
- JEKYLL_VERSION=2.4
- JEKYLL_VERSION=3.0.0.beta5
global:
- NOKOGIRI_USE_SYSTEM_LIBRARIES=true
before_install:
- gem update --system
before_script: bundle update
script: script/cibuild
notifications:
email: false
deploy:
provider: rubygems
api_key:
secure: qz0q6ur0kGo03jjLdoAak6WcEAwxX2r9LG3DVrhOrcfoFipkuW+uwR0et4tpK8uFrz0P9y7eTIKOb0XCXeIsIXWj6R5benpRGr2U8m9A+tE/jxviBFUaxaokte0lqWiX1fEyhRmW3zvcdLQ47Vd2EwTNaq6ZmPulmEe9gS0rBQghyclakGlZ17LI7oGgiNL9SQ335Yqa1qJklTHYHbodWQ3Z07v7VN2jxqi3WH6NacT5gUGp5iCNCLLa8+jpKr4uONNIoy6/geAWdqtvgGUE8oTjIWDoJarrknJpqfx9Rd0KLDzkyneAigHDYPW60QtrE6GGpK/+TF1pF4DzdK2EgTWqGFnZf8ehfnxmtHVl2Xq/DPr6hS8Q/f+ut4ioMzBQxPD0hfh8/EOMYKsO8mOuOlYTiZXC7iuGyvFUOl2hnBgWA99t+I0NNB06qFp3ZxIjolEc3zjzc9f1a5HUXlEut5V8nqvCwbctNiTVpT8ZEWlsQlyRUnr9cIMUTEfLgQ+v6DnvAJBMO1EILq6liB5qfutjNhzhlREt7P/ZdppGsAzWpgt0q2PafqVoPe62WR3+/8Lj2ErMr034xSSqZVNcBS0mbdvW6k3jaABo1VJ4XuHm6/yDuemWzWb7kdG9/14+IIJMW1VuaWcmnCnB6gxjkCW3Dm2ftYiN7Rfn3AUz/nU=
gem: jekyll-feed
on:
tags: true
repo: jekyll/jekyll-feed
source 'https://rubygems.org'
# frozen_string_literal: true
source "https://rubygems.org"
gemspec
if ENV["JEKYLL_VERSION"]
......
## 0.9.3 / 2018-02-04
* Define path with __dir (#187)
* Bump Ruby for Travis (#188)
### Documentation
* Fix: Add note about using plugins instead of gems key (#197)
* Add documentation for disabling smartify filter (#205)
### Development Fixes
* Rubocop: Target Ruby 2.2 (#195)
* 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.1 / 2017-02-17
### Minor Enhancements
* Update feed.xml (#162)
## 0.9.0 / 2017-02-16
### Minor Enhancements
* Use absolute_url to generate the feed_meta url (#150)
* Make feed stylesheet optional (#149)
* Use new `normalize_whitespace` filter (#143)
* Feed entries must contain <author> (#152)
* Remove trailing slash from feed ID (#159)
### Development Fixes
* Simplify minify regular expression (#141)
* Namespace as JekyllFeed (#151)
* rubocop -a (#160)
### Bug Fixes
* Filter out drafts before limit (#154)
## 0.8.0 / 2016-10-06
* Use filters to clean up Liquid template (#134)
### Minor Enhancements
* Don't set @site.config["time"] on feed generation (#138)
### pedantry
* Appease Rubocop (#139)
## 0.7.2 / 2016-10-06
* Support `image.path` when `post.image` is an object (#137)
## 0.7.1 / 2016-09-26
* Assign `url_base` before first usage (#133)
## 0.7.0 / 2016-09-06
* Use type="html" to skirt around double escaping problem (#127)
## 0.6.0 / 2016-07-08
* Cleanup `post_author` logic (#113)
* Add XML stylesheet example with XSLT (#119)
* DRY up and add more doc (#120)
* Use smartify filter (#117)
## 0.5.1 / 2016-04-18
* Fix mangling of whitespace when `site.lang` is set (#110)
## 0.5.0 / 2016-04-13
* Consolidate regexps for stripping whitespace (#82)
* Only test against Jekyll 3 (#99)
* Think about how i18n might work (#75)
* Find author by reference (#106)
* Drop support for Jekyll 2 (#105)
* Add support for post image (#104)
### Minor Enhancements
* Use Module#method_defined? (#83)
* Use site.title for meta tag if available (#100)
### Development Fixes
* Do not require [**jekyll-last-modified-at**](https://github.com/gjtorikian/jekyll-last-modified-at) in tests (#87)
* Add Rubocop (#81)
* Correct typo in tests (#102)
* Simplify testing feed_meta tag (#101)
* Quiet known warnings in tests (#103)
## 0.4.0 / 2015-12-30
* Feed uses `site.title`, or `site.name` if `title` doesn't exist (#72)
* Replace newlines with spaces in `title` and `summary` elements (#67)
* Properly render post content with Jekyll (#73)
Copyright (c) 2015 Ben Balter
Copyright (c) 2015-present Ben Balter and jekyll-feed contributors
MIT License
......
......@@ -2,7 +2,7 @@
A Jekyll plugin to generate an Atom (RSS-like) feed of your Jekyll posts
[![Build Status](https://travis-ci.org/jekyll/jekyll-feed.svg)](https://travis-ci.org/jekyll/jekyll-feed) [![Gem Version](https://badge.fury.io/rb/jekyll-feed.svg)](http://badge.fury.io/rb/jekyll-feed)
[![Build Status](https://travis-ci.org/jekyll/jekyll-feed.svg)](https://travis-ci.org/jekyll/jekyll-feed) [![Gem Version](https://badge.fury.io/rb/jekyll-feed.svg)](https://badge.fury.io/rb/jekyll-feed)
## Installation
......@@ -15,10 +15,12 @@ gem 'jekyll-feed'
And then add this line to your site's `_config.yml`:
```yml
gems:
plugins:
- jekyll-feed
```
:warning: If you are using Jekyll < 3.5.0 use the `gems` key instead of `plugins`.
## Usage
The plugin will automatically generate an Atom feed at `/feed.xml`.
......@@ -27,13 +29,10 @@ The plugin will automatically generate an Atom feed at `/feed.xml`.
The plugin will automatically use any of the following configuration variables, if they are present in your site's `_config.yml` file.
* `name` - The title of the site, e.g., "My awesome site"
* `title` or `name` - The title of the site, e.g., "My awesome site"
* `description` - A longer description of what your site is about, e.g., "Where I blog about Jekyll and other awesome things"
* `url` - The URL to your site, e.g., `http://example.com`. If none is provided, the plugin will try to use `site.github.url`.
* `author` - Your name, e.g., "Dr. Jekyll." This can be a string (with the author's name), or an object with the following properties:
- `name` - **Required** Display name of the author
- `email` - Email address of the author
- `uri` - Webpage where more information about the author can be found
* `author` - Global author information (see below)
### Already have a feed path?
......@@ -59,12 +58,84 @@ The plugin will use the following post metadata, automatically generated by Jeky
Additionally, the plugin will use the following values, if present in a post's YAML front matter:
* `author` - The author of the post, e.g., "Dr. Jekyll". If none is given, feed readers will look to the feed author as defined in `_config.yml`. Like the feed author, this can also be an object.
* `image` - URL of an image that is representative of the post (can also be passed as `image.path`)
* `author` - The author of the post, e.g., "Dr. Jekyll". If none is given, feed readers will look to the feed author as defined in `_config.yml`. Like the feed author, this can also be an object or a reference to an author in `_data/authors.yml` (see below).
### Author information
*TL;DR: In most cases, put `author: [your name]` in the document's front matter, for sites with multiple authors. If you need something more complicated, read on.*
There are several ways to convey author-specific information. Author information is found in the following order of priority:
1. An `author` object, in the documents's front matter, e.g.:
```yml
author:
twitter: benbalter
```
2. An `author` object, in the site's `_config.yml`, e.g.:
```yml
author:
twitter: benbalter
```
3. `site.data.authors[author]`, if an author is specified in the document's front matter, and a corresponding key exists in `site.data.authors`. E.g., you have the following in the document's front matter:
```yml
author: benbalter
```
And you have the following in `_data/authors.yml`:
```yml
benbalter:
picture: /img/benbalter.png
twitter: jekyllrb
potus:
picture: /img/potus.png
twitter: whitehouse
```
In the above example, the author `benbalter`'s Twitter handle will be resolved to `@jekyllrb`. This allows you to centralize author information in a single `_data/authors` file for site with many authors that require more than just the author's username.
*Pro-tip: If `authors` is present in the document's front matter as an array (and `author` is not), the plugin will use the first author listed.*
4. An author in the document's front matter (the simplest way), e.g.:
```yml
author: benbalter
```
5. An author in the site's `_config.yml`, e.g.:
```yml
author: benbalter
```
### Meta tags
The plugin exposes a helper tag to expose the appropriate meta tags to support automated discovery of your feed. Simply place `{% feed_meta %}` someplace in your template's `<head>` section, to output the necessary metadata.
### SmartyPants
The plugin uses [Jekyll's `smartify` filter](https://jekyllrb.com/docs/templates/) for processing the site title and post titles. This will translate plain ASCII punctuation into "smart" typographic punctuation. This will not render or strip any Markdown you may be using in a title.
Jekyll's `smartify` filter uses [kramdown](https://kramdown.gettalong.org/options.html) as a processor. Accordingly, if you do not want "smart" typographic punctuation, disabling them in kramdown in your `_config.yml` will disable them in your feed. For example:
```yml
kramdown:
smart_quotes: apos,apos,quot,quot
typographic_symbols: {hellip: ...}
```
### Custom styling
Want to style what your feed looks like in the browser? Simply add an XSLT at `/feed.xslt.xml` and Jekyll Feed will link to the stylesheet.
## Why Atom, and not RSS?
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).
......
# frozen_string_literal: true
require "bundler/gem_tasks"
require 'rspec/core/rake_task'
require "rspec/core/rake_task"
RSpec::Core::RakeTask.new(:spec)
......
# coding: utf-8
# frozen_string_literal: true
lib = File.expand_path("lib", __dir__)
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
require "jekyll-feed/version"
Gem::Specification.new do |spec|
spec.name = "jekyll-feed"
spec.version = "0.3.1"
spec.version = Jekyll::Feed::VERSION
spec.authors = ["Ben Balter"]
spec.email = ["ben.balter@github.com"]
spec.summary = "A Jekyll plugin to generate an Atom feed of your Jekyll posts"
......@@ -10,15 +14,16 @@ 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.executables = spec.files.grep(%r!^bin/!) { |f| File.basename(f) }
spec.test_files = spec.files.grep(%r!^(test|spec|features)/!)
spec.require_paths = ["lib"]
spec.add_development_dependency "jekyll", ">= 2.4.0", "< 3.1.0"
spec.add_development_dependency "bundler", "~> 1.6"
spec.add_development_dependency "rake", "~> 10.0"
spec.add_dependency "jekyll", "~> 3.3"
spec.add_development_dependency "bundler", "~> 1.15"
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 "nokogiri", "~> 1.6"
spec.add_development_dependency "jekyll-last-modified-at", "0.3.4"
end
<?xml version="1.0" encoding="utf-8"?>
{% if site.url %}
{% assign url_base = site.url | append: site.baseurl %}
{% else %}
{% assign url_base = site.github.url %}
{% endif %}
<feed xmlns="http://www.w3.org/2005/Atom">
<generator uri="http://jekyllrb.com" version="{{ jekyll.version }}">Jekyll</generator>
<link href="{{ page.url | prepend: url_base }}" rel="self" type="application/atom+xml" />
<link href="{{ url_base }}/" rel="alternate" type="text/html" />
<updated>{{ site.time | date_to_xmlschema }}</updated>
<id>{{ url_base | xml_escape }}/</id>
{% if site.name %}
<title>{{ site.name | xml_escape }}</title>
{% endif %}
{% if site.description %}
<subtitle>{{ site.description | xml_escape }}</subtitle>
{% endif %}
{% if site.author %}
<author>
{% if site.author.name %}
<name>{{ site.author.name | xml_escape }}</name>
{% else %}
<name>{{ site.author | xml_escape }}</name>
{% endif %}
{% if site.author.email %}
<email>{{ site.author.email | xml_escape }}</email>
{% endif %}
{% if site.author.uri %}
<uri>{{ site.author.uri | xml_escape }}</uri>
{% endif %}
</author>
{% endif %}
{% for post in site.posts limit: 10 %}
<entry>
<title>{{ post.title | markdownify | strip_html | strip_newlines | xml_escape }}</title>
<link href="{{ post.url | prepend: url_base }}" rel="alternate" type="text/html" title="{{ post.title | xml_escape }}" />
<published>{{ post.date | date_to_xmlschema }}</published>
{% if post.last_modified_at %}
<updated>{{ post.last_modified_at | date_to_xmlschema }}</updated>
{% else %}
<updated>{{ post.date | date_to_xmlschema }}</updated>
{% endif %}
<id>{{ post.id | prepend: url_base | xml_escape }}</id>
<content type="html" xml:base="{{ post.url | prepend: url_base | xml_escape }}">{{ post.content | markdownify | xml_escape }}</content>
{% if post.author %}
<author>
{% if post.author.name %}
<name>{{ post.author.name | xml_escape }}</name>
{% else %}
<name>{{ post.author | xml_escape }}</name>
{% endif %}
{% if post.author.email %}
<email>{{ post.author.email | xml_escape }}</email>
{% endif %}
{% if post.author.uri %}
<uri>{{ post.author.uri | xml_escape }}</uri>
{% endif %}
</author>
{% endif %}
{% if post.category %}
<category term="{{ post.category | xml_escape }}" />
{% endif %}
{% for tag in post.tags %}
<category term="{{ tag | xml_escape }}" />
{% endfor %}
{% if post.excerpt and post.excerpt != blank %}
<summary>{{ post.excerpt | markdownify | strip_html | strip_newlines | xml_escape }}</summary>
{% endif %}
</entry>
{% endfor %}
</feed>
require 'fileutils'
# frozen_string_literal: true
module Jekyll
class PageWithoutAFile < Page
def read_yaml(*)
@data ||= {}
end
end
require "jekyll"
require "fileutils"
require "jekyll-feed/generator"
class FeedMetaTag < Liquid::Tag
def config
@context.registers[:site].config
end
def path
if config["feed"] && config["feed"]["path"]
config["feed"]["path"]
else
"feed.xml"
end
end
def url
if config["url"]
config["url"]
elsif config["github"] && config["github"]["url"]
config["github"]["url"]
end
end
def render(context)
@context = context
"<link type=\"application/atom+xml\" rel=\"alternate\" href=\"#{url}/#{path}\" title=\"#{config["name"]}\" />"
end
end
class JekyllFeed < Jekyll::Generator
safe true
priority :lowest
# Path to feed from config, or feed.xml for default
def path
if @site.config["feed"] && @site.config["feed"]["path"]
@site.config["feed"]["path"]
else
"feed.xml"
end
end
# Main plugin action, called by Jekyll-core
def generate(site)
@site = site
@site.config["time"] = Time.new
unless feed_exists?
write
@site.keep_files ||= []
@site.keep_files << path
end
end
# Path to feed.xml template file
def source_path
File.expand_path "feed.xml", File.dirname(__FILE__)
end
# Destination for feed.xml file within the site source directory
def destination_path
if @site.respond_to?(:in_dest_dir)
@site.in_dest_dir(path)
else
Jekyll.sanitized_path(@site.dest, path)
end
end
# copy feed template from source to destination
def write
FileUtils.mkdir_p File.dirname(destination_path)
File.open(destination_path, 'w') { |f| f.write(feed_content) }
end
def feed_content
site_map = PageWithoutAFile.new(@site, File.dirname(__FILE__), "", path)
site_map.content = File.read(source_path).gsub(/\s*\n\s*/, "\n").gsub(/\n{%/, "{%")
site_map.data["layout"] = nil
site_map.render(Hash.new, @site.site_payload)
site_map.output
end
# Checks if a feed already exists in the site source
def feed_exists?
if @site.respond_to?(:in_source_dir)
File.exists? @site.in_source_dir(path)
else
File.exists? Jekyll.sanitized_path(@site.source, path)
end
end
end
module JekyllFeed
autoload :MetaTag, "jekyll-feed/meta-tag"
autoload :PageWithoutAFile, "jekyll-feed/page-without-a-file.rb"
end
Liquid::Template.register_tag('feed_meta', Jekyll::FeedMetaTag)
Liquid::Template.register_tag "feed_meta", JekyllFeed::MetaTag
<?xml version="1.0" encoding="utf-8"?>
{% if page.xsl %}
<?xml-stylesheet type="text/xml" href="{{ '/feed.xslt.xml' | absolute_url }}"?>
{% endif %}
<feed xmlns="http://www.w3.org/2005/Atom" {% if site.lang %}xml:lang="{{ site.lang }}"{% endif %}>
<generator uri="https://jekyllrb.com/" version="{{ jekyll.version }}">Jekyll</generator>
<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>
{% if site.title %}
<title type="html">{{ site.title | smartify | xml_escape }}</title>
{% elsif site.name %}
<title type="html">{{ site.name | smartify | xml_escape }}</title>
{% endif %}
{% if site.description %}
<subtitle>{{ site.description | xml_escape }}</subtitle>
{% endif %}
{% if site.author %}
<author>
<name>{{ site.author.name | default: site.author | xml_escape }}</name>
{% if site.author.email %}
<email>{{ site.author.email | xml_escape }}</email>
{% endif %}
{% if site.author.uri %}
<uri>{{ site.author.uri | xml_escape }}</uri>
{% endif %}
</author>
{% endif %}
{% assign posts = site.posts | where_exp: "post", "post.draft != true" %}
{% 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>
<link href="{{ post.url | absolute_url }}" rel="alternate" type="text/html" title="{{ post.title | xml_escape }}" />
<published>{{ post.date | date_to_xmlschema }}</published>
<updated>{{ post.last_modified_at | default: post.date | date_to_xmlschema }}</updated>
<id>{{ post.id | absolute_url | xml_escape }}</id>
<content type="html" xml:base="{{ post.url | absolute_url | xml_escape }}">{{ post.content | strip | xml_escape }}</content>
{% assign post_author = post.author | default: post.authors[0] | default: site.author %}
{% assign post_author = site.data.authors[post_author] | default: post_author %}
{% assign post_author_email = post_author.email | default: nil %}
{% assign post_author_uri = post_author.uri | default: nil %}
{% assign post_author_name = post_author.name | default: post_author %}
<author>
<name>{{ post_author_name | default: "" | xml_escape }}</name>
{% if post_author_email %}
<email>{{ post_author_email | xml_escape }}</email>
{% endif %}
{% if post_author_uri %}
<uri>{{ post_author_uri | xml_escape }}</uri>
{% endif %}
</author>
{% if post.category %}
<category term="{{ post.category | xml_escape }}" />
{% endif %}
{% for tag in post.tags %}
<category term="{{ tag | xml_escape }}" />
{% endfor %}
{% if post.excerpt and post.excerpt != empty %}
<summary type="html">{{ post.excerpt | strip_html | normalize_whitespace | xml_escape }}</summary>
{% endif %}
{% 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 %}
{% endunless %}
<media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="{{ post_image }}" />
{% endif %}
</entry>
{% endfor %}
</feed>
# frozen_string_literal: true
module JekyllFeed
class Generator < Jekyll::Generator
safe true
priority :lowest
# 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)
end
private
# Matches all whitespace that follows
# 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"
end
end
# Path to feed.xml template file
def feed_source_path
File.expand_path "feed.xml", __dir__
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
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
end
end
end
# frozen_string_literal: true
module JekyllFeed
class MetaTag < Liquid::Tag
# Use Jekyll's native relative_url filter
include Jekyll::Filters::URLFilters
def render(context)
@context = context
attrs = attributes.map { |k, v| %(#{k}="#{v}") }.join(" ")
"<link #{attrs} />"
end
private
def config
@context.registers[:site].config
end
def attributes
{
:type => "application/atom+xml",
:rel => "alternate",
:href => absolute_url(path),
:title => title,
}.keep_if { |_, v| v }
end
def path
if config["feed"] && config["feed"]["path"]
config["feed"]["path"]
else
"feed.xml"
end
end
def title