bunto-import 2.0.0 → 3.0.0

Sign up to get free protection for your applications and to get access to all the features.
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