From a77c92aebe89a127ef4d9c39b39df892798c7e6a Mon Sep 17 00:00:00 2001 From: Parker Moore Date: Tue, 1 Apr 2014 19:06:42 -0400 Subject: [PATCH 01/30] Replace load-in of YAML data with Jekyll::Document logic. COLLECTIONS IS COMING --- lib/jekyll.rb | 1 + lib/jekyll/document.rb | 83 ++++++++++++++++++++++++++++++++++++++++++ lib/jekyll/site.rb | 2 +- test/test_site.rb | 12 +++--- 4 files changed, 91 insertions(+), 7 deletions(-) create mode 100644 lib/jekyll/document.rb diff --git a/lib/jekyll.rb b/lib/jekyll.rb index e2dd369c..d133f0b4 100644 --- a/lib/jekyll.rb +++ b/lib/jekyll.rb @@ -34,6 +34,7 @@ require 'jekyll/utils' require 'jekyll/stevenson' require 'jekyll/deprecator' require 'jekyll/configuration' +require 'jekyll/document' require 'jekyll/plugin_manager' require 'jekyll/site' require 'jekyll/convertible' diff --git a/lib/jekyll/document.rb b/lib/jekyll/document.rb new file mode 100644 index 00000000..50fbb056 --- /dev/null +++ b/lib/jekyll/document.rb @@ -0,0 +1,83 @@ +module Jekyll + class Document + + attr_reader :path + attr_accessor :content + + # Create a new Document. + # + # site - the Jekyll::Site instance to which this Document belongs + # path - the path to the file + # + # Returns nothing. + def initialize(site, path) + @site = site + @path = path + end + + # Fetch the Document's data. + # + # Returns a Hash containing the data. An empty hash is returned if + # no data was read. + def data + @data ||= Hash.new + end + + def extname + File.extname(path) + end + + def yaml_file? + %w[.yaml .yml].include?(extname) + end + + # Returns merged option hash for File.read of self.site (if exists) + # and a given param + # + # opts - override options + # + # Return + def merged_file_read_opts(opts) + (site ? site.file_read_opts : {}).merge(opts) + end + + # Whether the file is published or not, as indicated in YAML front-matter + # + # Returns true if the 'published' key is specified in the YAML front-matter and not `false`. + def published? + !(data.has_key?('published') && data['published'] == false) + end + + # Read in the file and assign the content and data based on the file contents. + # + # Returns nothing. + def read + if yaml_file? + @data = SafeYAML.load_file(path) + else + begin + @content = File.read(path, merged_file_read_opts(opts)) + if content =~ /\A(---\s*\n.*?\n?)^(---\s*$\n?)/m + @content = $POSTMATCH + @data = SafeYAML.load($1) + end + rescue SyntaxError => e + puts "YAML Exception reading #{path}: #{e.message}" + rescue Exception => e + puts "Error reading file #{path}: #{e.message}" + end + end + end + + # Create a Liquid-understandable version of this Document. + # + # Returns a Hash representing this Document's data. + def to_liquid + data.merge({ + "content" => content, + "path" => path + }) + end + + end +end diff --git a/lib/jekyll/site.rb b/lib/jekyll/site.rb index 14224898..4bb8f838 100644 --- a/lib/jekyll/site.rb +++ b/lib/jekyll/site.rb @@ -177,7 +177,7 @@ module Jekyll next if File.symlink?(path) && safe key = sanitize_filename(File.basename(entry, '.*')) - self.data[key] = SafeYAML.load_file(path) + (self.data[key] = Jekyll::Document.new(self, path)).read end end diff --git a/test/test_site.rb b/test/test_site.rb index c5d78594..8b805860 100644 --- a/test/test_site.rb +++ b/test/test_site.rb @@ -364,8 +364,8 @@ class TestSite < Test::Unit::TestCase file_content = SafeYAML.load_file(File.join(source_dir, '_data', 'members.yaml')) - assert_equal site.data['members'], file_content - assert_equal site.site_payload['site']['data']['members'], file_content + assert_equal site.data['members'].data, file_content + assert_equal site.site_payload['site']['data']['members'].data, file_content end should 'auto load yml files' do @@ -374,8 +374,8 @@ class TestSite < Test::Unit::TestCase file_content = SafeYAML.load_file(File.join(source_dir, '_data', 'languages.yml')) - assert_equal site.data['languages'], file_content - assert_equal site.site_payload['site']['data']['languages'], file_content + assert_equal site.data['languages'].data, file_content + assert_equal site.site_payload['site']['data']['languages'].data, file_content end should "load symlink files in unsafe mode" do @@ -384,8 +384,8 @@ class TestSite < Test::Unit::TestCase file_content = SafeYAML.load_file(File.join(source_dir, '_data', 'products.yml')) - assert_equal site.data['products'], file_content - assert_equal site.site_payload['site']['data']['products'], file_content + assert_equal site.data['products'].data, file_content + assert_equal site.site_payload['site']['data']['products'].data, file_content end should "not load symlink files in safe mode" do From 50b46d7beeda175aef77b638ac9c66ae86c83ba8 Mon Sep 17 00:00:00 2001 From: Parker Moore Date: Tue, 1 Apr 2014 21:19:10 -0400 Subject: [PATCH 02/30] OMG it's happening ~*Collections*~ --- lib/jekyll.rb | 1 + lib/jekyll/collection.rb | 33 ++++++++++++ lib/jekyll/configuration.rb | 1 + lib/jekyll/document.rb | 15 ++++-- lib/jekyll/site.rb | 20 +++++-- test/source/_methods/configuration.md | 8 +++ test/source/_methods/sanitized_path.md | 5 ++ test/source/_methods/site/generate.md | 5 ++ test/source/_methods/site/initialize.md | 5 ++ test/source/_methods/um_hi.md | 1 + test/test_collections.rb | 71 +++++++++++++++++++++++++ 11 files changed, 157 insertions(+), 8 deletions(-) create mode 100644 lib/jekyll/collection.rb create mode 100644 test/source/_methods/configuration.md create mode 100644 test/source/_methods/sanitized_path.md create mode 100644 test/source/_methods/site/generate.md create mode 100644 test/source/_methods/site/initialize.md create mode 120000 test/source/_methods/um_hi.md create mode 100644 test/test_collections.rb diff --git a/lib/jekyll.rb b/lib/jekyll.rb index d133f0b4..7fa69a7a 100644 --- a/lib/jekyll.rb +++ b/lib/jekyll.rb @@ -35,6 +35,7 @@ require 'jekyll/stevenson' require 'jekyll/deprecator' require 'jekyll/configuration' require 'jekyll/document' +require 'jekyll/collection' require 'jekyll/plugin_manager' require 'jekyll/site' require 'jekyll/convertible' diff --git a/lib/jekyll/collection.rb b/lib/jekyll/collection.rb new file mode 100644 index 00000000..8e0b379c --- /dev/null +++ b/lib/jekyll/collection.rb @@ -0,0 +1,33 @@ +module Jekyll + class Collection + attr_reader :site, :label + + def initialize(site, label) + @site = site + @label = label + end + + def docs + @docs ||= [] + end + + def read + Dir.glob(File.join(directory, "**", "*.*")).each do |file_path| + if allowed_document?(file_path) + doc = Jekyll::Document.new(file_path, { site: site, collection: self }) + docs << doc + end + end + docs + end + + def directory + Jekyll.sanitized_path(site.source, "_#{label}") + end + + def allowed_document?(path) + !(site.safe && File.symlink?(path)) + end + + end +end diff --git a/lib/jekyll/configuration.rb b/lib/jekyll/configuration.rb index 93dc5193..ffdb0a22 100644 --- a/lib/jekyll/configuration.rb +++ b/lib/jekyll/configuration.rb @@ -13,6 +13,7 @@ module Jekyll 'data_source' => '_data', 'keep_files' => ['.git','.svn'], 'gems' => [], + 'collections' => nil, 'timezone' => nil, # use the local timezone diff --git a/lib/jekyll/document.rb b/lib/jekyll/document.rb index 50fbb056..198b77fb 100644 --- a/lib/jekyll/document.rb +++ b/lib/jekyll/document.rb @@ -1,18 +1,19 @@ module Jekyll class Document - attr_reader :path - attr_accessor :content + attr_reader :path, :site + attr_accessor :content, :collection # Create a new Document. # - # site - the Jekyll::Site instance to which this Document belongs + # shit - the Jekyll::Site instance to which this Document belongs # path - the path to the file # # Returns nothing. - def initialize(site, path) - @site = site + def initialize(path, relations) + @site = relations[:site] @path = path + @collection = relations[:collection] end # Fetch the Document's data. @@ -23,6 +24,10 @@ module Jekyll @data ||= Hash.new end + def relative_path + Pathname.new(path).relative_path_from(Pathname.new(site.source)).to_s + end + def extname File.extname(path) end diff --git a/lib/jekyll/site.rb b/lib/jekyll/site.rb index 4bb8f838..7e3cdc5d 100644 --- a/lib/jekyll/site.rb +++ b/lib/jekyll/site.rb @@ -4,7 +4,7 @@ module Jekyll :exclude, :include, :source, :dest, :lsi, :highlighter, :permalink_style, :time, :future, :unpublished, :safe, :plugins, :limit_posts, :show_drafts, :keep_files, :baseurl, :data, :file_read_opts, :gems, - :plugin_manager + :plugin_manager, :collections attr_accessor :converters, :generators @@ -14,7 +14,9 @@ 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].each do |opt| + %w[ + safe lsi highlighter baseurl exclude include future unpublished + show_drafts limit_posts keep_files gems collections].each do |opt| self.send("#{opt}=", config[opt]) end @@ -90,6 +92,7 @@ module Jekyll self.layouts = LayoutReader.new(self).read read_directories read_data(config['data_source']) + read_collections end # Recursively traverse directories to find posts, pages and static files @@ -171,13 +174,24 @@ module Jekyll entries = Dir.chdir(base) { Dir['*.{yaml,yml}'] } entries.delete_if { |e| File.directory?(File.join(base, e)) } + data_collection = Jekyll::Collection.new(self, "data") entries.each do |entry| path = File.join(source, dir, entry) next if File.symlink?(path) && safe key = sanitize_filename(File.basename(entry, '.*')) - (self.data[key] = Jekyll::Document.new(self, path)).read + (self.data[key] = Jekyll::Document.new(path, { site: self, collection: data_collection })).read + end + end + + # Read in all collections specified in the configuration + # + # 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 diff --git a/test/source/_methods/configuration.md b/test/source/_methods/configuration.md new file mode 100644 index 00000000..fd17980b --- /dev/null +++ b/test/source/_methods/configuration.md @@ -0,0 +1,8 @@ +--- +title: "Jekyll.configuration" +whatever: foo.bar +--- + +Use `{{ page.title }}` to build a full configuration for use w/Jekyll. + +Whatever: {{ page.whatever }} diff --git a/test/source/_methods/sanitized_path.md b/test/source/_methods/sanitized_path.md new file mode 100644 index 00000000..8b4d767a --- /dev/null +++ b/test/source/_methods/sanitized_path.md @@ -0,0 +1,5 @@ +--- +title: "Jekyll.sanitized_path" +--- + +`{{ page.title }}` is used to make sure your path is in your source. diff --git a/test/source/_methods/site/generate.md b/test/source/_methods/site/generate.md new file mode 100644 index 00000000..b7eaaf62 --- /dev/null +++ b/test/source/_methods/site/generate.md @@ -0,0 +1,5 @@ +--- +title: "Site#generate" +--- + +Run your generators! diff --git a/test/source/_methods/site/initialize.md b/test/source/_methods/site/initialize.md new file mode 100644 index 00000000..9c23b967 --- /dev/null +++ b/test/source/_methods/site/initialize.md @@ -0,0 +1,5 @@ +--- +title: "Site#initialize" +--- + +Create dat site. diff --git a/test/source/_methods/um_hi.md b/test/source/_methods/um_hi.md new file mode 120000 index 00000000..c549c8b4 --- /dev/null +++ b/test/source/_methods/um_hi.md @@ -0,0 +1 @@ +test/source/_methods/sanitized_path.md \ No newline at end of file diff --git a/test/test_collections.rb b/test/test_collections.rb new file mode 100644 index 00000000..afe8bd06 --- /dev/null +++ b/test/test_collections.rb @@ -0,0 +1,71 @@ +require 'helper' + +class TestCollections < Test::Unit::TestCase + + context "with no collections specified" do + setup do + @site = Site.new(Jekyll.configuration({ + "source" => source_dir, + "destination" => dest_dir + })) + @site.process + end + + should "not contain any collections" do + assert_nil @site.collections + end + end + + context "with a collection" do + setup do + @site = Site.new(Jekyll.configuration({ + "collections" => ["methods"], + "source" => source_dir, + "destination" => dest_dir + })) + @site.process + end + + should "create a Hash on Site with the label mapped to the instance of the Collection" do + assert @site.collections.is_a?(Hash) + assert_not_nil @site.collections["methods"] + assert @site.collections["methods"].is_a? Jekyll::Collection + end + + should "collects docs in an array on the Collection object" do + assert @site.collections["methods"].docs.is_a? Array + @site.collections["methods"].docs.each do |doc| + assert doc.is_a? Jekyll::Document + assert_include %w[ + _methods/configuration.md + _methods/sanitized_path.md + _methods/site/generate.md + _methods/site/initialize.md + _methods/um_hi.md + ], doc.relative_path + end + end + end + + context "in safe mode" do + setup do + @site = Site.new(Jekyll.configuration({ + "collections" => ["methods"], + "safe" => true, + "source" => source_dir, + "destination" => dest_dir + })) + @site.process + @collection = @site.collections["methods"] + end + + should "not allow symlinks" do + assert !@collection.allowed_document?(File.join(@collection.directory, "um_hi.md")) + end + + should "not include the symlinked file in the list of docs" do + assert_not_include %w[_methods/um_hi.md], @collection.docs.map(&:relative_path) + end + end + +end From 08162dbb50dd9973e483e079de4ca816ac660853 Mon Sep 17 00:00:00 2001 From: Parker Moore Date: Tue, 1 Apr 2014 21:38:30 -0400 Subject: [PATCH 03/30] Hey girl, i heard you like YAML. --- lib/jekyll/site.rb | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/lib/jekyll/site.rb b/lib/jekyll/site.rb index 7e3cdc5d..de3d027e 100644 --- a/lib/jekyll/site.rb +++ b/lib/jekyll/site.rb @@ -181,7 +181,11 @@ module Jekyll next if File.symlink?(path) && safe key = sanitize_filename(File.basename(entry, '.*')) - (self.data[key] = Jekyll::Document.new(path, { site: self, collection: data_collection })).read + + doc = Jekyll::Document.new(path, { site: self, collection: data_collection }) + doc.read + + self.data[key] = doc.data end end From 90807ac6e78cd9977a9cc80832840326e621cd29 Mon Sep 17 00:00:00 2001 From: Parker Moore Date: Tue, 1 Apr 2014 23:10:17 -0400 Subject: [PATCH 04/30] DEM TESTS --- lib/jekyll/collection.rb | 1 + lib/jekyll/document.rb | 6 ++++- lib/jekyll/site.rb | 13 +++------- test/source/_methods/um_hi.md | 2 +- test/test_document.rb | 45 +++++++++++++++++++++++++++++++++++ 5 files changed, 55 insertions(+), 12 deletions(-) create mode 100644 test/test_document.rb diff --git a/lib/jekyll/collection.rb b/lib/jekyll/collection.rb index 8e0b379c..95e4c8a1 100644 --- a/lib/jekyll/collection.rb +++ b/lib/jekyll/collection.rb @@ -15,6 +15,7 @@ module Jekyll Dir.glob(File.join(directory, "**", "*.*")).each do |file_path| if allowed_document?(file_path) doc = Jekyll::Document.new(file_path, { site: site, collection: self }) + doc.read docs << doc end end diff --git a/lib/jekyll/document.rb b/lib/jekyll/document.rb index 198b77fb..e637fbd1 100644 --- a/lib/jekyll/document.rb +++ b/lib/jekyll/document.rb @@ -28,6 +28,10 @@ module Jekyll Pathname.new(path).relative_path_from(Pathname.new(site.source)).to_s end + def basename(suffix = "") + File.basename(path, suffix) + end + def extname File.extname(path) end @@ -56,7 +60,7 @@ module Jekyll # Read in the file and assign the content and data based on the file contents. # # Returns nothing. - def read + def read(opts = {}) if yaml_file? @data = SafeYAML.load_file(path) else diff --git a/lib/jekyll/site.rb b/lib/jekyll/site.rb index de3d027e..8fe10776 100644 --- a/lib/jekyll/site.rb +++ b/lib/jekyll/site.rb @@ -175,16 +175,9 @@ module Jekyll entries = Dir.chdir(base) { Dir['*.{yaml,yml}'] } entries.delete_if { |e| File.directory?(File.join(base, e)) } data_collection = Jekyll::Collection.new(self, "data") - - entries.each do |entry| - path = File.join(source, dir, entry) - next if File.symlink?(path) && safe - - key = sanitize_filename(File.basename(entry, '.*')) - - doc = Jekyll::Document.new(path, { site: self, collection: data_collection }) - doc.read - + data_collection.read + data_collection.docs.each do |doc| + key = sanitize_filename(doc.basename(".*")) self.data[key] = doc.data end end diff --git a/test/source/_methods/um_hi.md b/test/source/_methods/um_hi.md index c549c8b4..9ebb5325 120000 --- a/test/source/_methods/um_hi.md +++ b/test/source/_methods/um_hi.md @@ -1 +1 @@ -test/source/_methods/sanitized_path.md \ No newline at end of file +./site/generate.md \ No newline at end of file diff --git a/test/test_document.rb b/test/test_document.rb new file mode 100644 index 00000000..75bf2a84 --- /dev/null +++ b/test/test_document.rb @@ -0,0 +1,45 @@ +require 'helper' + +class TestDocument < Test::Unit::TestCase + + context "" do + setup do + @site = Site.new(Jekyll.configuration({ + "collections" => ["methods"], + "source" => source_dir, + "destination" => dest_dir + })) + @site.process + @document = @site.collections["methods"].docs.first + end + + should "know its relative path" do + assert_equal "_methods/configuration.md", @document.relative_path + end + + should "knows its extname" do + assert_equal ".md", @document.extname + end + + should "know its basename" do + assert_equal "configuration.md", @document.basename + end + + should "allow the suffix to be specified for the basename" do + assert_equal "configuration", @document.basename(".*") + end + + should "know whether its a yaml file" do + assert_equal false, @document.yaml_file? + end + + should "know its data" do + assert_equal({ + "title" => "Jekyll.configuration", + "whatever" => "foo.bar" + }, @document.data) + end + + end + +end From cefe99bed22456553284053f946f3fca997da799 Mon Sep 17 00:00:00 2001 From: Parker Moore Date: Tue, 1 Apr 2014 23:18:18 -0400 Subject: [PATCH 05/30] Sort the docs based on path --- lib/jekyll/collection.rb | 2 +- lib/jekyll/document.rb | 5 +++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/lib/jekyll/collection.rb b/lib/jekyll/collection.rb index 95e4c8a1..21351374 100644 --- a/lib/jekyll/collection.rb +++ b/lib/jekyll/collection.rb @@ -19,7 +19,7 @@ module Jekyll docs << doc end end - docs + docs.sort! end def directory diff --git a/lib/jekyll/document.rb b/lib/jekyll/document.rb index e637fbd1..23ee437e 100644 --- a/lib/jekyll/document.rb +++ b/lib/jekyll/document.rb @@ -1,5 +1,6 @@ module Jekyll class Document + include Comparable attr_reader :path, :site attr_accessor :content, :collection @@ -88,5 +89,9 @@ module Jekyll }) end + def <=>(anotherDocument) + path <=> anotherDocument.path + end + end end From c1c5cc78a529a4b70b28daadb20fb19eba010cd1 Mon Sep 17 00:00:00 2001 From: Parker Moore Date: Tue, 1 Apr 2014 23:51:48 -0400 Subject: [PATCH 06/30] Expect site.data stuff to be hashes --- test/test_site.rb | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/test/test_site.rb b/test/test_site.rb index 8b805860..c5d78594 100644 --- a/test/test_site.rb +++ b/test/test_site.rb @@ -364,8 +364,8 @@ class TestSite < Test::Unit::TestCase file_content = SafeYAML.load_file(File.join(source_dir, '_data', 'members.yaml')) - assert_equal site.data['members'].data, file_content - assert_equal site.site_payload['site']['data']['members'].data, file_content + assert_equal site.data['members'], file_content + assert_equal site.site_payload['site']['data']['members'], file_content end should 'auto load yml files' do @@ -374,8 +374,8 @@ class TestSite < Test::Unit::TestCase file_content = SafeYAML.load_file(File.join(source_dir, '_data', 'languages.yml')) - assert_equal site.data['languages'].data, file_content - assert_equal site.site_payload['site']['data']['languages'].data, file_content + assert_equal site.data['languages'], file_content + assert_equal site.site_payload['site']['data']['languages'], file_content end should "load symlink files in unsafe mode" do @@ -384,8 +384,8 @@ class TestSite < Test::Unit::TestCase file_content = SafeYAML.load_file(File.join(source_dir, '_data', 'products.yml')) - assert_equal site.data['products'].data, file_content - assert_equal site.site_payload['site']['data']['products'].data, file_content + assert_equal site.data['products'], file_content + assert_equal site.site_payload['site']['data']['products'], file_content end should "not load symlink files in safe mode" do From f082eca791f1f9369e8d13bf45555031d0880c20 Mon Sep 17 00:00:00 2001 From: Parker Moore Date: Wed, 2 Apr 2014 00:53:43 -0400 Subject: [PATCH 07/30] GUYS failing test for rendering --- features/collections.feature | 24 +++++++++++++++++++++++ features/step_definitions/jekyll_steps.rb | 15 +++++++++++--- features/support/env.rb | 5 +++++ lib/jekyll/collection.rb | 11 +++++++++++ lib/jekyll/document.rb | 4 ++++ lib/jekyll/site.rb | 3 ++- 6 files changed, 58 insertions(+), 4 deletions(-) create mode 100644 features/collections.feature diff --git a/features/collections.feature b/features/collections.feature new file mode 100644 index 00000000..209232e3 --- /dev/null +++ b/features/collections.feature @@ -0,0 +1,24 @@ +Feature: Collections + As a hacker who likes to structure content + I want to be able to create collections of similar information + And render them + + Scenario: Unrendered collection + Given I have an "index.html" page that contains "Collections: {{ site.collections }}" + And I have fixture collections + And I have a configuration file with "collections" set to "['methods']" + When I debug jekyll + Then the _site directory should exist + And I should see "Collections: {\"methods\"=>#, #, #, #, #\]>}" in "_site/index.html" + + Scenario: Rendered collection + Given I have an "index.html" page that contains "Collections: {{ site.collections.methods.label }}" + And I have fixture collections + And I have a configuration file with: + | key | value | + | collections | ['methods'] | + | render | \n methods: /methods/:collection_name/:subdir/:title:extname | + When I run jekyll + Then the _site directory should exist + And I should see "Collections: methods" in "_site/index.html" + And I should see "Whatever: foo.bar" in "_site/methods/configuration.html" \ No newline at end of file diff --git a/features/step_definitions/jekyll_steps.rb b/features/step_definitions/jekyll_steps.rb index 93de5c10..45210630 100644 --- a/features/step_definitions/jekyll_steps.rb +++ b/features/step_definitions/jekyll_steps.rb @@ -16,15 +16,14 @@ def file_content_from_hash(input_hash) EOF end - Before do FileUtils.mkdir_p(TEST_DIR) unless File.exist?(TEST_DIR) Dir.chdir(TEST_DIR) end After do - FileUtils.rm_rf(TEST_DIR) if File.exist?(TEST_DIR) - FileUtils.rm(JEKYLL_COMMAND_OUTPUT_FILE) + FileUtils.rm_rf(TEST_DIR) if File.exists?(TEST_DIR) + FileUtils.rm(JEKYLL_COMMAND_OUTPUT_FILE) if File.exists?(JEKYLL_COMMAND_OUTPUT_FILE) end World(Test::Unit::Assertions) @@ -130,6 +129,16 @@ Given /^I have a configuration file with "([^\"]*)" set to:$/ do |key, table| end end +Given /^I have fixture collections$/ do + FileUtils.cp_r File.join(JEKYLL_SOURCE_DIR, "test", "source", "_methods"), source_dir +end + +################## +# +# Changing stuff +# +################## + When /^I run jekyll(?: with "(.+)")?$/ do |opt| run_jekyll_build(opt) end diff --git a/features/support/env.rb b/features/support/env.rb index ad3bb38a..593e1304 100644 --- a/features/support/env.rb +++ b/features/support/env.rb @@ -6,10 +6,15 @@ require 'rr' require 'test/unit' require 'time' +JEKYLL_SOURCE_DIR = File.dirname(File.dirname(File.dirname(__FILE__))) TEST_DIR = File.expand_path(File.join('..', '..', 'tmp', 'jekyll'), File.dirname(__FILE__)) JEKYLL_PATH = File.join(File.dirname(__FILE__), '..', '..', 'bin', 'jekyll') JEKYLL_COMMAND_OUTPUT_FILE = File.join(File.dirname(TEST_DIR), 'jekyll_output.txt') +def source_dir(*files) + File.join(TEST_DIR, *files) +end + def jekyll_output_file JEKYLL_COMMAND_OUTPUT_FILE end diff --git a/lib/jekyll/collection.rb b/lib/jekyll/collection.rb index 21351374..6a12fd65 100644 --- a/lib/jekyll/collection.rb +++ b/lib/jekyll/collection.rb @@ -30,5 +30,16 @@ module Jekyll !(site.safe && File.symlink?(path)) end + def inspect + "#" + end + + def to_liquid + { + "label" => label, + "docs" => docs + } + end + end end diff --git a/lib/jekyll/document.rb b/lib/jekyll/document.rb index 23ee437e..f137e988 100644 --- a/lib/jekyll/document.rb +++ b/lib/jekyll/document.rb @@ -89,6 +89,10 @@ module Jekyll }) end + def inspect + "#" + end + def <=>(anotherDocument) path <=> anotherDocument.path end diff --git a/lib/jekyll/site.rb b/lib/jekyll/site.rb index 8fe10776..204d0bbd 100644 --- a/lib/jekyll/site.rb +++ b/lib/jekyll/site.rb @@ -290,7 +290,8 @@ module Jekyll "html_pages" => pages.reject { |page| !page.html? }, "categories" => post_attr_hash('categories'), "tags" => post_attr_hash('tags'), - "data" => site_data})} + "data" => site_data, + "collections" => collections})} end # Filter out any files/directories that are hidden or backup files (start From 7fef0302a72e873fa467e73115dffd51d426818e Mon Sep 17 00:00:00 2001 From: Parker Moore Date: Wed, 2 Apr 2014 00:56:17 -0400 Subject: [PATCH 08/30] Strike duplicate methods & :collection_name filler --- features/collections.feature | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/features/collections.feature b/features/collections.feature index 209232e3..8c760915 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/:collection_name/:subdir/:title:extname | + | render | \n methods: /methods/:subdir/:title:extname | When I run jekyll Then the _site directory should exist And I should see "Collections: methods" in "_site/index.html" From 75f49a751e5e33d1b660cacb6783548d7d43ded7 Mon Sep 17 00:00:00 2001 From: Parker Moore Date: Wed, 2 Apr 2014 16:31:02 -0400 Subject: [PATCH 09/30] 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 From a15a58413622698ca4971531720d73155c6f84c5 Mon Sep 17 00:00:00 2001 From: Parker Moore Date: Wed, 2 Apr 2014 16:47:31 -0400 Subject: [PATCH 10/30] Don't let that render get you down. --- lib/jekyll/site.rb | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/lib/jekyll/site.rb b/lib/jekyll/site.rb index 3172dd16..1c036939 100644 --- a/lib/jekyll/site.rb +++ b/lib/jekyll/site.rb @@ -92,6 +92,10 @@ module Jekyll end end + def render + config['render'] || Array.new + end + # Read Site data from disk and load it into internal data structures. # # Returns nothing. @@ -387,7 +391,7 @@ module Jekyll docs = Set.new if collections collections.each do |label, coll| - if config['render'].include?(label) + if render.include?(label) docs = docs.merge(coll.docs) end end From 37a7236e20b5b671e1e609501afe0ddec64a83f3 Mon Sep 17 00:00:00 2001 From: Parker Moore Date: Wed, 2 Apr 2014 17:17:55 -0400 Subject: [PATCH 11/30] Homagah it all renders I think. --- features/collections.feature | 18 ++++++++++++++++-- lib/jekyll/renderer.rb | 3 ++- lib/jekyll/site.rb | 22 ++++++++-------------- test/source/_methods/site/generate.md | 3 ++- test/test_collections.rb | 2 +- test/test_document.rb | 5 ++++- 6 files changed, 33 insertions(+), 20 deletions(-) diff --git a/features/collections.feature b/features/collections.feature index 0abf15e8..4eb526d3 100644 --- a/features/collections.feature +++ b/features/collections.feature @@ -7,7 +7,7 @@ Feature: Collections Given I have an "index.html" page that contains "Collections: {{ site.collections }}" And I have fixture collections And I have a configuration file with "collections" set to "['methods']" - When I debug jekyll + When I run jekyll Then the _site directory should exist And I should see "Collections: {\"methods\"=>#, #, #, #, #\]>}" in "_site/index.html" @@ -21,4 +21,18 @@ Feature: Collections When I run jekyll Then the _site directory should exist And I should see "Collections: methods" in "_site/index.html" - And I should see "Whatever: foo.bar" in "_site/methods/configuration.html" \ No newline at end of file + And I should see "

Whatever: foo.bar

" in "_site/methods/configuration.html" + + Scenario: Rendered document in a layout + Given I have an "index.html" page that contains "Collections: {{ site.collections.methods.label }}" + And I have a default layout that contains "
Tom Preston-Werner
{{content}}" + And I have fixture collections + And I have a configuration file with: + | key | value | + | collections | ['methods'] | + | render | ['methods'] | + When I run jekyll + Then the _site directory should exist + And I should see "Collections: methods" in "_site/index.html" + And I should see "

Run your generators! default

" in "_site/methods/site/generate.html" + And I should see "
Tom Preston-Werner
" in "_site/methods/site/generate.html" \ No newline at end of file diff --git a/lib/jekyll/renderer.rb b/lib/jekyll/renderer.rb index 568619d7..0fb7b7f1 100644 --- a/lib/jekyll/renderer.rb +++ b/lib/jekyll/renderer.rb @@ -31,6 +31,7 @@ module Jekyll payload = Utils.deep_merge_hashes({ "page" => document.to_liquid }, site.site_payload) + info = { filters: [Jekyll::Filters], registers: { :site => site, :page => payload['page'] } @@ -117,7 +118,7 @@ module Jekyll File.join(site.config['layouts'], layout.name) ) - if layout = layouts[layout.data["layout"]] + if layout = site.layouts[layout.data["layout"]] if used.include?(layout) layout = nil # avoid recursive chain else diff --git a/lib/jekyll/site.rb b/lib/jekyll/site.rb index 1c036939..2bdf1898 100644 --- a/lib/jekyll/site.rb +++ b/lib/jekyll/site.rb @@ -92,7 +92,7 @@ module Jekyll end end - def render + def to_render config['render'] || Array.new end @@ -197,9 +197,7 @@ module Jekyll # # Returns nothing. def read_collections - if collections - collections.each { |_, collection| collection.read } - end + collections.each { |_, collection| collection.read } end # Run each of the Generators. @@ -217,11 +215,9 @@ 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 + collections.each do |label, collection| + collection.docs.each do |document| + document.output = Jekyll::Renderer.new(self, document).run end end @@ -389,11 +385,9 @@ module Jekyll def documents docs = Set.new - if collections - collections.each do |label, coll| - if render.include?(label) - docs = docs.merge(coll.docs) - end + collections.each do |label, coll| + if to_render.include?(label) + docs = docs.merge(coll.docs) end end docs diff --git a/test/source/_methods/site/generate.md b/test/source/_methods/site/generate.md index b7eaaf62..1cab376e 100644 --- a/test/source/_methods/site/generate.md +++ b/test/source/_methods/site/generate.md @@ -1,5 +1,6 @@ --- title: "Site#generate" +layout: default --- -Run your generators! +Run your generators! {{ page.layout }} diff --git a/test/test_collections.rb b/test/test_collections.rb index afe8bd06..06120790 100644 --- a/test/test_collections.rb +++ b/test/test_collections.rb @@ -12,7 +12,7 @@ class TestCollections < Test::Unit::TestCase end should "not contain any collections" do - assert_nil @site.collections + assert_equal @site.collections, Hash.new end end diff --git a/test/test_document.rb b/test/test_document.rb index 75bf2a84..794c2117 100644 --- a/test/test_document.rb +++ b/test/test_document.rb @@ -2,7 +2,7 @@ require 'helper' class TestDocument < Test::Unit::TestCase - context "" do + context "a document in a collection" do setup do @site = Site.new(Jekyll.configuration({ "collections" => ["methods"], @@ -42,4 +42,7 @@ class TestDocument < Test::Unit::TestCase end + context " a document part of a rendered collection" do + end + end From 77bb678a3db0dc324c228b320bf84efbe5f0da1f Mon Sep 17 00:00:00 2001 From: Parker Moore Date: Wed, 2 Apr 2014 17:54:17 -0400 Subject: [PATCH 12/30] Add some documentation --- site/_data/docs.yml | 1 + site/docs/collections.md | 48 ++++++++++++++++++++++++++++++++++++++++ site/docs/datafiles.md | 2 +- site/docs/variables.md | 2 +- 4 files changed, 51 insertions(+), 2 deletions(-) create mode 100644 site/docs/collections.md diff --git a/site/_data/docs.yml b/site/_data/docs.yml index 1c790636..7d0a83f3 100644 --- a/site/_data/docs.yml +++ b/site/_data/docs.yml @@ -14,6 +14,7 @@ - drafts - pages - variables + - collections - datafiles - assets - migrations diff --git a/site/docs/collections.md b/site/docs/collections.md new file mode 100644 index 00000000..0016cf4c --- /dev/null +++ b/site/docs/collections.md @@ -0,0 +1,48 @@ +--- +layout: docs +title: Collections +prev_section: variables +next_section: datafiles +permalink: /docs/collections/ +--- + +
+
Collections support is currently unreleased.
+

+ In order to use this feature, + install the latest development version of Jekyll. +

+
+ +Put some things in a folder and add the folder to your config. It's simple... + +Why did we write this feature? What is it useful for? + +## Using Collections + +### Step 1: Tell Jekyll to read in your collection + +{% highlight yaml %} +collections: +- my_collection +{% endhighlight %} + +### Step 2: Add your content + +Create a corresponding folder (e.g. `/_my_collection`) and add documents. +YAML front-matter is read in as data if it exists, if not, then everything is just +stuck in the Document's `content` attribute. + +### Step 3: Optionally render your collection's documents into independent files + +If you'd like your files rendered, add it to your config: + +{% highlight yaml %} +render: +- my_collection +{% endhighlight %} + +This will produce a file for each document in the collection. +For example, if you have `_my_collection/some_subdir/some_doc.md`, +it will be rendered using Liquid and the Markdown converter of your +choice and written out to `/my_collection/some_subdir/some_doc.html`. diff --git a/site/docs/datafiles.md b/site/docs/datafiles.md index 55af6ec4..1dbe4097 100644 --- a/site/docs/datafiles.md +++ b/site/docs/datafiles.md @@ -1,7 +1,7 @@ --- layout: docs title: Data Files -prev_section: variables +prev_section: collections next_section: assets permalink: /docs/datafiles/ --- diff --git a/site/docs/variables.md b/site/docs/variables.md index 2c6d3849..d6c7390b 100644 --- a/site/docs/variables.md +++ b/site/docs/variables.md @@ -2,7 +2,7 @@ layout: docs title: Variables prev_section: pages -next_section: datafiles +next_section: collections permalink: /docs/variables/ --- From aa2fb685d95959ce32ae63e1ea7587fcffdcec51 Mon Sep 17 00:00:00 2001 From: Ben Balter Date: Thu, 3 Apr 2014 13:57:40 -0400 Subject: [PATCH 13/30] I am the King of Copy --- site/docs/collections.md | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/site/docs/collections.md b/site/docs/collections.md index 0016cf4c..f4c2239e 100644 --- a/site/docs/collections.md +++ b/site/docs/collections.md @@ -14,14 +14,23 @@ permalink: /docs/collections/

+
+
Collections support is unstable and may change
+

+ This is an experimental feature and that the API may likely change until the feature stabilizes. +

+
+ Put some things in a folder and add the folder to your config. It's simple... -Why did we write this feature? What is it useful for? +Not everything is a post or a page. Maybe you want to document the various methods in your open source project, members of a team, or talks at a conference. Collections allow you to define a new type of document that behave like Pages or Posts do normally, but also have their own unique properties and namespace. ## Using Collections ### Step 1: Tell Jekyll to read in your collection +Add the following to your site's `_config.yml` file, replacing `my_collection` with the name of your collection: + {% highlight yaml %} collections: - my_collection @@ -30,12 +39,13 @@ collections: ### Step 2: Add your content Create a corresponding folder (e.g. `/_my_collection`) and add documents. -YAML front-matter is read in as data if it exists, if not, then everything is just -stuck in the Document's `content` attribute. +YAML front-matter is read in as data if it exists, if not, then everything is just stuck in the Document's `content` attribute. + +Note: the folder must be named identical to the collection you defined in you config.yml file, with the addition of the preceding `_` character. ### Step 3: Optionally render your collection's documents into independent files -If you'd like your files rendered, add it to your config: +If you'd like Jekyll to create a public-facing, rendered version of each document in your collection, add your collection name to the `render` config key in your `_config.yml`: {% highlight yaml %} render: From d84cde1f7a277288afed026581cb10b04378d1cd Mon Sep 17 00:00:00 2001 From: Parker Moore Date: Thu, 3 Apr 2014 14:06:00 -0400 Subject: [PATCH 14/30] Add docs for how the collections stuff is exposed via Liquid. --- site/docs/collections.md | 101 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 101 insertions(+) diff --git a/site/docs/collections.md b/site/docs/collections.md index f4c2239e..3bf9d989 100644 --- a/site/docs/collections.md +++ b/site/docs/collections.md @@ -56,3 +56,104 @@ This will produce a file for each document in the collection. For example, if you have `_my_collection/some_subdir/some_doc.md`, it will be rendered using Liquid and the Markdown converter of your choice and written out to `/my_collection/some_subdir/some_doc.html`. + +## Liquid Attributes + +### Collections + +Each collection is part of the `site.collections` array in Liquid. Each collection has the following attributes: + +
+ + + + + + + + + + + + + + + + + +
VariableDescription
+

label

+
+

+ The name of the collection. +

+
+

docs

+
+

+ An array of Documents contained in this collection. +

+
+
+ +### Documents + +In addition to any YAML front-matter provided in the document's corresponding file, each document has the following attributes: + +
+ + + + + + + + + + + + + + + + + + + + + + + + + +
VariableDescription
+

content

+
+

+ The content of the document. If no YAML front-matter is provided, + this is the entirety of the file contents. If YAML front-matter + is used, then this is all the contents of the file after the terminating + `---` of the front-matter. +

+
+

path

+
+

+ The full path to the document's source file. +

+
+

relative_path

+
+

+ The path to the document's source file relative to the site source. +

+
+

url

+
+

+ The URL of the rendered collection. The file is only written to the + destination when the name of the collection to which it belongs is + included in the render key in the site's configuration file. +

+
+
From be769dcf00874cb03da668a937a8967e1a49379d Mon Sep 17 00:00:00 2001 From: Parker Moore Date: Thu, 3 Apr 2014 14:13:35 -0400 Subject: [PATCH 15/30] SANITIZE THE collection name plz. --- lib/jekyll/collection.rb | 6 +++++- test/test_collections.rb | 40 ++++++++++++++++++++++++++-------------- 2 files changed, 31 insertions(+), 15 deletions(-) diff --git a/lib/jekyll/collection.rb b/lib/jekyll/collection.rb index e7878962..b74af552 100644 --- a/lib/jekyll/collection.rb +++ b/lib/jekyll/collection.rb @@ -4,7 +4,7 @@ module Jekyll def initialize(site, label) @site = site - @label = label + @label = sanitize_label(label) end def docs @@ -38,6 +38,10 @@ module Jekyll "#" end + def sanitize_label(label) + label.gsub(/[^a-z0-9_\-]/i, '') + end + def to_liquid { "label" => label, diff --git a/test/test_collections.rb b/test/test_collections.rb index 06120790..d550536a 100644 --- a/test/test_collections.rb +++ b/test/test_collections.rb @@ -2,12 +2,28 @@ require 'helper' class TestCollections < Test::Unit::TestCase - context "with no collections specified" do - setup do - @site = Site.new(Jekyll.configuration({ + def fixture_site(overrides = {}) + Jekyll::Site.new(Jekyll.configuration( + overrides.merge({ "source" => source_dir, "destination" => dest_dir - })) + }) + )) + end + + context "a simple collection" do + setup do + @collection = Jekyll::Collection.new(fixture_site, "../../etc/password") + end + + should "sanitize the label name" do + assert_equal @collection.label, "etcpassword" + end + end + + context "with no collections specified" do + setup do + @site = fixture_site @site.process end @@ -18,11 +34,9 @@ class TestCollections < Test::Unit::TestCase context "with a collection" do setup do - @site = Site.new(Jekyll.configuration({ - "collections" => ["methods"], - "source" => source_dir, - "destination" => dest_dir - })) + @site = fixture_site({ + "collections" => ["methods"] + }) @site.process end @@ -49,12 +63,10 @@ class TestCollections < Test::Unit::TestCase context "in safe mode" do setup do - @site = Site.new(Jekyll.configuration({ + @site = fixture_site({ "collections" => ["methods"], - "safe" => true, - "source" => source_dir, - "destination" => dest_dir - })) + "safe" => true + }) @site.process @collection = @site.collections["methods"] end From 45120ad3eb2517991c911e45cbf06bdd8482a086 Mon Sep 17 00:00:00 2001 From: Parker Moore Date: Fri, 4 Apr 2014 13:13:38 -0400 Subject: [PATCH 16/30] Moar tests for collections. --- test/test_collections.rb | 32 +++++++++++++++++++++++++++++++- 1 file changed, 31 insertions(+), 1 deletion(-) diff --git a/test/test_collections.rb b/test/test_collections.rb index d550536a..82915d19 100644 --- a/test/test_collections.rb +++ b/test/test_collections.rb @@ -11,7 +11,7 @@ class TestCollections < Test::Unit::TestCase )) end - context "a simple collection" do + context "an evil collection" do setup do @collection = Jekyll::Collection.new(fixture_site, "../../etc/password") end @@ -19,6 +19,36 @@ class TestCollections < Test::Unit::TestCase should "sanitize the label name" do assert_equal @collection.label, "etcpassword" end + + should "have a sanitized relative path name" do + assert_equal @collection.relative_directory, "_etcpassword" + end + + should "have a sanitized full path" do + assert_equal @collection.directory, source_dir("_etcpassword") + end + end + + context "a simple collection" do + setup do + @collection = Jekyll::Collection.new(fixture_site, "methods") + end + + should "sanitize the label name" do + assert_equal @collection.label, "methods" + end + + should "contain no docs when initialized" do + assert_empty @collection.docs + end + + should "know its relative directory" do + assert_equal @collection.relative_directory, "_methods" + end + + should "know the full path to itself on the filesystem" do + assert_equal @collection.directory, source_dir("_methods") + end end context "with no collections specified" do From 5ae1c34857c06999279bb0d110d33144abbabab9 Mon Sep 17 00:00:00 2001 From: Parker Moore Date: Fri, 4 Apr 2014 13:13:44 -0400 Subject: [PATCH 17/30] Add comments for Collection --- lib/jekyll/collection.rb | 43 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/lib/jekyll/collection.rb b/lib/jekyll/collection.rb index b74af552..1aa563e7 100644 --- a/lib/jekyll/collection.rb +++ b/lib/jekyll/collection.rb @@ -2,15 +2,28 @@ module Jekyll class Collection attr_reader :site, :label + # Create a new Collection. + # + # site - the site to which this collection belongs. + # label - the name of the collection + # + # Returns nothing. def initialize(site, label) @site = site @label = sanitize_label(label) end + # Fetch the Documents in this collection. + # Defaults to an empty array if no documents have been read in. + # + # Returns an array of Jekyll::Document objects. def docs @docs ||= [] end + # Read the allowed documents into the collection's array of docs. + # + # Returns the sorted array of docs. def read Dir.glob(File.join(directory, "**", "*.*")).each do |file_path| if allowed_document?(file_path) @@ -22,26 +35,56 @@ module Jekyll docs.sort! end + # The directory for this Collection, relative to the site source. + # + # Returns a String containing the directory name where the collection + # is stored on the filesystem. def relative_directory "_#{label}" end + # The full path to the directory containing the + # + # Returns a String containing th directory name where the collection + # is stored on the filesystem. def directory Jekyll.sanitized_path(site.source, relative_directory) end + # Determine whether the document at a given path is an allowed document. + # + # path - the path to the document within this collection + # + # Returns false if the site is in safe mode and the document is a symlink, + # true otherwise. def allowed_document?(path) !(site.safe && File.symlink?(path)) end + # An inspect string. + # + # Returns the inspecr string def inspect "#" end + # Produce a sanitized label name + # Label names may not contain anything but alphanumeric characters, + # underscores, and hyphens. + # + # label - the possibly-unsafe label + # + # Returns a sanitized version of the label. def sanitize_label(label) label.gsub(/[^a-z0-9_\-]/i, '') end + # Produce a representation of this Collection for use in Liquid. + # Exposes two attributes: + # - label + # - docs + # + # Returns a representation of this collection for use in Liquid. def to_liquid { "label" => label, From a307aff858642d46100af5c97ff26ba400477f8b Mon Sep 17 00:00:00 2001 From: Parker Moore Date: Fri, 4 Apr 2014 13:22:22 -0400 Subject: [PATCH 18/30] Do not render any asset files with Liquid. --- lib/jekyll/document.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/jekyll/document.rb b/lib/jekyll/document.rb index a82408ce..69da984a 100644 --- a/lib/jekyll/document.rb +++ b/lib/jekyll/document.rb @@ -45,12 +45,12 @@ module Jekyll %w[.yaml .yml].include?(extname) end - def sass_file? - %w[.sass .scss].include?(extname) + def asset_file? + %w[.sass .scss .coffee].include?(extname) end def render_with_liquid? - !(sass_file? || yaml_file?) + !(asset_file? || yaml_file?) end def url_template From 00ca09a2ea9ce38bb68d9e896dd53b29964149f1 Mon Sep 17 00:00:00 2001 From: Parker Moore Date: Fri, 4 Apr 2014 14:38:19 -0400 Subject: [PATCH 19/30] Add comments for Document --- lib/jekyll/document.rb | 65 +++++++++++++++++++++++++++++++++++++++++- 1 file changed, 64 insertions(+), 1 deletion(-) diff --git a/lib/jekyll/document.rb b/lib/jekyll/document.rb index 69da984a..97a2360a 100644 --- a/lib/jekyll/document.rb +++ b/lib/jekyll/document.rb @@ -25,38 +25,80 @@ module Jekyll @data ||= Hash.new end + # The path to the document, relative to the site source. + # + # Returns a String path which represents the relative path + # from the site source to this document def relative_path Pathname.new(path).relative_path_from(Pathname.new(site.source)).to_s end + # The base filename of the document. + # + # suffix - (optional) the suffix to be removed from the end of the filename + # + # Returns the base filename of the document. def basename(suffix = "") File.basename(path, suffix) end + # The extension name of the document. + # + # Returns the extension name of the document. def extname File.extname(path) end + # Produces a "cleaned" relative path. + # The "cleaned" relative path is the relative path without the extname + # and with the collection's directory removed as well. + # This method is useful when building the URL of the document. + # + # Examples: + # When relative_path is "_methods/site/generate.md": + # cleaned_relative_path + # # => "/site/generate" + # + # Returns the cleaned relative path of the document. def cleaned_relative_path relative_path[0 .. -extname.length - 1].sub(collection.relative_directory, "") end + # Determine whether the document is a YAML file. + # + # Returns true if the extname is either .yml or .yaml, false otherwise. def yaml_file? %w[.yaml .yml].include?(extname) end + # Determine whether the document is an asset file. + # Asset files include CoffeeScript files and Sass/SCSS files. + # + # Returns true if the extname belongs to the set of extensions + # that asset files use. def asset_file? %w[.sass .scss .coffee].include?(extname) end + # Determine whether the file should be rendered with Liquid. + # + # Returns false if the document is either an asset file or a yaml file, + # true otherwise. def render_with_liquid? !(asset_file? || yaml_file?) end + # The URL template where the document would be accessible. + # + # Returns the URL template for the document. def url_template "/:collection/:path:output_ext" end + # Construct a Hash of key-value pairs which contain a mapping between + # a key in the URL template and the corresponding value for this document. + # + # Returns the Hash of key-value pairs for replacement in the URL. def url_placeholders { collection: collection.label, @@ -65,11 +107,18 @@ module Jekyll } end + # The permalink for this Document. + # Permalink is set via the data Hash. + # + # Returns the permalink or nil if no permalink was set in the data. def permalink return nil if data.nil? || data['permalink'].nil? data['permalink'] end + # The computed URL for the document. See `Jekyll::URL#to_s` for more details. + # + # Returns the computed URL for the document. def url @url ||= URL.new({ :template => url_template, @@ -78,6 +127,11 @@ module Jekyll }).to_s end + # The full path to the output file. + # + # base_directory - the base path of the output directory + # + # Returns the full path to the output file of this document. def destination(base_directory) path = Jekyll.sanitized_path(base_directory, url) path = File.join(path, "index.html") if url =~ /\/$/ @@ -102,7 +156,7 @@ module Jekyll # # opts - override options # - # Return + # Return the file read options hash. def merged_file_read_opts(opts) (site ? site.file_read_opts : {}).merge(opts) end @@ -147,10 +201,19 @@ module Jekyll }) end + # The inspect string for this document. + # Includes the relative path and the collection label. + # + # Returns the inspect string for this document. def inspect "#" end + # Compare this document against another document. + # Comparison is a comparison between the 2 paths of the documents. + # + # Returns -1, 0, +1 or nil depending on whether this doc's path is less than, + # equal or greater than the other doc's path. See String#<=> for more details. def <=>(anotherDocument) path <=> anotherDocument.path end From f0e68d7d86ee7d3336bfb35cf995e938a2c848b1 Mon Sep 17 00:00:00 2001 From: Parker Moore Date: Sat, 5 Apr 2014 17:28:04 -0400 Subject: [PATCH 20/30] Expose collections as site. in Liquid as array of docs. --- features/collections.feature | 8 ++++---- lib/jekyll/collection.rb | 5 +---- lib/jekyll/document.rb | 7 +++++++ lib/jekyll/site.rb | 12 +++++++----- 4 files changed, 19 insertions(+), 13 deletions(-) diff --git a/features/collections.feature b/features/collections.feature index 4eb526d3..451c84d8 100644 --- a/features/collections.feature +++ b/features/collections.feature @@ -4,15 +4,15 @@ Feature: Collections And render them Scenario: Unrendered collection - Given I have an "index.html" page that contains "Collections: {{ site.collections }}" + Given I have an "index.html" page that contains "Collections: {{ site.methods }}" And I have fixture collections And I have a configuration file with "collections" set to "['methods']" When I run jekyll Then the _site directory should exist - And I should see "Collections: {\"methods\"=>#, #, #, #, #\]>}" in "_site/index.html" + And I should see "Collections: Use `{{ page.title }}` to build a full configuration for use w/Jekyll.\n\nWhatever: {{ page.whatever }}\n`{{ page.title }}` is used to make sure your path is in your source.\nRun your generators! {{ page.layout }}\nCreate dat site.\nRun your generators! {{ page.layout }}" in "_site/index.html" Scenario: Rendered collection - Given I have an "index.html" page that contains "Collections: {{ site.collections.methods.label }}" + Given I have an "index.html" page that contains "Collections: {{ site.collections }}" And I have fixture collections And I have a configuration file with: | key | value | @@ -24,7 +24,7 @@ Feature: Collections And I should see "

Whatever: foo.bar

" in "_site/methods/configuration.html" Scenario: Rendered document in a layout - Given I have an "index.html" page that contains "Collections: {{ site.collections.methods.label }}" + Given I have an "index.html" page that contains "Collections: {{ site.collections }}" And I have a default layout that contains "
Tom Preston-Werner
{{content}}" And I have fixture collections And I have a configuration file with: diff --git a/lib/jekyll/collection.rb b/lib/jekyll/collection.rb index 1aa563e7..6f9be70e 100644 --- a/lib/jekyll/collection.rb +++ b/lib/jekyll/collection.rb @@ -86,10 +86,7 @@ module Jekyll # # Returns a representation of this collection for use in Liquid. def to_liquid - { - "label" => label, - "docs" => docs - } + docs end end diff --git a/lib/jekyll/document.rb b/lib/jekyll/document.rb index 97a2360a..c35b9101 100644 --- a/lib/jekyll/document.rb +++ b/lib/jekyll/document.rb @@ -209,6 +209,13 @@ module Jekyll "#" end + # The string representation for this document. + # + # Returns the content of the document + def to_s + output || content + end + # Compare this document against another document. # Comparison is a comparison between the 2 paths of the documents. # diff --git a/lib/jekyll/site.rb b/lib/jekyll/site.rb index 2bdf1898..7b84b8b9 100644 --- a/lib/jekyll/site.rb +++ b/lib/jekyll/site.rb @@ -215,8 +215,8 @@ module Jekyll def render relative_permalinks_deprecation_method - collections.each do |label, collection| - collection.docs.each do |document| + to_render.each do |label| + collections[label].docs.each do |document| document.output = Jekyll::Renderer.new(self, document).run end end @@ -296,7 +296,8 @@ module Jekyll # See Site#post_attr_hash for type info. def site_payload {"jekyll" => { "version" => Jekyll::VERSION }, - "site" => config.merge({ + "site" => Utils.deep_merge_hashes(config, + Utils.deep_merge_hashes(collections, { "time" => time, "posts" => posts.sort { |a, b| b <=> a }, "pages" => pages, @@ -304,8 +305,9 @@ module Jekyll "html_pages" => pages.reject { |page| !page.html? }, "categories" => post_attr_hash('categories'), "tags" => post_attr_hash('tags'), - "data" => site_data, - "collections" => collections})} + "data" => site_data + })) + } end # Filter out any files/directories that are hidden or backup files (start From 323ea0ef73584eb26fd23d420d535f6e3da0917d Mon Sep 17 00:00:00 2001 From: Parker Moore Date: Sun, 6 Apr 2014 12:58:56 -0400 Subject: [PATCH 21/30] EntryFilter#special? should also check the base name of the entry --- lib/jekyll/entry_filter.rb | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/lib/jekyll/entry_filter.rb b/lib/jekyll/entry_filter.rb index 11db9e11..0b00218b 100644 --- a/lib/jekyll/entry_filter.rb +++ b/lib/jekyll/entry_filter.rb @@ -1,5 +1,7 @@ module Jekyll class EntryFilter + SPECIAL_LEADING_CHARACTERS = ['.', '_', '#'].freeze + attr_reader :site def initialize(site, base_directory = nil) @@ -35,7 +37,8 @@ module Jekyll end def special?(entry) - ['.', '_', '#'].include?(entry[0..0]) + SPECIAL_LEADING_CHARACTERS.include?(entry[0..0]) || + SPECIAL_LEADING_CHARACTERS.include?(File.basename(entry)[0..0]) end def backup?(entry) From aa502348e5271c99695833e5f141e4d9f52f4063 Mon Sep 17 00:00:00 2001 From: Parker Moore Date: Sun, 6 Apr 2014 12:59:31 -0400 Subject: [PATCH 22/30] Filter entries in the collection per EntryFilter#filter --- lib/jekyll/collection.rb | 42 +++++++++++++------ lib/jekyll/document.rb | 4 +- test/source/_methods/_do_not_read_me.md | 5 +++ .../_methods/site/_dont_include_me_either.md | 5 +++ test/test_collections.rb | 18 +++++++- 5 files changed, 57 insertions(+), 17 deletions(-) create mode 100644 test/source/_methods/_do_not_read_me.md create mode 100644 test/source/_methods/site/_dont_include_me_either.md diff --git a/lib/jekyll/collection.rb b/lib/jekyll/collection.rb index 6f9be70e..13455e2a 100644 --- a/lib/jekyll/collection.rb +++ b/lib/jekyll/collection.rb @@ -25,16 +25,34 @@ module Jekyll # # Returns the sorted array of docs. def read - Dir.glob(File.join(directory, "**", "*.*")).each do |file_path| - if allowed_document?(file_path) - doc = Jekyll::Document.new(file_path, { site: site, collection: self }) - doc.read - docs << doc - end + filtered_entries.each do |file_path| + doc = Jekyll::Document.new(Jekyll.sanitized_path(directory, file_path), { site: site, collection: self }) + doc.read + docs << doc end docs.sort! end + # All the entries in this collection. + # + # Returns an Array of file paths to the documents in this collection + # relative to the collection's directory + def entries + Dir.glob(File.join(directory, "**", "*.*")).map do |entry| + entry[File.join(directory, "")] = ''; entry + end + end + + # Filtered version of the entries in this collection. + # See `Jekyll::EntryFilter#filter` for more information. + # + # Returns a list of filtered entry paths. + def filtered_entries + Dir.chdir(directory) do + entry_filter.filter(entries) + end + end + # The directory for this Collection, relative to the site source. # # Returns a String containing the directory name where the collection @@ -51,14 +69,12 @@ module Jekyll Jekyll.sanitized_path(site.source, relative_directory) end - # Determine whether the document at a given path is an allowed document. + # The entry filter for this collection. + # Creates an instance of Jekyll::EntryFilter. # - # path - the path to the document within this collection - # - # Returns false if the site is in safe mode and the document is a symlink, - # true otherwise. - def allowed_document?(path) - !(site.safe && File.symlink?(path)) + # Returns the instance of Jekyll::EntryFilter for this collection. + def entry_filter + @entry_filter ||= Jekyll::EntryFilter.new(site, relative_directory) end # An inspect string. diff --git a/lib/jekyll/document.rb b/lib/jekyll/document.rb index c35b9101..ce4d6bff 100644 --- a/lib/jekyll/document.rb +++ b/lib/jekyll/document.rb @@ -193,12 +193,12 @@ module Jekyll # # Returns a Hash representing this Document's data. def to_liquid - data.merge({ + Utils.deep_merge_hashes data, { "content" => content, "path" => path, "relative_path" => relative_path, "url" => url - }) + } end # The inspect string for this document. diff --git a/test/source/_methods/_do_not_read_me.md b/test/source/_methods/_do_not_read_me.md new file mode 100644 index 00000000..1b5ad07d --- /dev/null +++ b/test/source/_methods/_do_not_read_me.md @@ -0,0 +1,5 @@ +--- +title: The unreadable wonder +--- + +Don't read me, you fool! FILTER ME diff --git a/test/source/_methods/site/_dont_include_me_either.md b/test/source/_methods/site/_dont_include_me_either.md new file mode 100644 index 00000000..66079613 --- /dev/null +++ b/test/source/_methods/site/_dont_include_me_either.md @@ -0,0 +1,5 @@ +--- +title: Don't Include Me Either +--- + +Don't include me either. FILTER ME PLZ diff --git a/test/test_collections.rb b/test/test_collections.rb index 82915d19..18ab22d6 100644 --- a/test/test_collections.rb +++ b/test/test_collections.rb @@ -68,6 +68,7 @@ class TestCollections < Test::Unit::TestCase "collections" => ["methods"] }) @site.process + @collection = @site.collections["methods"] end should "create a Hash on Site with the label mapped to the instance of the Collection" do @@ -89,6 +90,19 @@ class TestCollections < Test::Unit::TestCase ], doc.relative_path end end + + should "not include files which start with an underscore in the base collection directory" do + assert_not_include @collection.filtered_entries, "_do_not_read_me.md" + end + + should "not include files which start with an underscore in a subdirectory" do + assert_not_include @collection.filtered_entries, "site/_dont_include_me_either.md" + end + + should "not include the underscored files in the list of docs" do + assert_not_include @collection.docs.map(&:relative_path), "_methods/_do_not_read_me.md" + assert_not_include @collection.docs.map(&:relative_path), "_methods/site/_dont_include_me_either.md" + end end context "in safe mode" do @@ -102,11 +116,11 @@ class TestCollections < Test::Unit::TestCase end should "not allow symlinks" do - assert !@collection.allowed_document?(File.join(@collection.directory, "um_hi.md")) + assert_not_include @collection.filtered_entries, "um_hi.md" end should "not include the symlinked file in the list of docs" do - assert_not_include %w[_methods/um_hi.md], @collection.docs.map(&:relative_path) + assert_not_include @collection.docs.map(&:relative_path), "_methods/um_hi.md" end end From 62551b5ff962690dde8587c5b919a94e541f098c Mon Sep 17 00:00:00 2001 From: Parker Moore Date: Sun, 6 Apr 2014 13:25:05 -0400 Subject: [PATCH 23/30] Include data in the array of collections --- lib/jekyll/site.rb | 25 +++++++++++++++++-------- 1 file changed, 17 insertions(+), 8 deletions(-) diff --git a/lib/jekyll/site.rb b/lib/jekyll/site.rb index 7b84b8b9..a2cf2b72 100644 --- a/lib/jekyll/site.rb +++ b/lib/jekyll/site.rb @@ -84,6 +84,11 @@ module Jekyll end end + # The list of collections and their corresponding Jekyll::Collection instances. + # If config['collections'] is set, a new instance is created for each item in the collection. + # If config['collections'] is not set, a new hash is returned. + # + # Returns a Hash containing collection name-to-instance pairs. def collections @collections ||= if config['collections'] Hash[config['collections'].map { |coll| [coll, Jekyll::Collection.new(self, coll)] } ] @@ -92,8 +97,11 @@ module Jekyll end end + # The list of collections to render. + # + # The array of collection labels to render. def to_render - config['render'] || Array.new + @to_render ||= (config['render'] || Array.new) end # Read Site data from disk and load it into internal data structures. @@ -180,14 +188,15 @@ module Jekyll # # Returns nothing def read_data(dir) - base = File.join(source, dir) - return unless File.directory?(base) && (!safe || !File.symlink?(base)) + unless dir.eql?("_data") + Jekyll.logger.warn "Error:", "Data source directories other than '_data' have been removed.\n" + + "Please move your YAML files to `_data` and remove the `data_source` key from your `_config.yml`." + end - entries = Dir.chdir(base) { Dir['*.{yaml,yml}'] } - entries.delete_if { |e| File.directory?(File.join(base, e)) } - data_collection = Jekyll::Collection.new(self, "data") - data_collection.read - data_collection.docs.each do |doc| + collections['data'] = Jekyll::Collection.new(self, "data") + collections['data'].read + + collections['data'].docs.each do |doc| key = sanitize_filename(doc.basename(".*")) self.data[key] = doc.data end From 2f99e1d5c10bfdbf3129e67f49256596b727b441 Mon Sep 17 00:00:00 2001 From: Parker Moore Date: Sun, 6 Apr 2014 13:33:59 -0400 Subject: [PATCH 24/30] Fix test for non-collectionized sites still containing data coll --- test/test_collections.rb | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/test/test_collections.rb b/test/test_collections.rb index 18ab22d6..8b4c2d23 100644 --- a/test/test_collections.rb +++ b/test/test_collections.rb @@ -57,8 +57,10 @@ class TestCollections < Test::Unit::TestCase @site.process end - should "not contain any collections" do - assert_equal @site.collections, Hash.new + should "not contain any collections other than the default ones" do + collections = @site.collections.dup + assert collections.delete("data").is_a?(Jekyll::Collection) + assert_equal Hash.new, collections end end From af61451f877daaa3cb073367094d7b05dc6ca78e Mon Sep 17 00:00:00 2001 From: Parker Moore Date: Sun, 6 Apr 2014 13:34:41 -0400 Subject: [PATCH 25/30] Use #error instead of #warn when telling the user not to use a custom data source --- lib/jekyll/site.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/jekyll/site.rb b/lib/jekyll/site.rb index a2cf2b72..1f127304 100644 --- a/lib/jekyll/site.rb +++ b/lib/jekyll/site.rb @@ -188,8 +188,8 @@ module Jekyll # # Returns nothing def read_data(dir) - unless dir.eql?("_data") - Jekyll.logger.warn "Error:", "Data source directories other than '_data' have been removed.\n" + + unless dir.to_s.eql?("_data") + Jekyll.logger.error "Error:", "Data source directories other than '_data' have been removed.\n" + "Please move your YAML files to `_data` and remove the `data_source` key from your `_config.yml`." end From ad7efb23e6a00f3a353d8db3b1e4ad56524a54aa Mon Sep 17 00:00:00 2001 From: Parker Moore Date: Sun, 6 Apr 2014 13:43:05 -0400 Subject: [PATCH 26/30] Code/docs cleanup, props @baweaver --- lib/jekyll/collection.rb | 2 +- lib/jekyll/document.rb | 13 ++++++------- lib/jekyll/renderer.rb | 2 +- 3 files changed, 8 insertions(+), 9 deletions(-) diff --git a/lib/jekyll/collection.rb b/lib/jekyll/collection.rb index 13455e2a..4bee732a 100644 --- a/lib/jekyll/collection.rb +++ b/lib/jekyll/collection.rb @@ -79,7 +79,7 @@ module Jekyll # An inspect string. # - # Returns the inspecr string + # Returns the inspect string def inspect "#" end diff --git a/lib/jekyll/document.rb b/lib/jekyll/document.rb index ce4d6bff..ed200511 100644 --- a/lib/jekyll/document.rb +++ b/lib/jekyll/document.rb @@ -7,7 +7,7 @@ module Jekyll # Create a new Document. # - # shit - the Jekyll::Site instance to which this Document belongs + # site - the Jekyll::Site instance to which this Document belongs # path - the path to the file # # Returns nothing. @@ -112,8 +112,7 @@ module Jekyll # # Returns the permalink or nil if no permalink was set in the data. def permalink - return nil if data.nil? || data['permalink'].nil? - data['permalink'] + data && data['permalink'] end # The computed URL for the document. See `Jekyll::URL#to_s` for more details. @@ -121,9 +120,9 @@ module Jekyll # Returns the computed URL for the document. def url @url ||= URL.new({ - :template => url_template, - :placeholders => url_placeholders, - :permalink => permalink + template: url_template, + placeholders: url_placeholders, + permalink: permalink }).to_s end @@ -158,7 +157,7 @@ module Jekyll # # Return the file read options hash. def merged_file_read_opts(opts) - (site ? site.file_read_opts : {}).merge(opts) + site ? site.file_read_opts.merge(opts) : opts end # Whether the file is published or not, as indicated in YAML front-matter diff --git a/lib/jekyll/renderer.rb b/lib/jekyll/renderer.rb index 0fb7b7f1..f9df731e 100644 --- a/lib/jekyll/renderer.rb +++ b/lib/jekyll/renderer.rb @@ -32,7 +32,7 @@ module Jekyll "page" => document.to_liquid }, site.site_payload) - info = { + info = { filters: [Jekyll::Filters], registers: { :site => site, :page => payload['page'] } } From a1af95c34e0e62ffb07bba1e35ce3d56be357c70 Mon Sep 17 00:00:00 2001 From: Parker Moore Date: Mon, 14 Apr 2014 17:14:12 -0400 Subject: [PATCH 27/30] Clean up some code per @baweaver's suggestions. --- lib/jekyll/renderer.rb | 6 ++---- lib/jekyll/site.rb | 8 ++++---- 2 files changed, 6 insertions(+), 8 deletions(-) diff --git a/lib/jekyll/renderer.rb b/lib/jekyll/renderer.rb index f9df731e..e3d233df 100644 --- a/lib/jekyll/renderer.rb +++ b/lib/jekyll/renderer.rb @@ -60,16 +60,14 @@ module Jekyll # # Returns the converted content. def convert(content) - output = content.dup - converters.each do |converter| + converters.reduce(content) do |output, converter| begin - output = converter.convert(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 diff --git a/lib/jekyll/site.rb b/lib/jekyll/site.rb index 1f127304..92e5e359 100644 --- a/lib/jekyll/site.rb +++ b/lib/jekyll/site.rb @@ -395,13 +395,13 @@ module Jekyll end def documents - docs = Set.new - collections.each do |label, coll| + collections.reduce(Set.new) do |docs, (label, coll)| if to_render.include?(label) - docs = docs.merge(coll.docs) + docs.merge(coll.docs) + else + docs end end - docs end def each_site_file From ee29bf39393990a1ffd8ec06d1b16b8587a0ce30 Mon Sep 17 00:00:00 2001 From: Parker Moore Date: Mon, 14 Apr 2014 17:18:59 -0400 Subject: [PATCH 28/30] Update docs around collections. --- site/docs/collections.md | 35 +---------------------------------- 1 file changed, 1 insertion(+), 34 deletions(-) diff --git a/site/docs/collections.md b/site/docs/collections.md index 3bf9d989..23ac190a 100644 --- a/site/docs/collections.md +++ b/site/docs/collections.md @@ -61,40 +61,7 @@ choice and written out to `/my_collection/some_subdir/some_doc.html`. ### Collections -Each collection is part of the `site.collections` array in Liquid. Each collection has the following attributes: - -
- - - - - - - - - - - - - - - - - -
VariableDescription
-

label

-
-

- The name of the collection. -

-
-

docs

-
-

- An array of Documents contained in this collection. -

-
-
+Each collection is accessible via the `site` Liquid variable. For example, if you want to access the `albums` collection found in `_albums`, you'd use `site.albums`. Each collection is itself an array of documents (e.g. `site.albums` is an array of documents, much like `site.pages` and `site.posts`). See below for how to access attributes of those documents. ### Documents From 696aea211ac344c81382258c7dedcc4a5b15014b Mon Sep 17 00:00:00 2001 From: Parker Moore Date: Mon, 14 Apr 2014 22:56:23 -0400 Subject: [PATCH 29/30] Don't gather any entries if the collection directory doesn't exist --- lib/jekyll/collection.rb | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/lib/jekyll/collection.rb b/lib/jekyll/collection.rb index 4bee732a..d2d1ae93 100644 --- a/lib/jekyll/collection.rb +++ b/lib/jekyll/collection.rb @@ -38,6 +38,7 @@ module Jekyll # Returns an Array of file paths to the documents in this collection # relative to the collection's directory def entries + return Array.new unless exists? Dir.glob(File.join(directory, "**", "*.*")).map do |entry| entry[File.join(directory, "")] = ''; entry end @@ -69,6 +70,16 @@ module Jekyll Jekyll.sanitized_path(site.source, relative_directory) end + # Checks whether the directory "exists" for this collection. + # The directory must exist on the filesystem and must not be a symlink + # if in safe mode. + # + # Returns false if the directory doesn't exist or if it's a symlink + # and we're in safe mode. + def exists? + File.directory?(directory) && !(File.symlink?(directory) && site.safe) + end + # The entry filter for this collection. # Creates an instance of Jekyll::EntryFilter. # From 5a6f1d42a966dce1cb428c7619148264125874d8 Mon Sep 17 00:00:00 2001 From: Parker Moore Date: Mon, 14 Apr 2014 23:03:19 -0400 Subject: [PATCH 30/30] Fix #filtered_entries so it returns a new Array if the directory doesn't exist --- features/support/env.rb | 2 +- lib/jekyll/collection.rb | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/features/support/env.rb b/features/support/env.rb index 593e1304..5f6752aa 100644 --- a/features/support/env.rb +++ b/features/support/env.rb @@ -24,7 +24,7 @@ def jekyll_run_output end def run_jekyll(args, output_file) - command = "#{JEKYLL_PATH} #{args} > #{jekyll_output_file} 2>&1" + command = "#{JEKYLL_PATH} #{args} --trace > #{jekyll_output_file} 2>&1" system command end diff --git a/lib/jekyll/collection.rb b/lib/jekyll/collection.rb index d2d1ae93..ad0e5fa5 100644 --- a/lib/jekyll/collection.rb +++ b/lib/jekyll/collection.rb @@ -49,6 +49,7 @@ module Jekyll # # Returns a list of filtered entry paths. def filtered_entries + return Array.new unless exists? Dir.chdir(directory) do entry_filter.filter(entries) end