jamescallmebrent-rumblr 0.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.
- data/LICENSE.rdoc +24 -0
- data/README.rdoc +66 -0
- data/VERSION.yml +4 -0
- data/lib/rumblr/client.rb +141 -0
- data/lib/rumblr/post.rb +51 -0
- data/lib/rumblr/resource.rb +13 -0
- data/lib/rumblr/tumblelog.rb +24 -0
- data/lib/rumblr/user.rb +23 -0
- data/lib/rumblr.rb +28 -0
- data/spec/rumblr/client_spec.rb +111 -0
- data/spec/rumblr/post_spec.rb +25 -0
- data/spec/rumblr/tumblelog_spec.rb +39 -0
- data/spec/rumblr/user_spec.rb +69 -0
- data/spec/spec_helper.rb +105 -0
- metadata +78 -0
data/LICENSE.rdoc
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
= License
|
2
|
+
|
3
|
+
(The MIT License)
|
4
|
+
|
5
|
+
Copyright (c) 2009 Brent Hargrave
|
6
|
+
|
7
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
8
|
+
a copy of this software and associated documentation files (the
|
9
|
+
"Software"), to deal in the Software without restriction, including
|
10
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
11
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
12
|
+
permit persons to whom the Software is furnished to do so, subject to
|
13
|
+
the following conditions:
|
14
|
+
|
15
|
+
The above copyright notice and this permission notice shall be
|
16
|
+
included in all copies or substantial portions of the Software.
|
17
|
+
|
18
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
19
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
20
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
21
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
22
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
23
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
24
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.rdoc
ADDED
@@ -0,0 +1,66 @@
|
|
1
|
+
= Rumblr
|
2
|
+
|
3
|
+
Rumblr is a Ruby client for http://www.tumblr.com XML API. It currently only provides read functionality; you can read User, Tumblelog and Post data.
|
4
|
+
|
5
|
+
= Overview
|
6
|
+
|
7
|
+
Rumblr maps the Tumblr API 'authenticate' and 'read' calls. User, Tumblelog and Post models have attributes provided by the API, as well as some convenience methods for common tasks.
|
8
|
+
|
9
|
+
Please read Tumblr's official documentation (http://tumblr.com/api/) to learn more the API.
|
10
|
+
|
11
|
+
|
12
|
+
== Dependencies
|
13
|
+
|
14
|
+
* Ruby >= 1.8.6 (not tested with previous versions)
|
15
|
+
* libxml-ruby >= 0.8.3
|
16
|
+
|
17
|
+
|
18
|
+
== Download and Installation
|
19
|
+
|
20
|
+
RubyGems[http://rubyforge.org/projects/rubygems/] is the preferred install method.
|
21
|
+
To get the latest version, simply type the following instruction into your command prompt:
|
22
|
+
|
23
|
+
$ sudo gem install jamescallmebrent-rumblr -s http://gems.github.com
|
24
|
+
|
25
|
+
|
26
|
+
== Getting Started
|
27
|
+
|
28
|
+
In order to use this library you need a valid Tumblr account.
|
29
|
+
Go to http://tumblr.com and register for a new account if you don't already have one.
|
30
|
+
|
31
|
+
|
32
|
+
=== Sample usage:
|
33
|
+
|
34
|
+
require 'rumblr'
|
35
|
+
include Rumblr
|
36
|
+
|
37
|
+
user = User.login({:email => 'name@domain.com', :password => 'pass'})
|
38
|
+
|
39
|
+
user.tumblelogs.each do |log|
|
40
|
+
puts log.name # 'the tumblelogger'
|
41
|
+
puts log.type # 'public'
|
42
|
+
puts log.url # 'http://tumblelogger.tumblr.com/'
|
43
|
+
puts
|
44
|
+
end
|
45
|
+
|
46
|
+
tumblelog = user.primary_tumblelog
|
47
|
+
|
48
|
+
tumblelog.posts.each do |post|
|
49
|
+
puts post.type # 'quote'
|
50
|
+
puts post.url # 'http://tumblelogger.tumblr.com/post/12345/my-first-tumblr-post'
|
51
|
+
end
|
52
|
+
|
53
|
+
|
54
|
+
== Credits
|
55
|
+
|
56
|
+
Author:: {Brent Hargrave}[http://brenthargrave.info/] <brent.hargrave@gmail.com>
|
57
|
+
|
58
|
+
|
59
|
+
== FeedBack and Bug reports
|
60
|
+
|
61
|
+
Please email {Brent Hargrave}[mailto:brent.hargrave@gmail.com] with any questions or feedback.
|
62
|
+
|
63
|
+
|
64
|
+
== License
|
65
|
+
|
66
|
+
Copyright (c) 2009 Brent Hargrave. Rumblr is released under the MIT license.
|
data/VERSION.yml
ADDED
@@ -0,0 +1,141 @@
|
|
1
|
+
module Rumblr
|
2
|
+
|
3
|
+
class Client
|
4
|
+
include Singleton
|
5
|
+
|
6
|
+
# The default set of headers for each request.
|
7
|
+
DEFAULT_HEADER = {
|
8
|
+
'User-Agent' => 'Rumblr',
|
9
|
+
'Accept' => 'application/xml'
|
10
|
+
}
|
11
|
+
|
12
|
+
def authenticate(email,password)
|
13
|
+
raise ArgumentError unless (email && password)
|
14
|
+
user_credentials = { :email => email, :password => password }
|
15
|
+
uri = URI::HTTP.build({:path => '/api/authenticate'})
|
16
|
+
request = Net::HTTP::Post.new(uri.request_uri, DEFAULT_HEADER)
|
17
|
+
request.set_form_data(user_credentials)
|
18
|
+
|
19
|
+
user_attributes = nil
|
20
|
+
complete_request(request) do |response_body|
|
21
|
+
user_attributes = parse_user_attributes_from(response_body)
|
22
|
+
user_attributes.merge!(user_credentials)
|
23
|
+
end
|
24
|
+
User.new(user_attributes)
|
25
|
+
end
|
26
|
+
|
27
|
+
def read(options={})
|
28
|
+
tumblelog, posts = nil, [] # initialize the return targets
|
29
|
+
|
30
|
+
uri = URI::HTTP.build({:path => '/api/read'})
|
31
|
+
request = Net::HTTP::Post.new(uri.request_uri, DEFAULT_HEADER)
|
32
|
+
request.set_form_data(options)
|
33
|
+
|
34
|
+
raise(ArgumentError) unless (options && options[:url] && !options[:url].empty?)
|
35
|
+
|
36
|
+
request_url_host = URI.parse(options[:url]).host
|
37
|
+
complete_request(request,host=request_url_host) do |response_body|
|
38
|
+
parser, parser.string = XML::Parser.new, response_body
|
39
|
+
doc = parser.parse
|
40
|
+
# parse and map posts
|
41
|
+
posts = doc.find('//tumblr/posts/post').inject([]) do |array, element|
|
42
|
+
post_attrs = cleanup_hash(element.attributes.to_h)
|
43
|
+
# inner elements => sublcass-specific attribute hash
|
44
|
+
subclass_attrs = element.children.inject({'tags' => []}) do |hash, child|
|
45
|
+
hash['tags'] << child.content if (child.name[/tag/])
|
46
|
+
hash[child.name] = child.content
|
47
|
+
hash
|
48
|
+
end
|
49
|
+
# merge hashes, clean out cruft
|
50
|
+
inner_attrs = cleanup_hash(subclass_attrs)
|
51
|
+
inner_attrs.delete(:text)
|
52
|
+
inner_attrs.delete(:tag)
|
53
|
+
post_attrs.merge!(inner_attrs)
|
54
|
+
# turn attributes into proper model
|
55
|
+
klass = case post_attrs[:type]
|
56
|
+
when 'regular' then RegularPost
|
57
|
+
when 'photo' then PhotoPost
|
58
|
+
when 'quote' then QuotePost
|
59
|
+
when 'link' then LinkPost
|
60
|
+
when 'conversation' then ConversationPost
|
61
|
+
when 'video' then VideoPost
|
62
|
+
when 'audio' then AudioPost
|
63
|
+
else raise 'unknown post type'
|
64
|
+
end
|
65
|
+
post = klass.new(post_attrs)
|
66
|
+
array << post
|
67
|
+
array
|
68
|
+
end
|
69
|
+
# parse and map tumblelog
|
70
|
+
tumblelog_element = doc.find_first('//tumblr/tumblelog')
|
71
|
+
tumblelog_attrs = cleanup_hash(tumblelog_element.attributes.to_h)
|
72
|
+
tumblelog_attrs.merge!(:url => options[:url])
|
73
|
+
tumblelog = Tumblelog.new(tumblelog_attrs)
|
74
|
+
end
|
75
|
+
return tumblelog, posts
|
76
|
+
end
|
77
|
+
|
78
|
+
|
79
|
+
protected
|
80
|
+
|
81
|
+
# Starts and completes the given request. Returns or yields the response body.
|
82
|
+
def complete_request(request, host = URI.parse(Rumblr.api_url).host)
|
83
|
+
http = Net::HTTP.new(host)
|
84
|
+
# Set to debug http output.
|
85
|
+
# http.set_debug_output $stderr
|
86
|
+
response = http.start { |http| http.request(request) }
|
87
|
+
|
88
|
+
case response
|
89
|
+
when Net::HTTPSuccess then yield response.body if block_given?
|
90
|
+
when Net::HTTPBadRequest then raise Rumblr::RequestError, parse_error_message(response)
|
91
|
+
when Net::HTTPForbidden then raise Rumblr::AuthorizationError, parse_error_message(response)
|
92
|
+
when Net::HTTPNotFound then raise Rumblr::MissingResourceError, parse_error_message(response)
|
93
|
+
when Net::HTTPMovedPermanently then raise Rumblr::MovedResourceError, parse_error_message(response)
|
94
|
+
when Net::HTTPServerError then raise Rumblr::ServerError, connection_error_message
|
95
|
+
else raise "Received an unexpected HTTP response: #{response}"
|
96
|
+
end
|
97
|
+
|
98
|
+
response.body
|
99
|
+
end
|
100
|
+
|
101
|
+
# Extracts the error message from the response for the exception.
|
102
|
+
def parse_error_message(response)
|
103
|
+
response.body
|
104
|
+
end
|
105
|
+
|
106
|
+
def connection_error_message
|
107
|
+
"There was a problem connecting to Tumblr"
|
108
|
+
end
|
109
|
+
|
110
|
+
|
111
|
+
private
|
112
|
+
|
113
|
+
def parse_user_attributes_from(response_body)
|
114
|
+
parser, parser.string = XML::Parser.new, response_body
|
115
|
+
doc = parser.parse
|
116
|
+
tumblelogs = doc.find('//tumblr/tumblelog').inject([]) do |array, element|
|
117
|
+
tumblelog_attrs = cleanup_hash(element.attributes.to_h)
|
118
|
+
array << Tumblelog.new(tumblelog_attrs)
|
119
|
+
array
|
120
|
+
end
|
121
|
+
raw_user_attributes = doc.find('//tumblr/user').first.attributes.to_h
|
122
|
+
user_attributes = cleanup_hash(raw_user_attributes)
|
123
|
+
user_attributes.merge!(:tumblelogs => tumblelogs)
|
124
|
+
end
|
125
|
+
|
126
|
+
def cleanup_hash(attrs={})
|
127
|
+
clean_attrs = attrs.inject({}) do |hash,(key,value)|
|
128
|
+
mapped_key = key.gsub(/-/,'_').to_sym
|
129
|
+
mapped_value = case value
|
130
|
+
when /($1^|$yes^)/ then true
|
131
|
+
when /($0^|$no^)/ then false
|
132
|
+
else value
|
133
|
+
end
|
134
|
+
hash[mapped_key] = mapped_value
|
135
|
+
hash
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
end
|
140
|
+
|
141
|
+
end
|
data/lib/rumblr/post.rb
ADDED
@@ -0,0 +1,51 @@
|
|
1
|
+
module Rumblr
|
2
|
+
|
3
|
+
# for attribute details, see Tumblr's documentation:
|
4
|
+
# http://www.tumblr.com/api
|
5
|
+
class Post < Resource
|
6
|
+
attr_reader :id, :url, :type, :unix_timestamp, :date_gmt, :date, :tags, :private
|
7
|
+
|
8
|
+
def initialize(attrs={})
|
9
|
+
@private = false
|
10
|
+
super
|
11
|
+
end
|
12
|
+
|
13
|
+
def private?
|
14
|
+
@private
|
15
|
+
end
|
16
|
+
|
17
|
+
def public?
|
18
|
+
!self.private?
|
19
|
+
end
|
20
|
+
|
21
|
+
end
|
22
|
+
|
23
|
+
class RegularPost < Post
|
24
|
+
attr_reader :title, :body
|
25
|
+
end
|
26
|
+
|
27
|
+
class PhotoPost < Post
|
28
|
+
attr_reader :source, :data, :caption, :click_through_url
|
29
|
+
end
|
30
|
+
|
31
|
+
class QuotePost < Post
|
32
|
+
attr_reader :quote, :source
|
33
|
+
end
|
34
|
+
|
35
|
+
class LinkPost < Post
|
36
|
+
attr_reader :name, :url, :description
|
37
|
+
end
|
38
|
+
|
39
|
+
class ConversationPost < Post
|
40
|
+
attr_reader :title, :conversation
|
41
|
+
end
|
42
|
+
|
43
|
+
class VideoPost < Post
|
44
|
+
attr_reader :embed, :data, :title, :caption
|
45
|
+
end
|
46
|
+
|
47
|
+
class AudioPost < Post
|
48
|
+
attr_reader :data, :caption
|
49
|
+
end
|
50
|
+
|
51
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
module Rumblr
|
2
|
+
|
3
|
+
class Tumblelog < Resource
|
4
|
+
attr_reader :name, :timezone, :cname, :title, :url, :avatar_url, :is_primary,
|
5
|
+
:type, :private_id
|
6
|
+
|
7
|
+
def posts
|
8
|
+
return [] unless self.url
|
9
|
+
log, posts = Client.instance.read(:url => self.url)
|
10
|
+
return posts
|
11
|
+
end
|
12
|
+
|
13
|
+
class << self
|
14
|
+
|
15
|
+
def find_by_url(url)
|
16
|
+
log, posts = Client.instance.read(:url => url)
|
17
|
+
return log
|
18
|
+
end
|
19
|
+
|
20
|
+
end
|
21
|
+
|
22
|
+
end
|
23
|
+
|
24
|
+
end
|
data/lib/rumblr/user.rb
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
module Rumblr
|
2
|
+
|
3
|
+
class User < Resource
|
4
|
+
attr_reader :email, :password, :can_upload_video, :can_upload_aiff, :vimeo_login_url, :can_upload_audio
|
5
|
+
|
6
|
+
def tumblelogs
|
7
|
+
instance_variable_get(:@tumblelogs) || []
|
8
|
+
end
|
9
|
+
|
10
|
+
def primary_tumblelog
|
11
|
+
self.tumblelogs.find { |log| log.is_primary }
|
12
|
+
end
|
13
|
+
|
14
|
+
class << self
|
15
|
+
def login(attrs={})
|
16
|
+
email, password = attrs[:email], attrs[:password]
|
17
|
+
Client.instance.authenticate(email,password)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
end
|
22
|
+
|
23
|
+
end
|
data/lib/rumblr.rb
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
module Rumblr
|
2
|
+
|
3
|
+
class MissingResourceError < Exception; end
|
4
|
+
class MovedResourceError < Exception; end
|
5
|
+
class AuthorizationError < Exception; end
|
6
|
+
class RequestError < Exception; end
|
7
|
+
class ServerError < Exception; end
|
8
|
+
|
9
|
+
class << self
|
10
|
+
attr_accessor :api_url
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
Rumblr.api_url = "http://www.tumblr.com"
|
15
|
+
|
16
|
+
require 'rubygems'
|
17
|
+
require 'singleton'
|
18
|
+
require 'net/http'
|
19
|
+
require 'uri'
|
20
|
+
gem 'libxml-ruby', '>= 0.8.3'
|
21
|
+
require 'xml'
|
22
|
+
require 'pp'
|
23
|
+
|
24
|
+
require 'rumblr/client'
|
25
|
+
require 'rumblr/resource'
|
26
|
+
require 'rumblr/tumblelog'
|
27
|
+
require 'rumblr/user'
|
28
|
+
require 'rumblr/post'
|
@@ -0,0 +1,111 @@
|
|
1
|
+
require File.join(File.dirname(__FILE__), "/../spec_helper" )
|
2
|
+
|
3
|
+
module Rumblr
|
4
|
+
|
5
|
+
describe Client do
|
6
|
+
|
7
|
+
it 'should not be able to be instantiated externally' do
|
8
|
+
lambda { Client.new }.should raise_error(NoMethodError)
|
9
|
+
end
|
10
|
+
|
11
|
+
it 'should be able to instantiate itself' do
|
12
|
+
lambda { Client.instance }.should_not raise_error
|
13
|
+
end
|
14
|
+
|
15
|
+
end
|
16
|
+
|
17
|
+
describe Client, "authentication" do
|
18
|
+
|
19
|
+
before(:each) { mock_successful(:authenticate) }
|
20
|
+
|
21
|
+
it 'should raise an ArgumentError without email' do
|
22
|
+
lambda do
|
23
|
+
@client.authenticate('email', nil)
|
24
|
+
end.should raise_error(ArgumentError)
|
25
|
+
end
|
26
|
+
|
27
|
+
it 'should raise an ArgumentError without password' do
|
28
|
+
lambda do
|
29
|
+
@client.authenticate(nil,'password')
|
30
|
+
end.should raise_error(ArgumentError)
|
31
|
+
end
|
32
|
+
|
33
|
+
it 'should return a User if successful' do
|
34
|
+
result = @client.authenticate('foo@foo.com','password')
|
35
|
+
result.should be_an_instance_of(User)
|
36
|
+
end
|
37
|
+
|
38
|
+
it "should include user's existing tumblelogs" do
|
39
|
+
user = @client.authenticate('foo@foo.com','password')
|
40
|
+
user.tumblelogs.size.should == 2
|
41
|
+
end
|
42
|
+
|
43
|
+
end
|
44
|
+
|
45
|
+
describe Client, "reading from a public tumblelog" do
|
46
|
+
|
47
|
+
it 'without a URL should raise an error' do
|
48
|
+
mock_successful(:anonymous_read)
|
49
|
+
lambda do
|
50
|
+
@client.read
|
51
|
+
end.should raise_error(ArgumentError)
|
52
|
+
end
|
53
|
+
|
54
|
+
it 'with an empty URL should raise an error' do
|
55
|
+
mock_successful(:anonymous_read)
|
56
|
+
lambda do
|
57
|
+
@client.read(:url => '')
|
58
|
+
end.should raise_error(ArgumentError)
|
59
|
+
end
|
60
|
+
|
61
|
+
describe 'without credentials' do
|
62
|
+
|
63
|
+
before(:each) do
|
64
|
+
mock_successful(:anonymous_read)
|
65
|
+
@tumblelog, posts = @client.read(:url => 'http://dummylog.tumblr.com/')
|
66
|
+
end
|
67
|
+
|
68
|
+
it 'should yield only posts' do
|
69
|
+
@tumblelog.posts.all? {|post| post.kind_of?(Post)}.should be_true
|
70
|
+
end
|
71
|
+
|
72
|
+
it 'should not include all posts, assuming some are private' do
|
73
|
+
@tumblelog.posts.size.should == 7
|
74
|
+
end
|
75
|
+
|
76
|
+
it 'should only include public posts' do
|
77
|
+
@tumblelog.posts.all? {|post| post.public? }.should be_true
|
78
|
+
end
|
79
|
+
|
80
|
+
it 'should not include any private posts' do
|
81
|
+
@tumblelog.posts.any? {|post| post.private? }.should be_false
|
82
|
+
end
|
83
|
+
|
84
|
+
end
|
85
|
+
|
86
|
+
describe 'with valid credentials' do
|
87
|
+
|
88
|
+
before(:each) do
|
89
|
+
mock_successful(:authenticated_read)
|
90
|
+
@tumblelog, posts = @client.read( :url => 'http://dummylog.tumblr.com/',
|
91
|
+
:email => 'valid',
|
92
|
+
:password => 'valid' )
|
93
|
+
end
|
94
|
+
|
95
|
+
it 'should include all tumblelog posts' do
|
96
|
+
@tumblelog.posts.size.should == 14
|
97
|
+
end
|
98
|
+
|
99
|
+
it 'should include all public posts' do
|
100
|
+
@tumblelog.posts.find_all {|p| p.public? }.size.should == 7
|
101
|
+
end
|
102
|
+
|
103
|
+
it 'should include private posts' do
|
104
|
+
@tumblelog.posts.find_all {|p| p.public? }.size.should == 7
|
105
|
+
end
|
106
|
+
|
107
|
+
end
|
108
|
+
|
109
|
+
end
|
110
|
+
|
111
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
require File.join(File.dirname(__FILE__), "/../spec_helper" )
|
2
|
+
|
3
|
+
module Rumblr
|
4
|
+
|
5
|
+
describe Post, 'instances' do
|
6
|
+
|
7
|
+
it 'should have attributes shared by every Post' do
|
8
|
+
Post.new.should respond_to(:id, :url, :type, :unix_timestamp, :date_gmt, :date, :tags, :private)
|
9
|
+
end
|
10
|
+
|
11
|
+
it 'private attribute should default to false' do
|
12
|
+
Post.new.private.should be_false
|
13
|
+
end
|
14
|
+
|
15
|
+
it 'should provide it private status' do
|
16
|
+
Post.new.should respond_to(:private?)
|
17
|
+
end
|
18
|
+
|
19
|
+
it 'should provide it public status (inverse of private status)' do
|
20
|
+
Post.new.should respond_to(:public?)
|
21
|
+
end
|
22
|
+
|
23
|
+
end
|
24
|
+
|
25
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
require File.join(File.dirname(__FILE__), "/../spec_helper" )
|
2
|
+
|
3
|
+
module Rumblr
|
4
|
+
|
5
|
+
describe Tumblelog, 'instances' do
|
6
|
+
|
7
|
+
it 'should have the attributes of a Tumblelog' do
|
8
|
+
Tumblelog.new.should respond_to( :name, :timezone, :cname, :title, :url,
|
9
|
+
:avatar_url, :is_primary, :type, :private_id)
|
10
|
+
end
|
11
|
+
|
12
|
+
it "' posts should initialize empty" do
|
13
|
+
Tumblelog.new.posts.should be_a_kind_of(Array)
|
14
|
+
end
|
15
|
+
|
16
|
+
end
|
17
|
+
|
18
|
+
describe Tumblelog, 'finder' do
|
19
|
+
|
20
|
+
describe 'provided a valid URL' do
|
21
|
+
|
22
|
+
it 'should successfully retrieve a tumblelog' do
|
23
|
+
mock_successful(:anonymous_read)
|
24
|
+
log = Tumblelog.find_by_url('valid_url')
|
25
|
+
log.should be_an_instance_of(Tumblelog)
|
26
|
+
end
|
27
|
+
|
28
|
+
it 'should retrieve a tumblelog with posts' do
|
29
|
+
mock_successful(:anonymous_read)
|
30
|
+
log = Tumblelog.find_by_url('valid_url')
|
31
|
+
log.posts.first.should be_a_kind_of(Post)
|
32
|
+
log.posts.first.type.should_not be_nil
|
33
|
+
end
|
34
|
+
|
35
|
+
end
|
36
|
+
|
37
|
+
end
|
38
|
+
|
39
|
+
end
|
@@ -0,0 +1,69 @@
|
|
1
|
+
require File.join(File.dirname(__FILE__), "/../spec_helper" )
|
2
|
+
|
3
|
+
module Rumblr
|
4
|
+
|
5
|
+
describe User, 'initializing' do
|
6
|
+
|
7
|
+
it 'should have the attributes of a User' do
|
8
|
+
User.new.should respond_to( :email,
|
9
|
+
:password,
|
10
|
+
:can_upload_video,
|
11
|
+
:can_upload_aiff,
|
12
|
+
:vimeo_login_url,
|
13
|
+
:can_upload_audio )
|
14
|
+
end
|
15
|
+
|
16
|
+
it 'tumblelogs should initialize empty' do
|
17
|
+
User.new.tumblelogs.should be_a_kind_of(Array)
|
18
|
+
end
|
19
|
+
|
20
|
+
end
|
21
|
+
|
22
|
+
describe User, 'logging in' do
|
23
|
+
|
24
|
+
before(:each) do
|
25
|
+
mock_successful(:authenticate)
|
26
|
+
end
|
27
|
+
|
28
|
+
it 'should login with email and password' do
|
29
|
+
user = User.login(:email => 'valid_email', :password => 'valid_password')
|
30
|
+
user.should be_an_instance_of(User)
|
31
|
+
end
|
32
|
+
|
33
|
+
it 'should not initialize without email' do
|
34
|
+
lambda do
|
35
|
+
User.login(:email => nil, :password => 'valid_password')
|
36
|
+
end.should raise_error(ArgumentError)
|
37
|
+
end
|
38
|
+
|
39
|
+
it 'should not initialize without password' do
|
40
|
+
lambda do
|
41
|
+
User.login(:email => 'valid_email', :password => nil)
|
42
|
+
end.should raise_error(ArgumentError)
|
43
|
+
end
|
44
|
+
|
45
|
+
end
|
46
|
+
|
47
|
+
describe User, ', once logged in,' do
|
48
|
+
|
49
|
+
before(:each) do
|
50
|
+
mock_successful(:authenticate)
|
51
|
+
@user = User.login(:email => 'valid_email', :password => 'valid_password')
|
52
|
+
end
|
53
|
+
|
54
|
+
it 'should provide one or more tumblelogs' do
|
55
|
+
@user.tumblelogs.size.should == 2
|
56
|
+
end
|
57
|
+
|
58
|
+
it 'should provide *only* tumblelogs' do
|
59
|
+
@user.tumblelogs.all? { |obj| obj.should be_an_instance_of(Tumblelog) }
|
60
|
+
end
|
61
|
+
|
62
|
+
it 'should provide a primary tumblelog' do
|
63
|
+
primary = @user.primary_tumblelog
|
64
|
+
primary.is_primary.should be_true
|
65
|
+
end
|
66
|
+
|
67
|
+
end
|
68
|
+
|
69
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,105 @@
|
|
1
|
+
$: << File.join(File.dirname(__FILE__), "/../lib" )
|
2
|
+
|
3
|
+
require 'spec'
|
4
|
+
require 'rumblr'
|
5
|
+
|
6
|
+
def mock_successful(request)
|
7
|
+
responses_to = {
|
8
|
+
:authenticate => successful_authenticate_xml,
|
9
|
+
:anonymous_read => successful_anonymous_read_xml,
|
10
|
+
:authenticated_read => successful_authenticated_read_xml,
|
11
|
+
:authenticated_write => successful_authenticated_write_xml
|
12
|
+
}
|
13
|
+
|
14
|
+
@client = Rumblr::Client.instance
|
15
|
+
# yes, this is cheating, but for now just concerned that xml is parsed right
|
16
|
+
@client.stub!(:complete_request).and_yield(responses_to[request])
|
17
|
+
end
|
18
|
+
|
19
|
+
def successful_authenticate_xml
|
20
|
+
response = <<-EOF
|
21
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
22
|
+
<tumblr version="1.0">
|
23
|
+
<user can-upload-audio="1" can-upload-aiff="1" can-upload-video="0" vimeo-login-url="http://www.vimeo.com/services/foo"/>
|
24
|
+
<tumblelog title="" name="foo" url="http://foo.com/" type="public" avatar-url="http://assets.tumblr.com/images/default_avatar_foo.gif" is-primary="yes"/>
|
25
|
+
<tumblelog title="bar" private-id="000000" type="private"/>
|
26
|
+
</tumblr>
|
27
|
+
EOF
|
28
|
+
response.strip!
|
29
|
+
end
|
30
|
+
|
31
|
+
def successful_anonymous_read_xml
|
32
|
+
response = <<-EOF
|
33
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
34
|
+
<tumblr version="1.0">
|
35
|
+
<tumblelog name="dummylog" timezone="US/Eastern" title="dummy tumblelog title">dummylog description</tumblelog>
|
36
|
+
<posts start="0" total="7">
|
37
|
+
<post id="78846368" url="http://dummylog.tumblr.com/post/78846368" type="video" date-gmt="2009-02-16 19:11:45 GMT" date="Mon, 16 Feb 2009 14:11:45" unix-timestamp="1234811505">
|
38
|
+
<video-caption>public video post caption</video-caption>
|
39
|
+
<video-source>http://www.youtube.com/watch?v=rPaWZOM1LnY</video-source>
|
40
|
+
<video-player><object width="400" height="336"><param name="movie" value="http://www.youtube.com/v/rPaWZOM1LnY&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/rPaWZOM1LnY&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></video-player>
|
41
|
+
<tag>sale</tag>
|
42
|
+
<tag>music</tag>
|
43
|
+
</post>
|
44
|
+
<post id="78846080" url="http://dummylog.tumblr.com/post/78846080" type="audio" date-gmt="2009-02-16 19:10:20 GMT" date="Mon, 16 Feb 2009 14:10:20" unix-timestamp="1234811420" audio-plays="0">
|
45
|
+
<audio-caption>public audio post description</audio-caption><audio-player><embed type="application/x-shockwave-flash" src="http://dummylog.tumblr.com/swf/audio_player.swf?audio_file=http://www.tumblr.com/audio_file/78846080/TofwJxqCAk12g1e0ClxHx7gh&color=FFFFFF" height="27" width="207" quality="best"></embed></audio-player>
|
46
|
+
<tag>sale</tag>
|
47
|
+
</post>
|
48
|
+
<post id="78844911" url="http://dummylog.tumblr.com/post/78844911" type="conversation" date-gmt="2009-02-16 19:04:48 GMT" date="Mon, 16 Feb 2009 14:04:48" unix-timestamp="1234811088">
|
49
|
+
<conversation-title>public chat post title</conversation-title>
|
50
|
+
<conversation-text>me: public
|
51
|
+
you: chat
|
52
|
+
me: post
|
53
|
+
you: dialogue</conversation-text><conversation><line name="me" label="me:">public </line><line name="you" label="you:">chat </line><line name="me" label="me:">post </line><line name="you" label="you:">dialogue</line></conversation><conversation-line name="me" label="me:" deprecated="conversation-line is deprecated and will soon be removed. Use ./conversation/line.">public </conversation-line><conversation-line name="you" label="you:" deprecated="conversation-line is deprecated and will soon be removed. Use ./conversation/line.">chat </conversation-line><conversation-line name="me" label="me:" deprecated="conversation-line is deprecated and will soon be removed. Use ./conversation/line.">post </conversation-line><conversation-line name="you" label="you:" deprecated="conversation-line is deprecated and will soon be removed. Use ./conversation/line.">dialogue</conversation-line>
|
54
|
+
<tag>sale</tag>
|
55
|
+
</post>
|
56
|
+
<post id="78844351" url="http://dummylog.tumblr.com/post/78844351" type="link" date-gmt="2009-02-16 19:02:00 GMT" date="Mon, 16 Feb 2009 14:02:00" unix-timestamp="1234810920">
|
57
|
+
<link-text>public link post name</link-text><link-url>http://www.google.com</link-url>
|
58
|
+
<link-description>public link post description</link-description>
|
59
|
+
<tag>sale</tag>
|
60
|
+
</post>
|
61
|
+
<post id="78844076" url="http://dummylog.tumblr.com/post/78844076" type="quote" date-gmt="2009-02-16 19:01:06 GMT" date="Mon, 16 Feb 2009 14:01:06" unix-timestamp="1234810866">
|
62
|
+
<quote-text>public quote post content</quote-text>
|
63
|
+
<quote-source>public quote post source</quote-source>
|
64
|
+
<tag>sale</tag>
|
65
|
+
</post>
|
66
|
+
<post id="78841604" url="http://dummylog.tumblr.com/post/78841604" type="regular" date-gmt="2009-02-16 18:50:00 GMT" date="Mon, 16 Feb 2009 13:50:00" unix-timestamp="1234810200">
|
67
|
+
<regular-title>public text post title</regular-title>
|
68
|
+
<regular-body>public text post body</regular-body>
|
69
|
+
<tag>sale</tag>
|
70
|
+
</post>
|
71
|
+
<post id="78841444" url="http://dummylog.tumblr.com/post/78841444" type="photo" date-gmt="2009-02-16 18:49:00 GMT" date="Mon, 16 Feb 2009 13:49:00" unix-timestamp="1234810140">
|
72
|
+
<photo-caption>public photo post</photo-caption>
|
73
|
+
<photo-url max-width="500">http://21.media.tumblr.com/TofwJxqCAk11p5psVgVhLwzJo1_500.jpg</photo-url>
|
74
|
+
<photo-url max-width="400">http://1.media.tumblr.com/TofwJxqCAk11p5psVgVhLwzJo1_400.jpg</photo-url>
|
75
|
+
<photo-url max-width="250">http://22.media.tumblr.com/TofwJxqCAk11p5psVgVhLwzJo1_250.jpg</photo-url>
|
76
|
+
<photo-url max-width="100">http://19.media.tumblr.com/TofwJxqCAk11p5psVgVhLwzJo1_100.jpg</photo-url>
|
77
|
+
<photo-url max-width="75">http://2.media.tumblr.com/TofwJxqCAk11p5psVgVhLwzJo1_75sq.jpg</photo-url>
|
78
|
+
<tag>sale</tag>
|
79
|
+
</post>
|
80
|
+
</posts>
|
81
|
+
</tumblr>
|
82
|
+
EOF
|
83
|
+
response.strip!
|
84
|
+
end
|
85
|
+
|
86
|
+
def successful_authenticated_read_xml
|
87
|
+
response = <<-EOF
|
88
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
89
|
+
<tumblr version="1.0"><tumblelog name="dummylog" timezone="US/Eastern" title="dummy tumblelog title">dummy tumblelog description</tumblelog><posts start="0" total="7"><post id="78846474" url="http://dummylog.tumblr.com/post/78846474" type="video" date-gmt="2009-02-16 19:12:00 GMT" date="Mon, 16 Feb 2009 14:12:00" unix-timestamp="1234811520" private="true"><video-caption>private video post caption</video-caption><video-source>http://www.youtube.com/watch?v=rPaWZOM1LnY</video-source><video-player><object width="400" height="336"><param name="movie" value="http://www.youtube.com/v/rPaWZOM1LnY&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/rPaWZOM1LnY&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></video-player><tag>sale</tag><tag>tag2</tag><tag>music</tag></post><post id="78846368" url="http://dummylog.tumblr.com/post/78846368" type="video" date-gmt="2009-02-16 19:11:00 GMT" date="Mon, 16 Feb 2009 14:11:00" unix-timestamp="1234811460"><video-caption>public video post caption</video-caption><video-source>hilhttp://www.youtube.com/watch?v=rPaWZOM1LnY</video-source><video-player><object width="400" height="336"><param name="movie" value="http://www.youtube.com/v/rPaWZOM1LnY&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/rPaWZOM1LnY&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></video-player><tag>hilarious</tag></post><post id="78846150" url="http://dummylog.tumblr.com/post/78846150" type="audio" date-gmt="2009-02-16 19:10:43 GMT" date="Mon, 16 Feb 2009 14:10:43" unix-timestamp="1234811443" private="true" audio-plays="2"><audio-caption>private audio post description</audio-caption><audio-player><embed type="application/x-shockwave-flash" src="http://dummylog.tumblr.com/swf/audio_player.swf?audio_file=http://www.tumblr.com/audio_file/78846150/TofwJxqCAk12gje4UYRllUwM&color=FFFFFF" height="27" width="207" quality="best"></embed></audio-player><tag>sale</tag></post><post id="78846080" url="http://dummylog.tumblr.com/post/78846080" type="audio" date-gmt="2009-02-16 19:10:00 GMT" date="Mon, 16 Feb 2009 14:10:00" unix-timestamp="1234811400" audio-plays="0"><audio-caption>public audio post description</audio-caption><audio-player><embed type="application/x-shockwave-flash" src="http://dummylog.tumblr.com/swf/audio_player.swf?audio_file=http://www.tumblr.com/audio_file/78846080/TofwJxqCAk12g1e0ClxHx7gh&color=FFFFFF" height="27" width="207" quality="best"></embed></audio-player><tag>sale</tag><tag>music</tag></post><post id="78844968" url="http://dummylog.tumblr.com/post/78844968" type="conversation" date-gmt="2009-02-16 19:05:08 GMT" date="Mon, 16 Feb 2009 14:05:08" unix-timestamp="1234811108" private="true"><conversation-title>private chat post title</conversation-title><conversation-text>me: private
|
90
|
+
you: chat
|
91
|
+
me: post
|
92
|
+
you: dialogue</conversation-text><conversation><line name="me" label="me:">private </line><line name="you" label="you:">chat </line><line name="me" label="me:">post </line><line name="you" label="you:">dialogue</line></conversation><conversation-line name="me" label="me:" deprecated="conversation-line is deprecated and will soon be removed. Use ./conversation/line.">private </conversation-line><conversation-line name="you" label="you:" deprecated="conversation-line is deprecated and will soon be removed. Use ./conversation/line.">chat </conversation-line><conversation-line name="me" label="me:" deprecated="conversation-line is deprecated and will soon be removed. Use ./conversation/line.">post </conversation-line><conversation-line name="you" label="you:" deprecated="conversation-line is deprecated and will soon be removed. Use ./conversation/line.">dialogue</conversation-line><tag>sale</tag></post><post id="78844911" url="http://dummylog.tumblr.com/post/78844911" type="conversation" date-gmt="2009-02-16 19:04:48 GMT" date="Mon, 16 Feb 2009 14:04:48" unix-timestamp="1234811088"><conversation-title>public chat post title</conversation-title><conversation-text>me: public
|
93
|
+
you: chat
|
94
|
+
me: post
|
95
|
+
you: dialogue</conversation-text><conversation><line name="me" label="me:">public </line><line name="you" label="you:">chat </line><line name="me" label="me:">post </line><line name="you" label="you:">dialogue</line></conversation><conversation-line name="me" label="me:" deprecated="conversation-line is deprecated and will soon be removed. Use ./conversation/line.">public </conversation-line><conversation-line name="you" label="you:" deprecated="conversation-line is deprecated and will soon be removed. Use ./conversation/line.">chat </conversation-line><conversation-line name="me" label="me:" deprecated="conversation-line is deprecated and will soon be removed. Use ./conversation/line.">post </conversation-line><conversation-line name="you" label="you:" deprecated="conversation-line is deprecated and will soon be removed. Use ./conversation/line.">dialogue</conversation-line><tag>sale</tag></post><post id="78844625" url="http://dummylog.tumblr.com/post/78844625" type="link" date-gmt="2009-02-16 19:03:00 GMT" date="Mon, 16 Feb 2009 14:03:00" unix-timestamp="1234810980" private="true"><link-text>private link post name</link-text><link-url>http://www.google.com</link-url><link-description>private link post description</link-description><tag>sale</tag></post><post id="78844351" url="http://dummylog.tumblr.com/post/78844351" type="link" date-gmt="2009-02-16 19:02:00 GMT" date="Mon, 16 Feb 2009 14:02:00" unix-timestamp="1234810920"><link-text>public link post name</link-text><link-url>http://www.google.com</link-url><link-description>public link post description</link-description><tag>sale</tag></post><post id="78844076" url="http://dummylog.tumblr.com/post/78844076" type="quote" date-gmt="2009-02-16 19:01:06 GMT" date="Mon, 16 Feb 2009 14:01:06" unix-timestamp="1234810866"><quote-text>public quote post content</quote-text><quote-source>public quote post source</quote-source><tag>sale</tag></post><post id="78844136" url="http://dummylog.tumblr.com/post/78844136" type="quote" date-gmt="2009-02-16 19:01:00 GMT" date="Mon, 16 Feb 2009 14:01:00" unix-timestamp="1234810860" private="true"><quote-text>private quote post content</quote-text><quote-source>private quote post content</quote-source><tag>sale</tag></post><post id="78843778" url="http://dummylog.tumblr.com/post/78843778" type="photo" date-gmt="2009-02-16 18:59:40 GMT" date="Mon, 16 Feb 2009 13:59:40" unix-timestamp="1234810780" private="true"><photo-caption>private photo post</photo-caption><photo-url max-width="500">http://7.media.tumblr.com/TofwJxqCAk122bofvQLGsl89o1_500.jpg</photo-url><photo-url max-width="400">http://6.media.tumblr.com/TofwJxqCAk122bofvQLGsl89o1_400.jpg</photo-url><photo-url max-width="250">http://11.media.tumblr.com/TofwJxqCAk122bofvQLGsl89o1_250.jpg</photo-url><photo-url max-width="100">http://11.media.tumblr.com/TofwJxqCAk122bofvQLGsl89o1_100.jpg</photo-url><photo-url max-width="75">http://22.media.tumblr.com/TofwJxqCAk122bofvQLGsl89o1_75sq.jpg</photo-url><tag>sale</tag></post><post id="78843501" url="http://dummylog.tumblr.com/post/78843501" type="regular" date-gmt="2009-02-16 18:58:00 GMT" date="Mon, 16 Feb 2009 13:58:00" unix-timestamp="1234810680" private="true"><regular-title>private text post title</regular-title><regular-body>private text post body</regular-body><tag>sale</tag></post><post id="78841604" url="http://dummylog.tumblr.com/post/78841604" type="regular" date-gmt="2009-02-16 18:50:00 GMT" date="Mon, 16 Feb 2009 13:50:00" unix-timestamp="1234810200"><regular-title>public text post title</regular-title><regular-body>public text post body</regular-body><tag>sale</tag></post><post id="78841444" url="http://dummylog.tumblr.com/post/78841444" type="photo" date-gmt="2009-02-16 18:49:00 GMT" date="Mon, 16 Feb 2009 13:49:00" unix-timestamp="1234810140"><photo-caption>public photo post</photo-caption><photo-url max-width="500">http://21.media.tumblr.com/TofwJxqCAk11p5psVgVhLwzJo1_500.jpg</photo-url><photo-url max-width="400">http://1.media.tumblr.com/TofwJxqCAk11p5psVgVhLwzJo1_400.jpg</photo-url><photo-url max-width="250">http://22.media.tumblr.com/TofwJxqCAk11p5psVgVhLwzJo1_250.jpg</photo-url><photo-url max-width="100">http://19.media.tumblr.com/TofwJxqCAk11p5psVgVhLwzJo1_100.jpg</photo-url><photo-url max-width="75">http://2.media.tumblr.com/TofwJxqCAk11p5psVgVhLwzJo1_75sq.jpg</photo-url><tag>sale</tag></post></posts></tumblr>
|
96
|
+
EOF
|
97
|
+
response.strip!
|
98
|
+
end
|
99
|
+
|
100
|
+
def successful_authenticated_write_xml
|
101
|
+
response = <<-EOF
|
102
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
103
|
+
EOF
|
104
|
+
response.strip!
|
105
|
+
end
|
metadata
ADDED
@@ -0,0 +1,78 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: jamescallmebrent-rumblr
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Brent Hargrave
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
|
12
|
+
date: 2009-02-17 00:00:00 -08:00
|
13
|
+
default_executable:
|
14
|
+
dependencies:
|
15
|
+
- !ruby/object:Gem::Dependency
|
16
|
+
name: libxml-ruby
|
17
|
+
type: :runtime
|
18
|
+
version_requirement:
|
19
|
+
version_requirements: !ruby/object:Gem::Requirement
|
20
|
+
requirements:
|
21
|
+
- - ">="
|
22
|
+
- !ruby/object:Gem::Version
|
23
|
+
version: 0.8.3
|
24
|
+
version:
|
25
|
+
description: Ruby client for the Tumblr API
|
26
|
+
email: brent.hargrave@gmail.com
|
27
|
+
executables: []
|
28
|
+
|
29
|
+
extensions: []
|
30
|
+
|
31
|
+
extra_rdoc_files: []
|
32
|
+
|
33
|
+
files:
|
34
|
+
- LICENSE.rdoc
|
35
|
+
- README.rdoc
|
36
|
+
- VERSION.yml
|
37
|
+
- lib/rumblr
|
38
|
+
- lib/rumblr/client.rb
|
39
|
+
- lib/rumblr/post.rb
|
40
|
+
- lib/rumblr/resource.rb
|
41
|
+
- lib/rumblr/tumblelog.rb
|
42
|
+
- lib/rumblr/user.rb
|
43
|
+
- lib/rumblr.rb
|
44
|
+
- spec/rumblr
|
45
|
+
- spec/rumblr/client_spec.rb
|
46
|
+
- spec/rumblr/post_spec.rb
|
47
|
+
- spec/rumblr/tumblelog_spec.rb
|
48
|
+
- spec/rumblr/user_spec.rb
|
49
|
+
- spec/spec_helper.rb
|
50
|
+
has_rdoc: true
|
51
|
+
homepage: http://github.com/jamescallmebrent/rumblr
|
52
|
+
post_install_message:
|
53
|
+
rdoc_options:
|
54
|
+
- --inline-source
|
55
|
+
- --charset=UTF-8
|
56
|
+
require_paths:
|
57
|
+
- lib
|
58
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
59
|
+
requirements:
|
60
|
+
- - ">="
|
61
|
+
- !ruby/object:Gem::Version
|
62
|
+
version: "0"
|
63
|
+
version:
|
64
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - ">="
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: "0"
|
69
|
+
version:
|
70
|
+
requirements: []
|
71
|
+
|
72
|
+
rubyforge_project:
|
73
|
+
rubygems_version: 1.2.0
|
74
|
+
signing_key:
|
75
|
+
specification_version: 2
|
76
|
+
summary: Ruby client for the Tumblr API
|
77
|
+
test_files: []
|
78
|
+
|