t 1.6.0 → 1.7.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,48 @@
1
+ ## Contributing
2
+ In the spirit of [free software][free-sw], **everyone** is encouraged to help
3
+ improve this project.
4
+
5
+ [free-sw]: http://www.fsf.org/licensing/essays/free-sw.html
6
+
7
+ Here are some ways *you* can contribute:
8
+
9
+ * by using alpha, beta, and prerelease versions
10
+ * by reporting bugs
11
+ * by suggesting new features
12
+ * by writing or editing documentation
13
+ * by writing specifications
14
+ * by writing code (**no patch is too small**: fix typos, add comments, clean up
15
+ inconsistent whitespace)
16
+ * by refactoring code
17
+ * by fixing [issues][]
18
+ * by reviewing patches
19
+ * [financially][pledgie]
20
+
21
+ [issues]: https://github.com/sferik/t/issues
22
+ [pledgie]: http://www.pledgie.com/campaigns/17330
23
+
24
+ ## Submitting an Issue
25
+ We use the [GitHub issue tracker][issues] to track bugs and features. Before
26
+ submitting a bug report or feature request, check to make sure it hasn't
27
+ already been submitted. When submitting a bug report, please include a [Gist][]
28
+ that includes a stack trace and any details that may be necessary to reproduce
29
+ the bug, including your gem version, Ruby version, and operating system.
30
+ Ideally, a bug report should include a pull request with failing specs.
31
+
32
+ [gist]: https://gist.github.com/
33
+
34
+ ## Submitting a Pull Request
35
+ 1. [Fork the repository.][fork]
36
+ 2. [Create a topic branch.][branch]
37
+ 3. Add specs for your unimplemented feature or bug fix.
38
+ 4. Run `bundle exec rake spec`. If your specs pass, return to step 3.
39
+ 5. Implement your feature or bug fix.
40
+ 6. Run `bundle exec rake spec`. If your specs fail, return to step 5.
41
+ 7. Run `open coverage/index.html`. If your changes are not completely covered
42
+ by your tests, return to step 3.
43
+ 8. Add, commit, and push your changes.
44
+ 9. [Submit a pull request.][pr]
45
+
46
+ [fork]: http://help.github.com/fork-a-repo/
47
+ [branch]: http://learn.github.com/p/branching.html
48
+ [pr]: http://help.github.com/send-pull-requests/
data/README.md CHANGED
@@ -3,6 +3,7 @@
3
3
  [icon]: https://github.com/sferik/t/raw/master/icon/t.png
4
4
 
5
5
  # Twitter CLI
6
+ [![Gem Version](https://badge.fury.io/rb/t.png)][gem]
6
7
  [![Build Status](https://secure.travis-ci.org/sferik/t.png?branch=master)][travis]
7
8
  [![Dependency Status](https://gemnasium.com/sferik/t.png?travis)][gemnasium]
8
9
  [![Pledgie](http://www.pledgie.com/campaigns/17330.png)][pledgie]
@@ -13,6 +14,7 @@
13
14
  The CLI takes syntactic cues from the [Twitter SMS commands][sms], however it
14
15
  offers vastly more commands and capabilities than are available via SMS.
15
16
 
17
+ [gem]: https://rubygems.org/gems/t
16
18
  [travis]: http://travis-ci.org/sferik/t
17
19
  [gemnasium]: https://gemnasium.com/sferik/t
18
20
  [pledgie]: http://www.pledgie.com/campaigns/17330
@@ -242,6 +244,13 @@ used by `t`:
242
244
  ![Timeline](https://github.com/sferik/t/raw/master/screenshots/timeline.png)
243
245
  ![List](https://github.com/sferik/t/raw/master/screenshots/list.png)
244
246
 
247
+ ## Shell completion
248
+ If you're running [Zsh][zsh], you can source the [bundled completion file][completion]
249
+ to get shell completion for `t` commands, subcommands, and flags.
250
+
251
+ Don't run Zsh? Why not [contribute][] completion support for your favorite
252
+ shell?
253
+
245
254
  ## History
246
255
  The [twitter gem][gem] previously contained a command-line interface, up until
247
256
  version 0.5.0, when it was [removed][]. This project is offered as a sucessor
@@ -273,8 +282,16 @@ implementation, you will be responsible for providing patches in a timely
273
282
  fashion. If critical issues for a particular implementation exist at the time
274
283
  of a major release, support for that Ruby version may be dropped.
275
284
 
285
+ ## Troubleshooting
286
+ If you are running t on a remote computer you can use the flag --display-url during authorize process to display the url instead of opening the web browser.
287
+
288
+ t authorize --display-url
289
+
276
290
  ## Copyright
277
291
  Copyright (c) 2011 Erik Michaels-Ober. See [LICENSE][] for details.
278
292
  Application icon by [@nvk][nvk].
279
293
  [license]: https://github.com/sferik/t/blob/master/LICENSE.md
280
294
  [nvk]: http://rodolfonovak.com
295
+ [zsh]: http://zsh.org
296
+ [completion]: https://github.com/sferik/t/tree/etc/t-completion.zsh
297
+ [contribute]: https://github.com/sferik/t/blob/master/CONTRIBUTING.md
data/Rakefile CHANGED
@@ -4,5 +4,8 @@ Bundler::GemHelper.install_tasks
4
4
  require 'rspec/core/rake_task'
5
5
  RSpec::Core::RakeTask.new(:spec)
6
6
 
7
+ import 'tasks/zsh.rake'
8
+
9
+ task :release => 'completion:zsh'
7
10
  task :test => :spec
8
11
  task :default => :spec
data/bin/t CHANGED
@@ -1,12 +1,17 @@
1
1
  #!/usr/bin/env ruby
2
2
 
3
+ # Trap interrupts to quit cleanly. See
4
+ # https://twitter.com/mitchellh/status/283014103189053442
5
+ Signal.trap("INT") { exit 1 }
6
+
3
7
  require 'oauth'
4
8
  require 't'
5
9
  require 'twitter'
6
10
 
7
11
  # Output message to $stderr, prefixed with the program name
8
- def pute(message="")
9
- $stderr.puts "#{$0}: #{message}"
12
+ def pute(*args)
13
+ args.first.insert(0, "#{$0}: ")
14
+ $stderr.puts(*args)
10
15
  end
11
16
 
12
17
  begin
@@ -17,9 +22,13 @@ rescue Interrupt
17
22
  rescue OAuth::Unauthorized
18
23
  pute "Authorization failed"
19
24
  exit 1
25
+ rescue Twitter::Error::TooManyRequests => error
26
+ pute error.message,
27
+ "The rate limit for this request will reset in #{error.rate_limit.reset_in} seconds.",
28
+ "While you wait, consider making a polite request for Twitter to increase the API rate limit at https://dev.twitter.com/discussions/10644"
29
+ exit 1
20
30
  rescue Twitter::Error::Unauthorized => error
21
- pute error.message
22
- pute "Run `#{$0} authorize` to authorize."
31
+ pute error.message, "Run `t authorize` to authorize."
23
32
  exit 1
24
33
  rescue Twitter::Error => error
25
34
  pute error.message
@@ -29,7 +29,7 @@ module T
29
29
  check_unknown_options!
30
30
 
31
31
  class_option "host", :aliases => "-H", :type => :string, :default => T::Requestable::DEFAULT_HOST, :desc => "Twitter API server"
32
- class_option "no-color", :aliases => "-N", :type => :boolean, :desc => "Disable colorization in output"
32
+ class_option "color", :type => :string, :default => "auto", :desc => "Control how color is used in output."
33
33
  class_option "no-ssl", :aliases => "-U", :type => :boolean, :default => false, :desc => "Disable SSL"
34
34
  class_option "profile", :aliases => "-P", :type => :string, :default => File.join(File.expand_path("~"), T::RCFile::FILE_NAME), :desc => "Path to RC file", :banner => "FILE"
35
35
 
@@ -130,8 +130,8 @@ module T
130
130
  method_option "reverse", :aliases => "-r", :type => :boolean, :default => false, :desc => "Reverse the order of the sort."
131
131
  def direct_messages
132
132
  count = options['number'] || DEFAULT_NUM_RESULTS
133
- direct_messages = collect_with_count(count) do |opts|
134
- client.direct_messages(opts)
133
+ direct_messages = collect_with_count(count) do |count_opts|
134
+ client.direct_messages(count_opts)
135
135
  end
136
136
  direct_messages.reverse! if options['reverse']
137
137
  require 'htmlentities'
@@ -163,8 +163,8 @@ module T
163
163
  method_option "reverse", :aliases => "-r", :type => :boolean, :default => false, :desc => "Reverse the order of the sort."
164
164
  def direct_messages_sent
165
165
  count = options['number'] || DEFAULT_NUM_RESULTS
166
- direct_messages = collect_with_count(count) do |opts|
167
- client.direct_messages_sent(opts)
166
+ direct_messages = collect_with_count(count) do |count_opts|
167
+ client.direct_messages_sent(count_opts)
168
168
  end
169
169
  direct_messages.reverse! if options['reverse']
170
170
  require 'htmlentities'
@@ -197,13 +197,15 @@ module T
197
197
  method_option "sort", :aliases => "-s", :type => :string, :enum => %w(favorites followers friends listed screen_name since tweets tweeted), :default => "screen_name", :desc => "Specify the order of the results.", :banner => "ORDER"
198
198
  method_option "unsorted", :aliases => "-u", :type => :boolean, :default => false, :desc => "Output is not sorted."
199
199
  def groupies(user=nil)
200
- if user
200
+ user = if user
201
201
  require 't/core_ext/string'
202
- user = if options['id']
202
+ if options['id']
203
203
  user.to_i
204
204
  else
205
205
  user.strip_ats
206
206
  end
207
+ else
208
+ client.verify_credentials.screen_name
207
209
  end
208
210
  follower_ids = Thread.new do
209
211
  collect_with_cursor do |cursor|
@@ -319,8 +321,8 @@ module T
319
321
  end
320
322
  end
321
323
  count = options['number'] || DEFAULT_NUM_RESULTS
322
- tweets = collect_with_count(count) do |opts|
323
- client.favorites(user, opts)
324
+ tweets = collect_with_count(count) do |count_opts|
325
+ client.favorites(user, count_opts)
324
326
  end
325
327
  print_tweets(tweets)
326
328
  end
@@ -397,13 +399,15 @@ module T
397
399
  method_option "sort", :aliases => "-s", :type => :string, :enum => %w(favorites followers friends listed screen_name since tweets tweeted), :default => "screen_name", :desc => "Specify the order of the results.", :banner => "ORDER"
398
400
  method_option "unsorted", :aliases => "-u", :type => :boolean, :default => false, :desc => "Output is not sorted."
399
401
  def friends(user=nil)
400
- if user
402
+ user = if user
401
403
  require 't/core_ext/string'
402
- user = if options['id']
404
+ if options['id']
403
405
  user.to_i
404
406
  else
405
407
  user.strip_ats
406
408
  end
409
+ else
410
+ client.verify_credentials.screen_name
407
411
  end
408
412
  following_ids = Thread.new do
409
413
  collect_with_cursor do |cursor|
@@ -431,13 +435,15 @@ module T
431
435
  method_option "sort", :aliases => "-s", :type => :string, :enum => %w(favorites followers friends listed screen_name since tweets tweeted), :default => "screen_name", :desc => "Specify the order of the results.", :banner => "ORDER"
432
436
  method_option "unsorted", :aliases => "-u", :type => :boolean, :default => false, :desc => "Output is not sorted."
433
437
  def leaders(user=nil)
434
- if user
438
+ user = if user
435
439
  require 't/core_ext/string'
436
- user = if options['id']
440
+ if options['id']
437
441
  user.to_i
438
442
  else
439
443
  user.strip_ats
440
444
  end
445
+ else
446
+ client.verify_credentials.screen_name
441
447
  end
442
448
  following_ids = Thread.new do
443
449
  collect_with_cursor do |cursor|
@@ -489,8 +495,8 @@ module T
489
495
  method_option "reverse", :aliases => "-r", :type => :boolean, :default => false, :desc => "Reverse the order of the sort."
490
496
  def mentions
491
497
  count = options['number'] || DEFAULT_NUM_RESULTS
492
- tweets = collect_with_count(count) do |opts|
493
- client.mentions(opts)
498
+ tweets = collect_with_count(count) do |count_opts|
499
+ client.mentions(count_opts)
494
500
  end
495
501
  print_tweets(tweets)
496
502
  end
@@ -574,12 +580,12 @@ module T
574
580
  else
575
581
  user.strip_ats
576
582
  end
577
- collect_with_count(count) do |opts|
578
- client.retweeted_by_user(user, opts)
583
+ collect_with_count(count) do |count_opts|
584
+ client.retweeted_by_user(user, count_opts)
579
585
  end
580
586
  else
581
- collect_with_count(count) do |opts|
582
- client.retweeted_by_me(opts)
587
+ collect_with_count(count) do |count_opts|
588
+ client.retweeted_by_me(count_opts)
583
589
  end
584
590
  end
585
591
  print_tweets(tweets)
@@ -647,16 +653,13 @@ module T
647
653
  method_option "long", :aliases => "-l", :type => :boolean, :default => false, :desc => "Output in long format."
648
654
  method_option "number", :aliases => "-n", :type => :numeric, :default => DEFAULT_NUM_RESULTS, :desc => "Limit the number of results."
649
655
  method_option "reverse", :aliases => "-r", :type => :boolean, :default => false, :desc => "Reverse the order of the sort."
656
+ method_option "since_id", :aliases => "-s", :type => :numeric, :desc => "Returns only the results with an ID greater than the specified ID."
650
657
  def timeline(user=nil)
651
658
  count = options['number'] || DEFAULT_NUM_RESULTS
652
- exclude_opts = case options['exclude']
653
- when 'replies'
654
- {:exclude_replies => true}
655
- when 'retweets'
656
- {:include_rts => false}
657
- else
658
- {}
659
- end
659
+ opts = {}
660
+ opts[:exclude_replies] = true if options['exclude'] == 'replies'
661
+ opts[:include_rts] = false if options['exclude'] == 'retweets'
662
+ opts[:since_id] = options['since_id'] if options['since_id']
660
663
  if user
661
664
  require 't/core_ext/string'
662
665
  user = if options['id']
@@ -664,12 +667,12 @@ module T
664
667
  else
665
668
  user.strip_ats
666
669
  end
667
- tweets = collect_with_count(count) do |opts|
668
- client.user_timeline(user, opts.merge(exclude_opts))
670
+ tweets = collect_with_count(count) do |count_opts|
671
+ client.user_timeline(user, count_opts.merge(opts))
669
672
  end
670
673
  else
671
- tweets = collect_with_count(count) do |opts|
672
- client.home_timeline(opts.merge(exclude_opts))
674
+ tweets = collect_with_count(count) do |count_opts|
675
+ client.home_timeline(count_opts.merge(opts))
673
676
  end
674
677
  end
675
678
  print_tweets(tweets)
@@ -69,7 +69,7 @@ module T
69
69
  end
70
70
  else
71
71
  status_ids.each do |status_id|
72
- status = client.status(status_id, :include_my_retweet => false, :trim_user => true)
72
+ status = client.status(status_id, :include_my_retweet => false)
73
73
  return unless yes? "Are you sure you want to remove @#{status.from_user}'s status: \"#{status.full_text}\" from your favorites? [y/N]"
74
74
  client.unfavorite(status_id)
75
75
  say "@#{@rcfile.active_profile[0]} unfavorited @#{status.from_user}'s status: \"#{status.full_text}\""
@@ -107,7 +107,7 @@ module T
107
107
  end
108
108
  else
109
109
  status_ids.each do |status_id|
110
- status = client.status(status_id, :include_my_retweet => false, :trim_user => true)
110
+ status = client.status(status_id, :include_my_retweet => false)
111
111
  return unless yes? "Are you sure you want to permanently delete @#{status.from_user}'s status: \"#{status.full_text}\"? [y/N]"
112
112
  client.status_destroy(status_id, :trim_user => true)
113
113
  say "@#{@rcfile.active_profile[0]} deleted the Tweet: \"#{status.full_text}\""
@@ -91,7 +91,7 @@ module T
91
91
  def members(list)
92
92
  owner, list = extract_owner(list, options)
93
93
  users = collect_with_cursor do |cursor|
94
- client.list_members(owner, list, :cursor => cursor, :skip_status => true)
94
+ client.list_members(owner, list, :cursor => cursor)
95
95
  end
96
96
  print_users(users)
97
97
  end
@@ -121,8 +121,8 @@ module T
121
121
  def timeline(list)
122
122
  owner, list = extract_owner(list, options)
123
123
  count = options['number'] || DEFAULT_NUM_RESULTS
124
- tweets = collect_with_count(count) do |opts|
125
- client.list_timeline(owner, list, opts)
124
+ tweets = collect_with_count(count) do |count_opts|
125
+ client.list_timeline(owner, list, count_opts)
126
126
  end
127
127
  print_tweets(tweets)
128
128
  end
@@ -16,7 +16,7 @@ module T
16
16
  end
17
17
 
18
18
  def build_long_user(user)
19
- [user.id, ls_formatted_time(user), ls_formatted_time(user.status), user.statuses_count, user.favourites_count, user.listed_count, user.friends_count, user.followers_count, "@#{user.screen_name}", user.name, user.verified? ? "Yes" : "No", decode_full_text(user.status, options['decode_urls']).gsub(/\n+/, ' '), user.location, user.url]
19
+ [user.id, ls_formatted_time(user), ls_formatted_time(user.status), user.statuses_count, user.favourites_count, user.listed_count, user.friends_count, user.followers_count, "@#{user.screen_name}", user.name, user.verified? ? "Yes" : "No", user.status ? decode_full_text(user.status, options['decode_urls']).gsub(/\n+/, ' ') : nil, user.location, user.url]
20
20
  end
21
21
 
22
22
  def csv_formatted_time(object, key=:created_at)
@@ -51,7 +51,7 @@ module T
51
51
  def print_csv_user(user)
52
52
  require 'csv'
53
53
  require 'fastercsv' unless Array.new.respond_to?(:to_csv)
54
- say [user.id, csv_formatted_time(user), csv_formatted_time(user.status), user.statuses_count, user.favourites_count, user.listed_count, user.friends_count, user.followers_count, user.screen_name, user.name, user.verified?, user.description, user.status.text, user.location, user.url].to_csv
54
+ say [user.id, csv_formatted_time(user), csv_formatted_time(user.status), user.statuses_count, user.favourites_count, user.listed_count, user.friends_count, user.followers_count, user.screen_name, user.name, user.verified?, user.description, user.status ? user.status.full_text : nil, user.location, user.url].to_csv
55
55
  end
56
56
 
57
57
  def print_lists(lists)
@@ -113,8 +113,14 @@ module T
113
113
  end
114
114
 
115
115
  def print_message(from_user, message)
116
- if STDOUT.tty? && !options['no-color']
116
+ if options['color'] == 'always'
117
117
  say(" @#{from_user}", [:bold, :yellow])
118
+ elsif options['color'] == 'auto'
119
+ if $stdout.tty?
120
+ say(" @#{from_user}", [:bold, :yellow])
121
+ else
122
+ say(" @#{from_user}")
123
+ end
118
124
  else
119
125
  say(" @#{from_user}")
120
126
  end
@@ -19,7 +19,7 @@ module T
19
19
  :consumer_key => @rcfile.active_consumer_key,
20
20
  :consumer_secret => @rcfile.active_consumer_secret,
21
21
  :oauth_token => @rcfile.active_token,
22
- :oauth_token_secret => @rcfile.active_secret
22
+ :oauth_token_secret => @rcfile.active_secret,
23
23
  )
24
24
  end
25
25
 
@@ -35,9 +35,9 @@ module T
35
35
  method_option "number", :aliases => "-n", :type => :numeric, :default => DEFAULT_NUM_RESULTS
36
36
  def all(query)
37
37
  count = options['number'] || DEFAULT_NUM_RESULTS
38
- tweets = collect_with_count(count) do |opts|
39
- opts[:include_entities] = 1 if options['decode_urls']
40
- client.search(query, opts).results
38
+ tweets = collect_with_count(count) do |count_opts|
39
+ count_opts[:include_entities] = 1 if options['decode_urls']
40
+ client.search(query, count_opts).results
41
41
  end
42
42
  tweets.reverse! if options['reverse']
43
43
  require 'htmlentities'
@@ -169,23 +169,19 @@ module T
169
169
 
170
170
  desc "timeline [USER] QUERY", "Returns Tweets in your timeline that match the specified query."
171
171
  method_option "csv", :aliases => "-c", :type => :boolean, :default => false, :desc => "Output in CSV format."
172
+ method_option "decode_urls", :aliases => "-d", :type => :boolean, :default => false, :desc => "Decodes t.co URLs into their original form."
172
173
  method_option "exclude", :aliases => "-e", :type => :string, :enum => %w(replies retweets), :desc => "Exclude certain types of Tweets from the results.", :banner => "TYPE"
173
174
  method_option "id", :aliases => "-i", :type => :boolean, :default => false, :desc => "Specify user via ID instead of screen name."
174
175
  method_option "long", :aliases => "-l", :type => :boolean, :default => false, :desc => "Output in long format."
175
- method_option "decode_urls", :aliases => "-d", :type => :boolean, :default => false, :desc => "Decodes t.co URLs into their original form."
176
+ method_option "since_id", :aliases => "-s", :type => :numeric, :desc => "Returns only the results with an ID greater than the specified ID."
176
177
  def timeline(*args)
177
- opts = {:count => MAX_NUM_RESULTS}
178
178
  query = args.pop
179
179
  user = args.pop
180
+ opts = {:count => MAX_NUM_RESULTS}
180
181
  opts[:include_entities] = 1 if options['decode_urls']
181
- exclude_opts = case options['exclude']
182
- when 'replies'
183
- {:exclude_replies => true}
184
- when 'retweets'
185
- {:include_rts => false}
186
- else
187
- {}
188
- end
182
+ opts[:exclude_replies] = true if options['exclude'] == 'replies'
183
+ opts[:include_rts] = false if options['exclude'] == 'retweets'
184
+ opts[:since_id] = options['since_id'] if options['since_id']
189
185
  if user
190
186
  require 't/core_ext/string'
191
187
  user = if options['id']
@@ -195,12 +191,12 @@ module T
195
191
  end
196
192
  tweets = collect_with_max_id do |max_id|
197
193
  opts[:max_id] = max_id unless max_id.nil?
198
- client.user_timeline(user, opts.merge(exclude_opts))
194
+ client.user_timeline(user, opts)
199
195
  end
200
196
  else
201
197
  tweets = collect_with_max_id do |max_id|
202
198
  opts[:max_id] = max_id unless max_id.nil?
203
- client.home_timeline(opts.merge(exclude_opts))
199
+ client.home_timeline(opts)
204
200
  end
205
201
  end
206
202
  tweets = tweets.select do |tweet|
@@ -9,7 +9,7 @@ module T
9
9
 
10
10
  # @return [Integer]
11
11
  def minor
12
- 6
12
+ 7
13
13
  end
14
14
 
15
15
  # @return [Integer]