├── test
├── source
│ └── _posts
│ │ ├── 2011-01-16-post-3.textile
│ │ ├── 2011-02-16-post-0.textile
│ │ ├── 2011-08-16-post-1.textile
│ │ ├── 2011-08-16-post-4.textile
│ │ ├── 2011-08-16-post-5.textile
│ │ ├── 2011-08-16-post-6.textile
│ │ └── 2011-08-20-post-2.textile
└── test_related_posts.rb
├── .gitignore
├── README.rdoc
├── LICENCE.rdoc
└── _plugins
└── related_posts.rb
/test/source/_posts/2011-01-16-post-3.textile:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/test/source/_posts/2011-02-16-post-0.textile:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/test/source/_posts/2011-08-16-post-1.textile:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/test/source/_posts/2011-08-16-post-4.textile:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/test/source/_posts/2011-08-16-post-5.textile:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/test/source/_posts/2011-08-16-post-6.textile:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/test/source/_posts/2011-08-20-post-2.textile:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Ignore backup text files
2 | *~
3 |
4 | # Ignore vi swap files
5 | *.swp
6 |
--------------------------------------------------------------------------------
/README.rdoc:
--------------------------------------------------------------------------------
1 | = related_posts
2 |
3 | This is a {jekyll}[http://jekyllrb.com/] plugin that overrides the built in
4 | related_posts function to calculate related posts based on a posts' tags.
5 |
6 | == Installation
7 | To register it as a plugin, copy _plugins/related_posts.rb to the
8 | _plugins directory of your jekyll project.
9 |
10 | == How to use it
11 | The plugin replaces the functionality of site.related_posts so you
12 | can use it as follows:
13 | {% for post in site.related_posts %}
14 | {{ post.title }}
15 | {% endfor %}
16 |
17 | == Licence
18 | Copyright (c) 2011-2012, Lawrence Woodman
19 | This software is licensed under an MIT Licence. Please see the file, LICENCE.rdoc, for details.
20 |
--------------------------------------------------------------------------------
/LICENCE.rdoc:
--------------------------------------------------------------------------------
1 | = The MIT Licence
2 |
3 | Copyright (c) 2011-2012 Lawrence Woodman
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in
13 | all copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21 | THE SOFTWARE.
22 |
--------------------------------------------------------------------------------
/_plugins/related_posts.rb:
--------------------------------------------------------------------------------
1 | require 'jekyll/post'
2 |
3 | module RelatedPosts
4 |
5 | # Used to remove #related_posts so that it can be overridden
6 | def self.included(klass)
7 | klass.class_eval do
8 | remove_method :related_posts
9 | end
10 | end
11 |
12 | # Calculate related posts.
13 | #
14 | # Returns []
15 | def related_posts(posts)
16 | return [] unless posts.size > 1
17 | highest_freq = Jekyll::Post.tag_freq(posts).values.max
18 | related_scores = Hash.new(0)
19 | posts.each do |post|
20 | post.tags.each do |tag|
21 | if self.tags.include?(tag) && post != self
22 | cat_freq = Jekyll::Post.tag_freq(posts)[tag]
23 | related_scores[post] += (1+highest_freq-cat_freq)
24 | end
25 | end
26 | end
27 |
28 | Jekyll::Post.sort_related_posts(related_scores)
29 | end
30 |
31 | module ClassMethods
32 | # Calculate the frequency of each tag.
33 | #
34 | # Returns {tag => freq, tag => freq, ...}
35 | def tag_freq(posts)
36 | return @tag_freq if @tag_freq
37 | @tag_freq = Hash.new(0)
38 | posts.each do |post|
39 | post.tags.each {|tag| @tag_freq[tag] += 1}
40 | end
41 | @tag_freq
42 | end
43 |
44 | # Sort the related posts in order of their score and date
45 | # and return just the posts
46 | def sort_related_posts(related_scores)
47 | related_scores.sort do |a,b|
48 | if a[1] < b[1]
49 | 1
50 | elsif a[1] > b[1]
51 | -1
52 | else
53 | b[0].date <=> a[0].date
54 | end
55 | end.collect {|post,freq| post}
56 | end
57 | end
58 |
59 | end
60 |
61 | module Jekyll
62 | class Post
63 | include RelatedPosts
64 | extend RelatedPosts::ClassMethods
65 | end
66 | end
67 |
--------------------------------------------------------------------------------
/test/test_related_posts.rb:
--------------------------------------------------------------------------------
1 | require 'test/unit'
2 | require 'minitest/mock'
3 | require 'minitest/spec'
4 | require 'jekyll'
5 | require_relative '../_plugins/related_posts'
6 |
7 | def createPost(site, id, date, tags)
8 | file_dir = File.expand_path(File.dirname(__FILE__))
9 | post = Jekyll::Post.new(
10 | site, file_dir, 'source', "#{date}-post-#{id}.textile"
11 | )
12 | post.define_singleton_method(:id) {id}
13 | post.define_singleton_method(:tags) {tags}
14 | post
15 | end
16 |
17 | describe Jekyll::Post do
18 | before do
19 | @site = MiniTest::Mock.new
20 | @posts = []
21 | @posts << createPost(@site, 0, "2011-02-16", ['Retro', 'Programming'])
22 | @posts << createPost(@site, 1, "2011-08-16", ['Retro', 'Games'])
23 | @posts << createPost(@site, 2, "2011-08-20", ['Programming', 'Ruby'])
24 | @posts << createPost(@site, 3, "2011-01-16", ['Programming', 'C'])
25 | @posts << createPost(@site, 4, "2011-08-16", ['Off-topic', 'Funny', 'Story'])
26 | @posts << createPost(@site, 5, "2011-08-16", ['Programming', 'Games', 'C'])
27 | @posts << createPost(@site, 6, "2011-08-16", ['Retro', 'C'])
28 | end
29 |
30 | describe 'when given a post with nothing related' do
31 | it 'must return an empty array' do
32 | @posts[4].related_posts(@posts).must_equal []
33 | end
34 | end
35 |
36 | describe 'when given a post with more than one related post' do
37 | it 'must return related posts in order of relationship strength and date' do
38 | @posts[5].related_posts(@posts).must_equal [
39 | @posts[1], @posts[3], @posts[6], @posts[2], @posts[0]
40 | ]
41 | end
42 |
43 | it 'must return posts of equal relationship score in date order' do
44 | @posts[2].related_posts(@posts).must_equal [
45 | @posts[5], @posts[0], @posts[3]
46 | ]
47 | end
48 | end
49 |
50 | end
51 |
--------------------------------------------------------------------------------