From 75f49a751e5e33d1b660cacb6783548d7d43ded7 Mon Sep 17 00:00:00 2001 From: Parker Moore Date: Wed, 2 Apr 2014 16:31:02 -0400 Subject: [PATCH] OMG COLLECTIONS ARE RENDERING CALL THE POLICE --- features/collections.feature | 2 +- lib/jekyll.rb | 1 + lib/jekyll/cleaner.rb | 8 ++- lib/jekyll/collection.rb | 6 +- lib/jekyll/document.rb | 64 ++++++++++++++++- lib/jekyll/renderer.rb | 133 +++++++++++++++++++++++++++++++++++ lib/jekyll/site.rb | 40 +++++++++-- lib/jekyll/url.rb | 4 +- script/console | 38 ++++++++++ 9 files changed, 279 insertions(+), 17 deletions(-) create mode 100644 lib/jekyll/renderer.rb create mode 100755 script/console diff --git a/features/collections.feature b/features/collections.feature index 8c760915..0abf15e8 100644 --- a/features/collections.feature +++ b/features/collections.feature @@ -17,7 +17,7 @@ Feature: Collections And I have a configuration file with: | key | value | | collections | ['methods'] | - | render | \n methods: /methods/:subdir/:title:extname | + | render | ['methods'] | When I run jekyll Then the _site directory should exist And I should see "Collections: methods" in "_site/index.html" diff --git a/lib/jekyll.rb b/lib/jekyll.rb index 7fa69a7a..d7a2c2ea 100644 --- a/lib/jekyll.rb +++ b/lib/jekyll.rb @@ -53,6 +53,7 @@ require 'jekyll/cleaner' require 'jekyll/entry_filter' require 'jekyll/layout_reader' require 'jekyll/publisher' +require 'jekyll/renderer' # extensions require 'jekyll/plugin' diff --git a/lib/jekyll/cleaner.rb b/lib/jekyll/cleaner.rb index e3a89b4b..583fc844 100644 --- a/lib/jekyll/cleaner.rb +++ b/lib/jekyll/cleaner.rb @@ -4,6 +4,8 @@ module Jekyll class Site # Handles the cleanup of a site's destination before it is built. class Cleaner + attr_reader :site + def initialize(site) @site = site end @@ -27,7 +29,7 @@ module Jekyll # Returns a Set with the file paths def existing_files files = Set.new - Dir.glob(File.join(@site.dest, "**", "*"), File::FNM_DOTMATCH) do |file| + Dir.glob(File.join(site.dest, "**", "*"), File::FNM_DOTMATCH) do |file| files << file unless file =~ /\/\.{1,2}$/ || file =~ keep_file_regex end files @@ -38,7 +40,7 @@ module Jekyll # Returns a Set with the file paths def new_files files = Set.new - @site.each_site_file { |item| files << item.destination(@site.dest) } + site.each_site_file { |item| files << item.destination(site.dest) } files end @@ -64,7 +66,7 @@ module Jekyll # # Returns the regular expression def keep_file_regex - or_list = @site.keep_files.join("|") + or_list = site.keep_files.join("|") pattern = "\/(#{or_list.gsub(".", "\.")})" Regexp.new pattern end diff --git a/lib/jekyll/collection.rb b/lib/jekyll/collection.rb index 6a12fd65..e7878962 100644 --- a/lib/jekyll/collection.rb +++ b/lib/jekyll/collection.rb @@ -22,8 +22,12 @@ module Jekyll docs.sort! end + def relative_directory + "_#{label}" + end + def directory - Jekyll.sanitized_path(site.source, "_#{label}") + Jekyll.sanitized_path(site.source, relative_directory) end def allowed_document?(path) diff --git a/lib/jekyll/document.rb b/lib/jekyll/document.rb index f137e988..a82408ce 100644 --- a/lib/jekyll/document.rb +++ b/lib/jekyll/document.rb @@ -3,7 +3,7 @@ module Jekyll include Comparable attr_reader :path, :site - attr_accessor :content, :collection + attr_accessor :content, :collection, :output # Create a new Document. # @@ -37,10 +37,66 @@ module Jekyll File.extname(path) end + def cleaned_relative_path + relative_path[0 .. -extname.length - 1].sub(collection.relative_directory, "") + end + def yaml_file? %w[.yaml .yml].include?(extname) end + def sass_file? + %w[.sass .scss].include?(extname) + end + + def render_with_liquid? + !(sass_file? || yaml_file?) + end + + def url_template + "/:collection/:path:output_ext" + end + + def url_placeholders + { + collection: collection.label, + path: cleaned_relative_path, + output_ext: Jekyll::Renderer.new(site, self).output_ext + } + end + + def permalink + return nil if data.nil? || data['permalink'].nil? + data['permalink'] + end + + def url + @url ||= URL.new({ + :template => url_template, + :placeholders => url_placeholders, + :permalink => permalink + }).to_s + end + + def destination(base_directory) + path = Jekyll.sanitized_path(base_directory, url) + path = File.join(path, "index.html") if url =~ /\/$/ + path + end + + # Write the generated Document file to the destination directory. + # + # dest - The String path to the destination dir. + # + # Returns nothing. + def write(dest) + path = destination(dest) + FileUtils.mkdir_p(File.dirname(path)) + File.open(path, 'wb') do |f| + f.write(output) + end + end + # Returns merged option hash for File.read of self.site (if exists) # and a given param # @@ -84,8 +140,10 @@ module Jekyll # Returns a Hash representing this Document's data. def to_liquid data.merge({ - "content" => content, - "path" => path + "content" => content, + "path" => path, + "relative_path" => relative_path, + "url" => url }) end diff --git a/lib/jekyll/renderer.rb b/lib/jekyll/renderer.rb new file mode 100644 index 00000000..568619d7 --- /dev/null +++ b/lib/jekyll/renderer.rb @@ -0,0 +1,133 @@ +module Jekyll + class Renderer + + attr_reader :document, :site + + def initialize(site, document) + @site = site + @document = document + end + + # Determine which converters to use based on this document's + # extension. + # + # Returns an array of Converter instances. + def converters + @converters ||= site.converters.select { |c| c.matches(document.extname) } + end + + # Determine the extname the outputted file should have + # + # Returns the output extname including the leading period. + def output_ext + converters.first.output_ext(document.extname) + end + + ###################### + ## DAT RENDER THO + ###################### + + def run + payload = Utils.deep_merge_hashes({ + "page" => document.to_liquid + }, site.site_payload) + info = { + filters: [Jekyll::Filters], + registers: { :site => site, :page => payload['page'] } + } + + # render and transform content (this becomes the final content of the object) + payload["highlighter_prefix"] = converters.first.highlighter_prefix + payload["highlighter_suffix"] = converters.first.highlighter_suffix + + output = document.content + + if document.render_with_liquid? + output = render_liquid(output, payload, info) + end + + place_in_layouts( + convert(output), + payload, + info + ) + end + + # Convert the given content using the converters which match this renderer's document. + # + # content - the raw, unconverted content + # + # Returns the converted content. + def convert(content) + output = content.dup + converters.each do |converter| + begin + output = converter.convert(output) + rescue => e + Jekyll.logger.error "Conversion error:", "#{converter.class} encountered an error converting '#{document.relative_path}'." + raise e + end + end + output + end + + # Render the given content with the payload and info + # + # content - + # payload - + # info - + # path - (optional) the path to the file, for use in ex + # + # Returns the content, rendered by Liquid. + def render_liquid(content, payload, info, path = nil) + Liquid::Template.parse(content).render!(payload, info) + rescue Tags::IncludeTagError => e + Jekyll.logger.error "Liquid Exception:", "#{e.message} in #{e.path}, included in #{path || document.relative_path}" + raise e + rescue Exception => e + Jekyll.logger.error "Liquid Exception:", "#{e.message} in #{path || document.relative_path}" + raise e + end + + # Render layouts and place given content inside. + # + # content - the content to be placed in the layout + # + # + # Returns the content placed in the Liquid-rendered layouts + def place_in_layouts(content, payload, info) + output = content.dup + layout = site.layouts[document.data["layout"]] + used = Set.new([layout]) + + while layout + payload = Utils.deep_merge_hashes( + payload, + { + "content" => output, + "page" => document.to_liquid, + "layout" => layout.data + } + ) + + output = render_liquid( + layout.content, + payload, + info, + File.join(site.config['layouts'], layout.name) + ) + + if layout = layouts[layout.data["layout"]] + if used.include?(layout) + layout = nil # avoid recursive chain + else + used << layout + end + end + end + + output + end + + end +end diff --git a/lib/jekyll/site.rb b/lib/jekyll/site.rb index 204d0bbd..3172dd16 100644 --- a/lib/jekyll/site.rb +++ b/lib/jekyll/site.rb @@ -14,14 +14,13 @@ module Jekyll def initialize(config) self.config = config.clone - %w[ - safe lsi highlighter baseurl exclude include future unpublished - show_drafts limit_posts keep_files gems collections].each do |opt| + %w[safe lsi highlighter baseurl exclude include future unpublished + show_drafts limit_posts keep_files gems].each do |opt| self.send("#{opt}=", config[opt]) end - self.source = File.expand_path(config['source']) - self.dest = File.expand_path(config['destination']) + self.source = File.expand_path(config['source']) + self.dest = File.expand_path(config['destination']) self.permalink_style = config['permalink'].to_sym self.plugin_manager = Jekyll::PluginManager.new(self) @@ -85,6 +84,14 @@ module Jekyll end end + def collections + @collections ||= if config['collections'] + Hash[config['collections'].map { |coll| [coll, Jekyll::Collection.new(self, coll)] } ] + else + Hash.new + end + end + # Read Site data from disk and load it into internal data structures. # # Returns nothing. @@ -187,7 +194,6 @@ module Jekyll # Returns nothing. def read_collections if collections - self.collections = Hash[collections.map { |coll| [coll, Jekyll::Collection.new(self, coll)] } ] collections.each { |_, collection| collection.read } end end @@ -207,6 +213,14 @@ module Jekyll def render relative_permalinks_deprecation_method + if collections + collections.each do |label, collection| + collection.docs.each do |document| + document.output = Jekyll::Renderer.new(self, document).run + end + end + end + payload = site_payload [posts, pages].flatten.each do |page_or_post| page_or_post.render(layouts, payload) @@ -369,8 +383,20 @@ module Jekyll end end + def documents + docs = Set.new + if collections + collections.each do |label, coll| + if config['render'].include?(label) + docs = docs.merge(coll.docs) + end + end + end + docs + end + def each_site_file - %w(posts pages static_files).each do |type| + %w(posts pages static_files documents).each do |type| send(type).each do |item| yield item end diff --git a/lib/jekyll/url.rb b/lib/jekyll/url.rb index 66b4412d..8cd47242 100644 --- a/lib/jekyll/url.rb +++ b/lib/jekyll/url.rb @@ -24,9 +24,9 @@ module Jekyll # template. Instead, the given permalink will be # used as URL. def initialize(options) - @template = options[:template] + @template = options[:template] @placeholders = options[:placeholders] || {} - @permalink = options[:permalink] + @permalink = options[:permalink] if (@template || @permalink).nil? raise ArgumentError, "One of :template or :permalink must be supplied." diff --git a/script/console b/script/console new file mode 100755 index 00000000..34ad6e8a --- /dev/null +++ b/script/console @@ -0,0 +1,38 @@ +#!/usr/bin/env ruby + +require 'pry' +$LOAD_PATH.unshift File.join(File.dirname(__FILE__), *%w{ .. lib }) +require 'jekyll' + +TEST_DIR = File.expand_path(File.join(File.dirname(__FILE__), *%w{ .. test })) + +def fixture_site(overrides = {}) + Jekyll::Site.new(site_configuration(overrides)) +end + +def build_configs(overrides, base_hash = Jekyll::Configuration::DEFAULTS) + Jekyll::Utils.deep_merge_hashes(base_hash, overrides) +end + +def site_configuration(overrides = {}) + build_configs({ + "source" => source_dir, + "destination" => dest_dir + }, build_configs(overrides)) +end + +def dest_dir(*subdirs) + test_dir('dest', *subdirs) +end + +def source_dir(*subdirs) + test_dir('source', *subdirs) +end + +def test_dir(*subdirs) + File.join(TEST_DIR, *subdirs) +end + +module Jekyll + binding.pry +end