Skip to content

Commit

Permalink
Use custom classifications (tags, categories, ...)
Browse files Browse the repository at this point in the history
We used to classify posts with tags & categories.

This patch adds a generic way to define & use your own classifications through `site.config[:classifications]` by adding `tags` like behavior to site & posts for added classifications.

Let's say you want classify Post by referring to Project(s), Customer(s) and Layout(s) you can now simply add the following to `_config.yml` :

```
classifications:
  - projects
  - customers
  - layouts
```

And start using those classifications like you would with `categories` or `tags` :

Add the following to a Post:

```
projects:
  - project1
  - project2
layout:
  - grid
  - responsive
```

And then use the following in any template :

```
<h2>Projects</h2>
<ul>
{% for project in site.projects %}
  <li>{{ project | first }}</li>
{% endfor %}
</ul>
```
  • Loading branch information
goodtouch authored and Jean-Paul Bonnetouche committed Oct 10, 2013
1 parent 848a11b commit 6a329d7
Show file tree
Hide file tree
Showing 4 changed files with 56 additions and 22 deletions.
1 change: 1 addition & 0 deletions jekyll.gemspec
Expand Up @@ -24,6 +24,7 @@ Gem::Specification.new do |s|
s.extra_rdoc_files = %w[README.markdown LICENSE]

s.add_runtime_dependency('liquid', "~> 2.5.2")
s.add_runtime_dependency('activesupport', "~> 3.2.13")
s.add_runtime_dependency('classifier', "~> 1.3")
s.add_runtime_dependency('directory_watcher', "~> 1.4.1")
s.add_runtime_dependency('maruku', "~> 0.6.0")
Expand Down
1 change: 1 addition & 0 deletions lib/jekyll.rb
Expand Up @@ -26,6 +26,7 @@ def require_all(path)
require 'liquid'
require 'maruku'
require 'colorator'
require 'active_support/inflector'

# internal requires
require 'jekyll/core_ext'
Expand Down
29 changes: 23 additions & 6 deletions lib/jekyll/post.rb
Expand Up @@ -11,10 +11,8 @@ class Post
url
date
id
categories
next
previous
tags
path
]

Expand All @@ -34,7 +32,7 @@ def self.valid?(name)

attr_accessor :site
attr_accessor :data, :extracted_excerpt, :content, :output, :ext
attr_accessor :date, :slug, :published, :tags, :categories
attr_accessor :date, :slug, :published

attr_reader :name

Expand All @@ -51,6 +49,10 @@ def initialize(site, source, dir, name)
@base = self.containing_dir(source, dir)
@name = name

@site.classifications.each do |classification|
self.class.send :attr_accessor, classification
end

self.categories = dir.downcase.split('/').reject { |x| x.empty? }
self.process(name)
self.read_yaml(@base, name)
Expand All @@ -62,7 +64,7 @@ def initialize(site, source, dir, name)
self.published = self.published?

self.populate_categories
self.populate_tags
self.populate_classifications
end

def published?
Expand All @@ -80,8 +82,15 @@ def populate_categories
self.categories.flatten!
end

def populate_tags
self.tags = self.data.pluralized_array("tag", "tags").flatten
def populate_classifications
@site.classifications.each do |classification|
next if classification == 'categories'
self.send(
"#{classification}=".to_sym,
self.data.pluralized_array(ActiveSupport::Inflector.singularize(classification), classification).flatten
)
# => self.tags = self.data.pluralized_array("tag", "tags").flatten
end
end

# Get the full path to the directory containing the post files
Expand Down Expand Up @@ -271,6 +280,14 @@ def destination(dest)
path
end

# Convert this post into a Hash for use in Liquid templates.
#
# Returns the representative Hash.
def to_liquid(attrs = nil)
classification_data = Hash[@site.classifications.map{ |classification| [classification, send(classification)] }]
super(attrs).deep_merge(classification_data)
end

# Returns the shorthand String identifier of this Post.
def inspect
"<Post: #{self.id}>"
Expand Down
47 changes: 31 additions & 16 deletions lib/jekyll/site.rb
@@ -1,8 +1,10 @@
module Jekyll
class Site
FORBIDDEN_CLASSIFICATIONS = %w(posts pages html_pages categories tags).freeze

attr_accessor :config, :layouts, :posts, :pages, :static_files,
:categories, :exclude, :include, :source, :dest, :lsi, :pygments,
:permalink_style, :tags, :time, :future, :safe, :plugins, :limit_posts,
:classifications, :exclude, :include, :source, :dest, :lsi, :pygments,
:permalink_style, :time, :future, :safe, :plugins, :limit_posts,
:show_drafts, :keep_files, :baseurl, :data, :file_read_opts

attr_accessor :converters, :generators
Expand All @@ -25,6 +27,11 @@ def initialize(config)
self.file_read_opts = {}
self.file_read_opts[:encoding] = config['encoding'] if config['encoding']

self.classifications = %w(categories tags)
self.classifications |= (config['classifications'] || []).reject { |c| c !~ /\A[a-z][a-zA-Z_-]*\z/ || FORBIDDEN_CLASSIFICATIONS.include?(c) }

self.class.send :attr_accessor, *classifications

self.reset
self.setup
end
Expand Down Expand Up @@ -54,10 +61,12 @@ def reset
self.posts = []
self.pages = []
self.static_files = []
self.categories = Hash.new { |hash, key| hash[key] = [] }
self.tags = Hash.new { |hash, key| hash[key] = [] }
self.data = {}

self.classifications.each do |classification|
self.send("#{classification}=".to_sym, Hash.new { |hash, key| hash[key] = [] })
end

if self.limit_posts < 0
raise ArgumentError, "limit_posts must be a non-negative number"
end
Expand Down Expand Up @@ -241,8 +250,9 @@ def render
page.render(self.layouts, payload)
end

self.categories.values.map { |ps| ps.sort! { |a, b| b <=> a } }
self.tags.values.map { |ps| ps.sort! { |a, b| b <=> a } }
self.classifications.each do |classification|
self.send(classification.to_sym).values.map { |ps| ps.sort! { |a, b| b <=> a } }
end
rescue Errno::ENOENT => e
# ignore missing layout dir
end
Expand Down Expand Up @@ -304,16 +314,20 @@ def site_data
# See Site#post_attr_hash for type info.
# "tags" - The Hash of tag values and Posts.
# See Site#post_attr_hash for type info.
# For each custom classification, the Hash will also contain a key:
# "#{classification}" - The Hash of custom classification values and Posts.
def site_payload
site_payload = {
"time" => self.time,
"posts" => self.posts.sort { |a, b| b <=> a },
"pages" => self.pages,
"html_pages" => self.pages.reject { |page| !page.html? },
"data" => site_data
}
self.classifications.each { |key| site_payload[key] = post_attr_hash(key) }

{"jekyll" => { "version" => Jekyll::VERSION },
"site" => self.config.merge({
"time" => self.time,
"posts" => self.posts.sort { |a, b| b <=> a },
"pages" => self.pages,
"html_pages" => self.pages.reject { |page| !page.html? },
"categories" => post_attr_hash('categories'),
"tags" => post_attr_hash('tags'),
"data" => site_data})}
"site" => self.config.merge(site_payload)}
end

# Filter out any files/directories that are hidden or backup files (start
Expand Down Expand Up @@ -384,8 +398,9 @@ def get_entries(dir, subfolder)
# Returns nothing
def aggregate_post_info(post)
self.posts << post
post.categories.each { |c| self.categories[c] << post }
post.tags.each { |c| self.tags[c] << post }
self.classifications.each do |classification|
post.send(classification.to_sym).each { |c| self.send(classification.to_sym)[c] << post }
end
end

def relative_permalinks_deprecation_method
Expand Down

0 comments on commit 6a329d7

Please sign in to comment.