usaidwat 1.3.0 → 1.4.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitattributes +1 -0
- data/README.textile +7 -2
- data/bin/usaidwat +2 -1
- data/features/fixtures/submissions_blank.json +1 -0
- data/features/fixtures/submissions_mipadi.json +1 -0
- data/features/fixtures/submissions_testuser.json +1 -0
- data/features/help.feature +5 -2
- data/features/log.feature +2 -1
- data/features/posts.feature +349 -0
- data/features/step_definitions/time_steps.rb +1 -1
- data/features/tally.feature +12 -6
- data/features/user.feature +2 -1
- data/lib/usaidwat.rb +1 -2
- data/lib/usaidwat/application.rb +87 -61
- data/lib/usaidwat/client.rb +8 -0
- data/lib/usaidwat/count.rb +27 -0
- data/lib/usaidwat/ext/array.rb +5 -0
- data/lib/usaidwat/ext/mercenary.rb +16 -0
- data/lib/usaidwat/filter.rb +40 -0
- data/lib/usaidwat/formatter.rb +42 -1
- data/lib/usaidwat/service.rb +17 -0
- data/lib/usaidwat/version.rb +1 -1
- data/spec/usaidwat/array_spec.rb +36 -0
- data/spec/usaidwat/client_spec.rb +24 -0
- data/spec/usaidwat/count_spec.rb +61 -0
- data/spec/usaidwat/filter_spec.rb +76 -0
- data/spec/usaidwat/formatter_spec.rb +92 -0
- data/usaidwat.gemspec +2 -2
- metadata +40 -26
- data/features/support/time.rb +0 -5
- data/lib/usaidwat/terminal.rb +0 -19
- data/spec/usaidwat/terminal_spec.rb +0 -21
data/features/tally.feature
CHANGED
@@ -63,7 +63,8 @@ Feature: Tally comments
|
|
63
63
|
Scenario: Search for a comment when tallying
|
64
64
|
Given the Reddit service returns comments for the user "mipadi"
|
65
65
|
When I run `usaidwat tally --grep='Heisenbug' mipadi`
|
66
|
-
Then
|
66
|
+
Then the exit status should not be 0
|
67
|
+
And stderr should contain:
|
67
68
|
"""
|
68
69
|
invalid option: --grep=Heisenbug
|
69
70
|
"""
|
@@ -71,7 +72,8 @@ Feature: Tally comments
|
|
71
72
|
Scenario: Search for a comment when sorting
|
72
73
|
Given the Reddit service returns comments for the user "mipadi"
|
73
74
|
When I run `usaidwat tally -c --grep='Heisenbug' mipadi`
|
74
|
-
Then
|
75
|
+
Then the exit status should not be 0
|
76
|
+
And stderr should contain:
|
75
77
|
"""
|
76
78
|
invalid option: --grep=Heisenbug
|
77
79
|
"""
|
@@ -87,7 +89,8 @@ Feature: Tally comments
|
|
87
89
|
Scenario: Tally comments with subreddit
|
88
90
|
Given the Reddit service returns comments for the user "mipadi"
|
89
91
|
When I run `usaidwat tally mipadi AskReddit`
|
90
|
-
Then
|
92
|
+
Then the exit status should not be 0
|
93
|
+
And stderr should contain exactly:
|
91
94
|
"""
|
92
95
|
You cannot specify a subreddit when tallying comments
|
93
96
|
"""
|
@@ -95,7 +98,8 @@ Feature: Tally comments
|
|
95
98
|
Scenario: Sort comments with subreddit
|
96
99
|
Given the Reddit service returns comments for the user "mipadi"
|
97
100
|
When I run `usaidwat tally -c mipadi AskReddit`
|
98
|
-
Then
|
101
|
+
Then the exit status should not be 0
|
102
|
+
And stderr should contain exactly:
|
99
103
|
"""
|
100
104
|
You cannot specify a subreddit when tallying comments
|
101
105
|
"""
|
@@ -103,7 +107,8 @@ Feature: Tally comments
|
|
103
107
|
Scenario: Pass no arguments when tallying
|
104
108
|
Given the Reddit service returns comments for the user "mipadi"
|
105
109
|
When I run `usaidwat tally`
|
106
|
-
Then
|
110
|
+
Then the exit status should not be 0
|
111
|
+
And stderr should contain exactly:
|
107
112
|
"""
|
108
113
|
You must specify a username
|
109
114
|
"""
|
@@ -111,7 +116,8 @@ Feature: Tally comments
|
|
111
116
|
Scenario: Pass no arguments when sorting
|
112
117
|
Given the Reddit service returns comments for the user "mipadi"
|
113
118
|
When I run `usaidwat tally -c`
|
114
|
-
Then
|
119
|
+
Then the exit status should not be 0
|
120
|
+
And stderr should contain exactly:
|
115
121
|
"""
|
116
122
|
You must specify a username
|
117
123
|
"""
|
data/features/user.feature
CHANGED
@@ -27,7 +27,8 @@ Feature: Display user information
|
|
27
27
|
Scenario: Fail to pass a username when querying for information
|
28
28
|
Given the Reddit service returns information for the user "mipadi"
|
29
29
|
When I run `usaidwat info`
|
30
|
-
Then
|
30
|
+
Then the exit status should not be 0
|
31
|
+
And stderr should contain exactly:
|
31
32
|
"""
|
32
33
|
You must specify a username
|
33
34
|
"""
|
data/lib/usaidwat.rb
CHANGED
data/lib/usaidwat/application.rb
CHANGED
@@ -1,8 +1,12 @@
|
|
1
|
-
require '
|
1
|
+
require 'sysexits'
|
2
2
|
require 'usaidwat/client'
|
3
|
+
require 'usaidwat/count'
|
3
4
|
require 'usaidwat/either'
|
5
|
+
require 'usaidwat/filter'
|
4
6
|
require 'usaidwat/pager'
|
5
|
-
require '
|
7
|
+
require 'usaidwat/ext/array'
|
8
|
+
|
9
|
+
require 'timecop' if ENV['USAIDWAT_ENV'] == 'cucumber'
|
6
10
|
|
7
11
|
module USaidWat
|
8
12
|
module Application
|
@@ -25,6 +29,7 @@ module USaidWat
|
|
25
29
|
|
26
30
|
def initialize(prog)
|
27
31
|
@client = cucumber? ? USaidWat::Client::TestRedditor : USaidWat::Client::Redditor
|
32
|
+
Timecop.freeze(Time.parse(ENV['USAIDWAT_CURRENT_TIME'])) if cucumber_time?
|
28
33
|
end
|
29
34
|
|
30
35
|
protected
|
@@ -33,8 +38,13 @@ module USaidWat
|
|
33
38
|
ENV['USAIDWAT_ENV'] == 'cucumber'
|
34
39
|
end
|
35
40
|
|
41
|
+
def cucumber_time?
|
42
|
+
cucumber? && !ENV['USAIDWAT_CURRENT_TIME'].nil?
|
43
|
+
end
|
44
|
+
|
36
45
|
def quit(message, code=:ok)
|
37
|
-
|
46
|
+
stream = code == :ok ? $stdout : $stderr
|
47
|
+
stream.puts message
|
38
48
|
exit code
|
39
49
|
end
|
40
50
|
end
|
@@ -64,6 +74,8 @@ module USaidWat
|
|
64
74
|
end
|
65
75
|
|
66
76
|
class Log < Command
|
77
|
+
include FilterCommand
|
78
|
+
|
67
79
|
def initialize(prog)
|
68
80
|
prog.command(:log) do |c|
|
69
81
|
c.alias :l
|
@@ -83,15 +95,15 @@ module USaidWat
|
|
83
95
|
def process(options, args)
|
84
96
|
raise ArgumentError.new('You must specify a username') if args.empty?
|
85
97
|
username = args.shift
|
86
|
-
subreddits = args.
|
98
|
+
subreddits = args.subreddits
|
87
99
|
|
88
100
|
redditor = client.new(username)
|
89
101
|
comments = redditor.comments
|
90
102
|
|
91
|
-
res =
|
92
|
-
lambda { |r|
|
93
|
-
lambda { |r|
|
94
|
-
lambda { |r|
|
103
|
+
res = filter_entries('comments', redditor, comments, subreddits) >>
|
104
|
+
lambda { |r| grep_entries('comments', redditor, r.value, options['grep']) } >>
|
105
|
+
lambda { |r| limit_entries('comments', redditor, r.value, options['limit']) } >>
|
106
|
+
lambda { |r| ensure_entries('comments', redditor, r.value) }
|
95
107
|
|
96
108
|
quit res.value if res.left?
|
97
109
|
|
@@ -108,50 +120,83 @@ module USaidWat
|
|
108
120
|
|
109
121
|
private
|
110
122
|
|
111
|
-
def
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
else
|
117
|
-
USaidWat::Right.new(comments)
|
118
|
-
end
|
123
|
+
def list_comments(comments, options = {})
|
124
|
+
oneline = options[:oneline]
|
125
|
+
formatter = (oneline ? USaidWat::CLI::CompactCommentFormatter : USaidWat::CLI::CommentFormatter).new(options)
|
126
|
+
page
|
127
|
+
comments.each { |c| print formatter.format(c) }
|
119
128
|
end
|
129
|
+
end
|
130
|
+
|
131
|
+
class Posts < Command
|
132
|
+
include CountCommand
|
133
|
+
include FilterCommand
|
120
134
|
|
121
|
-
def
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
135
|
+
def initialize(prog)
|
136
|
+
prog.command(:posts) do |c|
|
137
|
+
c.action do |args, options|
|
138
|
+
process(options, args)
|
139
|
+
end
|
140
|
+
|
141
|
+
c.command(:log) do |s|
|
142
|
+
s.description "Show a user's submitted posts"
|
143
|
+
s.action do |args, options|
|
144
|
+
process_log(options, args)
|
145
|
+
end
|
146
|
+
end
|
147
|
+
|
148
|
+
c.command(:tally) do |s|
|
149
|
+
s.description "Tally a user's posts by subreddit"
|
150
|
+
s.option 'count', '-c', '--count', 'Sort output by number of comments'
|
151
|
+
s.action do |args, options|
|
152
|
+
process_tally(options, args)
|
153
|
+
end
|
154
|
+
end
|
129
155
|
end
|
156
|
+
super
|
130
157
|
end
|
131
158
|
|
132
|
-
def
|
133
|
-
|
134
|
-
comments = comments[0...n.to_i]
|
135
|
-
USaidWat::Right.new(comments)
|
159
|
+
def process(options, args)
|
160
|
+
quit "Do you want to tally or log posts?", :usage
|
136
161
|
end
|
137
162
|
|
138
|
-
def
|
139
|
-
if
|
140
|
-
|
141
|
-
|
142
|
-
USaidWat::Right.new(comments)
|
143
|
-
end
|
144
|
-
end
|
163
|
+
def process_log(options, args)
|
164
|
+
raise ArgumentError.new('You must specify a username') if args.empty?
|
165
|
+
username = args.shift
|
166
|
+
subreddits = args.subreddits
|
145
167
|
|
146
|
-
|
147
|
-
|
148
|
-
|
168
|
+
redditor = client.new(username)
|
169
|
+
posts = redditor.posts
|
170
|
+
|
171
|
+
res = filter_entries('posts', redditor, posts, subreddits) >>
|
172
|
+
lambda { |r| ensure_entries('posts', redditor, r.value) }
|
173
|
+
|
174
|
+
quit res.value if res.left?
|
175
|
+
posts = res.value
|
176
|
+
|
177
|
+
formatter = USaidWat::CLI::PostFormatter.new
|
149
178
|
page
|
150
|
-
|
179
|
+
posts.each { |p| print formatter.format(p) }
|
180
|
+
end
|
181
|
+
|
182
|
+
def process_tally(options, args)
|
183
|
+
raise ArgumentError.new('You must specify a username') if args.empty?
|
184
|
+
raise ArgumentError.new('You cannot specify a subreddit when tallying comments') if args.count > 1
|
185
|
+
username = args.first
|
186
|
+
|
187
|
+
redditor = client.new(username)
|
188
|
+
quit "#{redditor.username} has no posts." if redditor.posts.empty?
|
189
|
+
partition_data = partition(redditor.posts, options['count'])
|
190
|
+
formatter = USaidWat::CLI::TallyFormatter.new
|
191
|
+
print formatter.format(partition_data)
|
192
|
+
rescue USaidWat::Client::NoSuchUserError
|
193
|
+
quit "No such user: #{username}", :no_such_user
|
151
194
|
end
|
152
195
|
end
|
153
196
|
|
154
197
|
class Tally < Command
|
198
|
+
include CountCommand
|
199
|
+
|
155
200
|
def initialize(prog)
|
156
201
|
prog.command(:tally) do |c|
|
157
202
|
c.alias :t
|
@@ -171,31 +216,12 @@ module USaidWat
|
|
171
216
|
|
172
217
|
redditor = client.new(username)
|
173
218
|
quit "#{redditor.username} has no comments." if redditor.comments.empty?
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
longest_subreddit = 0
|
178
|
-
buckets = Hash.new { |hash, key| hash[key] = 0 }
|
179
|
-
redditor.comments.each do |comment|
|
180
|
-
subreddit = comment.subreddit
|
181
|
-
longest_subreddit = subreddit.length if subreddit.length > longest_subreddit
|
182
|
-
buckets[subreddit] += 1
|
183
|
-
end
|
184
|
-
algo = algorithm(options['count']).new(buckets)
|
185
|
-
subreddits = buckets.keys.sort { |a,b| algo.sort(a, b) }
|
186
|
-
subreddits.each do |subreddit|
|
187
|
-
tally = buckets[subreddit]
|
188
|
-
printf "%-*s %3d\n", longest_subreddit, subreddit, tally
|
189
|
-
end
|
219
|
+
partition_data = partition(redditor.comments, options['count'])
|
220
|
+
formatter = USaidWat::CLI::TallyFormatter.new
|
221
|
+
print formatter.format(partition_data)
|
190
222
|
rescue USaidWat::Client::NoSuchUserError
|
191
223
|
quit "No such user: #{username}", :no_such_user
|
192
224
|
end
|
193
|
-
|
194
|
-
private
|
195
|
-
|
196
|
-
def algorithm(count)
|
197
|
-
count ? USaidWat::Algorithms::CountAlgorithm : USaidWat::Algorithms::LexicographicalAlgorithm
|
198
|
-
end
|
199
225
|
end
|
200
226
|
end
|
201
227
|
end
|
data/lib/usaidwat/client.rb
CHANGED
@@ -0,0 +1,27 @@
|
|
1
|
+
require 'usaidwat/algo'
|
2
|
+
|
3
|
+
module USaidWat
|
4
|
+
module Application
|
5
|
+
module CountCommand
|
6
|
+
def partition(entries, sort_by_count)
|
7
|
+
longest_subreddit = 0
|
8
|
+
buckets = Hash.new { |hash, key| hash[key] = 0 }
|
9
|
+
entries.each do |e|
|
10
|
+
subreddit = e.subreddit
|
11
|
+
longest_subreddit = subreddit.length if subreddit.length > longest_subreddit
|
12
|
+
buckets[subreddit] += 1
|
13
|
+
end
|
14
|
+
algo = algorithm(sort_by_count).new(buckets)
|
15
|
+
subreddits = buckets.keys.sort { |a,b| algo.sort(a, b) }
|
16
|
+
counts = subreddits.map { |s| buckets[s] }
|
17
|
+
subreddit_counts = subreddits.zip(counts)
|
18
|
+
partition_data = Struct.new(:longest, :counts)
|
19
|
+
partition_data.new(longest_subreddit, subreddit_counts)
|
20
|
+
end
|
21
|
+
|
22
|
+
def algorithm(sort_by_count)
|
23
|
+
sort_by_count ? USaidWat::Algorithms::CountAlgorithm : USaidWat::Algorithms::LexicographicalAlgorithm
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
module Mercenary
|
2
|
+
class Command
|
3
|
+
def logger(level = nil)
|
4
|
+
unless @logger
|
5
|
+
@logger = Logger.new(STDERR)
|
6
|
+
@logger.level = level || Logger::INFO
|
7
|
+
@logger.formatter = proc do |severity, datetime, progname, msg|
|
8
|
+
"#{msg}\n"
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
@logger.level = level unless level.nil?
|
13
|
+
@logger
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
module USaidWat
|
2
|
+
module Application
|
3
|
+
module FilterCommand
|
4
|
+
def filter_entries(noun, redditor, entries, subreddits)
|
5
|
+
return USaidWat::Right.new(entries) if subreddits.empty?
|
6
|
+
entries = entries.find_all { |e| subreddits.include?(e.subreddit.downcase) }
|
7
|
+
if entries.empty?
|
8
|
+
USaidWat::Left.new("No #{noun} by #{redditor.username} for #{subreddits.join(', ')}.")
|
9
|
+
else
|
10
|
+
USaidWat::Right.new(entries)
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
def grep_entries(noun, redditor, entries, grep)
|
15
|
+
return USaidWat::Right.new(entries) if grep.nil?
|
16
|
+
entries = entries.select { |e| e.body =~ /#{grep}/i }
|
17
|
+
if entries.empty?
|
18
|
+
msg = "#{redditor.username} has no #{noun} matching /#{grep}/."
|
19
|
+
USaidWat::Left.new(msg)
|
20
|
+
else
|
21
|
+
USaidWat::Right.new(entries)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def limit_entries(noun, redditor, entries, n)
|
26
|
+
return USaidWat::Right.new(entries) if n.nil?
|
27
|
+
entries = entries[0...n.to_i]
|
28
|
+
USaidWat::Right.new(entries)
|
29
|
+
end
|
30
|
+
|
31
|
+
def ensure_entries(noun, redditor, entries)
|
32
|
+
if entries.empty?
|
33
|
+
USaidWat::Left.new("#{redditor.username} has no #{noun}.")
|
34
|
+
else
|
35
|
+
USaidWat::Right.new(entries)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
data/lib/usaidwat/formatter.rb
CHANGED
@@ -4,6 +4,7 @@ require 'rainbow/ext/string'
|
|
4
4
|
require 'redcarpet'
|
5
5
|
require 'set'
|
6
6
|
require 'stringio'
|
7
|
+
require 'ttycaca'
|
7
8
|
require 'usaidwat/ext/string'
|
8
9
|
require 'usaidwat/ext/time'
|
9
10
|
|
@@ -36,7 +37,32 @@ module USaidWat
|
|
36
37
|
protected
|
37
38
|
|
38
39
|
def tty
|
39
|
-
@tty ||=
|
40
|
+
@tty ||= Ttycaca::Terminal.new
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
class PostFormatter < BaseFormatter
|
45
|
+
def format(post)
|
46
|
+
cols = tty.width
|
47
|
+
out = StringIO.new
|
48
|
+
out.write("\n\n\n") unless @count == 0
|
49
|
+
out.write("#{post.subreddit}\n".color(:green))
|
50
|
+
out.write("#{post_link(post)}\n".color(:yellow))
|
51
|
+
out.write("#{post.title.strip.truncate(cols)}\n".color(:magenta))
|
52
|
+
out.write("#{post_date(post)}".color(:blue))
|
53
|
+
@count += 1
|
54
|
+
out.rewind
|
55
|
+
out.read
|
56
|
+
end
|
57
|
+
|
58
|
+
private
|
59
|
+
|
60
|
+
def post_link(post)
|
61
|
+
"https://www.reddit.com#{post.permalink.split('/')[0..-2].join('/')}"
|
62
|
+
end
|
63
|
+
|
64
|
+
def post_date(post)
|
65
|
+
Time.at(post.created_utc).ago
|
40
66
|
end
|
41
67
|
end
|
42
68
|
|
@@ -127,5 +153,20 @@ module USaidWat
|
|
127
153
|
comments.include?(comment)
|
128
154
|
end
|
129
155
|
end
|
156
|
+
|
157
|
+
class TallyFormatter < BaseFormatter
|
158
|
+
def format(partition_data)
|
159
|
+
out = StringIO.new
|
160
|
+
longest_subreddit = partition_data.longest
|
161
|
+
subreddits = partition_data.counts
|
162
|
+
subreddits.each do |subreddit_count|
|
163
|
+
subreddit, tally = subreddit_count
|
164
|
+
line = sprintf("%-*s %3d\n", longest_subreddit, subreddit, tally)
|
165
|
+
out.write(line)
|
166
|
+
end
|
167
|
+
out.rewind
|
168
|
+
out.read
|
169
|
+
end
|
170
|
+
end
|
130
171
|
end
|
131
172
|
end
|