diff --git a/History.txt b/History.txt
index a0326f16d..bb0887b49 100644
--- a/History.txt
+++ b/History.txt
@@ -1,3 +1,30 @@
+==
+ * Minor Enhancements
+ * Type importer [github.com/codeslinger]
+ * site.topics accessor [github.com/baz]
+ * Add array_to_sentence_string filter [github.com/mchung]
+
+== 0.3.0 / 2008-12-24
+ * Major Enhancements
+ * Added --server option to start a simple WEBrick server on destination directory [github.com/johnreilly and github.com/mchung]
+ * Minor Enhancements
+ * Added post categories based on directories containing _posts [github.com/mreid]
+ * Added post topics based on directories underneath _posts
+ * Added new date filter that shows the full month name [github.com/mreid]
+ * Merge Post's YAML front matter into its to_liquid payload [github.com/remi]
+ * Restrict includes to regular files underneath _includes
+ * Bug Fixes
+ * Change YAML delimiter matcher so as to not chew up 2nd level markdown headers [github.com/mreid]
+ * Fix bug that meant page data (such as the date) was not available in templates [github.com/mreid]
+ * Properly reject directories in _layouts
+
+== 0.2.1 / 2008-12-15
+ * Major Changes
+ * Use Maruku (pure Ruby) for Markdown by default [github.com/mreid]
+ * Allow use of RDiscount with --rdiscount flag
+ * Minor Enhancements
+ * Don't load directory_watcher unless it's needed [github.com/pjhyett]
+
== 0.2.0 / 2008-12-14
* Major Changes
* related_posts is now found in site.related_posts
diff --git a/Manifest.txt b/Manifest.txt
index f30590130..9ed6da420 100644
--- a/Manifest.txt
+++ b/Manifest.txt
@@ -8,7 +8,11 @@ lib/jekyll.rb
lib/jekyll/albino.rb
lib/jekyll/converters/csv.rb
lib/jekyll/converters/mephisto.rb
+lib/jekyll/converters/mt.rb
+lib/jekyll/converters/typo.rb
+lib/jekyll/converters/wordpress.rb
lib/jekyll/convertible.rb
+lib/jekyll/core_ext.rb
lib/jekyll/filters.rb
lib/jekyll/layout.rb
lib/jekyll/page.rb
@@ -17,16 +21,18 @@ lib/jekyll/site.rb
lib/jekyll/tags/highlight.rb
lib/jekyll/tags/include.rb
test/helper.rb
-test/source/_includes/sig.textile
+test/source/_includes/sig.markdown
test/source/_layouts/default.html
test/source/_layouts/simple.html
test/source/_posts/2008-10-18-foo-bar.textile
test/source/_posts/2008-11-21-complex.textile
-test/source/_posts/2008-12-13-include.textile
+test/source/_posts/2008-12-03-permalinked-post.textile
+test/source/_posts/2008-12-13-include.markdown
test/source/css/screen.css
test/source/index.html
-test/source/posts/2008-12-03-permalinked-post.textile
test/suite.rb
+test/test_filters.rb
+test/test_generated_site.rb
test/test_jekyll.rb
test/test_post.rb
test/test_site.rb
diff --git a/README.textile b/README.textile
index 5bfdc4dfb..4fa4c4061 100644
--- a/README.textile
+++ b/README.textile
@@ -53,12 +53,11 @@ filename is used to construct the URL in the generated site. The example post,
for instance, ends up at
http://tom.preston-werner.com/2008/11/17/blogging-like-a-hacker.html.
-Categories for posts are derived from the directory structure the posts were
-found within.
-A post that appears in the directory foo/bar/_posts is placed in the categories
-'foo' and 'bar'.
-By selecting posts from particular categories in your Liquid templates, you will
-be able to host multiple blogs within a site.
+Categories for posts are derived from the directory structure the posts were
+found within. A post that appears in the directory foo/bar/_posts is placed in
+the categories 'foo' and 'bar'. By selecting posts from particular categories
+in your Liquid templates, you will be able to host multiple blogs within a
+site.
Files that do not reside in directories prefixed with an underscore are
mirrored into a corresponding directory structure in the generated site. If a
@@ -88,7 +87,7 @@ The best way to install Jekyll is via RubyGems:
$ sudo gem install mojombo-jekyll -s http://gems.github.com/
Jekyll requires the gems `directory_watcher`, `liquid`, `open4`,
-and `maruku` for markdown support. These are automatically
+and `maruku` (for markdown support). These are automatically
installed by the gem install command.
Maruku comes with optional support for LaTeX to PNG rendering via
@@ -131,6 +130,21 @@ during the conversion:
$ jekyll --pygments
+By default, Jekyll uses "Maruku":http://maruku.rubyforge.org (pure Ruby) for
+Markdown support. If you'd like to use RDiscount (faster, but requires
+compilation), you must install it (gem install rdiscount) and then you can
+have it used instead:
+
+ $ jekyll --rdiscount
+
+When previewing complex sites locally, simply opening the site in a web
+browser (using file://) can cause problems with links that are relative to
+the site root (e.g., "/stylesheets/style.css"). To get around this, Jekyll
+can launch a simple WEBrick server (works well in conjunction with --auto).
+Default port is 4000:
+
+ $ jekyll --server [PORT]
+
h2. Data
Jekyll traverses your site looking for files to process. Any files with YAML
@@ -150,7 +164,7 @@ h3. Global
content
In layout files, this contains the content of the subview(s). In Posts or
- pages, this is undefined.
+ Pages, this is undefined.
h3. Site
@@ -167,7 +181,7 @@ h3. Site
--lsi (latent semantic indexing) option.
site.categories.CATEGORY
- The list of all posts in category CATEGORY.
+ The list of all Posts in category CATEGORY.
h3. Post
@@ -185,12 +199,21 @@ h3. Post
An identifier unique to the Post (useful in RSS feeds).
e.g. /2008/12/14/my-post
+ post.categories
+ The list of categories to which this post belongs. Categories are
+ derived from the directory structure above the _posts directory. For
+ example, a post at /work/code/_posts/2008-12-24-closures.textile
+ would have this field set to ['work', 'code'].
+
+ post.topics
+ The list of topics for this Post. Topics are derived from the directory
+ structure beneath the _posts directory. For example, a post at
+ /_posts/music/metal/2008-12-24-metalocalypse.textile would have this field
+ set to ['music', 'metal'].
+
post.content
The content of the Post.
- post.categories
- The list of categories to which this post belongs.
-
h2. YAML Front Matter
Any files that contain a YAML front matter block will be processed by Jekyll
@@ -263,9 +286,20 @@ becomes
1337
+h3. Array to Sentence String
+
+Convert an array into a sentence.
+
+ {{ page.tags | array_to_sentence_string }}
+
+becomes
+
+ foo, bar, and baz
+
h3. Include (Tag)
-If you have small page fragments that you wish to include in multiple places on your site, you can use the include tag.
+If you have small page fragments that you wish to include in multiple places
+on your site, you can use the include tag.
{% include sig.textile %}
@@ -332,11 +366,49 @@ The best way to get your changes merged back into core is as follows:
# Clone down your fork
# Create a topic branch to contain your change
# Hack away
+# If you are adding new functionality, document it in README.textile
# Do not change the version number, I will do that on my end
# If necessary, rebase your commits into logical chunks, without errors
# Push the branch up to GitHub
# Send me (mojombo) a pull request for your branch
+h2. Blog migrations
+
+h3. Movable Type
+
+To migrate your MT blog into Jekyll, you'll need read access to the database.
+The lib/jekyll/converters/mt.rb module provides a simple convert to create
+.markdown files in a _posts directory based on the entries contained therein.
+
+ $ export DB=my_mtdb
+ $ export USER=dbuser
+ $ export PASS=dbpass
+ $ ruby -r './lib/jekyll/converters/mt' -e 'Jekyll::MT.process( \
+ "#{ENV["DB"]}", "#{ENV["USER"]}", "#{ENV["PASS"]}")'
+
+You may need to adjust the SQL query used to retrieve MT entries. Left alone,
+it will attempt to pull all entries across all blogs regardless of status.
+Please check the results and verify the posts before publishing.
+
+h3. Typo 4+
+
+To migrate your Typo blog into Jekyll, you'll need read access to the MySQL
+database. The lib/jekyll/converters/typo.rb module provides a simple convert
+to create .html, .textile, or .markdown files in a _posts directory based on
+the entries contained therein.
+
+ $ export DB=my_typo_db
+ $ export USER=dbuser
+ $ export PASS=dbpass
+ $ ruby -r './lib/jekyll/converters/typo' -e 'Jekyll::Typo.process( \
+ "#{ENV["DB"]}", "#{ENV["USER"]}", "#{ENV["PASS"]}")'
+
+You may need to adjust the code used to filter Typo entries. Left alone,
+it will attempt to pull all entries across all blogs that were published.
+This code also has only been tested with Typo version 4+. Previous versions
+of Typo may not convert correctly. Please check the results and verify the
+posts before publishing.
+
h2. License
(The MIT License)
diff --git a/Rakefile b/Rakefile
index 3241e9935..3f301a771 100644
--- a/Rakefile
+++ b/Rakefile
@@ -18,4 +18,12 @@ namespace :convert do
task :mephisto do
sh %q(ruby -r './lib/jekyll/converters/mephisto' -e 'Jekyll::Mephisto.postgres(:database => "#{ENV["DB"]}")')
end
+ desc "Migrate from Movable Type in the current directory"
+ task :mt do
+ sh %q(ruby -r './lib/jekyll/converters/mt' -e 'Jekyll::MT.process("#{ENV["DB"]}", "#{ENV["USER"]}", "#{ENV["PASS"]}")')
+ end
+ desc "Migrate from Typo in the current directory"
+ task :typo do
+ sh %q(ruby -r './lib/jekyll/converters/typo' -e 'Jekyll::Typo.process("#{ENV["DB"]}", "#{ENV["USER"]}", "#{ENV["PASS"]}")')
+ end
end
diff --git a/bin/jekyll b/bin/jekyll
index 6a641f6b6..d746806f7 100755
--- a/bin/jekyll
+++ b/bin/jekyll
@@ -25,6 +25,11 @@ opts = OptionParser.new do |opts|
options[:auto] = true
end
+ opts.on("--server [PORT]", "Start web server (default port 4000)") do |port|
+ options[:server] = true
+ options[:server_port] = port || 4000
+ end
+
opts.on("--lsi", "Use LSI for better related posts") do
Jekyll.lsi = true
end
@@ -32,6 +37,16 @@ opts = OptionParser.new do |opts|
opts.on("--pygments", "Use pygments to highlight code") do
Jekyll.pygments = true
end
+
+ opts.on("--rdiscount", "Use rdiscount gem for Markdown") do
+ begin
+ require 'rdiscount'
+ Jekyll.markdown_proc = Proc.new { |x| RDiscount.new(x).to_html }
+ puts 'Using rdiscount for Markdown'
+ rescue LoadError
+ puts 'You must have the rdiscount gem installed first'
+ end
+ end
end
opts.parse!
@@ -69,6 +84,8 @@ case ARGV.size
end
if options[:auto]
+ require 'directory_watcher'
+
puts "Auto-regenerating enabled: #{source} -> #{destination}"
dw = DirectoryWatcher.new(source)
@@ -83,7 +100,28 @@ if options[:auto]
dw.start
- loop { sleep 1000 }
+ unless options[:server]
+ loop { sleep 1000 }
+ end
else
Jekyll.process(source, destination)
+ puts "Successfully generated site in #{destination}"
+end
+
+if options[:server]
+ require 'webrick'
+ include WEBrick
+
+ FileUtils.mkdir_p(destination)
+
+ s = HTTPServer.new(
+ :Port => options[:server_port],
+ :DocumentRoot => destination
+ )
+ t = Thread.new {
+ s.start
+ }
+
+ trap("INT") { s.shutdown }
+ t.join()
end
\ No newline at end of file
diff --git a/jekyll.gemspec b/jekyll.gemspec
index 9b75d2693..ff8002f7f 100644
--- a/jekyll.gemspec
+++ b/jekyll.gemspec
@@ -1,22 +1,22 @@
Gem::Specification.new do |s|
s.name = %q{jekyll}
- s.version = "0.2.0"
+ s.version = "0.3.0"
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
s.authors = ["Tom Preston-Werner"]
- s.date = %q{2008-12-14}
+ s.date = %q{2008-12-24}
s.default_executable = %q{jekyll}
s.email = ["tom@mojombo.com"]
s.executables = ["jekyll"]
s.extra_rdoc_files = ["History.txt", "Manifest.txt"]
- s.files = ["History.txt", "Manifest.txt", "README.textile", "Rakefile", "bin/jekyll", "jekyll.gemspec", "lib/jekyll.rb", "lib/jekyll/albino.rb", "lib/jekyll/converters/csv.rb", "lib/jekyll/converters/mephisto.rb", "lib/jekyll/convertible.rb", "lib/jekyll/filters.rb", "lib/jekyll/layout.rb", "lib/jekyll/page.rb", "lib/jekyll/post.rb", "lib/jekyll/site.rb", "lib/jekyll/tags/highlight.rb", "lib/jekyll/tags/include.rb", "test/helper.rb", "test/source/_includes/sig.textile", "test/source/_layouts/default.html", "test/source/_layouts/simple.html", "test/source/_posts/2008-10-18-foo-bar.textile", "test/source/_posts/2008-11-21-complex.textile", "test/source/_posts/2008-12-13-include.textile", "test/source/css/screen.css", "test/source/index.html", "test/source/posts/2008-12-03-permalinked-post.textile", "test/suite.rb", "test/test_jekyll.rb", "test/test_post.rb", "test/test_site.rb"]
+ s.files = ["History.txt", "Manifest.txt", "README.textile", "Rakefile", "bin/jekyll", "jekyll.gemspec", "lib/jekyll.rb", "lib/jekyll/albino.rb", "lib/jekyll/converters/csv.rb", "lib/jekyll/converters/mephisto.rb", "lib/jekyll/converters/mt.rb", "lib/jekyll/converters/wordpress.rb", "lib/jekyll/convertible.rb", "lib/jekyll/core_ext.rb", "lib/jekyll/filters.rb", "lib/jekyll/layout.rb", "lib/jekyll/page.rb", "lib/jekyll/post.rb", "lib/jekyll/site.rb", "lib/jekyll/tags/highlight.rb", "lib/jekyll/tags/include.rb", "test/helper.rb", "test/source/_includes/sig.markdown", "test/source/_layouts/default.html", "test/source/_layouts/simple.html", "test/source/_posts/2008-10-18-foo-bar.textile", "test/source/_posts/2008-11-21-complex.textile", "test/source/_posts/2008-12-03-permalinked-post.textile", "test/source/_posts/2008-12-13-include.markdown", "test/source/css/screen.css", "test/source/index.html", "test/suite.rb", "test/test_generated_site.rb", "test/test_jekyll.rb", "test/test_post.rb", "test/test_site.rb"]
s.has_rdoc = true
s.rdoc_options = ["--main", "README.txt"]
s.require_paths = ["lib"]
s.rubyforge_project = %q{jekyll}
s.rubygems_version = %q{1.3.0}
s.summary = %q{Jekyll is a simple, blog aware, static site generator.}
- s.test_files = ["test/test_jekyll.rb", "test/test_post.rb", "test/test_site.rb"]
+ s.test_files = ["test/test_generated_site.rb", "test/test_jekyll.rb", "test/test_post.rb", "test/test_site.rb"]
if s.respond_to? :specification_version then
current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
diff --git a/lib/jekyll.rb b/lib/jekyll.rb
index aa67073e2..829d1d4bd 100644
--- a/lib/jekyll.rb
+++ b/lib/jekyll.rb
@@ -29,9 +29,9 @@ begin
rescue LoadError
puts "The maruku gem is required for markdown support!"
end
-require 'directory_watcher'
# internal requires
+require 'jekyll/core_ext'
require 'jekyll/site'
require 'jekyll/convertible'
require 'jekyll/layout'
@@ -43,14 +43,15 @@ require 'jekyll/tags/include'
require 'jekyll/albino'
module Jekyll
- VERSION = '0.2.0'
+ VERSION = '0.3.0'
class << self
- attr_accessor :source, :dest, :lsi, :pygments
+ attr_accessor :source, :dest, :lsi, :pygments, :markdown_proc
end
Jekyll.lsi = false
Jekyll.pygments = false
+ Jekyll.markdown_proc = Proc.new { |x| Maruku.new(x).to_html }
def self.process(source, dest)
require 'classifier' if Jekyll.lsi
diff --git a/lib/jekyll/converters/mt.rb b/lib/jekyll/converters/mt.rb
new file mode 100644
index 000000000..3b98b139f
--- /dev/null
+++ b/lib/jekyll/converters/mt.rb
@@ -0,0 +1,59 @@
+# Created by Nick Gerakines, open source and publically available under the
+# MIT license. Use this module at your own risk.
+# I'm an Erlang/Perl/C++ guy so please forgive my dirty ruby.
+
+require 'rubygems'
+require 'sequel'
+require 'fileutils'
+
+# NOTE: This converter requires Sequel and the MySQL gems.
+# The MySQL gem can be difficult to install on OS X. Once you have MySQL
+# installed, running the following commands should work:
+# $ sudo gem install sequel
+# $ sudo gem install mysql -- --with-mysql-config=/usr/local/mysql/bin/mysql_config
+
+module Jekyll
+ module MT
+ # This query will pull blog posts from all entries across all blogs. If
+ # you've got unpublished, deleted or otherwise hidden posts please sift
+ # through the created posts to make sure nothing is accidently published.
+ QUERY = "SELECT entry_id, entry_basename, entry_text, entry_text_more, entry_created_on, entry_title FROM mt_entry"
+
+ def self.process(dbname, user, pass, host = 'localhost')
+ db = Sequel.mysql(dbname, :user => user, :password => pass, :host => host)
+
+ FileUtils.mkdir_p "_posts"
+
+ db[QUERY].each do |post|
+ title = post[:entry_title]
+ slug = post[:entry_basename]
+ date = post[:entry_created_on]
+ content = post[:entry_text]
+ more_content = post[:entry_text_more]
+
+ # Be sure to include the body and extended body.
+ if more_content != nil
+ content = content + " \n" + more_content
+ end
+
+ # Ideally, this script would determine the post format (markdown, html
+ # , etc) and create files with proper extensions. At this point it
+ # just assumes that markdown will be acceptable.
+ name = [date.year, date.month, date.day, slug].join('-') + ".markdown"
+
+ data = {
+ 'layout' => 'post',
+ 'title' => title.to_s,
+ 'mt_id' => post[:entry_id],
+ }.delete_if { |k,v| v.nil? || v == ''}.to_yaml
+
+ File.open("_posts/#{name}", "w") do |f|
+ f.puts data
+ f.puts "---"
+ f.puts content
+ end
+ end
+
+ end
+ end
+end
diff --git a/lib/jekyll/converters/typo.rb b/lib/jekyll/converters/typo.rb
new file mode 100644
index 000000000..febac65dc
--- /dev/null
+++ b/lib/jekyll/converters/typo.rb
@@ -0,0 +1,49 @@
+# Author: Toby DiPasquale
+require 'fileutils'
+require 'rubygems'
+require 'sequel'
+
+module Jekyll
+ module Typo
+ # this SQL *should* work for both MySQL and PostgreSQL, but I haven't
+ # tested PostgreSQL yet (as of 2008-12-16)
+ SQL = <<-EOS
+ SELECT c.id id,
+ c.title title,
+ c.permalink slug,
+ c.body body,
+ c.published_at date,
+ c.state state,
+ COALESCE(tf.name, 'html') filter
+ FROM contents c
+ LEFT OUTER JOIN text_filters tf
+ ON c.text_filter_id = tf.id
+ EOS
+
+ def self.process dbname, user, pass, host='localhost'
+ FileUtils.mkdir_p '_posts'
+ db = Sequel.mysql dbname, :user => user, :password => pass, :host => host
+ db[SQL].each do |post|
+ next unless post[:state] =~ /Published/
+
+ name = [ sprintf("%.04d", post[:date].year),
+ sprintf("%.02d", post[:date].month),
+ sprintf("%.02d", post[:date].day),
+ post[:slug].strip ].join('-')
+ # Can have more than one text filter in this field, but we just want
+ # the first one for this
+ name += '.' + post[:filter].split(' ')[0]
+
+ File.open("_posts/#{name}", 'w') do |f|
+ f.puts({ 'layout' => 'post',
+ 'title' => post[:title].to_s,
+ 'typo_id' => post[:id]
+ }.delete_if { |k, v| v.nil? || v == '' }.to_yaml)
+ f.puts '---'
+ f.puts post[:body].delete("\r")
+ end
+ end
+ end
+
+ end # module Typo
+end # module Jekyll
diff --git a/lib/jekyll/convertible.rb b/lib/jekyll/convertible.rb
index b8b9d82c1..d68d6039c 100644
--- a/lib/jekyll/convertible.rb
+++ b/lib/jekyll/convertible.rb
@@ -19,18 +19,18 @@ module Jekyll
self.data = YAML.load($1)
end
end
-
+
# Transform the contents based on the file extension.
#
# Returns nothing
def transform
- case self.ext
- when ".textile":
+ case self.ext[1..-1]
+ when /textile/i
self.ext = ".html"
self.content = RedCloth.new(self.content).to_html
- when ".markdown":
+ when /markdown/i, /mkdn/i, /md/i
self.ext = ".html"
- self.content = Maruku.new(self.content).to_html
+ self.content = Jekyll.markdown_proc.call(self.content)
end
end
@@ -39,10 +39,8 @@ module Jekyll
# +site_payload+ is the site payload hash
#
# Returns nothing
- def do_layout(payload, layouts, site_payload)
- # construct payload
- payload = payload.merge(site_payload)
- # render content
+ def do_layout(payload, layouts)
+ # render and transform content (this becomes the final content of the object)
self.content = Liquid::Template.parse(self.content).render(payload, [Jekyll::Filters])
self.transform
@@ -52,7 +50,7 @@ module Jekyll
# recursively render layouts
layout = layouts[self.data["layout"]]
while layout
- payload = payload.merge({"content" => self.output, "page" => payload['page']})
+ payload = payload.deep_merge({"content" => self.output, "page" => layout.data})
self.output = Liquid::Template.parse(layout.content).render(payload, [Jekyll::Filters])
layout = layouts[layout.data["layout"]]
diff --git a/lib/jekyll/core_ext.rb b/lib/jekyll/core_ext.rb
new file mode 100644
index 000000000..de5847110
--- /dev/null
+++ b/lib/jekyll/core_ext.rb
@@ -0,0 +1,22 @@
+class Hash
+ # Merges self with another hash, recursively.
+ #
+ # This code was lovingly stolen from some random gem:
+ # http://gemjack.com/gems/tartan-0.1.1/classes/Hash.html
+ #
+ # Thanks to whoever made it.
+ def deep_merge(hash)
+ target = dup
+
+ hash.keys.each do |key|
+ if hash[key].is_a? Hash and self[key].is_a? Hash
+ target[key] = target[key].deep_merge(hash[key])
+ next
+ end
+
+ target[key] = hash[key]
+ end
+
+ target
+ end
+end
\ No newline at end of file
diff --git a/lib/jekyll/filters.rb b/lib/jekyll/filters.rb
index 6051626a8..6cfa6361d 100644
--- a/lib/jekyll/filters.rb
+++ b/lib/jekyll/filters.rb
@@ -19,6 +19,21 @@ module Jekyll
def number_of_words(input)
input.split.length
- end
+ end
+
+ def array_to_sentence_string(array)
+ connector = "and"
+ case array.length
+ when 0
+ ""
+ when 1
+ array[0].to_s
+ when 2
+ "#{array[0]} #{connector} #{array[1]}"
+ else
+ "#{array[0...-1].join(', ')}, #{connector} #{array[-1]}"
+ end
+ end
+
end
end
\ No newline at end of file
diff --git a/lib/jekyll/layout.rb b/lib/jekyll/layout.rb
index 50397c40c..391cf69ce 100644
--- a/lib/jekyll/layout.rb
+++ b/lib/jekyll/layout.rb
@@ -28,21 +28,6 @@ module Jekyll
def process(name)
self.ext = File.extname(name)
end
-
- # Add any necessary layouts to this post
- # +layouts+ is a Hash of {"name" => "layout"}
- # +site_payload+ is the site payload hash
- #
- # Returns nothing
- def add_layout(layouts, site_payload)
- payload = {"page" => self.data}.merge(site_payload)
- self.content = Liquid::Template.parse(self.content).render(payload, [Jekyll::Filters])
-
- layout = layouts[self.data["layout"]] || self.content
- payload = {"content" => self.content, "page" => self.data}
-
- self.content = Liquid::Template.parse(layout).render(payload, [Jekyll::Filters])
- end
end
end
\ No newline at end of file
diff --git a/lib/jekyll/page.rb b/lib/jekyll/page.rb
index 01fe88021..140258fcc 100644
--- a/lib/jekyll/page.rb
+++ b/lib/jekyll/page.rb
@@ -37,9 +37,9 @@ module Jekyll
# +site_payload+ is the site payload hash
#
# Returns nothing
- def add_layout(layouts, site_payload)
- payload = {"page" => self.data}
- do_layout(payload, layouts, site_payload)
+ def render(layouts, site_payload)
+ payload = {"page" => self.data}.deep_merge(site_payload)
+ do_layout(payload, layouts)
end
# Write the generated page file to the destination directory.
diff --git a/lib/jekyll/post.rb b/lib/jekyll/post.rb
index fabbaf85f..890cae8d4 100644
--- a/lib/jekyll/post.rb
+++ b/lib/jekyll/post.rb
@@ -8,7 +8,7 @@ module Jekyll
attr_accessor :lsi
end
- MATCHER = /^(\d+-\d+-\d+)-(.*)(\.[^.]+)$/
+ MATCHER = /^(.+\/)*(\d+-\d+-\d+)-(.*)(\.[^.]+)$/
# Post name validator. Post filenames must be like:
# 2008-11-05-my-awesome-post.textile
@@ -18,7 +18,7 @@ module Jekyll
name =~ MATCHER
end
- attr_accessor :date, :slug, :ext, :categories
+ attr_accessor :date, :slug, :ext, :categories, :topics
attr_accessor :data, :content, :output
# Initialize this Post instance.
@@ -27,15 +27,17 @@ module Jekyll
# +categories+ is an Array of Strings for the categories for this post
#
# Returns
- def initialize(base, name)
- @base = base
+ def initialize(source, dir, name)
+ @base = File.join(source, dir, '_posts')
@name = name
- @categories = base.split('/').reject { |p| ['.', '_posts'].include? p }
+
+ self.categories = dir.split('/').reject { |x| x.empty? }
+
+ parts = name.split('/')
+ self.topics = parts.size > 1 ? parts[0..-2] : []
self.process(name)
- self.read_yaml(base, name)
- #Removed to avoid munging of liquid tags, replaced in convertible.rb#48
- #self.transform
+ self.read_yaml(@base, name)
end
# Spaceship is based on Post#date
@@ -50,7 +52,7 @@ module Jekyll
#
# Returns nothing
def process(name)
- m, date, slug, ext = *name.match(MATCHER)
+ m, cats, date, slug, ext = *name.match(MATCHER)
self.date = Time.parse(date)
self.slug = slug
self.ext = ext
@@ -63,10 +65,12 @@ module Jekyll
#
# Returns
def dir
- path = @categories ? '/' + @categories.join('/') : ''
- permalink ?
- permalink.to_s.split("/")[0..-2].join("/") :
- "#{path}" + date.strftime("/%Y/%m/%d/")
+ if permalink
+ permalink.to_s.split("/")[0..-2].join("/")
+ else
+ prefix = self.categories.empty? ? '' : '/' + self.categories.join('/')
+ prefix + date.strftime("/%Y/%m/%d/")
+ end
end
# The full path and filename of the post.
@@ -121,11 +125,16 @@ module Jekyll
# +site_payload+ is the site payload hash
#
# Returns nothing
- def add_layout(layouts, site_payload)
- # construct post payload
- related = related_posts(site_payload["site"]["posts"])
- payload = {"page" => self.to_liquid.merge(self.data)}
- do_layout(payload, layouts, site_payload.merge({"site" => {"related_posts" => related}}))
+ def render(layouts, site_payload)
+ # construct payload
+ payload =
+ {
+ "site" => { "related_posts" => related_posts(site_payload["site"]["posts"]) },
+ "page" => self.to_liquid
+ }
+ payload = payload.deep_merge(site_payload)
+
+ do_layout(payload, layouts)
end
# Write the generated post file to the destination directory.
@@ -145,11 +154,16 @@ module Jekyll
#
# Returns
def to_liquid
- { "title" => self.data["title"] || "",
+ { "title" => self.data["title"] || self.slug.split('-').select {|w| w.capitalize! || w }.join(' '),
"url" => self.url,
"date" => self.date,
"id" => self.id,
- "content" => self.content }
+ "topics" => self.topics,
+ "content" => self.content }.deep_merge(self.data)
+ end
+
+ def inspect
+ ""
end
end
diff --git a/lib/jekyll/site.rb b/lib/jekyll/site.rb
index b808bc59c..a0cdffd44 100644
--- a/lib/jekyll/site.rb
+++ b/lib/jekyll/site.rb
@@ -28,14 +28,15 @@ module Jekyll
self.write_posts
end
- # Read all the files in /_layouts into memory for
- # later use.
+ # Read all the files in /_layouts except backup files
+ # (end with "~") into memory for later use.
#
# Returns nothing
def read_layouts
base = File.join(self.source, "_layouts")
entries = Dir.entries(base)
- entries = entries.reject { |e| File.directory?(e) }
+ entries = entries.reject { |e| e[-1..-1] == '~' }
+ entries = entries.reject { |e| File.directory?(File.join(base, e)) }
entries.each do |f|
name = f.split(".")[0..-2].join(".")
@@ -45,16 +46,29 @@ module Jekyll
# ignore missing layout dir
end
- # Read all the files in /_posts and create a new Post
- # object with each one.
+ # Read all the files in /_posts except backup files (end with "~")
+ # and create a new Post object with each one.
#
# Returns nothing
- def read_posts(base)
- entries = Dir.entries(base)
- entries = entries.reject { |e| File.directory?(e) }
+ def read_posts(dir)
+ base = File.join(self.source, dir, '_posts')
+
+ entries = []
+ Dir.chdir(base) { entries = Dir['**/*'] }
+ entries = entries.reject { |e| e[-1..-1] == '~' }
+ entries = entries.reject { |e| File.directory?(File.join(base, e)) }
+ # first pass processes, but does not yet render post content
entries.each do |f|
- self.posts << Post.new(base, f) if Post.valid?(f)
+ if Post.valid?(f)
+ post = Post.new(self.source, dir, f)
+ self.posts << post
+ end
+ end
+
+ # second pass renders each post now that full site payload is available
+ self.posts.each do |post|
+ post.render(self.layouts, site_payload)
end
self.posts.sort!
@@ -67,14 +81,14 @@ module Jekyll
# Returns nothing
def write_posts
self.posts.each do |post|
- post.add_layout(self.layouts, site_payload)
post.write(self.dest)
end
end
# Copy all regular files from to / ignoring
- # any files/directories that are hidden (start with ".") or contain
- # site content (start with "_") unless they are "_posts" directories
+ # any files/directories that are hidden or backup files (start
+ # with "." or end with "~") or contain site content (start with "_")
+ # unless they are "_posts" directories
# The +dir+ String is a relative path used to call this method
# recursively as it descends through directories
#
@@ -82,47 +96,63 @@ module Jekyll
def transform_pages(dir = '')
base = File.join(self.source, dir)
entries = Dir.entries(base)
- entries = entries.reject { |e|
- (e != '_posts') and ['.', '_'].include?(e[0..0])
- }
+ entries = entries.reject { |e| e[-1..-1] == '~' }
+ entries = entries.reject do |e|
+ (e != '_posts') and ['.', '_'].include?(e[0..0])
+ end
+
+ # we need to make sure to process _posts *first* otherwise they
+ # might not be available yet to other templates as {{ site.posts }}
+ if entries.include?('_posts')
+ entries.delete('_posts')
+ read_posts(dir)
+ end
entries.each do |f|
- if f == '_posts'
- read_posts(File.join(base, f))
- elsif File.directory?(File.join(base, f))
+ if File.directory?(File.join(base, f))
next if self.dest.sub(/\/$/, '') == File.join(base, f)
transform_pages(File.join(dir, f))
else
first3 = File.open(File.join(self.source, dir, f)) { |fd| fd.read(3) }
-
- # if the file appears to have a YAML header then process it as a page
+
if first3 == "---"
+ # file appears to have a YAML header so process it as a page
page = Page.new(self.source, dir, f)
- page.add_layout(self.layouts, site_payload)
+ page.render(self.layouts, site_payload)
page.write(self.dest)
- # otherwise copy the file without transforming it
else
+ # otherwise copy the file without transforming it
FileUtils.mkdir_p(File.join(self.dest, dir))
FileUtils.cp(File.join(self.source, dir, f), File.join(self.dest, dir, f))
end
end
end
end
-
+
+ # Constructs a hash map of Posts indexed by the specified Post attribute
+ #
+ # Returns {post_attr => []}
+ def post_attr_hash(post_attr)
+ # Build a hash map based on the specified post attribute ( post attr => array of posts )
+ # then sort each array in reverse order
+ hash = Hash.new { |hash, key| hash[key] = Array.new }
+ self.posts.each { |p| p.send(post_attr.to_sym).each { |t| hash[t] << p } }
+ hash.values.map { |sortme| sortme.sort! { |a, b| b <=> a} }
+ return hash
+ end
+
# The Hash payload containing site-wide data
#
- # Returns {"site" => {"time" =>