twitter4r 0.1.1 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
data/lib/twitter/core.rb CHANGED
@@ -1,198 +1,136 @@
1
1
  # The Twitter4R API provides a nicer Ruby object API to work with
2
2
  # instead of coding around the REST API.
3
3
 
4
- require('net/https')
5
- require('uri')
6
- require('json')
7
-
8
- # Encapsules the Twitter4R API.
4
+ # Module to encapsule the Twitter4R API.
9
5
  module Twitter
6
+ # Mixin module for classes that need to have a constructor similar to
7
+ # Rails' models, where a <tt>Hash</tt> is provided to set attributes
8
+ # appropriately.
9
+ #
10
+ # To define a class that uses this mixin, use the following code:
11
+ # class FilmActor
12
+ # include ClassUtilMixin
13
+ # end
10
14
  module ClassUtilMixin #:nodoc:
11
- def self.included(base)
15
+ def self.included(base) #:nodoc:
12
16
  base.send(:include, InstanceMethods)
13
17
  end
14
18
 
19
+ # Instance methods defined for <tt>Twitter::ModelMixin</tt> module.
15
20
  module InstanceMethods #:nodoc:
21
+ # Constructor/initializer that takes a hash of parameters that
22
+ # will initialize *members* or instance attributes to the
23
+ # values given. For example,
24
+ #
25
+ # class FilmActor
26
+ # include Twitter::ClassUtilMixin
27
+ # attr_accessor :name
28
+ # end
29
+ #
30
+ # class Production
31
+ # include Twitter::ClassUtilMixin
32
+ # attr_accessor :title, :year, :actors
33
+ # end
34
+ #
35
+ # # Favorite actress...
36
+ # jodhi = FilmActor.new(:name => "Jodhi May")
37
+ # jodhi.name # => "Jodhi May"
38
+ #
39
+ # # Favorite actor...
40
+ # robert = FilmActor.new(:name => "Robert Lindsay")
41
+ # robert.name # => "Robert Lindsay"
42
+ #
43
+ # # Jane is also an excellent pick...gotta love her accent!
44
+ # jane = FilmActor.new(name => "Jane Horrocks")
45
+ # jane.name # => "Jane Horrocks"
46
+ #
47
+ # # Witty BBC series...
48
+ # mrs_pritchard = Production.new(:title => "The Amazing Mrs. Pritchard",
49
+ # :year => 2005,
50
+ # :actors => [jodhi, jane])
51
+ # mrs_pritchard.title # => "The Amazing Mrs. Pritchard"
52
+ # mrs_pritchard.year # => 2005
53
+ # mrs_pritchard.actors # => [#<FilmActor:0xb79d6bbc @name="Jodhi May">,
54
+ # <FilmActor:0xb79d319c @name="Jane Horrocks">]
55
+ # # Any Ros Pritchard's out there to save us from the Tony Blair
56
+ # # and Gordon Brown *New Labour* debacle? You've got my vote!
57
+ #
58
+ # jericho = Production.new(:title => "Jericho",
59
+ # :year => 2005,
60
+ # :actors => [robert])
61
+ # jericho.title # => "Jericho"
62
+ # jericho.year # => 2005
63
+ # jericho.actors # => [#<FilmActor:0xc95d3eec @name="Robert Lindsay">]
64
+ #
65
+ # Assuming class <tt>FilmActor</tt> includes
66
+ # <tt>Twitter::ClassUtilMixin</tt> in the class definition
67
+ # and has an attribute of <tt>name</tt>, then that instance
68
+ # attribute will be set to "Jodhi May" for the <tt>actress</tt>
69
+ # object during object initialization (aka construction for
70
+ # you Java heads).
16
71
  def initialize(params = {})
17
72
  params.each do |key,val|
18
73
  self.send("#{key}=", val) if self.respond_to? key
19
74
  end
75
+ self.send(:init) if self.respond_to? :init
20
76
  end
77
+
78
+ protected
79
+ # Helper method to provide an easy and terse way to require
80
+ # a block is provided to a method.
81
+ def require_block(block_given)
82
+ raise ArgumentError, "Must provide a block" unless block_given
83
+ end
21
84
  end
22
85
  end # ClassUtilMixin
23
86
 
24
- # Error subclass raised when there is an error encountered upon
25
- # querying or posting to the REST API.
87
+ # Exception subclass raised when there is an error encountered upon
88
+ # querying or posting to the remote Twitter REST API.
89
+ #
90
+ # To consume and query any <tt>RESTError</tt> raised by Twitter4R:
91
+ # begin
92
+ # # Do something with your instance of <tt>Twitter::Client</tt>.
93
+ # # Maybe something like:
94
+ # timeline = twitter.timeline_for(:public)
95
+ # rescue RESTError => re
96
+ # puts re.code, re.message, re.uri
97
+ # end
98
+ # Which on the code raising a <tt>RESTError</tt> will output something like:
99
+ # 404
100
+ # Resource Not Found
101
+ # /i_am_crap.json
26
102
  class RESTError < Exception
27
103
  include ClassUtilMixin
28
104
  attr_accessor :code, :message, :uri
29
105
 
106
+ # Returns string in following format:
107
+ # "HTTP #{@code}: #{@message} at #{@uri}"
108
+ # For example,
109
+ # "HTTP 404: Resource Not Found at /i_am_crap.json"
30
110
  def to_s
31
- "HTTP #{code}: #{message} at #{uri}"
111
+ "HTTP #{@code}: #{@message} at #{@uri}"
32
112
  end
33
113
  end # RESTError
34
-
35
- # Represents a user of Twitter
36
- class User
37
- include ClassUtilMixin
38
- attr_accessor :id, :name, :description, :location, :screen_name, :url
39
-
40
- def eql?(obj)
41
- [:id, :name, :description, :location,
42
- :screen_name, :url].each do |att|
43
- return false unless self.send(att).eql?(obj.send(att))
44
- end
45
- true
46
- end
47
- end # User
48
114
 
49
- # Represents a status posted to Twitter by a user of Twitter.
50
- class Status
51
- include ClassUtilMixin
52
- attr_accessor :id, :text, :created_at, :user
53
-
54
- def eql?(obj)
55
- [:id, :text, :created_at, :user].each do |att|
56
- return false unless self.send(att).eql?(obj.send(att))
57
- end
58
- true
59
- end
60
- end # Status
61
-
62
- # Used to query or post to the Twitter REST API to simplify code.
63
- class Client
64
- include ClassUtilMixin
65
- attr_accessor :login, :password
66
-
67
- @@CONF = {
68
- :host => 'twitter.com',
69
- :port => 80,
70
- :ssl => false,
71
- }
72
- @@URIS = {:public => '/statuses/public_timeline.json',
73
- :friends => '/statuses/friends_timeline.json',
74
- :friends_statues => '/statuses/friends.json',
75
- :followers => '/statuses/followers.json',
76
- :update => '/statuses/update.json',
77
- :send_direct_message => '/direct_messages/new.json', }
78
-
79
- class << self
80
- # The following configuration options can be passed in a hash to this method:
81
- # * <tt>host</tt> - host of the REST server, defaults to twitter.com. Should only be changed for integration testing.
82
- # * <tt>port</tt> - port of the REST server, defaults to 443.
83
- # * <tt>ssl</tt> - SSL flag, defaults to false. Do not use :use_ssl anymore.
84
- # * <tt>proxy_host</tt> - proxy host. No default.
85
- # * <tt>proxy_port</tt> - proxy port. No default.
86
- # * <tt>proxy_user</tt> - proxy username. No default.
87
- # * <tt>proxy_pass</tt> - proxy password. No default.
88
- def config(conf)
89
- @@CONF.merge!(conf)
90
- end
91
-
92
- # Mostly helper method for irb shell prototyping
93
- # TODO: move this out as class extension for twitter4r console script
94
- def from_config(config_file, env = 'test')
95
- yaml_hash = YAML.load(File.read(config_file))
96
- self.new yaml_hash[env]
97
- end
98
- end # class << self
99
-
100
- def timeline(type = :public)
101
- http = create_http_connection
102
- http.start do |http|
103
- timeline_request(type, http)
104
- end # http.start block
105
- end # timeline
106
-
107
- def public_timeline
108
- timeline :public
109
- end
110
-
111
- def friend_timeline
112
- timeline :friends
113
- end
114
-
115
- def friend_statuses
116
- timeline :friends_statuses
117
- end
118
-
119
- def follower_statuses
120
- timeline :followers
121
- end
122
-
123
- def update(message)
124
- uri = @@URIS[:update]
125
- http = create_http_connection
126
- http.start do |http|
127
- request = Net::HTTP::Post.new(uri)
128
- request.basic_auth(@login, @password)
129
- response = http.request(request, "status=#{URI.escape(message)}")
130
- handle_rest_response(response, uri)
131
- end
132
- end
133
-
134
- def send_direct_message(user, message)
135
- login = user.respond_to?(:screen_name) ? user.screen_name : user
136
- uri = @@URIS[:send_direct_message]
137
- http = create_http_connection
138
- http.start do |http|
139
- request = Net::HTTP::Post.new(uri)
140
- request.basic_auth(@login, @password)
141
- response = http.request(request, "user=#{login}&text=#{URI.escape(message)}")
142
- handle_rest_response(response, uri)
143
- end
144
- end
145
-
146
- protected
147
- attr_accessor :login, :password, :host, :port
148
-
149
- def unmarshall_statuses(status_array)
150
- status_array.collect do |status|
151
- Status.new(:id => status['id'],
152
- :text => status['text'],
153
- :user => unmarshall_user(status['user']),
154
- :created_at => Time.parse(status['created_at'])
155
- )
156
- end
157
- end
158
-
159
- def unmarshall_user(map)
160
- User.new(:id => map['id'], :name => map['name'],
161
- :screen_name => map['screen_name'],
162
- :description => map['description'],
163
- :location => map['location'],
164
- :url => map['url'])
165
- end
166
-
167
- def timeline_request(type, http)
168
- uri = @@URIS[type]
169
- request = Net::HTTP::Get.new(uri)
170
- request.basic_auth(@login, @password)
171
- response = http.request(request)
172
-
173
- handle_rest_response(response, uri)
174
- unmarshall_statuses(JSON.parse(response.body))
175
- end
176
-
177
- def create_http_connection
178
- conn = Net::HTTP.new(@@CONF[:host], @@CONF[:port],
179
- @@CONF[:proxy_host], @@CONF[:proxy_port],
180
- @@CONF[:proxy_user], @@CONF[:proxy_pass])
181
- conn.use_ssl = @@CONF[:ssl]
182
- conn.verify_mode = OpenSSL::SSL::VERIFY_NONE if @@CONF[:ssl]
183
- conn
184
- end
185
-
186
- def raise_rest_error(response, uri = nil)
187
- raise RESTError.new(:code => response.code,
188
- :message => response.message,
189
- :uri => uri)
190
- end
191
-
192
- def handle_rest_response(response, uri)
193
- unless ["200", "201"].member?(response.code)
194
- raise_rest_error(response, uri)
195
- end
196
- end
115
+ # Remote REST API interface representation
116
+ #
117
+ class RESTInterfaceSpec
118
+ include ClassUtilMixin
119
+
120
+ end
121
+
122
+ # Remote REST API method representation
123
+ #
124
+ class RESTMethodSpec
125
+ include ClassUtilMixin
126
+ attr_accessor :uri, :method, :parameters
127
+ end
128
+
129
+ # Remote REST API method parameter representation
130
+ #
131
+ class RESTParameterSpec
132
+ include ClassUtilMixin
133
+ attr_accessor :name, :type, :required
134
+ def required?; @required; end
197
135
  end
198
136
  end
@@ -0,0 +1,32 @@
1
+ # Contains Ruby standard library extensions specific to <tt>Twitter4R</tt> library.
2
+
3
+ # Extension to Hash to create URL encoded string from key-values
4
+ class Hash
5
+ # Returns string formatted for HTTP URL encoded name-value pairs.
6
+ # For example,
7
+ # {:id => 'thomas_hardy'}.to_http_str
8
+ # # => "id=thomas_hardy"
9
+ # {:id => 23423, :since => Time.now}.to_http_str
10
+ # # => "since=Thu,%2021%20Jun%202007%2012:10:05%20-0500&id=23423"
11
+ def to_http_str
12
+ result = ''
13
+ return result if self.empty?
14
+ self.each do |key, val|
15
+ result << "#{key}=#{URI.encode(val.to_s)}&"
16
+ end
17
+ result.chop # remove the last '&' character, since it can be discarded
18
+ end
19
+ end
20
+
21
+ # Extension to Time that outputs RFC2822 compliant string on #to_s
22
+ class Time
23
+ alias :old_to_s :to_s
24
+ # Returns RFC2822 compliant string for <tt>Time</tt> object.
25
+ # For example,
26
+ # # Tony Blair's last day in office (hopefully)
27
+ # best_day_ever = Time.local(2007, 6, 27)
28
+ # best_day_ever.to_s # => "Wed, 27 Jun 2007 00:00:00 +0100"
29
+ def to_s
30
+ self.rfc2822
31
+ end
32
+ end
@@ -0,0 +1,2 @@
1
+
2
+ require_local('twitter/ext/stdlib')
@@ -0,0 +1,39 @@
1
+ # extra.rb contains features that are not considered part of the core library.
2
+ # This file is not imported by doing <tt>require('twitter')</tt>, so you will
3
+ # need to import this file separately like:
4
+ # require('twitter')
5
+ # require('twitter/extras')
6
+
7
+ require('twitter')
8
+
9
+ class Twitter::Client
10
+ @@FEATURED_URIS = {
11
+ :users => 'http://twitter.com/statuses/featured.json'
12
+ }
13
+
14
+ # Provides access to the Featured Twitter API.
15
+ #
16
+ # Currently the only value for <tt>type</tt> accepted is <tt>:users</tt>,
17
+ # which will return an Array of blessed Twitter::User objects that
18
+ # represent Twitter's featured users.
19
+ def featured(type)
20
+ uri = @@FEATURED_URIS[type]
21
+ response = http_connect {|conn| create_http_get_request(uri) }
22
+ bless_models(Twitter::User.unmarshal(response.body))
23
+ end
24
+ end
25
+
26
+ class Twitter::User
27
+ class << self
28
+ # Provides access to the Featured Twitter API via the Twitter4R Model
29
+ # interface.
30
+ #
31
+ # The following lines of code are equivalent to each other:
32
+ # users1 = Twitter::User.features(client)
33
+ # users2 = client.featured(:users)
34
+ # where <tt>users1</tt> and <tt>users2</tt> would be logically equivalent.
35
+ def featured(client)
36
+ client.featured(:users)
37
+ end
38
+ end
39
+ end
data/lib/twitter/meta.rb CHANGED
@@ -1,3 +1,6 @@
1
+ # meta.rb contains <tt>Twitter::Meta</tt> and related classes that
2
+ # help define the metadata of the <tt>Twitter4R</tt> project.
3
+
1
4
  require('rubygems')
2
5
  require('erb')
3
6
 
@@ -5,10 +8,12 @@ class Twitter::Meta #:nodoc:
5
8
  attr_accessor :root_dir
6
9
  attr_reader :gem_spec, :project_files, :spec_files
7
10
 
11
+ # Initializer for Twitter::Meta class. Takes <tt>root_dir</tt> as parameter.
8
12
  def initialize(root_dir)
9
13
  @root_dir = root_dir
10
14
  end
11
15
 
16
+ # Returns package information defined in <tt>root_dir</tt>/pkg-info.yml
12
17
  def pkg_info
13
18
  yaml_file = File.join(@root_dir, 'pkg-info.yml')
14
19
  ryaml = ERB.new(File.read(yaml_file), 0)
@@ -16,24 +21,34 @@ class Twitter::Meta #:nodoc:
16
21
  YAML.load(s)
17
22
  end
18
23
 
24
+ # Returns RubyGems spec information
19
25
  def spec_info
20
26
  self.pkg_info['spec'] if self.pkg_info
21
27
  end
22
28
 
29
+ # Returns list of project files
23
30
  def project_files
24
31
  @project_files ||= Dir.glob(File.join(@root_dir, 'lib/**/*.rb'))
25
32
  @project_files
26
33
  end
27
34
 
35
+ # Returns list of specification files
28
36
  def spec_files
29
37
  @spec_files ||= Dir.glob(File.join(@root_dir, 'spec/**/*.rb'))
30
38
  @spec_files
31
39
  end
32
40
 
41
+ # Returns RubyGem specification for Twitter4R project
33
42
  def gem_spec
34
43
  @gem_spec ||= Gem::Specification.new do |spec|
35
44
  self.spec_info.each do |key, val|
36
- spec.send("#{key}=", val)
45
+ if val.is_a?(Hash)
46
+ val.each do |k, v|
47
+ spec.send(key, k, v)
48
+ end
49
+ else
50
+ spec.send("#{key}=", val)
51
+ end
37
52
  end
38
53
  end
39
54
  @gem_spec