bridgetown-feed 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.gitignore +21 -0
- data/.rspec +2 -0
- data/.rubocop.yml +27 -0
- data/Gemfile +11 -0
- data/History.markdown +5 -0
- data/LICENSE.txt +23 -0
- data/README.md +201 -0
- data/Rakefile +8 -0
- data/bridgetown-feed.gemspec +28 -0
- data/lib/bridgetown-feed.rb +12 -0
- data/lib/bridgetown-feed/feed.xml +96 -0
- data/lib/bridgetown-feed/generator.rb +111 -0
- data/lib/bridgetown-feed/meta-tag.rb +37 -0
- data/lib/bridgetown-feed/page-without-a-file.rb +9 -0
- data/lib/bridgetown-feed/version.rb +7 -0
- data/script/bootstrap +3 -0
- data/script/cibuild +7 -0
- data/script/fmt +10 -0
- data/script/release +7 -0
- data/script/test +4 -0
- data/spec/bridgetown-feed_spec.rb +525 -0
- data/spec/fixtures/bridgetown.config.yml +9 -0
- data/spec/fixtures/src/_collection/2018-01-01-collection-doc.md +4 -0
- data/spec/fixtures/src/_collection/2018-01-02-collection-category-doc.md +5 -0
- data/spec/fixtures/src/_data/authors.yml +5 -0
- data/spec/fixtures/src/_layouts/some_default.html +11 -0
- data/spec/fixtures/src/_posts/2013-12-12-dec-the-second.md +7 -0
- data/spec/fixtures/src/_posts/2014-03-02-march-the-second.md +6 -0
- data/spec/fixtures/src/_posts/2014-03-04-march-the-fourth.md +9 -0
- data/spec/fixtures/src/_posts/2015-01-12-a-draft.md +5 -0
- data/spec/fixtures/src/_posts/2015-01-18-jekyll-last-modified-at.md +5 -0
- data/spec/fixtures/src/_posts/2015-02-12-strip-newlines.md +6 -0
- data/spec/fixtures/src/_posts/2015-05-12-liquid.md +7 -0
- data/spec/fixtures/src/_posts/2015-05-12-pre.html +8 -0
- data/spec/fixtures/src/_posts/2015-05-18-author-detail.md +9 -0
- data/spec/fixtures/src/_posts/2015-08-08-stuck-in-the-middle.html +6 -0
- data/spec/fixtures/src/_posts/2016-04-25-author-reference.md +6 -0
- data/spec/fixtures/src/feed.xslt.xml +0 -0
- data/spec/spec_helper.rb +35 -0
- metadata +210 -0
@@ -0,0 +1,111 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module BridgetownFeed
|
4
|
+
class Generator < Bridgetown::Generator
|
5
|
+
priority :lowest
|
6
|
+
|
7
|
+
# Main plugin action, called by Bridgetown-core
|
8
|
+
def generate(site)
|
9
|
+
@site = site
|
10
|
+
collections.each do |name, meta|
|
11
|
+
Bridgetown.logger.info "Bridgetown Feed:", "Generating feed for #{name}"
|
12
|
+
(meta["categories"] + [nil]).each do |category|
|
13
|
+
path = feed_path(:collection => name, :category => category)
|
14
|
+
next if file_exists?(path)
|
15
|
+
|
16
|
+
@site.pages << make_page(path, :collection => name, :category => category)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
private
|
22
|
+
|
23
|
+
# Matches all whitespace that follows
|
24
|
+
# 1. A '>', which closes an XML tag or
|
25
|
+
# 2. A '}', which closes a Liquid tag
|
26
|
+
# We will strip all of this whitespace to minify the template
|
27
|
+
MINIFY_REGEX = %r!(?<=>|})\s+!.freeze
|
28
|
+
|
29
|
+
# Returns the plugin's config or an empty hash if not set
|
30
|
+
def config
|
31
|
+
@config ||= @site.config["feed"] || {}
|
32
|
+
end
|
33
|
+
|
34
|
+
# Determines the destination path of a given feed
|
35
|
+
#
|
36
|
+
# collection - the name of a collection, e.g., "posts"
|
37
|
+
# category - a category within that collection, e.g., "news"
|
38
|
+
#
|
39
|
+
# Will return "/feed.xml", or the config-specified default feed for posts
|
40
|
+
# Will return `/feed/category.xml` for post categories
|
41
|
+
# WIll return `/feed/collection.xml` for other collections
|
42
|
+
# Will return `/feed/collection/category.xml` for other collection categories
|
43
|
+
def feed_path(collection: "posts", category: nil)
|
44
|
+
prefix = collection == "posts" ? "/feed" : "/feed/#{collection}"
|
45
|
+
return "#{prefix}/#{category}.xml" if category
|
46
|
+
|
47
|
+
collections.dig(collection, "path") || "#{prefix}.xml"
|
48
|
+
end
|
49
|
+
|
50
|
+
# Returns a hash representing all collections to be processed and their metadata
|
51
|
+
# in the form of { collection_name => { categories = [...], path = "..." } }
|
52
|
+
def collections
|
53
|
+
return @collections if defined?(@collections)
|
54
|
+
|
55
|
+
@collections = if config["collections"].is_a?(Array)
|
56
|
+
config["collections"].map { |c| [c, {}] }.to_h
|
57
|
+
elsif config["collections"].is_a?(Hash)
|
58
|
+
config["collections"]
|
59
|
+
else
|
60
|
+
{}
|
61
|
+
end
|
62
|
+
|
63
|
+
@collections = normalize_posts_meta(@collections)
|
64
|
+
@collections.each_value do |meta|
|
65
|
+
meta["categories"] = (meta["categories"] || []).to_set
|
66
|
+
end
|
67
|
+
|
68
|
+
@collections
|
69
|
+
end
|
70
|
+
|
71
|
+
# Path to feed.xml template file
|
72
|
+
def feed_source_path
|
73
|
+
@feed_source_path ||= File.expand_path "feed.xml", __dir__
|
74
|
+
end
|
75
|
+
|
76
|
+
def feed_template
|
77
|
+
@feed_template ||= File.read(feed_source_path).gsub(MINIFY_REGEX, "")
|
78
|
+
end
|
79
|
+
|
80
|
+
# Checks if a file already exists in the site source
|
81
|
+
def file_exists?(file_path)
|
82
|
+
File.exist? @site.in_source_dir(file_path)
|
83
|
+
end
|
84
|
+
|
85
|
+
# Generates contents for a file
|
86
|
+
|
87
|
+
def make_page(file_path, collection: "posts", category: nil)
|
88
|
+
PageWithoutAFile.new(@site, __dir__, "", file_path).tap do |file|
|
89
|
+
file.content = feed_template
|
90
|
+
file.data.merge!(
|
91
|
+
"layout" => nil,
|
92
|
+
"sitemap" => false,
|
93
|
+
"xsl" => file_exists?("feed.xslt.xml"),
|
94
|
+
"collection" => collection,
|
95
|
+
"category" => category
|
96
|
+
)
|
97
|
+
file.output
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
# Special case the "posts" collection, which, for ease of use and backwards
|
102
|
+
# compatability, can be configured via top-level keys or directly as a collection
|
103
|
+
def normalize_posts_meta(hash)
|
104
|
+
hash["posts"] ||= {}
|
105
|
+
hash["posts"]["path"] ||= config["path"]
|
106
|
+
hash["posts"]["categories"] ||= config["categories"]
|
107
|
+
config["path"] ||= hash["posts"]["path"]
|
108
|
+
hash
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module BridgetownFeed
|
4
|
+
class MetaTag < Liquid::Tag
|
5
|
+
# Use Bridgetown's native relative_url filter
|
6
|
+
include Bridgetown::Filters::URLFilters
|
7
|
+
|
8
|
+
def render(context)
|
9
|
+
@context = context
|
10
|
+
attrs = attributes.map { |k, v| %(#{k}="#{v}") }.join(" ")
|
11
|
+
"<link #{attrs} />"
|
12
|
+
end
|
13
|
+
|
14
|
+
private
|
15
|
+
|
16
|
+
def config
|
17
|
+
@config ||= @context.registers[:site].config
|
18
|
+
end
|
19
|
+
|
20
|
+
def attributes
|
21
|
+
{
|
22
|
+
:type => "application/atom+xml",
|
23
|
+
:rel => "alternate",
|
24
|
+
:href => absolute_url(path),
|
25
|
+
:title => title,
|
26
|
+
}.keep_if { |_, v| v }
|
27
|
+
end
|
28
|
+
|
29
|
+
def path
|
30
|
+
config.dig("feed", "path") || "feed.xml"
|
31
|
+
end
|
32
|
+
|
33
|
+
def title
|
34
|
+
config["title"] || config["name"]
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
data/script/bootstrap
ADDED
data/script/cibuild
ADDED
data/script/fmt
ADDED
data/script/release
ADDED
data/script/test
ADDED
@@ -0,0 +1,525 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "spec_helper"
|
4
|
+
|
5
|
+
describe(BridgetownFeed) do
|
6
|
+
let(:overrides) { {} }
|
7
|
+
let(:config) do
|
8
|
+
Bridgetown.configuration(Bridgetown::Utils.deep_merge_hashes({
|
9
|
+
"full_rebuild" => true,
|
10
|
+
"root_dir" => root_dir,
|
11
|
+
"source" => source_dir,
|
12
|
+
"destination" => dest_dir,
|
13
|
+
"show_drafts" => true,
|
14
|
+
"url" => "http://example.org",
|
15
|
+
"name" => "My awesome site",
|
16
|
+
"author" => {
|
17
|
+
"name" => "Dr. Bridgetown",
|
18
|
+
},
|
19
|
+
"collections" => {
|
20
|
+
"my_collection" => { "output" => true },
|
21
|
+
"other_things" => { "output" => false },
|
22
|
+
},
|
23
|
+
}, overrides))
|
24
|
+
end
|
25
|
+
let(:site) { Bridgetown::Site.new(config) }
|
26
|
+
let(:contents) { File.read(dest_dir("feed.xml")) }
|
27
|
+
let(:context) { make_context(:site => site) }
|
28
|
+
let(:feed_meta) { Liquid::Template.parse("{% feed_meta %}").render!(context, {}) }
|
29
|
+
before(:each) do
|
30
|
+
site.process
|
31
|
+
end
|
32
|
+
|
33
|
+
it "has no layout" do
|
34
|
+
expect(contents).not_to match(%r!\ATHIS IS MY LAYOUT!)
|
35
|
+
end
|
36
|
+
|
37
|
+
it "creates a feed.xml file" do
|
38
|
+
expect(Pathname.new(dest_dir("feed.xml"))).to exist
|
39
|
+
end
|
40
|
+
|
41
|
+
it "doesn't have multiple new lines or trailing whitespace" do
|
42
|
+
expect(contents).to_not match %r!\s+\n!
|
43
|
+
expect(contents).to_not match %r!\n{2,}!
|
44
|
+
end
|
45
|
+
|
46
|
+
it "puts all the posts in the feed.xml file" do
|
47
|
+
expect(contents).to match "http://example.org/updates/bridgetown/2014/03/04/march-the-fourth.html"
|
48
|
+
expect(contents).to match "http://example.org/news/2014/03/02/march-the-second.html"
|
49
|
+
expect(contents).to match "http://example.org/news/2013/12/12/dec-the-second.html"
|
50
|
+
expect(contents).to match "http://example.org/2015/08/08/stuck-in-the-middle.html"
|
51
|
+
expect(contents).to_not match "http://example.org/2016/02/09/a-draft.html"
|
52
|
+
end
|
53
|
+
|
54
|
+
it "does not include assets or any static files that aren't .html" do
|
55
|
+
expect(contents).not_to match "http://example.org/images/hubot.png"
|
56
|
+
expect(contents).not_to match "http://example.org/feeds/atom.xml"
|
57
|
+
end
|
58
|
+
|
59
|
+
it "preserves linebreaks in preformatted text in posts" do
|
60
|
+
expect(contents).to match "Line 1\nLine 2\nLine 3"
|
61
|
+
end
|
62
|
+
|
63
|
+
it "supports post author name as an object" do
|
64
|
+
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>!
|
65
|
+
end
|
66
|
+
|
67
|
+
it "supports post author name as a string" do
|
68
|
+
expect(contents).to match %r!<author>\s*<name>Pat</name>\s*</author>!
|
69
|
+
end
|
70
|
+
|
71
|
+
it "does not output author tag no author is provided" do
|
72
|
+
expect(contents).not_to match %r!<author>\s*<name></name>\s*</author>!
|
73
|
+
end
|
74
|
+
|
75
|
+
it "does use author reference with data from _data/authors.yml" do
|
76
|
+
expect(contents).to match %r!<author>\s*<name>Garth</name>\s*<email>example@mail\.com</email>\s*<uri>http://garthdb\.com</uri>\s*</author>!
|
77
|
+
end
|
78
|
+
|
79
|
+
it "converts markdown posts to HTML" do
|
80
|
+
expect(contents).to match %r!<p>March the second\!</p>!
|
81
|
+
end
|
82
|
+
|
83
|
+
it "uses last_modified_at where available" do
|
84
|
+
expect(contents).to match %r!<updated>2015-05-12T13:27:59\+00:00</updated>!
|
85
|
+
end
|
86
|
+
|
87
|
+
it "replaces newlines in posts to spaces" do
|
88
|
+
expect(contents).to match '<title type="html">The plugin will properly strip newlines.</title>'
|
89
|
+
end
|
90
|
+
|
91
|
+
it "renders Liquid inside posts" do
|
92
|
+
expect(contents).to match "Liquid is rendered."
|
93
|
+
expect(contents).not_to match "Liquid is not rendered."
|
94
|
+
end
|
95
|
+
|
96
|
+
context "images" do
|
97
|
+
let(:image1) { 'http://example.org/image.png' }
|
98
|
+
let(:image2) { 'https://cdn.example.org/absolute.png?h=188&w=250' }
|
99
|
+
let(:image3) { 'http://example.org/object-image.png' }
|
100
|
+
|
101
|
+
it "includes the item image" do
|
102
|
+
expect(contents).to include(%(<media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="#{image1}" />))
|
103
|
+
expect(contents).to include(%(<media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="#{image2}" />))
|
104
|
+
expect(contents).to include(%(<media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="#{image3}" />))
|
105
|
+
end
|
106
|
+
|
107
|
+
it "included media content for mail templates (Mailchimp)" do
|
108
|
+
expect(contents).to include(%(<media:content medium="image" url="#{image1}" xmlns:media="http://search.yahoo.com/mrss/" />))
|
109
|
+
expect(contents).to include(%(<media:content medium="image" url="#{image2}" xmlns:media="http://search.yahoo.com/mrss/" />))
|
110
|
+
expect(contents).to include(%(<media:content medium="image" url="#{image3}" xmlns:media="http://search.yahoo.com/mrss/" />))
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
context "parsing" do
|
115
|
+
let(:feed) { RSS::Parser.parse(contents) }
|
116
|
+
|
117
|
+
it "outputs an RSS feed" do
|
118
|
+
expect(feed.feed_type).to eql("atom")
|
119
|
+
expect(feed.feed_version).to eql("1.0")
|
120
|
+
expect(feed.encoding).to eql("UTF-8")
|
121
|
+
expect(feed.lang).to be_nil
|
122
|
+
expect(feed.valid?).to eql(true)
|
123
|
+
end
|
124
|
+
|
125
|
+
it "outputs the link" do
|
126
|
+
expect(feed.link.href).to eql("http://example.org/feed.xml")
|
127
|
+
end
|
128
|
+
|
129
|
+
it "outputs the generator" do
|
130
|
+
expect(feed.generator.content).to eql("Bridgetown")
|
131
|
+
expect(feed.generator.version).to eql(Bridgetown::VERSION)
|
132
|
+
end
|
133
|
+
|
134
|
+
it "includes the items" do
|
135
|
+
expect(feed.items.count).to eql(10)
|
136
|
+
end
|
137
|
+
|
138
|
+
it "includes item contents" do
|
139
|
+
post = feed.items.last
|
140
|
+
expect(post.title.content).to eql("Dec The Second")
|
141
|
+
expect(post.link.href).to eql("http://example.org/news/2013/12/12/dec-the-second.html")
|
142
|
+
expect(post.published.content).to eql(Time.parse("2013-12-12"))
|
143
|
+
end
|
144
|
+
|
145
|
+
it "includes the item's excerpt" do
|
146
|
+
post = feed.items.last
|
147
|
+
expect(post.summary.content).to eql("Foo")
|
148
|
+
end
|
149
|
+
|
150
|
+
it "doesn't include the item's excerpt if blank" do
|
151
|
+
post = feed.items.first
|
152
|
+
expect(post.summary).to be_nil
|
153
|
+
end
|
154
|
+
|
155
|
+
context "with site.lang set" do
|
156
|
+
lang = "en_US"
|
157
|
+
let(:overrides) { { "lang" => lang } }
|
158
|
+
it "outputs a valid feed" do
|
159
|
+
expect(feed.feed_type).to eql("atom")
|
160
|
+
expect(feed.feed_version).to eql("1.0")
|
161
|
+
expect(feed.encoding).to eql("UTF-8")
|
162
|
+
expect(feed.valid?).to eql(true)
|
163
|
+
end
|
164
|
+
|
165
|
+
it "outputs the correct language" do
|
166
|
+
expect(feed.lang).to eql(lang)
|
167
|
+
end
|
168
|
+
|
169
|
+
it "sets the language of entries" do
|
170
|
+
post = feed.items.first
|
171
|
+
expect(post.lang).to eql(lang)
|
172
|
+
end
|
173
|
+
|
174
|
+
it "renders the feed meta" do
|
175
|
+
expected = %r!<link href="http://example.org/" rel="alternate" type="text/html" hreflang="#{lang}" />!
|
176
|
+
expect(contents).to match(expected)
|
177
|
+
end
|
178
|
+
end
|
179
|
+
|
180
|
+
context "with site.title set" do
|
181
|
+
let(:site_title) { "My Site Title" }
|
182
|
+
let(:overrides) { { "title" => site_title } }
|
183
|
+
|
184
|
+
it "uses site.title for the title" do
|
185
|
+
expect(feed.title.content).to eql(site_title)
|
186
|
+
end
|
187
|
+
end
|
188
|
+
|
189
|
+
context "with site.name set" do
|
190
|
+
let(:site_name) { "My Site Name" }
|
191
|
+
let(:overrides) { { "name" => site_name } }
|
192
|
+
|
193
|
+
it "uses site.name for the title" do
|
194
|
+
expect(feed.title.content).to eql(site_name)
|
195
|
+
end
|
196
|
+
end
|
197
|
+
|
198
|
+
context "with site.name and site.title set" do
|
199
|
+
let(:site_title) { "My Site Title" }
|
200
|
+
let(:site_name) { "My Site Name" }
|
201
|
+
let(:overrides) { { "title" => site_title, "name" => site_name } }
|
202
|
+
|
203
|
+
it "uses site.title for the title, dropping site.name" do
|
204
|
+
expect(feed.title.content).to eql(site_title)
|
205
|
+
end
|
206
|
+
end
|
207
|
+
end
|
208
|
+
|
209
|
+
context "smartify" do
|
210
|
+
let(:site_title) { "Pat's Site" }
|
211
|
+
let(:overrides) { { "title" => site_title } }
|
212
|
+
let(:feed) { RSS::Parser.parse(contents) }
|
213
|
+
|
214
|
+
it "processes site title with SmartyPants" do
|
215
|
+
expect(feed.title.content).to eql("Pat’s Site")
|
216
|
+
end
|
217
|
+
end
|
218
|
+
|
219
|
+
context "validation" do
|
220
|
+
it "validates" do
|
221
|
+
skip "Typhoeus couldn't find the 'libcurl' module on Windows" if Gem.win_platform?
|
222
|
+
# See https://validator.w3.org/docs/api.html
|
223
|
+
url = "https://validator.w3.org/feed/check.cgi?output=soap12"
|
224
|
+
response = Typhoeus.post(url, :body => { :rawdata => contents }, :accept_encoding => "gzip")
|
225
|
+
pending "Something went wrong with the W3 validator" unless response.success?
|
226
|
+
result = Nokogiri::XML(response.body)
|
227
|
+
result.remove_namespaces!
|
228
|
+
|
229
|
+
result.css("warning").each do |warning|
|
230
|
+
# Quiet a warning that results from us passing the feed as a string
|
231
|
+
next if warning.css("text").text =~ %r!Self reference doesn't match document location!
|
232
|
+
|
233
|
+
# Quiet expected warning that results from blank summary test case
|
234
|
+
next if warning.css("text").text =~ %r!(content|summary) should not be blank!
|
235
|
+
|
236
|
+
# Quiet expected warning about multiple posts with same updated time
|
237
|
+
next if warning.css("text").text =~ %r!Two entries with the same value for atom:updated!
|
238
|
+
|
239
|
+
warn "Validation warning: #{warning.css("text").text} on line #{warning.css("line").text} column #{warning.css("column").text}"
|
240
|
+
end
|
241
|
+
|
242
|
+
errors = result.css("error").map do |error|
|
243
|
+
"Validation error: #{error.css("text").text} on line #{error.css("line").text} column #{error.css("column").text}"
|
244
|
+
end
|
245
|
+
|
246
|
+
expect(result.css("validity").text).to eql("true"), errors.join("\n")
|
247
|
+
end
|
248
|
+
end
|
249
|
+
|
250
|
+
context "with a baseurl" do
|
251
|
+
let(:overrides) do
|
252
|
+
{ "baseurl" => "/bass" }
|
253
|
+
end
|
254
|
+
|
255
|
+
it "correctly adds the baseurl to the posts" do
|
256
|
+
expect(contents).to match "http://example.org/bass/updates/bridgetown/2014/03/04/march-the-fourth.html"
|
257
|
+
expect(contents).to match "http://example.org/bass/news/2014/03/02/march-the-second.html"
|
258
|
+
expect(contents).to match "http://example.org/bass/news/2013/12/12/dec-the-second.html"
|
259
|
+
end
|
260
|
+
|
261
|
+
it "renders the feed meta" do
|
262
|
+
expected = 'href="http://example.org/bass/feed.xml"'
|
263
|
+
expect(feed_meta).to include(expected)
|
264
|
+
end
|
265
|
+
end
|
266
|
+
|
267
|
+
context "feed meta" do
|
268
|
+
it "renders the feed meta" do
|
269
|
+
expected = '<link type="application/atom+xml" rel="alternate" href="http://example.org/feed.xml" title="My awesome site" />'
|
270
|
+
expect(feed_meta).to eql(expected)
|
271
|
+
end
|
272
|
+
|
273
|
+
context "with a blank site name" do
|
274
|
+
let(:config) do
|
275
|
+
Bridgetown.configuration(
|
276
|
+
"source" => source_dir,
|
277
|
+
"destination" => dest_dir,
|
278
|
+
"url" => "http://example.org"
|
279
|
+
)
|
280
|
+
end
|
281
|
+
|
282
|
+
it "does not output blank title" do
|
283
|
+
expect(feed_meta).not_to include("title=")
|
284
|
+
end
|
285
|
+
end
|
286
|
+
end
|
287
|
+
|
288
|
+
context "changing the feed path" do
|
289
|
+
let(:overrides) do
|
290
|
+
{
|
291
|
+
"feed" => {
|
292
|
+
"path" => "atom.xml",
|
293
|
+
},
|
294
|
+
}
|
295
|
+
end
|
296
|
+
|
297
|
+
it "should write to atom.xml" do
|
298
|
+
expect(Pathname.new(dest_dir("atom.xml"))).to exist
|
299
|
+
end
|
300
|
+
|
301
|
+
it "renders the feed meta with custom feed path" do
|
302
|
+
expected = 'href="http://example.org/atom.xml"'
|
303
|
+
expect(feed_meta).to include(expected)
|
304
|
+
end
|
305
|
+
end
|
306
|
+
|
307
|
+
context "changing the file path via collection meta" do
|
308
|
+
let(:overrides) do
|
309
|
+
{
|
310
|
+
"feed" => {
|
311
|
+
"collections" => {
|
312
|
+
"posts" => {
|
313
|
+
"path" => "atom.xml",
|
314
|
+
},
|
315
|
+
},
|
316
|
+
},
|
317
|
+
}
|
318
|
+
end
|
319
|
+
|
320
|
+
it "should write to atom.xml" do
|
321
|
+
expect(Pathname.new(dest_dir("atom.xml"))).to exist
|
322
|
+
end
|
323
|
+
|
324
|
+
it "renders the feed meta with custom feed path" do
|
325
|
+
expected = 'href="http://example.org/atom.xml"'
|
326
|
+
expect(feed_meta).to include(expected)
|
327
|
+
end
|
328
|
+
end
|
329
|
+
|
330
|
+
context "feed stylesheet" do
|
331
|
+
it "includes the stylesheet" do
|
332
|
+
expect(contents).to include('<?xml-stylesheet type="text/xml" href="http://example.org/feed.xslt.xml"?>')
|
333
|
+
end
|
334
|
+
end
|
335
|
+
|
336
|
+
context "with site.lang set" do
|
337
|
+
let(:overrides) { { "lang" => "en-US" } }
|
338
|
+
|
339
|
+
it "should set the language" do
|
340
|
+
expect(contents).to match 'type="text/html" hreflang="en-US" />'
|
341
|
+
end
|
342
|
+
end
|
343
|
+
|
344
|
+
context "with post.lang set" do
|
345
|
+
it "should set the language for that entry" do
|
346
|
+
expect(contents).to match '<entry xml:lang="en">'
|
347
|
+
expect(contents).to match '<entry>'
|
348
|
+
end
|
349
|
+
end
|
350
|
+
|
351
|
+
context "categories" do
|
352
|
+
context "with top-level post categories" do
|
353
|
+
let(:overrides) do
|
354
|
+
{
|
355
|
+
"feed" => { "categories" => ["news"] },
|
356
|
+
}
|
357
|
+
end
|
358
|
+
let(:news_feed) { File.read(dest_dir("feed/news.xml")) }
|
359
|
+
|
360
|
+
it "outputs the primary feed" do
|
361
|
+
expect(contents).to match "http://example.org/updates/bridgetown/2014/03/04/march-the-fourth.html"
|
362
|
+
expect(contents).to match "http://example.org/news/2014/03/02/march-the-second.html"
|
363
|
+
expect(contents).to match "http://example.org/news/2013/12/12/dec-the-second.html"
|
364
|
+
expect(contents).to match "http://example.org/2015/08/08/stuck-in-the-middle.html"
|
365
|
+
expect(contents).to_not match "http://example.org/2016/02/09/a-draft.html"
|
366
|
+
end
|
367
|
+
|
368
|
+
it "outputs the category feed" do
|
369
|
+
expect(news_feed).to match '<title type="html">My awesome site | News</title>'
|
370
|
+
expect(news_feed).to match "http://example.org/news/2014/03/02/march-the-second.html"
|
371
|
+
expect(news_feed).to match "http://example.org/news/2013/12/12/dec-the-second.html"
|
372
|
+
expect(news_feed).to_not match "http://example.org/updates/bridgetown/2014/03/04/march-the-fourth.html"
|
373
|
+
expect(news_feed).to_not match "http://example.org/2015/08/08/stuck-in-the-middle.html"
|
374
|
+
end
|
375
|
+
end
|
376
|
+
|
377
|
+
context "with collection-level post categories" do
|
378
|
+
let(:overrides) do
|
379
|
+
{
|
380
|
+
"feed" => {
|
381
|
+
"collections" => {
|
382
|
+
"posts" => {
|
383
|
+
"categories" => ["news"],
|
384
|
+
},
|
385
|
+
},
|
386
|
+
},
|
387
|
+
}
|
388
|
+
end
|
389
|
+
let(:news_feed) { File.read(dest_dir("feed/news.xml")) }
|
390
|
+
|
391
|
+
it "outputs the primary feed" do
|
392
|
+
expect(contents).to match "http://example.org/updates/bridgetown/2014/03/04/march-the-fourth.html"
|
393
|
+
expect(contents).to match "http://example.org/news/2014/03/02/march-the-second.html"
|
394
|
+
expect(contents).to match "http://example.org/news/2013/12/12/dec-the-second.html"
|
395
|
+
expect(contents).to match "http://example.org/2015/08/08/stuck-in-the-middle.html"
|
396
|
+
expect(contents).to_not match "http://example.org/2016/02/09/a-draft.html"
|
397
|
+
end
|
398
|
+
|
399
|
+
it "outputs the category feed" do
|
400
|
+
expect(news_feed).to match '<title type="html">My awesome site | News</title>'
|
401
|
+
expect(news_feed).to match "http://example.org/news/2014/03/02/march-the-second.html"
|
402
|
+
expect(news_feed).to match "http://example.org/news/2013/12/12/dec-the-second.html"
|
403
|
+
expect(news_feed).to_not match "http://example.org/updates/bridgetown/2014/03/04/march-the-fourth.html"
|
404
|
+
expect(news_feed).to_not match "http://example.org/2015/08/08/stuck-in-the-middle.html"
|
405
|
+
end
|
406
|
+
end
|
407
|
+
end
|
408
|
+
|
409
|
+
context "collections" do
|
410
|
+
let(:collection_feed) { File.read(dest_dir("feed/collection.xml")) }
|
411
|
+
|
412
|
+
context "when initialized as an array" do
|
413
|
+
let(:overrides) do
|
414
|
+
{
|
415
|
+
"collections" => {
|
416
|
+
"collection" => {
|
417
|
+
"output" => true,
|
418
|
+
},
|
419
|
+
},
|
420
|
+
"feed" => { "collections" => ["collection"] },
|
421
|
+
}
|
422
|
+
end
|
423
|
+
|
424
|
+
it "outputs the collection feed" do
|
425
|
+
expect(collection_feed).to match '<title type="html">My awesome site | Collection</title>'
|
426
|
+
expect(collection_feed).to match "http://example.org/collection/2018-01-01-collection-doc.html"
|
427
|
+
expect(collection_feed).to match "http://example.org/collection/2018-01-02-collection-category-doc.html"
|
428
|
+
expect(collection_feed).to_not match "http://example.org/updates/bridgetown/2014/03/04/march-the-fourth.html"
|
429
|
+
expect(collection_feed).to_not match "http://example.org/2015/08/08/stuck-in-the-middle.html"
|
430
|
+
end
|
431
|
+
end
|
432
|
+
|
433
|
+
context "with categories" do
|
434
|
+
let(:overrides) do
|
435
|
+
{
|
436
|
+
"collections" => {
|
437
|
+
"collection" => {
|
438
|
+
"output" => true,
|
439
|
+
},
|
440
|
+
},
|
441
|
+
"feed" => {
|
442
|
+
"collections" => {
|
443
|
+
"collection" => {
|
444
|
+
"categories" => ["news"],
|
445
|
+
},
|
446
|
+
},
|
447
|
+
},
|
448
|
+
}
|
449
|
+
end
|
450
|
+
let(:news_feed) { File.read(dest_dir("feed/collection/news.xml")) }
|
451
|
+
|
452
|
+
it "outputs the collection category feed" do
|
453
|
+
expect(news_feed).to match '<title type="html">My awesome site | Collection | News</title>'
|
454
|
+
expect(news_feed).to match "http://example.org/collection/2018-01-02-collection-category-doc.html"
|
455
|
+
expect(news_feed).to_not match "http://example.org/collection/2018-01-01-collection-doc.html"
|
456
|
+
expect(news_feed).to_not match "http://example.org/updates/bridgetown/2014/03/04/march-the-fourth.html"
|
457
|
+
expect(news_feed).to_not match "http://example.org/2015/08/08/stuck-in-the-middle.html"
|
458
|
+
end
|
459
|
+
end
|
460
|
+
|
461
|
+
context "with a custom path" do
|
462
|
+
let(:overrides) do
|
463
|
+
{
|
464
|
+
"collections" => {
|
465
|
+
"collection" => {
|
466
|
+
"output" => true,
|
467
|
+
},
|
468
|
+
},
|
469
|
+
"feed" => {
|
470
|
+
"collections" => {
|
471
|
+
"collection" => {
|
472
|
+
"categories" => ["news"],
|
473
|
+
"path" => "custom.xml",
|
474
|
+
},
|
475
|
+
},
|
476
|
+
},
|
477
|
+
}
|
478
|
+
end
|
479
|
+
|
480
|
+
it "should write to the custom path" do
|
481
|
+
expect(Pathname.new(dest_dir("custom.xml"))).to exist
|
482
|
+
expect(Pathname.new(dest_dir("feed/collection.xml"))).to_not exist
|
483
|
+
expect(Pathname.new(dest_dir("feed/collection/news.xml"))).to exist
|
484
|
+
end
|
485
|
+
end
|
486
|
+
end
|
487
|
+
|
488
|
+
context "excerpt_only flag" do
|
489
|
+
context "backward compatibility for no excerpt_only flag" do
|
490
|
+
it "should be in contents" do
|
491
|
+
expect(contents).to match '<content '
|
492
|
+
end
|
493
|
+
end
|
494
|
+
|
495
|
+
context "when site.excerpt_only flag is true" do
|
496
|
+
let(:overrides) do
|
497
|
+
{ "feed" => { "excerpt_only" => true } }
|
498
|
+
end
|
499
|
+
|
500
|
+
it "should not set any contents" do
|
501
|
+
expect(contents).to_not match '<content '
|
502
|
+
end
|
503
|
+
end
|
504
|
+
|
505
|
+
context "when site.excerpt_only flag is false" do
|
506
|
+
let(:overrides) do
|
507
|
+
{ "feed" => { "excerpt_only" => false } }
|
508
|
+
end
|
509
|
+
|
510
|
+
it "should be in contents" do
|
511
|
+
expect(contents).to match '<content '
|
512
|
+
end
|
513
|
+
end
|
514
|
+
|
515
|
+
context "when post.excerpt_only flag is true" do
|
516
|
+
let(:overrides) do
|
517
|
+
{ "feed" => { "excerpt_only" => false } }
|
518
|
+
end
|
519
|
+
|
520
|
+
it "should not be in contents" do
|
521
|
+
expect(contents).to_not match "This content should not be in feed.</content>"
|
522
|
+
end
|
523
|
+
end
|
524
|
+
end
|
525
|
+
end
|