From 6a329d7166a10b064891a3338774ad8668e2f0e1 Mon Sep 17 00:00:00 2001 From: Jean-Paul Bonnetouche Date: Tue, 28 May 2013 04:27:17 +0200 Subject: [PATCH] Use custom classifications (tags, categories, ...) 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 : ```

Projects

``` --- jekyll.gemspec | 1 + lib/jekyll.rb | 1 + lib/jekyll/post.rb | 29 ++++++++++++++++++++++------ lib/jekyll/site.rb | 47 ++++++++++++++++++++++++++++++---------------- 4 files changed, 56 insertions(+), 22 deletions(-) diff --git a/jekyll.gemspec b/jekyll.gemspec index 35368433721..14baa4c19df 100644 --- a/jekyll.gemspec +++ b/jekyll.gemspec @@ -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") diff --git a/lib/jekyll.rb b/lib/jekyll.rb index 1ad928bf7b0..0f67cd3612d 100644 --- a/lib/jekyll.rb +++ b/lib/jekyll.rb @@ -26,6 +26,7 @@ def require_all(path) require 'liquid' require 'maruku' require 'colorator' +require 'active_support/inflector' # internal requires require 'jekyll/core_ext' diff --git a/lib/jekyll/post.rb b/lib/jekyll/post.rb index da64af93114..960b5dd4b73 100644 --- a/lib/jekyll/post.rb +++ b/lib/jekyll/post.rb @@ -11,10 +11,8 @@ class Post url date id - categories next previous - tags path ] @@ -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 @@ -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) @@ -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? @@ -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 @@ -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 "" diff --git a/lib/jekyll/site.rb b/lib/jekyll/site.rb index 5cad11c878d..a4ad7f10dd8 100644 --- a/lib/jekyll/site.rb +++ b/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 @@ -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 @@ -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 @@ -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 @@ -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 @@ -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