diff --git a/README.md b/README.md index d9a87821..35b468e1 100644 --- a/README.md +++ b/README.md @@ -205,6 +205,44 @@ feed: The same flag can be used directly in post file. It will be disable `` tag for selected post. Settings in post file has higher priority than in config file. +## Tags + +To automatically generate feeds for each tag you apply to your posts you can add a tags setting to your config: + +```yml +feed: + tags: true +``` + +If there are tags you don't want included in this auto generation you can exclude them + +```yml +feed: + tags: + except: + - tag-to-exclude + - another-tag +``` + +If you wish to change the location of these auto generated feeds (`/feed/by_tag/.xml` by default) you can provide an alternative folder for them to live in. + +```yml +feed: + tags: + path: "alternative/path/for/tags/feeds/" +``` + +If you only want to generate feeds for a few tags you can also set this. + +```yml +feed: + tags: + only: + - tag-to-include + - another-tag +``` + +Note that if you include a tag that is excluded a feed will not be generated for it. ## Contributing diff --git a/lib/jekyll-feed/feed.xml b/lib/jekyll-feed/feed.xml index edd01838..40a221ae 100644 --- a/lib/jekyll-feed/feed.xml +++ b/lib/jekyll-feed/feed.xml @@ -39,7 +39,12 @@ {% endif %} - {% assign posts = site[page.collection] | where_exp: "post", "post.draft != true" | sort: "date" | reverse %} + {% if page.tags %} + {% assign entries = site.tags[page.tags] %} + {% else %} + {% assign entries = site[page.collection] %} + {% endif %} + {% assign posts = entries | where_exp: "post", "post.draft != true" | sort: "date" | reverse %} {% if page.category %} {% assign posts = posts | where: "category",page.category %} {% endif %} diff --git a/lib/jekyll-feed/generator.rb b/lib/jekyll-feed/generator.rb index 13684056..590f66f4 100644 --- a/lib/jekyll-feed/generator.rb +++ b/lib/jekyll-feed/generator.rb @@ -17,6 +17,7 @@ def generate(site) @site.pages << make_page(path, :collection => name, :category => category) end end + generate_feed_by_tag if config["tags"] && !@site.tags.empty? end private @@ -69,6 +70,32 @@ def collections @collections end + def generate_feed_by_tag + tags_config = config["tags"] + tags_config = {} unless tags_config.is_a?(Hash) + + except = tags_config["except"] || [] + only = tags_config["only"] || @site.tags.keys + tags_pool = only - except + tags_path = tags_config["path"] || "/feed/by_tag/" + + generate_tag_feed(tags_pool, tags_path) + end + + def generate_tag_feed(tags_pool, tags_path) + tags_pool.each do |tag| + # allow only tags with basic alphanumeric characters and underscore to keep + # feed path simple. + next if tag =~ %r![^a-zA-Z0-9_]! + + Jekyll.logger.info "Jekyll Feed:", "Generating feed for posts tagged #{tag}" + path = "#{tags_path}#{tag}.xml" + next if file_exists?(path) + + @site.pages << make_page(path, :tags => tag) + end + end + # Path to feed.xml template file def feed_source_path @feed_source_path ||= File.expand_path "feed.xml", __dir__ @@ -85,7 +112,7 @@ def file_exists?(file_path) # Generates contents for a file - def make_page(file_path, collection: "posts", category: nil) + def make_page(file_path, collection: "posts", category: nil, tags: nil) PageWithoutAFile.new(@site, __dir__, "", file_path).tap do |file| file.content = feed_template file.data.merge!( @@ -93,7 +120,8 @@ def make_page(file_path, collection: "posts", category: nil) "sitemap" => false, "xsl" => file_exists?("feed.xslt.xml"), "collection" => collection, - "category" => category + "category" => category, + "tags" => tags ) file.output end diff --git a/spec/fixtures/_posts/2013-12-12-dec-the-second.md b/spec/fixtures/_posts/2013-12-12-dec-the-second.md index 13b97917..265eaf22 100644 --- a/spec/fixtures/_posts/2013-12-12-dec-the-second.md +++ b/spec/fixtures/_posts/2013-12-12-dec-the-second.md @@ -2,6 +2,8 @@ excerpt: "Foo" image: "/image.png" category: news +tags: + - test --- # December the twelfth, actually. diff --git a/spec/fixtures/_posts/2014-03-02-march-the-second.md b/spec/fixtures/_posts/2014-03-02-march-the-second.md index e33a699d..a74e1b5a 100644 --- a/spec/fixtures/_posts/2014-03-02-march-the-second.md +++ b/spec/fixtures/_posts/2014-03-02-march-the-second.md @@ -1,6 +1,8 @@ --- image: https://cdn.example.org/absolute.png?h=188&w=250 category: news +tags: + - test --- March the second! diff --git a/spec/fixtures/_posts/2014-03-04-march-the-fourth.md b/spec/fixtures/_posts/2014-03-04-march-the-fourth.md index 3e3ff140..b649b729 100644 --- a/spec/fixtures/_posts/2014-03-04-march-the-fourth.md +++ b/spec/fixtures/_posts/2014-03-04-march-the-fourth.md @@ -2,6 +2,7 @@ title: Sparkling Title tags: - '"/>' + - test image: path: "/object-image.png" categories: updates jekyll diff --git a/spec/fixtures/_posts/2015-01-18-jekyll-last-modified-at.md b/spec/fixtures/_posts/2015-01-18-jekyll-last-modified-at.md index f032a1bc..706ad1fd 100644 --- a/spec/fixtures/_posts/2015-01-18-jekyll-last-modified-at.md +++ b/spec/fixtures/_posts/2015-01-18-jekyll-last-modified-at.md @@ -1,5 +1,8 @@ --- last_modified_at: 2015-05-12T13:27:59+00:00 +tags: + - test + - fail --- Please don't modify this file. It's modified time is important. diff --git a/spec/fixtures/_posts/2015-05-12-pre.html b/spec/fixtures/_posts/2015-05-12-pre.html index 42b54b59..2c2a60ff 100644 --- a/spec/fixtures/_posts/2015-05-12-pre.html +++ b/spec/fixtures/_posts/2015-05-12-pre.html @@ -1,6 +1,8 @@ --- author: Pat lang: en +tags: + - test ---
Line 1
diff --git a/spec/fixtures/_posts/2015-05-18-author-detail.md b/spec/fixtures/_posts/2015-05-18-author-detail.md
index c4c42bd3..db50bb0e 100644
--- a/spec/fixtures/_posts/2015-05-18-author-detail.md
+++ b/spec/fixtures/_posts/2015-05-18-author-detail.md
@@ -4,6 +4,9 @@ author:
   name: Ben
   uri: "http://ben.balter.com"
   email: ben@example.com
+tags:
+ - test
+ - success
 ---
 
 # December the twelfth, actually.
diff --git a/spec/fixtures/_posts/2015-08-08-stuck-in-the-middle.html b/spec/fixtures/_posts/2015-08-08-stuck-in-the-middle.html
index 5e28ff35..196ff21b 100644
--- a/spec/fixtures/_posts/2015-08-08-stuck-in-the-middle.html
+++ b/spec/fixtures/_posts/2015-08-08-stuck-in-the-middle.html
@@ -1,4 +1,7 @@
 ---
+tags:
+ - test
+ - fail
 feed:
   excerpt_only: true
 ---
diff --git a/spec/jekyll-feed_spec.rb b/spec/jekyll-feed_spec.rb
index a1ea3a73..5f23ebb4 100644
--- a/spec/jekyll-feed_spec.rb
+++ b/spec/jekyll-feed_spec.rb
@@ -497,6 +497,141 @@
     end
   end
 
+  context "tags" do
+    let(:tags_feed_test) { File.read(dest_dir("feed/by_tag/test.xml")) }
+    let(:tags_feed_fail) { File.read(dest_dir("feed/by_tag/fail.xml")) }
+    let(:tags_feed_success) { File.read(dest_dir("feed/by_tag/success.xml")) }
+
+
+    context "do not set tags setting" do
+      it "should not write tags feeds" do
+        expect(Pathname.new(dest_dir("feed/by_tag/test.xml"))).to_not exist
+        expect(Pathname.new(dest_dir("feed/by_tag/fail.xml"))).to_not exist
+        expect(Pathname.new(dest_dir("feed/by_tag/success.xml"))).to_not exist
+      end
+    end
+
+    context "set tags setting" do
+      let(:overrides) do
+        {
+          "feed" => {
+            "tags" => true
+          },
+        }
+      end
+
+      it "should write tags feeds" do
+        expect(Pathname.new(dest_dir("feed/by_tag/test.xml"))).to exist
+        expect(Pathname.new(dest_dir("feed/by_tag/fail.xml"))).to exist
+        expect(Pathname.new(dest_dir("feed/by_tag/success.xml"))).to exist
+
+        expect(tags_feed_test).to match "/2013/12/12/dec-the-second.html"
+        expect(tags_feed_test).to match "/2014/03/02/march-the-second.html"
+        expect(tags_feed_test).to match "/2014/03/04/march-the-fourth.html"
+        expect(tags_feed_test).to match "/2015/01/18/jekyll-last-modified-at.html"
+        expect(tags_feed_test).to match "/2015/05/12/pre.html"
+        expect(tags_feed_test).to match "/2015/05/18/author-detail.html"
+        expect(tags_feed_test).to match "/2015/08/08/stuck-in-the-middle.html"
+
+        expect(tags_feed_fail).to match "/2015/01/18/jekyll-last-modified-at.html"
+        expect(tags_feed_fail).to match "/2015/08/08/stuck-in-the-middle.html"
+        expect(tags_feed_fail).to_not match "/2013/12/12/dec-the-second.html"
+        expect(tags_feed_fail).to_not match "/2014/03/02/march-the-second.html"
+        expect(tags_feed_fail).to_not match "/2014/03/04/march-the-fourth.html"
+        expect(tags_feed_fail).to_not match "/2015/05/12/pre.html"
+        expect(tags_feed_fail).to_not match "/2015/05/18/author-detail.html"
+
+        expect(tags_feed_success).to match "2015/05/18/author-detail.html"
+        expect(tags_feed_success).to_not match "/2013/12/12/dec-the-second.html"
+        expect(tags_feed_success).to_not match "/2014/03/02/march-the-second.html"
+        expect(tags_feed_success).to_not match "/2014/03/04/march-the-fourth.html"
+        expect(tags_feed_success).to_not match "/2015/01/18/jekyll-last-modified-at.html"
+        expect(tags_feed_success).to_not match "/2015/05/12/pre.html"
+        expect(tags_feed_success).to_not match "/2015/08/08/stuck-in-the-middle.html"
+      end
+    end
+
+    context "set exclusions" do
+      let(:overrides) do
+        {
+          "feed" => {
+            "tags" => {
+              "except" => ["fail"]
+            },
+          },
+        }
+      end
+
+      it "should not write fail feed" do
+        expect(Pathname.new(dest_dir("feed/by_tag/test.xml"))).to exist
+        expect(Pathname.new(dest_dir("feed/by_tag/fail.xml"))).to_not exist
+        expect(Pathname.new(dest_dir("feed/by_tag/success.xml"))).to exist
+      end
+    end
+
+    context "set inclusions" do
+      let(:overrides) do
+        {
+          "feed" => {
+            "tags" => {
+              "only" => ["success"]
+            },
+          },
+        }
+      end
+
+      it "should not write fail feed" do
+        expect(Pathname.new(dest_dir("feed/by_tag/test.xml"))).to_not exist
+        expect(Pathname.new(dest_dir("feed/by_tag/fail.xml"))).to_not exist
+        expect(Pathname.new(dest_dir("feed/by_tag/success.xml"))).to exist
+      end
+    end
+
+    context "set alternate path" do
+      let(:overrides) do
+        {
+          "feed" => {
+            "tags" => {
+              "path" => "alternate/path/"
+            },
+          },
+        }
+      end
+
+      it "should write feeds to new path" do
+        expect(Pathname.new(dest_dir("feed/by_tag/test.xml"))).to_not exist
+        expect(Pathname.new(dest_dir("feed/by_tag/fail.xml"))).to_not exist
+        expect(Pathname.new(dest_dir("feed/by_tag/success.xml"))).to_not exist
+
+        expect(Pathname.new(dest_dir("alternate/path/test.xml"))).to exist
+        expect(Pathname.new(dest_dir("alternate/path/fail.xml"))).to exist
+        expect(Pathname.new(dest_dir("alternate/path/success.xml"))).to exist
+      end
+
+      context "set to questionable path" do
+        let(:overrides) do
+          {
+            "feed" => {
+              "tags" => {
+                "path" => "../../../../../../../questionable/path/"
+              },
+            },
+          }
+        end
+
+        it "should write feeds to sane paths" do
+          expect(Pathname.new(dest_dir("feed/by_tag/test.xml"))).to_not exist
+          expect(Pathname.new(dest_dir("feed/by_tag/fail.xml"))).to_not exist
+          expect(Pathname.new(dest_dir("feed/by_tag/success.xml"))).to_not exist
+
+          expect(Pathname.new(dest_dir("questionable/path/test.xml"))).to exist
+          expect(Pathname.new(dest_dir("questionable/path/fail.xml"))).to exist
+          expect(Pathname.new(dest_dir("questionable/path/success.xml"))).to exist
+        end
+      end
+    end
+  end
+
   context "excerpt_only flag" do
     context "backward compatibility for no excerpt_only flag" do
       it "should be in contents" do