tumblr4r 0.7.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.
data/ChangeLog ADDED
@@ -0,0 +1,4 @@
1
+ == 0.0.1 / 2009-09-07
2
+
3
+ * initial release
4
+
data/README ADDED
@@ -0,0 +1,48 @@
1
+ = tumblr4r
2
+
3
+ * http://github.com/tmaeda/tumblr4r
4
+
5
+ == Description
6
+
7
+ Tumblr API wrapper for Ruby.
8
+
9
+ == Synopsis
10
+
11
+ Finding by conditions.
12
+ require 'rubygems'
13
+ require 'tumblr4r'
14
+
15
+ site_a = Tumblr4r::Site.new("site_a.tumblr.com")
16
+ posts = site_a.find(:all)
17
+ quote_posts = site_a.find(:all, :type => "quote")
18
+ posts_offset_and_limit = site_a.find(:all, :offset => 50, :limit => 20)
19
+ quote_search = site_a.find(:all, :type => "quote", :search => "foo")
20
+ quote_tagged = site_a.find(:all, :type => "quote", :tagged => "bar")
21
+
22
+ Finding by id.
23
+ post = site_a.find(12345678)
24
+
25
+ Posting.
26
+ site_b = Tumblr4r::Site.new("site_b.tumblr.com", "foo@example.com", "password")
27
+ site_b.save(post[0])
28
+
29
+ Deleting.
30
+ site_b.delete(post[0].post_id)
31
+
32
+ == Installation
33
+
34
+ gem install tumblr4r
35
+
36
+ == Problems
37
+
38
+ * Can't get private posts yet.
39
+ * Can't upload audio and video data yet.
40
+ * Can't handle feeds yet.
41
+
42
+
43
+ == Copyright
44
+
45
+ Author:: Tomoki MAEDA <http://twitter.com/tmaeda>
46
+ Copyright:: Copyright (c) 2009 Tomoki MAEDA
47
+ License:: Ruby's license
48
+
data/Rakefile ADDED
@@ -0,0 +1,145 @@
1
+ require 'rubygems'
2
+ require 'rake'
3
+ require 'rake/clean'
4
+ require 'rake/testtask'
5
+ require 'rake/packagetask'
6
+ require 'rake/gempackagetask'
7
+ require 'rake/rdoctask'
8
+ require 'rake/contrib/rubyforgepublisher'
9
+ require 'rake/contrib/sshpublisher'
10
+ require 'fileutils'
11
+ require 'lib/tumblr4r'
12
+ include FileUtils
13
+
14
+ NAME = "tumblr4r"
15
+ AUTHOR = "Tomoki MAEDA"
16
+ EMAIL = "tmaeda@ruby-sapporo.org"
17
+ DESCRIPTION = "Tumblr API Wrapper for Ruby"
18
+ RUBYFORGE_PROJECT = "tumblr4r"
19
+ HOMEPATH = "http://#{RUBYFORGE_PROJECT}.rubyforge.org"
20
+ BIN_FILES = %w( )
21
+
22
+ VERS = Tumblr4r::VERSION
23
+ REV = File.read(".svn/entries")[/committed-rev="(d+)"/, 1] rescue nil
24
+ CLEAN.include ['**/.*.sw?', '*.gem', '.config']
25
+ RDOC_OPTS = [
26
+ '--title', "#{NAME} documentation",
27
+ "--charset", "utf-8",
28
+ "--opname", "index.html",
29
+ "--line-numbers",
30
+ "--main", "README",
31
+ "--inline-source",
32
+ ]
33
+
34
+ task :default => [:test]
35
+ task :package => [:clean]
36
+
37
+ Rake::TestTask.new("test") do |t|
38
+ t.libs << "test"
39
+ t.pattern = "test/**/*_test.rb"
40
+ t.verbose = true
41
+ end
42
+
43
+ spec = Gem::Specification.new do |s|
44
+ s.name = NAME
45
+ s.version = VERS
46
+ s.platform = Gem::Platform::RUBY
47
+ s.has_rdoc = true
48
+ s.extra_rdoc_files = ["README", "ChangeLog"]
49
+ s.rdoc_options += RDOC_OPTS + ['--exclude', '^(examples|extras)/']
50
+ s.summary = DESCRIPTION
51
+ s.description = DESCRIPTION
52
+ s.author = AUTHOR
53
+ s.email = EMAIL
54
+ s.homepage = HOMEPATH
55
+ s.executables = BIN_FILES
56
+ s.rubyforge_project = RUBYFORGE_PROJECT
57
+ s.bindir = "bin"
58
+ s.require_path = "lib"
59
+ #s.autorequire = ""
60
+ s.test_files = Dir["test/*_test.rb"]
61
+
62
+ s.add_runtime_dependency('activesupport', '>=2.3.2')
63
+ s.add_development_dependency('pit', '>=0.0.6')
64
+ s.required_ruby_version = '>= 1.8.6'
65
+
66
+ s.files = %w(README ChangeLog Rakefile) +
67
+ Dir.glob("{bin,doc,test,lib,templates,generator,extras,website,script}/**/*") +
68
+ Dir.glob("ext/**/*.{h,c,rb}") +
69
+ Dir.glob("examples/**/*.rb") +
70
+ Dir.glob("tools/*.rb") +
71
+ Dir.glob("rails/*.rb")
72
+
73
+ s.extensions = FileList["ext/**/extconf.rb"].to_a
74
+ end
75
+
76
+ Rake::GemPackageTask.new(spec) do |p|
77
+ p.need_tar = true
78
+ p.gem_spec = spec
79
+ end
80
+
81
+ task :install do
82
+ name = "#{NAME}-#{VERS}.gem"
83
+ sh %{rake package}
84
+ sh %{sudo gem install pkg/#{name}}
85
+ end
86
+
87
+ task :uninstall => [:clean] do
88
+ sh %{sudo gem uninstall #{NAME}}
89
+ end
90
+
91
+
92
+ Rake::RDocTask.new do |rdoc|
93
+ rdoc.rdoc_dir = 'html'
94
+ rdoc.options += RDOC_OPTS
95
+ rdoc.template = "resh"
96
+ #rdoc.template = "#{ENV['template']}.rb" if ENV['template']
97
+ if ENV['DOC_FILES']
98
+ rdoc.rdoc_files.include(ENV['DOC_FILES'].split(/,\s*/))
99
+ else
100
+ rdoc.rdoc_files.include('README', 'ChangeLog')
101
+ rdoc.rdoc_files.include('lib/**/*.rb')
102
+ rdoc.rdoc_files.include('ext/**/*.c')
103
+ end
104
+ end
105
+
106
+ desc "Publish to RubyForge"
107
+ task :rubyforge => [:rdoc, :package] do
108
+ require 'rubyforge'
109
+ Rake::RubyForgePublisher.new(RUBYFORGE_PROJECT, 'tmaeda').upload
110
+ end
111
+
112
+ desc 'Package and upload the release to rubyforge.'
113
+ task :release => [:clean, :package] do |t|
114
+ v = ENV["VERSION"] or abort "Must supply VERSION=x.y.z"
115
+ abort "Versions don't match #{v} vs #{VERS}" unless v == VERS
116
+ pkg = "pkg/#{NAME}-#{VERS}"
117
+
118
+ require 'rubyforge'
119
+ rf = RubyForge.new.configure
120
+ puts "Logging in"
121
+ rf.login
122
+
123
+ c = rf.userconfig
124
+ # c["release_notes"] = description if description
125
+ # c["release_changes"] = changes if changes
126
+ c["preformatted"] = true
127
+
128
+ files = [
129
+ "#{pkg}.tgz",
130
+ "#{pkg}.gem"
131
+ ].compact
132
+
133
+ puts "Releasing #{NAME} v. #{VERS}"
134
+ rf.add_release RUBYFORGE_PROJECT, NAME, VERS, *files
135
+ end
136
+
137
+ desc 'Show information about the gem.'
138
+ task :debug_gem do
139
+ puts spec.to_ruby
140
+ end
141
+
142
+ desc 'Update gem spec'
143
+ task :gemspec do
144
+ open("#{NAME}.gemspec", 'w').write spec.to_ruby
145
+ end
data/lib/tumblr4r.rb ADDED
@@ -0,0 +1,510 @@
1
+ require 'net/http'
2
+ require 'rubygems'
3
+ require 'rexml/document'
4
+ require 'active_support'
5
+ require 'logger'
6
+ require 'cgi'
7
+ module Tumblr4r
8
+ VERSION = '0.7.0'
9
+ class TumblrError < StandardError
10
+ end
11
+
12
+ module POST_TYPE
13
+ REGULAR = "regular"
14
+ PHOTO = "photo"
15
+ QUOTE = "quote"
16
+ LINK = "link"
17
+ CHAT = "conversation"
18
+ AUDIO = "audio"
19
+ VIDEO = "video"
20
+ end
21
+
22
+ # ConnectionオブジェクトとParserオブジェクトを組み合わせて、
23
+ # TumblrAPIとRubyオブジェクトの相互変換を行う
24
+ # TODO: private な post だけを取得する API が無いのだなぁ
25
+ # * Webから更新したものがAPIで取得できるデータに反映されるには少しタイムラグがあるようだ
26
+ # * Webから更新しちゃうと、POST日時の秒が丸められてしまう
27
+ class Site
28
+ attr_accessor :hostname, :email, :password, :name, :timezone, :title, :cname,
29
+ :description, :feeds
30
+ attr_accessor :logger
31
+ # TODO: 変数名もうちょっと考える
32
+ API_READ_LIMIT = 50
33
+ @@default_log_level = Logger::INFO
34
+ cattr_accessor :default_log_level
35
+
36
+ class << self
37
+ # TODO: unit test
38
+ def find(hostname, email=nil, password=nil, http=nil, &block)
39
+ site = self.new(hostname, email, password, http)
40
+ result = site.find(:all)
41
+ if block_given?
42
+ result.each do |post|
43
+ yield post
44
+ end
45
+ else
46
+ return result
47
+ end
48
+ end
49
+ end
50
+
51
+ def initialize(hostname, email=nil, password=nil, http = nil, logger = nil)
52
+ @hostname = hostname
53
+ @email = email
54
+ @password = password
55
+ @logger = logger || Logger.new(STDERR)
56
+ @logger.level = @@default_log_level
57
+ @conn = XMLConnection.new(http || @hostname, email, password, @logger)
58
+ @parser = XMLParser.new
59
+ self.site_info
60
+ end
61
+
62
+ # TODO: ここの再帰取得ロジックはTumblrAPIとは独立してるので
63
+ # TumblrAPIとは独立した形に切り出したり、TumblrAPIとは切り離してテストを書きたいものだ
64
+ # @param [Symbol|Integer] id_or_type :all, id
65
+ # @return [Array<Post>|Post]
66
+ def find(id_or_type, options = { })
67
+ params = { }
68
+ return result if options[:offset] && options[:offset].to_i < 0
69
+ [:type, :filter, :tagged, :search].each do |option|
70
+ params[option] = options[option] if options[option]
71
+ end
72
+
73
+ if id_or_type == :all
74
+ result = []
75
+ # 取得開始位置の初期化
76
+ params[:start] = options[:offset] || 0
77
+ # goal の設定
78
+ total = self.count(options)
79
+ if options[:limit]
80
+ goal = [total - params[:start],
81
+ options[:limit] - params[:start]].min
82
+ else
83
+ goal = total - params[:start]
84
+ end
85
+ # 取得件数の初期化
86
+ if goal < 0
87
+ return result
88
+ elsif goal < API_READ_LIMIT
89
+ params[:num] = goal
90
+ else
91
+ params[:num] = API_READ_LIMIT # :num を指定しないとデフォルトでは20件しかとれない
92
+ end
93
+
94
+ loop do
95
+ xml = @conn.get(params)
96
+ posts, start, total = @parser.posts(xml)
97
+ @logger.info("size: #{posts.size}")
98
+ @logger.info("start: #{start}")
99
+ @logger.info("total: #{total}")
100
+ result += posts
101
+ if result.size >= goal || posts.size == 0
102
+ # Tumblr API の total で得られる値は全く信用ならない。
103
+ # 検索条件を考慮した件数を返してくれない。
104
+ # (つまり、goalは信用ならない)ので、posts.sizeも終了判定に利用する。
105
+ # TODO: もしくは:numの値を足し合わせていって、それとgoalを比較する?
106
+ break
107
+ end
108
+ # 取得開始位置の調整
109
+ params[:start] += params[:num]
110
+ # 取得件数の調整
111
+ if (goal - result.size) >= API_READ_LIMIT
112
+ params[:num] = API_READ_LIMIT
113
+ else
114
+ params[:num] = goal - result.size
115
+ end
116
+ end
117
+ return result
118
+ elsif id_or_type.kind_of?(Integer)
119
+ xml = @conn.get({:id => id_or_type})
120
+ posts, start, total = @parser.posts(xml)
121
+ @logger.info("size: #{posts.size}")
122
+ @logger.info("start: #{start}")
123
+ @logger.info("total: #{total}")
124
+ return posts[0]
125
+ else
126
+ raise ArgumentError
127
+ end
128
+ end
129
+
130
+ def count(options = { })
131
+ params = { }
132
+ [:id, :type, :filter, :tagged, :search].each do |option|
133
+ params[option] = options[option] if options[option]
134
+ end
135
+ params[:num] = 1
136
+ params[:start] = 0
137
+ xml = @conn.get(params)
138
+ posts, start, total = @parser.posts(xml)
139
+ return total
140
+ end
141
+
142
+ def site_info
143
+ xml = @conn.get(:num => 1)
144
+ @parser.siteinfo(self, xml)
145
+ end
146
+
147
+ def save(post)
148
+ post_id = @conn.write(post.params)
149
+ new_post = self.find(post_id)
150
+ return new_post
151
+ end
152
+
153
+ end
154
+
155
+ # Postおよびその子クラスは原則として単なるData Transfer Objectとし、
156
+ # 何かのロジックをこの中に実装はしない。
157
+ class Post
158
+ attr_accessor :post_id, # Integer
159
+ :url, # String
160
+ :url_with_slug, # String
161
+ :type, # String
162
+ :date_gmt,
163
+ :date,
164
+ :unix_timestamp, # Integer
165
+ :format, # String("html"|"markdown")
166
+ :tags, # Array<String>
167
+ :bookmarklet, # true|false
168
+ :private, # Integer(0|1)
169
+ :generator # String
170
+
171
+ @@default_generator = nil
172
+ cattr_accessor :default_generator
173
+
174
+ def initialize
175
+ @generator = @@default_generator || "Tumblr4R"
176
+ @tags = []
177
+ end
178
+
179
+ def params
180
+ {"type" => @type,
181
+ "generator" => @generator,
182
+ "date" => @date,
183
+ "private" => @private,
184
+ "tags" => @tags.join(","),
185
+ "format" => @format
186
+ }
187
+ end
188
+ end
189
+
190
+ class Regular < Post
191
+ attr_accessor :regular_title, :regular_body
192
+
193
+ def params
194
+ super.merge!({"title" => @regular_title, "body" => @regular_body })
195
+ end
196
+ end
197
+
198
+ # TODO: Feed の扱いをどうするか
199
+ class Feed < Post
200
+ attr_accessor :regular_body, :feed_item, :from_feed_id
201
+ # TODO: titleのあるfeed itemってあるのか?
202
+ end
203
+
204
+ class Photo < Post
205
+ attr_accessor :photo_caption, :photo_link_url, :photo_url
206
+ #TODO: photo_url の max-width って何?
207
+ attr_accessor :data
208
+
209
+ # TODO: data をどうやってPOSTするか考える
210
+ # 生のデータを持たせるんじゃなく、TumblrPostDataみたいな
211
+ # クラスにラップして、それを各POSTのivarに保持させる?
212
+ def params
213
+ super.merge!(
214
+ {"source" => @photo_url,
215
+ "caption" => @photo_caption,
216
+ "click-through-url" => @photo_link_url,
217
+ "data" => @data})
218
+ end
219
+ end
220
+
221
+ class Quote < Post
222
+ attr_accessor :quote_text, :quote_source
223
+
224
+ def params
225
+ super.merge!(
226
+ {"quote" => @quote_text,
227
+ "source" => @quote_source})
228
+ end
229
+ end
230
+
231
+ class Link < Post
232
+ attr_accessor :link_text, :link_url, :link_description
233
+ def params
234
+ super.merge!(
235
+ {"name" => @link_text,
236
+ "url" => @link_url,
237
+ "description" => @link_description})
238
+ end
239
+ end
240
+
241
+ class Chat < Post
242
+ attr_accessor :conversation_title, :conversation_text
243
+ # <conversation><line name="..." label="...">text</line>のリスト</conversation>
244
+
245
+ def params
246
+ super.merge!(
247
+ {"title" => @conversation_title,
248
+ "conversation" => @conversation_text})
249
+ end
250
+ end
251
+
252
+ class Audio < Post
253
+ attr_accessor :audio_plays, :audio_caption, :audio_player
254
+ attr_accessor :data
255
+
256
+ def params
257
+ super.merge!(
258
+ {"data" => @data,
259
+ "caption" => @audio_caption})
260
+ end
261
+ end
262
+
263
+ class Video < Post
264
+ attr_accessor :video_caption, :video_source, :video_player
265
+ attr_accessor :data, :title
266
+ # TODO: title は vimeo へのアップロードのときのみ有効らしい
267
+ # TODO: embed を使うか、アップロードしたdataを使うかってのは
268
+ # Tumblr側で勝手に判断されるのかなぁ?
269
+ def params
270
+ super.merge!(
271
+ {"embed" => @video_source,
272
+ "data" => @data,
273
+ "title" => @title,
274
+ "caption" => @video_caption})
275
+ end
276
+ end
277
+
278
+ # Tumblr XML API への薄いラッパー。
279
+ # Rubyオブジェクトからの変換やRubyオブジェクトへの変換などは
280
+ # Parserクラスで行う。Parserクラスへの依存関係は一切持たない。
281
+ class XMLConnection
282
+ attr_accessor :logger, :group, :authenticated
283
+ def initialize(http_or_hostname, email=nil, password=nil, logger = nil)
284
+ case http_or_hostname
285
+ when String
286
+ @conn = Net::HTTP.new(http_or_hostname)
287
+ when Net::HTTP
288
+ @conn = http_or_hostname
289
+ else
290
+ raise ArgumentError.new("http_or_hostname must be String or Net::HTTP")
291
+ end
292
+ @email= email
293
+ @password = password
294
+ if @email && @password
295
+ begin
296
+ @authenticated = authenticate
297
+ rescue TumblrError
298
+ @authenticated = false
299
+ end
300
+ end
301
+ @group = @conn.address
302
+ @logger = logger || Logger.new(STDERR)
303
+ end
304
+
305
+ # @param [Hash] options :id, :type, :filter, :tagged, :search, :start, :num
306
+ def get(options = { })
307
+ params = options.map{|k, v|
308
+ "#{k}=#{v}"
309
+ }.join("&")
310
+ req = "/api/read?#{params}"
311
+ logger.info(req)
312
+ res = @conn.get(req)
313
+ logger.debug(res.body)
314
+ case res
315
+ when Net::HTTPOK
316
+ return res.body
317
+ when Net::HTTPNotFound
318
+ raise TumblrError.new("no such site(#{@hostname})")
319
+ else
320
+ raise TumblrError.new("unexpected response #{res.inspect}")
321
+ end
322
+ end
323
+
324
+ # @return true if email and password are valid
325
+ # @raise TumblrError if email or password is invalid
326
+ def authenticate
327
+ response = nil
328
+ http = Net::HTTP.new("www.tumblr.com")
329
+ response = http.post('/api/authenticate',
330
+ "email=#{CGI.escape(@email)}&password=#{CGI.escape(@password)}")
331
+
332
+ case response
333
+ when Net::HTTPOK
334
+ return true
335
+ else
336
+ raise TumblrError.new(response.inspect + "\n" + response.body)
337
+ end
338
+ end
339
+
340
+ # @return [Integer] post_id if success
341
+ # @raise [TumblrError] if fail
342
+ def write(options)
343
+ raise TumblrError.new("email or password is invalid") unless authenticated
344
+
345
+ response = nil
346
+ http = Net::HTTP.new("www.tumblr.com")
347
+ params = options.merge({"email" => @email, "password" => @password, "group" => @group})
348
+ query_string = params.delete_if{|k,v| v == nil }.map{|k,v| "#{k}=#{CGI.escape(v.to_s)}" unless v.nil?}.join("&")
349
+ logger.debug("#### query_string: #{query_string}")
350
+ response = http.post('/api/write', query_string)
351
+ case response
352
+ when Net::HTTPSuccess
353
+ return response.body.to_i
354
+ else
355
+ msg = response.inspect + "\n"
356
+ response.each{|k,v| msg += "#{k}: #{v}\n"}
357
+ msg += response.body
358
+ raise TumblrError.new(msg)
359
+ end
360
+ end
361
+
362
+ # @params [Integer] post_id
363
+ def delete(post_id)
364
+ raise TumblrError.new("email or password is invalid") unless authenticated
365
+ response = nil
366
+ http = Net::HTTP.new("www.tumblr.com")
367
+ params = {"post-id" => post_id, "email" => @email, "password" => @password, "group" => @group}
368
+ query_string = params.delete_if{|k,v| v == nil }.map{|k,v| "#{k}=#{CGI.escape(v.to_s)}" unless v.nil?}.join("&")
369
+ logger.debug("#### query_string: #{query_string}")
370
+ response = http.post('/api/delete', query_string)
371
+ case response
372
+ when Net::HTTPSuccess
373
+ logger.debug("#### response: #{response.code}: #{response.body}")
374
+ return true
375
+ else
376
+ msg = response.inspect + "\n"
377
+ response.each{|k,v| msg += "#{k}: #{v}\n"}
378
+ msg += response.body
379
+ raise TumblrError.new(msg)
380
+ end
381
+ end
382
+ end
383
+
384
+ # Tumblr XML API
385
+ class XMLParser
386
+ # @param [Site] site xmlをパースした結果を埋める入れ物
387
+ # @param [String] xml TumblrAPIのレスポンスのXMLそのまま
388
+ def siteinfo(site, xml)
389
+ xml_doc = REXML::Document.new(xml)
390
+ tumblelog = REXML::XPath.first(xml_doc, "//tumblr/tumblelog")
391
+ site.name = tumblelog.attributes["name"]
392
+ site.timezone = tumblelog.attributes["timezone"]
393
+ site.title = tumblelog.attributes["title"]
394
+ site.cname = tumblelog.attributes["cname"]
395
+ site.description = tumblelog.text
396
+ # tumblelog.elements["/feeds"]}
397
+ # TODO: feeds は後回し
398
+ return site
399
+ end
400
+
401
+ # XMLをパースしてオブジェクトのArrayを作る
402
+ # @param [String] xml APIからのレスポンス全体
403
+ # @return [Array<Post>, Integer, Integer] 各種Postの子クラスのArray, start, total
404
+ def posts(xml)
405
+ rexml_doc = REXML::Document.new(xml)
406
+ rexml_posts = REXML::XPath.first(rexml_doc, "//tumblr/posts")
407
+ start = rexml_posts.attributes["start"]
408
+ total = rexml_posts.attributes["total"]
409
+ posts = []
410
+ rexml_posts.elements.each("//posts/post") do |rexml_post|
411
+ post_type = rexml_post.attributes["type"]
412
+ post = nil
413
+ case post_type
414
+ when POST_TYPE::REGULAR
415
+ post = self.regular(Regular.new, rexml_post)
416
+ when POST_TYPE::PHOTO
417
+ post = self.photo(Photo.new, rexml_post)
418
+ when POST_TYPE::QUOTE
419
+ post = self.quote(Quote.new, rexml_post)
420
+ when POST_TYPE::LINK
421
+ post = self.link(Link.new, rexml_post)
422
+ when POST_TYPE::CHAT
423
+ post = self.chat(Chat.new, rexml_post)
424
+ when POST_TYPE::AUDIO
425
+ post = self.audio(Audio.new, rexml_post)
426
+ when POST_TYPE::VIDEO
427
+ post = self.video(Video.new, rexml_post)
428
+ else
429
+ raise TumblrError.new("unknown post type #{post_type}")
430
+ end
431
+ posts << post
432
+ end
433
+ return posts, start.to_i, total.to_i
434
+ end
435
+
436
+ # TODO: この辺りの設計についてはもう少し考慮の余地がある?
437
+ # みんな同じような構造(まずはpost(post, rexml_post)呼んでその後独自処理)してるし、
438
+ # 引数にpostとrexml_postをもらってくるってのもなんかイケてない気がする。
439
+ def post(post, rexml_post)
440
+ post.post_id = rexml_post.attributes["id"].to_i
441
+ post.url = rexml_post.attributes["url"]
442
+ post.url_with_slug = rexml_post.attributes["url-with-slug"]
443
+ post.type = rexml_post.attributes["type"]
444
+ # TODO: time 関係の型をStringじゃなくTimeとかにする?
445
+ post.date_gmt = rexml_post.attributes["date-gmt"]
446
+ post.date = rexml_post.attributes["date"]
447
+ post.unix_timestamp = rexml_post.attributes["unix-timestamp"].to_i
448
+ post.format = rexml_post.attributes["format"]
449
+ post.tags = rexml_post.get_elements("tag").map(&:text)
450
+ post.bookmarklet = (rexml_post.attributes["bookmarklet"] == "true")
451
+ post
452
+ end
453
+
454
+ def regular(post, rexml_post)
455
+ post = self.post(post, rexml_post)
456
+ post.regular_title = rexml_post.elements["regular-title"].try(:text) || ""
457
+ post.regular_body = rexml_post.elements["regular-body"].try(:text) || ""
458
+ post
459
+ end
460
+
461
+ def photo(post, rexml_post)
462
+ post = self.post(post, rexml_post)
463
+ post.type
464
+ post.photo_caption = rexml_post.elements["photo-caption"].try(:text) || ""
465
+ post.photo_link_url = rexml_post.elements["photo-link-url"].try(:text) || ""
466
+ post.photo_url = rexml_post.elements["photo-url"].try(:text) || ""
467
+ post
468
+ end
469
+
470
+ def quote(post, rexml_post)
471
+ post = self.post(post, rexml_post)
472
+ post.quote_text = rexml_post.elements["quote-text"].try(:text) || ""
473
+ post.quote_source = rexml_post.elements["quote-source"].try(:text) || ""
474
+ post
475
+ end
476
+
477
+ def link(post, rexml_post)
478
+ post = self.post(post, rexml_post)
479
+ post.link_text = rexml_post.elements["link-text"].try(:text) || ""
480
+ post.link_url = rexml_post.elements["link-url"].try(:text) || ""
481
+ post.link_description = rexml_post.elements["link-description"].try(:text) || ""
482
+ post
483
+ end
484
+
485
+ def chat(post, rexml_post)
486
+ post = self.post(post, rexml_post)
487
+ post.conversation_title = rexml_post.elements["conversation-title"].try(:text) || ""
488
+ post.conversation_text = rexml_post.elements["conversation-text"].try(:text) || ""
489
+ post
490
+ end
491
+
492
+ def audio(post, rexml_post)
493
+ post = self.post(post, rexml_post)
494
+ post.audio_plays = (rexml_post.attributes["audio-plays"] == "1")
495
+ post.audio_caption = rexml_post.elements["audio-caption"].try(:text) || ""
496
+ post.audio_player = rexml_post.elements["audio-player"].try(:text) || ""
497
+ post
498
+ end
499
+
500
+ def video(post, rexml_post)
501
+ post = self.post(post, rexml_post)
502
+ post.video_caption = rexml_post.elements["video-caption"].try(:text) || ""
503
+ post.video_source = rexml_post.elements["video-source"].try(:text) || ""
504
+ post.video_player = rexml_post.elements["video-player"].try(:text) || ""
505
+ post
506
+ end
507
+
508
+ end
509
+
510
+ end # module
@@ -0,0 +1,3 @@
1
+ require 'test/unit'
2
+ require File.dirname(__FILE__) + '/../lib/tumblr4r'
3
+
@@ -0,0 +1,379 @@
1
+ require File.dirname(__FILE__) + '/test_helper.rb'
2
+ require "test/unit"
3
+ require 'pit'
4
+ class Tumblr4rTest < Test::Unit::TestCase
5
+ include Tumblr4r
6
+ READ_TEST_HOST = "tumblr4rtest.tumblr.com"
7
+ WRITE_TEST_HOST = "tumblr4rwritetest.tumblr.com"
8
+ TOTAL_COUNT = 114
9
+ QUOTE_COUNT = 102
10
+
11
+ def setup
12
+ @site = Site.new(READ_TEST_HOST)
13
+ writetest_conf = Pit.get("tumblr4rwritetest",
14
+ :require => {"email" => "required email",
15
+ "password" => "required password"})
16
+ @write_site = Site.new(WRITE_TEST_HOST,
17
+ writetest_conf["email"],
18
+ writetest_conf["password"])
19
+ @large_site = Site.new("tumblr4rfindtest.tumblr.com")
20
+ end
21
+
22
+ def teardown
23
+ end
24
+
25
+
26
+ def test_initialize
27
+ assert_equal READ_TEST_HOST, @site.hostname
28
+ assert_nil @site.email
29
+ assert_nil @site.password
30
+
31
+ assert_equal "tumblr4rtest", @site.name
32
+ assert_equal "Asia/Tokyo", @site.timezone
33
+ assert_equal "tumblr4rテスト", @site.title
34
+ assert_nil @site.cname
35
+ assert_equal "tumblr4rのテスト用サイトです。\r\ntumblrサイコー", @site.description
36
+ # TODO: feeds は後回し
37
+ end
38
+
39
+ def test_count
40
+ total = @large_site.count
41
+ assert_equal TOTAL_COUNT, total
42
+
43
+ total = @large_site.count(:type => "quote")
44
+ assert_equal QUOTE_COUNT, total
45
+
46
+ total = @large_site.count(:filter => "text")
47
+ assert_equal TOTAL_COUNT, total
48
+
49
+ total = @large_site.count(:tagged => "test")
50
+ assert_equal TOTAL_COUNT, total
51
+
52
+ total = @large_site.count(:search => "test")
53
+ assert_equal TOTAL_COUNT, total
54
+ end
55
+
56
+ def test_find
57
+ posts = @site.find(:all)
58
+ assert_equal 8, posts.size
59
+ assert_equal Video, posts[0].class
60
+ end
61
+
62
+ def test_find_all
63
+ posts = @large_site.find(:all)
64
+ assert_equal TOTAL_COUNT, posts.size
65
+ end
66
+
67
+ def test_find_all_quote
68
+ posts = @large_site.find(:all, :type => "quote")
69
+ assert_equal QUOTE_COUNT, posts.size
70
+ end
71
+
72
+ # 実際に存在する件数より少なく指定した場合
73
+ def test_find_all_with_num
74
+ posts = @large_site.find(:all, :limit => 74)
75
+ assert_equal 74, posts.size
76
+ end
77
+
78
+ # 実際に存在する件数より多く指定した場合
79
+ def test_find_all_with_over_num
80
+ posts = @large_site.find(:all, :limit => 765)
81
+ assert_equal TOTAL_COUNT, posts.size
82
+ end
83
+
84
+ def test_find_all_with_offset
85
+ posts = @large_site.find(:all, :offset => 12)
86
+ assert_equal TOTAL_COUNT-12, posts.size
87
+ end
88
+
89
+ # 実際に存在するよりも多いoffsetを指定した場合
90
+ def test_find_all_over_offset
91
+ posts = @large_site.find(:all, :type => "quote", :offset => TOTAL_COUNT + 1)
92
+ assert_equal 0, posts.size
93
+ end
94
+
95
+ def test_find_with_type_regular
96
+ posts = @site.find(:all, :type => "regular")
97
+ assert_equal 2, posts.size
98
+
99
+ assert_equal Regular, posts[0].class
100
+ assert_equal 123459291, posts[0].post_id
101
+ assert_equal "http://tumblr4rtest.tumblr.com/post/123459291", posts[0].url
102
+ assert_equal "http://tumblr4rtest.tumblr.com/post/123459291/regular-test", posts[0].url_with_slug
103
+ assert_equal "regular", posts[0].type
104
+ assert_equal "2009-06-14 16:30:44 GMT", posts[0].date_gmt
105
+ assert_equal "Mon, 15 Jun 2009 01:30:44", posts[0].date
106
+ assert_equal 1244997044, posts[0].unix_timestamp
107
+ assert_equal "html", posts[0].format
108
+ assert_equal ["test", "regular"], posts[0].tags
109
+ assert_equal false, posts[0].bookmarklet
110
+
111
+ assert_equal "Text Postのテストです", posts[0].regular_title
112
+ assert_equal <<EOF.chomp, posts[0].regular_body
113
+ <p>テキストです。</p>
114
+ <p><b>ボールドです。</b></p>
115
+ <p><i>イタリックです。</i></p>
116
+ <p><strike>取り消し線です。</strike></p>
117
+ <ul>
118
+ <li>unordered 1</li>
119
+ <li>unordered 2</li>
120
+ </ul>
121
+ <ol>
122
+ <li>ordered 1</li>
123
+ <li>ordered 2</li>
124
+ </ol>
125
+ <p>ここからインデント</p>
126
+ <blockquote style="margin: 0 0 0 40px; border: none; padding: 0px;">インデント開始<br/>ああああ<br/>インデント終了</blockquote>
127
+ <p>ここまでインデント</p>
128
+ EOF
129
+
130
+ assert_equal Regular, posts[1].class
131
+ assert_equal 122871637, posts[1].post_id
132
+ assert_equal "http://tumblr4rtest.tumblr.com/post/122871637", posts[1].url
133
+ assert_equal "http://tumblr4rtest.tumblr.com/post/122871637/tumblr4r", posts[1].url_with_slug
134
+ assert_equal "regular", posts[1].type
135
+ assert_equal "2009-06-13 12:46:23 GMT", posts[1].date_gmt
136
+ assert_equal "Sat, 13 Jun 2009 21:46:23", posts[1].date
137
+ assert_equal 1244897183, posts[1].unix_timestamp
138
+ assert_equal "html", posts[1].format
139
+ assert_equal [], posts[1].tags
140
+ assert_equal false, posts[1].bookmarklet
141
+
142
+ assert_equal "", posts[1].regular_title
143
+ assert_equal "Tumblr4rのテストです", posts[1].regular_body
144
+ end
145
+
146
+ def test_find_with_type_photo
147
+ posts = @site.find(:all, :type => "photo")
148
+ assert_equal 1, posts.size
149
+ assert_equal Photo, posts[0].class
150
+ assert_equal 123461063, posts[0].post_id
151
+ assert_equal "http://tumblr4rtest.tumblr.com/post/123461063", posts[0].url
152
+ assert_equal "http://tumblr4rtest.tumblr.com/post/123461063/photo", posts[0].url_with_slug
153
+ assert_equal "photo", posts[0].type
154
+ assert_equal "2009-06-14 16:34:50 GMT", posts[0].date_gmt
155
+ assert_equal "Mon, 15 Jun 2009 01:34:50", posts[0].date
156
+ assert_equal 1244997290, posts[0].unix_timestamp
157
+ assert_equal "html", posts[0].format
158
+ assert_equal ["test", "photo"], posts[0].tags
159
+ assert_equal false, posts[0].bookmarklet
160
+
161
+ assert_equal "<p>Photoのテストです。</p>\n\n<p>ギコです。</p>", posts[0].photo_caption
162
+ assert_equal "http://www.google.co.jp/", posts[0].photo_link_url
163
+ assert_equal "http://5.media.tumblr.com/GyEYZujUYopiula4XKmXhCgmo1_250.jpg", posts[0].photo_url
164
+ end
165
+
166
+ def test_find_with_type_quote
167
+ posts = @site.find(:all, :type => "quote")
168
+ assert_equal 1, posts.size
169
+ assert_equal Quote, posts[0].class
170
+ assert_equal 123470309, posts[0].post_id
171
+ assert_equal "http://tumblr4rtest.tumblr.com/post/123470309", posts[0].url
172
+ assert_equal "http://tumblr4rtest.tumblr.com/post/123470309/wikipedia-tumblr", posts[0].url_with_slug
173
+ assert_equal "quote", posts[0].type
174
+ assert_equal "2009-06-14 16:58:00 GMT", posts[0].date_gmt
175
+ assert_equal "Mon, 15 Jun 2009 01:58:00", posts[0].date
176
+ assert_equal 1244998680, posts[0].unix_timestamp
177
+ assert_equal "html", posts[0].format
178
+ assert_equal ["quote", "test"], posts[0].tags
179
+ assert_equal true, posts[0].bookmarklet
180
+
181
+ assert_equal <<EOF.chomp, posts[0].quote_text
182
+ Tumblelog/Tumblr(タンブルログ/タンブラー)は、メディアミックスウェブログサービス。米国 Davidville.incにより2007年3月1日にサービスが開始された。
183
+ EOF
184
+ assert_equal "<a href=\"http://ja.wikipedia.org/wiki/Tumblr\">Tumblelog - Wikipedia</a>", posts[0].quote_source
185
+ end
186
+
187
+ def test_find_with_type_link
188
+ posts = @site.find(:all, :type => "link")
189
+ assert_equal 1, posts.size
190
+ assert_equal Link, posts[0].class
191
+ assert_equal 123470990, posts[0].post_id
192
+ assert_equal "http://tumblr4rtest.tumblr.com/post/123470990", posts[0].url
193
+ assert_equal "http://tumblr4rtest.tumblr.com/post/123470990/tumblr-link", posts[0].url_with_slug
194
+ assert_equal "link", posts[0].type
195
+ assert_equal "2009-06-14 17:00:00 GMT", posts[0].date_gmt
196
+ assert_equal "Mon, 15 Jun 2009 02:00:00", posts[0].date
197
+ assert_equal 1244998800, posts[0].unix_timestamp
198
+ assert_equal "html", posts[0].format
199
+ assert_equal ["test", "link"], posts[0].tags
200
+ assert_equal false, posts[0].bookmarklet
201
+
202
+ assert_equal "たんぶらー", posts[0].link_text
203
+ assert_equal "http://www.tumblr.com/", posts[0].link_url
204
+ assert_equal "ですくりぷしょん", posts[0].link_description
205
+ end
206
+
207
+ def test_find_with_type_conversation
208
+ posts = @site.find(:all, :type => "conversation")
209
+ assert_equal 1, posts.size
210
+ assert_equal Chat, posts[0].class
211
+ assert_equal 123471808, posts[0].post_id
212
+ assert_equal "http://tumblr4rtest.tumblr.com/post/123471808", posts[0].url
213
+ assert_equal "http://tumblr4rtest.tumblr.com/post/123471808/pointcard-chat", posts[0].url_with_slug
214
+ assert_equal "conversation", posts[0].type
215
+ assert_equal "2009-06-14 17:01:54 GMT", posts[0].date_gmt
216
+ assert_equal "Mon, 15 Jun 2009 02:01:54", posts[0].date
217
+ assert_equal 1244998914, posts[0].unix_timestamp
218
+ assert_equal "html", posts[0].format
219
+ assert_equal ["test", "chat"], posts[0].tags
220
+ assert_equal false, posts[0].bookmarklet
221
+
222
+ assert_equal "なにそれこわい", posts[0].conversation_title
223
+ assert_equal <<EOF.chomp, posts[0].conversation_text
224
+ 店員: 当店のポイントカードはお餅でしょうか\r
225
+ ぼく: えっ\r
226
+ 店員: 当店のポイントカードはお餅ですか \r
227
+ ぼく: いえしりません\r
228
+ 店員: えっ\r
229
+ ぼく: えっ\r
230
+ EOF
231
+ end
232
+
233
+ def test_find_with_type_audio
234
+ posts = @site.find(:all, :type => "audio")
235
+ assert_equal 1, posts.size
236
+ assert_equal Audio, posts[0].class
237
+ assert_equal 131705561, posts[0].post_id
238
+ assert_equal "http://tumblr4rtest.tumblr.com/post/131705561", posts[0].url
239
+ assert_equal "http://tumblr4rtest.tumblr.com/post/131705561/tumblr4r-miku", posts[0].url_with_slug
240
+ assert_equal "audio", posts[0].type
241
+ assert_equal "2009-06-28 13:58:13 GMT", posts[0].date_gmt
242
+ assert_equal "Sun, 28 Jun 2009 22:58:13", posts[0].date
243
+ assert_equal 1246197493, posts[0].unix_timestamp
244
+ assert_equal "html", posts[0].format
245
+ assert_equal [], posts[0].tags
246
+ assert_equal false, posts[0].bookmarklet
247
+
248
+ assert_equal true, posts[0].audio_plays
249
+ assert_equal "tumblr4r miku", posts[0].audio_caption
250
+ assert_equal "<embed type=\"application/x-shockwave-flash\" src=\"http://tumblr4rtest.tumblr.com/swf/audio_player.swf?audio_file=http://www.tumblr.com/audio_file/131705561/GyEYZujUYp9df3nv1WMefTH8&color=FFFFFF\" height=\"27\" width=\"207\" quality=\"best\"></embed>", posts[0].audio_player
251
+ end
252
+
253
+ def test_find_with_type_video
254
+ posts = @site.find(:all, :type => "video")
255
+ assert_equal 1, posts.size
256
+ assert_equal Video, posts[0].class
257
+ assert_equal 131714219, posts[0].post_id
258
+ assert_equal "http://tumblr4rtest.tumblr.com/post/131714219", posts[0].url
259
+ assert_equal "http://tumblr4rtest.tumblr.com/post/131714219/matrix-sappoloaded", posts[0].url_with_slug
260
+ assert_equal "video", posts[0].type
261
+ assert_equal "2009-06-28 14:22:56 GMT", posts[0].date_gmt
262
+ assert_equal "Sun, 28 Jun 2009 23:22:56", posts[0].date
263
+ assert_equal 1246198976, posts[0].unix_timestamp
264
+ assert_equal "html", posts[0].format
265
+ assert_equal [], posts[0].tags
266
+ assert_equal false, posts[0].bookmarklet
267
+
268
+ assert_equal "matrix sappoloaded", posts[0].video_caption
269
+ assert_equal "http://www.youtube.com/watch?v=FavWH5RhYpw", posts[0].video_source
270
+ assert_equal "<object width=\"400\" height=\"336\"><param name=\"movie\" value=\"http://www.youtube.com/v/FavWH5RhYpw&amp;rel=0&amp;egm=0&amp;showinfo=0&amp;fs=1\"></param><param name=\"wmode\" value=\"transparent\"></param><param name=\"allowFullScreen\" value=\"true\"></param><embed src=\"http://www.youtube.com/v/FavWH5RhYpw&amp;rel=0&amp;egm=0&amp;showinfo=0&amp;fs=1\" type=\"application/x-shockwave-flash\" width=\"400\" height=\"336\" allowFullScreen=\"true\" wmode=\"transparent\"></embed></object>", posts[0].video_player
271
+ end
272
+
273
+ def test_find_with_tagged
274
+ posts = @site.find(:all, :tagged => "test")
275
+ assert_equal 5, posts.size
276
+ end
277
+
278
+ def test_find_with_search
279
+ posts = @site.find(:all, :search => "matrix")
280
+ assert_equal 1, posts.size
281
+ end
282
+
283
+ def test_generator_default
284
+ post = Post.new
285
+ assert_equal "Tumblr4R", post.generator
286
+
287
+ Post.default_generator = "foo"
288
+ post2 = Post.new
289
+ assert_equal "foo", post2.generator
290
+ assert_equal "Tumblr4R", post.generator
291
+
292
+
293
+ Post.default_generator = nil
294
+ post3 = Post.new
295
+ assert_equal "Tumblr4R", post3.generator
296
+ end
297
+
298
+ def test_find_by_id
299
+ post = @site.find(123459291)
300
+
301
+ assert_equal Regular, post.class
302
+ assert_equal 123459291, post.post_id
303
+ assert_equal "http://tumblr4rtest.tumblr.com/post/123459291", post.url
304
+ assert_equal "http://tumblr4rtest.tumblr.com/post/123459291/regular-test", post.url_with_slug
305
+ assert_equal "regular", post.type
306
+ assert_equal "2009-06-14 16:30:44 GMT", post.date_gmt
307
+ assert_equal "Mon, 15 Jun 2009 01:30:44", post.date
308
+ assert_equal 1244997044, post.unix_timestamp
309
+ assert_equal "html", post.format
310
+ assert_equal ["test", "regular"], post.tags
311
+ assert_equal false, post.bookmarklet
312
+
313
+ assert_equal "Text Postのテストです", post.regular_title
314
+ assert_equal <<EOF.chomp, post.regular_body
315
+ <p>テキストです。</p>
316
+ <p><b>ボールドです。</b></p>
317
+ <p><i>イタリックです。</i></p>
318
+ <p><strike>取り消し線です。</strike></p>
319
+ <ul>
320
+ <li>unordered 1</li>
321
+ <li>unordered 2</li>
322
+ </ul>
323
+ <ol>
324
+ <li>ordered 1</li>
325
+ <li>ordered 2</li>
326
+ </ol>
327
+ <p>ここからインデント</p>
328
+ <blockquote style="margin: 0 0 0 40px; border: none; padding: 0px;">インデント開始<br/>ああああ<br/>インデント終了</blockquote>
329
+ <p>ここまでインデント</p>
330
+ EOF
331
+ end
332
+
333
+ def test_quote_create
334
+ quotes = @site.find(:all, :type => Tumblr4r::POST_TYPE::QUOTE)
335
+ post = @write_site.save(quotes[0])
336
+ assert_not_equal quotes[0].post_id, post.post_id
337
+ assert_equal "http://#{WRITE_TEST_HOST}/post/#{post.post_id}", post.url
338
+ assert_equal "http://#{WRITE_TEST_HOST}/post/#{post.post_id}/tumblelog-tumblr", post.url_with_slug
339
+ assert_equal Tumblr4r::POST_TYPE::QUOTE, post.type
340
+ assert_equal quotes[0].date_gmt, post.date_gmt
341
+ assert_equal quotes[0].date, post.date
342
+ # TODO
343
+ # assert_equal 1245045480, post.unix_timestamp
344
+ assert_equal quotes[0].format, post.format
345
+ assert_equal quotes[0].tags, post.tags
346
+ assert_equal false, post.bookmarklet
347
+
348
+ assert_equal quotes[0].quote_text, post.quote_text
349
+ assert_equal quotes[0].quote_source, post.quote_source
350
+ end
351
+
352
+ def test_regular_create
353
+ regulars = @site.find(:all, :type => Tumblr4r::POST_TYPE::REGULAR)
354
+ post = @write_site.save(regulars[0])
355
+ end
356
+
357
+ def test_link_create
358
+ links = @site.find(:all, :type => Tumblr4r::POST_TYPE::LINK)
359
+ post = @write_site.save(links[0])
360
+ end
361
+
362
+ def test_photo_create
363
+ photos = @site.find(:all, :type => Tumblr4r::POST_TYPE::PHOTO)
364
+ post = @write_site.save(photos[0])
365
+ end
366
+
367
+
368
+ # def test_audio_create
369
+ # audios = @site.find(:all, :type => Tumblr4r::POST_TYPE::AUDIO)
370
+ # post = @write_site.save(audios[0])
371
+ # end
372
+
373
+ def test_video_create
374
+ videos = @site.find(:all, :type => Tumblr4r::POST_TYPE::VIDEO)
375
+ post = @write_site.save(videos[0])
376
+ end
377
+
378
+ end
379
+
metadata ADDED
@@ -0,0 +1,89 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: tumblr4r
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.7.0
5
+ platform: ruby
6
+ authors:
7
+ - Tomoki MAEDA
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2009-09-09 00:00:00 +09:00
13
+ default_executable:
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: activesupport
17
+ type: :runtime
18
+ version_requirement:
19
+ version_requirements: !ruby/object:Gem::Requirement
20
+ requirements:
21
+ - - ">="
22
+ - !ruby/object:Gem::Version
23
+ version: 2.3.2
24
+ version:
25
+ - !ruby/object:Gem::Dependency
26
+ name: pit
27
+ type: :development
28
+ version_requirement:
29
+ version_requirements: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: 0.0.6
34
+ version:
35
+ description: Tumblr API Wrapper for Ruby
36
+ email: tmaeda@ruby-sapporo.org
37
+ executables: []
38
+
39
+ extensions: []
40
+
41
+ extra_rdoc_files:
42
+ - README
43
+ - ChangeLog
44
+ files:
45
+ - README
46
+ - ChangeLog
47
+ - Rakefile
48
+ - test/test_helper.rb
49
+ - test/tumblr4r_test.rb
50
+ - lib/tumblr4r.rb
51
+ has_rdoc: true
52
+ homepage: http://tumblr4r.rubyforge.org
53
+ post_install_message:
54
+ rdoc_options:
55
+ - --title
56
+ - tumblr4r documentation
57
+ - --charset
58
+ - utf-8
59
+ - --opname
60
+ - index.html
61
+ - --line-numbers
62
+ - --main
63
+ - README
64
+ - --inline-source
65
+ - --exclude
66
+ - ^(examples|extras)/
67
+ require_paths:
68
+ - lib
69
+ required_ruby_version: !ruby/object:Gem::Requirement
70
+ requirements:
71
+ - - ">="
72
+ - !ruby/object:Gem::Version
73
+ version: 1.8.6
74
+ version:
75
+ required_rubygems_version: !ruby/object:Gem::Requirement
76
+ requirements:
77
+ - - ">="
78
+ - !ruby/object:Gem::Version
79
+ version: "0"
80
+ version:
81
+ requirements: []
82
+
83
+ rubyforge_project: tumblr4r
84
+ rubygems_version: 1.3.1
85
+ signing_key:
86
+ specification_version: 2
87
+ summary: Tumblr API Wrapper for Ruby
88
+ test_files:
89
+ - test/tumblr4r_test.rb