jekyll-json-feed 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +20 -0
- data/.rspec +2 -0
- data/.rubocop.yml +29 -0
- data/.travis.yml +7 -0
- data/Gemfile +6 -0
- data/LICENSE.txt +22 -0
- data/README.md +131 -0
- data/Rakefile +6 -0
- data/jekyll-json-feed.gemspec +23 -0
- data/lib/jekyll-json-feed.rb +10 -0
- data/lib/jekyll-json-feed/feed.json +62 -0
- data/lib/jekyll-json-feed/generator.rb +54 -0
- data/lib/jekyll-json-feed/meta-tag.rb +39 -0
- data/lib/jekyll-json-feed/page-without-a-file.rb +7 -0
- data/script/bootstrap +3 -0
- data/script/cibuild +7 -0
- data/script/release +7 -0
- data/spec/fixtures/_config.yml +9 -0
- data/spec/fixtures/_data/authors.yml +6 -0
- data/spec/fixtures/_drafts/2015-01-12-a-draft.md +4 -0
- data/spec/fixtures/_layouts/some_default.html +11 -0
- data/spec/fixtures/_posts/2013-12-12-dec-the-second.md +6 -0
- data/spec/fixtures/_posts/2014-03-02-march-the-second.md +5 -0
- data/spec/fixtures/_posts/2014-03-04-march-the-fourth.md +9 -0
- data/spec/fixtures/_posts/2015-01-18-jekyll-last-modified-at.md +5 -0
- data/spec/fixtures/_posts/2015-02-12-strip-newlines.md +6 -0
- data/spec/fixtures/_posts/2015-05-12-liquid.md +7 -0
- data/spec/fixtures/_posts/2015-05-12-pre.html +8 -0
- data/spec/fixtures/_posts/2015-05-18-author-detail.md +10 -0
- data/spec/fixtures/_posts/2015-08-08-stuck-in-the-middle.html +2 -0
- data/spec/fixtures/_posts/2016-04-25-author-reference.md +6 -0
- data/spec/jekyll-json-feed_spec.rb +230 -0
- data/spec/spec_helper.rb +25 -0
- metadata +163 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 81fbe54b756186f7df4e7ed2283815eeffd44d44
|
4
|
+
data.tar.gz: 77944ea7447c9e73de28d3485b687c095d9c51a6
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: a8d2e610097b29b4e45129cf7f7593e5e58ec0ddf6e33afccf5163b338703a76a36fd41358092c1cf48c8e119b9618a1a8300e2d9d42f7ab8be2e2a666aa5b1f
|
7
|
+
data.tar.gz: 5bfe4cf12f34a7d17545a54acd93523d8c7fc0f5fc9f86ea0f8f01fbc81f7e74112ac2f443bcfb74d472d38368c1a8c971d2433071463961cf1fb786bd2a81c9
|
data/.gitignore
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
/vendor
|
2
|
+
/.bundle/
|
3
|
+
/.yardoc
|
4
|
+
/Gemfile.lock
|
5
|
+
/_yardoc/
|
6
|
+
/coverage/
|
7
|
+
/doc/
|
8
|
+
/pkg/
|
9
|
+
/spec/reports/
|
10
|
+
/tmp/
|
11
|
+
*.bundle
|
12
|
+
*.so
|
13
|
+
*.o
|
14
|
+
*.a
|
15
|
+
mkmf.log
|
16
|
+
*.gem
|
17
|
+
Gemfile.lock
|
18
|
+
spec/dest
|
19
|
+
.bundle
|
20
|
+
spec/fixtures/.jekyll-metadata
|
data/.rspec
ADDED
data/.rubocop.yml
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
inherit_gem:
|
2
|
+
jekyll: .rubocop.yml
|
3
|
+
|
4
|
+
AllCops:
|
5
|
+
TargetRubyVersion: 2.0
|
6
|
+
Include:
|
7
|
+
- lib/*.rb
|
8
|
+
|
9
|
+
Exclude:
|
10
|
+
- .rubocop.yml
|
11
|
+
- .codeclimate.yml
|
12
|
+
- .travis.yml
|
13
|
+
- .gitignore
|
14
|
+
- .rspec
|
15
|
+
|
16
|
+
- Gemfile.lock
|
17
|
+
- CHANGELOG.md
|
18
|
+
- readme.md
|
19
|
+
- README.md
|
20
|
+
- Readme.md
|
21
|
+
- ReadMe.md
|
22
|
+
- COPYING
|
23
|
+
- LICENSE
|
24
|
+
|
25
|
+
- test/**/*
|
26
|
+
- vendor/**/*
|
27
|
+
- features/**/*
|
28
|
+
- script/**/*
|
29
|
+
- spec/**/*
|
data/.travis.yml
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2017 Colin Seymour
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,131 @@
|
|
1
|
+
# Jekyll JSON Feed plugin
|
2
|
+
|
3
|
+
A Jekyll plugin to generate a [JSON feed](https://jsonfeed.org/) of your Jekyll posts.
|
4
|
+
|
5
|
+
[![Build Status](https://travis-ci.org/lildude/jekyll-json-feed.svg)](https://travis-ci.org/lildude/jekyll-json-feed) [![Gem Version](https://badge.fury.io/rb/jekyll-json-feed.svg)](https://badge.fury.io/rb/jekyll-json-feed)
|
6
|
+
|
7
|
+
## Installation
|
8
|
+
|
9
|
+
Add this line to your site's Gemfile:
|
10
|
+
|
11
|
+
```ruby
|
12
|
+
gem 'jekyll-json-feed'
|
13
|
+
```
|
14
|
+
|
15
|
+
And then add this line to your site's `_config.yml`:
|
16
|
+
|
17
|
+
```yml
|
18
|
+
gems:
|
19
|
+
- jekyll-json-feed
|
20
|
+
```
|
21
|
+
|
22
|
+
## Usage
|
23
|
+
|
24
|
+
The plugin will automatically generate a JSON feed at `/feed.json`.
|
25
|
+
|
26
|
+
### Optional configuration options
|
27
|
+
|
28
|
+
The plugin will automatically use any of the following configuration variables, if they are present in your site's `_config.yml` file.
|
29
|
+
|
30
|
+
* `title` or `name` - The title of the site, e.g., "My awesome site"
|
31
|
+
* `description` - A longer description of what your site is about, e.g., "Where I blog about Jekyll and other awesome things"
|
32
|
+
* `url` - The URL to your site, e.g., `http://example.com`. If none is provided, the plugin will try to use `site.github.url`.
|
33
|
+
* `author` - Global author information (see below)
|
34
|
+
|
35
|
+
### Optional front matter
|
36
|
+
|
37
|
+
The plugin will use the following post metadata, automatically generated by Jekyll, which you can override via a post's YAML front matter:
|
38
|
+
|
39
|
+
* `date`
|
40
|
+
* `title`
|
41
|
+
* `excerpt`
|
42
|
+
* `id`
|
43
|
+
* `tags`
|
44
|
+
|
45
|
+
Additionally, the plugin will use the following values, if present in a post's YAML front matter:
|
46
|
+
|
47
|
+
* `image` - URL of an image that is representative of the post (can also be passed as `image.path`)
|
48
|
+
* `banner_image` - URL of an image to use as a banner, like those used on Medium
|
49
|
+
* `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).
|
50
|
+
|
51
|
+
### Author information
|
52
|
+
|
53
|
+
*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.*
|
54
|
+
|
55
|
+
There are several ways to convey author-specific information. Author information is found in the following order of priority:
|
56
|
+
|
57
|
+
1. An `author` object, in the documents's front matter, e.g.:
|
58
|
+
|
59
|
+
```yml
|
60
|
+
author:
|
61
|
+
twitter: benbalter
|
62
|
+
```
|
63
|
+
|
64
|
+
2. An `author` object, in the site's `_config.yml`, e.g.:
|
65
|
+
|
66
|
+
```yml
|
67
|
+
author:
|
68
|
+
twitter: benbalter
|
69
|
+
```
|
70
|
+
|
71
|
+
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:
|
72
|
+
|
73
|
+
```yml
|
74
|
+
author: benbalter
|
75
|
+
```
|
76
|
+
|
77
|
+
And you have the following in `_data/authors.yml`:
|
78
|
+
|
79
|
+
```yml
|
80
|
+
benbalter:
|
81
|
+
picture: /img/benbalter.png
|
82
|
+
twitter: jekyllrb
|
83
|
+
|
84
|
+
potus:
|
85
|
+
picture: /img/potus.png
|
86
|
+
twitter: whitehouse
|
87
|
+
```
|
88
|
+
|
89
|
+
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.
|
90
|
+
|
91
|
+
*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.*
|
92
|
+
|
93
|
+
4. An author in the document's front matter (the simplest way), e.g.:
|
94
|
+
|
95
|
+
```yml
|
96
|
+
author: benbalter
|
97
|
+
```
|
98
|
+
|
99
|
+
5. An author in the site's `_config.yml`, e.g.:
|
100
|
+
|
101
|
+
```yml
|
102
|
+
author: benbalter
|
103
|
+
```
|
104
|
+
|
105
|
+
### Meta tags
|
106
|
+
|
107
|
+
The plugin exposes a helper tag to expose the appropriate meta tags to support automated discovery of your feed. Simply place `{% json_feed_meta %}` someplace in your template's `<head>` section, to output the necessary metadata.
|
108
|
+
|
109
|
+
### SmartyPants
|
110
|
+
|
111
|
+
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.
|
112
|
+
|
113
|
+
## Why JSON Feed?
|
114
|
+
|
115
|
+
Great question and I'll leave it to the [spec](https://jsonfeed.org/version/1) to answer:
|
116
|
+
|
117
|
+
> The JSON Feed format is a pragmatic syndication format, like RSS and Atom, but with one big difference: it’s JSON instead of XML.
|
118
|
+
>
|
119
|
+
> For most developers, JSON is far easier to read and write than XML. Developers may groan at picking up an XML parser, but decoding JSON is often just a single line of code.
|
120
|
+
|
121
|
+
## Credit where credit is due
|
122
|
+
|
123
|
+
This plugin is heavily inspired by the [Jekyll Feed plugin](https://github.com/jekyll/jekyll-feed) plugin. So much so, that it is a fork with JSON tweaks. Big thanks go to @benbalter for creating the Jekyll Feed plugin, licensing it under the MIT license and making it so easy to create this plugin (it took me an afternoon whilst I was doing other things).
|
124
|
+
|
125
|
+
## Contributing
|
126
|
+
|
127
|
+
1. Fork it (https://github.com/lildude/jekyll-json-feed/fork)
|
128
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
129
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
130
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
131
|
+
5. Create a new Pull Request
|
data/Rakefile
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
|
3
|
+
Gem::Specification.new do |spec|
|
4
|
+
spec.name = "jekyll-json-feed"
|
5
|
+
spec.version = "1.0.0"
|
6
|
+
spec.authors = ["Colin Seymour"]
|
7
|
+
spec.email = ["lildood@gmail.com"]
|
8
|
+
spec.summary = "A Jekyll plugin to generate a JSON feed of your Jekyll posts"
|
9
|
+
spec.homepage = "https://github.com/lildude/jekyll-json-feed"
|
10
|
+
spec.license = "MIT"
|
11
|
+
|
12
|
+
spec.files = `git ls-files -z`.split("\x0")
|
13
|
+
spec.executables = spec.files.grep(%r!^bin/!) { |f| File.basename(f) }
|
14
|
+
spec.test_files = spec.files.grep(%r!^(test|spec|features)/!)
|
15
|
+
spec.require_paths = ["lib"]
|
16
|
+
|
17
|
+
spec.add_dependency "jekyll", "~> 3.3"
|
18
|
+
|
19
|
+
spec.add_development_dependency "bundler", "~> 1.6"
|
20
|
+
spec.add_development_dependency "rake", "~> 10.0"
|
21
|
+
spec.add_development_dependency "rspec", "~> 3.0"
|
22
|
+
spec.add_development_dependency "rubocop"
|
23
|
+
end
|
@@ -0,0 +1,10 @@
|
|
1
|
+
require "jekyll"
|
2
|
+
require "fileutils"
|
3
|
+
require "jekyll-json-feed/generator"
|
4
|
+
|
5
|
+
module JekyllJsonFeed
|
6
|
+
autoload :MetaTag, "jekyll-json-feed/meta-tag"
|
7
|
+
autoload :PageWithoutAFile, "jekyll-json-feed/page-without-a-file.rb"
|
8
|
+
end
|
9
|
+
|
10
|
+
Liquid::Template.register_tag "json_feed_meta", JekyllJsonFeed::MetaTag
|
@@ -0,0 +1,62 @@
|
|
1
|
+
{
|
2
|
+
"version": "https://jsonfeed.org/version/1",
|
3
|
+
{% if site.title %}
|
4
|
+
"title": {{ site.title | smartify | jsonify }},
|
5
|
+
{% elsif site.name %}
|
6
|
+
"title": {{ site.name | smartify | jsonify }},
|
7
|
+
{% endif %}
|
8
|
+
{% if site.description %}
|
9
|
+
"description": {{ site.description | jsonify }},
|
10
|
+
{% endif %}
|
11
|
+
"home_page_url": {{ '/' | absolute_url | jsonify }},
|
12
|
+
"feed_url": {{ page.url | absolute_url | jsonify }},
|
13
|
+
{% if site.author %}
|
14
|
+
"author": {
|
15
|
+
"name": {{ site.author.name | default: site.author | jsonify }}
|
16
|
+
{% if site.author.uri %}
|
17
|
+
, "url": {{ site.author.uri | jsonify }}
|
18
|
+
{% endif %}
|
19
|
+
{% if site.author.avatar %}
|
20
|
+
, "avatar": {{ site.author.avatar | jsonify }}
|
21
|
+
{% endif %}
|
22
|
+
},
|
23
|
+
{% endif %}
|
24
|
+
"items": [
|
25
|
+
{% assign posts = site.posts | where_exp: "post", "post.draft != true" %}
|
26
|
+
{% for post in posts limit: 10 %}
|
27
|
+
{
|
28
|
+
"id": {{ post.id | absolute_url | jsonify }},
|
29
|
+
"url": {{ post.url | absolute_url | jsonify }},
|
30
|
+
"title": {{ post.title | smartify | strip_html | normalize_whitespace | jsonify }},
|
31
|
+
"content_html": {{ post.content | strip | jsonify }},
|
32
|
+
{% if post.excerpt and post.excerpt != empty %}
|
33
|
+
"summary": {{ post.excerpt | strip_html | normalize_whitespace | jsonify }},
|
34
|
+
{% endif %}
|
35
|
+
{% assign post_image = post.image.path | default: post.image %}
|
36
|
+
{% if post_image %}
|
37
|
+
{% unless post_image contains "://" %}
|
38
|
+
{% assign post_image = post_image | absolute_url | xml_escape %}
|
39
|
+
{% endunless %}
|
40
|
+
"image": {{ post_image | jsonify }},
|
41
|
+
{% endif %}
|
42
|
+
{% assign post_banner_image = post.banner_image.path | default: post.banner_image %}
|
43
|
+
{% if post_banner_image %}
|
44
|
+
"banner_image": {{ post_banner_image | jsonify }},
|
45
|
+
{% endif %}
|
46
|
+
"date_published": "{{ post.date | date_to_xmlschema }}",
|
47
|
+
"date_modified": "{{ post.last_modified_at | default: post.date | date_to_xmlschema }}",
|
48
|
+
{% assign post_author = post.author | default: post.authors[0] | default: site.author %}
|
49
|
+
{% assign post_author = site.data.authors[post_author] | default: post_author %}
|
50
|
+
{% assign post_author_uri = post_author.uri | default: nil %}
|
51
|
+
{% assign post_author_name = post_author.name | default: post_author %}
|
52
|
+
"author": {
|
53
|
+
"name": {{ post_author_name | default: "" | jsonify }}
|
54
|
+
{% if post_author_uri != nil %}
|
55
|
+
, "url": {{ post_author_uri | jsonify }}
|
56
|
+
{% endif %}
|
57
|
+
},
|
58
|
+
"tags": {{ post.tags | jsonify }}
|
59
|
+
}{% unless forloop.last == true %},{% endunless %}
|
60
|
+
{% endfor %}
|
61
|
+
]
|
62
|
+
}
|
@@ -0,0 +1,54 @@
|
|
1
|
+
module JekyllJsonFeed
|
2
|
+
class Generator < Jekyll::Generator
|
3
|
+
safe true
|
4
|
+
priority :lowest
|
5
|
+
|
6
|
+
# Main plugin action, called by Jekyll-core
|
7
|
+
def generate(site)
|
8
|
+
@site = site
|
9
|
+
return if file_exists?(feed_path)
|
10
|
+
@site.pages << content_for_file(feed_path, feed_source_path)
|
11
|
+
end
|
12
|
+
|
13
|
+
private
|
14
|
+
|
15
|
+
# Matches all whitespace that follows
|
16
|
+
# 1. A '>', which closes an XML tag or
|
17
|
+
# 2. A '}', which closes a Liquid tag
|
18
|
+
# We will strip all of this whitespace to minify the template
|
19
|
+
MINIFY_REGEX = %r!(?<=>|})\s+!
|
20
|
+
|
21
|
+
# Path to feed from config, or feed.json for default
|
22
|
+
def feed_path
|
23
|
+
if @site.config["json_feed"] && @site.config["json_feed"]["path"]
|
24
|
+
@site.config["json_feed"]["path"]
|
25
|
+
else
|
26
|
+
"feed.json"
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
# Path to feed.json template file
|
31
|
+
def feed_source_path
|
32
|
+
File.expand_path "./feed.json", File.dirname(__FILE__)
|
33
|
+
end
|
34
|
+
|
35
|
+
# Checks if a file already exists in the site source
|
36
|
+
def file_exists?(file_path)
|
37
|
+
if @site.respond_to?(:in_source_dir)
|
38
|
+
File.exist? @site.in_source_dir(file_path)
|
39
|
+
else
|
40
|
+
File.exist? Jekyll.sanitized_path(@site.source, file_path)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
# Generates contents for a file
|
45
|
+
def content_for_file(file_path, file_source_path)
|
46
|
+
file = PageWithoutAFile.new(@site, File.dirname(__FILE__), "", file_path)
|
47
|
+
file.content = File.read(file_source_path).gsub(MINIFY_REGEX, "")
|
48
|
+
file.data["layout"] = nil
|
49
|
+
file.data["sitemap"] = false
|
50
|
+
file.output
|
51
|
+
file
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
module JekyllJsonFeed
|
2
|
+
class MetaTag < Liquid::Tag
|
3
|
+
# Use Jekyll's native relative_url filter
|
4
|
+
include Jekyll::Filters::URLFilters
|
5
|
+
|
6
|
+
def render(context)
|
7
|
+
@context = context
|
8
|
+
attrs = attributes.map { |k, v| %(#{k}="#{v}") }.join(" ")
|
9
|
+
"<link #{attrs} />"
|
10
|
+
end
|
11
|
+
|
12
|
+
private
|
13
|
+
|
14
|
+
def config
|
15
|
+
@context.registers[:site].config
|
16
|
+
end
|
17
|
+
|
18
|
+
def attributes
|
19
|
+
{
|
20
|
+
:type => "application/json",
|
21
|
+
:rel => "alternate",
|
22
|
+
:href => absolute_url(path),
|
23
|
+
:title => title,
|
24
|
+
}.keep_if { |_, v| v }
|
25
|
+
end
|
26
|
+
|
27
|
+
def path
|
28
|
+
if config["json_feed"] && config["json_feed"]["path"]
|
29
|
+
config["json_feed"]["path"]
|
30
|
+
else
|
31
|
+
"feed.json"
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def title
|
36
|
+
config["title"] || config["name"]
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
data/script/bootstrap
ADDED
data/script/cibuild
ADDED
data/script/release
ADDED
@@ -0,0 +1,230 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe(JekyllJsonFeed) do
|
4
|
+
let(:overrides) { Hash.new }
|
5
|
+
let(:config) do
|
6
|
+
Jekyll.configuration(Jekyll::Utils.deep_merge_hashes({
|
7
|
+
"full_rebuild" => true,
|
8
|
+
"source" => source_dir,
|
9
|
+
"destination" => dest_dir,
|
10
|
+
"show_drafts" => true,
|
11
|
+
"url" => "http://example.org",
|
12
|
+
"name" => "My awesome site",
|
13
|
+
"author" => {
|
14
|
+
"name" => "Dr. Jekyll",
|
15
|
+
"url" => "http://example.org/dr_jekyll",
|
16
|
+
"avatar" => "http://example.org/dr_jekyll/avatar.png"
|
17
|
+
},
|
18
|
+
"collections" => {
|
19
|
+
"my_collection" => { "output" => true },
|
20
|
+
"other_things" => { "output" => false }
|
21
|
+
}
|
22
|
+
}, overrides))
|
23
|
+
end
|
24
|
+
let(:site) { Jekyll::Site.new(config) }
|
25
|
+
let(:contents) { File.read(dest_dir("feed.json")) }
|
26
|
+
let(:context) { make_context(site: site) }
|
27
|
+
let(:json_feed_meta) { Liquid::Template.parse("{% json_feed_meta %}").render!(context, {}) }
|
28
|
+
before(:each) do
|
29
|
+
site.process
|
30
|
+
end
|
31
|
+
|
32
|
+
it "has no layout" do
|
33
|
+
expect(contents).not_to match(/\ATHIS IS MY LAYOUT/)
|
34
|
+
end
|
35
|
+
|
36
|
+
it "creates a feed.json file" do
|
37
|
+
expect(Pathname.new(dest_dir("feed.json"))).to exist
|
38
|
+
end
|
39
|
+
|
40
|
+
|
41
|
+
it "doesn't have multiple new lines or trailing whitespace" do
|
42
|
+
expect(contents).to_not match /\s+\n/
|
43
|
+
expect(contents).to_not match /\n{2,}/
|
44
|
+
end
|
45
|
+
|
46
|
+
it "puts all the posts in the feed.json file" do
|
47
|
+
expect(contents).to match /http:\/\/example\.org\/2014\/03\/04\/march-the-fourth\.html/
|
48
|
+
expect(contents).to match /http:\/\/example\.org\/2014\/03\/02\/march-the-second\.html/
|
49
|
+
expect(contents).to match /http:\/\/example\.org\/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*{\s*"name":\s*"Ben",\s*"url":\s*"http://ben.balter.com"\s*}!
|
65
|
+
end
|
66
|
+
|
67
|
+
it "supports post author name as a string" do
|
68
|
+
expect(contents).to match %r!"author":\s*{\s*"name":\s*"Pat"\s*}!
|
69
|
+
end
|
70
|
+
|
71
|
+
it "does not output author tag no author is provided" do
|
72
|
+
expect(contents).not_to match %r{"author":\s*{\s*}}
|
73
|
+
end
|
74
|
+
|
75
|
+
it "does use author reference with data from _data/authors.yml" do
|
76
|
+
expect(contents).to match %r!"author":\s*{\s*"name": "Garth",\s*"url":\s*"http://garthdb.com"\s*}!
|
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{"date_modified": "2015-05-12T13:27:59\+00:00"}
|
85
|
+
end
|
86
|
+
|
87
|
+
it "replaces newlines in posts to spaces" do
|
88
|
+
expect(contents).to match %r!"title": "The plugin will properly strip newlines."!
|
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
|
+
it "includes the item image" do
|
97
|
+
expect(contents).to include('"image": "http://example.org/image.png"')
|
98
|
+
expect(contents).to include('"image": "https://cdn.example.org/absolute.png"')
|
99
|
+
expect(contents).to include('"image": "http://example.org/object-image.png"')
|
100
|
+
end
|
101
|
+
|
102
|
+
context "parsing" do
|
103
|
+
let(:feed) { JSON.parse(contents) }
|
104
|
+
|
105
|
+
it "outputs a JSON feed" do
|
106
|
+
expect(feed['version']).to eql("https://jsonfeed.org/version/1")
|
107
|
+
end
|
108
|
+
|
109
|
+
it "outputs the link" do
|
110
|
+
expect(feed['feed_url']).to eql("http://example.org/feed.json")
|
111
|
+
end
|
112
|
+
|
113
|
+
it "includes the items" do
|
114
|
+
expect(feed['items'].count).to eql(10)
|
115
|
+
end
|
116
|
+
|
117
|
+
it "includes item contents" do
|
118
|
+
post = feed['items'].last
|
119
|
+
expect(post['title']).to eql("Dec The Second")
|
120
|
+
expect(post['url']).to eql("http://example.org/2013/12/12/dec-the-second.html")
|
121
|
+
expect(post['date_published']).to eql(Time.parse("2013-12-12").iso8601)
|
122
|
+
end
|
123
|
+
|
124
|
+
it "includes the item's excerpt" do
|
125
|
+
post = feed['items'].last
|
126
|
+
expect(post['summary']).to eql("Foo")
|
127
|
+
end
|
128
|
+
|
129
|
+
it "doesn't include the item's excerpt if blank" do
|
130
|
+
post = feed['items'].first
|
131
|
+
expect(post['summary']).to be_nil
|
132
|
+
end
|
133
|
+
|
134
|
+
context "with site.title set" do
|
135
|
+
let(:site_title) { "My Site Title" }
|
136
|
+
let(:overrides) { {"title" => site_title} }
|
137
|
+
|
138
|
+
it "uses site.title for the title" do
|
139
|
+
expect(feed['title']).to eql(site_title)
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
143
|
+
context "with site.name set" do
|
144
|
+
let(:site_name) { "My Site Name" }
|
145
|
+
let(:overrides) { {"name" => site_name} }
|
146
|
+
|
147
|
+
it "uses site.name for the title" do
|
148
|
+
expect(feed['title']).to eql(site_name)
|
149
|
+
end
|
150
|
+
end
|
151
|
+
|
152
|
+
context "with site.name and site.title set" do
|
153
|
+
let(:site_title) { "My Site Title" }
|
154
|
+
let(:site_name) { "My Site Name" }
|
155
|
+
let(:overrides) { {"title" => site_title, "name" => site_name} }
|
156
|
+
|
157
|
+
it "uses site.title for the title, dropping site.name" do
|
158
|
+
expect(feed['title']).to eql(site_title)
|
159
|
+
end
|
160
|
+
end
|
161
|
+
end
|
162
|
+
|
163
|
+
context "smartify" do
|
164
|
+
let(:site_title) { "Pat's Site" }
|
165
|
+
let(:overrides) { { "title" => site_title } }
|
166
|
+
let(:feed) { JSON.parse(contents) }
|
167
|
+
|
168
|
+
it "processes site title with SmartyPants" do
|
169
|
+
expect(feed['title']).to eql("Pat’s Site")
|
170
|
+
end
|
171
|
+
end
|
172
|
+
|
173
|
+
context "with a baseurl" do
|
174
|
+
let(:overrides) do
|
175
|
+
{ "baseurl" => "/bass" }
|
176
|
+
end
|
177
|
+
|
178
|
+
it "correctly adds the baseurl to the posts" do
|
179
|
+
expect(contents).to match /http:\/\/example\.org\/bass\/2014\/03\/04\/march-the-fourth\.html/
|
180
|
+
expect(contents).to match /http:\/\/example\.org\/bass\/2014\/03\/02\/march-the-second\.html/
|
181
|
+
expect(contents).to match /http:\/\/example\.org\/bass\/2013\/12\/12\/dec-the-second\.html/
|
182
|
+
end
|
183
|
+
|
184
|
+
it "renders the feed meta" do
|
185
|
+
expected = 'href="http://example.org/bass/feed.json"'
|
186
|
+
expect(json_feed_meta).to include(expected)
|
187
|
+
end
|
188
|
+
end
|
189
|
+
|
190
|
+
context "feed meta" do
|
191
|
+
it "renders the feed meta" do
|
192
|
+
expected = '<link type="application/json" rel="alternate" href="http://example.org/feed.json" title="My awesome site" />'
|
193
|
+
expect(json_feed_meta).to eql(expected)
|
194
|
+
end
|
195
|
+
|
196
|
+
context "with a blank site name" do
|
197
|
+
let(:config) do
|
198
|
+
Jekyll.configuration({
|
199
|
+
"source" => source_dir,
|
200
|
+
"destination" => dest_dir,
|
201
|
+
"url" => "http://example.org"
|
202
|
+
})
|
203
|
+
end
|
204
|
+
|
205
|
+
it "does not output blank title" do
|
206
|
+
expect(json_feed_meta).not_to include('title=')
|
207
|
+
end
|
208
|
+
end
|
209
|
+
end
|
210
|
+
|
211
|
+
context "changing the feed path" do
|
212
|
+
let(:overrides) do
|
213
|
+
{
|
214
|
+
"json_feed" => {
|
215
|
+
"path" => "atom.json"
|
216
|
+
}
|
217
|
+
}
|
218
|
+
end
|
219
|
+
|
220
|
+
it "should write to atom.json" do
|
221
|
+
expect(Pathname.new(dest_dir("atom.json"))).to exist
|
222
|
+
end
|
223
|
+
|
224
|
+
it "renders the feed meta with custom feed path" do
|
225
|
+
expected = 'href="http://example.org/atom.json"'
|
226
|
+
expect(json_feed_meta).to include(expected)
|
227
|
+
end
|
228
|
+
end
|
229
|
+
|
230
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
require 'jekyll'
|
2
|
+
require File.expand_path('../lib/jekyll-json-feed', File.dirname(__FILE__))
|
3
|
+
|
4
|
+
Jekyll.logger.log_level = :error
|
5
|
+
|
6
|
+
RSpec.configure do |config|
|
7
|
+
config.run_all_when_everything_filtered = true
|
8
|
+
config.filter_run :focus
|
9
|
+
config.order = 'random'
|
10
|
+
|
11
|
+
SOURCE_DIR = File.expand_path("../fixtures", __FILE__)
|
12
|
+
DEST_DIR = File.expand_path("../dest", __FILE__)
|
13
|
+
|
14
|
+
def source_dir(*files)
|
15
|
+
File.join(SOURCE_DIR, *files)
|
16
|
+
end
|
17
|
+
|
18
|
+
def dest_dir(*files)
|
19
|
+
File.join(DEST_DIR, *files)
|
20
|
+
end
|
21
|
+
|
22
|
+
def make_context(registers = {})
|
23
|
+
Liquid::Context.new({}, {}, { site: site }.merge(registers))
|
24
|
+
end
|
25
|
+
end
|
metadata
ADDED
@@ -0,0 +1,163 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: jekyll-json-feed
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.0.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Colin Seymour
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2017-05-20 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: jekyll
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '3.3'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '3.3'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: bundler
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '1.6'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '1.6'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: rake
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '10.0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '10.0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: rspec
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - "~>"
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '3.0'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - "~>"
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '3.0'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: rubocop
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - ">="
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '0'
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - ">="
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '0'
|
83
|
+
description:
|
84
|
+
email:
|
85
|
+
- lildood@gmail.com
|
86
|
+
executables: []
|
87
|
+
extensions: []
|
88
|
+
extra_rdoc_files: []
|
89
|
+
files:
|
90
|
+
- ".gitignore"
|
91
|
+
- ".rspec"
|
92
|
+
- ".rubocop.yml"
|
93
|
+
- ".travis.yml"
|
94
|
+
- Gemfile
|
95
|
+
- LICENSE.txt
|
96
|
+
- README.md
|
97
|
+
- Rakefile
|
98
|
+
- jekyll-json-feed.gemspec
|
99
|
+
- lib/jekyll-json-feed.rb
|
100
|
+
- lib/jekyll-json-feed/feed.json
|
101
|
+
- lib/jekyll-json-feed/generator.rb
|
102
|
+
- lib/jekyll-json-feed/meta-tag.rb
|
103
|
+
- lib/jekyll-json-feed/page-without-a-file.rb
|
104
|
+
- script/bootstrap
|
105
|
+
- script/cibuild
|
106
|
+
- script/release
|
107
|
+
- spec/fixtures/_config.yml
|
108
|
+
- spec/fixtures/_data/authors.yml
|
109
|
+
- spec/fixtures/_drafts/2015-01-12-a-draft.md
|
110
|
+
- spec/fixtures/_layouts/some_default.html
|
111
|
+
- spec/fixtures/_posts/2013-12-12-dec-the-second.md
|
112
|
+
- spec/fixtures/_posts/2014-03-02-march-the-second.md
|
113
|
+
- spec/fixtures/_posts/2014-03-04-march-the-fourth.md
|
114
|
+
- spec/fixtures/_posts/2015-01-18-jekyll-last-modified-at.md
|
115
|
+
- spec/fixtures/_posts/2015-02-12-strip-newlines.md
|
116
|
+
- spec/fixtures/_posts/2015-05-12-liquid.md
|
117
|
+
- spec/fixtures/_posts/2015-05-12-pre.html
|
118
|
+
- spec/fixtures/_posts/2015-05-18-author-detail.md
|
119
|
+
- spec/fixtures/_posts/2015-08-08-stuck-in-the-middle.html
|
120
|
+
- spec/fixtures/_posts/2016-04-25-author-reference.md
|
121
|
+
- spec/jekyll-json-feed_spec.rb
|
122
|
+
- spec/spec_helper.rb
|
123
|
+
homepage: https://github.com/lildude/jekyll-json-feed
|
124
|
+
licenses:
|
125
|
+
- MIT
|
126
|
+
metadata: {}
|
127
|
+
post_install_message:
|
128
|
+
rdoc_options: []
|
129
|
+
require_paths:
|
130
|
+
- lib
|
131
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
132
|
+
requirements:
|
133
|
+
- - ">="
|
134
|
+
- !ruby/object:Gem::Version
|
135
|
+
version: '0'
|
136
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
137
|
+
requirements:
|
138
|
+
- - ">="
|
139
|
+
- !ruby/object:Gem::Version
|
140
|
+
version: '0'
|
141
|
+
requirements: []
|
142
|
+
rubyforge_project:
|
143
|
+
rubygems_version: 2.6.8
|
144
|
+
signing_key:
|
145
|
+
specification_version: 4
|
146
|
+
summary: A Jekyll plugin to generate a JSON feed of your Jekyll posts
|
147
|
+
test_files:
|
148
|
+
- spec/fixtures/_config.yml
|
149
|
+
- spec/fixtures/_data/authors.yml
|
150
|
+
- spec/fixtures/_drafts/2015-01-12-a-draft.md
|
151
|
+
- spec/fixtures/_layouts/some_default.html
|
152
|
+
- spec/fixtures/_posts/2013-12-12-dec-the-second.md
|
153
|
+
- spec/fixtures/_posts/2014-03-02-march-the-second.md
|
154
|
+
- spec/fixtures/_posts/2014-03-04-march-the-fourth.md
|
155
|
+
- spec/fixtures/_posts/2015-01-18-jekyll-last-modified-at.md
|
156
|
+
- spec/fixtures/_posts/2015-02-12-strip-newlines.md
|
157
|
+
- spec/fixtures/_posts/2015-05-12-liquid.md
|
158
|
+
- spec/fixtures/_posts/2015-05-12-pre.html
|
159
|
+
- spec/fixtures/_posts/2015-05-18-author-detail.md
|
160
|
+
- spec/fixtures/_posts/2015-08-08-stuck-in-the-middle.html
|
161
|
+
- spec/fixtures/_posts/2016-04-25-author-reference.md
|
162
|
+
- spec/jekyll-json-feed_spec.rb
|
163
|
+
- spec/spec_helper.rb
|