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>
```

You can also access classifications through `classifications` methods of `Site` and `Post` if you're not in a Liquid template context (ie. when writting a Jekyll plugin) :

```
module Jekyll
  class CustomGenerator
    def generate(site)
      related_layouts = {}
      site.classifications['projects'].each do |project, posts|
        related_layouts[project] = posts.map{ |post| post.classifications['layouts'] }.flatten
      end
      # ...
    end
  end
end
```
  • Loading branch information
goodtouch committed May 22, 2014
1 parent 8d65c9c commit de5a9b1
Show file tree
Hide file tree
Showing 5 changed files with 98 additions and 37 deletions.
2 changes: 1 addition & 1 deletion jekyll.gemspec
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ Gem::Specification.new do |s|
s.extra_rdoc_files = %w[README.markdown LICENSE]

s.add_runtime_dependency('liquid', "~> 2.5.5")
s.add_runtime_dependency('activesupport', "~> 3.2.13")
s.add_runtime_dependency('classifier', "~> 1.3")
s.add_runtime_dependency('listen', "~> 2.5")
s.add_runtime_dependency('kramdown', "~> 1.3")
Expand All @@ -55,7 +56,6 @@ Gem::Specification.new do |s|
s.add_development_dependency('simplecov-gem-adapter', "~> 1.0.1")
s.add_development_dependency('coveralls', "~> 0.7.0")
s.add_development_dependency('mime-types', "~> 1.5")
s.add_development_dependency('activesupport', '~> 3.2.13')
s.add_development_dependency('jekyll_test_plugin')
s.add_development_dependency('jekyll_test_plugin_malicious')
s.add_development_dependency('rouge', '~> 1.3')
Expand Down
1 change: 1 addition & 0 deletions lib/jekyll.rb
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ def require_all(path)
require 'kramdown'
require 'colorator'
require 'toml'
require 'active_support/inflector'

# internal requires
require 'jekyll/version'
Expand Down
2 changes: 2 additions & 0 deletions lib/jekyll/convertible.rb
Original file line number Diff line number Diff line change
Expand Up @@ -226,6 +226,8 @@ def write(dest)
def [](property)
if self.class::ATTRIBUTES_FOR_LIQUID.include?(property)
send(property)
elsif self.respond_to?(:classifications) && self.classifications.keys.include?(property)
self.classifications[property]
else
data[property]
end
Expand Down
54 changes: 41 additions & 13 deletions lib/jekyll/post.rb
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,10 @@ class Post
dir
date
id
categories
next
previous
tags
path
classifications
]

# Attributes for Liquid templates
Expand All @@ -35,7 +34,7 @@ def self.valid?(name)

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

attr_reader :name

Expand All @@ -52,7 +51,8 @@ def initialize(site, source, dir, name)
@base = containing_dir(source, dir)
@name = name

self.categories = dir.downcase.split('/').reject { |x| x.empty? }
self.classifications = {'categories' => dir.downcase.split('/').reject { |x| x.empty? }}

process(name)
read_yaml(@base, name)

Expand All @@ -64,8 +64,7 @@ def initialize(site, source, dir, name)
self.date = Time.parse(data["date"].to_s)
end

populate_categories
populate_tags
populate_classifications
end

def published?
Expand All @@ -76,15 +75,37 @@ def published?
end
end

def populate_categories
if categories.empty?
self.categories = Utils.pluralized_array_from_hash(data, 'category', 'categories').map {|c| c.to_s.downcase}
end
categories.flatten!
# Delegate accessors to @classifications for backward compatibility
def categories=(c)
@classifications ||= {}
@classifications['categories'] = c
end

def categories
@classifications ||= {}
@classifications['categories']
end

def tags=(t)
@classifications ||= {}
@classifications['tags'] = t
end

def tags
@classifications ||= {}
@classifications['tags']
end

def populate_tags
self.tags = Utils.pluralized_array_from_hash(data, "tag", "tags").flatten
def populate_classifications
@site.classifications.keys.each do |classification|
value = Utils.pluralized_array_from_hash(data, ActiveSupport::Inflector.singularize(classification), classification)

if classification == 'categories'
value = self.categories.empty? ? value.map { |c| c.to_s.downcase } : self.categories
end

classifications[classification] = value.flatten
end
end

# Get the full path to the directory containing the post files
Expand Down Expand Up @@ -278,6 +299,13 @@ 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)
Utils.deep_merge_hashes(super(attrs), @classifications)
end

# Returns the shorthand String identifier of this Post.
def inspect
"<Post: #{id}>"
Expand Down
76 changes: 53 additions & 23 deletions lib/jekyll/site.rb
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,27 @@ def collection_names
end
end

# The list of classifications and their corresponding Jekyll::Post instances.
#
# Returns a Hash containing classifications name-to-posts pairs.
def classifications
Hash[classification_names.map { |classification| [classification, post_attr_hash(classification)]} ]
end

# The list of classification names.
#
# Returns an array of classification names from the configuration
# or ["categories", "tags"] if the `classifications` key is not set.
def classification_names
if !@classifications
# Include categories and tags for backward compatibility
@classifications = %w(categories tags)
# Sanitize classifications
@classifications |= (config['classifications'] || []).reject { |c| c !~ /\A[a-z][a-zA-Z_-]*\z/ }
end
@classifications
end

# Read Site data from disk and load it into internal data structures.
#
# Returns nothing.
Expand Down Expand Up @@ -295,7 +316,13 @@ def post_attr_hash(post_attr)
# Build a hash map based on the specified post attribute ( post attr =>
# array of posts ) then sort each array in reverse order.
hash = Hash.new { |h, key| h[key] = [] }
posts.each { |p| p.send(post_attr.to_sym).each { |t| hash[t] << p } }
posts.each do |p|
if p.classifications.keys.include?(post_attr) && !p.respond_to?(post_attr.to_sym)
p.classifications[post_attr].each { |t| hash[t] << p }
else
p.send(post_attr.to_sym).each { |t| hash[t] << p }
end
end
hash.values.each { |posts| posts.sort!.reverse! }
hash
end
Expand All @@ -319,35 +346,38 @@ def site_data
# The Hash payload containing site-wide data.
#
# Returns the Hash: { "site" => data } where data is a Hash with keys:
# "time" - The Time as specified in the configuration or the
# current time if none was specified.
# "posts" - The Array of Posts, sorted chronologically by post date
# and then title.
# "pages" - The Array of all Pages.
# "html_pages" - The Array of HTML Pages.
# "categories" - The Hash of category values and Posts.
# See Site#post_attr_hash for type info.
# "tags" - The Hash of tag values and Posts.
# See Site#post_attr_hash for type info.
# "time" - The Time as specified in the configuration or the
# current time if none was specified.
# "posts" - The Array of Posts, sorted chronologically by post date
# and then title.
# "pages" - The Array of all Pages.
# "html_pages" - The Array of HTML Pages.
# "classifications" - The Hash of classification names and Hash of classification values and Posts.
# "categories" - The Hash of category values and Posts.
# 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
{
"jekyll" => {
"version" => Jekyll::VERSION,
"environment" => Jekyll.env
},
"site" => Utils.deep_merge_hashes(config,
Utils.deep_merge_hashes(Hash[collections.map{|label, coll| [label, coll.docs]}], {
"time" => time,
"posts" => posts.sort { |a, b| b <=> a },
"pages" => pages,
"static_files" => static_files.sort { |a, b| a.relative_path <=> b.relative_path },
"html_pages" => pages.reject { |page| !page.html? },
"categories" => post_attr_hash('categories'),
"tags" => post_attr_hash('tags'),
"collections" => collections,
"documents" => documents,
"data" => site_data
}))
Utils.deep_merge_hashes(Hash[collections.map{|label, coll| [label, coll.docs]}],
Utils.deep_merge_hashes(classifications, {
"time" => time,
"posts" => posts.sort { |a, b| b <=> a },
"pages" => pages,
"static_files" => static_files.sort { |a, b| a.relative_path <=> b.relative_path },
"html_pages" => pages.reject { |page| !page.html? },
"collections" => collections,
"classifications" => classifications,
"documents" => documents,
"data" => site_data
})))
}
end

Expand Down

0 comments on commit de5a9b1

Please sign in to comment.