t 3.1.0 → 4.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -5
- data/LICENSE.md +1 -1
- data/README.md +8 -9
- data/bin/t +21 -21
- data/lib/t/cli.rb +470 -463
- data/lib/t/collectable.rb +9 -8
- data/lib/t/core_ext/kernel.rb +3 -3
- data/lib/t/core_ext/string.rb +2 -2
- data/lib/t/delete.rb +44 -41
- data/lib/t/editor.rb +5 -5
- data/lib/t/identicon.rb +1 -1
- data/lib/t/list.rb +51 -51
- data/lib/t/printable.rb +69 -65
- data/lib/t/rcfile.rb +18 -17
- data/lib/t/requestable.rb +3 -2
- data/lib/t/search.rb +82 -82
- data/lib/t/set.rb +21 -21
- data/lib/t/stream.rb +67 -60
- data/lib/t/utils.rb +25 -25
- data/lib/t/version.rb +2 -2
- data/lib/t.rb +2 -2
- data/t.gemspec +22 -21
- metadata +29 -49
data/lib/t/printable.rb
CHANGED
@@ -1,8 +1,8 @@
|
|
1
1
|
module T
|
2
|
-
module Printable # rubocop:disable ModuleLength
|
3
|
-
LIST_HEADINGS = [
|
4
|
-
TWEET_HEADINGS = [
|
5
|
-
USER_HEADINGS = [
|
2
|
+
module Printable # rubocop:disable Metrics/ModuleLength
|
3
|
+
LIST_HEADINGS = ["ID", "Created at", "Screen name", "Slug", "Members", "Subscribers", "Mode", "Description"].freeze
|
4
|
+
TWEET_HEADINGS = ["ID", "Posted at", "Screen name", "Text"].freeze
|
5
|
+
USER_HEADINGS = ["ID", "Since", "Last tweeted at", "Tweets", "Favorites", "Listed", "Following", "Followers", "Screen name", "Name", "Verified", "Protected", "Bio", "Status", "Location", "URL"].freeze
|
6
6
|
MONTH_IN_SECONDS = 2_592_000
|
7
7
|
|
8
8
|
private
|
@@ -12,73 +12,75 @@ module T
|
|
12
12
|
end
|
13
13
|
|
14
14
|
def build_long_tweet(tweet)
|
15
|
-
[tweet.id, ls_formatted_time(tweet), "@#{tweet.user.screen_name}", decode_full_text(tweet, options[
|
15
|
+
[tweet.id, ls_formatted_time(tweet), "@#{tweet.user.screen_name}", decode_full_text(tweet, options["decode_uris"]).gsub(/\n+/, " ")]
|
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.favorites_count, user.listed_count, user.friends_count, user.followers_count, "@#{user.screen_name}", user.name, user.verified? ?
|
19
|
+
[user.id, ls_formatted_time(user), ls_formatted_time(user.status), user.statuses_count, user.favorites_count, user.listed_count, user.friends_count, user.followers_count, "@#{user.screen_name}", user.name, user.verified? ? "Yes" : "No", user.protected? ? "Yes" : "No", user.description.gsub(/\n+/, " "), user.status? ? decode_full_text(user.status, options["decode_uris"]).gsub(/\n+/, " ") : nil, user.location, user.website.to_s]
|
20
20
|
end
|
21
21
|
|
22
22
|
def csv_formatted_time(object, key = :created_at)
|
23
23
|
return nil if object.nil?
|
24
|
+
|
24
25
|
time = object.send(key.to_sym).dup
|
25
|
-
time.utc.strftime(
|
26
|
+
time.utc.strftime("%Y-%m-%d %H:%M:%S %z")
|
26
27
|
end
|
27
28
|
|
28
29
|
def ls_formatted_time(object, key = :created_at, allow_relative = true)
|
29
|
-
return
|
30
|
+
return "" if object.nil?
|
31
|
+
|
30
32
|
time = T.local_time(object.send(key.to_sym))
|
31
|
-
if allow_relative && options[
|
32
|
-
distance_of_time_in_words(time)
|
33
|
-
elsif time > Time.now - MONTH_IN_SECONDS * 6
|
34
|
-
time.strftime(
|
33
|
+
if allow_relative && options["relative_dates"]
|
34
|
+
"#{distance_of_time_in_words(time)} ago"
|
35
|
+
elsif time > Time.now - (MONTH_IN_SECONDS * 6)
|
36
|
+
time.strftime("%b %e %H:%M")
|
35
37
|
else
|
36
|
-
time.strftime(
|
38
|
+
time.strftime("%b %e %Y")
|
37
39
|
end
|
38
40
|
end
|
39
41
|
|
40
42
|
def print_csv_list(list)
|
41
|
-
require
|
43
|
+
require "csv"
|
42
44
|
say [list.id, csv_formatted_time(list), list.user.screen_name, list.slug, list.member_count, list.subscriber_count, list.mode, list.description].to_csv
|
43
45
|
end
|
44
46
|
|
45
47
|
def print_csv_tweet(tweet)
|
46
|
-
require
|
47
|
-
say [tweet.id, csv_formatted_time(tweet), tweet.user.screen_name, decode_full_text(tweet, options[
|
48
|
+
require "csv"
|
49
|
+
say [tweet.id, csv_formatted_time(tweet), tweet.user.screen_name, decode_full_text(tweet, options["decode_uris"])].to_csv
|
48
50
|
end
|
49
51
|
|
50
52
|
def print_csv_user(user)
|
51
|
-
require
|
53
|
+
require "csv"
|
52
54
|
say [user.id, csv_formatted_time(user), csv_formatted_time(user.status), user.statuses_count, user.favorites_count, user.listed_count, user.friends_count, user.followers_count, user.screen_name, user.name, user.verified?, user.protected?, user.description, user.status? ? user.status.full_text : nil, user.location, user.website].to_csv
|
53
55
|
end
|
54
56
|
|
55
57
|
def print_lists(lists)
|
56
|
-
unless options[
|
57
|
-
lists = case options[
|
58
|
-
when
|
58
|
+
unless options["unsorted"]
|
59
|
+
lists = case options["sort"]
|
60
|
+
when "members"
|
59
61
|
lists.sort_by(&:member_count)
|
60
|
-
when
|
62
|
+
when "mode"
|
61
63
|
lists.sort_by(&:mode)
|
62
|
-
when
|
64
|
+
when "since"
|
63
65
|
lists.sort_by(&:created_at)
|
64
|
-
when
|
66
|
+
when "subscribers"
|
65
67
|
lists.sort_by(&:subscriber_count)
|
66
68
|
else
|
67
69
|
lists.sort_by { |list| list.slug.downcase }
|
68
70
|
end
|
69
71
|
end
|
70
|
-
lists.reverse! if options[
|
71
|
-
if options[
|
72
|
-
require
|
72
|
+
lists.reverse! if options["reverse"]
|
73
|
+
if options["csv"]
|
74
|
+
require "csv"
|
73
75
|
say LIST_HEADINGS.to_csv unless lists.empty?
|
74
76
|
lists.each do |list|
|
75
77
|
print_csv_list(list)
|
76
78
|
end
|
77
|
-
elsif options[
|
79
|
+
elsif options["long"]
|
78
80
|
array = lists.collect do |list|
|
79
81
|
build_long_list(list)
|
80
82
|
end
|
81
|
-
format = options[
|
83
|
+
format = options["format"] || Array.new(LIST_HEADINGS.size) { "%s" }
|
82
84
|
print_table_with_headings(array, LIST_HEADINGS, format)
|
83
85
|
else
|
84
86
|
print_attribute(lists, :full_name)
|
@@ -97,12 +99,14 @@ module T
|
|
97
99
|
|
98
100
|
def print_table_with_headings(array, headings, format)
|
99
101
|
return if array.flatten.empty?
|
102
|
+
|
100
103
|
if STDOUT.tty?
|
101
104
|
array.unshift(headings)
|
102
|
-
require
|
105
|
+
require "t/core_ext/kernel"
|
103
106
|
array.collect! do |row|
|
104
107
|
row.each_with_index.collect do |element, index|
|
105
108
|
next if element.nil?
|
109
|
+
|
106
110
|
Kernel.send(element.class.name.to_sym, format[index] % element)
|
107
111
|
end
|
108
112
|
end
|
@@ -114,14 +118,14 @@ module T
|
|
114
118
|
end
|
115
119
|
|
116
120
|
def print_message(from_user, message)
|
117
|
-
require
|
121
|
+
require "htmlentities"
|
118
122
|
|
119
|
-
case options[
|
120
|
-
when
|
123
|
+
case options["color"]
|
124
|
+
when "icon"
|
121
125
|
print_identicon(from_user, message)
|
122
126
|
say
|
123
|
-
when
|
124
|
-
say(" @#{from_user}", [
|
127
|
+
when "auto"
|
128
|
+
say(" @#{from_user}", %i[bold yellow])
|
125
129
|
print_wrapped(HTMLEntities.new.decode(message), indent: 3)
|
126
130
|
else
|
127
131
|
say(" @#{from_user}")
|
@@ -131,30 +135,30 @@ module T
|
|
131
135
|
end
|
132
136
|
|
133
137
|
def print_identicon(from_user, message)
|
134
|
-
require
|
135
|
-
require
|
138
|
+
require "htmlentities"
|
139
|
+
require "t/identicon"
|
136
140
|
icon = Identicon.for_user_name(from_user)
|
137
141
|
|
138
142
|
# Save 6 chars for icon, ensure at least 3 lines long
|
139
|
-
lines = wrapped(HTMLEntities.new.decode(message), indent: 2, width: terminal_width - (6 + 5))
|
143
|
+
lines = wrapped(HTMLEntities.new.decode(message), indent: 2, width: Thor::Shell::Terminal.terminal_width - (6 + 5))
|
140
144
|
lines.unshift(set_color(" @#{from_user}", :bold, :yellow))
|
141
|
-
lines.concat(Array.new([3 - lines.length, 0].max) {
|
145
|
+
lines.concat(Array.new([3 - lines.length, 0].max) { "" })
|
142
146
|
|
143
|
-
$stdout.puts
|
147
|
+
$stdout.puts(lines.zip(icon.lines).map { |x, i| " #{i || ' '}#{x}" })
|
144
148
|
end
|
145
149
|
|
146
150
|
def wrapped(message, options = {})
|
147
151
|
indent = options[:indent] || 0
|
148
|
-
width = options[:width] || terminal_width - indent
|
152
|
+
width = options[:width] || (Thor::Shell::Terminal.terminal_width - indent)
|
149
153
|
paras = message.split("\n\n")
|
150
154
|
|
151
155
|
paras.map! do |unwrapped|
|
152
|
-
unwrapped.strip.squeeze(
|
156
|
+
unwrapped.strip.squeeze(" ").gsub(/.{1,#{width}}(?:\s|\Z)/) { (::Regexp.last_match(0) + 5.chr).gsub(/\n\005/, "\n").gsub(/\005/, "\n") }
|
153
157
|
end
|
154
158
|
|
155
159
|
lines = paras.inject([]) do |memo, para|
|
156
|
-
memo.concat(para.split("\n").map { |line| line.insert(0,
|
157
|
-
memo.push
|
160
|
+
memo.concat(para.split("\n").map { |line| line.insert(0, " " * indent) })
|
161
|
+
memo.push ""
|
158
162
|
end
|
159
163
|
|
160
164
|
lines.pop
|
@@ -162,59 +166,59 @@ module T
|
|
162
166
|
end
|
163
167
|
|
164
168
|
def print_tweets(tweets)
|
165
|
-
tweets.reverse! if options[
|
166
|
-
if options[
|
167
|
-
require
|
169
|
+
tweets.reverse! if options["reverse"]
|
170
|
+
if options["csv"]
|
171
|
+
require "csv"
|
168
172
|
say TWEET_HEADINGS.to_csv unless tweets.empty?
|
169
173
|
tweets.each do |tweet|
|
170
174
|
print_csv_tweet(tweet)
|
171
175
|
end
|
172
|
-
elsif options[
|
176
|
+
elsif options["long"]
|
173
177
|
array = tweets.collect do |tweet|
|
174
178
|
build_long_tweet(tweet)
|
175
179
|
end
|
176
|
-
format = options[
|
180
|
+
format = options["format"] || Array.new(TWEET_HEADINGS.size) { "%s" }
|
177
181
|
print_table_with_headings(array, TWEET_HEADINGS, format)
|
178
182
|
else
|
179
183
|
tweets.each do |tweet|
|
180
|
-
print_message(tweet.user.screen_name, decode_uris(tweet.full_text, options[
|
184
|
+
print_message(tweet.user.screen_name, decode_uris(tweet.full_text, options["decode_uris"] ? tweet.uris : nil))
|
181
185
|
end
|
182
186
|
end
|
183
187
|
end
|
184
188
|
|
185
|
-
def print_users(users) # rubocop:disable CyclomaticComplexity
|
186
|
-
unless options[
|
187
|
-
users = case options[
|
188
|
-
when
|
189
|
+
def print_users(users) # rubocop:disable Metrics/CyclomaticComplexity
|
190
|
+
unless options["unsorted"]
|
191
|
+
users = case options["sort"]
|
192
|
+
when "favorites"
|
189
193
|
users.sort_by { |user| user.favorites_count.to_i }
|
190
|
-
when
|
194
|
+
when "followers"
|
191
195
|
users.sort_by { |user| user.followers_count.to_i }
|
192
|
-
when
|
196
|
+
when "friends"
|
193
197
|
users.sort_by { |user| user.friends_count.to_i }
|
194
|
-
when
|
198
|
+
when "listed"
|
195
199
|
users.sort_by { |user| user.listed_count.to_i }
|
196
|
-
when
|
200
|
+
when "since"
|
197
201
|
users.sort_by(&:created_at)
|
198
|
-
when
|
202
|
+
when "tweets"
|
199
203
|
users.sort_by { |user| user.statuses_count.to_i }
|
200
|
-
when
|
201
|
-
users.sort_by { |user| user.status? ? user.status.created_at : Time.at(0) } # rubocop:disable BlockNesting
|
204
|
+
when "tweeted"
|
205
|
+
users.sort_by { |user| user.status? ? user.status.created_at : Time.at(0) } # rubocop:disable Metrics/BlockNesting
|
202
206
|
else
|
203
207
|
users.sort_by { |user| user.screen_name.downcase }
|
204
208
|
end
|
205
209
|
end
|
206
|
-
users.reverse! if options[
|
207
|
-
if options[
|
208
|
-
require
|
210
|
+
users.reverse! if options["reverse"]
|
211
|
+
if options["csv"]
|
212
|
+
require "csv"
|
209
213
|
say USER_HEADINGS.to_csv unless users.empty?
|
210
214
|
users.each do |user|
|
211
215
|
print_csv_user(user)
|
212
216
|
end
|
213
|
-
elsif options[
|
217
|
+
elsif options["long"]
|
214
218
|
array = users.collect do |user|
|
215
219
|
build_long_user(user)
|
216
220
|
end
|
217
|
-
format = options[
|
221
|
+
format = options["format"] || Array.new(USER_HEADINGS.size) { "%s" }
|
218
222
|
print_table_with_headings(array, USER_HEADINGS, format)
|
219
223
|
else
|
220
224
|
print_attribute(users, :screen_name)
|
data/lib/t/rcfile.rb
CHANGED
@@ -1,13 +1,14 @@
|
|
1
|
-
require
|
1
|
+
require "singleton"
|
2
2
|
|
3
3
|
module T
|
4
4
|
class RCFile
|
5
5
|
include Singleton
|
6
6
|
attr_reader :path
|
7
|
-
|
7
|
+
|
8
|
+
FILE_NAME = ".trc".freeze
|
8
9
|
|
9
10
|
def initialize
|
10
|
-
@path = File.join(File.expand_path(
|
11
|
+
@path = File.join(File.expand_path("~"), FILE_NAME)
|
11
12
|
@data = load_file
|
12
13
|
end
|
13
14
|
|
@@ -17,7 +18,8 @@ module T
|
|
17
18
|
|
18
19
|
def find(username)
|
19
20
|
possibilities = Array(find_case_insensitive_match(username) || find_case_insensitive_possibilities(username))
|
20
|
-
raise(ArgumentError.new("Username #{username} is #{possibilities.empty? ? 'not found.' :
|
21
|
+
raise(ArgumentError.new("Username #{username} is #{possibilities.empty? ? 'not found.' : "ambiguous, matching #{possibilities.join(', ')}"}")) unless possibilities.size == 1
|
22
|
+
|
21
23
|
possibilities.first
|
22
24
|
end
|
23
25
|
|
@@ -36,36 +38,36 @@ module T
|
|
36
38
|
end
|
37
39
|
|
38
40
|
def configuration
|
39
|
-
@data[
|
41
|
+
@data["configuration"]
|
40
42
|
end
|
41
43
|
|
42
44
|
def active_consumer_key
|
43
|
-
profiles[active_profile[0]][active_profile[1]][
|
45
|
+
profiles[active_profile[0]][active_profile[1]]["consumer_key"] if active_profile?
|
44
46
|
end
|
45
47
|
|
46
48
|
def active_consumer_secret
|
47
|
-
profiles[active_profile[0]][active_profile[1]][
|
49
|
+
profiles[active_profile[0]][active_profile[1]]["consumer_secret"] if active_profile?
|
48
50
|
end
|
49
51
|
|
50
52
|
def active_profile
|
51
|
-
configuration[
|
53
|
+
configuration["default_profile"]
|
52
54
|
end
|
53
55
|
|
54
56
|
def active_profile=(profile)
|
55
|
-
configuration[
|
57
|
+
configuration["default_profile"] = [profile["username"], profile["consumer_key"]]
|
56
58
|
write
|
57
59
|
end
|
58
60
|
|
59
61
|
def active_secret
|
60
|
-
profiles[active_profile[0]][active_profile[1]][
|
62
|
+
profiles[active_profile[0]][active_profile[1]]["secret"] if active_profile?
|
61
63
|
end
|
62
64
|
|
63
65
|
def active_token
|
64
|
-
profiles[active_profile[0]][active_profile[1]][
|
66
|
+
profiles[active_profile[0]][active_profile[1]]["token"] if active_profile?
|
65
67
|
end
|
66
68
|
|
67
69
|
def delete
|
68
|
-
|
70
|
+
FileUtils.rm_f(@path)
|
69
71
|
end
|
70
72
|
|
71
73
|
def empty?
|
@@ -73,7 +75,7 @@ module T
|
|
73
75
|
end
|
74
76
|
|
75
77
|
def load_file
|
76
|
-
require
|
78
|
+
require "yaml"
|
77
79
|
YAML.load_file(@path)
|
78
80
|
rescue Errno::ENOENT
|
79
81
|
default_structure
|
@@ -82,11 +84,10 @@ module T
|
|
82
84
|
def path=(path)
|
83
85
|
@path = path
|
84
86
|
@data = load_file
|
85
|
-
@path
|
86
87
|
end
|
87
88
|
|
88
89
|
def profiles
|
89
|
-
@data[
|
90
|
+
@data["profiles"]
|
90
91
|
end
|
91
92
|
|
92
93
|
def reset
|
@@ -110,11 +111,11 @@ module T
|
|
110
111
|
end
|
111
112
|
|
112
113
|
def default_structure
|
113
|
-
{
|
114
|
+
{"configuration" => {}, "profiles" => {}}
|
114
115
|
end
|
115
116
|
|
116
117
|
def write
|
117
|
-
require
|
118
|
+
require "yaml"
|
118
119
|
File.open(@path, File::RDWR | File::TRUNC | File::CREAT, 0o0600) do |rcfile|
|
119
120
|
rcfile.write @data.to_yaml
|
120
121
|
end
|
data/lib/t/requestable.rb
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
require
|
1
|
+
require "twitter"
|
2
2
|
|
3
3
|
module T
|
4
4
|
module Requestable
|
@@ -6,7 +6,8 @@ module T
|
|
6
6
|
|
7
7
|
def client
|
8
8
|
return @client if @client
|
9
|
-
|
9
|
+
|
10
|
+
@rcfile.path = options["profile"] if options["profile"]
|
10
11
|
@client = Twitter::REST::Client.new do |config|
|
11
12
|
config.consumer_key = @rcfile.active_consumer_key
|
12
13
|
config.consumer_secret = @rcfile.active_consumer_secret
|