jekyll-reloaded 0.12

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (109) hide show
  1. data/Gemfile +2 -0
  2. data/History.txt +321 -0
  3. data/LICENSE +21 -0
  4. data/README.textile +41 -0
  5. data/Rakefile +161 -0
  6. data/bin/jekyll +289 -0
  7. data/cucumber.yml +1 -0
  8. data/features/create_sites.feature +112 -0
  9. data/features/embed_filters.feature +60 -0
  10. data/features/markdown.feature +30 -0
  11. data/features/pagination.feature +27 -0
  12. data/features/permalinks.feature +65 -0
  13. data/features/post_data.feature +153 -0
  14. data/features/site_configuration.feature +145 -0
  15. data/features/site_data.feature +82 -0
  16. data/features/step_definitions/jekyll_steps.rb +145 -0
  17. data/features/support/env.rb +19 -0
  18. data/jekyll.gemspec +146 -0
  19. data/lib/guard/jekyll.rb +57 -0
  20. data/lib/jekyll/converter.rb +50 -0
  21. data/lib/jekyll/converters/identity.rb +22 -0
  22. data/lib/jekyll/converters/markdown.rb +125 -0
  23. data/lib/jekyll/converters/textile.rb +50 -0
  24. data/lib/jekyll/convertible.rb +116 -0
  25. data/lib/jekyll/core_ext.rb +52 -0
  26. data/lib/jekyll/errors.rb +6 -0
  27. data/lib/jekyll/filters.rb +118 -0
  28. data/lib/jekyll/generator.rb +7 -0
  29. data/lib/jekyll/generators/pagination.rb +113 -0
  30. data/lib/jekyll/layout.rb +51 -0
  31. data/lib/jekyll/live_site.rb +216 -0
  32. data/lib/jekyll/migrators/csv.rb +26 -0
  33. data/lib/jekyll/migrators/drupal.rb +103 -0
  34. data/lib/jekyll/migrators/enki.rb +49 -0
  35. data/lib/jekyll/migrators/joomla.rb +53 -0
  36. data/lib/jekyll/migrators/marley.rb +52 -0
  37. data/lib/jekyll/migrators/mephisto.rb +84 -0
  38. data/lib/jekyll/migrators/mt.rb +86 -0
  39. data/lib/jekyll/migrators/posterous.rb +67 -0
  40. data/lib/jekyll/migrators/rss.rb +47 -0
  41. data/lib/jekyll/migrators/textpattern.rb +58 -0
  42. data/lib/jekyll/migrators/tumblr.rb +195 -0
  43. data/lib/jekyll/migrators/typo.rb +51 -0
  44. data/lib/jekyll/migrators/wordpress.rb +294 -0
  45. data/lib/jekyll/migrators/wordpressdotcom.rb +70 -0
  46. data/lib/jekyll/page.rb +160 -0
  47. data/lib/jekyll/plugin.rb +77 -0
  48. data/lib/jekyll/post.rb +262 -0
  49. data/lib/jekyll/site.rb +339 -0
  50. data/lib/jekyll/static_file.rb +77 -0
  51. data/lib/jekyll/tags/highlight.rb +118 -0
  52. data/lib/jekyll/tags/include.rb +37 -0
  53. data/lib/jekyll/tags/post_url.rb +38 -0
  54. data/lib/jekyll.rb +134 -0
  55. data/test/helper.rb +34 -0
  56. data/test/source/.htaccess +8 -0
  57. data/test/source/_includes/sig.markdown +3 -0
  58. data/test/source/_layouts/default.html +27 -0
  59. data/test/source/_layouts/simple.html +1 -0
  60. data/test/source/_posts/2008-02-02-not-published.textile +8 -0
  61. data/test/source/_posts/2008-02-02-published.textile +8 -0
  62. data/test/source/_posts/2008-10-18-foo-bar.textile +8 -0
  63. data/test/source/_posts/2008-11-21-complex.textile +8 -0
  64. data/test/source/_posts/2008-12-03-permalinked-post.textile +9 -0
  65. data/test/source/_posts/2008-12-13-include.markdown +8 -0
  66. data/test/source/_posts/2009-01-27-array-categories.textile +10 -0
  67. data/test/source/_posts/2009-01-27-categories.textile +7 -0
  68. data/test/source/_posts/2009-01-27-category.textile +7 -0
  69. data/test/source/_posts/2009-01-27-empty-categories.textile +7 -0
  70. data/test/source/_posts/2009-01-27-empty-category.textile +7 -0
  71. data/test/source/_posts/2009-03-12-hash-#1.markdown +6 -0
  72. data/test/source/_posts/2009-05-18-empty-tag.textile +6 -0
  73. data/test/source/_posts/2009-05-18-empty-tags.textile +6 -0
  74. data/test/source/_posts/2009-05-18-tag.textile +6 -0
  75. data/test/source/_posts/2009-05-18-tags.textile +9 -0
  76. data/test/source/_posts/2009-06-22-empty-yaml.textile +3 -0
  77. data/test/source/_posts/2009-06-22-no-yaml.textile +1 -0
  78. data/test/source/_posts/2010-01-08-triple-dash.markdown +5 -0
  79. data/test/source/_posts/2010-01-09-date-override.textile +7 -0
  80. data/test/source/_posts/2010-01-09-time-override.textile +7 -0
  81. data/test/source/_posts/2010-01-09-timezone-override.textile +7 -0
  82. data/test/source/_posts/2010-01-16-override-data.textile +4 -0
  83. data/test/source/_posts/2011-04-12-md-extension.md +7 -0
  84. data/test/source/_posts/2011-04-12-text-extension.text +0 -0
  85. data/test/source/about.html +6 -0
  86. data/test/source/category/_posts/2008-9-23-categories.textile +6 -0
  87. data/test/source/contacts.html +5 -0
  88. data/test/source/css/screen.css +76 -0
  89. data/test/source/deal.with.dots.html +7 -0
  90. data/test/source/foo/_posts/bar/2008-12-12-topical-post.textile +8 -0
  91. data/test/source/index.html +22 -0
  92. data/test/source/sitemap.xml +32 -0
  93. data/test/source/win/_posts/2009-05-24-yaml-linebreak.markdown +7 -0
  94. data/test/source/z_category/_posts/2008-9-23-categories.textile +6 -0
  95. data/test/suite.rb +11 -0
  96. data/test/test_configuration.rb +29 -0
  97. data/test/test_core_ext.rb +66 -0
  98. data/test/test_filters.rb +62 -0
  99. data/test/test_generated_site.rb +72 -0
  100. data/test/test_kramdown.rb +23 -0
  101. data/test/test_page.rb +117 -0
  102. data/test/test_pager.rb +113 -0
  103. data/test/test_post.rb +450 -0
  104. data/test/test_rdiscount.rb +18 -0
  105. data/test/test_redcarpet.rb +21 -0
  106. data/test/test_redcloth.rb +86 -0
  107. data/test/test_site.rb +220 -0
  108. data/test/test_tags.rb +201 -0
  109. metadata +332 -0
@@ -0,0 +1,53 @@
1
+ require 'rubygems'
2
+ require 'sequel'
3
+ require 'fileutils'
4
+ require 'yaml'
5
+
6
+ # NOTE: This migrator is made for Joomla 1.5 databases.
7
+ # NOTE: This converter requires Sequel and the MySQL gems.
8
+ # The MySQL gem can be difficult to install on OS X. Once you have MySQL
9
+ # installed, running the following commands should work:
10
+ # $ sudo gem install sequel
11
+ # $ sudo gem install mysql -- --with-mysql-config=/usr/local/mysql/bin/mysql_config
12
+
13
+ module Jekyll
14
+ module Joomla
15
+ def self.process(dbname, user, pass, host = 'localhost', table_prefix = 'jos_', section = '1')
16
+ db = Sequel.mysql(dbname, :user => user, :password => pass, :host => host, :encoding => 'utf8')
17
+
18
+ FileUtils.mkdir_p("_posts")
19
+
20
+ # Reads a MySQL database via Sequel and creates a post file for each
21
+ # post in wp_posts that has post_status = 'publish'. This restriction is
22
+ # made because 'draft' posts are not guaranteed to have valid dates.
23
+ query = "SELECT `title`, `alias`, CONCAT(`introtext`,`fulltext`) as content, `created`, `id` FROM #{table_prefix}content WHERE state = '0' OR state = '1' AND sectionid = '#{section}'"
24
+
25
+ db[query].each do |post|
26
+ # Get required fields and construct Jekyll compatible name.
27
+ title = post[:title]
28
+ slug = post[:alias]
29
+ date = post[:created]
30
+ content = post[:content]
31
+ name = "%02d-%02d-%02d-%s.markdown" % [date.year, date.month, date.day,
32
+ slug]
33
+
34
+ # Get the relevant fields as a hash, delete empty fields and convert
35
+ # to YAML for the header.
36
+ data = {
37
+ 'layout' => 'post',
38
+ 'title' => title.to_s,
39
+ 'joomla_id' => post[:id],
40
+ 'joomla_url' => post[:alias],
41
+ 'date' => date
42
+ }.delete_if { |k,v| v.nil? || v == '' }.to_yaml
43
+
44
+ # Write out the data and content to file
45
+ File.open("_posts/#{name}", "w") do |f|
46
+ f.puts data
47
+ f.puts "---"
48
+ f.puts content
49
+ end
50
+ end
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,52 @@
1
+ require 'yaml'
2
+ require 'fileutils'
3
+
4
+ module Jekyll
5
+ module Marley
6
+ def self.regexp
7
+ { :id => /^\d{0,4}-{0,1}(.*)$/,
8
+ :title => /^#\s*(.*)\s+$/,
9
+ :title_with_date => /^#\s*(.*)\s+\(([0-9\/]+)\)$/,
10
+ :published_on => /.*\s+\(([0-9\/]+)\)$/,
11
+ :perex => /^([^\#\n]+\n)$/,
12
+ :meta => /^\{\{\n(.*)\}\}\n$/mi # Multiline Regexp
13
+ }
14
+ end
15
+
16
+ def self.process(marley_data_dir)
17
+ raise ArgumentError, "marley dir #{marley_data_dir} not found" unless File.directory?(marley_data_dir)
18
+
19
+ FileUtils.mkdir_p "_posts"
20
+
21
+ posts = 0
22
+ Dir["#{marley_data_dir}/**/*.txt"].each do |f|
23
+ next unless File.exists?(f)
24
+
25
+ #copied over from marley's app/lib/post.rb
26
+ file_content = File.read(f)
27
+ meta_content = file_content.slice!( self.regexp[:meta] )
28
+ body = file_content.sub( self.regexp[:title], '').sub( self.regexp[:perex], '').strip
29
+
30
+ title = file_content.scan( self.regexp[:title] ).first.to_s.strip
31
+ prerex = file_content.scan( self.regexp[:perex] ).first.to_s.strip
32
+ published_on = DateTime.parse( post[:published_on] ) rescue File.mtime( File.dirname(f) )
33
+ meta = ( meta_content ) ? YAML::load( meta_content.scan( self.regexp[:meta]).to_s ) : {}
34
+ meta['title'] = title
35
+ meta['layout'] = 'post'
36
+
37
+ formatted_date = published_on.strftime('%Y-%m-%d')
38
+ post_name = File.dirname(f).split(%r{/}).last.gsub(/\A\d+-/, '')
39
+
40
+ name = "#{formatted_date}-#{post_name}"
41
+ File.open("_posts/#{name}.markdown", "w") do |f|
42
+ f.puts meta.to_yaml
43
+ f.puts "---\n"
44
+ f.puts "\n#{prerex}\n\n" if prerex
45
+ f.puts body
46
+ end
47
+ posts += 1
48
+ end
49
+ "Created #{posts} posts!"
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,84 @@
1
+ # Quickly hacked together my Michael Ivey
2
+ # Based on mt.rb by Nick Gerakines, open source and publically
3
+ # available under the MIT license. Use this module at your own risk.
4
+
5
+ require 'rubygems'
6
+ require 'sequel'
7
+ require 'fastercsv'
8
+ require 'fileutils'
9
+ require File.join(File.dirname(__FILE__),"csv.rb")
10
+
11
+ # NOTE: This converter requires Sequel and the MySQL gems.
12
+ # The MySQL gem can be difficult to install on OS X. Once you have MySQL
13
+ # installed, running the following commands should work:
14
+ # $ sudo gem install sequel
15
+ # $ sudo gem install mysql -- --with-mysql-config=/usr/local/mysql/bin/mysql_config
16
+
17
+ module Jekyll
18
+ module Mephisto
19
+ #Accepts a hash with database config variables, exports mephisto posts into a csv
20
+ #export PGPASSWORD if you must
21
+ def self.postgres(c)
22
+ sql = <<-SQL
23
+ BEGIN;
24
+ CREATE TEMP TABLE jekyll AS
25
+ SELECT title, permalink, body, published_at, filter FROM contents
26
+ WHERE user_id = 1 AND type = 'Article' ORDER BY published_at;
27
+ COPY jekyll TO STDOUT WITH CSV HEADER;
28
+ ROLLBACK;
29
+ SQL
30
+ command = %Q(psql -h #{c[:host] || "localhost"} -c "#{sql.strip}" #{c[:database]} #{c[:username]} -o #{c[:filename] || "posts.csv"})
31
+ puts command
32
+ `#{command}`
33
+ CSV.process
34
+ end
35
+
36
+ # This query will pull blog posts from all entries across all blogs. If
37
+ # you've got unpublished, deleted or otherwise hidden posts please sift
38
+ # through the created posts to make sure nothing is accidently published.
39
+ QUERY = "SELECT id, \
40
+ permalink, \
41
+ body, \
42
+ published_at, \
43
+ title \
44
+ FROM contents \
45
+ WHERE user_id = 1 AND \
46
+ type = 'Article' AND \
47
+ published_at IS NOT NULL \
48
+ ORDER BY published_at"
49
+
50
+ def self.process(dbname, user, pass, host = 'localhost')
51
+ db = Sequel.mysql(dbname, :user => user,
52
+ :password => pass,
53
+ :host => host,
54
+ :encoding => 'utf8')
55
+
56
+ FileUtils.mkdir_p "_posts"
57
+
58
+ db[QUERY].each do |post|
59
+ title = post[:title]
60
+ slug = post[:permalink]
61
+ date = post[:published_at]
62
+ content = post[:body]
63
+
64
+ # Ideally, this script would determine the post format (markdown,
65
+ # html, etc) and create files with proper extensions. At this point
66
+ # it just assumes that markdown will be acceptable.
67
+ name = [date.year, date.month, date.day, slug].join('-') + ".markdown"
68
+
69
+ data = {
70
+ 'layout' => 'post',
71
+ 'title' => title.to_s,
72
+ 'mt_id' => post[:entry_id],
73
+ }.delete_if { |k,v| v.nil? || v == ''}.to_yaml
74
+
75
+ File.open("_posts/#{name}", "w") do |f|
76
+ f.puts data
77
+ f.puts "---"
78
+ f.puts content
79
+ end
80
+ end
81
+
82
+ end
83
+ end
84
+ end
@@ -0,0 +1,86 @@
1
+ # Created by Nick Gerakines, open source and publically available under the
2
+ # MIT license. Use this module at your own risk.
3
+ # I'm an Erlang/Perl/C++ guy so please forgive my dirty ruby.
4
+
5
+ require 'rubygems'
6
+ require 'sequel'
7
+ require 'fileutils'
8
+ require 'yaml'
9
+
10
+ # NOTE: This converter requires Sequel and the MySQL gems.
11
+ # The MySQL gem can be difficult to install on OS X. Once you have MySQL
12
+ # installed, running the following commands should work:
13
+ # $ sudo gem install sequel
14
+ # $ sudo gem install mysql -- --with-mysql-config=/usr/local/mysql/bin/mysql_config
15
+
16
+ module Jekyll
17
+ module MT
18
+ # This query will pull blog posts from all entries across all blogs. If
19
+ # you've got unpublished, deleted or otherwise hidden posts please sift
20
+ # through the created posts to make sure nothing is accidently published.
21
+ QUERY = "SELECT entry_id, \
22
+ entry_basename, \
23
+ entry_text, \
24
+ entry_text_more, \
25
+ entry_authored_on, \
26
+ entry_title, \
27
+ entry_convert_breaks \
28
+ FROM mt_entry"
29
+
30
+ def self.process(dbname, user, pass, host = 'localhost')
31
+ db = Sequel.mysql(dbname, :user => user, :password => pass, :host => host, :encoding => 'utf8')
32
+
33
+ FileUtils.mkdir_p "_posts"
34
+
35
+ db[QUERY].each do |post|
36
+ title = post[:entry_title]
37
+ slug = post[:entry_basename].gsub(/_/, '-')
38
+ date = post[:entry_authored_on]
39
+ content = post[:entry_text]
40
+ more_content = post[:entry_text_more]
41
+ entry_convert_breaks = post[:entry_convert_breaks]
42
+
43
+ # Be sure to include the body and extended body.
44
+ if more_content != nil
45
+ content = content + " \n" + more_content
46
+ end
47
+
48
+ # Ideally, this script would determine the post format (markdown,
49
+ # html, etc) and create files with proper extensions. At this point
50
+ # it just assumes that markdown will be acceptable.
51
+ name = [date.year, date.month, date.day, slug].join('-') + '.' +
52
+ self.suffix(entry_convert_breaks)
53
+
54
+ data = {
55
+ 'layout' => 'post',
56
+ 'title' => title.to_s,
57
+ 'mt_id' => post[:entry_id],
58
+ 'date' => date
59
+ }.delete_if { |k,v| v.nil? || v == '' }.to_yaml
60
+
61
+ File.open("_posts/#{name}", "w") do |f|
62
+ f.puts data
63
+ f.puts "---"
64
+ f.puts content
65
+ end
66
+ end
67
+ end
68
+
69
+ def self.suffix(entry_type)
70
+ if entry_type.nil? || entry_type.include?("markdown")
71
+ # The markdown plugin I have saves this as
72
+ # "markdown_with_smarty_pants", so I just look for "markdown".
73
+ "markdown"
74
+ elsif entry_type.include?("textile")
75
+ # This is saved as "textile_2" on my installation of MT 5.1.
76
+ "textile"
77
+ elsif entry_type == "0" || entry_type.include?("richtext")
78
+ # Richtext looks to me like it's saved as HTML, so I include it here.
79
+ "html"
80
+ else
81
+ # Other values might need custom work.
82
+ entry_type
83
+ end
84
+ end
85
+ end
86
+ end
@@ -0,0 +1,67 @@
1
+ require 'rubygems'
2
+ require 'jekyll'
3
+ require 'fileutils'
4
+ require 'net/http'
5
+ require 'uri'
6
+ require "json"
7
+
8
+ # ruby -r './lib/jekyll/migrators/posterous.rb' -e 'Jekyll::Posterous.process(email, pass, api_key, blog)'
9
+
10
+ module Jekyll
11
+ module Posterous
12
+ def self.fetch(uri_str, limit = 10)
13
+ # You should choose better exception.
14
+ raise ArgumentError, 'Stuck in a redirect loop. Please double check your email and password' if limit == 0
15
+
16
+ response = nil
17
+ Net::HTTP.start('posterous.com') do |http|
18
+ req = Net::HTTP::Get.new(uri_str)
19
+ req.basic_auth @email, @pass
20
+ response = http.request(req)
21
+ end
22
+
23
+ case response
24
+ when Net::HTTPSuccess then response
25
+ when Net::HTTPRedirection then fetch(response['location'], limit - 1)
26
+ else response.error!
27
+ end
28
+ end
29
+
30
+ def self.process(email, pass, api_token, blog = 'primary')
31
+ @email, @pass, @api_token = email, pass, api_token
32
+ FileUtils.mkdir_p "_posts"
33
+
34
+ posts = JSON.parse(self.fetch("/api/v2/users/me/sites/#{blog}/posts?api_token=#{@api_token}").body)
35
+ page = 1
36
+
37
+ while posts.any?
38
+ posts.each do |post|
39
+ title = post["title"]
40
+ slug = title.gsub(/[^[:alnum:]]+/, '-').downcase
41
+ date = Date.parse(post["display_date"])
42
+ content = post["body_html"]
43
+ published = !post["is_private"]
44
+ name = "%02d-%02d-%02d-%s.html" % [date.year, date.month, date.day, slug]
45
+
46
+ # Get the relevant fields as a hash, delete empty fields and convert
47
+ # to YAML for the header
48
+ data = {
49
+ 'layout' => 'post',
50
+ 'title' => title.to_s,
51
+ 'published' => published
52
+ }.delete_if { |k,v| v.nil? || v == ''}.to_yaml
53
+
54
+ # Write out the data and content to file
55
+ File.open("_posts/#{name}", "w") do |f|
56
+ f.puts data
57
+ f.puts "---"
58
+ f.puts content
59
+ end
60
+ end
61
+
62
+ page += 1
63
+ posts = JSON.parse(self.fetch("/api/v2/users/me/sites/#{blog}/posts?api_token=#{@api_token}&page=#{page}").body)
64
+ end
65
+ end
66
+ end
67
+ end
@@ -0,0 +1,47 @@
1
+ # Created by Kendall Buchanan (https://github.com/kendagriff) on 2011-12-22.
2
+ # Use at your own risk. The end.
3
+ #
4
+ # Usage:
5
+ # (URL)
6
+ # ruby -r '_import/rss.rb' -e "Jekyll::MigrateRSS.process('http://yourdomain.com/your-favorite-feed.xml')"
7
+ #
8
+ # (Local file)
9
+ # ruby -r '_import/rss.rb' -e "Jekyll::MigrateRSS.process('./somefile/on/your/computer.xml')"
10
+
11
+ require 'rubygems'
12
+ require 'rss/1.0'
13
+ require 'rss/2.0'
14
+ require 'open-uri'
15
+ require 'fileutils'
16
+ require 'yaml'
17
+
18
+ module Jekyll
19
+ module MigrateRSS
20
+
21
+ # The `source` argument may be a URL or a local file.
22
+ def self.process(source)
23
+ content = ""
24
+ open(source) { |s| content = s.read }
25
+ rss = RSS::Parser.parse(content, false)
26
+
27
+ raise "There doesn't appear to be any RSS items at the source (#{source}) provided." unless rss
28
+
29
+ rss.items.each do |item|
30
+ formatted_date = item.date.strftime('%Y-%m-%d')
31
+ post_name = item.title.split(%r{ |!|/|:|&|-|$|,}).map { |i| i.downcase if i != '' }.compact.join('-')
32
+ name = "#{formatted_date}-#{post_name}"
33
+
34
+ header = {
35
+ 'layout' => 'post',
36
+ 'title' => item.title
37
+ }
38
+
39
+ File.open("_posts/#{name}.html", "w") do |f|
40
+ f.puts header.to_yaml
41
+ f.puts "---\n"
42
+ f.puts item.description
43
+ end
44
+ end
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,58 @@
1
+ require 'rubygems'
2
+ require 'sequel'
3
+ require 'fileutils'
4
+ require 'yaml'
5
+
6
+ # NOTE: This converter requires Sequel and the MySQL gems.
7
+ # The MySQL gem can be difficult to install on OS X. Once you have MySQL
8
+ # installed, running the following commands should work:
9
+ # $ sudo gem install sequel
10
+ # $ sudo gem install mysql -- --with-mysql-config=/usr/local/mysql/bin/mysql_config
11
+
12
+ module Jekyll
13
+ module TextPattern
14
+ # Reads a MySQL database via Sequel and creates a post file for each post.
15
+ # The only posts selected are those with a status of 4 or 5, which means
16
+ # "live" and "sticky" respectively.
17
+ # Other statuses are 1 => draft, 2 => hidden and 3 => pending.
18
+ QUERY = "SELECT Title, \
19
+ url_title, \
20
+ Posted, \
21
+ Body, \
22
+ Keywords \
23
+ FROM textpattern \
24
+ WHERE Status = '4' OR \
25
+ Status = '5'"
26
+
27
+ def self.process(dbname, user, pass, host = 'localhost')
28
+ db = Sequel.mysql(dbname, :user => user, :password => pass, :host => host, :encoding => 'utf8')
29
+
30
+ FileUtils.mkdir_p "_posts"
31
+
32
+ db[QUERY].each do |post|
33
+ # Get required fields and construct Jekyll compatible name.
34
+ title = post[:Title]
35
+ slug = post[:url_title]
36
+ date = post[:Posted]
37
+ content = post[:Body]
38
+
39
+ name = [date.strftime("%Y-%m-%d"), slug].join('-') + ".textile"
40
+
41
+ # Get the relevant fields as a hash, delete empty fields and convert
42
+ # to YAML for the header.
43
+ data = {
44
+ 'layout' => 'post',
45
+ 'title' => title.to_s,
46
+ 'tags' => post[:Keywords].split(',')
47
+ }.delete_if { |k,v| v.nil? || v == ''}.to_yaml
48
+
49
+ # Write out the data and content to file.
50
+ File.open("_posts/#{name}", "w") do |f|
51
+ f.puts data
52
+ f.puts "---"
53
+ f.puts content
54
+ end
55
+ end
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,195 @@
1
+ require 'rubygems'
2
+ require 'open-uri'
3
+ require 'fileutils'
4
+ require 'nokogiri'
5
+ require 'date'
6
+ require 'json'
7
+ require 'uri'
8
+ require 'jekyll'
9
+
10
+ module Jekyll
11
+ module Tumblr
12
+ def self.process(url, format = "html", grab_images = false,
13
+ add_highlights = false, rewrite_urls = true)
14
+ @grab_images = grab_images
15
+ FileUtils.mkdir_p "_posts/tumblr"
16
+ url += "/api/read/json/"
17
+ per_page = 50
18
+ posts = []
19
+ # Two passes are required so that we can rewrite URLs.
20
+ # First pass builds up an array of each post as a hash.
21
+ begin
22
+ current_page = (current_page || -1) + 1
23
+ feed = open(url + "?num=#{per_page}&start=#{current_page * per_page}")
24
+ json = feed.readlines.join("\n")[21...-2] # Strip Tumblr's JSONP chars.
25
+ blog = JSON.parse(json)
26
+ puts "Page: #{current_page + 1} - Posts: #{blog["posts"].size}"
27
+ posts += blog["posts"].map { |post| post_to_hash(post, format) }
28
+ end until blog["posts"].size < per_page
29
+ # Rewrite URLs and create redirects.
30
+ posts = rewrite_urls_and_redirects posts if rewrite_urls
31
+ # Second pass for writing post files.
32
+ posts.each do |post|
33
+ if format == "md"
34
+ post[:content] = html_to_markdown post[:content]
35
+ post[:content] = add_syntax_highlights post[:content] if add_highlights
36
+ end
37
+ File.open("_posts/tumblr/#{post[:name]}", "w") do |f|
38
+ f.puts post[:header].to_yaml + "---\n" + post[:content]
39
+ end
40
+ end
41
+ end
42
+
43
+ private
44
+
45
+ # Converts each type of Tumblr post to a hash with all required
46
+ # data for Jekyll.
47
+ def self.post_to_hash(post, format)
48
+ case post['type']
49
+ when "regular"
50
+ title = post["regular-title"]
51
+ content = post["regular-body"]
52
+ when "link"
53
+ title = post["link-text"] || post["link-url"]
54
+ content = "<a href=\"#{post["link-url"]}\">#{title}</a>"
55
+ unless post["link-description"].nil?
56
+ content << "<br/>" + post["link-description"]
57
+ end
58
+ when "photo"
59
+ title = post["photo-caption"]
60
+ max_size = post.keys.map{ |k| k.gsub("photo-url-", "").to_i }.max
61
+ url = post["photo-url"] || post["photo-url-#{max_size}"]
62
+ ext = "." + post[post.keys.select { |k|
63
+ k =~ /^photo-url-/ && post[k].split("/").last =~ /\./
64
+ }.first].split(".").last
65
+ content = "<img src=\"#{save_file(url, ext)}\"/>"
66
+ unless post["photo-link-url"].nil?
67
+ content = "<a href=\"#{post["photo-link-url"]}\">#{content}</a>"
68
+ end
69
+ when "audio"
70
+ if !post["id3-title"].nil?
71
+ title = post["id3-title"]
72
+ content = post.at["audio-player"] + "<br/>" + post["audio-caption"]
73
+ else
74
+ title = post["audio-caption"]
75
+ content = post.at["audio-player"]
76
+ end
77
+ when "quote"
78
+ title = post["quote-text"]
79
+ content = "<blockquote>#{post["quote-text"]}</blockquote>"
80
+ unless post["quote-source"].nil?
81
+ content << "&#8212;" + post["quote-source"]
82
+ end
83
+ when "conversation"
84
+ title = post["conversation-title"]
85
+ content = "<section><dialog>"
86
+ post["conversation"]["line"].each do |line|
87
+ content << "<dt>#{line['label']}</dt><dd>#{line}</dd>"
88
+ end
89
+ content << "</section></dialog>"
90
+ when "video"
91
+ title = post["video-title"]
92
+ content = post["video-player"]
93
+ unless post["video-caption"].nil?
94
+ content << "<br/>" + post["video-caption"]
95
+ end
96
+ end
97
+ date = Date.parse(post['date']).to_s
98
+ title = Nokogiri::HTML(title).text
99
+ slug = title.downcase.strip.gsub(' ', '-').gsub(/[^\w-]/, '')
100
+ {
101
+ :name => "#{date}-#{slug}.#{format}",
102
+ :header => {
103
+ "layout" => "post",
104
+ "title" => title,
105
+ "tags" => post["tags"],
106
+ },
107
+ :content => content,
108
+ :url => post["url"],
109
+ :slug => post["url-with-slug"],
110
+ }
111
+ end
112
+
113
+ # Create a Hash of old urls => new urls, for rewriting and
114
+ # redirects, and replace urls in each post. Instantiate Jekyll
115
+ # site/posts to get the correct permalink format.
116
+ def self.rewrite_urls_and_redirects(posts)
117
+ site = Jekyll::Site.new(Jekyll.configuration({}))
118
+ dir = File.join(File.dirname(__FILE__), "..")
119
+ urls = Hash[posts.map { |post|
120
+ # Create an initial empty file for the post so that
121
+ # we can instantiate a post object.
122
+ File.open("_posts/tumblr/#{post[:name]}", "w")
123
+ tumblr_url = URI.parse(post[:slug]).path
124
+ jekyll_url = Jekyll::Post.new(site, dir, "", "tumblr/" + post[:name]).url
125
+ redirect_dir = tumblr_url.sub(/\//, "") + "/"
126
+ FileUtils.mkdir_p redirect_dir
127
+ File.open(redirect_dir + "index.html", "w") do |f|
128
+ f.puts "<html><head><meta http-equiv='Refresh' content='0; " +
129
+ "url=#{jekyll_url}'></head><body></body></html>"
130
+ end
131
+ [tumblr_url, jekyll_url]
132
+ }]
133
+ posts.map { |post|
134
+ urls.each do |tumblr_url, jekyll_url|
135
+ post[:content].gsub!(/#{tumblr_url}/i, jekyll_url)
136
+ end
137
+ post
138
+ }
139
+ end
140
+
141
+ # Uses Python's html2text to convert a post's content to
142
+ # markdown. Preserve HTML tables as per the markdown docs.
143
+ def self.html_to_markdown(content)
144
+ preserve = ["table", "tr", "th", "td"]
145
+ preserve.each do |tag|
146
+ content.gsub!(/<#{tag}/i, "$$" + tag)
147
+ content.gsub!(/<\/#{tag}/i, "||" + tag)
148
+ end
149
+ content = %x[echo '#{content.gsub("'", "''")}' | html2text]
150
+ preserve.each do |tag|
151
+ content.gsub!("$$" + tag, "<" + tag)
152
+ content.gsub!("||" + tag, "</" + tag)
153
+ end
154
+ content
155
+ end
156
+
157
+ # Adds pygments highlight tags to code blocks in posts that use
158
+ # markdown format. This doesn't guess the language of the code
159
+ # block, so you should modify this to suit your own content.
160
+ # For example, my code block only contain Python and JavaScript,
161
+ # so I can assume the block is JavaScript if it contains a
162
+ # semi-colon.
163
+ def self.add_syntax_highlights(content)
164
+ lines = content.split("\n")
165
+ block, indent, lang, start = false, /^ /, nil, nil
166
+ lines.each_with_index do |line, i|
167
+ if !block && line =~ indent
168
+ block = true
169
+ lang = "python"
170
+ start = i
171
+ elsif block
172
+ lang = "javascript" if line =~ /;$/
173
+ block = line =~ indent && i < lines.size - 1 # Also handle EOF
174
+ if !block
175
+ lines[start] = "{% highlight #{lang} %}"
176
+ lines[i - 1] = "{% endhighlight %}"
177
+ end
178
+ lines[i] = lines[i].sub(indent, "")
179
+ end
180
+ end
181
+ lines.join("\n")
182
+ end
183
+
184
+ def self.save_file(url, ext)
185
+ if @grab_images
186
+ path = "tumblr_files/#{url.split('/').last}"
187
+ path += ext unless path =~ /#{ext}$/
188
+ FileUtils.mkdir_p "tumblr_files"
189
+ File.open(path, "w") { |f| f.write(open(url).read) }
190
+ url = "/" + path
191
+ end
192
+ url
193
+ end
194
+ end
195
+ end