jekyll-import 0.1.0.beta3 → 0.1.0.beta4

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 (50) hide show
  1. checksums.yaml +6 -14
  2. data/History.markdown +18 -0
  3. data/README.markdown +12 -1
  4. data/jekyll-import.gemspec +31 -25
  5. data/lib/jekyll-import.rb +50 -1
  6. data/lib/jekyll-import/importer.rb +11 -0
  7. data/lib/jekyll-import/importers.rb +10 -0
  8. data/lib/jekyll-import/importers/csv.rb +50 -0
  9. data/lib/jekyll-import/importers/drupal6.rb +139 -0
  10. data/lib/jekyll-import/importers/drupal7.rb +102 -0
  11. data/lib/jekyll-import/importers/enki.rb +76 -0
  12. data/lib/jekyll-import/importers/google_reader.rb +68 -0
  13. data/lib/jekyll-import/importers/joomla.rb +83 -0
  14. data/lib/jekyll-import/importers/jrnl.rb +127 -0
  15. data/lib/jekyll-import/importers/marley.rb +72 -0
  16. data/lib/jekyll-import/importers/mephisto.rb +109 -0
  17. data/lib/jekyll-import/importers/mt.rb +169 -0
  18. data/lib/jekyll-import/importers/posterous.rb +139 -0
  19. data/lib/jekyll-import/importers/rss.rb +71 -0
  20. data/lib/jekyll-import/importers/s9y.rb +67 -0
  21. data/lib/jekyll-import/importers/textpattern.rb +76 -0
  22. data/lib/jekyll-import/importers/tumblr.rb +265 -0
  23. data/lib/jekyll-import/importers/typo.rb +89 -0
  24. data/lib/jekyll-import/importers/wordpress.rb +323 -0
  25. data/lib/jekyll-import/importers/wordpressdotcom.rb +97 -0
  26. data/lib/jekyll/commands/import.rb +1 -0
  27. data/test/helper.rb +3 -1
  28. data/test/test_jrnl_importer.rb +39 -0
  29. data/test/test_mt_importer.rb +16 -16
  30. data/test/test_tumblr_importer.rb +61 -0
  31. data/test/test_wordpress_importer.rb +1 -1
  32. data/test/test_wordpressdotcom_importer.rb +1 -1
  33. metadata +53 -32
  34. data/lib/jekyll/jekyll-import/csv.rb +0 -30
  35. data/lib/jekyll/jekyll-import/drupal6.rb +0 -112
  36. data/lib/jekyll/jekyll-import/drupal7.rb +0 -74
  37. data/lib/jekyll/jekyll-import/enki.rb +0 -49
  38. data/lib/jekyll/jekyll-import/google_reader.rb +0 -61
  39. data/lib/jekyll/jekyll-import/joomla.rb +0 -53
  40. data/lib/jekyll/jekyll-import/marley.rb +0 -52
  41. data/lib/jekyll/jekyll-import/mephisto.rb +0 -84
  42. data/lib/jekyll/jekyll-import/mt.rb +0 -142
  43. data/lib/jekyll/jekyll-import/posterous.rb +0 -122
  44. data/lib/jekyll/jekyll-import/rss.rb +0 -63
  45. data/lib/jekyll/jekyll-import/s9y.rb +0 -59
  46. data/lib/jekyll/jekyll-import/textpattern.rb +0 -58
  47. data/lib/jekyll/jekyll-import/tumblr.rb +0 -242
  48. data/lib/jekyll/jekyll-import/typo.rb +0 -69
  49. data/lib/jekyll/jekyll-import/wordpress.rb +0 -299
  50. data/lib/jekyll/jekyll-import/wordpressdotcom.rb +0 -84
@@ -1,242 +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 JekyllImport
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_url = url + "?num=#{per_page}&start=#{current_page * per_page}"
24
- puts "Fetching #{feed_url}"
25
- feed = open(feed_url)
26
- json = feed.readlines.join("\n")[21...-2] # Strip Tumblr's JSONP chars.
27
- blog = JSON.parse(json)
28
- puts "Page: #{current_page + 1} - Posts: #{blog["posts"].size}"
29
- batch = blog["posts"].map { |post| post_to_hash(post, format) }
30
-
31
- # If we're rewriting, save the posts for later. Otherwise, go ahead and
32
- # dump these to disk now
33
- if rewrite_urls
34
- posts += batch
35
- else
36
- batch.each {|post| write_post(post, format == "md", add_highlights)}
37
- end
38
-
39
- end until blog["posts"].size < per_page
40
-
41
- # Rewrite URLs, create redirects and write out out posts if necessary
42
- if rewrite_urls
43
- posts = rewrite_urls_and_redirects posts
44
- posts.each {|post| write_post(post, format == "md", add_highlights)}
45
- end
46
- end
47
-
48
- private
49
-
50
- # Writes a post out to disk
51
- def self.write_post(post, use_markdown, add_highlights)
52
- content = post[:content]
53
- if use_markdown
54
- content = html_to_markdown content
55
- content = add_syntax_highlights content if add_highlights
56
- end
57
-
58
- File.open("_posts/tumblr/#{post[:name]}", "w") do |f|
59
- f.puts post[:header].to_yaml + "---\n" + content
60
- end
61
- end
62
-
63
- # Converts each type of Tumblr post to a hash with all required
64
- # data for Jekyll.
65
- def self.post_to_hash(post, format)
66
- case post['type']
67
- when "regular"
68
- title = post["regular-title"]
69
- content = post["regular-body"]
70
- when "link"
71
- title = post["link-text"] || post["link-url"]
72
- content = "<a href=\"#{post["link-url"]}\">#{title}</a>"
73
- unless post["link-description"].nil?
74
- content << "<br/>" + post["link-description"]
75
- end
76
- when "photo"
77
- title = post["photo-caption"]
78
- content = if post["photo-link-url"].nil?
79
- "<a href=\"#{post["photo-link-url"]}\">#{content}</a>"
80
- else
81
- fetch_photo post
82
- end
83
- when "audio"
84
- if !post["id3-title"].nil?
85
- title = post["id3-title"]
86
- content = post["audio-player"] + "<br/>" + post["audio-caption"]
87
- else
88
- title = post["audio-caption"]
89
- content = post["audio-player"]
90
- end
91
- when "quote"
92
- title = post["quote-text"]
93
- content = "<blockquote>#{post["quote-text"]}</blockquote>"
94
- unless post["quote-source"].nil?
95
- content << "&#8212;" + post["quote-source"]
96
- end
97
- when "conversation"
98
- title = post["conversation-title"]
99
- content = "<section><dialog>"
100
- post["conversation"].each do |line|
101
- content << "<dt>#{line['label']}</dt><dd>#{line['phrase']}</dd>"
102
- end
103
- content << "</section></dialog>"
104
- when "video"
105
- title = post["video-title"]
106
- content = post["video-player"]
107
- unless post["video-caption"].nil?
108
- content << "<br/>" + post["video-caption"]
109
- end
110
- end
111
- date = Date.parse(post['date']).to_s
112
- title = Nokogiri::HTML(title).text
113
- slug = if post["slug"] && post["slug"].strip != ""
114
- post["slug"]
115
- else
116
- slug = title.downcase.strip.gsub(' ', '-').gsub(/[^\w-]/, '')
117
- slug.length > 200 ? slug.slice(0..200) : slug
118
- end
119
- {
120
- :name => "#{date}-#{slug}.#{format}",
121
- :header => {
122
- "layout" => "post",
123
- "title" => title,
124
- "tags" => post["tags"],
125
- },
126
- :content => content,
127
- :url => post["url"],
128
- :slug => post["url-with-slug"],
129
- }
130
- end
131
-
132
- # Attempts to fetch the largest version of a photo available for a post.
133
- # If that file fails, it tries the next smaller size until all available
134
- # photo URLs are exhausted. If they all fail, the import is aborted.
135
- def self.fetch_photo(post)
136
- sizes = post.keys.map {|k| k.gsub("photo-url-", "").to_i}
137
- sizes.sort! {|a,b| b <=> a}
138
-
139
- ext_key, ext_val = post.find do |k,v|
140
- k =~ /^photo-url-/ && v.split("/").last =~ /\./
141
- end
142
- ext = "." + ext_val.split(".").last
143
-
144
- sizes.each do |size|
145
- url = post["photo-url"] || post["photo-url-#{size}"]
146
- next if url.nil?
147
- begin
148
- return "<img src=\"#{save_photo(url, ext)}\"/>"
149
- rescue OpenURI::HTTPError => err
150
- puts "Failed to grab photo"
151
- end
152
- end
153
-
154
- abort "Failed to fetch photo for post #{post['url']}"
155
- end
156
-
157
- # Create a Hash of old urls => new urls, for rewriting and
158
- # redirects, and replace urls in each post. Instantiate Jekyll
159
- # site/posts to get the correct permalink format.
160
- def self.rewrite_urls_and_redirects(posts)
161
- site = Jekyll::Site.new(Jekyll.configuration({}))
162
- urls = Hash[posts.map { |post|
163
- # Create an initial empty file for the post so that
164
- # we can instantiate a post object.
165
- File.open("_posts/tumblr/#{post[:name]}", "w")
166
- tumblr_url = URI.parse(post[:slug]).path
167
- jekyll_url = Jekyll::Post.new(site, Dir.pwd, "", "tumblr/" + post[:name]).url
168
- redirect_dir = tumblr_url.sub(/\//, "") + "/"
169
- FileUtils.mkdir_p redirect_dir
170
- File.open(redirect_dir + "index.html", "w") do |f|
171
- f.puts "<html><head><meta http-equiv='Refresh' content='0; " +
172
- "url=#{jekyll_url}'></head><body></body></html>"
173
- end
174
- [tumblr_url, jekyll_url]
175
- }]
176
- posts.map { |post|
177
- urls.each do |tumblr_url, jekyll_url|
178
- post[:content].gsub!(/#{tumblr_url}/i, jekyll_url)
179
- end
180
- post
181
- }
182
- end
183
-
184
- # Convert preserving HTML tables as per the markdown docs.
185
- def self.html_to_markdown(content)
186
- preserve = ["table", "tr", "th", "td"]
187
- preserve.each do |tag|
188
- content.gsub!(/<#{tag}/i, "$$" + tag)
189
- content.gsub!(/<\/#{tag}/i, "||" + tag)
190
- end
191
- content = Nokogiri::HTML(content.gsub("'", "''")).text
192
- preserve.each do |tag|
193
- content.gsub!("$$" + tag, "<" + tag)
194
- content.gsub!("||" + tag, "</" + tag)
195
- end
196
- content
197
- end
198
-
199
- # Adds pygments highlight tags to code blocks in posts that use
200
- # markdown format. This doesn't guess the language of the code
201
- # block, so you should modify this to suit your own content.
202
- # For example, my code block only contain Python and JavaScript,
203
- # so I can assume the block is JavaScript if it contains a
204
- # semi-colon.
205
- def self.add_syntax_highlights(content)
206
- lines = content.split("\n")
207
- block, indent, lang, start = false, /^ /, nil, nil
208
- lines.each_with_index do |line, i|
209
- if !block && line =~ indent
210
- block = true
211
- lang = "python"
212
- start = i
213
- elsif block
214
- lang = "javascript" if line =~ /;$/
215
- block = line =~ indent && i < lines.size - 1 # Also handle EOF
216
- if !block
217
- lines[start] = "{% highlight #{lang} %}"
218
- lines[i - 1] = "{% endhighlight %}"
219
- end
220
- lines[i] = lines[i].sub(indent, "")
221
- end
222
- end
223
- lines.join("\n")
224
- end
225
-
226
- def self.save_photo(url, ext)
227
- if @grab_images
228
- path = "tumblr_files/#{url.split('/').last}"
229
- path += ext unless path =~ /#{ext}$/
230
- FileUtils.mkdir_p "tumblr_files"
231
-
232
- # Don't fetch if we've already cached this file
233
- unless File.size? path
234
- puts "Fetching photo #{url}"
235
- File.open(path, "w") { |f| f.write(open(url).read) }
236
- end
237
- url = "/" + path
238
- end
239
- url
240
- end
241
- end
242
- end
@@ -1,69 +0,0 @@
1
- # Author: Toby DiPasquale <toby@cbcg.net>
2
- require 'fileutils'
3
- require 'rubygems'
4
- require 'sequel'
5
- require 'safe_yaml'
6
-
7
- module JekyllImport
8
- module Typo
9
- # This SQL *should* work for both MySQL and PostgreSQL.
10
- SQL = <<-EOS
11
- SELECT c.id id,
12
- c.title title,
13
- c.permalink slug,
14
- c.body body,
15
- c.extended extended,
16
- c.published_at date,
17
- c.state state,
18
- c.keywords keywords,
19
- COALESCE(tf.name, 'html') filter
20
- FROM contents c
21
- LEFT OUTER JOIN text_filters tf
22
- ON c.text_filter_id = tf.id
23
- EOS
24
-
25
- def self.process server, dbname, user, pass, host='localhost'
26
- FileUtils.mkdir_p '_posts'
27
- case server.intern
28
- when :postgres
29
- db = Sequel.postgres(dbname, :user => user, :password => pass, :host => host, :encoding => 'utf8')
30
- when :mysql
31
- db = Sequel.mysql(dbname, :user => user, :password => pass, :host => host, :encoding => 'utf8')
32
- else
33
- raise "Unknown database server '#{server}'"
34
- end
35
- db[SQL].each do |post|
36
- next unless post[:state] =~ /published/i
37
-
38
- if post[:slug] == nil
39
- post[:slug] = "no slug"
40
- end
41
-
42
- if post[:extended]
43
- post[:body] << "\n<!-- more -->\n"
44
- post[:body] << post[:extended]
45
- end
46
-
47
- name = [ sprintf("%.04d", post[:date].year),
48
- sprintf("%.02d", post[:date].month),
49
- sprintf("%.02d", post[:date].day),
50
- post[:slug].strip ].join('-')
51
-
52
- # Can have more than one text filter in this field, but we just want
53
- # the first one for this.
54
- name += '.' + post[:filter].split(' ')[0]
55
-
56
- File.open("_posts/#{name}", 'w') do |f|
57
- f.puts({ 'layout' => 'post',
58
- 'title' => (post[:title] and post[:title].to_s.force_encoding('UTF-8')),
59
- 'tags' => (post[:keywords] and post[:keywords].to_s.force_encoding('UTF-8')),
60
- 'typo_id' => post[:id]
61
- }.delete_if { |k, v| v.nil? || v == '' }.to_yaml)
62
- f.puts '---'
63
- f.puts post[:body].delete("\r")
64
- end
65
- end
66
- end
67
-
68
- end
69
- end
@@ -1,299 +0,0 @@
1
- require 'rubygems'
2
- require 'sequel'
3
- require 'fileutils'
4
- require 'safe_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 JekyllImport
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(options={})
54
- options = {
55
- :user => '',
56
- :pass => '',
57
- :host => 'localhost',
58
- :dbname => '',
59
- :table_prefix => 'wp_',
60
- :clean_entities => true,
61
- :comments => true,
62
- :categories => true,
63
- :tags => true,
64
- :more_excerpt => true,
65
- :more_anchor => true,
66
- :status => [:publish] # :draft, :private, :revision
67
- }.merge(options)
68
-
69
- if options[:clean_entities]
70
- begin
71
- require 'htmlentities'
72
- rescue LoadError
73
- STDERR.puts "Could not require 'htmlentities', so the " +
74
- ":clean_entities option is now disabled."
75
- options[:clean_entities] = false
76
- end
77
- end
78
-
79
- FileUtils.mkdir_p("_posts")
80
-
81
- db = Sequel.mysql2(options[:dbname], :user => options[:user], :password => options[:pass],
82
- :host => options[:host], :encoding => 'utf8')
83
-
84
- px = options[:table_prefix]
85
-
86
- posts_query = "
87
- SELECT
88
- posts.ID AS `id`,
89
- posts.guid AS `guid`,
90
- posts.post_type AS `type`,
91
- posts.post_status AS `status`,
92
- posts.post_title AS `title`,
93
- posts.post_name AS `slug`,
94
- posts.post_date AS `date`,
95
- posts.post_content AS `content`,
96
- posts.post_excerpt AS `excerpt`,
97
- posts.comment_count AS `comment_count`,
98
- users.display_name AS `author`,
99
- users.user_login AS `author_login`,
100
- users.user_email AS `author_email`,
101
- users.user_url AS `author_url`
102
- FROM #{px}posts AS `posts`
103
- LEFT JOIN #{px}users AS `users`
104
- ON posts.post_author = users.ID"
105
-
106
- if options[:status] and not options[:status].empty?
107
- status = options[:status][0]
108
- posts_query << "
109
- WHERE posts.post_status = '#{status.to_s}'"
110
- options[:status][1..-1].each do |status|
111
- posts_query << " OR
112
- posts.post_status = '#{status.to_s}'"
113
- end
114
- end
115
-
116
- db[posts_query].each do |post|
117
- process_post(post, db, options)
118
- end
119
- end
120
-
121
-
122
- def self.process_post(post, db, options)
123
- px = options[:table_prefix]
124
-
125
- title = post[:title]
126
- if options[:clean_entities]
127
- title = clean_entities(title)
128
- end
129
-
130
- slug = post[:slug]
131
- if !slug or slug.empty?
132
- slug = sluggify(title)
133
- end
134
-
135
- date = post[:date] || Time.now
136
- name = "%02d-%02d-%02d-%s.markdown" % [date.year, date.month,
137
- date.day, slug]
138
- content = post[:content].to_s
139
- if options[:clean_entities]
140
- content = clean_entities(content)
141
- end
142
-
143
- excerpt = post[:excerpt].to_s
144
-
145
- more_index = content.index(/<!-- *more *-->/)
146
- more_anchor = nil
147
- if more_index
148
- if options[:more_excerpt] and
149
- (post[:excerpt].nil? or post[:excerpt].empty?)
150
- excerpt = content[0...more_index]
151
- end
152
- if options[:more_anchor]
153
- more_link = "more"
154
- content.sub!(/<!-- *more *-->/,
155
- "<a id=\"more\"></a>" +
156
- "<a id=\"more-#{post[:id]}\"></a>")
157
- end
158
- end
159
-
160
- categories = []
161
- tags = []
162
-
163
- if options[:categories] or options[:tags]
164
-
165
- cquery =
166
- "SELECT
167
- terms.name AS `name`,
168
- ttax.taxonomy AS `type`
169
- FROM
170
- #{px}terms AS `terms`,
171
- #{px}term_relationships AS `trels`,
172
- #{px}term_taxonomy AS `ttax`
173
- WHERE
174
- trels.object_id = '#{post[:id]}' AND
175
- trels.term_taxonomy_id = ttax.term_taxonomy_id AND
176
- terms.term_id = ttax.term_id"
177
-
178
- db[cquery].each do |term|
179
- if options[:categories] and term[:type] == "category"
180
- if options[:clean_entities]
181
- categories << clean_entities(term[:name])
182
- else
183
- categories << term[:name]
184
- end
185
- elsif options[:tags] and term[:type] == "post_tag"
186
- if options[:clean_entities]
187
- tags << clean_entities(term[:name])
188
- else
189
- tags << term[:name]
190
- end
191
- end
192
- end
193
- end
194
-
195
- comments = []
196
-
197
- if options[:comments] and post[:comment_count].to_i > 0
198
- cquery =
199
- "SELECT
200
- comment_ID AS `id`,
201
- comment_author AS `author`,
202
- comment_author_email AS `author_email`,
203
- comment_author_url AS `author_url`,
204
- comment_date AS `date`,
205
- comment_date_gmt AS `date_gmt`,
206
- comment_content AS `content`
207
- FROM #{px}comments
208
- WHERE
209
- comment_post_ID = '#{post[:id]}' AND
210
- comment_approved != 'spam'"
211
-
212
-
213
- db[cquery].each do |comment|
214
-
215
- comcontent = comment[:content].to_s
216
- if comcontent.respond_to?(:force_encoding)
217
- comcontent.force_encoding("UTF-8")
218
- end
219
- if options[:clean_entities]
220
- comcontent = clean_entities(comcontent)
221
- end
222
- comauthor = comment[:author].to_s
223
- if options[:clean_entities]
224
- comauthor = clean_entities(comauthor)
225
- end
226
-
227
- comments << {
228
- 'id' => comment[:id].to_i,
229
- 'author' => comauthor,
230
- 'author_email' => comment[:author_email].to_s,
231
- 'author_url' => comment[:author_url].to_s,
232
- 'date' => comment[:date].to_s,
233
- 'date_gmt' => comment[:date_gmt].to_s,
234
- 'content' => comcontent,
235
- }
236
- end
237
-
238
- comments.sort!{ |a,b| a['id'] <=> b['id'] }
239
- end
240
-
241
- # Get the relevant fields as a hash, delete empty fields and
242
- # convert to YAML for the header.
243
- data = {
244
- 'layout' => post[:type].to_s,
245
- 'status' => post[:status].to_s,
246
- 'published' => (post[:status].to_s == "publish"),
247
- 'title' => title.to_s,
248
- 'author' => post[:author].to_s,
249
- 'author_login' => post[:author_login].to_s,
250
- 'author_email' => post[:author_email].to_s,
251
- 'author_url' => post[:author_url].to_s,
252
- 'excerpt' => excerpt,
253
- 'more_anchor' => more_anchor,
254
- 'wordpress_id' => post[:id],
255
- 'wordpress_url' => post[:guid].to_s,
256
- 'date' => date,
257
- 'categories' => options[:categories] ? categories : nil,
258
- 'tags' => options[:tags] ? tags : nil,
259
- 'comments' => options[:comments] ? comments : nil,
260
- }.delete_if { |k,v| v.nil? || v == '' }.to_yaml
261
-
262
- # Write out the data and content to file
263
- File.open("_posts/#{name}", "w") do |f|
264
- f.puts data
265
- f.puts "---"
266
- f.puts content
267
- end
268
- end
269
-
270
-
271
- def self.clean_entities( text )
272
- if text.respond_to?(:force_encoding)
273
- text.force_encoding("UTF-8")
274
- end
275
- text = HTMLEntities.new.encode(text, :named)
276
- # We don't want to convert these, it would break all
277
- # HTML tags in the post and comments.
278
- text.gsub!("&amp;", "&")
279
- text.gsub!("&lt;", "<")
280
- text.gsub!("&gt;", ">")
281
- text.gsub!("&quot;", '"')
282
- text.gsub!("&apos;", "'")
283
- text.gsub!("/", "&#47;")
284
- text
285
- end
286
-
287
-
288
- def self.sluggify( title )
289
- begin
290
- require 'unidecode'
291
- title = title.to_ascii
292
- rescue LoadError
293
- STDERR.puts "Could not require 'unidecode'. If your post titles have non-ASCII characters, you could get nicer permalinks by installing unidecode."
294
- end
295
- title.downcase.gsub(/[^0-9A-Za-z]+/, " ").strip.gsub(" ", "-")
296
- end
297
-
298
- end
299
- end