jekyll 0.12.1 → 1.0.0.beta1

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.

Files changed (124) hide show
  1. data/CONTRIBUTING.md +67 -0
  2. data/Gemfile +1 -1
  3. data/History.txt +50 -6
  4. data/README.textile +10 -6
  5. data/Rakefile +74 -36
  6. data/bin/jekyll +78 -276
  7. data/cucumber.yml +3 -1
  8. data/features/create_sites.feature +1 -1
  9. data/features/drafts.feature +25 -0
  10. data/features/site_configuration.feature +1 -1
  11. data/features/step_definitions/jekyll_steps.rb +13 -3
  12. data/features/support/env.rb +3 -1
  13. data/jekyll.gemspec +73 -17
  14. data/lib/jekyll.rb +31 -21
  15. data/lib/jekyll/command.rb +12 -0
  16. data/lib/jekyll/commands/build.rb +81 -0
  17. data/lib/jekyll/commands/serve.rb +28 -0
  18. data/lib/jekyll/converter.rb +1 -3
  19. data/lib/jekyll/converters/identity.rb +13 -14
  20. data/lib/jekyll/converters/markdown.rb +128 -128
  21. data/lib/jekyll/converters/textile.rb +37 -37
  22. data/lib/jekyll/convertible.rb +6 -4
  23. data/lib/jekyll/core_ext.rb +9 -1
  24. data/lib/jekyll/draft.rb +35 -0
  25. data/lib/jekyll/errors.rb +1 -3
  26. data/lib/jekyll/filters.rb +13 -4
  27. data/lib/jekyll/generator.rb +1 -4
  28. data/lib/jekyll/generators/pagination.rb +46 -46
  29. data/lib/jekyll/layout.rb +0 -2
  30. data/lib/jekyll/mime.types +1588 -0
  31. data/lib/jekyll/page.rb +24 -8
  32. data/lib/jekyll/plugin.rb +0 -2
  33. data/lib/jekyll/post.rb +66 -40
  34. data/lib/jekyll/site.rb +96 -20
  35. data/lib/jekyll/static_file.rb +0 -2
  36. data/lib/jekyll/tags/gist.rb +19 -0
  37. data/lib/jekyll/tags/highlight.rb +63 -62
  38. data/lib/jekyll/tags/include.rb +25 -25
  39. data/lib/jekyll/tags/post_url.rb +30 -25
  40. data/script/bootstrap +2 -0
  41. data/site/.gitignore +4 -0
  42. data/site/CNAME +1 -0
  43. data/site/README +1 -0
  44. data/site/_config.yml +5 -0
  45. data/site/_includes/analytics.html +32 -0
  46. data/site/_includes/docs_contents.html +82 -0
  47. data/site/_includes/footer.html +15 -0
  48. data/site/_includes/header.html +26 -0
  49. data/site/_includes/section_nav.html +22 -0
  50. data/site/_includes/top.html +14 -0
  51. data/site/_layouts/default.html +12 -0
  52. data/site/_layouts/docs.html +21 -0
  53. data/site/_posts/2012-07-01-configuration.md +277 -0
  54. data/site/_posts/2012-07-01-contributing.md +66 -0
  55. data/site/_posts/2012-07-01-deployment-methods.md +108 -0
  56. data/site/_posts/2012-07-01-extras.md +103 -0
  57. data/site/_posts/2012-07-01-frontmatter.md +120 -0
  58. data/site/_posts/2012-07-01-github-pages.md +34 -0
  59. data/site/_posts/2012-07-01-heroku.md +8 -0
  60. data/site/_posts/2012-07-01-home.md +47 -0
  61. data/site/_posts/2012-07-01-installation.md +43 -0
  62. data/site/_posts/2012-07-01-migrations.md +180 -0
  63. data/site/_posts/2012-07-01-pages.md +62 -0
  64. data/site/_posts/2012-07-01-pagination.md +116 -0
  65. data/site/_posts/2012-07-01-permalinks.md +163 -0
  66. data/site/_posts/2012-07-01-plugins.md +384 -0
  67. data/site/_posts/2012-07-01-posts.md +106 -0
  68. data/site/_posts/2012-07-01-resources.md +49 -0
  69. data/site/_posts/2012-07-01-sites.md +28 -0
  70. data/site/_posts/2012-07-01-structure.md +95 -0
  71. data/site/_posts/2012-07-01-templates.md +217 -0
  72. data/site/_posts/2012-07-01-troubleshooting.md +108 -0
  73. data/site/_posts/2012-07-01-usage.md +38 -0
  74. data/site/_posts/2012-07-01-variables.md +166 -0
  75. data/site/css/grid.css +62 -0
  76. data/site/css/normalize.css +504 -0
  77. data/site/css/pygments.css +70 -0
  78. data/site/css/style.css +697 -0
  79. data/site/docs/index.html +11 -0
  80. data/site/favicon.png +0 -0
  81. data/site/img/article-footer.png +0 -0
  82. data/site/img/footer-arrow.png +0 -0
  83. data/site/img/footer-logo.png +0 -0
  84. data/site/img/logo-2x.png +0 -0
  85. data/site/img/octojekyll.png +0 -0
  86. data/site/img/tube.png +0 -0
  87. data/site/img/tube1x.png +0 -0
  88. data/site/index.html +77 -0
  89. data/site/js/modernizr-2.5.3.min.js +4 -0
  90. data/test/fixtures/broken_front_matter2.erb +4 -0
  91. data/test/fixtures/broken_front_matter3.erb +7 -0
  92. data/test/fixtures/exploit_front_matter.erb +4 -0
  93. data/test/helper.rb +16 -0
  94. data/test/source/_posts/2013-01-12-nil-layout.textile +6 -0
  95. data/test/source/_posts/2013-01-12-no-layout.textile +5 -0
  96. data/test/source/contacts/bar.html +5 -0
  97. data/test/source/contacts/index.html +5 -0
  98. data/test/test_configuration.rb +7 -8
  99. data/test/test_convertible.rb +29 -0
  100. data/test/test_core_ext.rb +22 -0
  101. data/test/test_generated_site.rb +1 -1
  102. data/test/test_kramdown.rb +3 -3
  103. data/test/test_page.rb +88 -2
  104. data/test/test_post.rb +42 -6
  105. data/test/test_rdiscount.rb +1 -1
  106. data/test/test_redcarpet.rb +1 -1
  107. data/test/test_redcloth.rb +6 -6
  108. data/test/test_site.rb +73 -8
  109. data/test/test_tags.rb +36 -13
  110. metadata +150 -19
  111. data/lib/jekyll/migrators/csv.rb +0 -26
  112. data/lib/jekyll/migrators/drupal.rb +0 -103
  113. data/lib/jekyll/migrators/enki.rb +0 -49
  114. data/lib/jekyll/migrators/joomla.rb +0 -53
  115. data/lib/jekyll/migrators/marley.rb +0 -52
  116. data/lib/jekyll/migrators/mephisto.rb +0 -84
  117. data/lib/jekyll/migrators/mt.rb +0 -86
  118. data/lib/jekyll/migrators/posterous.rb +0 -67
  119. data/lib/jekyll/migrators/rss.rb +0 -47
  120. data/lib/jekyll/migrators/textpattern.rb +0 -58
  121. data/lib/jekyll/migrators/tumblr.rb +0 -195
  122. data/lib/jekyll/migrators/typo.rb +0 -51
  123. data/lib/jekyll/migrators/wordpress.rb +0 -294
  124. data/lib/jekyll/migrators/wordpressdotcom.rb +0 -70
@@ -1,67 +0,0 @@
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
@@ -1,47 +0,0 @@
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,58 +0,0 @@
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
@@ -1,195 +0,0 @@
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
@@ -1,51 +0,0 @@
1
- # Author: Toby DiPasquale <toby@cbcg.net>
2
- require 'fileutils'
3
- require 'rubygems'
4
- require 'sequel'
5
- require 'yaml'
6
-
7
- module Jekyll
8
- module Typo
9
- # This SQL *should* work for both MySQL and PostgreSQL, but I haven't
10
- # tested PostgreSQL yet (as of 2008-12-16).
11
- SQL = <<-EOS
12
- SELECT c.id id,
13
- c.title title,
14
- c.permalink slug,
15
- c.body body,
16
- c.published_at date,
17
- c.state state,
18
- COALESCE(tf.name, 'html') filter
19
- FROM contents c
20
- LEFT OUTER JOIN text_filters tf
21
- ON c.text_filter_id = tf.id
22
- EOS
23
-
24
- def self.process dbname, user, pass, host='localhost'
25
- FileUtils.mkdir_p '_posts'
26
- db = Sequel.mysql(dbname, :user => user, :password => pass, :host => host, :encoding => 'utf8')
27
- db[SQL].each do |post|
28
- next unless post[:state] =~ /published/
29
-
30
- name = [ sprintf("%.04d", post[:date].year),
31
- sprintf("%.02d", post[:date].month),
32
- sprintf("%.02d", post[:date].day),
33
- post[:slug].strip ].join('-')
34
-
35
- # Can have more than one text filter in this field, but we just want
36
- # the first one for this.
37
- name += '.' + post[:filter].split(' ')[0]
38
-
39
- File.open("_posts/#{name}", 'w') do |f|
40
- f.puts({ 'layout' => 'post',
41
- 'title' => post[:title].to_s,
42
- 'typo_id' => post[:id]
43
- }.delete_if { |k, v| v.nil? || v == '' }.to_yaml)
44
- f.puts '---'
45
- f.puts post[:body].delete("\r")
46
- end
47
- end
48
- end
49
-
50
- end
51
- end
@@ -1,294 +0,0 @@
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 WordPress
14
-
15
- # Main migrator function. Call this to perform the migration.
16
- #
17
- # dbname:: The name of the database
18
- # user:: The database user name
19
- # pass:: The database user's password
20
- # host:: The address of the MySQL database host. Default: 'localhost'
21
- # options:: A hash table of configuration options.
22
- #
23
- # Supported options are:
24
- #
25
- # :table_prefix:: Prefix of database tables used by WordPress.
26
- # Default: 'wp_'
27
- # :clean_entities:: If true, convert non-ASCII characters to HTML
28
- # entities in the posts, comments, titles, and
29
- # names. Requires the 'htmlentities' gem to
30
- # work. Default: true.
31
- # :comments:: If true, migrate post comments too. Comments
32
- # are saved in the post's YAML front matter.
33
- # Default: true.
34
- # :categories:: If true, save the post's categories in its
35
- # YAML front matter.
36
- # :tags:: If true, save the post's tags in its
37
- # YAML front matter.
38
- # :more_excerpt:: If true, when a post has no excerpt but
39
- # does have a <!-- more --> tag, use the
40
- # preceding post content as the excerpt.
41
- # Default: true.
42
- # :more_anchor:: If true, convert a <!-- more --> tag into
43
- # two HTML anchors with ids "more" and
44
- # "more-NNN" (where NNN is the post number).
45
- # Default: true.
46
- # :status:: Array of allowed post statuses. Only
47
- # posts with matching status will be migrated.
48
- # Known statuses are :publish, :draft, :private,
49
- # and :revision. If this is nil or an empty
50
- # array, all posts are migrated regardless of
51
- # status. Default: [:publish].
52
- #
53
- def self.process(dbname, user, pass, host='localhost', options={})
54
- options = {
55
- :table_prefix => 'wp_',
56
- :clean_entities => true,
57
- :comments => true,
58
- :categories => true,
59
- :tags => true,
60
- :more_excerpt => true,
61
- :more_anchor => true,
62
- :status => [:publish] # :draft, :private, :revision
63
- }.merge(options)
64
-
65
- if options[:clean_entities]
66
- begin
67
- require 'htmlentities'
68
- rescue LoadError
69
- STDERR.puts "Could not require 'htmlentities', so the " +
70
- ":clean_entities option is now disabled."
71
- options[:clean_entities] = false
72
- end
73
- end
74
-
75
- FileUtils.mkdir_p("_posts")
76
-
77
- db = Sequel.mysql(dbname, :user => user, :password => pass,
78
- :host => host, :encoding => 'utf8')
79
-
80
- px = options[:table_prefix]
81
-
82
- posts_query = "
83
- SELECT
84
- posts.ID AS `id`,
85
- posts.guid AS `guid`,
86
- posts.post_type AS `type`,
87
- posts.post_status AS `status`,
88
- posts.post_title AS `title`,
89
- posts.post_name AS `slug`,
90
- posts.post_date AS `date`,
91
- posts.post_content AS `content`,
92
- posts.post_excerpt AS `excerpt`,
93
- posts.comment_count AS `comment_count`,
94
- users.display_name AS `author`,
95
- users.user_login AS `author_login`,
96
- users.user_email AS `author_email`,
97
- users.user_url AS `author_url`
98
- FROM #{px}posts AS `posts`
99
- LEFT JOIN #{px}users AS `users`
100
- ON posts.post_author = users.ID"
101
-
102
- if options[:status] and not options[:status].empty?
103
- status = options[:status][0]
104
- posts_query << "
105
- WHERE posts.post_status = '#{status.to_s}'"
106
- options[:status][1..-1].each do |status|
107
- posts_query << " OR
108
- posts.post_status = '#{status.to_s}'"
109
- end
110
- end
111
-
112
- db[posts_query].each do |post|
113
- process_post(post, db, options)
114
- end
115
- end
116
-
117
-
118
- def self.process_post(post, db, options)
119
- px = options[:table_prefix]
120
-
121
- title = post[:title]
122
- if options[:clean_entities]
123
- title = clean_entities(title)
124
- end
125
-
126
- slug = post[:slug]
127
- if !slug or slug.empty?
128
- slug = sluggify(title)
129
- end
130
-
131
- date = post[:date] || Time.now
132
- name = "%02d-%02d-%02d-%s.markdown" % [date.year, date.month,
133
- date.day, slug]
134
- content = post[:content].to_s
135
- if options[:clean_entities]
136
- content = clean_entities(content)
137
- end
138
-
139
- excerpt = post[:excerpt].to_s
140
-
141
- more_index = content.index(/<!-- *more *-->/)
142
- more_anchor = nil
143
- if more_index
144
- if options[:more_excerpt] and
145
- (post[:excerpt].nil? or post[:excerpt].empty?)
146
- excerpt = content[0...more_index]
147
- end
148
- if options[:more_anchor]
149
- more_link = "more"
150
- content.sub!(/<!-- *more *-->/,
151
- "<a id=\"more\"></a>" +
152
- "<a id=\"more-#{post[:id]}\"></a>")
153
- end
154
- end
155
-
156
- categories = []
157
- tags = []
158
-
159
- if options[:categories] or options[:tags]
160
-
161
- cquery =
162
- "SELECT
163
- terms.name AS `name`,
164
- ttax.taxonomy AS `type`
165
- FROM
166
- #{px}terms AS `terms`,
167
- #{px}term_relationships AS `trels`,
168
- #{px}term_taxonomy AS `ttax`
169
- WHERE
170
- trels.object_id = '#{post[:id]}' AND
171
- trels.term_taxonomy_id = ttax.term_taxonomy_id AND
172
- terms.term_id = ttax.term_id"
173
-
174
- db[cquery].each do |term|
175
- if options[:categories] and term[:type] == "category"
176
- if options[:clean_entities]
177
- categories << clean_entities(term[:name])
178
- else
179
- categories << term[:name]
180
- end
181
- elsif options[:tags] and term[:type] == "post_tag"
182
- if options[:clean_entities]
183
- tags << clean_entities(term[:name])
184
- else
185
- tags << term[:name]
186
- end
187
- end
188
- end
189
- end
190
-
191
- comments = []
192
-
193
- if options[:comments] and post[:comment_count].to_i > 0
194
- cquery =
195
- "SELECT
196
- comment_ID AS `id`,
197
- comment_author AS `author`,
198
- comment_author_email AS `author_email`,
199
- comment_author_url AS `author_url`,
200
- comment_date AS `date`,
201
- comment_date_gmt AS `date_gmt`,
202
- comment_content AS `content`
203
- FROM #{px}comments
204
- WHERE
205
- comment_post_ID = '#{post[:id]}' AND
206
- comment_approved != 'spam'"
207
-
208
-
209
- db[cquery].each do |comment|
210
-
211
- comcontent = comment[:content].to_s
212
- if comcontent.respond_to?(:force_encoding)
213
- comcontent.force_encoding("UTF-8")
214
- end
215
- if options[:clean_entities]
216
- comcontent = clean_entities(comcontent)
217
- end
218
- comauthor = comment[:author].to_s
219
- if options[:clean_entities]
220
- comauthor = clean_entities(comauthor)
221
- end
222
-
223
- comments << {
224
- 'id' => comment[:id].to_i,
225
- 'author' => comauthor,
226
- 'author_email' => comment[:author_email].to_s,
227
- 'author_url' => comment[:author_url].to_s,
228
- 'date' => comment[:date].to_s,
229
- 'date_gmt' => comment[:date_gmt].to_s,
230
- 'content' => comcontent,
231
- }
232
- end
233
-
234
- comments.sort!{ |a,b| a['id'] <=> b['id'] }
235
- end
236
-
237
- # Get the relevant fields as a hash, delete empty fields and
238
- # convert to YAML for the header.
239
- data = {
240
- 'layout' => post[:type].to_s,
241
- 'status' => post[:status].to_s,
242
- 'published' => (post[:status].to_s == "publish"),
243
- 'title' => title.to_s,
244
- 'author' => post[:author].to_s,
245
- 'author_login' => post[:author_login].to_s,
246
- 'author_email' => post[:author_email].to_s,
247
- 'author_url' => post[:author_url].to_s,
248
- 'excerpt' => excerpt,
249
- 'more_anchor' => more_anchor,
250
- 'wordpress_id' => post[:id],
251
- 'wordpress_url' => post[:guid].to_s,
252
- 'date' => date,
253
- 'categories' => options[:categories] ? categories : nil,
254
- 'tags' => options[:tags] ? tags : nil,
255
- 'comments' => options[:comments] ? comments : nil,
256
- }.delete_if { |k,v| v.nil? || v == '' }.to_yaml
257
-
258
- # Write out the data and content to file
259
- File.open("_posts/#{name}", "w") do |f|
260
- f.puts data
261
- f.puts "---"
262
- f.puts content
263
- end
264
- end
265
-
266
-
267
- def self.clean_entities( text )
268
- if text.respond_to?(:force_encoding)
269
- text.force_encoding("UTF-8")
270
- end
271
- text = HTMLEntities.new.encode(text, :named)
272
- # We don't want to convert these, it would break all
273
- # HTML tags in the post and comments.
274
- text.gsub!("&amp;", "&")
275
- text.gsub!("&lt;", "<")
276
- text.gsub!("&gt;", ">")
277
- text.gsub!("&quot;", '"')
278
- text.gsub!("&apos;", "'")
279
- text
280
- end
281
-
282
-
283
- def self.sluggify( title )
284
- begin
285
- require 'unidecode'
286
- title = title.to_ascii
287
- rescue LoadError
288
- STDERR.puts "Could not require 'unidecode'. If your post titles have non-ASCII characters, you could get nicer permalinks by installing unidecode."
289
- end
290
- title.downcase.gsub(/[^0-9A-Za-z]+/, " ").strip.gsub(" ", "-")
291
- end
292
-
293
- end
294
- end