jekyll 0.11.2 → 0.12.0
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of jekyll might be problematic. Click here for more details.
- data/History.txt +19 -0
- data/Rakefile +1 -5
- data/bin/jekyll +22 -5
- data/features/create_sites.feature +18 -0
- data/features/pagination.feature +28 -1
- data/features/site_configuration.feature +33 -0
- data/features/step_definitions/jekyll_steps.rb +8 -1
- data/jekyll.gemspec +13 -5
- data/lib/jekyll.rb +36 -25
- data/lib/jekyll/converters/markdown.rb +31 -7
- data/lib/jekyll/converters/textile.rb +17 -1
- data/lib/jekyll/convertible.rb +16 -8
- data/lib/jekyll/generators/pagination.rb +7 -1
- data/lib/jekyll/migrators/drupal.rb +27 -16
- data/lib/jekyll/migrators/joomla.rb +53 -0
- data/lib/jekyll/migrators/posterous.rb +3 -4
- data/lib/jekyll/migrators/rss.rb +47 -0
- data/lib/jekyll/migrators/textpattern.rb +1 -0
- data/lib/jekyll/migrators/tumblr.rb +175 -99
- data/lib/jekyll/migrators/wordpress.rb +273 -41
- data/lib/jekyll/post.rb +8 -2
- data/lib/jekyll/site.rb +23 -16
- data/lib/jekyll/tags/highlight.rb +17 -6
- data/lib/jekyll/tags/post_url.rb +38 -0
- data/test/fixtures/broken_front_matter1.erb +5 -0
- data/test/fixtures/front_matter.erb +4 -0
- data/test/test_convertible.rb +22 -0
- data/test/test_kramdown.rb +14 -4
- data/test/test_post.rb +13 -0
- data/test/test_rdiscount.rb +6 -2
- data/test/test_redcarpet.rb +21 -3
- data/test/test_redcloth.rb +86 -0
- data/test/test_site.rb +36 -2
- data/test/test_tags.rb +62 -1
- metadata +42 -30
@@ -27,7 +27,23 @@ module Jekyll
|
|
27
27
|
|
28
28
|
def convert(content)
|
29
29
|
setup
|
30
|
-
|
30
|
+
|
31
|
+
# Shortcut if config doesn't contain RedCloth section
|
32
|
+
return RedCloth.new(content).to_html if @config['redcloth'].nil?
|
33
|
+
|
34
|
+
# List of attributes defined on RedCloth
|
35
|
+
# (from http://redcloth.rubyforge.org/classes/RedCloth/TextileDoc.html)
|
36
|
+
attrs = ['filter_classes', 'filter_html', 'filter_ids', 'filter_styles',
|
37
|
+
'hard_breaks', 'lite_mode', 'no_span_caps', 'sanitize_html']
|
38
|
+
|
39
|
+
r = RedCloth.new(content)
|
40
|
+
|
41
|
+
# Set attributes in r if they are NOT nil in the config
|
42
|
+
attrs.each do |attr|
|
43
|
+
r.instance_variable_set("@#{attr}".to_sym, @config['redcloth'][attr]) unless @config['redcloth'][attr].nil?
|
44
|
+
end
|
45
|
+
|
46
|
+
r.to_html
|
31
47
|
end
|
32
48
|
end
|
33
49
|
|
data/lib/jekyll/convertible.rb
CHANGED
@@ -10,6 +10,7 @@ require 'set'
|
|
10
10
|
# self.data=
|
11
11
|
# self.ext=
|
12
12
|
# self.output=
|
13
|
+
# self.name
|
13
14
|
module Jekyll
|
14
15
|
module Convertible
|
15
16
|
# Returns the contents as a String.
|
@@ -26,14 +27,13 @@ module Jekyll
|
|
26
27
|
def read_yaml(base, name)
|
27
28
|
self.content = File.read(File.join(base, name))
|
28
29
|
|
29
|
-
|
30
|
-
self.content
|
31
|
-
|
32
|
-
begin
|
30
|
+
begin
|
31
|
+
if self.content =~ /\A(---\s*\n.*?\n?)^(---\s*$\n?)/m
|
32
|
+
self.content = $POSTMATCH
|
33
33
|
self.data = YAML.load($1)
|
34
|
-
rescue => e
|
35
|
-
puts "YAML Exception reading #{name}: #{e.message}"
|
36
34
|
end
|
35
|
+
rescue => e
|
36
|
+
puts "YAML Exception reading #{name}: #{e.message}"
|
37
37
|
end
|
38
38
|
|
39
39
|
self.data ||= {}
|
@@ -76,9 +76,13 @@ module Jekyll
|
|
76
76
|
payload["pygments_suffix"] = converter.pygments_suffix
|
77
77
|
|
78
78
|
begin
|
79
|
-
self.content = Liquid::Template.parse(self.content).render(payload, info)
|
79
|
+
self.content = Liquid::Template.parse(self.content).render!(payload, info)
|
80
80
|
rescue => e
|
81
81
|
puts "Liquid Exception: #{e.message} in #{self.name}"
|
82
|
+
e.backtrace.each do |backtrace|
|
83
|
+
puts backtrace
|
84
|
+
end
|
85
|
+
abort("Build Failed")
|
82
86
|
end
|
83
87
|
|
84
88
|
self.transform
|
@@ -94,9 +98,13 @@ module Jekyll
|
|
94
98
|
payload = payload.deep_merge({"content" => self.output, "page" => layout.data})
|
95
99
|
|
96
100
|
begin
|
97
|
-
self.output = Liquid::Template.parse(layout.content).render(payload, info)
|
101
|
+
self.output = Liquid::Template.parse(layout.content).render!(payload, info)
|
98
102
|
rescue => e
|
99
103
|
puts "Liquid Exception: #{e.message} in #{self.data["layout"]}"
|
104
|
+
e.backtrace.each do |backtrace|
|
105
|
+
puts backtrace
|
106
|
+
end
|
107
|
+
abort("Build Failed")
|
100
108
|
end
|
101
109
|
|
102
110
|
if layout = layouts[layout.data["layout"]]
|
@@ -37,13 +37,19 @@ module Jekyll
|
|
37
37
|
if num_page > 1
|
38
38
|
newpage = Page.new(site, site.source, page.dir, page.name)
|
39
39
|
newpage.pager = pager
|
40
|
-
newpage.dir = File.join(page.dir,
|
40
|
+
newpage.dir = File.join(page.dir, paginate_path(site, num_page))
|
41
41
|
site.pages << newpage
|
42
42
|
else
|
43
43
|
page.pager = pager
|
44
44
|
end
|
45
45
|
end
|
46
46
|
end
|
47
|
+
|
48
|
+
private
|
49
|
+
def paginate_path(site, num_page)
|
50
|
+
format = site.config['paginate_path']
|
51
|
+
format.sub(':num', num_page.to_s)
|
52
|
+
end
|
47
53
|
end
|
48
54
|
|
49
55
|
class Pager
|
@@ -14,19 +14,24 @@ module Jekyll
|
|
14
14
|
# Reads a MySQL database via Sequel and creates a post file for each post
|
15
15
|
# in wp_posts that has post_status = 'publish'. This restriction is made
|
16
16
|
# because 'draft' posts are not guaranteed to have valid dates.
|
17
|
-
QUERY = "SELECT
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
FROM node, \
|
23
|
-
node_revisions \
|
24
|
-
WHERE (
|
25
|
-
AND
|
17
|
+
QUERY = "SELECT n.nid, \
|
18
|
+
n.title, \
|
19
|
+
nr.body, \
|
20
|
+
n.created, \
|
21
|
+
n.status \
|
22
|
+
FROM node AS n, \
|
23
|
+
node_revisions AS nr \
|
24
|
+
WHERE (n.type = 'blog' OR n.type = 'story') \
|
25
|
+
AND n.vid = nr.vid"
|
26
26
|
|
27
|
-
def self.process(dbname, user, pass, host = 'localhost')
|
27
|
+
def self.process(dbname, user, pass, host = 'localhost', prefix = '')
|
28
28
|
db = Sequel.mysql(dbname, :user => user, :password => pass, :host => host, :encoding => 'utf8')
|
29
29
|
|
30
|
+
if prefix != ''
|
31
|
+
QUERY[" node "] = " " + prefix + "node "
|
32
|
+
QUERY[" node_revisions "] = " " + prefix + "node_revisions "
|
33
|
+
end
|
34
|
+
|
30
35
|
FileUtils.mkdir_p "_posts"
|
31
36
|
FileUtils.mkdir_p "_drafts"
|
32
37
|
|
@@ -73,12 +78,18 @@ EOF
|
|
73
78
|
|
74
79
|
# Make a file to redirect from the old Drupal URL
|
75
80
|
if is_published
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
81
|
+
aliases = db["SELECT dst FROM #{prefix}url_alias WHERE src = ?", "node/#{node_id}"].all
|
82
|
+
|
83
|
+
aliases.push(:dst => "node/#{node_id}")
|
84
|
+
|
85
|
+
aliases.each do |url_alias|
|
86
|
+
FileUtils.mkdir_p url_alias[:dst]
|
87
|
+
File.open("#{url_alias[:dst]}/index.md", "w") do |f|
|
88
|
+
f.puts "---"
|
89
|
+
f.puts "layout: refresh"
|
90
|
+
f.puts "refresh_to_post_id: /#{time.strftime("%Y/%m/%d/") + slug}"
|
91
|
+
f.puts "---"
|
92
|
+
end
|
82
93
|
end
|
83
94
|
end
|
84
95
|
end
|
@@ -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
|
@@ -5,7 +5,7 @@ require 'net/http'
|
|
5
5
|
require 'uri'
|
6
6
|
require "json"
|
7
7
|
|
8
|
-
# ruby -r './lib/jekyll/migrators/posterous.rb' -e 'Jekyll::Posterous.process(email, pass, blog)'
|
8
|
+
# ruby -r './lib/jekyll/migrators/posterous.rb' -e 'Jekyll::Posterous.process(email, pass, api_key, blog)'
|
9
9
|
|
10
10
|
module Jekyll
|
11
11
|
module Posterous
|
@@ -27,9 +27,8 @@ module Jekyll
|
|
27
27
|
end
|
28
28
|
end
|
29
29
|
|
30
|
-
def self.process(email, pass, blog = 'primary')
|
31
|
-
@email, @pass = email, pass
|
32
|
-
@api_token = JSON.parse(self.fetch("/api/2/auth/token").body)['api_token']
|
30
|
+
def self.process(email, pass, api_token, blog = 'primary')
|
31
|
+
@email, @pass, @api_token = email, pass, api_token
|
33
32
|
FileUtils.mkdir_p "_posts"
|
34
33
|
|
35
34
|
posts = JSON.parse(self.fetch("/api/v2/users/me/sites/#{blog}/posts?api_token=#{@api_token}").body)
|
@@ -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
|
@@ -1,119 +1,195 @@
|
|
1
1
|
require 'rubygems'
|
2
|
-
require 'nokogiri'
|
3
2
|
require 'open-uri'
|
4
3
|
require 'fileutils'
|
5
|
-
require '
|
6
|
-
require 'iconv'
|
4
|
+
require 'nokogiri'
|
7
5
|
require 'date'
|
6
|
+
require 'json'
|
7
|
+
require 'uri'
|
8
|
+
require 'jekyll'
|
8
9
|
|
9
10
|
module Jekyll
|
10
11
|
module Tumblr
|
11
|
-
def self.process(url, grab_images = false
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
(
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
elsif post['type'] == "photo"
|
42
|
-
content = ""
|
43
|
-
|
44
|
-
if post.at("photo-link-url") != nil
|
45
|
-
content = "<a href=\"#{post.at("photo-link-url").inner_html}\"><img src=\"#{save_file((post/"photo-url")[1].inner_html, grab_images)}\"/></a>"
|
46
|
-
else
|
47
|
-
content = "<img src=\"#{save_file((post/"photo-url")[1].inner_html, grab_images)}\"/>"
|
48
|
-
end
|
49
|
-
|
50
|
-
if post.at("photo-caption") != nil
|
51
|
-
content << "<br/>" unless content == nil
|
52
|
-
content << CGI::unescapeHTML(post.at("photo-caption").inner_html)
|
53
|
-
end
|
54
|
-
elsif post['type'] == "audio"
|
55
|
-
content = CGI::unescapeHTML(post.at("audio-player").inner_html)
|
56
|
-
content << CGI::unescapeHTML(post.at("audio-caption").inner_html) unless post.at("audio-caption") == nil
|
57
|
-
elsif post['type'] == "quote"
|
58
|
-
content = "<blockquote>" + CGI::unescapeHTML(post.at("quote-text").inner_html) + "</blockquote>"
|
59
|
-
content << "—" + CGI::unescapeHTML(post.at("quote-source").inner_html) unless post.at("quote-source") == nil
|
60
|
-
elsif post['type'] == "conversation"
|
61
|
-
title = post.at("conversation-title").inner_html unless post.at("conversation-title") == nil
|
62
|
-
content = "<section><dialog>"
|
63
|
-
|
64
|
-
(post/:conversation/:line).each do |line|
|
65
|
-
content << "<dt>" + line['label'] + "</dt><dd>" + line.inner_html + "</dd>" unless line['label'] == nil || line == nil
|
66
|
-
end
|
67
|
-
|
68
|
-
content << "</section></dialog>"
|
69
|
-
elsif post['type'] == "video"
|
70
|
-
title = post.at("video-title").inner_html unless post.at("video-title") == nil
|
71
|
-
content = CGI::unescapeHTML(post.at("video-player").inner_html)
|
72
|
-
content << CGI::unescapeHTML(post.at("video-caption").inner_html) unless post.at("video-caption") == nil
|
73
|
-
end # End post types
|
74
|
-
|
75
|
-
name = "#{Date.parse(post['date']).to_s}-#{post['id'].downcase.gsub(/[^a-z0-9]/, '-')}.html"
|
76
|
-
|
77
|
-
if title != nil || content != nil && name != nil
|
78
|
-
File.open("_posts/tumblr/#{name}", "w") do |f|
|
79
|
-
|
80
|
-
f.puts <<-HEADER
|
81
|
-
---
|
82
|
-
layout: post
|
83
|
-
title: #{title}
|
84
|
-
---
|
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
|
85
42
|
|
86
|
-
|
43
|
+
private
|
87
44
|
|
88
|
-
|
89
|
-
|
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"]
|
90
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 << "—" + 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
|
91
112
|
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
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>"
|
98
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
|
99
140
|
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
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
|
108
156
|
|
109
|
-
|
110
|
-
|
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, "")
|
111
179
|
end
|
180
|
+
end
|
181
|
+
lines.join("\n")
|
182
|
+
end
|
112
183
|
|
113
|
-
|
114
|
-
|
115
|
-
|
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
|
116
191
|
end
|
192
|
+
url
|
117
193
|
end
|
118
194
|
end
|
119
195
|
end
|