rams-jekyll-feed 0.13.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.
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.