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/README.md +51 -20
- data/bin/t +2 -1
- data/lib/t.rb +0 -7
- data/lib/t/cli.rb +188 -241
- data/lib/t/collectable.rb +11 -3
- data/lib/t/core_ext/string.rb +1 -1
- data/lib/t/delete.rb +36 -30
- data/lib/t/list.rb +19 -82
- data/lib/t/printable.rb +43 -38
- data/lib/t/rcfile.rb +76 -70
- data/lib/t/search.rb +57 -44
- data/lib/t/set.rb +3 -3
- data/lib/t/stream.rb +9 -9
- data/lib/t/{format_helpers.rb → utils.rb} +40 -3
- data/lib/t/version.rb +3 -3
- data/spec/cli_spec.rb +918 -436
- data/spec/delete_spec.rb +4 -4
- data/spec/fixtures/sferik.json +46 -62
- data/spec/fixtures/status_no_attributes.json +4 -4
- data/spec/fixtures/status_no_country.json +4 -4
- data/spec/fixtures/status_no_full_name.json +4 -4
- data/spec/fixtures/status_no_locality.json +4 -4
- data/spec/fixtures/status_no_street_address.json +4 -4
- data/spec/fixtures/users.json +105 -75
- data/spec/fixtures/users_list.json +105 -75
- data/spec/helper.rb +0 -1
- data/spec/list_spec.rb +125 -49
- data/spec/rcfile_spec.rb +28 -27
- data/spec/search_spec.rb +272 -249
- data/spec/set_spec.rb +24 -24
- data/spec/{format_helpers_spec.rb → utils_spec.rb} +7 -7
- data/t.gemspec +3 -5
- metadata +12 -54
- data/lib/t/authorizable.rb +0 -38
- data/lib/t/core_ext/enumerable.rb +0 -19
- data/spec/t_spec.rb +0 -31
data/lib/t/rcfile.rb
CHANGED
@@ -1,95 +1,101 @@
|
|
1
1
|
require 'singleton'
|
2
2
|
|
3
|
-
|
4
|
-
|
5
|
-
|
3
|
+
module T
|
4
|
+
class RCFile
|
5
|
+
FILE_NAME = '.trc'
|
6
|
+
attr_reader :path
|
6
7
|
|
7
|
-
|
8
|
+
include Singleton
|
8
9
|
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
10
|
+
def initialize
|
11
|
+
@path = File.join(File.expand_path("~"), FILE_NAME)
|
12
|
+
@data = load
|
13
|
+
end
|
13
14
|
|
14
|
-
|
15
|
-
|
16
|
-
|
15
|
+
def [](username)
|
16
|
+
profiles[username]
|
17
|
+
end
|
17
18
|
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
19
|
+
def []=(username, profile)
|
20
|
+
profiles[username] ||= {}
|
21
|
+
profiles[username].merge!(profile)
|
22
|
+
write
|
23
|
+
end
|
23
24
|
|
24
|
-
|
25
|
-
|
26
|
-
|
25
|
+
def configuration
|
26
|
+
@data['configuration']
|
27
|
+
end
|
27
28
|
|
28
|
-
|
29
|
-
|
30
|
-
|
29
|
+
def active_consumer_key
|
30
|
+
profiles[active_profile[0]][active_profile[1]]['consumer_key'] if active_profile?
|
31
|
+
end
|
31
32
|
|
32
|
-
|
33
|
-
|
34
|
-
|
33
|
+
def active_consumer_secret
|
34
|
+
profiles[active_profile[0]][active_profile[1]]['consumer_secret'] if active_profile?
|
35
|
+
end
|
35
36
|
|
36
|
-
|
37
|
-
|
38
|
-
|
37
|
+
def active_profile
|
38
|
+
configuration['default_profile']
|
39
|
+
end
|
39
40
|
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
41
|
+
def active_profile=(profile)
|
42
|
+
configuration['default_profile'] = [profile['username'], profile['consumer_key']]
|
43
|
+
write
|
44
|
+
end
|
44
45
|
|
45
|
-
|
46
|
-
|
47
|
-
|
46
|
+
def active_secret
|
47
|
+
profiles[active_profile[0]][active_profile[1]]['secret'] if active_profile?
|
48
|
+
end
|
48
49
|
|
49
|
-
|
50
|
-
|
51
|
-
|
50
|
+
def active_token
|
51
|
+
profiles[active_profile[0]][active_profile[1]]['token'] if active_profile?
|
52
|
+
end
|
52
53
|
|
53
|
-
|
54
|
-
|
55
|
-
|
54
|
+
def delete
|
55
|
+
File.delete(@path) if File.exist?(@path)
|
56
|
+
end
|
56
57
|
|
57
|
-
|
58
|
-
|
59
|
-
|
58
|
+
def empty?
|
59
|
+
@data == default_structure
|
60
|
+
end
|
60
61
|
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
62
|
+
def load
|
63
|
+
require 'yaml'
|
64
|
+
YAML.load_file(@path)
|
65
|
+
rescue Errno::ENOENT
|
66
|
+
default_structure
|
67
|
+
end
|
67
68
|
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
69
|
+
def path=(path)
|
70
|
+
@path = path
|
71
|
+
@data = load
|
72
|
+
@path
|
73
|
+
end
|
73
74
|
|
74
|
-
|
75
|
-
|
76
|
-
|
75
|
+
def profiles
|
76
|
+
@data['profiles']
|
77
|
+
end
|
77
78
|
|
78
|
-
|
79
|
-
|
80
|
-
|
79
|
+
def reset
|
80
|
+
self.send(:initialize)
|
81
|
+
end
|
81
82
|
|
82
|
-
private
|
83
|
+
private
|
83
84
|
|
84
|
-
|
85
|
-
|
86
|
-
|
85
|
+
def active_profile?
|
86
|
+
active_profile && profiles[active_profile[0]] && profiles[active_profile[0]][active_profile[1]]
|
87
|
+
end
|
87
88
|
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
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
|
-
|
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(
|
62
|
+
def favorites(*args)
|
61
63
|
opts = {:count => MAX_NUM_RESULTS}
|
62
|
-
|
63
|
-
|
64
|
-
|
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
|
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(
|
127
|
+
def retweets(*args)
|
121
128
|
opts = {:count => MAX_NUM_RESULTS}
|
122
|
-
|
123
|
-
|
124
|
-
|
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 "
|
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
|
-
|
178
|
-
|
179
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
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
|
-
|
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
|