t 1.6.0 → 1.7.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.
@@ -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]