rams-jekyll-feed 0.13.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (43) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +21 -0
  3. data/.rspec +2 -0
  4. data/.rubocop.yml +27 -0
  5. data/.travis.yml +32 -0
  6. data/Gemfile +11 -0
  7. data/History.markdown +165 -0
  8. data/LICENSE.txt +22 -0
  9. data/README.md +206 -0
  10. data/Rakefile +8 -0
  11. data/appveyor.yml +29 -0
  12. data/lib/jekyll-feed.rb +12 -0
  13. data/lib/jekyll-feed/feed.xml +96 -0
  14. data/lib/jekyll-feed/generator.rb +112 -0
  15. data/lib/jekyll-feed/meta-tag.rb +37 -0
  16. data/lib/jekyll-feed/page-without-a-file.rb +9 -0
  17. data/lib/jekyll-feed/version.rb +7 -0
  18. data/rams-jekyll-feed.gemspec +30 -0
  19. data/script/bootstrap +3 -0
  20. data/script/cibuild +7 -0
  21. data/script/fmt +10 -0
  22. data/script/release +7 -0
  23. data/script/test +4 -0
  24. data/spec/fixtures/_collection/2018-01-01-collection-doc.md +4 -0
  25. data/spec/fixtures/_collection/2018-01-02-collection-category-doc.md +5 -0
  26. data/spec/fixtures/_config.yml +9 -0
  27. data/spec/fixtures/_data/authors.yml +5 -0
  28. data/spec/fixtures/_drafts/2015-01-12-a-draft.md +4 -0
  29. data/spec/fixtures/_layouts/some_default.html +11 -0
  30. data/spec/fixtures/_posts/2013-12-12-dec-the-second.md +7 -0
  31. data/spec/fixtures/_posts/2014-03-02-march-the-second.md +6 -0
  32. data/spec/fixtures/_posts/2014-03-04-march-the-fourth.md +9 -0
  33. data/spec/fixtures/_posts/2015-01-18-jekyll-last-modified-at.md +5 -0
  34. data/spec/fixtures/_posts/2015-02-12-strip-newlines.md +6 -0
  35. data/spec/fixtures/_posts/2015-05-12-liquid.md +7 -0
  36. data/spec/fixtures/_posts/2015-05-12-pre.html +8 -0
  37. data/spec/fixtures/_posts/2015-05-18-author-detail.md +9 -0
  38. data/spec/fixtures/_posts/2015-08-08-stuck-in-the-middle.html +6 -0
  39. data/spec/fixtures/_posts/2016-04-25-author-reference.md +6 -0
  40. data/spec/fixtures/feed.xslt.xml +0 -0
  41. data/spec/jekyll-feed_spec.rb +524 -0
  42. data/spec/spec_helper.rb +30 -0
  43. metadata +214 -0
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "bundler/gem_tasks"
4
+ require "rspec/core/rake_task"
5
+
6
+ RSpec::Core::RakeTask.new(:spec)
7
+
8
+ task :default => :spec
@@ -0,0 +1,29 @@
1
+ version: "{build}"
2
+ clone_depth: 5
3
+ build: off
4
+
5
+ environment:
6
+ NOKOGIRI_USE_SYSTEM_LIBRARIES: true
7
+ JEKYLL_VERSION: "~> 3.8"
8
+ matrix:
9
+ - RUBY_FOLDER_VER: "26"
10
+ JEKYLL_VERSION : "~> 3.7.4"
11
+ - RUBY_FOLDER_VER: "26"
12
+ JEKYLL_VERSION : ">= 4.0.0.pre.alpha1"
13
+ - RUBY_FOLDER_VER: "26"
14
+ - RUBY_FOLDER_VER: "24"
15
+ - RUBY_FOLDER_VER: "23"
16
+
17
+ install:
18
+ - SET PATH=C:\Ruby%RUBY_FOLDER_VER%-x64\bin;%PATH%
19
+ - bundle install --retry 5 --jobs=%NUMBER_OF_PROCESSORS% --clean --path vendor\bundle
20
+
21
+ test_script:
22
+ - ruby --version
23
+ - gem --version
24
+ - bundler --version
25
+ - bash ./script/test
26
+
27
+ cache:
28
+ # If one of the files after the right arrow changes, cache will be invalidated
29
+ - 'vendor\bundle -> appveyor.yml, Gemfile, jekyll-feed.gemspec'
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "jekyll"
4
+ require "fileutils"
5
+ require "jekyll-feed/generator"
6
+
7
+ module JekyllFeed
8
+ autoload :MetaTag, "jekyll-feed/meta-tag"
9
+ autoload :PageWithoutAFile, "jekyll-feed/page-without-a-file.rb"
10
+ end
11
+
12
+ Liquid::Template.register_tag "feed_meta", JekyllFeed::MetaTag
@@ -0,0 +1,96 @@
1
+ <?xml version="1.0" encoding="utf-8"?>
2
+ {% if page.xsl %}
3
+ <?xml-stylesheet type="text/xml" href="{{ '/feed.xslt.xml' | absolute_url }}"?>
4
+ {% endif %}
5
+ <feed xmlns="http://www.w3.org/2005/Atom" {% if site.lang %}xml:lang="{{ site.lang }}"{% endif %}>
6
+ <generator uri="https://jekyllrb.com/" version="{{ jekyll.version }}">Jekyll</generator>
7
+ <link href="{{ page.url | absolute_url }}" rel="self" type="application/atom+xml" />
8
+ <link href="{{ '/' | absolute_url }}" rel="alternate" type="text/html" {% if site.lang %}hreflang="{{ site.lang }}" {% endif %}/>
9
+ <updated>{{ site.time | date_to_xmlschema }}</updated>
10
+ <id>{{ page.url | absolute_url | xml_escape }}</id>
11
+
12
+ {% assign title = site.title | default: site.name %}
13
+ {% if page.collection != "posts" %}
14
+ {% assign collection = page.collection | capitalize %}
15
+ {% assign title = title | append: " | " | append: collection %}
16
+ {% endif %}
17
+ {% if page.category %}
18
+ {% assign category = page.category | capitalize %}
19
+ {% assign title = title | append: " | " | append: category %}
20
+ {% endif %}
21
+
22
+ {% if title %}
23
+ <title type="html">{{ title | smartify | xml_escape }}</title>
24
+ {% endif %}
25
+
26
+ {% if site.description %}
27
+ <subtitle>{{ site.description | xml_escape }}</subtitle>
28
+ {% endif %}
29
+
30
+ {% if site.author %}
31
+ <author>
32
+ <name>{{ site.author.name | default: site.author | xml_escape }}</name>
33
+ {% if site.author.email %}
34
+ <email>{{ site.author.email | xml_escape }}</email>
35
+ {% endif %}
36
+ {% if site.author.uri %}
37
+ <uri>{{ site.author.uri | xml_escape }}</uri>
38
+ {% endif %}
39
+ </author>
40
+ {% endif %}
41
+
42
+ {% assign posts = site[page.collection] | where_exp: "post", "post.draft != true" | sort: "date" | reverse %}
43
+ {% if page.category %}
44
+ {% assign posts = posts | where: "category",page.category %}
45
+ {% endif %}
46
+ {% for post in posts %}
47
+ <entry{% if post.lang %}{{" "}}xml:lang="{{ post.lang }}"{% endif %}>
48
+ <title type="html">{{ post.title | smartify | strip_html | normalize_whitespace | xml_escape }}</title>
49
+ <link href="{{ post.url | absolute_url }}" rel="alternate" type="text/html" title="{{ post.title | xml_escape }}" />
50
+ <published>{{ post.date | date_to_xmlschema }}</published>
51
+ <updated>{{ post.last_modified_at | default: post.date | date_to_xmlschema }}</updated>
52
+ <id>{{ post.id | absolute_url | xml_escape }}</id>
53
+ {% assign excerpt_only = post.feed.excerpt_only | default: site.feed.excerpt_only %}
54
+ {% unless excerpt_only %}
55
+ <content type="html" xml:base="{{ post.url | absolute_url | xml_escape }}">{{ post.content | strip | xml_escape }}</content>
56
+ {% endunless %}
57
+
58
+ {% assign post_author = post.author | default: post.authors[0] | default: site.author %}
59
+ {% assign post_author = site.data.authors[post_author] | default: post_author %}
60
+ {% assign post_author_email = post_author.email | default: nil %}
61
+ {% assign post_author_uri = post_author.uri | default: nil %}
62
+ {% assign post_author_name = post_author.name | default: post_author %}
63
+
64
+ <author>
65
+ <name>{{ post_author_name | default: "" | xml_escape }}</name>
66
+ {% if post_author_email %}
67
+ <email>{{ post_author_email | xml_escape }}</email>
68
+ {% endif %}
69
+ {% if post_author_uri %}
70
+ <uri>{{ post_author_uri | xml_escape }}</uri>
71
+ {% endif %}
72
+ </author>
73
+
74
+ {% if post.category %}
75
+ <category term="{{ post.category | xml_escape }}" />
76
+ {% endif %}
77
+
78
+ {% for tag in post.tags %}
79
+ <category term="{{ tag | xml_escape }}" />
80
+ {% endfor %}
81
+
82
+ {% if post.excerpt and post.excerpt != empty %}
83
+ <summary type="html">{{ post.excerpt | strip_html | normalize_whitespace | xml_escape }}</summary>
84
+ {% endif %}
85
+
86
+ {% assign post_image = post.image.path | default: post.image %}
87
+ {% if post_image %}
88
+ {% unless post_image contains "://" %}
89
+ {% assign post_image = post_image | absolute_url %}
90
+ {% endunless %}
91
+ <media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="{{ post_image | xml_escape }}" />
92
+ <media:content medium="image" url="{{ post_image | xml_escape }}" xmlns:media="http://search.yahoo.com/mrss/" />
93
+ {% endif %}
94
+ </entry>
95
+ {% endfor %}
96
+ </feed>
@@ -0,0 +1,112 @@
1
+ # frozen_string_literal: true
2
+
3
+ module JekyllFeed
4
+ class Generator < Jekyll::Generator
5
+ safe true
6
+ priority :lowest
7
+
8
+ # Main plugin action, called by Jekyll-core
9
+ def generate(site)
10
+ @site = site
11
+ collections.each do |name, meta|
12
+ Jekyll.logger.info "Jekyll Feed:", "Generating feed for #{name}"
13
+ (meta["categories"] + [nil]).each do |category|
14
+ path = feed_path(:collection => name, :category => category)
15
+ next if file_exists?(path)
16
+
17
+ @site.pages << make_page(path, :collection => name, :category => category)
18
+ end
19
+ end
20
+ end
21
+
22
+ private
23
+
24
+ # Matches all whitespace that follows
25
+ # 1. A '>', which closes an XML tag or
26
+ # 2. A '}', which closes a Liquid tag
27
+ # We will strip all of this whitespace to minify the template
28
+ MINIFY_REGEX = %r!(?<=>|})\s+!.freeze
29
+
30
+ # Returns the plugin's config or an empty hash if not set
31
+ def config
32
+ @config ||= @site.config["feed"] || {}
33
+ end
34
+
35
+ # Determines the destination path of a given feed
36
+ #
37
+ # collection - the name of a collection, e.g., "posts"
38
+ # category - a category within that collection, e.g., "news"
39
+ #
40
+ # Will return "/feed.xml", or the config-specified default feed for posts
41
+ # Will return `/feed/category.xml` for post categories
42
+ # WIll return `/feed/collection.xml` for other collections
43
+ # Will return `/feed/collection/category.xml` for other collection categories
44
+ def feed_path(collection: "posts", category: nil)
45
+ prefix = collection == "posts" ? "/feed" : "/feed/#{collection}"
46
+ return "#{prefix}/#{category}.xml" if category
47
+
48
+ collections.dig(collection, "path") || "#{prefix}.xml"
49
+ end
50
+
51
+ # Returns a hash representing all collections to be processed and their metadata
52
+ # in the form of { collection_name => { categories = [...], path = "..." } }
53
+ def collections
54
+ return @collections if defined?(@collections)
55
+
56
+ @collections = if config["collections"].is_a?(Array)
57
+ config["collections"].map { |c| [c, {}] }.to_h
58
+ elsif config["collections"].is_a?(Hash)
59
+ config["collections"]
60
+ else
61
+ {}
62
+ end
63
+
64
+ @collections = normalize_posts_meta(@collections)
65
+ @collections.each_value do |meta|
66
+ meta["categories"] = (meta["categories"] || []).to_set
67
+ end
68
+
69
+ @collections
70
+ end
71
+
72
+ # Path to feed.xml template file
73
+ def feed_source_path
74
+ @feed_source_path ||= File.expand_path "feed.xml", __dir__
75
+ end
76
+
77
+ def feed_template
78
+ @feed_template ||= File.read(feed_source_path).gsub(MINIFY_REGEX, "")
79
+ end
80
+
81
+ # Checks if a file already exists in the site source
82
+ def file_exists?(file_path)
83
+ File.exist? @site.in_source_dir(file_path)
84
+ end
85
+
86
+ # Generates contents for a file
87
+
88
+ def make_page(file_path, collection: "posts", category: nil)
89
+ PageWithoutAFile.new(@site, __dir__, "", file_path).tap do |file|
90
+ file.content = feed_template
91
+ file.data.merge!(
92
+ "layout" => nil,
93
+ "sitemap" => false,
94
+ "xsl" => file_exists?("feed.xslt.xml"),
95
+ "collection" => collection,
96
+ "category" => category
97
+ )
98
+ file.output
99
+ end
100
+ end
101
+
102
+ # Special case the "posts" collection, which, for ease of use and backwards
103
+ # compatability, can be configured via top-level keys or directly as a collection
104
+ def normalize_posts_meta(hash)
105
+ hash["posts"] ||= {}
106
+ hash["posts"]["path"] ||= config["path"]
107
+ hash["posts"]["categories"] ||= config["categories"]
108
+ config["path"] ||= hash["posts"]["path"]
109
+ hash
110
+ end
111
+ end
112
+ end
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ module JekyllFeed
4
+ class MetaTag < Liquid::Tag
5
+ # Use Jekyll's native relative_url filter
6
+ include Jekyll::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
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ module JekyllFeed
4
+ class PageWithoutAFile < Jekyll::Page
5
+ def read_yaml(*)
6
+ @data ||= {}
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Jekyll
4
+ module Feed
5
+ VERSION = "0.13.0"
6
+ end
7
+ end
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ lib = File.expand_path("lib", __dir__)
4
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
5
+ require "jekyll-feed/version"
6
+
7
+ Gem::Specification.new do |spec|
8
+ spec.name = "rams-jekyll-feed"
9
+ spec.version = Jekyll::Feed::VERSION
10
+ spec.authors = ["Ram Patra"]
11
+ spec.email = ["hi@rampatra.com"]
12
+ spec.summary = "A Jekyll plugin to generate an Atom feed of your Jekyll posts without the hard limit of 10 posts."
13
+ spec.homepage = "https://github.com/rampatra/jekyll-feed"
14
+ spec.license = "MIT"
15
+
16
+ spec.files = `git ls-files -z`.split("\x0")
17
+ spec.test_files = spec.files.grep(%r!^spec/!)
18
+ spec.require_paths = ["lib"]
19
+
20
+ spec.required_ruby_version = ">= 2.3.0"
21
+
22
+ spec.add_dependency "jekyll", ">= 3.7", "< 5.0"
23
+
24
+ spec.add_development_dependency "bundler"
25
+ spec.add_development_dependency "nokogiri", "~> 1.6"
26
+ spec.add_development_dependency "rake", "~> 12.0"
27
+ spec.add_development_dependency "rspec", "~> 3.0"
28
+ spec.add_development_dependency "rubocop-jekyll", "~> 0.5"
29
+ spec.add_development_dependency "typhoeus", ">= 0.7", "< 2.0"
30
+ end
@@ -0,0 +1,3 @@
1
+ #! /bin/bash
2
+
3
+ bundle install
@@ -0,0 +1,7 @@
1
+ #! /bin/bash
2
+
3
+ set -e
4
+
5
+ script/test
6
+ script/fmt
7
+ bundle exec rake build
@@ -0,0 +1,10 @@
1
+ #!/bin/bash
2
+ set -e
3
+
4
+ echo "Rubocop $(bundle exec rubocop --version)"
5
+ bundle exec rubocop -D -E $@
6
+ success=$?
7
+ if ((success != 0)); then
8
+ echo -e "\nTry running \`script/fmt -a\` to automatically fix errors"
9
+ fi
10
+ exit $success
@@ -0,0 +1,7 @@
1
+ #!/bin/sh
2
+ # Tag and push a release.
3
+
4
+ set -e
5
+
6
+ script/cibuild
7
+ bundle exec rake release
@@ -0,0 +1,4 @@
1
+ #!/bin/bash
2
+ set -ex
3
+
4
+ bundle exec rspec "$@"
@@ -0,0 +1,4 @@
1
+ ---
2
+ ---
3
+
4
+ Look at me! I'm a collection!
@@ -0,0 +1,5 @@
1
+ ---
2
+ category: news
3
+ ---
4
+
5
+ Look at me! I'm a collection doc in a category!
@@ -0,0 +1,9 @@
1
+ timezone: UTC
2
+
3
+ defaults:
4
+ -
5
+ scope:
6
+ path: ""
7
+ type: pages
8
+ values:
9
+ layout: some_default
@@ -0,0 +1,5 @@
1
+ garthdb:
2
+ name: Garth
3
+ twitter: garthdb
4
+ uri: "http://garthdb.com"
5
+ email: example@mail.com
@@ -0,0 +1,4 @@
1
+ ---
2
+ ---
3
+
4
+ This is a draft.
@@ -0,0 +1,11 @@
1
+ ---
2
+ ---
3
+ <html>
4
+ <head>
5
+ {% feed_meta %}
6
+ </head>
7
+ <body>
8
+ THIS IS MY LAYOUT
9
+ {{ content }}
10
+ </body>
11
+ </html>
@@ -0,0 +1,7 @@
1
+ ---
2
+ excerpt: "Foo"
3
+ image: "/image.png"
4
+ category: news
5
+ ---
6
+
7
+ # December the twelfth, actually.