bunto-import 2.0.0 → 3.0.0

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 (35) hide show
  1. checksums.yaml +4 -4
  2. data/LICENSE +21 -21
  3. data/README.markdown +33 -33
  4. data/lib/bunto-import.rb +49 -49
  5. data/lib/bunto-import/importer.rb +26 -26
  6. data/lib/bunto-import/importers.rb +10 -10
  7. data/lib/bunto-import/importers/behance.rb +80 -80
  8. data/lib/bunto-import/importers/blogger.rb +330 -264
  9. data/lib/bunto-import/importers/csv.rb +96 -96
  10. data/lib/bunto-import/importers/drupal6.rb +53 -139
  11. data/lib/bunto-import/importers/drupal7.rb +54 -111
  12. data/lib/bunto-import/importers/drupal_common.rb +157 -0
  13. data/lib/bunto-import/importers/easyblog.rb +96 -96
  14. data/lib/bunto-import/importers/enki.rb +74 -74
  15. data/lib/bunto-import/importers/ghost.rb +68 -68
  16. data/lib/bunto-import/importers/google_reader.rb +64 -64
  17. data/lib/bunto-import/importers/joomla.rb +92 -90
  18. data/lib/bunto-import/importers/joomla3.rb +91 -91
  19. data/lib/bunto-import/importers/jrnl.rb +125 -125
  20. data/lib/bunto-import/importers/marley.rb +72 -72
  21. data/lib/bunto-import/importers/mephisto.rb +99 -99
  22. data/lib/bunto-import/importers/mt.rb +257 -257
  23. data/lib/bunto-import/importers/posterous.rb +130 -130
  24. data/lib/bunto-import/importers/rss.rb +62 -62
  25. data/lib/bunto-import/importers/s9y.rb +60 -60
  26. data/lib/bunto-import/importers/s9y_database.rb +363 -0
  27. data/lib/bunto-import/importers/textpattern.rb +70 -70
  28. data/lib/bunto-import/importers/tumblr.rb +300 -289
  29. data/lib/bunto-import/importers/typo.rb +88 -88
  30. data/lib/bunto-import/importers/wordpress.rb +372 -372
  31. data/lib/bunto-import/importers/wordpressdotcom.rb +207 -207
  32. data/lib/bunto-import/util.rb +76 -76
  33. data/lib/bunto-import/version.rb +3 -3
  34. data/lib/bunto/commands/import.rb +79 -79
  35. metadata +84 -54
@@ -1,289 +1,300 @@
1
- module BuntoImport
2
- module Importers
3
- class Tumblr < Importer
4
- def self.require_deps
5
- BuntoImport.require_with_fallback(%w[
6
- rubygems
7
- fileutils
8
- open-uri
9
- nokogiri
10
- json
11
- uri
12
- time
13
- bunto
14
- ])
15
- end
16
-
17
- def self.specify_options(c)
18
- c.option 'url', '--url URL', 'Tumblr URL'
19
- c.option 'format', '--format FORMAT', 'Output format (default: "html")'
20
- c.option 'grab_images', '--grab_images', 'Whether to grab images (default: false)'
21
- c.option 'add_highlights', '--add_highlights', 'Whether to add highlights (default: false)'
22
- c.option 'rewrite_urls', '--rewrite_urls', 'Whether to rewrite URLs (default: false)'
23
- end
24
-
25
- def self.process(options)
26
- url = options.fetch('url')
27
- format = options.fetch('format', "html")
28
- grab_images = options.fetch('grab_images', false)
29
- add_highlights = options.fetch('add_highlights', false)
30
- rewrite_urls = options.fetch('rewrite_urls', false)
31
-
32
- @grab_images = grab_images
33
- FileUtils.mkdir_p "_posts/tumblr"
34
- url += "/api/read/json/"
35
- per_page = 50
36
- posts = []
37
- # Two passes are required so that we can rewrite URLs.
38
- # First pass builds up an array of each post as a hash.
39
- begin
40
- current_page = (current_page || -1) + 1
41
- feed_url = url + "?num=#{per_page}&start=#{current_page * per_page}"
42
- puts "Fetching #{feed_url}"
43
- feed = open(feed_url)
44
- json = feed.readlines.join("\n")[21...-2] # Strip Tumblr's JSONP chars.
45
- blog = JSON.parse(json)
46
- puts "Page: #{current_page + 1} - Posts: #{blog["posts"].size}"
47
- batch = blog["posts"].map { |post| post_to_hash(post, format) }
48
-
49
- # If we're rewriting, save the posts for later. Otherwise, go ahead and
50
- # dump these to disk now
51
- if rewrite_urls
52
- posts += batch
53
- else
54
- batch.each {|post| write_post(post, format == "md", add_highlights)}
55
- end
56
-
57
- end until blog["posts"].size < per_page
58
-
59
- # Rewrite URLs, create redirects and write out out posts if necessary
60
- if rewrite_urls
61
- posts = rewrite_urls_and_redirects posts
62
- posts.each {|post| write_post(post, format == "md", add_highlights)}
63
- end
64
- end
65
-
66
- private
67
-
68
- # Writes a post out to disk
69
- def self.write_post(post, use_markdown, add_highlights)
70
- content = post[:content]
71
-
72
- if content
73
- if use_markdown
74
- content = html_to_markdown content
75
- if add_highlights
76
- tumblr_url = URI.parse(post[:slug]).path
77
- redirect_dir = tumblr_url.sub(/\//, "") + "/"
78
- FileUtils.mkdir_p redirect_dir
79
- content = add_syntax_highlights(content, redirect_dir)
80
- end
81
- end
82
-
83
- File.open("_posts/tumblr/#{post[:name]}", "w") do |f|
84
- f.puts post[:header].to_yaml + "---\n" + content
85
- end
86
- end
87
- end
88
-
89
- # Converts each type of Tumblr post to a hash with all required
90
- # data for Bunto.
91
- def self.post_to_hash(post, format)
92
- case post['type']
93
- when "regular"
94
- title = post["regular-title"]
95
- content = post["regular-body"]
96
- when "link"
97
- title = post["link-text"] || post["link-url"]
98
- content = "<a href=\"#{post["link-url"]}\">#{title}</a>"
99
- unless post["link-description"].nil?
100
- content << "<br/>" + post["link-description"]
101
- end
102
- when "photo"
103
- title = post["slug"].gsub("-"," ")
104
- if post["photos"].size > 1
105
- content = ""
106
- post["photos"].each do |post_photo|
107
- photo = fetch_photo post_photo
108
- content << photo + "<br/>"
109
- content << post_photo["caption"]
110
- end
111
- else
112
- content = fetch_photo post
113
- end
114
- content << "<br/>" + post["photo-caption"]
115
- when "audio"
116
- if !post["id3-title"].nil?
117
- title = post["id3-title"]
118
- content = post["audio-player"] + "<br/>" + post["audio-caption"]
119
- else
120
- title = post["audio-caption"]
121
- content = post["audio-player"]
122
- end
123
- when "quote"
124
- title = post["quote-text"]
125
- content = "<blockquote>#{post["quote-text"]}</blockquote>"
126
- unless post["quote-source"].nil?
127
- content << "&#8212;" + post["quote-source"]
128
- end
129
- when "conversation"
130
- title = post["conversation-title"]
131
- content = "<section><dialog>"
132
- post["conversation"].each do |line|
133
- content << "<dt>#{line['label']}</dt><dd>#{line['phrase']}</dd>"
134
- end
135
- content << "</section></dialog>"
136
- when "video"
137
- title = post["video-title"]
138
- content = post["video-player"]
139
- unless post["video-caption"].nil?
140
- unless content.nil?
141
- content << "<br/>" + post["video-caption"]
142
- else
143
- content = post["video-caption"]
144
- end
145
- end
146
- when "answer"
147
- title = post["question"]
148
- content = post["answer"]
149
- end
150
- date = Date.parse(post['date']).to_s
151
- title = Nokogiri::HTML(title).text
152
- title = "no title" if title.empty?
153
- slug = if post["slug"] && post["slug"].strip != ""
154
- post["slug"]
155
- elsif title && title.downcase.gsub(/[^a-z0-9\-]/, '') != '' && title != 'no title'
156
- slug = title.downcase.strip.gsub(' ', '-').gsub(/[^a-z0-9\-]/, '')
157
- slug.length > 200 ? slug.slice(0..200) : slug
158
- else
159
- slug = post['id']
160
- end
161
- {
162
- :name => "#{date}-#{slug}.#{format}",
163
- :header => {
164
- "layout" => "post",
165
- "title" => title,
166
- "date" => Time.parse(post['date']).xmlschema,
167
- "tags" => (post["tags"] or []),
168
- "tumblr_url" => post["url-with-slug"]
169
- },
170
- :content => content,
171
- :url => post["url"],
172
- :slug => post["url-with-slug"],
173
- }
174
- end
175
-
176
- # Attempts to fetch the largest version of a photo available for a post.
177
- # If that file fails, it tries the next smaller size until all available
178
- # photo URLs are exhausted. If they all fail, the import is aborted.
179
- def self.fetch_photo(post)
180
- sizes = post.keys.map {|k| k.gsub("photo-url-", "").to_i}
181
- sizes.sort! {|a,b| b <=> a}
182
-
183
- ext_key, ext_val = post.find do |k,v|
184
- k =~ /^photo-url-/ && v.split("/").last =~ /\./
185
- end
186
- ext = "." + ext_val.split(".").last
187
-
188
- sizes.each do |size|
189
- url = post["photo-url"] || post["photo-url-#{size}"]
190
- next if url.nil?
191
- begin
192
- return "<img src=\"#{save_photo(url, ext)}\"/>"
193
- rescue OpenURI::HTTPError => err
194
- puts "Failed to grab photo"
195
- end
196
- end
197
-
198
- abort "Failed to fetch photo for post #{post['url']}"
199
- end
200
-
201
- # Create a Hash of old urls => new urls, for rewriting and
202
- # redirects, and replace urls in each post. Instantiate Bunto
203
- # site/posts to get the correct permalink format.
204
- def self.rewrite_urls_and_redirects(posts)
205
- site = Bunto::Site.new(Bunto.configuration({}))
206
- urls = Hash[posts.map { |post|
207
- # Create an initial empty file for the post so that
208
- # we can instantiate a post object.
209
- File.open("_posts/tumblr/#{post[:name]}", "w")
210
- tumblr_url = URI.parse(URI.encode(post[:slug])).path
211
- bunto_url = Bunto::Post.new(site, Dir.pwd, "", "tumblr/" + post[:name]).url
212
- redirect_dir = tumblr_url.sub(/\//, "") + "/"
213
- FileUtils.mkdir_p redirect_dir
214
- File.open(redirect_dir + "index.html", "w") do |f|
215
- f.puts "<html><head><link rel=\"canonical\" href=\"" +
216
- "#{bunto_url}\"><meta http-equiv=\"refresh\" content=\"0; " +
217
- "url=#{bunto_url}\"></head><body></body></html>"
218
- end
219
- [tumblr_url, bunto_url]
220
- }]
221
- posts.map { |post|
222
- urls.each do |tumblr_url, bunto_url|
223
- post[:content].gsub!(/#{tumblr_url}/i, bunto_url)
224
- end
225
- post
226
- }
227
- end
228
-
229
- # Convert preserving HTML tables as per the markdown docs.
230
- def self.html_to_markdown(content)
231
- preserve = ["table", "tr", "th", "td"]
232
- preserve.each do |tag|
233
- content.gsub!(/<#{tag}/i, "$$" + tag)
234
- content.gsub!(/<\/#{tag}/i, "||" + tag)
235
- end
236
- content = Nokogiri::HTML(content.gsub("'", "''")).text
237
- preserve.each do |tag|
238
- content.gsub!("$$" + tag, "<" + tag)
239
- content.gsub!("||" + tag, "</" + tag)
240
- end
241
- content
242
- end
243
-
244
- # Adds pygments highlight tags to code blocks in posts that use
245
- # markdown format. This doesn't guess the language of the code
246
- # block, so you should modify this to suit your own content.
247
- # For example, my code block only contain Python and JavaScript,
248
- # so I can assume the block is JavaScript if it contains a
249
- # semi-colon.
250
- def self.add_syntax_highlights(content, redirect_dir)
251
- lines = content.split("\n")
252
- block, indent, lang, start = false, /^ /, nil, nil
253
- lines.each_with_index do |line, i|
254
- if !block && line =~ indent
255
- block = true
256
- lang = "python"
257
- start = i
258
- elsif block
259
- lang = "javascript" if line =~ /;$/
260
- block = line =~ indent && i < lines.size - 1 # Also handle EOF
261
- if !block
262
- lines[start] = "{% highlight #{lang} %}"
263
- lines[i - 1] = "{% endhighlight %}"
264
- end
265
- FileUtils.cp(redirect_dir + "index.html", redirect_dir + "../" + "index.html")
266
- lines[i] = lines[i].sub(indent, "")
267
- end
268
- end
269
- lines.join("\n")
270
- end
271
-
272
- def self.save_photo(url, ext)
273
- if @grab_images
274
- path = "tumblr_files/#{url.split('/').last}"
275
- path += ext unless path =~ /#{ext}$/
276
- FileUtils.mkdir_p "tumblr_files"
277
-
278
- # Don't fetch if we've already cached this file
279
- unless File.size? path
280
- puts "Fetching photo #{url}"
281
- File.open(path, "w") { |f| f.write(open(url).read) }
282
- end
283
- url = "/" + path
284
- end
285
- url
286
- end
287
- end
288
- end
289
- end
1
+ module BuntoImport
2
+ module Importers
3
+ class Tumblr < Importer
4
+ def self.require_deps
5
+ BuntoImport.require_with_fallback(%w[
6
+ rubygems
7
+ fileutils
8
+ open-uri
9
+ nokogiri
10
+ json
11
+ uri
12
+ time
13
+ bunto
14
+ ])
15
+ end
16
+
17
+ def self.specify_options(c)
18
+ c.option 'url', '--url URL', 'Tumblr URL'
19
+ c.option 'format', '--format FORMAT', 'Output format (default: "html")'
20
+ c.option 'grab_images', '--grab_images', 'Whether to grab images (default: false)'
21
+ c.option 'add_highlights', '--add_highlights', 'Whether to add highlights (default: false)'
22
+ c.option 'rewrite_urls', '--rewrite_urls', 'Whether to rewrite URLs (default: false)'
23
+ end
24
+
25
+ def self.process(options)
26
+ url = options.fetch('url')
27
+ format = options.fetch('format', "html")
28
+ grab_images = options.fetch('grab_images', false)
29
+ add_highlights = options.fetch('add_highlights', false)
30
+ rewrite_urls = options.fetch('rewrite_urls', false)
31
+
32
+ @grab_images = grab_images
33
+ FileUtils.mkdir_p "_posts/tumblr"
34
+ url += "/api/read/json/"
35
+ per_page = 50
36
+ posts = []
37
+ # Two passes are required so that we can rewrite URLs.
38
+ # First pass builds up an array of each post as a hash.
39
+ begin
40
+ current_page = (current_page || -1) + 1
41
+ feed_url = url + "?num=#{per_page}&start=#{current_page * per_page}"
42
+ puts "Fetching #{feed_url}"
43
+ feed = open(feed_url)
44
+ contents = feed.readlines.join("\n")
45
+ blog = extract_json(contents)
46
+ puts "Page: #{current_page + 1} - Posts: #{blog["posts"].size}"
47
+ batch = blog["posts"].map { |post| post_to_hash(post, format) }
48
+
49
+ # If we're rewriting, save the posts for later. Otherwise, go ahead and
50
+ # dump these to disk now
51
+ if rewrite_urls
52
+ posts += batch
53
+ else
54
+ batch.each {|post| write_post(post, format == "md", add_highlights)}
55
+ end
56
+
57
+ end until blog["posts"].size < per_page
58
+
59
+ # Rewrite URLs, create redirects and write out out posts if necessary
60
+ if rewrite_urls
61
+ posts = rewrite_urls_and_redirects posts
62
+ posts.each {|post| write_post(post, format == "md", add_highlights)}
63
+ end
64
+ end
65
+
66
+ private
67
+
68
+ def self.extract_json(contents)
69
+ beginning = contents.index("{")
70
+ ending = contents.rindex("}")+1
71
+ json = contents[beginning...ending] # Strip Tumblr's JSONP chars.
72
+ blog = JSON.parse(json)
73
+ end
74
+
75
+ # Writes a post out to disk
76
+ def self.write_post(post, use_markdown, add_highlights)
77
+ content = post[:content]
78
+
79
+ if content
80
+ if use_markdown
81
+ content = html_to_markdown content
82
+ if add_highlights
83
+ tumblr_url = URI.parse(post[:slug]).path
84
+ redirect_dir = tumblr_url.sub(/\//, "") + "/"
85
+ FileUtils.mkdir_p redirect_dir
86
+ content = add_syntax_highlights(content, redirect_dir)
87
+ end
88
+ end
89
+
90
+ File.open("_posts/tumblr/#{post[:name]}", "w") do |f|
91
+ f.puts post[:header].to_yaml + "---\n" + content
92
+ end
93
+ end
94
+ end
95
+
96
+ # Converts each type of Tumblr post to a hash with all required
97
+ # data for Bunto.
98
+ def self.post_to_hash(post, format)
99
+ case post['type']
100
+ when "regular"
101
+ title = post["regular-title"]
102
+ content = post["regular-body"]
103
+ when "link"
104
+ title = post["link-text"] || post["link-url"]
105
+ content = "<a href=\"#{post["link-url"]}\">#{title}</a>"
106
+ unless post["link-description"].nil?
107
+ content << "<br/>" + post["link-description"]
108
+ end
109
+ when "photo"
110
+ title = post["slug"].gsub("-"," ")
111
+ if post["photos"].size > 1
112
+ content = ""
113
+ post["photos"].each do |post_photo|
114
+ photo = fetch_photo post_photo
115
+ content << photo + "<br/>"
116
+ content << post_photo["caption"]
117
+ end
118
+ else
119
+ content = fetch_photo post
120
+ end
121
+ content << "<br/>" + post["photo-caption"]
122
+ when "audio"
123
+ if !post["id3-title"].nil?
124
+ title = post["id3-title"]
125
+ content = post["audio-player"] + "<br/>" + post["audio-caption"]
126
+ else
127
+ title = post["audio-caption"]
128
+ content = post["audio-player"]
129
+ end
130
+ when "quote"
131
+ title = post["quote-text"]
132
+ content = "<blockquote>#{post["quote-text"]}</blockquote>"
133
+ unless post["quote-source"].nil?
134
+ content << "&#8212;" + post["quote-source"]
135
+ end
136
+ when "conversation"
137
+ title = post["conversation-title"]
138
+ content = "<section><dialog>"
139
+ post["conversation"].each do |line|
140
+ content << "<dt>#{line['label']}</dt><dd>#{line['phrase']}</dd>"
141
+ end
142
+ content << "</dialog></section>"
143
+ when "video"
144
+ title = post["video-title"]
145
+ content = post["video-player"]
146
+ unless post["video-caption"].nil?
147
+ if content
148
+ content << "<br/>" + post["video-caption"]
149
+ else
150
+ content = post["video-caption"]
151
+ end
152
+ end
153
+ when "answer"
154
+ title = post["question"]
155
+ content = post["answer"]
156
+ end
157
+ date = Date.parse(post['date']).to_s
158
+ title = Nokogiri::HTML(title).text
159
+ title = "no title" if title.empty?
160
+ slug = if post["slug"] && post["slug"].strip != ""
161
+ post["slug"]
162
+ elsif title && title.downcase.gsub(/[^a-z0-9\-]/, '') != '' && title != 'no title'
163
+ slug = title.downcase.strip.gsub(' ', '-').gsub(/[^a-z0-9\-]/, '')
164
+ slug.length > 200 ? slug.slice(0..200) : slug
165
+ else
166
+ slug = post['id']
167
+ end
168
+ {
169
+ :name => "#{date}-#{slug}.#{format}",
170
+ :header => {
171
+ "layout" => "post",
172
+ "title" => title,
173
+ "date" => Time.parse(post['date']).xmlschema,
174
+ "tags" => (post["tags"] or []),
175
+ "tumblr_url" => post["url-with-slug"]
176
+ },
177
+ :content => content,
178
+ :url => post["url"],
179
+ :slug => post["url-with-slug"],
180
+ }
181
+ end
182
+
183
+ # Attempts to fetch the largest version of a photo available for a post.
184
+ # If that file fails, it tries the next smaller size until all available
185
+ # photo URLs are exhausted. If they all fail, the import is aborted.
186
+ def self.fetch_photo(post)
187
+ sizes = post.keys.map {|k| k.gsub("photo-url-", "").to_i}
188
+ sizes.sort! {|a,b| b <=> a}
189
+
190
+ ext_key, ext_val = post.find do |k,v|
191
+ k =~ /^photo-url-/ && v.split("/").last =~ /\./
192
+ end
193
+ ext = "." + ext_val.split(".").last
194
+
195
+ sizes.each do |size|
196
+ url = post["photo-url"] || post["photo-url-#{size}"]
197
+ next if url.nil?
198
+ begin
199
+ return "<img src=\"#{save_photo(url, ext)}\"/>"
200
+ rescue OpenURI::HTTPError => err
201
+ puts "Failed to grab photo"
202
+ end
203
+ end
204
+
205
+ abort "Failed to fetch photo for post #{post['url']}"
206
+ end
207
+
208
+ # Create a Hash of old urls => new urls, for rewriting and
209
+ # redirects, and replace urls in each post. Instantiate Bunto
210
+ # site/posts to get the correct permalink format.
211
+ def self.rewrite_urls_and_redirects(posts)
212
+ site = Bunto::Site.new(Bunto.configuration({}))
213
+ urls = Hash[posts.map { |post|
214
+ # Create an initial empty file for the post so that
215
+ # we can instantiate a post object.
216
+ tumblr_url = URI.parse(URI.encode(post[:slug])).path
217
+ bunto_url = if Bunto.const_defined? :Post
218
+ File.open("_posts/tumblr/#{post[:name]}", "w") { |f| f.puts }
219
+ Bunto::Post.new(site, Dir.pwd, "", "tumblr/" + post[:name]).url
220
+ else
221
+ Bunto::Document.new(File.expand_path("tumblr/#{post[:name]}"), site: site, collection: site.posts).url
222
+ end
223
+ redirect_dir = tumblr_url.sub(/\//, "") + "/"
224
+ FileUtils.mkdir_p redirect_dir
225
+ File.open(redirect_dir + "index.html", "w") do |f|
226
+ f.puts "<html><head><link rel=\"canonical\" href=\"" +
227
+ "#{bunto_url}\"><meta http-equiv=\"refresh\" content=\"0; " +
228
+ "url=#{bunto_url}\"></head><body></body></html>"
229
+ end
230
+ [tumblr_url, bunto_url]
231
+ }]
232
+ posts.map { |post|
233
+ urls.each do |tumblr_url, bunto_url|
234
+ post[:content].gsub!(/#{tumblr_url}/i, bunto_url)
235
+ end
236
+ post
237
+ }
238
+ end
239
+
240
+ # Convert preserving HTML tables as per the markdown docs.
241
+ def self.html_to_markdown(content)
242
+ preserve = ["table", "tr", "th", "td"]
243
+ preserve.each do |tag|
244
+ content.gsub!(/<#{tag}/i, "$$" + tag)
245
+ content.gsub!(/<\/#{tag}/i, "||" + tag)
246
+ end
247
+ content = Nokogiri::HTML(content.gsub("'", "''")).text
248
+ preserve.each do |tag|
249
+ content.gsub!("$$" + tag, "<" + tag)
250
+ content.gsub!("||" + tag, "</" + tag)
251
+ end
252
+ content
253
+ end
254
+
255
+ # Adds pygments highlight tags to code blocks in posts that use
256
+ # markdown format. This doesn't guess the language of the code
257
+ # block, so you should modify this to suit your own content.
258
+ # For example, my code block only contain Python and JavaScript,
259
+ # so I can assume the block is JavaScript if it contains a
260
+ # semi-colon.
261
+ def self.add_syntax_highlights(content, redirect_dir)
262
+ lines = content.split("\n")
263
+ block, indent, lang, start = false, /^ /, nil, nil
264
+ lines.each_with_index do |line, i|
265
+ if !block && line =~ indent
266
+ block = true
267
+ lang = "python"
268
+ start = i
269
+ elsif block
270
+ lang = "javascript" if line =~ /;$/
271
+ block = line =~ indent && i < lines.size - 1 # Also handle EOF
272
+ if !block
273
+ lines[start] = "{% highlight #{lang} %}"
274
+ lines[i - 1] = "{% endhighlight %}"
275
+ end
276
+ FileUtils.cp(redirect_dir + "index.html", redirect_dir + "../" + "index.html")
277
+ lines[i] = lines[i].sub(indent, "")
278
+ end
279
+ end
280
+ lines.join("\n")
281
+ end
282
+
283
+ def self.save_photo(url, ext)
284
+ if @grab_images
285
+ path = "tumblr_files/#{url.split('/').last}"
286
+ path += ext unless path =~ /#{ext}$/
287
+ FileUtils.mkdir_p "tumblr_files"
288
+
289
+ # Don't fetch if we've already cached this file
290
+ unless File.size? path
291
+ puts "Fetching photo #{url}"
292
+ File.open(path, "w") { |f| f.write(open(url).read) }
293
+ end
294
+ url = "/" + path
295
+ end
296
+ url
297
+ end
298
+ end
299
+ end
300
+ end