t 0.9.9 → 1.0.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/t/rcfile.rb CHANGED
@@ -1,95 +1,101 @@
1
1
  require 'singleton'
2
2
 
3
- class RCFile
4
- FILE_NAME = '.trc'
5
- attr_reader :path
3
+ module T
4
+ class RCFile
5
+ FILE_NAME = '.trc'
6
+ attr_reader :path
6
7
 
7
- include Singleton
8
+ include Singleton
8
9
 
9
- def initialize
10
- @path = File.join(File.expand_path("~"), FILE_NAME)
11
- @data = load
12
- end
10
+ def initialize
11
+ @path = File.join(File.expand_path("~"), FILE_NAME)
12
+ @data = load
13
+ end
13
14
 
14
- def [](username)
15
- profiles[username]
16
- end
15
+ def [](username)
16
+ profiles[username]
17
+ end
17
18
 
18
- def []=(username, profile)
19
- profiles[username] ||= {}
20
- profiles[username].merge!(profile)
21
- write
22
- end
19
+ def []=(username, profile)
20
+ profiles[username] ||= {}
21
+ profiles[username].merge!(profile)
22
+ write
23
+ end
23
24
 
24
- def configuration
25
- @data['configuration']
26
- end
25
+ def configuration
26
+ @data['configuration']
27
+ end
27
28
 
28
- def active_consumer_key
29
- profiles[active_profile[0]][active_profile[1]]['consumer_key'] if active_profile && profiles[active_profile[0]] && profiles[active_profile[0]][active_profile[1]]
30
- end
29
+ def active_consumer_key
30
+ profiles[active_profile[0]][active_profile[1]]['consumer_key'] if active_profile?
31
+ end
31
32
 
32
- def active_consumer_secret
33
- profiles[active_profile[0]][active_profile[1]]['consumer_secret'] if active_profile && profiles[active_profile[0]] && profiles[active_profile[0]][active_profile[1]]
34
- end
33
+ def active_consumer_secret
34
+ profiles[active_profile[0]][active_profile[1]]['consumer_secret'] if active_profile?
35
+ end
35
36
 
36
- def active_profile
37
- configuration['default_profile']
38
- end
37
+ def active_profile
38
+ configuration['default_profile']
39
+ end
39
40
 
40
- def active_profile=(profile)
41
- configuration['default_profile'] = [profile['username'], profile['consumer_key']]
42
- write
43
- end
41
+ def active_profile=(profile)
42
+ configuration['default_profile'] = [profile['username'], profile['consumer_key']]
43
+ write
44
+ end
44
45
 
45
- def active_secret
46
- profiles[active_profile[0]][active_profile[1]]['secret'] if active_profile && profiles[active_profile[0]] && profiles[active_profile[0]][active_profile[1]]
47
- end
46
+ def active_secret
47
+ profiles[active_profile[0]][active_profile[1]]['secret'] if active_profile?
48
+ end
48
49
 
49
- def active_token
50
- profiles[active_profile[0]][active_profile[1]]['token'] if active_profile && profiles[active_profile[0]] && profiles[active_profile[0]][active_profile[1]]
51
- end
50
+ def active_token
51
+ profiles[active_profile[0]][active_profile[1]]['token'] if active_profile?
52
+ end
52
53
 
53
- def delete
54
- File.delete(@path) if File.exist?(@path)
55
- end
54
+ def delete
55
+ File.delete(@path) if File.exist?(@path)
56
+ end
56
57
 
57
- def empty?
58
- @data == default_structure
59
- end
58
+ def empty?
59
+ @data == default_structure
60
+ end
60
61
 
61
- def load
62
- require 'yaml'
63
- YAML.load_file(@path)
64
- rescue Errno::ENOENT
65
- default_structure
66
- end
62
+ def load
63
+ require 'yaml'
64
+ YAML.load_file(@path)
65
+ rescue Errno::ENOENT
66
+ default_structure
67
+ end
67
68
 
68
- def path=(path)
69
- @path = path
70
- @data = load
71
- @path
72
- end
69
+ def path=(path)
70
+ @path = path
71
+ @data = load
72
+ @path
73
+ end
73
74
 
74
- def profiles
75
- @data['profiles']
76
- end
75
+ def profiles
76
+ @data['profiles']
77
+ end
77
78
 
78
- def reset
79
- self.send(:initialize)
80
- end
79
+ def reset
80
+ self.send(:initialize)
81
+ end
81
82
 
82
- private
83
+ private
83
84
 
84
- def default_structure
85
- {'configuration' => {}, 'profiles' => {}}
86
- end
85
+ def active_profile?
86
+ active_profile && profiles[active_profile[0]] && profiles[active_profile[0]][active_profile[1]]
87
+ end
87
88
 
88
- def write
89
- require 'yaml'
90
- File.open(@path, File::RDWR|File::TRUNC|File::CREAT, 0600) do |rcfile|
91
- rcfile.write @data.to_yaml
89
+ def default_structure
90
+ {'configuration' => {}, 'profiles' => {}}
91
+ end
92
+
93
+ def write
94
+ require 'yaml'
95
+ File.open(@path, File::RDWR|File::TRUNC|File::CREAT, 0600) do |rcfile|
96
+ rcfile.write @data.to_yaml
97
+ end
92
98
  end
93
- end
94
99
 
100
+ end
95
101
  end
data/lib/t/search.rb CHANGED
@@ -1,26 +1,26 @@
1
1
  require 'thor'
2
2
  require 'twitter'
3
+ require 't/collectable'
4
+ require 't/printable'
5
+ require 't/rcfile'
6
+ require 't/requestable'
7
+ require 't/utils'
3
8
 
4
9
  module T
5
- autoload :Collectable, 't/collectable'
6
- autoload :Printable, 't/printable'
7
- autoload :RCFile, 't/rcfile'
8
- autoload :Requestable, 't/requestable'
9
10
  class Search < Thor
10
11
  include T::Collectable
11
12
  include T::Printable
12
13
  include T::Requestable
14
+ include T::Utils
13
15
 
14
16
  DEFAULT_NUM_RESULTS = 20
15
17
  MAX_NUM_RESULTS = 200
16
- MAX_SCREEN_NAME_SIZE = 20
17
- MAX_USERS_PER_REQUEST = 20
18
18
 
19
19
  check_unknown_options!
20
20
 
21
21
  def initialize(*)
22
+ @rcfile = T::RCFile.instance
22
23
  super
23
- @rcfile = RCFile.instance
24
24
  end
25
25
 
26
26
  desc "all QUERY", "Returns the #{DEFAULT_NUM_RESULTS} most recent Tweets that match the specified query."
@@ -30,8 +30,9 @@ module T
30
30
  def all(query)
31
31
  rpp = options['number'] || DEFAULT_NUM_RESULTS
32
32
  statuses = collect_with_rpp(rpp) do |opts|
33
- client.search(query, opts)
33
+ client.search(query, opts).results
34
34
  end
35
+ statuses.reverse! if options['reverse']
35
36
  require 'htmlentities'
36
37
  if options['csv']
37
38
  require 'csv'
@@ -49,19 +50,35 @@ module T
49
50
  else
50
51
  say unless statuses.empty?
51
52
  statuses.each do |status|
52
- print_status(status)
53
+ print_message(status.from_user, status.full_text)
53
54
  end
54
55
  end
55
56
  end
56
57
 
57
- desc "favorites QUERY", "Returns Tweets you've favorited that match the specified query."
58
+ desc "favorites [USER] QUERY", "Returns Tweets you've favorited that match the specified query."
58
59
  method_option "csv", :aliases => "-c", :type => :boolean, :default => false, :desc => "Output in CSV format."
60
+ method_option "id", :aliases => "-i", :type => "boolean", :default => false, :desc => "Specify user via ID instead of screen name."
59
61
  method_option "long", :aliases => "-l", :type => :boolean, :default => false, :desc => "Output in long format."
60
- def favorites(query)
62
+ def favorites(*args)
61
63
  opts = {:count => MAX_NUM_RESULTS}
62
- statuses = collect_with_max_id do |max_id|
63
- opts[:max_id] = max_id unless max_id.nil?
64
- client.favorites(opts)
64
+ query = args.pop
65
+ user = args.pop
66
+ if user
67
+ require 't/core_ext/string'
68
+ user = if options['id']
69
+ user.to_i
70
+ else
71
+ user.strip_ats
72
+ end
73
+ statuses = collect_with_max_id do |max_id|
74
+ opts[:max_id] = max_id unless max_id.nil?
75
+ client.favorites(user, opts)
76
+ end
77
+ else
78
+ statuses = collect_with_max_id do |max_id|
79
+ opts[:max_id] = max_id unless max_id.nil?
80
+ client.favorites(opts)
81
+ end
65
82
  end
66
83
  statuses = statuses.select do |status|
67
84
  /#{query}/i.match(status.full_text)
@@ -75,18 +92,7 @@ module T
75
92
  method_option "id", :aliases => "-i", :type => "boolean", :default => false, :desc => "Specify user via ID instead of screen name."
76
93
  method_option "long", :aliases => "-l", :type => :boolean, :default => false, :desc => "Output in long format."
77
94
  def list(list, query)
78
- owner, list = list.split('/')
79
- if list.nil?
80
- list = owner
81
- owner = @rcfile.active_profile[0]
82
- else
83
- require 't/core_ext/string'
84
- owner = if options['id']
85
- owner.to_i
86
- else
87
- owner.strip_ats
88
- end
89
- end
95
+ owner, list = extract_owner(list, options)
90
96
  opts = {:count => MAX_NUM_RESULTS}
91
97
  statuses = collect_with_max_id do |max_id|
92
98
  opts[:max_id] = max_id unless max_id.nil?
@@ -114,14 +120,30 @@ module T
114
120
  end
115
121
  map %w(replies) => :mentions
116
122
 
117
- desc "retweets QUERY", "Returns Tweets you've retweeted that match the specified query."
123
+ desc "retweets [USER] QUERY", "Returns Tweets you've retweeted that match the specified query."
118
124
  method_option "csv", :aliases => "-c", :type => :boolean, :default => false, :desc => "Output in CSV format."
125
+ method_option "id", :aliases => "-i", :type => "boolean", :default => false, :desc => "Specify user via ID instead of screen name."
119
126
  method_option "long", :aliases => "-l", :type => :boolean, :default => false, :desc => "Output in long format."
120
- def retweets(query)
127
+ def retweets(*args)
121
128
  opts = {:count => MAX_NUM_RESULTS}
122
- statuses = collect_with_max_id do |max_id|
123
- opts[:max_id] = max_id unless max_id.nil?
124
- client.retweeted_by(opts)
129
+ query = args.pop
130
+ user = args.pop
131
+ if user
132
+ require 't/core_ext/string'
133
+ user = if options['id']
134
+ user.to_i
135
+ else
136
+ user.strip_ats
137
+ end
138
+ statuses = collect_with_max_id do |max_id|
139
+ opts[:max_id] = max_id unless max_id.nil?
140
+ client.retweeted_by_user(user, opts)
141
+ end
142
+ else
143
+ statuses = collect_with_max_id do |max_id|
144
+ opts[:max_id] = max_id unless max_id.nil?
145
+ client.retweeted_by_me(opts)
146
+ end
125
147
  end
126
148
  statuses = statuses.select do |status|
127
149
  /#{query}/i.match(status.full_text)
@@ -164,23 +186,14 @@ module T
164
186
 
165
187
  desc "users QUERY", "Returns users that match the specified query."
166
188
  method_option "csv", :aliases => "-c", :type => :boolean, :default => false, :desc => "Output in CSV format."
167
- method_option "favorites", :aliases => "-v", :type => :boolean, :default => false, :desc => "Sort by number of favorites."
168
- method_option "followers", :aliases => "-f", :type => :boolean, :default => false, :desc => "Sort by number of followers."
169
- method_option "friends", :aliases => "-e", :type => :boolean, :default => false, :desc => "Sort by number of friends."
170
- method_option "listed", :aliases => "-d", :type => :boolean, :default => false, :desc => "Sort by number of list memberships."
171
189
  method_option "long", :aliases => "-l", :type => :boolean, :default => false, :desc => "Output in long format."
172
- method_option "posted", :aliases => "-p", :type => :boolean, :default => false, :desc => "Sort by the time when Twitter account was posted."
173
190
  method_option "reverse", :aliases => "-r", :type => :boolean, :default => false, :desc => "Reverse the order of the sort."
174
- method_option "tweets", :aliases => "-t", :type => :boolean, :default => false, :desc => "Sort by number of Tweets."
191
+ 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"
175
192
  method_option "unsorted", :aliases => "-u", :type => :boolean, :default => false, :desc => "Output is not sorted."
176
193
  def users(query)
177
- require 't/core_ext/enumerable'
178
- require 'retryable'
179
- users = 1.upto(50).threaded_map do |page|
180
- retryable(:tries => 3, :on => Twitter::Error::ServerError, :sleep => 0) do
181
- client.user_search(query, :page => page, :per_page => MAX_USERS_PER_REQUEST)
182
- end
183
- end.flatten
194
+ users = collect_with_page do |page|
195
+ client.user_search(query, :page => page)
196
+ end
184
197
  print_users(users)
185
198
  end
186
199
 
data/lib/t/set.rb CHANGED
@@ -1,16 +1,16 @@
1
1
  require 'thor'
2
+ require 't/rcfile'
3
+ require 't/requestable'
2
4
 
3
5
  module T
4
- autoload :RCFile, 't/rcfile'
5
- autoload :Requestable, 't/requestable'
6
6
  class Set < Thor
7
7
  include T::Requestable
8
8
 
9
9
  check_unknown_options!
10
10
 
11
11
  def initialize(*)
12
+ @rcfile = T::RCFile.instance
12
13
  super
13
- @rcfile = RCFile.instance
14
14
  end
15
15
 
16
16
  desc "active SCREEN_NAME [CONSUMER_KEY]", "Set your active account."
data/lib/t/stream.rb CHANGED
@@ -1,10 +1,8 @@
1
1
  require 'thor'
2
+ require 't/printable'
3
+ require 't/rcfile'
2
4
 
3
5
  module T
4
- autoload :CLI, 't/cli'
5
- autoload :Printable, 't/printable'
6
- autoload :RCFile, 't/rcfile'
7
- autoload :Search, 't/search'
8
6
  class Stream < Thor
9
7
  include T::Printable
10
8
 
@@ -16,8 +14,8 @@ module T
16
14
  ]
17
15
 
18
16
  def initialize(*)
17
+ @rcfile = T::RCFile.instance
19
18
  super
20
- @rcfile = RCFile.instance
21
19
  end
22
20
 
23
21
  desc "all", "Stream a random sample of all Tweets (Control-C to stop)"
@@ -46,7 +44,7 @@ module T
46
44
  end
47
45
  print_table([array], :truncate => STDOUT.tty?)
48
46
  else
49
- print_status(status)
47
+ print_message(status.user.screen_name, status.text)
50
48
  end
51
49
  end
52
50
  client.sample
@@ -67,6 +65,7 @@ module T
67
65
  def search(keyword, *keywords)
68
66
  keywords.unshift(keyword)
69
67
  require 'tweetstream'
68
+ require 't/search'
70
69
  client.on_inited do
71
70
  search = T::Search.new
72
71
  search.options = search.options.merge(options)
@@ -83,7 +82,7 @@ module T
83
82
  end
84
83
  print_table([array], :truncate => STDOUT.tty?)
85
84
  else
86
- print_status(status)
85
+ print_message(status.user.screen_name, status.text)
87
86
  end
88
87
  end
89
88
  client.track(keywords)
@@ -94,6 +93,7 @@ module T
94
93
  method_option "long", :aliases => "-l", :type => :boolean, :default => false, :desc => "Output in long format."
95
94
  def timeline
96
95
  require 'tweetstream'
96
+ require 't/cli'
97
97
  client.on_inited do
98
98
  cli = T::CLI.new
99
99
  cli.options = cli.options.merge(options)
@@ -110,7 +110,7 @@ module T
110
110
  end
111
111
  print_table([array], :truncate => STDOUT.tty?)
112
112
  else
113
- print_status(status)
113
+ print_message(status.user.screen_name, status.text)
114
114
  end
115
115
  end
116
116
  client.userstream
@@ -144,7 +144,7 @@ module T
144
144
  end
145
145
  print_table([array], :truncate => STDOUT.tty?)
146
146
  else
147
- print_status(status)
147
+ print_message(status.user.screen_name, status.text)
148
148
  end
149
149
  end
150
150
  client.follow(user_ids)
@@ -1,5 +1,5 @@
1
1
  module T
2
- module FormatHelpers
2
+ module Utils
3
3
  private
4
4
 
5
5
  # https://github.com/rails/rails/blob/bd8a970/actionpack/lib/action_view/helpers/date_helper.rb
@@ -47,16 +47,53 @@ module T
47
47
  alias :time_ago_in_words :distance_of_time_in_words
48
48
  alias :time_from_now_in_words :distance_of_time_in_words
49
49
 
50
+ def fetch_users(users, options, &block)
51
+ format_users!(users, options)
52
+ require 'retryable'
53
+ users = retryable(:tries => 3, :on => Twitter::Error::ServerError, :sleep => 0) do
54
+ yield users
55
+ end
56
+ [users, users.length]
57
+ end
58
+
59
+ def format_users!(users, options)
60
+ require 't/core_ext/string'
61
+ if options['id']
62
+ users.map!(&:to_i)
63
+ else
64
+ users.map!(&:strip_ats)
65
+ end
66
+ end
67
+
68
+ def extract_owner(list, options)
69
+ owner, list = list.split('/')
70
+ if list.nil?
71
+ list = owner
72
+ owner = @rcfile.active_profile[0]
73
+ else
74
+ require 't/core_ext/string'
75
+ owner = if options['id']
76
+ owner.to_i
77
+ else
78
+ owner.strip_ats
79
+ end
80
+ end
81
+ [owner, list]
82
+ end
83
+
50
84
  def strip_tags(html)
51
85
  html.gsub(/<.+?>/, '')
52
86
  end
53
87
 
54
88
  def number_with_delimiter(number, delimiter=",")
55
89
  digits = number.to_s.split(//)
56
- require 'active_support/core_ext/array/grouping'
57
- groups = digits.reverse.in_groups_of(3).map{|g| g.join('')}
90
+ groups = digits.reverse.each_slice(3).map{|g| g.join('')}
58
91
  groups.join(delimiter).reverse
59
92
  end
60
93
 
94
+ def pluralize(count, singular, plural=nil)
95
+ "#{count || 0} " + ((count == 1 || count =~ /^1(\.0+)?$/) ? singular : (plural || "#{singular}s"))
96
+ end
97
+
61
98
  end
62
99
  end