dschn-twitter 0.3.7.2 → 0.4.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,3 +1,19 @@
1
+ 0.4.1 - January 1, 2009
2
+ * 4 minor enhancements and 2 bug fixes:
3
+ * Added better exception handling (Billy Gray)
4
+ * Added page to search (Michael Ivey)
5
+ * Adding an option to display tweets on CLI in reverse order (oldest first). (Cameron Booth)
6
+ * Added in_reply_to_status_id option for replying to statuses (anthonycrumley)
7
+ * Fixed a bug where the @config was improperly set (K. Adam Christensen)
8
+ * Fix verify_credentials to include a format (breaks in laconica). (dustin)
9
+
10
+ 0.4.0 - December 23, 2008
11
+ * 3 major changes
12
+ * Removed active support as dependency
13
+ * Removed CLI dependencies from install dependency list
14
+ (they are now only installed by you manually)
15
+ * Switched to echoe for gem managment
16
+
1
17
  0.3.7 - August 26, 2008
2
18
  * Fixed source param not getting through
3
19
 
File without changes
@@ -1,11 +1,4 @@
1
- History.txt
2
- License.txt
3
- Manifest.txt
4
- README.txt
5
- Rakefile
6
1
  bin/twitter
7
- config/hoe.rb
8
- config/requirements.rb
9
2
  examples/blocks.rb
10
3
  examples/direct_messages.rb
11
4
  examples/favorites.rb
@@ -20,9 +13,8 @@ examples/sent_messages.rb
20
13
  examples/timeline.rb
21
14
  examples/twitter.rb
22
15
  examples/verify_credentials.rb
23
- lib/twitter.rb
16
+ History
24
17
  lib/twitter/base.rb
25
- lib/twitter/cli.rb
26
18
  lib/twitter/cli/config.rb
27
19
  lib/twitter/cli/helpers.rb
28
20
  lib/twitter/cli/migrations/20080722194500_create_accounts.rb
@@ -32,17 +24,21 @@ lib/twitter/cli/migrations/20080722214606_create_configurations.rb
32
24
  lib/twitter/cli/models/account.rb
33
25
  lib/twitter/cli/models/configuration.rb
34
26
  lib/twitter/cli/models/tweet.rb
27
+ lib/twitter/cli.rb
35
28
  lib/twitter/direct_message.rb
36
29
  lib/twitter/easy_class_maker.rb
37
30
  lib/twitter/rate_limit_status.rb
38
31
  lib/twitter/search.rb
32
+ lib/twitter/search_result.rb
33
+ lib/twitter/search_result_info.rb
39
34
  lib/twitter/status.rb
40
35
  lib/twitter/user.rb
41
36
  lib/twitter/version.rb
42
- script/destroy
43
- script/generate
44
- script/txt2html
45
- setup.rb
37
+ lib/twitter.rb
38
+ License
39
+ Manifest
40
+ Rakefile
41
+ README
46
42
  spec/base_spec.rb
47
43
  spec/cli/helper_spec.rb
48
44
  spec/direct_message_spec.rb
@@ -51,8 +47,11 @@ spec/fixtures/friends.xml
51
47
  spec/fixtures/friends_for.xml
52
48
  spec/fixtures/friends_lite.xml
53
49
  spec/fixtures/friends_timeline.xml
50
+ spec/fixtures/friendship_already_exists.xml
51
+ spec/fixtures/friendship_created.xml
54
52
  spec/fixtures/public_timeline.xml
55
53
  spec/fixtures/rate_limit_status.xml
54
+ spec/fixtures/search_result_info.yml
56
55
  spec/fixtures/search_results.json
57
56
  spec/fixtures/status.xml
58
57
  spec/fixtures/user.xml
@@ -62,9 +61,6 @@ spec/spec.opts
62
61
  spec/spec_helper.rb
63
62
  spec/status_spec.rb
64
63
  spec/user_spec.rb
65
- tasks/deployment.rake
66
- tasks/environment.rake
67
- tasks/website.rake
68
64
  twitter.gemspec
69
65
  website/css/common.css
70
66
  website/images/terminal_output.png
File without changes
data/Rakefile CHANGED
@@ -1,4 +1,42 @@
1
- require 'config/requirements'
2
- require 'config/hoe' # setup Hoe + all gem configuration
1
+ ProjectName = 'twitter'
2
+ WebsitePath = "jnunemaker@rubyforge.org:/var/www/gforge-projects/#{ProjectName}"
3
3
 
4
- Dir['tasks/**/*.rake'].each { |rake| load rake }
4
+ require 'rubygems'
5
+ require 'rake'
6
+ require 'echoe'
7
+ require 'spec/rake/spectask'
8
+ require "lib/#{ProjectName}/version"
9
+
10
+ Echoe.new(ProjectName, Twitter::Version) do |p|
11
+ p.description = "a command line interface for twitter, also a library which wraps the twitter api"
12
+ p.url = "http://#{ProjectName}.rubyforge.org"
13
+ p.author = "John Nunemaker"
14
+ p.email = "nunemaker@gmail.com"
15
+ p.extra_deps = [['hpricot', '>= 0.6'], ['activesupport', '>= 2.1'], ['httparty', '>= 0.2.4']]
16
+ p.need_tar_gz = false
17
+ p.docs_host = WebsitePath
18
+ end
19
+
20
+ desc 'Upload website files to rubyforge'
21
+ task :website do
22
+ sh %{rsync -av website/ #{WebsitePath}}
23
+ Rake::Task['website_docs'].invoke
24
+ end
25
+
26
+ task :website_docs do
27
+ Rake::Task['redocs'].invoke
28
+ sh %{rsync -av doc/ #{WebsitePath}/docs}
29
+ end
30
+
31
+ desc 'Preps the gem for a new release'
32
+ task :prepare do
33
+ %w[manifest build_gemspec].each do |task|
34
+ Rake::Task[task].invoke
35
+ end
36
+ end
37
+
38
+ Rake::Task[:default].prerequisites.clear
39
+ task :default => :spec
40
+ Spec::Rake::SpecTask.new do |t|
41
+ t.spec_files = FileList["spec/**/*_spec.rb"]
42
+ end
data/bin/twitter CHANGED
@@ -10,6 +10,5 @@ if ARGV[0] && ARGV[0] == 'd' && !STDIN.tty?
10
10
  ARGV[2] = "#{STDIN.read}#{ARGV[2]}"
11
11
  end
12
12
 
13
- $:.unshift File.join(File.dirname(__FILE__), '..', 'lib')
14
- require 'twitter'
13
+ require File.expand_path(File.join(File.dirname(__FILE__), '..', 'lib', 'twitter'))
15
14
  require 'twitter/cli'
@@ -1,4 +1,5 @@
1
1
  require 'rubygems'
2
+ require 'activesupport'
2
3
  require File.join(File.dirname(__FILE__), '..', 'lib', 'twitter')
3
4
  config = YAML::load(open(ENV['HOME'] + '/.twitter'))
4
5
 
@@ -4,13 +4,13 @@ config = YAML::load(open(ENV['HOME'] + '/.twitter'))
4
4
 
5
5
  twitter = Twitter::Base.new(config['email'], config['password'])
6
6
 
7
- puts 'FAVORITES'
8
- twitter.favorites.each { |f| puts f.text }
7
+ puts 'CREATE'
8
+ puts twitter.create_favorite(865416114).text
9
9
  puts
10
10
  puts
11
11
 
12
- puts 'CREATE'
13
- puts twitter.create_favorite(865416114).text
12
+ puts 'FAVORITES'
13
+ twitter.favorites.each { |f| puts f.text }
14
14
  puts
15
15
  puts
16
16
 
data/examples/replies.rb CHANGED
@@ -1,4 +1,5 @@
1
1
  require 'rubygems'
2
+ require 'activesupport'
2
3
  require File.join(File.dirname(__FILE__), '..', 'lib', 'twitter')
3
4
  config = YAML::load(open(ENV['HOME'] + '/.twitter'))
4
5
 
data/examples/search.rb CHANGED
@@ -2,6 +2,7 @@ require 'rubygems'
2
2
  require File.join(File.dirname(__FILE__), '..', 'lib', 'twitter')
3
3
 
4
4
  Twitter::Search.new('httparty').each { |r| puts r.inspect,'' }
5
+ Twitter::Search.new('httparty').page(2).each { |r| puts r.inspect, '' }
5
6
 
6
7
  # search = Twitter::Search.new
7
8
  # search.from('jnunemaker').to('oaknd1').each { |r| puts r.inspect, '' }
@@ -1,4 +1,5 @@
1
1
  require 'rubygems'
2
+ require 'activesupport'
2
3
  require File.join(File.dirname(__FILE__), '..', 'lib', 'twitter')
3
4
  config = YAML::load(open(ENV['HOME'] + '/.twitter'))
4
5
 
data/examples/timeline.rb CHANGED
@@ -1,4 +1,5 @@
1
1
  require 'rubygems'
2
+ require 'activesupport'
2
3
  require File.join(File.dirname(__FILE__), '..', 'lib', 'twitter')
3
4
  config = YAML::load(open(ENV['HOME'] + '/.twitter'))
4
5
 
data/lib/twitter/base.rb CHANGED
@@ -12,16 +12,10 @@ module Twitter
12
12
  # Identi.ca example:
13
13
  # Twitter.new('email/username', 'password', :api_host => 'identi.ca/api')
14
14
  def initialize(email, password, options={})
15
- @config, @config[:email], @config[:password] = {}, email, password
16
15
  @api_host = options.delete(:api_host) || 'twitter.com'
17
- end
18
-
19
- def timeout=(time)
20
- @config[:timeout] = time
21
- end
22
-
23
- def timeout
24
- @config[:timeout]
16
+ @config, @config[:email], @config[:password] = options, email, password
17
+ @proxy_host = options[:proxy_host]
18
+ @proxy_port = options[:proxy_port]
25
19
  end
26
20
 
27
21
  # Returns an array of statuses for a timeline; Defaults to your friends timeline.
@@ -167,6 +161,7 @@ module Twitter
167
161
  def post(status, options={})
168
162
  form_data = {'status' => status}
169
163
  form_data.merge!({'source' => options[:source]}) if options[:source]
164
+ form_data.merge!({'in_reply_to_status_id' => options[:in_reply_to_status_id]}) if options[:in_reply_to_status_id]
170
165
  Status.new_from_xml(request('statuses/update.xml', :auth => true, :method => :post, :form_data => form_data))
171
166
  end
172
167
  alias :update :post
@@ -174,7 +169,7 @@ module Twitter
174
169
  # Verifies the credentials for the auth user.
175
170
  # raises Twitter::CantConnect on failure.
176
171
  def verify_credentials
177
- request('account/verify_credentials', :auth => true)
172
+ request('account/verify_credentials.xml', :auth => true)
178
173
  end
179
174
 
180
175
  private
@@ -192,41 +187,47 @@ module Twitter
192
187
  #
193
188
  # ie: call(:public_timeline, :auth => false)
194
189
  def call(method, options={})
195
- options.reverse_merge!({ :auth => true, :args => {} })
190
+ options = { :auth => true, :args => {} }.merge(options)
196
191
  # Following line needed as lite=false doesn't work in the API: http://tinyurl.com/yo3h5d
197
192
  options[:args].delete(:lite) unless options[:args][:lite]
198
193
  args = options.delete(:args)
199
194
  request(build_path("statuses/#{method.to_s}.xml", args), options)
200
195
  end
201
196
 
202
- # Makes a request to twitter.
203
- def request(path, options={})
204
- options.reverse_merge!({
205
- :headers => { "User-Agent" => @config[:email] },
206
- :method => :get
207
- })
208
- unless options[:since].blank?
209
- since = options[:since].kind_of?(Date) ? options[:since].strftime('%a, %d-%b-%y %T GMT') : options[:since].to_s
210
- options[:headers]["If-Modified-Since"] = since
211
- end
212
-
197
+ def response(path, options={})
213
198
  uri = URI.parse("http://#{@api_host}")
214
199
 
215
200
  begin
216
- response = Net::HTTP.start(uri.host, 80) do |http|
201
+ response = Net::HTTP::Proxy(@proxy_host, @proxy_port).start(uri.host, uri.port) do |http|
217
202
  klass = Net::HTTP.const_get options[:method].to_s.downcase.capitalize
218
203
  req = klass.new("#{uri.path}/#{path}", options[:headers])
219
204
  req.basic_auth(@config[:email], @config[:password]) if options[:auth]
220
205
  if options[:method].to_s == 'post' && options[:form_data]
221
206
  req.set_form_data(options[:form_data])
222
207
  end
223
- http.read_timeout = @config[:timeout] if @config[:timeout]
224
208
  http.request(req)
225
209
  end
226
210
  rescue => error
227
211
  raise CantConnect, error.message
228
212
  end
213
+ end
214
+
215
+ # Makes a request to twitter.
216
+ def request(path, options={})
217
+ options = {
218
+ :headers => { "User-Agent" => @config[:email] },
219
+ :method => :get,
220
+ }.merge(options)
221
+
222
+ unless options[:since].nil?
223
+ since = options[:since].kind_of?(Date) ? options[:since].strftime('%a, %d-%b-%y %T GMT') : options[:since].to_s
224
+ options[:headers]["If-Modified-Since"] = since
225
+ end
229
226
 
227
+ handle_response!(response(path, options))
228
+ end
229
+
230
+ def handle_response!(response)
230
231
  if %w[200 304].include?(response.code)
231
232
  response = parse(response.body)
232
233
  raise RateExceeded if (response/:hash/:error).text =~ /Rate limit exceeded/
@@ -237,14 +238,23 @@ module Twitter
237
238
  raise Unavailable, response.message
238
239
  elsif response.code == '401'
239
240
  raise CantConnect, 'Authentication failed. Check your username and password'
241
+ elsif response.code == '403'
242
+ error_message = (parse(response.body)/:hash/:error).text
243
+ raise CantFindUsers, error_message if error_message =~ /Could not find both specified users/
244
+ raise AlreadyFollowing, error_message if error_message =~ /already on your list/
245
+ raise CantFollowUser, "Response code #{response.code}: #{response.message} #{error_message}"
240
246
  else
241
247
  raise CantConnect, "Twitter is returning a #{response.code}: #{response.message}"
242
248
  end
243
- end
249
+ end
244
250
 
245
251
  # Given a path and a hash, build a full path with the hash turned into a query string
246
252
  def build_path(path, options)
247
- path += "?#{options.to_query}" unless options.blank?
253
+ unless options.nil?
254
+ query = options.inject('') { |str, h| str += "#{CGI.escape(h[0].to_s)}=#{CGI.escape(h[1].to_s)}&"; str }
255
+ path += "?#{query}"
256
+ end
257
+
248
258
  path
249
259
  end
250
260
 
@@ -259,4 +269,4 @@ module Twitter
259
269
  Hpricot.XML(response || '')
260
270
  end
261
271
  end
262
- end
272
+ end
@@ -5,25 +5,37 @@ module Twitter
5
5
  class NoAccounts < StandardError; end
6
6
 
7
7
  def output_tweets(collection, options={})
8
- options.reverse_merge!({
8
+ options = {
9
9
  :cache => false,
10
10
  :since_prefix => '',
11
- :empty_msg => 'Nothing new since your last check.'
12
- })
11
+ :empty_msg => 'Nothing new since your last check.',
12
+ :reverse => false
13
+ }.merge(options)
14
+
13
15
  if collection.size > 0
14
16
  justify = collection.collect { |s| s.user.screen_name }.max { |a,b| a.length <=> b.length }.length rescue 0
15
17
  indention = ' ' * (justify + 3)
16
18
  say("\n#{indention}#{collection.size} new tweet(s) found.\n\n")
19
+ collection.reverse! if options[:reverse]
17
20
  collection.each do |s|
18
21
  Tweet.create_from_tweet(current_account, s) if options[:cache]
22
+
19
23
  occurred_at = Time.parse(s.created_at).strftime('On %b %d at %l:%M%P')
20
24
  formatted_time = '-' * occurred_at.length + "\n#{indention}#{occurred_at}"
21
25
  formatted_name = s.user.screen_name.rjust(justify + 1)
22
26
  formatted_msg = ''
23
- s.text.split(' ').in_groups_of(6, false) { |row| formatted_msg += row.join(' ') + "\n#{indention}" }
24
- say "#{CGI::unescapeHTML(formatted_name)}: #{CGI::unescapeHTML(formatted_msg)}#{formatted_time}\n\n"
27
+
28
+ s.text.split(' ').each_with_index do |word, idx|
29
+ formatted_msg += "#{word} "
30
+
31
+ sixth_word = idx != 0 && idx % 6 == 0
32
+ formatted_msg += "\n#{indention}" if sixth_word
33
+ end
34
+
35
+ say "#{CGI::unescapeHTML(formatted_name)}: #{CGI::unescapeHTML(formatted_msg)}\n#{indention}#{formatted_time}\n\n"
25
36
  end
26
- Configuration["#{options[:since_prefix]}_since_id"] = collection.first.id
37
+
38
+ Configuration["#{options[:since_prefix]}_since_id"] = options[:reverse] ? collection.last.id : collection.first.id
27
39
  else
28
40
  say(options[:empty_msg])
29
41
  end
@@ -35,7 +47,7 @@ module Twitter
35
47
 
36
48
  def current_account
37
49
  @current_account ||= Account.active
38
- raise Account.count == 0 ? NoAccounts : NoActiveAccount if @current_account.blank?
50
+ raise Account.count == 0 ? NoAccounts : NoActiveAccount if @current_account.nil?
39
51
  @current_account
40
52
  end
41
53
 
@@ -44,7 +56,7 @@ module Twitter
44
56
  if File.exists?(tweet_file)
45
57
  say '.twitter file found, attempting import...'
46
58
  config = YAML::load(File.read(tweet_file))
47
- if !config['email'].blank? && !config['password'].blank?
59
+ if !config['email'].nil? && !config['password'].nil?
48
60
  Account.add(:username => config['email'], :password => config['password'])
49
61
  say 'Account imported'
50
62
  block.call if block_given?
data/lib/twitter/cli.rb CHANGED
@@ -1,8 +1,10 @@
1
1
  require 'rubygems'
2
+
2
3
  gem 'main', '>= 2.8.2'
3
4
  gem 'highline', '>= 1.4.0'
4
- gem 'activerecord', '>= 2.1'
5
+ gem 'activerecord', '= 2.2.2'
5
6
  gem 'sqlite3-ruby', '>= 1.2.1'
7
+
6
8
  require 'main'
7
9
  require 'highline/import'
8
10
  require 'activerecord'
@@ -177,7 +179,7 @@ Main {
177
179
  end
178
180
  post_thread.join
179
181
  progress_thread.join
180
- say "Got it! New twitter created at: #{status.created_at}\n"
182
+ say "Got it! New tweet created at: #{status.created_at}\n"
181
183
  end
182
184
  end
183
185
  end
@@ -215,7 +217,7 @@ Main {
215
217
  end
216
218
 
217
219
  mode 'follow' do
218
- description "Allows you to turn on notifications for a user"
220
+ description "Allows you to add notifications for a user (aka Follow Them)"
219
221
  argument('username') {
220
222
  required
221
223
  description 'username or id of twitterrer to follow'
@@ -275,21 +277,25 @@ Main {
275
277
  option('force', 'f') {
276
278
  description "Ignore since_id and show first page of results even if there aren't new ones"
277
279
  }
280
+ option('reverse', 'r') {
281
+ description 'Reverse the output so the oldest tweets are at the top'
282
+ }
278
283
 
279
284
  def run
280
285
  do_work do
281
286
  timeline = params['timeline'].value == 'me' ? 'user' : params['timeline'].value
282
287
  options, since_id = {}, Configuration["#{timeline}_since_id"]
283
- options[:since_id] = since_id if !since_id.blank? && !params['force'].given?
288
+ options[:since_id] = since_id if !since_id.nil? && !params['force'].given?
289
+ reverse = params['reverse'].given? ? true : false
284
290
  cache = [:friends, :user].include?(timeline)
285
291
  collection = base.timeline(timeline.to_sym, options)
286
- output_tweets(collection, {:cache => cache, :since_prefix => timeline})
292
+ output_tweets(collection, {:cache => cache, :since_prefix => timeline, :reverse => reverse})
287
293
  end
288
294
  end
289
295
  end
290
296
 
291
297
  mode 'replies' do
292
- description 'Allows you to view all @replies at you'
298
+ description 'Allows you to view all @replies sent to you'
293
299
  option('force', 'f') {
294
300
  description "Ignore since_id and show first page of replies even if there aren't new ones"
295
301
  }
@@ -297,7 +303,7 @@ Main {
297
303
  def run
298
304
  do_work do
299
305
  options, since_id = {}, Configuration["replies_since_id"]
300
- options[:since_id] = since_id if !since_id.blank? && !params['force'].given?
306
+ options[:since_id] = since_id if !since_id.nil? && !params['force'].given?
301
307
  collection = base.replies(options)
302
308
  output_tweets(collection, {:since_prefix => 'replies'})
303
309
  end
@@ -11,7 +11,7 @@ module Twitter
11
11
 
12
12
  def initialize(q=nil)
13
13
  clear
14
- containing(q) unless q.blank?
14
+ containing(q) if q && q.strip != ''
15
15
  end
16
16
 
17
17
  def from(user)
@@ -59,6 +59,12 @@ module Twitter
59
59
  self
60
60
  end
61
61
 
62
+ # Which page of results to fetch
63
+ def page(num)
64
+ @query[:page] = num
65
+ self
66
+ end
67
+
62
68
  # Only searches tweets since a given id.
63
69
  # Recommended to use this when possible.
64
70
  def since(since_id)
@@ -83,7 +89,7 @@ module Twitter
83
89
  # If you want to get results do something other than iterate over them.
84
90
  def fetch
85
91
  @query[:q] = @query[:q].join(' ')
86
- self.class.get('/search.json', {:query => @query})
92
+ SearchResultInfo.new_from_hash(self.class.get('/search.json', {:query => @query}))
87
93
  end
88
94
 
89
95
  def each
@@ -91,4 +97,5 @@ module Twitter
91
97
  @result['results'].each { |r| yield r }
92
98
  end
93
99
  end
94
- end
100
+ end
101
+
@@ -1,9 +1,3 @@
1
1
  module Twitter #:nodoc:
2
- module VERSION #:nodoc:
3
- MAJOR = 0
4
- MINOR = 3
5
- TINY = 7
6
-
7
- STRING = [MAJOR, MINOR, TINY].join('.')
8
- end
2
+ Version = '0.4.1'
9
3
  end
data/lib/twitter.rb CHANGED
@@ -1,6 +1,12 @@
1
- %w(uri cgi net/http yaml rubygems hpricot active_support).each { |f| require f }
1
+ require 'uri'
2
+ require 'cgi'
3
+ require 'net/http'
4
+ require 'yaml'
5
+ require 'time'
6
+ require 'rubygems'
7
+ require 'hpricot'
2
8
 
3
- $:.unshift(File.join(File.dirname(__FILE__)))
9
+ $:.unshift(File.dirname(__FILE__))
4
10
  require 'twitter/version'
5
11
  require 'twitter/easy_class_maker'
6
12
  require 'twitter/base'
@@ -9,6 +15,8 @@ require 'twitter/search'
9
15
  require 'twitter/status'
10
16
  require 'twitter/direct_message'
11
17
  require 'twitter/rate_limit_status'
18
+ require 'twitter/search_result_info'
19
+ require 'twitter/search_result'
12
20
 
13
21
  module Twitter
14
22
  class Unavailable < StandardError; end
@@ -16,6 +24,9 @@ module Twitter
16
24
  class BadResponse < StandardError; end
17
25
  class UnknownTimeline < ArgumentError; end
18
26
  class RateExceeded < StandardError; end
27
+ class CantFindUsers < ArgumentError; end
28
+ class AlreadyFollowing < StandardError; end
29
+ class CantFollowUser < StandardError; end
19
30
 
20
31
  SourceName = 'twittergem'
21
32
  end
data/spec/base_spec.rb CHANGED
@@ -16,7 +16,11 @@ describe "Twitter::Base" do
16
16
  lambda { @base.timeline(:fakeyoutey) }.should raise_error(Twitter::UnknownTimeline)
17
17
  end
18
18
 
19
- it "should default to friends timeline"
19
+ it "should default to friends timeline" do
20
+ @base.should_receive(:call).with("friends_timeline", {:auth=>true, :args=>{}, :since=>nil})
21
+ @base.should_receive(:statuses)
22
+ @base.timeline
23
+ end
20
24
 
21
25
  it "should be able to retrieve friends timeline" do
22
26
  data = open(File.dirname(__FILE__) + '/fixtures/friends_timeline.xml').read
@@ -65,6 +69,20 @@ describe "Twitter::Base" do
65
69
  timeline.size.should == 29
66
70
  timeline.first.name.should == 'Blaine Cook'
67
71
  end
72
+
73
+ it "should be able to create a friendship" do
74
+ data = open(File.dirname(__FILE__) + '/fixtures/friendship_created.xml').read
75
+ @base.should_receive(:request).and_return(Hpricot::XML(data))
76
+ user = @base.create_friendship('jnunemaker')
77
+ end
78
+
79
+ it "should bomb if friendship already exists" do
80
+ data = open(File.dirname(__FILE__) + '/fixtures/friendship_already_exists.xml').read
81
+ response = Net::HTTPForbidden.new("1.1", '403', '')
82
+ response.stub!(:body).and_return(data)
83
+ @base.should_receive(:response).and_return(response)
84
+ lambda { @base.create_friendship('billymeltdown') }.should raise_error(Twitter::AlreadyFollowing)
85
+ end
68
86
  end
69
87
 
70
88
  it "should be able to get single status" do
@@ -106,4 +124,4 @@ describe "Twitter::Base" do
106
124
  @base.rate_limit_status.remaining_hits.should == 5
107
125
  end
108
126
  end
109
- end
127
+ end
@@ -8,6 +8,10 @@ def say(str)
8
8
  puts str
9
9
  end
10
10
 
11
+ class Tweet < OpenStruct
12
+ attr_accessor :id
13
+ end
14
+
11
15
  describe Twitter::CLI::Helpers do
12
16
  include Twitter::CLI::Helpers
13
17
 
@@ -15,21 +19,31 @@ describe Twitter::CLI::Helpers do
15
19
  before do
16
20
  Configuration.stub!(:[]=).and_return(true)
17
21
  @collection = [
18
- OpenStruct.new(
19
- :text => 'This is my long message that I want to see formatted ooooh so pretty with a few words on each line so it is easy to scan.',
22
+ Tweet.new(
23
+ :id => 1,
24
+ :text => 'This is my long message that I want to see formatted ooooh so pretty with a few words on each line so it is easy to scan.',
20
25
  :created_at => Time.mktime(2008, 5, 1, 10, 15, 00).strftime('%Y-%m-%d %H:%M:%S'),
21
- :user => OpenStruct.new(:screen_name => 'jnunemaker')
26
+ :user => OpenStruct.new(:screen_name => 'jnunemaker')
22
27
  ),
23
- OpenStruct.new(
24
- :text => 'This is my long message that I want to see formatted ooooh so pretty with a.',
28
+ Tweet.new(
29
+ :id => 2,
30
+ :text => 'This is my long message that I want to see formatted ooooh so pretty with a.',
25
31
  :created_at => Time.mktime(2008, 4, 1, 10, 15, 00).strftime('%Y-%m-%d %H:%M:%S'),
26
- :user => OpenStruct.new(:screen_name => 'danielmorrison')
32
+ :user => OpenStruct.new(:screen_name => 'danielmorrison')
27
33
  )
28
34
  ]
29
35
  end
30
36
 
31
37
  specify "should properly format" do
32
- output_tweets(@collection)
38
+ stdout_for {
39
+ output_tweets(@collection)
40
+ }.should match(/with a few words[\w\W]*with a\./)
41
+ end
42
+
43
+ specify 'should format in reverse' do
44
+ stdout_for {
45
+ output_tweets(@collection, :reverse => true)
46
+ }.should match(/with a\.[\w\W]*with a few words/)
33
47
  end
34
48
  end
35
49
  end