usaidwat 1.0.1 → 1.1.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.
- checksums.yaml +4 -4
- data/Rakefile +6 -1
- data/bin/usaidwat +17 -1
- data/features/browse.feature +150 -16
- data/features/help.feature +15 -3
- data/lib/usaidwat/application.rb +107 -61
- data/lib/usaidwat/ext/string.rb +6 -4
- data/lib/usaidwat/formatter.rb +52 -6
- data/lib/usaidwat/version.rb +1 -1
- data/spec/usaidwat/formatter_spec.rb +23 -0
- data/spec/usaidwat/string_spec.rb +38 -30
- data/usaidwat.gemspec +12 -3
- metadata +35 -20
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 04536acd69f58116dbc58388d79dea104407598c
|
4
|
+
data.tar.gz: acb24952c11f213b5677808eda93bfe65329a4b9
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 3c6b186e4d3f45c2697e4eb6e76b46187dfc9c1217757b3d3058af9319f9bb6b6910a17cd68abc29e7e2408b4c5966da308aa61150d77de93fa1ee5ab9a93de8
|
7
|
+
data.tar.gz: 85719e6d25ad91a891fd28478cf638a85ff7fd53600dc859f6f863451aef84f34424968ffe5064ea2c93e04eebac120ad96dfc27708378ab62906abccc22f1b4
|
data/Rakefile
CHANGED
@@ -6,10 +6,15 @@ GEMSPEC = `git ls-files | grep gemspec`.chomp
|
|
6
6
|
GEM = "usaidwat-#{USaidWat::VERSION}.gem"
|
7
7
|
|
8
8
|
desc "Build usaidwat.gem"
|
9
|
-
task :build do
|
9
|
+
task :build => :perms do
|
10
10
|
system "gem", "build", GEMSPEC
|
11
11
|
end
|
12
12
|
|
13
|
+
desc "Ensure correct permissions for usaidwat.gem"
|
14
|
+
task :perms do
|
15
|
+
system "chmod", "-R", "a+rX", *`git ls-files`.chomp.split("\n")
|
16
|
+
end
|
17
|
+
|
13
18
|
desc "Install usaidwat.gem"
|
14
19
|
task :install => :build do
|
15
20
|
system "gem", "install", GEM
|
data/bin/usaidwat
CHANGED
@@ -1,5 +1,21 @@
|
|
1
1
|
#!/usr/bin/env ruby
|
2
2
|
|
3
3
|
require 'usaidwat'
|
4
|
+
require 'mercenary'
|
4
5
|
|
5
|
-
|
6
|
+
Mercenary.program(:usaidwat) do |p|
|
7
|
+
p.version USaidWat::VERSION
|
8
|
+
p.description 'Answers the age-old question, "Where does a Redditor comment the most?"'
|
9
|
+
p.syntax 'usaidwat <command> [options] <args>'
|
10
|
+
|
11
|
+
USaidWat::Application::Command.subclasses.each { |c| c.new(p) }
|
12
|
+
|
13
|
+
p.action do |args, options|
|
14
|
+
if args.empty?
|
15
|
+
puts p.to_s
|
16
|
+
exit 0
|
17
|
+
else
|
18
|
+
abort "Invalid command: #{args.first}"
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
data/features/browse.feature
CHANGED
@@ -18,6 +18,36 @@ Feature: Browse comments
|
|
18
18
|
Yep. My first experience with a Heisenbug occurred in a C++ program, and disappeared when I tried to print a variable with printf (only to reappear when that call was removed).
|
19
19
|
|
20
20
|
|
21
|
+
nyc
|
22
|
+
http://www.reddit.com/r/nyc/comments/141zqc/z/c79dxg6
|
23
|
+
NYC taxi roof lights get overhaul - A light simply will mean the cab is availab
|
24
|
+
over 3 years ago
|
25
|
+
|
26
|
+
It has a fare when the lights are off.
|
27
|
+
|
28
|
+
|
29
|
+
worldnews
|
30
|
+
http://www.reddit.com/r/worldnews/comments/140mra/z/c797jq4
|
31
|
+
Palestinians win upgraded UN status by wide margin
|
32
|
+
over 3 years ago
|
33
|
+
|
34
|
+
The Judgment of Solomon Accords.
|
35
|
+
"""
|
36
|
+
|
37
|
+
Scenario: List all comments with comment bodies unformatted
|
38
|
+
Given the Reddit service returns comments for the user "mipadi"
|
39
|
+
And time is frozen at Jun 24, 2015 11:05 AM
|
40
|
+
When I run `usaidwat log --raw mipadi`
|
41
|
+
Then it should pass with:
|
42
|
+
"""
|
43
|
+
wikipedia
|
44
|
+
http://www.reddit.com/r/wikipedia/comments/142t4w/z/c79peed
|
45
|
+
Heisenbug: a software bug that seems to disappear or alter its behavior when one
|
46
|
+
over 3 years ago
|
47
|
+
|
48
|
+
Yep. My first experience with a Heisenbug occurred in a C++ program, and disappeared when I tried to print a variable with printf (only to reappear when that call was removed).
|
49
|
+
|
50
|
+
|
21
51
|
nyc
|
22
52
|
http://www.reddit.com/r/nyc/comments/141zqc/z/c79dxg6
|
23
53
|
NYC taxi roof lights get overhaul - A light simply will mean the cab is availab
|
@@ -34,6 +64,54 @@ Feature: Browse comments
|
|
34
64
|
The Judgment of Solomon Accords.
|
35
65
|
"""
|
36
66
|
|
67
|
+
Scenario: Output comments on line line
|
68
|
+
Given the Reddit service returns comments for the user "mipadi"
|
69
|
+
And time is frozen at Jun 24, 2015 11:05 AM
|
70
|
+
When I run `usaidwat log --oneline mipadi`
|
71
|
+
Then it should pass with:
|
72
|
+
"""
|
73
|
+
wikipedia Heisenbug: a software bug that seems to disappear or alter its behavio
|
74
|
+
nyc NYC taxi roof lights get overhaul - A light simply will mean the cab is ava
|
75
|
+
worldnews Palestinians win upgraded UN status by wide margin
|
76
|
+
"""
|
77
|
+
|
78
|
+
Scenario: Search for a specific comment
|
79
|
+
Given the Reddit service returns comments for the user "mipadi"
|
80
|
+
And time is frozen at Jun 24, 2015 11:05 AM
|
81
|
+
When I run `usaidwat log --grep='Heisenbug' mipadi`
|
82
|
+
Then it should pass with:
|
83
|
+
"""
|
84
|
+
wikipedia
|
85
|
+
http://www.reddit.com/r/wikipedia/comments/142t4w/z/c79peed
|
86
|
+
Heisenbug: a software bug that seems to disappear or alter its behavior when one
|
87
|
+
over 3 years ago
|
88
|
+
|
89
|
+
Yep. My first experience with a Heisenbug occurred in a C++ program, and disappeared when I tried to print a variable with printf (only to reappear when that call was removed).
|
90
|
+
"""
|
91
|
+
|
92
|
+
Scenario: Search for a specific comment with wrong case
|
93
|
+
Given the Reddit service returns comments for the user "mipadi"
|
94
|
+
And time is frozen at Jun 24, 2015 11:05 AM
|
95
|
+
When I run `usaidwat log --grep='heisenbug' mipadi`
|
96
|
+
Then it should pass with:
|
97
|
+
"""
|
98
|
+
wikipedia
|
99
|
+
http://www.reddit.com/r/wikipedia/comments/142t4w/z/c79peed
|
100
|
+
Heisenbug: a software bug that seems to disappear or alter its behavior when one
|
101
|
+
over 3 years ago
|
102
|
+
|
103
|
+
Yep. My first experience with a Heisenbug occurred in a C++ program, and disappeared when I tried to print a variable with printf (only to reappear when that call was removed).
|
104
|
+
"""
|
105
|
+
|
106
|
+
Scenario: Search for a specific comment with no matches
|
107
|
+
Given the Reddit service returns comments for the user "mipadi"
|
108
|
+
And time is frozen at Jun 24, 2015 11:05 AM
|
109
|
+
When I run `usaidwat log --grep='oogabooga' mipadi`
|
110
|
+
Then it should pass with:
|
111
|
+
"""
|
112
|
+
mipadi has no comments matching /oogabooga/.
|
113
|
+
"""
|
114
|
+
|
37
115
|
Scenario: List all comments for a user that does not exist
|
38
116
|
Given the Reddit service does not have a user "testuser"
|
39
117
|
When I run `usaidwat log testuser`
|
@@ -50,6 +128,22 @@ Feature: Browse comments
|
|
50
128
|
blank has no comments.
|
51
129
|
"""
|
52
130
|
|
131
|
+
Scenario: Search for a comment for a user that does not exist
|
132
|
+
Given the Reddit service does not have a user "testuser"
|
133
|
+
When I run `usaidwat log --grep='Heisenbug' testuser`
|
134
|
+
Then it should fail with:
|
135
|
+
"""
|
136
|
+
No such user: testuser
|
137
|
+
"""
|
138
|
+
|
139
|
+
Scenario: Search for a comment when user has no comments
|
140
|
+
Given the Reddit service returns comments for the user "blank"
|
141
|
+
When I run `usaidwat log --grep='Heisenbug' blank`
|
142
|
+
Then it should pass with:
|
143
|
+
"""
|
144
|
+
blank has no comments matching /Heisenbug/.
|
145
|
+
"""
|
146
|
+
|
53
147
|
Scenario: Tally comments
|
54
148
|
Given the Reddit service returns comments for the user "mipadi"
|
55
149
|
When I run `usaidwat tally mipadi`
|
@@ -106,6 +200,22 @@ Feature: Browse comments
|
|
106
200
|
blank has no comments.
|
107
201
|
"""
|
108
202
|
|
203
|
+
Scenario: Search for a comment when tallying
|
204
|
+
Given the Reddit service returns comments for the user "mipadi"
|
205
|
+
When I run `usaidwat tally --grep='Heisenbug' mipadi`
|
206
|
+
Then it should fail with:
|
207
|
+
"""
|
208
|
+
invalid option: --grep=Heisenbug
|
209
|
+
"""
|
210
|
+
|
211
|
+
Scenario: Search for a comment when sorting
|
212
|
+
Given the Reddit service returns comments for the user "mipadi"
|
213
|
+
When I run `usaidwat tally -c --grep='Heisenbug' mipadi`
|
214
|
+
Then it should fail with:
|
215
|
+
"""
|
216
|
+
invalid option: --grep=Heisenbug
|
217
|
+
"""
|
218
|
+
|
109
219
|
Scenario: Sort comments when a user does not exist
|
110
220
|
Given the Reddit service does not have a user "testuser"
|
111
221
|
When I run `usaidwat tally testuser`
|
@@ -144,6 +254,34 @@ Feature: Browse comments
|
|
144
254
|
You didn't slow down for very long though, did you?
|
145
255
|
"""
|
146
256
|
|
257
|
+
Scenario: Search in comments for a particular subreddit
|
258
|
+
Given the Reddit service returns comments for the user "mipadi"
|
259
|
+
And time is frozen at Jun 24, 2015 11:05 AM
|
260
|
+
When I run `usaidwat log --grep='New Jersey' mipadi AskReddit`
|
261
|
+
Then it should pass with:
|
262
|
+
"""
|
263
|
+
AskReddit
|
264
|
+
http://www.reddit.com/r/AskReddit/comments/140t5c/z/c795nw3
|
265
|
+
I'm from Tennessee and most of our jokes are geared toward Mississippi and Alaba
|
266
|
+
over 3 years ago
|
267
|
+
|
268
|
+
You're from New Jersey? Which exit?
|
269
|
+
"""
|
270
|
+
|
271
|
+
Scenario: Search in comments for a particular subreddit with wrong case
|
272
|
+
Given the Reddit service returns comments for the user "mipadi"
|
273
|
+
And time is frozen at Jun 24, 2015 11:05 AM
|
274
|
+
When I run `usaidwat log --grep='new jersey' mipadi AskReddit`
|
275
|
+
Then it should pass with:
|
276
|
+
"""
|
277
|
+
AskReddit
|
278
|
+
http://www.reddit.com/r/AskReddit/comments/140t5c/z/c795nw3
|
279
|
+
I'm from Tennessee and most of our jokes are geared toward Mississippi and Alaba
|
280
|
+
over 3 years ago
|
281
|
+
|
282
|
+
You're from New Jersey? Which exit?
|
283
|
+
"""
|
284
|
+
|
147
285
|
Scenario: List comments for a particular subreddit specified with the wrong case
|
148
286
|
Given the Reddit service returns comments for the user "mipadi"
|
149
287
|
And time is frozen at Jun 24, 2015 11:05 AM
|
@@ -187,8 +325,7 @@ Feature: Browse comments
|
|
187
325
|
When I run `usaidwat tally mipadi AskReddit`
|
188
326
|
Then it should fail with:
|
189
327
|
"""
|
190
|
-
|
191
|
-
Usage: "usaidwat tally USERNAME"
|
328
|
+
You cannot specify a subreddit when tallying comments
|
192
329
|
"""
|
193
330
|
|
194
331
|
Scenario: Sort comments with subreddit
|
@@ -196,16 +333,7 @@ Feature: Browse comments
|
|
196
333
|
When I run `usaidwat tally -c mipadi AskReddit`
|
197
334
|
Then it should fail with:
|
198
335
|
"""
|
199
|
-
|
200
|
-
Usage: "usaidwat tally USERNAME"
|
201
|
-
"""
|
202
|
-
|
203
|
-
Scenario: Pass no arguments
|
204
|
-
Given the Reddit service returns comments for the user "mipadi"
|
205
|
-
When I run `usaidwat`
|
206
|
-
Then it should pass with:
|
207
|
-
"""
|
208
|
-
Commands:
|
336
|
+
You cannot specify a subreddit when tallying comments
|
209
337
|
"""
|
210
338
|
|
211
339
|
Scenario: Pass no arguments when tallying
|
@@ -213,8 +341,7 @@ Feature: Browse comments
|
|
213
341
|
When I run `usaidwat tally`
|
214
342
|
Then it should fail with:
|
215
343
|
"""
|
216
|
-
|
217
|
-
Usage: "usaidwat tally USERNAME"
|
344
|
+
You must specify a username
|
218
345
|
"""
|
219
346
|
|
220
347
|
Scenario: Pass no arguments when sorting
|
@@ -222,6 +349,13 @@ Feature: Browse comments
|
|
222
349
|
When I run `usaidwat tally -c`
|
223
350
|
Then it should fail with:
|
224
351
|
"""
|
225
|
-
|
226
|
-
|
352
|
+
You must specify a username
|
353
|
+
"""
|
354
|
+
|
355
|
+
Scenario: Pass no arguments when searching
|
356
|
+
Given the Reddit service returns comments for the user "mipadi"
|
357
|
+
When I run `usaidwat log --grep mipadi`
|
358
|
+
Then it should fail with:
|
359
|
+
"""
|
360
|
+
You must specify a username
|
227
361
|
"""
|
data/features/help.feature
CHANGED
@@ -4,17 +4,29 @@ Feature: Get help
|
|
4
4
|
I want to be able to list help information for usaidwat
|
5
5
|
In order to learn how to use it
|
6
6
|
|
7
|
+
Scenario: Pass no arguments
|
8
|
+
Given the Reddit service returns comments for the user "mipadi"
|
9
|
+
When I run `usaidwat`
|
10
|
+
Then it should pass with:
|
11
|
+
"""
|
12
|
+
Usage:
|
13
|
+
|
14
|
+
usaidwat <command> [options] <args>
|
15
|
+
"""
|
16
|
+
|
7
17
|
Scenario: List usage details
|
8
18
|
When I run `usaidwat --help`
|
9
19
|
Then it should pass with:
|
10
20
|
"""
|
11
|
-
|
21
|
+
Usage:
|
22
|
+
|
23
|
+
usaidwat <command> [options] <args>
|
12
24
|
"""
|
13
25
|
|
14
26
|
Scenario: Get version
|
15
27
|
When I run `usaidwat --version`
|
16
28
|
Then the exit status should be 0
|
17
|
-
And the
|
29
|
+
And the output should match:
|
18
30
|
"""
|
19
|
-
usaidwat
|
31
|
+
usaidwat [0-9]+\.[0-9]+\.[0-9]+
|
20
32
|
"""
|
data/lib/usaidwat/application.rb
CHANGED
@@ -1,84 +1,130 @@
|
|
1
|
-
require '
|
2
|
-
require '
|
1
|
+
require 'usaidwat/algo'
|
2
|
+
require 'usaidwat/client'
|
3
3
|
require 'usaidwat/pager'
|
4
|
+
require 'sysexits'
|
4
5
|
|
5
6
|
module USaidWat
|
6
|
-
|
7
|
-
|
8
|
-
|
7
|
+
module Application
|
8
|
+
class Command
|
9
|
+
include Pager
|
10
|
+
include Sysexits
|
11
|
+
|
12
|
+
attr_reader :client
|
13
|
+
|
14
|
+
class << self
|
15
|
+
def subclasses
|
16
|
+
@subclasses ||= []
|
17
|
+
end
|
9
18
|
|
10
|
-
|
11
|
-
|
12
|
-
|
19
|
+
def inherited(base)
|
20
|
+
subclasses << base
|
21
|
+
super
|
22
|
+
end
|
13
23
|
end
|
14
24
|
|
15
|
-
def
|
16
|
-
|
25
|
+
def initialize(prog)
|
26
|
+
@client = cucumber? ? USaidWat::Client::TestRedditor : USaidWat::Client::Redditor
|
17
27
|
end
|
18
|
-
end
|
19
28
|
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
desc 'tally USERNAME', 'Count comments by subreddit'
|
25
|
-
option :count, :aliases => '-c', :type => :boolean, :desc => 'Sort output by number of comments'
|
26
|
-
def tally(username)
|
27
|
-
redditor = Application.client.new(username)
|
28
|
-
algo_cls = options[:count] ? USaidWat::Algorithms::CountAlgorithm : USaidWat::Algorithms::LexicographicalAlgorithm
|
29
|
-
quit "#{redditor.username} has no comments." if redditor.comments.empty?
|
30
|
-
# Unfortunately Snooby cannot return comments for a specific
|
31
|
-
# user in a specific subreddit, so for now we have to sort them
|
32
|
-
# ourself.
|
33
|
-
longest_subreddit = 0
|
34
|
-
buckets = Hash.new { |hash, key| hash[key] = 0 }
|
35
|
-
redditor.comments.each do |comment|
|
36
|
-
subreddit = comment.subreddit
|
37
|
-
longest_subreddit = subreddit.length if subreddit.length > longest_subreddit
|
38
|
-
buckets[subreddit] += 1
|
29
|
+
protected
|
30
|
+
|
31
|
+
def cucumber?
|
32
|
+
ENV['USAIDWAT_ENV'] == 'cucumber'
|
39
33
|
end
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
printf "%-*s %3d\n", longest_subreddit, subreddit, tally
|
34
|
+
|
35
|
+
def quit(message, code=:ok)
|
36
|
+
puts message
|
37
|
+
exit code
|
45
38
|
end
|
46
|
-
rescue USaidWat::Client::NoSuchUserError
|
47
|
-
quit "No such user: #{username}", :no_such_user
|
48
39
|
end
|
49
40
|
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
41
|
+
class Log < Command
|
42
|
+
def initialize(prog)
|
43
|
+
prog.command(:log) do |c|
|
44
|
+
c.alias :l
|
45
|
+
c.option 'grep', '--grep STRING', 'Show only comments matching STRING'
|
46
|
+
c.option 'oneline', '--oneline', 'Output log in a more comptact form'
|
47
|
+
c.option 'raw', '--raw', 'Print raw comment bodies'
|
48
|
+
|
49
|
+
c.action do |args, options|
|
50
|
+
process(options, args)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
super
|
54
|
+
end
|
55
|
+
|
56
|
+
def process(options, args)
|
57
|
+
raise ArgumentError.new('You must specify a username') if args.empty?
|
58
|
+
username = args.shift
|
59
|
+
subreddit = args.shift
|
60
|
+
|
61
|
+
redditor = client.new(username)
|
54
62
|
comments = redditor.comments
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
63
|
+
if subreddit
|
64
|
+
comments = comments.group_by { |c| c.subreddit.downcase }
|
65
|
+
comments = comments[subreddit.downcase]
|
66
|
+
quit "No comments by #{redditor.username} for #{subreddit}." if comments.nil?
|
67
|
+
end
|
68
|
+
comments = comments.select { |c| c.body =~ /#{options['grep']}/i } if options['grep']
|
69
|
+
if comments.empty?
|
70
|
+
msg = "#{redditor.username} has no comments"
|
71
|
+
msg = "#{msg} matching /#{options['grep']}/" if options['grep']
|
72
|
+
msg = "#{msg}."
|
73
|
+
quit msg
|
74
|
+
end
|
75
|
+
list_comments(comments, options['grep'], !options['oneline'].nil?, !options['raw'].nil?)
|
76
|
+
rescue USaidWat::Client::NoSuchUserError
|
77
|
+
quit "No such user: #{username}", :no_such_user
|
62
78
|
end
|
63
|
-
rescue USaidWat::Client::NoSuchUserError
|
64
|
-
quit "No such user: #{username}", :no_such_user
|
65
|
-
end
|
66
79
|
|
67
|
-
|
68
|
-
def version
|
69
|
-
puts "usaidwat v#{USaidWat::VERSION}"
|
70
|
-
end
|
80
|
+
private
|
71
81
|
|
72
|
-
|
73
|
-
|
74
|
-
formatter = USaidWat::CLI::CommentFormatter.new
|
82
|
+
def list_comments(comments, pattern = nil, oneline = false, raw = false)
|
83
|
+
formatter = (oneline ? USaidWat::CLI::CompactCommentFormatter : USaidWat::CLI::CommentFormatter).new(pattern, raw)
|
75
84
|
page
|
76
85
|
comments.each { |c| print formatter.format(c) }
|
77
86
|
end
|
87
|
+
end
|
78
88
|
|
79
|
-
|
80
|
-
|
81
|
-
|
89
|
+
class Tally < Command
|
90
|
+
def initialize(prog)
|
91
|
+
prog.command(:tally) do |c|
|
92
|
+
c.alias :t
|
93
|
+
c.option 'count', '-c', '--count', 'Sort output by number of comments'
|
94
|
+
|
95
|
+
c.action do |args, options|
|
96
|
+
process(options, args)
|
97
|
+
end
|
98
|
+
end
|
99
|
+
super
|
100
|
+
end
|
101
|
+
|
102
|
+
def process(options, args)
|
103
|
+
raise ArgumentError.new('You must specify a username') if args.empty?
|
104
|
+
raise ArgumentError.new('You cannot specify a subreddit when tallying comments') if args.count > 1
|
105
|
+
username = args.first
|
106
|
+
|
107
|
+
redditor = client.new(username)
|
108
|
+
algo_cls = options['count'] ? USaidWat::Algorithms::CountAlgorithm : USaidWat::Algorithms::LexicographicalAlgorithm
|
109
|
+
quit "#{redditor.username} has no comments." if redditor.comments.empty?
|
110
|
+
# Unfortunately Snooby cannot return comments for a specific
|
111
|
+
# user in a specific subreddit, so for now we have to sort them
|
112
|
+
# ourself.
|
113
|
+
longest_subreddit = 0
|
114
|
+
buckets = Hash.new { |hash, key| hash[key] = 0 }
|
115
|
+
redditor.comments.each do |comment|
|
116
|
+
subreddit = comment.subreddit
|
117
|
+
longest_subreddit = subreddit.length if subreddit.length > longest_subreddit
|
118
|
+
buckets[subreddit] += 1
|
119
|
+
end
|
120
|
+
algo = algo_cls.new(buckets)
|
121
|
+
subreddits = buckets.keys.sort { |a,b| algo.sort(a, b) }
|
122
|
+
subreddits.each do |subreddit|
|
123
|
+
tally = buckets[subreddit]
|
124
|
+
printf "%-*s %3d\n", longest_subreddit, subreddit, tally
|
125
|
+
end
|
126
|
+
rescue USaidWat::Client::NoSuchUserError
|
127
|
+
quit "No such user: #{username}", :no_such_user
|
82
128
|
end
|
83
129
|
end
|
84
130
|
end
|
data/lib/usaidwat/ext/string.rb
CHANGED
@@ -1,12 +1,10 @@
|
|
1
|
+
require 'rainbow'
|
2
|
+
|
1
3
|
class String
|
2
4
|
def truncate(width)
|
3
5
|
self[0, width]
|
4
6
|
end
|
5
7
|
|
6
|
-
def convert_entities
|
7
|
-
self.gsub(/>/, '>').gsub(/</, '<').gsub(/&/, '&')
|
8
|
-
end
|
9
|
-
|
10
8
|
def pluralize(n, suffix = 's', singular_suffix = '')
|
11
9
|
if n == 1
|
12
10
|
self + singular_suffix
|
@@ -14,4 +12,8 @@ class String
|
|
14
12
|
self + suffix
|
15
13
|
end
|
16
14
|
end
|
15
|
+
|
16
|
+
def highlight(pattern)
|
17
|
+
gsub(/#{pattern}/i) { |m| Rainbow(m).red }
|
18
|
+
end
|
17
19
|
end
|
data/lib/usaidwat/formatter.rb
CHANGED
@@ -1,7 +1,9 @@
|
|
1
1
|
require 'date'
|
2
|
+
require 'downterm'
|
2
3
|
require 'highline'
|
3
|
-
require 'stringio'
|
4
4
|
require 'rainbow/ext/string'
|
5
|
+
require 'redcarpet'
|
6
|
+
require 'stringio'
|
5
7
|
require 'usaidwat/ext/string'
|
6
8
|
require 'usaidwat/ext/time'
|
7
9
|
|
@@ -9,27 +11,58 @@ Rainbow.enabled = true unless ENV['USAIDWAT_ENV'] == 'cucumber'
|
|
9
11
|
|
10
12
|
module USaidWat
|
11
13
|
module CLI
|
12
|
-
class
|
13
|
-
|
14
|
+
class BaseFormatter
|
15
|
+
attr_reader :pattern
|
16
|
+
|
17
|
+
def initialize(pattern = nil, raw = false)
|
18
|
+
@pattern = pattern
|
19
|
+
@raw = raw
|
14
20
|
@count = 0
|
15
21
|
end
|
16
|
-
|
22
|
+
|
23
|
+
def pattern?
|
24
|
+
!@pattern.nil?
|
25
|
+
end
|
26
|
+
|
27
|
+
def raw?
|
28
|
+
@raw
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
class CommentFormatter < BaseFormatter
|
33
|
+
def initialize(pattern = nil, raw = false)
|
34
|
+
@markdown = Redcarpet::Markdown.new(Downterm::Render::Terminal, :autolink => true,
|
35
|
+
:strikethrough => true,
|
36
|
+
:superscript => true)
|
37
|
+
super
|
38
|
+
end
|
39
|
+
|
17
40
|
def format(comment)
|
18
41
|
cols = HighLine::SystemExtensions.terminal_size[0]
|
19
42
|
out = StringIO.new
|
20
43
|
out.write("\n\n") unless @count == 0
|
21
44
|
out.write("#{comment.subreddit}\n".color(:green))
|
22
45
|
out.write("#{comment_link(comment)}\n".color(:yellow))
|
23
|
-
out.write("#{comment.link_title.strip.truncate(cols)}\n".color(:
|
46
|
+
out.write("#{comment.link_title.strip.truncate(cols)}\n".color(:magenta))
|
24
47
|
out.write("#{comment_date(comment)}\n".color(:blue))
|
25
48
|
out.write("\n")
|
26
|
-
out.write("#{comment
|
49
|
+
out.write("#{comment_body(comment)}\n")
|
27
50
|
@count += 1
|
28
51
|
out.rewind
|
29
52
|
out.read
|
30
53
|
end
|
31
54
|
|
32
55
|
private
|
56
|
+
def comment_body(comment)
|
57
|
+
body = comment.body
|
58
|
+
body = @markdown.render(body) unless raw?
|
59
|
+
if pattern?
|
60
|
+
body.highlight(pattern)
|
61
|
+
else
|
62
|
+
body
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
33
66
|
def comment_link(comment)
|
34
67
|
link = comment.link_id.split("_")[-1]
|
35
68
|
"http://www.reddit.com/r/#{comment.subreddit}/comments/#{link}/z/#{comment.id}"
|
@@ -39,5 +72,18 @@ module USaidWat
|
|
39
72
|
DateTime.strptime(comment.created_utc.to_s, "%s").to_time.localtime.ago
|
40
73
|
end
|
41
74
|
end
|
75
|
+
|
76
|
+
class CompactCommentFormatter < BaseFormatter
|
77
|
+
def format(comment)
|
78
|
+
cols = HighLine::SystemExtensions.terminal_size[0]
|
79
|
+
out = StringIO.new
|
80
|
+
subreddit = comment.subreddit
|
81
|
+
cols -= subreddit.length + 1
|
82
|
+
out.write("#{subreddit}".color(:green))
|
83
|
+
out.write(" #{comment.link_title.strip.truncate(cols)}\n")
|
84
|
+
out.rewind
|
85
|
+
out.read
|
86
|
+
end
|
87
|
+
end
|
42
88
|
end
|
43
89
|
end
|
data/lib/usaidwat/version.rb
CHANGED
@@ -72,5 +72,28 @@ EXPECTED
|
|
72
72
|
end
|
73
73
|
end
|
74
74
|
end
|
75
|
+
|
76
|
+
describe CompactCommentFormatter do
|
77
|
+
let (:formatter) { CompactCommentFormatter.new }
|
78
|
+
|
79
|
+
before do
|
80
|
+
Timecop.freeze(Time.new(2015, 6, 16, 17, 8))
|
81
|
+
end
|
82
|
+
|
83
|
+
after do
|
84
|
+
Timecop.return
|
85
|
+
end
|
86
|
+
|
87
|
+
describe "#format" do
|
88
|
+
it "should return a string containing the formatted comment" do
|
89
|
+
comment = double("comment")
|
90
|
+
expect(comment).to receive(:subreddit).and_return("programming")
|
91
|
+
expect(comment).to receive(:link_title).and_return("Why Brit Ruby 2013 was cancelled and why this is not ok - Gist")
|
92
|
+
expected = "programming Why Brit Ruby 2013 was cancelled and why this is not ok - Gist\n"
|
93
|
+
actual = formatter.format(comment).delete_ansi_color_codes
|
94
|
+
expect(actual).to eq(expected)
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
75
98
|
end
|
76
99
|
end
|
@@ -1,38 +1,9 @@
|
|
1
|
+
require 'rainbow'
|
1
2
|
require 'spec_helper'
|
2
3
|
|
3
4
|
module USaidWat
|
4
5
|
module Ext
|
5
6
|
describe String do
|
6
|
-
describe "#convert_entities" do
|
7
|
-
it "converts > to >" do
|
8
|
-
s = "-> look at this!"
|
9
|
-
expected = "-> look at this!"
|
10
|
-
actual = s.convert_entities
|
11
|
-
expect(actual).to eq(expected)
|
12
|
-
end
|
13
|
-
|
14
|
-
it "converts < to <" do
|
15
|
-
s = "left << shift!"
|
16
|
-
expected = "left << shift!"
|
17
|
-
actual = s.convert_entities
|
18
|
-
expect(actual).to eq(expected)
|
19
|
-
end
|
20
|
-
|
21
|
-
it "converts both > and < to > and <" do
|
22
|
-
s = "look -> this string has both <- awesome, huh?"
|
23
|
-
expected = "look -> this string has both <- awesome, huh?"
|
24
|
-
actual = s.convert_entities
|
25
|
-
expect(actual).to eq(expected)
|
26
|
-
end
|
27
|
-
|
28
|
-
it "converts & to &" do
|
29
|
-
s = "i like cake & ice cream"
|
30
|
-
expected = "i like cake & ice cream"
|
31
|
-
actual = s.convert_entities
|
32
|
-
expect(actual).to eq(expected)
|
33
|
-
end
|
34
|
-
end
|
35
|
-
|
36
7
|
describe "#truncate" do
|
37
8
|
it "truncates strings longer than the given width to the width" do
|
38
9
|
n = 80
|
@@ -74,6 +45,43 @@ module USaidWat
|
|
74
45
|
expect(s.pluralize(1, "ies", "y")).to eq("poppy")
|
75
46
|
end
|
76
47
|
end
|
48
|
+
|
49
|
+
describe "#highlight" do
|
50
|
+
it "should highlight matching parts of strings" do
|
51
|
+
s = "apple pie is made from apples"
|
52
|
+
expected = "#{Rainbow("apple").red} pie is made from #{Rainbow("apple").red}s"
|
53
|
+
actual = s.highlight("apple")
|
54
|
+
expect(actual).to eq(expected)
|
55
|
+
end
|
56
|
+
|
57
|
+
it "should return an identical string if no matches are found" do
|
58
|
+
s = "apple pie is made from apples"
|
59
|
+
expected = s.dup
|
60
|
+
actual = s.highlight("cherry")
|
61
|
+
expect(actual).to eq(expected)
|
62
|
+
end
|
63
|
+
|
64
|
+
it "should match parts insensitive to case" do
|
65
|
+
s = "Apple pie is made from Apples"
|
66
|
+
expected = "#{Rainbow("Apple").red} pie is made from #{Rainbow("Apple").red}s"
|
67
|
+
actual = s.highlight("apple")
|
68
|
+
expect(actual).to eq(expected)
|
69
|
+
end
|
70
|
+
|
71
|
+
it "should highlight matching parts of strings using a regex-like string" do
|
72
|
+
s = "pears are tastier than bears"
|
73
|
+
expected = "#{Rainbow("pears").red} are tastier than #{Rainbow("bears").red}"
|
74
|
+
actual = s.highlight("[b|p]ears")
|
75
|
+
expect(actual).to eq(expected)
|
76
|
+
end
|
77
|
+
|
78
|
+
it "should highlight matching parts of strings using an actual regex" do
|
79
|
+
s = "pears are tastier than bears"
|
80
|
+
expected = "#{Rainbow("pears").red} are tastier than #{Rainbow("bears").red}"
|
81
|
+
actual = s.highlight(/[b|p]ears/)
|
82
|
+
expect(actual).to eq(expected)
|
83
|
+
end
|
84
|
+
end
|
77
85
|
end
|
78
86
|
end
|
79
87
|
end
|
data/usaidwat.gemspec
CHANGED
@@ -1,6 +1,8 @@
|
|
1
1
|
# -*- encoding: utf-8 -*-
|
2
|
+
|
2
3
|
lib = File.expand_path('../lib', __FILE__)
|
3
4
|
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
5
|
+
|
4
6
|
require 'usaidwat/version'
|
5
7
|
|
6
8
|
Gem::Specification.new do |gem|
|
@@ -13,20 +15,27 @@ Gem::Specification.new do |gem|
|
|
13
15
|
gem.description = %q{View a user's last 100 Reddit comments, organized by subreddit.}
|
14
16
|
gem.summary = %q{Answers the age-old question, "Where does a Redditor comment the most?"}
|
15
17
|
|
18
|
+
gem.metadata = {
|
19
|
+
'build_date' => Time.now.strftime("%Y-%m-%d %H:%M:%S.%L %Z"),
|
20
|
+
}
|
21
|
+
|
16
22
|
gem.files = `git ls-files`.split($/)
|
17
23
|
gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
|
18
24
|
gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
|
19
25
|
gem.require_paths = ["lib"]
|
20
26
|
|
27
|
+
gem.required_ruby_version = '>= 1.9.3'
|
28
|
+
|
29
|
+
gem.add_runtime_dependency('downterm', '~> 0.1.0')
|
21
30
|
gem.add_runtime_dependency('highline', '~> 1.7')
|
31
|
+
gem.add_runtime_dependency('mercenary', '~> 0.3.5')
|
22
32
|
gem.add_runtime_dependency('rainbow', '~> 2.0')
|
23
33
|
gem.add_runtime_dependency('snooby', '~> 0.1.5')
|
24
34
|
gem.add_runtime_dependency('sysexits', '~> 1.2')
|
25
|
-
gem.add_runtime_dependency('thor', '~> 0.19.1')
|
26
35
|
|
27
|
-
gem.add_development_dependency('aruba', '~> 0.
|
36
|
+
gem.add_development_dependency('aruba', '~> 0.9.0')
|
28
37
|
gem.add_development_dependency('cucumber', '~> 2.0')
|
29
38
|
gem.add_development_dependency('rspec', '~> 3.2')
|
30
|
-
gem.add_development_dependency('timecop', '~> 0.
|
39
|
+
gem.add_development_dependency('timecop', '~> 0.8.0')
|
31
40
|
gem.add_development_dependency('webmock', '~> 1.21')
|
32
41
|
end
|
metadata
CHANGED
@@ -1,15 +1,29 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: usaidwat
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.0
|
4
|
+
version: 1.1.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Michael Dippery
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2015-08-
|
11
|
+
date: 2015-08-28 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: downterm
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: 0.1.0
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: 0.1.0
|
13
27
|
- !ruby/object:Gem::Dependency
|
14
28
|
name: highline
|
15
29
|
requirement: !ruby/object:Gem::Requirement
|
@@ -25,75 +39,75 @@ dependencies:
|
|
25
39
|
- !ruby/object:Gem::Version
|
26
40
|
version: '1.7'
|
27
41
|
- !ruby/object:Gem::Dependency
|
28
|
-
name:
|
42
|
+
name: mercenary
|
29
43
|
requirement: !ruby/object:Gem::Requirement
|
30
44
|
requirements:
|
31
45
|
- - "~>"
|
32
46
|
- !ruby/object:Gem::Version
|
33
|
-
version:
|
47
|
+
version: 0.3.5
|
34
48
|
type: :runtime
|
35
49
|
prerelease: false
|
36
50
|
version_requirements: !ruby/object:Gem::Requirement
|
37
51
|
requirements:
|
38
52
|
- - "~>"
|
39
53
|
- !ruby/object:Gem::Version
|
40
|
-
version:
|
54
|
+
version: 0.3.5
|
41
55
|
- !ruby/object:Gem::Dependency
|
42
|
-
name:
|
56
|
+
name: rainbow
|
43
57
|
requirement: !ruby/object:Gem::Requirement
|
44
58
|
requirements:
|
45
59
|
- - "~>"
|
46
60
|
- !ruby/object:Gem::Version
|
47
|
-
version: 0
|
61
|
+
version: '2.0'
|
48
62
|
type: :runtime
|
49
63
|
prerelease: false
|
50
64
|
version_requirements: !ruby/object:Gem::Requirement
|
51
65
|
requirements:
|
52
66
|
- - "~>"
|
53
67
|
- !ruby/object:Gem::Version
|
54
|
-
version: 0
|
68
|
+
version: '2.0'
|
55
69
|
- !ruby/object:Gem::Dependency
|
56
|
-
name:
|
70
|
+
name: snooby
|
57
71
|
requirement: !ruby/object:Gem::Requirement
|
58
72
|
requirements:
|
59
73
|
- - "~>"
|
60
74
|
- !ruby/object:Gem::Version
|
61
|
-
version:
|
75
|
+
version: 0.1.5
|
62
76
|
type: :runtime
|
63
77
|
prerelease: false
|
64
78
|
version_requirements: !ruby/object:Gem::Requirement
|
65
79
|
requirements:
|
66
80
|
- - "~>"
|
67
81
|
- !ruby/object:Gem::Version
|
68
|
-
version:
|
82
|
+
version: 0.1.5
|
69
83
|
- !ruby/object:Gem::Dependency
|
70
|
-
name:
|
84
|
+
name: sysexits
|
71
85
|
requirement: !ruby/object:Gem::Requirement
|
72
86
|
requirements:
|
73
87
|
- - "~>"
|
74
88
|
- !ruby/object:Gem::Version
|
75
|
-
version:
|
89
|
+
version: '1.2'
|
76
90
|
type: :runtime
|
77
91
|
prerelease: false
|
78
92
|
version_requirements: !ruby/object:Gem::Requirement
|
79
93
|
requirements:
|
80
94
|
- - "~>"
|
81
95
|
- !ruby/object:Gem::Version
|
82
|
-
version:
|
96
|
+
version: '1.2'
|
83
97
|
- !ruby/object:Gem::Dependency
|
84
98
|
name: aruba
|
85
99
|
requirement: !ruby/object:Gem::Requirement
|
86
100
|
requirements:
|
87
101
|
- - "~>"
|
88
102
|
- !ruby/object:Gem::Version
|
89
|
-
version: 0.
|
103
|
+
version: 0.9.0
|
90
104
|
type: :development
|
91
105
|
prerelease: false
|
92
106
|
version_requirements: !ruby/object:Gem::Requirement
|
93
107
|
requirements:
|
94
108
|
- - "~>"
|
95
109
|
- !ruby/object:Gem::Version
|
96
|
-
version: 0.
|
110
|
+
version: 0.9.0
|
97
111
|
- !ruby/object:Gem::Dependency
|
98
112
|
name: cucumber
|
99
113
|
requirement: !ruby/object:Gem::Requirement
|
@@ -128,14 +142,14 @@ dependencies:
|
|
128
142
|
requirements:
|
129
143
|
- - "~>"
|
130
144
|
- !ruby/object:Gem::Version
|
131
|
-
version: 0.
|
145
|
+
version: 0.8.0
|
132
146
|
type: :development
|
133
147
|
prerelease: false
|
134
148
|
version_requirements: !ruby/object:Gem::Requirement
|
135
149
|
requirements:
|
136
150
|
- - "~>"
|
137
151
|
- !ruby/object:Gem::Version
|
138
|
-
version: 0.
|
152
|
+
version: 0.8.0
|
139
153
|
- !ruby/object:Gem::Dependency
|
140
154
|
name: webmock
|
141
155
|
requirement: !ruby/object:Gem::Requirement
|
@@ -194,7 +208,8 @@ files:
|
|
194
208
|
homepage: https://github.com/mdippery/usaidwat
|
195
209
|
licenses:
|
196
210
|
- MIT
|
197
|
-
metadata:
|
211
|
+
metadata:
|
212
|
+
build_date: 2015-08-27 18:54:51.726 PDT
|
198
213
|
post_install_message:
|
199
214
|
rdoc_options: []
|
200
215
|
require_paths:
|
@@ -203,7 +218,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
203
218
|
requirements:
|
204
219
|
- - ">="
|
205
220
|
- !ruby/object:Gem::Version
|
206
|
-
version:
|
221
|
+
version: 1.9.3
|
207
222
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
208
223
|
requirements:
|
209
224
|
- - ">="
|