Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

Merge remote-tracking branch 'mojombo/master'

Conflicts:
	lib/jekyll/post.rb
  • Loading branch information...
commit 3cebd952482d705e88f42e0540cb03649a927748 2 parents edb51ce + 4499df8
@wez authored
Showing with 1,899 additions and 512 deletions.
  1. +4 −0 .gitignore
  2. +2 −0  Gemfile
  3. +42 −1 History.txt
  4. +1 −1  README.textile
  5. +9 −2 Rakefile
  6. +100 −3 bin/jekyll
  7. +18 −0 features/create_sites.feature
  8. +2 −2 features/post_data.feature
  9. +19 −0 features/site_configuration.feature
  10. +3 −0  features/support/env.rb
  11. +27 −16 jekyll.gemspec
  12. +15 −4 lib/jekyll.rb
  13. +0 −120 lib/jekyll/albino.rb
  14. +14 −2 lib/jekyll/converters/markdown.rb
  15. +19 −2 lib/jekyll/converters/textile.rb
  16. +38 −23 lib/jekyll/convertible.rb
  17. +66 −1 lib/jekyll/filters.rb
  18. +33 −7 lib/jekyll/generators/pagination.rb
  19. +18 −10 lib/jekyll/layout.rb
  20. +3 −3 lib/jekyll/migrators/csv.rb
  21. +30 −13 lib/jekyll/migrators/drupal.rb
  22. +49 −0 lib/jekyll/migrators/enki.rb
  23. +53 −0 lib/jekyll/migrators/joomla.rb
  24. +0 −1  lib/jekyll/migrators/marley.rb
  25. +17 −12 lib/jekyll/migrators/mephisto.rb
  26. +26 −17 lib/jekyll/migrators/mt.rb
  27. +67 −0 lib/jekyll/migrators/posterous.rb
  28. +47 −0 lib/jekyll/migrators/rss.rb
  29. +16 −8 lib/jekyll/migrators/textpattern.rb
  30. +195 −0 lib/jekyll/migrators/tumblr.rb
  31. +8 −6 lib/jekyll/migrators/typo.rb
  32. +0 −38 lib/jekyll/migrators/wordpress.com.rb
  33. +274 −36 lib/jekyll/migrators/wordpress.rb
  34. +70 −0 lib/jekyll/migrators/wordpressdotcom.rb
  35. +56 −35 lib/jekyll/page.rb
  36. +1 −0  lib/jekyll/plugin.rb
  37. +28 −15 lib/jekyll/post.rb
  38. +150 −86 lib/jekyll/site.rb
  39. +12 −15 lib/jekyll/static_file.rb
  40. +17 −14 lib/jekyll/tags/highlight.rb
  41. +38 −0 lib/jekyll/tags/post_url.rb
  42. +3 −3 test/helper.rb
  43. +7 −0 test/source/_posts/2011-04-12-md-extension.md
  44. 0  test/source/_posts/2011-04-12-text-extension.text
  45. +3 −1 test/suite.rb
  46. +1 −1  test/test_configuration.rb
  47. +1 −1  test/test_core_ext.rb
  48. +10 −1 test/test_filters.rb
  49. +2 −2 test/test_generated_site.rb
  50. +1 −1  test/test_kramdown.rb
  51. +1 −1  test/test_page.rb
  52. +1 −1  test/test_pager.rb
  53. +62 −2 test/test_post.rb
  54. +1 −1  test/test_rdiscount.rb
  55. +21 −0 test/test_redcarpet.rb
  56. +86 −0 test/test_redcloth.rb
  57. +37 −3 test/test_site.rb
  58. +75 −1 test/test_tags.rb
View
4 .gitignore
@@ -1,6 +1,10 @@
+Gemfile.lock
test/dest
*.gem
pkg/
*.swp
*~
_site/
+.bundle/
+.DS_Store
+bbin/
View
2  Gemfile
@@ -0,0 +1,2 @@
+source :rubygems
+gemspec
View
43 History.txt
@@ -1,3 +1,44 @@
+== HEAD
+ * Minor Enhancements
+ * Add ability to explicitly specify included files (#261)
+ * Add --default-mimetype option (#279)
+ * Allow setting of RedCloth options (#284)
+ * Add post_url Liquid tag for internal post linking (#369)
+ * Allow multiple plugin dirs to be specified (#438)
+ * Bug Fixes
+ * Allow some special characters in highlight names
+ * URL escape category names in URL generation (#360)
+ * Fix error with limit_posts (#442)
+ * Properly select dotfile during directory scan (#363, #431, #377)
+
+== 0.11.2 / 2011-12-27
+ * Bug Fixes
+ * Fix gemspec
+
+== 0.11.1 / 2011-12-27
+ * Bug Fixes
+ * Fix extra blank line in highlight blocks (#409)
+ * Update dependencies
+
+== 0.11.0 / 2011-07-10
+ * Major Enhancements
+ * Add command line importer functionality (#253)
+ * Add Redcarpet Markdown support (#318)
+ * Make markdown/textile extensions configurable (#312)
+ * Add `markdownify` filter
+ * Minor Enhancements
+ * Switch to Albino gem
+ * Bundler support
+ * Use English library to avoid hoops (#292)
+ * Add Posterous importer (#254)
+ * Fixes for Wordpress importer (#274, #252, #271)
+ * Better error message for invalid post date (#291)
+ * Print formatted fatal exceptions to stdout on build failure
+ * Add Tumblr importer (#323)
+ * Add Enki importer (#320)
+ * Bug Fixes
+ * Secure additional path exploits
+
== 0.10.0 / 2010-12-16
* Bug Fixes
* Add --no-server option.
@@ -75,7 +116,7 @@
* Empty tags causes error in read_posts (#84)
* Fix pagination to adhere to read/render/write paradigm
* Test Enhancement
- * cucumber features no longer use site.ports.first where a better
+ * cucumber features no longer use site.posts.first where a better
alternative is available
== 0.5.6 / 2010-01-08
View
2  README.textile
@@ -27,7 +27,6 @@ h2. Runtime Dependencies
* Classifier: Generating related posts (Ruby)
* Maruku: Default markdown engine (Ruby)
* Directory Watcher: Auto-regeneration of sites (Ruby)
-* Open4: Talking to pygments for syntax highlighting (Ruby)
* Pygments: Syntax highlighting (Python)
h2. Developer Dependencies
@@ -35,6 +34,7 @@ h2. Developer Dependencies
* Shoulda: Test framework (Ruby)
* RR: Mocking (Ruby)
* RedGreen: Nicer test output (Ruby)
+* RDiscount: Discount Markdown Processor (Ruby)
h2. License
View
11 Rakefile
@@ -1,7 +1,10 @@
require 'rubygems'
require 'rake'
+require 'rdoc'
require 'date'
+$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), *%w[lib]))
+
#############################################################################
#
# Helper functions
@@ -47,6 +50,10 @@ task :default => [:test, :features]
require 'rake/testtask'
Rake::TestTask.new(:test) do |test|
+ if `which pygmentize` == ''
+ puts "You must have Pygments installed to run the tests."
+ exit 1
+ end
test.libs << 'lib' << 'test'
test.pattern = 'test/**/test_*.rb'
test.verbose = true
@@ -60,7 +67,7 @@ task :coverage do
sh "open coverage/index.html"
end
-require 'rake/rdoctask'
+require 'rdoc/task'
Rake::RDocTask.new do |rdoc|
rdoc.rdoc_dir = 'rdoc'
rdoc.title = "#{name} #{version}"
@@ -117,7 +124,7 @@ task :release => :build do
puts "You must be on the master branch to release!"
exit!
end
- sh "git commit --allow-empty -a -m 'Release #{version}'"
+ sh "git commit --allow-empty -m 'Release #{version}'"
sh "git tag v#{version}"
sh "git push origin master"
sh "git push origin v#{version}"
View
103 bin/jekyll
@@ -9,7 +9,8 @@ Basic Command Line Usage:
jekyll # . -> ./_site
jekyll <path to write generated site> # . -> <path>
jekyll <path to source> <path to write generated site> # <path> -> <path>
-
+ jekyll import <importer name> <options> # imports posts using named import script
+
Configuration is read from '<source>/_config.yml' but can be overriden
using the following options:
@@ -18,11 +19,37 @@ HELP
require 'optparse'
require 'jekyll'
+
exec = {}
options = {}
opts = OptionParser.new do |opts|
opts.banner = help
+ opts.on("--file [PATH]", "File to import from") do |import_file|
+ options['file'] = import_file
+ end
+
+ opts.on("--dbname [TEXT]", "DB to import from") do |import_dbname|
+ options['dbname'] = import_dbname
+ end
+
+ opts.on("--user [TEXT]", "Username to use when importing") do |import_user|
+ options['user'] = import_user
+ end
+
+ opts.on("--pass [TEXT]", "Password to use when importing") do |import_pass|
+ options['pass'] = import_pass
+ end
+
+ opts.on("--host [HOST ADDRESS]", "Host to import from") do |import_host|
+ options['host'] = import_host
+ end
+
+ opts.on("--site [SITE NAME]", "Site to import from") do |import_site|
+ options['site'] = import_site
+ end
+
+
opts.on("--[no-]safe", "Safe mode (default unsafe)") do |safe|
options['safe'] = safe
end
@@ -44,6 +71,10 @@ opts = OptionParser.new do |opts|
options['baseurl'] = baseurl
end
+ opts.on("--default-mimetype [MT]", "Mimetype to use when no file extension (if --server)") do |mt|
+ options['default-mimetype'] = mt
+ end
+
opts.on("--[no-]lsi", "Use LSI for better related posts") do |lsi|
options['lsi'] = lsi
end
@@ -55,6 +86,10 @@ opts = OptionParser.new do |opts|
opts.on("--rdiscount", "Use rdiscount gem for Markdown") do
options['markdown'] = 'rdiscount'
end
+
+ opts.on("--redcarpet", "Use redcarpet gem for Markdown") do
+ options['markdown'] = 'redcarpet'
+ end
opts.on("--kramdown", "Use kramdown gem for Markdown") do
options['markdown'] = 'kramdown'
@@ -105,7 +140,62 @@ end
# Read command line options into `options` hash
opts.parse!
-# Get source and destintation from command line
+
+# Check for import stuff
+if ARGV.size > 0
+ if ARGV[0] == 'import'
+ migrator = ARGV[1]
+
+ if migrator.nil?
+ puts "Invalid options. Run `jekyll --help` for assistance."
+ exit(1)
+ else
+ migrator = migrator.downcase
+ end
+
+ cmd_options = []
+ ['file', 'dbname', 'user', 'pass', 'host', 'site'].each do |p|
+ cmd_options << "\"#{options[p]}\"" unless options[p].nil?
+ end
+
+ # It's import time
+ puts "Importing..."
+
+ # Ideally, this shouldn't be necessary. Maybe parse the actual
+ # src files for the migrator name?
+ migrators = {
+ :posterous => 'Posterous',
+ :wordpressdotcom => 'WordpressDotCom',
+ :wordpress => 'WordPress',
+ :csv => 'CSV',
+ :drupal => 'Drupal',
+ :enki => 'Enki',
+ :mephisto => 'Mephisto',
+ :mt => 'MT',
+ :textpattern => 'TextPattern',
+ :tumblr => 'Tumblr',
+ :typo => 'Typo'
+ }
+
+ app_root = File.join(File.dirname(__FILE__), '..')
+
+ require "#{app_root}/lib/jekyll/migrators/#{migrator}"
+
+ if Jekyll.const_defined?(migrators[migrator.to_sym])
+ migrator_class = Jekyll.const_get(migrators[migrator.to_sym])
+ migrator_class.process(*cmd_options)
+ else
+ puts "Invalid migrator. Run `jekyll --help` for assistance."
+ exit(1)
+ end
+
+ exit(0)
+ end
+end
+
+
+
+# Get source and destination from command line
case ARGV.size
when 0
when 1
@@ -162,7 +252,11 @@ else
puts "Building site: #{source} -> #{destination}"
begin
site.process
- rescue Jekyll::FatalException
+ rescue Jekyll::FatalException => e
+ puts
+ puts "ERROR: YOUR SITE COULD NOT BE BUILT:"
+ puts "------------------------------------"
+ puts e.message
exit(1)
end
puts "Successfully generated site: #{source} -> #{destination}"
@@ -177,6 +271,9 @@ if options['server']
mime_types = WEBrick::HTTPUtils::DefaultMimeTypes
mime_types.store 'js', 'application/javascript'
+ if options['default-mimetype']
+ mime_types.store(nil, options['default-mimetype'])
+ end
s = HTTPServer.new(
:Port => options['server_port'],
View
18 features/create_sites.feature
@@ -92,3 +92,21 @@ Feature: Create sites
When I debug jekyll
Then the _site directory should exist
And I should see "Basic Site with include tag: Generated by Jekyll" in "_site/index.html"
+
+ Scenario: Basic site with internal post linking
+ Given I have an "index.html" page that contains "URL: {% post_url 2020-01-31-entry2 %}"
+ And I have a configuration file with "permalink" set to "pretty"
+ And I have a _posts directory
+ And I have the following posts:
+ | title | date | layout | content |
+ | entry1 | 12/31/2007 | post | content for entry1. |
+ | entry2 | 01/31/2020 | post | content for entry2. |
+ When I run jekyll
+ Then the _site directory should exist
+ And I should see "URL: /2020/01/31/entry2/" in "_site/index.html"
+
+ Scenario: Basic site with whitelisted dotfile
+ Given I have an ".htaccess" file that contains "SomeDirective"
+ When I run jekyll
+ Then the _site directory should exist
+ And I should see "SomeDirective" in "_site/.htaccess"
View
4 features/post_data.feature
@@ -31,10 +31,10 @@ Feature: Post data
And I have the following post:
| title | date | layout | content |
| Star Wars | 3/27/2009 | simple | Luke, I am your father. |
- And I have a simple layout that contains "Post date: {{ page.date }}"
+ And I have a simple layout that contains "Post date: {{ page.date | date_to_string }}"
When I run jekyll
Then the _site directory should exist
- And I should see "Post date: Fri Mar 27" in "_site/2009/03/27/star-wars.html"
+ And I should see "Post date: 27 Mar 2009" in "_site/2009/03/27/star-wars.html"
Scenario: Use post.id variable
Given I have a _posts directory
View
19 features/site_configuration.feature
@@ -55,6 +55,13 @@ Feature: Site configuration
Then the _site directory should exist
And I should see "<a href="http://google.com">Google</a>" in "_site/index.html"
+ Scenario: Use Redcarpet for markup
+ Given I have an "index.markdown" page that contains "[Google](http://google.com)"
+ And I have a configuration file with "markdown" set to "redcarpet"
+ When I run jekyll
+ Then the _site directory should exist
+ And I should see "<a href="http://google.com">Google</a>" in "_site/index.html"
+
Scenario: Use Maruku for markup
Given I have an "index.markdown" page that contains "[Google](http://google.com)"
And I have a configuration file with "markdown" set to "maruku"
@@ -124,3 +131,15 @@ Feature: Site configuration
And the "_site/2009/04/05/bananas.html" file should exist
And the "_site/2009/04/01/oranges.html" file should exist
And the "_site/2009/03/27/apples.html" file should not exist
+
+ Scenario: Copy over normally excluded files when they are explicitly included
+ Given I have a ".gitignore" file that contains ".DS_Store"
+ And I have an ".htaccess" file that contains "SomeDirective"
+ And I have a configuration file with "include" set to:
+ | value |
+ | .gitignore |
+ | .foo |
+ When I run jekyll
+ Then the _site directory should exist
+ And I should see ".DS_Store" in "_site/.gitignore"
+ And the "_site/.htaccess" file should not exist
View
3  features/support/env.rb
@@ -14,3 +14,6 @@ def run_jekyll(opts = {})
command << " >> /dev/null 2>&1" if opts[:debug].nil?
system command
end
+
+# work around "invalid option: --format" cucumber bug (see #296)
+Test::Unit.run = true if RUBY_VERSION < '1.9'
View
43 jekyll.gemspec
@@ -4,8 +4,8 @@ Gem::Specification.new do |s|
s.rubygems_version = '1.3.5'
s.name = 'jekyll'
- s.version = '0.10.0'
- s.date = '2010-12-16'
+ s.version = '0.11.2'
+ s.date = '2011-12-27'
s.rubyforge_project = 'jekyll'
s.summary = "A simple, blog aware, static site generator."
@@ -18,25 +18,30 @@ Gem::Specification.new do |s|
s.require_paths = %w[lib]
s.executables = ["jekyll"]
- s.default_executable = 'jekyll'
s.rdoc_options = ["--charset=UTF-8"]
s.extra_rdoc_files = %w[README.textile LICENSE]
- s.add_runtime_dependency('liquid', [">= 1.9.0"])
- s.add_runtime_dependency('classifier', [">= 1.3.1"])
- s.add_runtime_dependency('directory_watcher', [">= 1.1.1"])
- s.add_runtime_dependency('maruku', [">= 0.5.9"])
-
- s.add_development_dependency('redgreen', [">= 4.2.1"])
- s.add_development_dependency('shoulda', [">= 4.2.1"])
- s.add_development_dependency('rr', [">= 4.2.1"])
- s.add_development_dependency('cucumber', [">= 4.2.1"])
- s.add_development_dependency('RedCloth', [">= 4.2.1"])
- s.add_development_dependency('kramdown', [">= 0.12.0"])
+ s.add_runtime_dependency('liquid', "~> 2.3")
+ s.add_runtime_dependency('classifier', "~> 1.3")
+ s.add_runtime_dependency('directory_watcher', "~> 1.1")
+ s.add_runtime_dependency('maruku', "~> 0.5")
+ s.add_runtime_dependency('kramdown', "~> 0.13")
+ s.add_runtime_dependency('albino', "~> 1.3")
+ s.add_development_dependency('rake', "~> 0.9")
+ s.add_development_dependency('rdoc', "~> 3.11")
+ s.add_development_dependency('redgreen', "~> 1.2")
+ s.add_development_dependency('shoulda', "~> 2.11")
+ s.add_development_dependency('rr', "~> 1.0")
+ s.add_development_dependency('cucumber', "1.1")
+ s.add_development_dependency('RedCloth', "~> 4.2")
+ s.add_development_dependency('rdiscount', "~> 1.6")
+ s.add_development_dependency('redcarpet', "~> 1.9")
+
# = MANIFEST =
s.files = %w[
+ Gemfile
History.txt
LICENSE
README.textile
@@ -55,7 +60,6 @@ Gem::Specification.new do |s|
features/support/env.rb
jekyll.gemspec
lib/jekyll.rb
- lib/jekyll/albino.rb
lib/jekyll/converter.rb
lib/jekyll/converters/identity.rb
lib/jekyll/converters/markdown.rb
@@ -69,13 +73,16 @@ Gem::Specification.new do |s|
lib/jekyll/layout.rb
lib/jekyll/migrators/csv.rb
lib/jekyll/migrators/drupal.rb
+ lib/jekyll/migrators/enki.rb
lib/jekyll/migrators/marley.rb
lib/jekyll/migrators/mephisto.rb
lib/jekyll/migrators/mt.rb
+ lib/jekyll/migrators/posterous.rb
lib/jekyll/migrators/textpattern.rb
+ lib/jekyll/migrators/tumblr.rb
lib/jekyll/migrators/typo.rb
- lib/jekyll/migrators/wordpress.com.rb
lib/jekyll/migrators/wordpress.rb
+ lib/jekyll/migrators/wordpressdotcom.rb
lib/jekyll/page.rb
lib/jekyll/plugin.rb
lib/jekyll/post.rb
@@ -111,6 +118,8 @@ Gem::Specification.new do |s|
test/source/_posts/2010-01-09-time-override.textile
test/source/_posts/2010-01-09-timezone-override.textile
test/source/_posts/2010-01-16-override-data.textile
+ test/source/_posts/2011-04-12-md-extension.md
+ test/source/_posts/2011-04-12-text-extension.text
test/source/about.html
test/source/category/_posts/2008-9-23-categories.textile
test/source/contacts.html
@@ -131,8 +140,10 @@ Gem::Specification.new do |s|
test/test_pager.rb
test/test_post.rb
test/test_rdiscount.rb
+ test/test_redcarpet.rb
test/test_site.rb
test/test_tags.rb
+ test/test_redcloth.rb
]
# = MANIFEST =
View
19 lib/jekyll.rb
@@ -19,10 +19,12 @@ def require_all(path)
require 'fileutils'
require 'time'
require 'yaml'
+require 'English'
# 3rd party
require 'liquid'
require 'maruku'
+require 'albino'
# internal requires
require 'jekyll/core_ext'
@@ -32,7 +34,6 @@ def require_all(path)
require 'jekyll/page'
require 'jekyll/post'
require 'jekyll/filters'
-require 'jekyll/albino'
require 'jekyll/static_file'
require 'jekyll/errors'
@@ -45,7 +46,7 @@ def require_all(path)
require_all 'jekyll/tags'
module Jekyll
- VERSION = '0.10.0'
+ VERSION = '0.11.2'
# Default options. Overriden by values in _config.yml or command-line opts.
# (Strings rather symbols used for compatability with YAML).
@@ -64,6 +65,10 @@ module Jekyll
'pygments' => false,
'markdown' => 'maruku',
'permalink' => 'date',
+ 'include' => ['.htaccess'],
+
+ 'markdown_ext' => 'markdown,mkd,mkdn,md',
+ 'textile_ext' => 'textile',
'maruku' => {
'use_tex' => false,
@@ -75,6 +80,9 @@ module Jekyll
'rdiscount' => {
'extensions' => []
},
+ 'redcarpet' => {
+ 'extensions' => []
+ },
'kramdown' => {
'auto_ids' => true,
'footnote_nr' => 1,
@@ -90,11 +98,14 @@ module Jekyll
'coderay_bold_every' => 10,
'coderay_css' => 'style'
}
+ },
+ 'redcloth' => {
+ 'hard_breaks' => true
}
}
- # Generate a Jekyll configuration Hash by merging the default options
- # with anything in _config.yml, and adding the given options on top.
+ # Public: Generate a Jekyll configuration Hash by merging the default
+ # options with anything in _config.yml, and adding the given options on top.
#
# override - A Hash of config directives that override any options in both
# the defaults and the config file. See Jekyll::DEFAULTS for a
View
120 lib/jekyll/albino.rb
@@ -1,120 +0,0 @@
-##
-# Wrapper for the Pygments command line tool, pygmentize.
-#
-# Pygments: http://pygments.org/
-#
-# Assumes pygmentize is in the path. If not, set its location
-# with Albino.bin = '/path/to/pygmentize'
-#
-# Use like so:
-#
-# @syntaxer = Albino.new('/some/file.rb', :ruby)
-# puts @syntaxer.colorize
-#
-# This'll print out an HTMLized, Ruby-highlighted version
-# of '/some/file.rb'.
-#
-# To use another formatter, pass it as the third argument:
-#
-# @syntaxer = Albino.new('/some/file.rb', :ruby, :bbcode)
-# puts @syntaxer.colorize
-#
-# You can also use the #colorize class method:
-#
-# puts Albino.colorize('/some/file.rb', :ruby)
-#
-# Another also: you get a #to_s, for somewhat nicer use in Rails views.
-#
-# ... helper file ...
-# def highlight(text)
-# Albino.new(text, :ruby)
-# end
-#
-# ... view file ...
-# <%= highlight text %>
-#
-# The default lexer is 'text'. You need to specify a lexer yourself;
-# because we are using STDIN there is no auto-detect.
-#
-# To see all lexers and formatters available, run `pygmentize -L`.
-#
-# Chris Wanstrath // chris@ozmm.org
-# GitHub // http://github.com
-#
-
-class Albino
- @@bin = Rails.development? ? 'pygmentize' : '/usr/bin/pygmentize' rescue 'pygmentize'
-
- def self.bin=(path)
- @@bin = path
- end
-
- def self.colorize(*args)
- new(*args).colorize
- end
-
- def initialize(target, lexer = :text, format = :html)
- @target = target
- @options = { :l => lexer, :f => format, :O => 'encoding=utf-8' }
- end
-
- def execute(command)
- output = ''
- IO.popen(command, mode='r+') do |p|
- p.write @target
- p.close_write
- output = p.read.strip
- end
- output
- end
-
- def colorize(options = {})
- html = execute(@@bin + convert_options(options))
- # Work around an RDiscount bug: http://gist.github.com/97682
- html.to_s.sub(%r{</pre></div>\Z}, "</pre>\n</div>")
- end
- alias_method :to_s, :colorize
-
- def convert_options(options = {})
- @options.merge(options).inject('') do |string, (flag, value)|
- string + " -#{flag} #{value}"
- end
- end
-end
-
-if $0 == __FILE__
- require 'rubygems'
- require 'test/spec'
- require 'mocha'
- begin require 'redgreen'; rescue LoadError; end
-
- context "Albino" do
- setup do
- @syntaxer = Albino.new(__FILE__, :ruby)
- end
-
- specify "defaults to text" do
- syntaxer = Albino.new(__FILE__)
- syntaxer.expects(:execute).with('pygmentize -f html -l text').returns(true)
- syntaxer.colorize
- end
-
- specify "accepts options" do
- @syntaxer.expects(:execute).with('pygmentize -f html -l ruby').returns(true)
- @syntaxer.colorize
- end
-
- specify "works with strings" do
- syntaxer = Albino.new('class New; end', :ruby)
- assert_match %r(highlight), syntaxer.colorize
- end
-
- specify "aliases to_s" do
- assert_equal @syntaxer.colorize, @syntaxer.to_s
- end
-
- specify "class method colorize" do
- assert_equal @syntaxer.colorize, Albino.colorize(__FILE__, :ruby)
- end
- end
-end
View
16 lib/jekyll/converters/markdown.rb
@@ -10,6 +10,15 @@ def setup
return if @setup
# Set the Markdown interpreter (and Maruku self.config, if necessary)
case @config['markdown']
+ when 'redcarpet'
+ begin
+ require 'redcarpet'
+ @redcarpet_extensions = @config['redcarpet']['extensions'].map { |e| e.to_sym }
+ rescue LoadError
+ STDERR.puts 'You are missing a library required for Markdown. Please run:'
+ STDERR.puts ' $ [sudo] gem install redcarpet'
+ raise FatalException.new("Missing dependency: redcarpet")
+ end
when 'kramdown'
begin
require 'kramdown'
@@ -65,9 +74,10 @@ def setup
end
@setup = true
end
-
+
def matches(ext)
- ext =~ /(markdown|mkdn?|md)/i
+ rgx = '(' + @config['markdown_ext'].gsub(',','|') +')'
+ ext =~ Regexp.new(rgx, Regexp::IGNORECASE)
end
def output_ext(ext)
@@ -77,6 +87,8 @@ def output_ext(ext)
def convert(content)
setup
case @config['markdown']
+ when 'redcarpet'
+ Redcarpet.new(content, *@redcarpet_extensions).to_html
when 'kramdown'
# Check for use of coderay
if @config['kramdown']['use_coderay']
View
21 lib/jekyll/converters/textile.rb
@@ -17,7 +17,8 @@ def setup
end
def matches(ext)
- ext =~ /textile/i
+ rgx = '(' + @config['textile_ext'].gsub(',','|') +')'
+ ext =~ Regexp.new(rgx, Regexp::IGNORECASE)
end
def output_ext(ext)
@@ -26,7 +27,23 @@ def output_ext(ext)
def convert(content)
setup
- RedCloth.new(content).to_html
+
+ # Shortcut if config doesn't contain RedCloth section
+ return RedCloth.new(content).to_html if @config['redcloth'].nil?
+
+ # List of attributes defined on RedCloth
+ # (from http://redcloth.rubyforge.org/classes/RedCloth/TextileDoc.html)
+ attrs = ['filter_classes', 'filter_html', 'filter_ids', 'filter_styles',
+ 'hard_breaks', 'lite_mode', 'no_span_caps', 'sanitize_html']
+
+ r = RedCloth.new(content)
+
+ # Set attributes in r if they are NOT nil in the config
+ attrs.each do |attr|
+ r.instance_variable_set("@#{attr}".to_sym, @config['redcloth'][attr]) unless @config['redcloth'][attr].nil?
+ end
+
+ r.to_html
end
end
View
61 lib/jekyll/convertible.rb
@@ -1,3 +1,5 @@
+require 'set'
+
# Convertible provides methods for converting a pagelike item
# from a certain type of markup into actual content
#
@@ -8,29 +10,30 @@
# self.data=
# self.ext=
# self.output=
+# self.name
module Jekyll
module Convertible
- # Return the contents as a string
+ # Returns the contents as a String.
def to_s
self.content || ''
end
- # Read the YAML frontmatter
- # +base+ is the String path to the dir containing the file
- # +name+ is the String filename of the file
+ # Read the YAML frontmatter.
+ #
+ # base - The String path to the dir containing the file.
+ # name - The String filename of the file.
#
- # Returns nothing
+ # Returns nothing.
def read_yaml(base, name)
self.content = File.read(File.join(base, name))
- if self.content =~ /^(---\s*\n.*?\n?)^(---\s*$\n?)/m
- self.content = self.content[($1.size + $2.size)..-1]
-
- begin
+ begin
+ if self.content =~ /^(---\s*\n.*?\n?)^(---\s*$\n?)/m
+ self.content = $POSTMATCH
self.data = YAML.load($1)
- rescue => e
- puts "YAML Exception: #{e.message}"
end
+ rescue => e
+ puts "YAML Exception reading #{name}: #{e.message}"
end
self.data ||= {}
@@ -38,42 +41,46 @@ def read_yaml(base, name)
# Transform the contents based on the content type.
#
- # Returns nothing
+ # Returns nothing.
def transform
self.content = converter.convert(self.content)
end
- # Determine the extension depending on content_type
+ # Determine the extension depending on content_type.
#
- # Returns the extensions for the output file
+ # Returns the String extension for the output file.
+ # e.g. ".html" for an HTML output file.
def output_ext
converter.output_ext(self.ext)
end
# Determine which converter to use based on this convertible's
- # extension
+ # extension.
+ #
+ # Returns the Converter instance.
def converter
@converter ||= self.site.converters.find { |c| c.matches(self.ext) }
end
- # Add any necessary layouts to this convertible document
- # +layouts+ is a Hash of {"name" => "layout"}
- # +site_payload+ is the site payload hash
+ # Add any necessary layouts to this convertible document.
+ #
+ # payload - The site payload Hash.
+ # layouts - A Hash of {"name" => "layout"}.
#
- # Returns nothing
+ # Returns nothing.
def do_layout(payload, layouts)
info = { :filters => [Jekyll::Filters], :registers => { :site => self.site } }
# render and transform content (this becomes the final content of the object)
payload["pygments_prefix"] = converter.pygments_prefix
payload["pygments_suffix"] = converter.pygments_suffix
-
+
begin
self.content = Liquid::Template.parse(self.content).render(payload, info)
rescue => e
- puts "Liquid Exception: #{e.message} in #{self.data["layout"]}"
+ puts "Liquid Exception: #{e.message} in #{self.name}"
end
-
+
self.transform
# output keeps track of what will finally be written
@@ -81,6 +88,8 @@ def do_layout(payload, layouts)
# recursively render layouts
layout = layouts[self.data["layout"]]
+ used = Set.new([layout])
+
while layout
payload = payload.deep_merge({"content" => self.output, "page" => layout.data})
@@ -90,7 +99,13 @@ def do_layout(payload, layouts)
puts "Liquid Exception: #{e.message} in #{self.data["layout"]}"
end
- layout = layouts[layout.data["layout"]]
+ if layout = layouts[layout.data["layout"]]
+ if used.include?(layout)
+ layout = nil # avoid recursive chain
+ else
+ used << layout
+ end
+ end
end
end
end
View
67 lib/jekyll/filters.rb
@@ -3,18 +3,56 @@
module Jekyll
module Filters
+ # Convert a Textile string into HTML output.
+ #
+ # input - The Textile String to convert.
+ #
+ # Returns the HTML formatted String.
def textilize(input)
- TextileConverter.new.convert(input)
+ site = @context.registers[:site]
+ converter = site.getConverterImpl(Jekyll::TextileConverter)
+ converter.convert(input)
end
+ # Convert a Markdown string into HTML output.
+ #
+ # input - The Markdown String to convert.
+ #
+ # Returns the HTML formatted String.
+ def markdownify(input)
+ site = @context.registers[:site]
+ converter = site.getConverterImpl(Jekyll::MarkdownConverter)
+ converter.convert(input)
+ end
+
+ # Format a date in short format e.g. "27 Jan 2011".
+ #
+ # date - the Time to format.
+ #
+ # Returns the formatting String.
def date_to_string(date)
date.strftime("%d %b %Y")
end
+ # Format a date in long format e.g. "27 January 2011".
+ #
+ # date - The Time to format.
+ #
+ # Returns the formatted String.
def date_to_long_string(date)
date.strftime("%d %B %Y")
end
+ # Format a date for use in XML.
+ #
+ # date - The Time to format.
+ #
+ # Examples
+ #
+ # date_to_xmlschema(Time.now)
+ # # => "2011-04-24T20:34:46+08:00"
+ #
+ # Returns the formatted String.
def date_to_xmlschema(date)
date.xmlschema
end
@@ -23,6 +61,17 @@ def xml_escape(input)
CGI.escapeHTML(input)
end
+ # CGI escape a string for use in a URL. Replaces any special characters
+ # with appropriate %XX replacements.
+ #
+ # input - The String to escape.
+ #
+ # Examples
+ #
+ # cgi_escape('foo,bar;baz?')
+ # # => "foo%2Cbar%3Bbaz%3F"
+ #
+ # Returns the escaped String.
def cgi_escape(input)
CGI::escape(input)
end
@@ -31,10 +80,26 @@ def uri_escape(input)
URI.escape(input)
end
+ # Count the number of words in the input string.
+ #
+ # input - The String on which to operate.
+ #
+ # Returns the Integer word count.
def number_of_words(input)
input.split.length
end
+ # Join an array of things into a string by separating with commes and the
+ # word "and" for the last one.
+ #
+ # array - The Array of Strings to join.
+ #
+ # Examples
+ #
+ # array_to_sentence_string(["apples", "oranges", "grapes"])
+ # # => "apples, oranges, and grapes"
+ #
+ # Returns the formatted String.
def array_to_sentence_string(array)
connector = "and"
case array.length
View
40 lib/jekyll/generators/pagination.rb
@@ -1,8 +1,14 @@
module Jekyll
class Pagination < Generator
+ # This generator is safe from arbitrary code execution.
safe true
+ # Generate paginated pages if necessary.
+ #
+ # site - The Site.
+ #
+ # Returns nothing.
def generate(site)
site.pages.dup.each do |page|
paginate(site, page) if Pager.pagination_enabled?(site.config, page.name)
@@ -10,9 +16,11 @@ def generate(site)
end
# Paginates the blog's posts. Renders the index.html file into paginated
- # directories, ie: page2/index.html, page3/index.html, etc and adds more
+ # directories, e.g.: page2/index.html, page3/index.html, etc and adds more
# site-wide data.
- # +page+ is the index.html Page that requires pagination
+ #
+ # site - The Site.
+ # page - The index.html Page that requires pagination.
#
# {"paginator" => { "page" => <Number>,
# "per_page" => <Number>,
@@ -36,22 +44,38 @@ def paginate(site, page)
end
end
end
-
end
class Pager
attr_reader :page, :per_page, :posts, :total_posts, :total_pages, :previous_page, :next_page
+ # Calculate the number of pages.
+ #
+ # all_posts - The Array of all Posts.
+ # per_page - The Integer of entries per page.
+ #
+ # Returns the Integer number of pages.
def self.calculate_pages(all_posts, per_page)
- num_pages = all_posts.size / per_page.to_i
- num_pages = num_pages + 1 if all_posts.size % per_page.to_i != 0
- num_pages
+ (all_posts.size.to_f / per_page.to_i).ceil
end
+ # Determine if pagination is enabled for a given file.
+ #
+ # config - The configuration Hash.
+ # file - The String filename of the file.
+ #
+ # Returns true if pagination is enabled, false otherwise.
def self.pagination_enabled?(config, file)
file == 'index.html' && !config['paginate'].nil?
end
+ # Initialize a new Pager.
+ #
+ # config - The Hash configuration of the site.
+ # page - The Integer page number.
+ # all_posts - The Array of all the site's Posts.
+ # num_pages - The Integer number of pages or nil if you'd like the number
+ # of pages calculated.
def initialize(config, page, all_posts, num_pages = nil)
@page = page
@per_page = config['paginate'].to_i
@@ -70,6 +94,9 @@ def initialize(config, page, all_posts, num_pages = nil)
@next_page = @page != @total_pages ? @page + 1 : nil
end
+ # Convert this Pager's data to a Hash suitable for use by Liquid.
+ #
+ # Returns the Hash representation of this Pager.
def to_liquid
{
'page' => page,
@@ -83,5 +110,4 @@ def to_liquid
end
end
-
end
View
28 lib/jekyll/layout.rb
@@ -3,16 +3,23 @@ module Jekyll
class Layout
include Convertible
- attr_accessor :site
+ # Gets the Site object.
+ attr_reader :site
+
+ # Gets/Sets the extension of this layout.
attr_accessor :ext
- attr_accessor :data, :content
+
+ # Gets/Sets the Hash that holds the metadata for this layout.
+ attr_accessor :data
+
+ # Gets/Sets the content of this layout.
+ attr_accessor :content
# Initialize a new Layout.
- # +site+ is the Site
- # +base+ is the String path to the <source>
- # +name+ is the String filename of the post file
#
- # Returns <Page>
+ # site - The Site.
+ # base - The String path to the source.
+ # name - The String filename of the post file.
def initialize(site, base, name)
@site = site
@base = base
@@ -24,13 +31,14 @@ def initialize(site, base, name)
self.read_yaml(base, name)
end
- # Extract information from the layout filename
- # +name+ is the String filename of the layout file
+ # Extract information from the layout filename.
+ #
+ # name - The String filename of the layout file.
#
- # Returns nothing
+ # Returns nothing.
def process(name)
self.ext = File.extname(name)
end
end
-end
+end
View
6 lib/jekyll/migrators/csv.rb
@@ -1,7 +1,7 @@
module Jekyll
module CSV
- #Reads a csv with title, permalink, body, published_at, and filter.
- #It creates a post file for each row in the csv
+ # Reads a csv with title, permalink, body, published_at, and filter.
+ # It creates a post file for each row in the csv
def self.process(file = "posts.csv")
FileUtils.mkdir_p "_posts"
posts = 0
@@ -23,4 +23,4 @@ def self.process(file = "posts.csv")
"Created #{posts} posts!"
end
end
-end
+end
View
43 lib/jekyll/migrators/drupal.rb
@@ -11,16 +11,27 @@
module Jekyll
module Drupal
+ # Reads a MySQL database via Sequel and creates a post file for each post
+ # in wp_posts that has post_status = 'publish'. This restriction is made
+ # because 'draft' posts are not guaranteed to have valid dates.
+ QUERY = "SELECT n.nid, \
+ n.title, \
+ nr.body, \
+ n.created, \
+ n.status \
+ FROM node AS n, \
+ node_revisions AS nr \
+ WHERE (n.type = 'blog' OR n.type = 'story') \
+ AND n.vid = nr.vid"
- # Reads a MySQL database via Sequel and creates a post file for each
- # post in wp_posts that has post_status = 'publish'.
- # This restriction is made because 'draft' posts are not guaranteed to
- # have valid dates.
- QUERY = "SELECT node.nid, node.title, node_revisions.body, node.created, node.status FROM node, node_revisions WHERE (node.type = 'blog' OR node.type = 'story') AND node.vid = node_revisions.vid"
-
- def self.process(dbname, user, pass, host = 'localhost')
+ def self.process(dbname, user, pass, host = 'localhost', prefix = '')
db = Sequel.mysql(dbname, :user => user, :password => pass, :host => host, :encoding => 'utf8')
+ if prefix != ''
+ QUERY[" node "] = " " + prefix + "node "
+ QUERY[" node_revisions "] = " " + prefix + "node_revisions "
+ end
+
FileUtils.mkdir_p "_posts"
FileUtils.mkdir_p "_drafts"
@@ -67,12 +78,18 @@ def self.process(dbname, user, pass, host = 'localhost')
# Make a file to redirect from the old Drupal URL
if is_published
- FileUtils.mkdir_p "node/#{node_id}"
- File.open("node/#{node_id}/index.md", "w") do |f|
- f.puts "---"
- f.puts "layout: refresh"
- f.puts "refresh_to_post_id: /#{time.strftime("%Y/%m/%d/") + slug}"
- f.puts "---"
+ aliases = db["SELECT dst FROM #{prefix}url_alias WHERE src = ?", "node/#{node_id}"].all
+
+ aliases.push(:dst => "node/#{node_id}")
+
+ aliases.each do |url_alias|
+ FileUtils.mkdir_p url_alias[:dst]
+ File.open("#{url_alias[:dst]}/index.md", "w") do |f|
+ f.puts "---"
+ f.puts "layout: refresh"
+ f.puts "refresh_to_post_id: /#{time.strftime("%Y/%m/%d/") + slug}"
+ f.puts "---"
+ end
end
end
end
View
49 lib/jekyll/migrators/enki.rb
@@ -0,0 +1,49 @@
+# Adapted by Rodrigo Pinto <rodrigopqn@gmail.com>
+# Based on typo.rb by Toby DiPasquale
+
+require 'fileutils'
+require 'rubygems'
+require 'sequel'
+
+module Jekyll
+ module Enki
+ SQL = <<-EOS
+ SELECT p.id,
+ p.title,
+ p.slug,
+ p.body,
+ p.published_at as date,
+ p.cached_tag_list as tags
+ FROM posts p
+ EOS
+
+ # Just working with postgres, but can be easily adapted
+ # to work with both mysql and postgres.
+ def self.process(dbname, user, pass, host = 'localhost')
+ FileUtils.mkdir_p('_posts')
+ db = Sequel.postgres(:database => dbname,
+ :user => user,
+ :password => pass,
+ :host => host,
+ :encoding => 'utf8')
+
+ db[SQL].each do |post|
+ name = [ sprintf("%.04d", post[:date].year),
+ sprintf("%.02d", post[:date].month),
+ sprintf("%.02d", post[:date].day),
+ post[:slug].strip ].join('-')
+ name += '.textile'
+
+ File.open("_posts/#{name}", 'w') do |f|
+ f.puts({ 'layout' => 'post',
+ 'title' => post[:title].to_s,
+ 'enki_id' => post[:id],
+ 'categories' => post[:tags]
+ }.delete_if { |k, v| v.nil? || v == '' }.to_yaml)
+ f.puts '---'
+ f.puts post[:body].delete("\r")
+ end
+ end
+ end
+ end
+end
View
53 lib/jekyll/migrators/joomla.rb
@@ -0,0 +1,53 @@
+require 'rubygems'
+require 'sequel'
+require 'fileutils'
+require 'yaml'
+
+# NOTE: This migrator is made for Joomla 1.5 databases.
+# 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 Joomla
+ def self.process(dbname, user, pass, host = 'localhost', table_prefix = 'jos_', section = '1')
+ db = Sequel.mysql(dbname, :user => user, :password => pass, :host => host, :encoding => 'utf8')
+
+ FileUtils.mkdir_p("_posts")
+
+ # Reads a MySQL database via Sequel and creates a post file for each
+ # post in wp_posts that has post_status = 'publish'. This restriction is
+ # made because 'draft' posts are not guaranteed to have valid dates.
+ query = "SELECT `title`, `alias`, CONCAT(`introtext`,`fulltext`) as content, `created`, `id` FROM #{table_prefix}content WHERE state = '0' OR state = '1' AND sectionid = '#{section}'"
+
+ db[query].each do |post|
+ # Get required fields and construct Jekyll compatible name.
+ title = post[:title]
+ slug = post[:alias]
+ date = post[:created]
+ content = post[:content]
+ name = "%02d-%02d-%02d-%s.markdown" % [date.year, date.month, date.day,
+ slug]
+
+ # Get the relevant fields as a hash, delete empty fields and convert
+ # to YAML for the header.
+ data = {
+ 'layout' => 'post',
+ 'title' => title.to_s,
+ 'joomla_id' => post[:id],
+ 'joomla_url' => post[:alias],
+ 'date' => date
+ }.delete_if { |k,v| v.nil? || v == '' }.to_yaml
+
+ # Write out the data and content to file
+ File.open("_posts/#{name}", "w") do |f|
+ f.puts data
+ f.puts "---"
+ f.puts content
+ end
+ end
+ end
+ end
+end
View
1  lib/jekyll/migrators/marley.rb
@@ -3,7 +3,6 @@
module Jekyll
module Marley
-
def self.regexp
{ :id => /^\d{0,4}-{0,1}(.*)$/,
:title => /^#\s*(.*)\s+$/,
View
29 lib/jekyll/migrators/mephisto.rb
@@ -36,11 +36,22 @@ def self.postgres(c)
# 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 id, permalink, body, published_at, title FROM contents WHERE user_id = 1 AND type = 'Article' AND published_at IS NOT NULL ORDER BY published_at"
+ QUERY = "SELECT id, \
+ permalink, \
+ body, \
+ published_at, \
+ title \
+ FROM contents \
+ WHERE user_id = 1 AND \
+ type = 'Article' AND \
+ published_at IS NOT NULL \
+ ORDER BY published_at"
def self.process(dbname, user, pass, host = 'localhost')
- db = Sequel.mysql(dbname, :user => user, :password => pass, :host => host, :encoding => 'utf8')
+ db = Sequel.mysql(dbname, :user => user,
+ :password => pass,
+ :host => host,
+ :encoding => 'utf8')
FileUtils.mkdir_p "_posts"
@@ -49,16 +60,10 @@ def self.process(dbname, user, pass, host = 'localhost')
slug = post[:permalink]
date = post[:published_at]
content = post[:body]
-# more_content = ''
-
- # 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.
+ # 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 = {
View
43 lib/jekyll/migrators/mt.rb
@@ -18,7 +18,14 @@ 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_authored_on, entry_title, entry_convert_breaks FROM mt_entry"
+ QUERY = "SELECT entry_id, \
+ entry_basename, \
+ entry_text, \
+ entry_text_more, \
+ entry_authored_on, \
+ entry_title, \
+ entry_convert_breaks \
+ FROM mt_entry"
def self.process(dbname, user, pass, host = 'localhost')
db = Sequel.mysql(dbname, :user => user, :password => pass, :host => host, :encoding => 'utf8')
@@ -38,17 +45,18 @@ def self.process(dbname, user, pass, host = 'localhost')
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('-') + '.' + self.suffix(entry_convert_breaks)
+ # 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('-') + '.' +
+ self.suffix(entry_convert_breaks)
data = {
'layout' => 'post',
'title' => title.to_s,
'mt_id' => post[:entry_id],
'date' => date
- }.delete_if { |k,v| v.nil? || v == ''}.to_yaml
+ }.delete_if { |k,v| v.nil? || v == '' }.to_yaml
File.open("_posts/#{name}", "w") do |f|
f.puts data
@@ -60,17 +68,18 @@ def self.process(dbname, user, pass, host = 'localhost')
def self.suffix(entry_type)
if entry_type.nil? || entry_type.include?("markdown")
- # The markdown plugin I have saves this as "markdown_with_smarty_pants", so I just look for "markdown".
- "markdown"
- elsif entry_type.include?("textile")
- # This is saved as "textile_2" on my installation of MT 5.1.
- "textile"
- elsif entry_type == "0" || entry_type.include?("richtext")
- # richtext looks to me like it's saved as HTML, so I include it here.
- "html"
- else
- # Other values might need custom work.
- entry_type
+ # The markdown plugin I have saves this as
+ # "markdown_with_smarty_pants", so I just look for "markdown".
+ "markdown"
+ elsif entry_type.include?("textile")
+ # This is saved as "textile_2" on my installation of MT 5.1.
+ "textile"
+ elsif entry_type == "0" || entry_type.include?("richtext")
+ # Richtext looks to me like it's saved as HTML, so I include it here.
+ "html"
+ else
+ # Other values might need custom work.
+ entry_type
end
end
end
View
67 lib/jekyll/migrators/posterous.rb
@@ -0,0 +1,67 @@
+require 'rubygems'
+require 'jekyll'
+require 'fileutils'
+require 'net/http'
+require 'uri'
+require "json"
+
+# ruby -r './lib/jekyll/migrators/posterous.rb' -e 'Jekyll::Posterous.process(email, pass, api_key, blog)'
+
+module Jekyll
+ module Posterous
+ def self.fetch(uri_str, limit = 10)
+ # You should choose better exception.
+ raise ArgumentError, 'Stuck in a redirect loop. Please double check your email and password' if limit == 0
+
+ response = nil
+ Net::HTTP.start('posterous.com') do |http|
+ req = Net::HTTP::Get.new(uri_str)
+ req.basic_auth @email, @pass
+ response = http.request(req)
+ end
+
+ case response
+ when Net::HTTPSuccess then response
+ when Net::HTTPRedirection then fetch(response['location'], limit - 1)
+ else response.error!
+ end
+ end
+
+ def self.process(email, pass, api_token, blog = 'primary')
+ @email, @pass, @api_token = email, pass, api_token
+ FileUtils.mkdir_p "_posts"
+
+ posts = JSON.parse(self.fetch("/api/v2/users/me/sites/#{blog}/posts?api_token=#{@api_token}").body)
+ page = 1
+
+ while posts.any?
+ posts.each do |post|
+ title = post["title"]
+ slug = title.gsub(/[^[:alnum:]]+/, '-').downcase
+ date = Date.parse(post["display_date"])
+ content = post["body_html"]
+ published = !post["is_private"]
+ name = "%02d-%02d-%02d-%s.html" % [date.year, date.month, date.day, slug]
+
+ # Get the relevant fields as a hash, delete empty fields and convert
+ # to YAML for the header
+ data = {
+ 'layout' => 'post',
+ 'title' => title.to_s,
+ 'published' => published
+ }.delete_if { |k,v| v.nil? || v == ''}.to_yaml
+
+ # Write out the data and content to file
+ File.open("_posts/#{name}", "w") do |f|
+ f.puts data
+ f.puts "---"
+ f.puts content
+ end
+ end
+
+ page += 1
+ posts = JSON.parse(self.fetch("/api/v2/users/me/sites/#{blog}/posts?api_token=#{@api_token}&page=#{page}").body)
+ end
+ end
+ end
+end
View
47 lib/jekyll/migrators/rss.rb
@@ -0,0 +1,47 @@
+# Created by Kendall Buchanan (https://github.com/kendagriff) on 2011-12-22.
+# Use at your own risk. The end.
+#
+# Usage:
+# (URL)
+# ruby -r '_import/rss.rb' -e "Jekyll::MigrateRSS.process('http://yourdomain.com/your-favorite-feed.xml')"
+#
+# (Local file)
+# ruby -r '_import/rss.rb' -e "Jekyll::MigrateRSS.process('./somefile/on/your/computer.xml')"
+
+require 'rubygems'
+require 'rss/1.0'
+require 'rss/2.0'
+require 'open-uri'
+require 'fileutils'
+require 'yaml'
+
+module Jekyll
+ module MigrateRSS
+
+ # The `source` argument may be a URL or a local file.
+ def self.process(source)
+ content = ""
+ open(source) { |s| content = s.read }
+ rss = RSS::Parser.parse(content, false)
+
+ raise "There doesn't appear to be any RSS items at the source (#{source}) provided." unless rss
+
+ rss.items.each do |item|
+ formatted_date = item.date.strftime('%Y-%m-%d')
+ post_name = item.title.split(%r{ |!|/|:|&|-|$|,}).map { |i| i.downcase if i != '' }.compact.join('-')
+ name = "#{formatted_date}-#{post_name}"
+
+ header = {
+ 'layout' => 'post',
+ 'title' => item.title
+ }
+
+ File.open("_posts/#{name}.html", "w") do |f|
+ f.puts header.to_yaml
+ f.puts "---\n"
+ f.puts item.description
+ end
+ end
+ end
+ end
+end
View
24 lib/jekyll/migrators/textpattern.rb
@@ -1,6 +1,7 @@
require 'rubygems'
require 'sequel'
require 'fileutils'
+require 'yaml'
# NOTE: This converter requires Sequel and the MySQL gems.
# The MySQL gem can be difficult to install on OS X. Once you have MySQL
@@ -11,10 +12,17 @@
module Jekyll
module TextPattern
# Reads a MySQL database via Sequel and creates a post file for each post.
- # The only posts selected are those with a status of 4 or 5, which means "live"
- # and "sticky" respectively.
- # Other statuses is 1 => draft, 2 => hidden and 3 => pending
- QUERY = "select Title, url_title, Posted, Body, Keywords from textpattern where Status = '4' or Status = '5'"
+ # The only posts selected are those with a status of 4 or 5, which means
+ # "live" and "sticky" respectively.
+ # Other statuses are 1 => draft, 2 => hidden and 3 => pending.
+ QUERY = "SELECT Title, \
+ url_title, \
+ Posted, \
+ Body, \
+ Keywords \
+ FROM textpattern \
+ WHERE Status = '4' OR \
+ Status = '5'"
def self.process(dbname, user, pass, host = 'localhost')
db = Sequel.mysql(dbname, :user => user, :password => pass, :host => host, :encoding => 'utf8')
@@ -22,7 +30,7 @@ def self.process(dbname, user, pass, host = 'localhost')
FileUtils.mkdir_p "_posts"
db[QUERY].each do |post|
- # Get required fields and construct Jekyll compatible name
+ # Get required fields and construct Jekyll compatible name.
title = post[:Title]
slug = post[:url_title]
date = post[:Posted]
@@ -31,14 +39,14 @@ def self.process(dbname, user, pass, host = 'localhost')
name = [date.strftime("%Y-%m-%d"), slug].join('-') + ".textile"
# Get the relevant fields as a hash, delete empty fields and convert
- # to YAML for the header
+ # to YAML for the header.
data = {
'layout' => 'post',
'title' => title.to_s,
'tags' => post[:Keywords].split(',')
}.delete_if { |k,v| v.nil? || v == ''}.to_yaml
- # Write out the data and content to file
+ # Write out the data and content to file.
File.open("_posts/#{name}", "w") do |f|
f.puts data
f.puts "---"
@@ -47,4 +55,4 @@ def self.process(dbname, user, pass, host = 'localhost')
end
end
end
-end
+end
View
195 lib/jekyll/migrators/tumblr.rb
@@ -0,0 +1,195 @@
+require 'rubygems'
+require 'open-uri'
+require 'fileutils'
+require 'nokogiri'
+require 'date'
+require 'json'
+require 'uri'
+require 'jekyll'
+
+module Jekyll
+ module Tumblr
+ def self.process(url, format = "html", grab_images = false,
+ add_highlights = false, rewrite_urls = true)
+ @grab_images = grab_images
+ FileUtils.mkdir_p "_posts/tumblr"
+ url += "/api/read/json/"
+ per_page = 50
+ posts = []
+ # Two passes are required so that we can rewrite URLs.
+ # First pass builds up an array of each post as a hash.
+ begin
+ current_page = (current_page || -1) + 1
+ feed = open(url + "?num=#{per_page}&start=#{current_page * per_page}")
+ json = feed.readlines.join("\n")[21...-2] # Strip Tumblr's JSONP chars.
+ blog = JSON.parse(json)
+ puts "Page: #{current_page + 1} - Posts: #{blog["posts"].size}"
+ posts += blog["posts"].map { |post| post_to_hash(post, format) }
+ end until blog["posts"].size < per_page
+ # Rewrite URLs and create redirects.
+ posts = rewrite_urls_and_redirects posts if rewrite_urls
+ # Second pass for writing post files.
+ posts.each do |post|
+ if format == "md"
+ post[:content] = html_to_markdown post[:content]
+ post[:content] = add_syntax_highlights post[:content] if add_highlights
+ end
+ File.open("_posts/tumblr/#{post[:name]}", "w") do |f|
+ f.puts post[:header].to_yaml + "---\n" + post[:content]
+ end
+ end
+ end
+
+ private
+
+ # Converts each type of Tumblr post to a hash with all required
+ # data for Jekyll.
+ def self.post_to_hash(post, format)
+ case post['type']
+ when "regular"
+ title = post["regular-title"]
+ content = post["regular-body"]
+ when "link"
+ title = post["link-text"] || post["link-url"]
+ content = "<a href=\"#{post["link-url"]}\">#{title}</a>"
+ unless post["link-description"].nil?
+ content << "<br/>" + post["link-description"]
+ end
+ when "photo"
+ title = post["photo-caption"]
+ max_size = post.keys.map{ |k| k.gsub("photo-url-", "").to_i }.max
+ url = post["photo-url"] || post["photo-url-#{max_size}"]
+ ext = "." + post[post.keys.select { |k|
+ k =~ /^photo-url-/ && post[k].split("/").last =~ /\./
+ }.first].split(".").last
+ content = "<img src=\"#{save_file(url, ext)}\"/>"
+ unless post["photo-link-url"].nil?
+ content = "<a href=\"#{post["photo-link-url"]}\">#{content}</a>"
+ end
+ when "audio"
+ if !post["id3-title"].nil?
+ title = post["id3-title"]
+ content = post.at["audio-player"] + "<br/>" + post["audio-caption"]
+ else
+ title = post["audio-caption"]
+ content = post.at["audio-player"]
+ end
+ when "quote"
+ title = post["quote-text"]
+ content = "<blockquote>#{post["quote-text"]}</blockquote>"
+ unless post["quote-source"].nil?
+ content << "&#8212;" + post["quote-source"]
+ end
+ when "conversation"
+ title = post["conversation-title"]
+ content = "<section><dialog>"
+ post["conversation"]["line"].each do |line|
+ content << "<dt>#{line['label']}</dt><dd>#{line}</dd>"
+ end
+ content << "</section></dialog>"
+ when "video"
+ title = post["video-title"]
+ content = post["video-player"]
+ unless post["video-caption"].nil?
+ content << "<br/>" + post["video-caption"]
+ end
+ end
+ date = Date.parse(post['date']).to_s
+ title = Nokogiri::HTML(title).text
+ slug = title.downcase.strip.gsub(' ', '-').gsub(/[^\w-]/, '')
+ {
+ :name => "#{date}-#{slug}.#{format}",
+ :header => {
+ "layout" => "post",
+ "title" => title,
+ "tags" => post["tags"],
+ },
+ :content => content,
+ :url => post["url"],
+ :slug => post["url-with-slug"],
+ }
+ end
+
+ # Create a Hash of old urls => new urls, for rewriting and
+ # redirects, and replace urls in each post. Instantiate Jekyll
+ # site/posts to get the correct permalink format.
+ def self.rewrite_urls_and_redirects(posts)
+ site = Jekyll::Site.new(Jekyll.configuration({}))
+ dir = File.join(File.dirname(__FILE__), "..")
+ urls = Hash[posts.map { |post|
+ # Create an initial empty file for the post so that
+ # we can instantiate a post object.
+ File.open("_posts/tumblr/#{post[:name]}", "w")
+ tumblr_url = URI.parse(post[:slug]).path
+ jekyll_url = Jekyll::Post.new(site, dir, "", "tumblr/" + post[:name]).url
+ redirect_dir = tumblr_url.sub(/\//, "") + "/"
+ FileUtils.mkdir_p redirect_dir
+ File.open(redirect_dir + "index.html", "w") do |f|
+ f.puts "<html><head><meta http-equiv='Refresh' content='0; " +
+ "url=#{jekyll_url}'></head><body></body></html>"
+ end
+ [tumblr_url, jekyll_url]
+ }]
+ posts.map { |post|
+ urls.each do |tumblr_url, jekyll_url|
+ post[:content].gsub!(/#{tumblr_url}/i, jekyll_url)
+ end
+ post
+ }
+ end
+
+ # Uses Python's html2text to convert a post's content to
+ # markdown. Preserve HTML tables as per the markdown docs.
+ def self.html_to_markdown(content)
+ preserve = ["table", "tr", "th", "td"]
+ preserve.each do |tag|
+ content.gsub!(/<#{tag}/i, "$$" + tag)
+ content.gsub!(/<\/#{tag}/i, "||" + tag)
+ end
+ content = %x[echo '#{content.gsub("'", "''")}' | html2text]
+ preserve.each do |tag|
+ content.gsub!("$$" + tag, "<" + tag)
+ content.gsub!("||" + tag, "</" + tag)
+ end
+ content
+ end
+
+ # Adds pygments highlight tags to code blocks in posts that use
+ # markdown format. This doesn't guess the language of the code
+ # block, so you should modify this to suit your own content.
+ # For example, my code block only contain Python and JavaScript,
+ # so I can assume the block is JavaScript if it contains a
+ # semi-colon.
+ def self.add_syntax_highlights(content)
+ lines = content.split("\n")
+ block, indent, lang, start = false, /^ /, nil, nil
+ lines.each_with_index do |line, i|
+ if !block && line =~ indent
+ block = true
+ lang = "python"
+ start = i
+ elsif block
+ lang = "javascript" if line =~ /;$/
+ block = line =~ indent && i < lines.size - 1 # Also handle EOF
+ if !block
+ lines[start] = "{% highlight #{lang} %}"
+ lines[i - 1] = "{% endhighlight %}"
+ end
+ lines[i] = lines[i].sub(indent, "")
+ end
+ end
+ lines.join("\n")
+ end
+
+ def self.save_file(url, ext)
+ if @grab_images
+ path = "tumblr_files/#{url.split('/').last}"
+ path += ext unless path =~ /#{ext}$/
+ FileUtils.mkdir_p "tumblr_files"
+ File.open(path, "w") { |f| f.write(open(url).read) }
+ url = "/" + path
+ end
+ url
+ end
+ end
+end
View
14 lib/jekyll/migrators/typo.rb
@@ -2,11 +2,12 @@
require 'fileutils'
require 'rubygems'
require 'sequel'
+require 'yaml'
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)
+ # 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,
@@ -24,14 +25,15 @@ def self.process dbname, user, pass, host='localhost'
FileUtils.mkdir_p '_posts'
db = Sequel.mysql(dbname, :user => user, :password => pass, :host => host, :encoding => 'utf8')
db[SQL].each do |post|
- next unless post[:state] =~ /Published/
+ 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
+ # the first one for this.
name += '.' + post[:filter].split(' ')[0]
File.open("_posts/#{name}", 'w') do |f|
@@ -45,5 +47,5 @@ def self.process dbname, user, pass, host='localhost'
end
end
- end # module Typo
-end # module Jekyll
+ end
+end
View
38 lib/jekyll/migrators/wordpress.com.rb
@@ -1,38 +0,0 @@
-require 'rubygems'
-require 'hpricot'
-require 'fileutils'
-
-# This importer takes a wordpress.xml file,
-# which can be exported from your
-# wordpress.com blog (/wp-admin/export.php)
-
-module Jekyll
- module WordpressDotCom
- def self.process(filename = "wordpress.xml")
- FileUtils.mkdir_p "_posts"
- posts = 0
-
- doc = Hpricot::XML(File.read(filename))
-
- (doc/:channel/:item).each do |item|
- title = item.at(:title).inner_text
- name = "#{Date.parse((doc/:channel/:item).first.at(:pubDate).inner_text).to_s("%Y-%m-%d")}-#{title.downcase.gsub('[^a-z0-9]', '-')}.html"
-
- File.open("_posts/#{name}", "w") do |f|
- f.puts <<-HEADER
----
-layout: post
-title: #{title}
----
-
-HEADER
- f.puts item.at('content:encoded').inner_text
- end
-
- posts += 1
- end
-
- "Imported #{posts} posts"
- end
- end
-end
View
310 lib/jekyll/migrators/wordpress.rb
@@ -12,45 +12,283 @@
module Jekyll
module WordPress
- # Reads a MySQL database via Sequel and creates a post file for each
- # post in wp_posts that has post_status = 'publish'.
- # This restriction is made because 'draft' posts are not guaranteed to
- # have valid dates.
- QUERY = "select post_title, post_name, post_date, post_content, post_excerpt, ID, guid from wp_posts where post_status = 'publish' and post_type = 'post'"
-
- def self.process(dbname, user, pass, host = 'localhost')
- db = Sequel.mysql(dbname, :user => user, :password => pass, :host => host, :encoding => 'utf8')
-
- FileUtils.mkdir_p "_posts"
-
- db[QUERY].each do |post|
- # Get required fields and construct Jekyll compatible name
- title = post[:post_title]
- slug = post[:post_name]
- date = post[:post_date]
- content = post[:post_content]
- name = "%02d-%02d-%02d-%s.markdown" % [date.year, date.month, date.day,
- slug]
-
- # Get the relevant fields as a hash, delete empty fields and convert
- # to YAML for the header
- data = {
- 'layout' => 'post',
- 'title' => title.to_s,
- 'excerpt' => post[:post_excerpt].to_s,
- 'wordpress_id' => post[:ID],
- 'wordpress_url' => post[:guid],
- 'date' => date
- }.delete_if { |k,v| v.nil? || v == ''}.to_yaml
-
- # Write out the data and content to file
- File.open("_posts/#{name}", "w") do |f|
- f.puts data
- f.puts "---"
- f.puts content
+ # Main migrator function. Call this to perform the migration.
+ #
+ # dbname:: The name of the database
+ # user:: The database user name
+ # pass:: The database user's password
+ # host:: The address of the MySQL database host. Default: 'localhost'
+ # options:: A hash table of configuration options.
+ #
+ # Supported options are:
+ #
+ # :table_prefix:: Prefix of database tables used by WordPress.
+ # Default: 'wp_'
+ # :clean_entities:: If true, convert non-ASCII characters to HTML
+ # entities in the posts, comments, titles, and
+ # names. Requires the 'htmlentities' gem to
+ # work. Default: true.
+ # :comments:: If true, migrate post comments too. Comments
+ # are saved in the post's YAML front matter.
+ # Default: true.
+ # :categories:: If true, save the post's categories in its
+ # YAML front matter.
+ # :tags:: If true, save the post's tags in its
+ # YAML front matter.
+ # :more_excerpt:: If true, when a post has no excerpt but
+ # does have a <!-- more --> tag, use the
+ # preceding post content as the excerpt.
+ # Default: true.
+ # :more_anchor:: If true, convert a <!-- more --> tag into
+ # two HTML anchors with ids "more" and
+ # "more-NNN" (where NNN is the post number).
+ # Default: true.
+ # :status:: Array of allowed post statuses. Only
+ # posts with matching status will be migrated.
+ # Known statuses are :publish, :draft, :private,
+ # and :revision. If this is nil or an empty
+ # array, all posts are migrated regardless of
+ # status. Default: [:publish].
+ #
+ def self.process(dbname, user, pass, host='localhost', options={})
+ options = {
+ :table_prefix => 'wp_',
+ :clean_entities => true,
+ :comments => true,
+ :categories => true,
+ :tags => true,
+ :more_excerpt => true,
+ :more_anchor => true,
+ :status => [:publish] # :draft, :private, :revision
+ }.merge(options)
+
+ if options[:clean_entities]
+ begin
+ require 'htmlentities'
+ rescue LoadError
+ STDERR.puts "Could not require 'htmlentities', so the " +
+ ":clean_entities option is now disabled."
+ options[:clean_entities] = false
+ end
+ end
+
+ FileUtils.mkdir_p("_posts")
+
+ db = Sequel.mysql(dbname, :user => user, :password => pass,
+ :host => host, :encoding => 'utf8')
+
+ px = options[:table_prefix]
+
+ posts_query = "
+ SELECT
+ posts.ID AS `id`,
+ posts.guid AS `guid`,
+ posts.post_type AS `type`,
+ posts.post_status AS `status`,
+ posts.post_title AS `title`,
+ posts.post_name AS `slug`,
+ posts.post_date AS `date`,
+ posts.post_content AS `content`,
+ posts.post_excerpt AS `excerpt`,
+ posts.comment_count AS `comment_count`,
+ users.display_name AS `author`,
+ users.user_login AS `author_login`,
+ users.user_email AS `author_email`,
+ users.user_url AS `author_url`
+ FROM #{px}posts AS `posts`
+ LEFT JOIN #{px}users AS `users`
+ ON posts.post_author = users.ID"
+
+ if options[:status] and not options[:status].empty?
+ status = options[:status][0]
+ posts_query << "
+ WHERE posts.post_status = '#{status.to_s}'"
+ options[:status][1..-1].each do |status|
+ posts_query << " OR
+ posts.post_status = '#{status.to_s}'"
+ end
+ end
+
+ db[posts_query].each do |post|
+ process_post(post, db, options)
+ end
+ end
+
+
+ def self.process_post(post, db, options)
+ px = options[:table_prefix]
+
+ title = post[:title]
+ if options[:clean_entities]
+ title = clean_entities(title)
+ end
+
+ slug = post[:slug]
+ if !slug or slug.empty?
+ slug = sluggify(title)
+ end
+
+ date = post[:date] || Time.now
+ name = "%02d-%02d-%02d-%s.markdown" % [date.year, date.month,
+ date.day, slug]
+ content = post[:content].to_s
+ if options[:clean_entities]
+ content = clean_entities(content)
+ end
+
+ excerpt = post[:excerpt].to_s
+
+ more_index = content.index(/<!-- *more *-->/)
+ more_anchor = nil
+ if more_index
+ if options[:more_excerpt] and
+ (post[:excerpt].nil? or post[:excerpt].empty?)
+ excerpt = content[0...more_index]
+ end
+ if options[:more_anchor]
+ more_link = "more"
+ content.sub!(/<!-- *more *-->/,
+ "<a id=\"more\"></a>" +
+ "<a id=\"more-#{post[:id]}\"></a>")
+ end
+ end
+
+ categories = []
+ tags = []
+
+ if options[:categories] or options[:tags]
+
+ cquery =
+ "SELECT
+ terms.name AS `name`,
+ ttax.taxonomy AS `type`
+ FROM
+ #{px}terms AS `terms`,
+ #{px}term_relationships AS `trels`,
+ #{px}term_taxonomy AS `ttax`
+ WHERE
+ trels.object_id = '#{post[:id]}' AND
+ trels.term_taxonomy_id = ttax.term_taxonomy_id AND
+ terms.term_id = ttax.term_id"
+
+ db[cquery].each do |term|
+ if options[:categories] and term[:type] == "category"
+ if options[:clean_entities]
+ categories << clean_entities(term[:name])
+ else
+ categories << term[:name]
+ end
+ elsif options[:tags] and term[:type] == "post_tag"
+ if options[:clean_entities]
+ tags << clean_entities(term[:name])
+ else
+ tags << term[:name]
+ end
+ end
end
end
+ comments = []
+
+ if options[:comments] and post[:comment_count].to_i > 0
+ cquery =
+ "SELECT
+ comment_ID AS `id`,
+ comment_author AS `author`,
+ comment_author_email AS `author_email`,
+ comment_author_url AS `author_url`,
+ comment_date AS `date`,
+ comment_date_gmt AS `date_gmt`,
+ comment_content AS `content`
+ FROM #{px}comments
+ WHERE
+ comment_post_ID = '#{post[:id]}' AND
+ comment_approved != 'spam'"
+
+
+ db[cquery].each do |comment|
+
+ comcontent = comment[:content].to_s
+ if comcontent.respond_to?(:force_encoding)
+ comcontent.force_encoding("UTF-8")
+ end
+ if options[:clean_entities]
+ comcontent = clean_entities(comcontent)
+ end
+ comauthor = comment[:author].to_s
+ if options[:clean_entities]
+ comauthor = clean_entities(comauthor)
+ end
+
+ comments << {
+ 'id' => comment[:id].to_i,
+ 'author' => comauthor,
+ 'author_email' => comment[:author_email].to_s,
+ 'author_url' => comment[:author_url].to_s,
+ 'date' => comment[:date].to_s,
+ 'date_gmt' => comment[:date_gmt].to_s,
+ 'content' => comcontent,
+ }
+ end
+
+ comments.sort!{ |a,b| a['id'] <=> b['id'] }
+ end
+
+ # Get the relevant fields as a hash, delete empty fields and
+ # convert to YAML for the header.
+ data = {
+ 'layout' => post[:type].to_s,
+ 'status' => post[:status].to_s,
+ 'published' => (post[:status].to_s == "publish"),
+ 'title' => title.to_s,
+ 'author' => post[:author].to_s,
+ 'author_login' => post[:author_login].to_s,
+ 'author_email' => post[:author_email].to_s,
+ 'author_url' => post[:author_url].to_s,
+ 'excerpt' => excerpt,
+ 'more_anchor' => more_anchor,
+ 'wordpress_id' => post[:id],
+ 'wordpress_url' => post[:guid].to_s,