twitter4r 0.1.1 → 0.2.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/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