usaidwat 1.4.5 → 1.5.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/Rakefile +7 -0
- data/bin/usaidwat +3 -1
- data/features/timeline.feature +46 -0
- data/lib/usaidwat.rb +1 -0
- data/lib/usaidwat/client.rb +15 -15
- data/lib/usaidwat/commands/timeline.rb +42 -0
- data/lib/usaidwat/formatter.rb +5 -185
- data/lib/usaidwat/formatter/base.rb +39 -0
- data/lib/usaidwat/formatter/comment.rb +92 -0
- data/lib/usaidwat/formatter/count.rb +20 -0
- data/lib/usaidwat/formatter/post.rb +43 -0
- data/lib/usaidwat/formatter/timeline.rb +32 -0
- data/lib/usaidwat/service.rb +30 -46
- data/lib/usaidwat/thing.rb +85 -0
- data/lib/usaidwat/version.rb +6 -1
- data/spec/usaidwat/client_spec.rb +8 -8
- data/spec/usaidwat/formatter_spec.rb +38 -10
- data/spec/usaidwat/service_spec.rb +4 -4
- data/spec/usaidwat/thing_spec.rb +139 -0
- data/spec/usaidwat/version_spec.rb +16 -0
- data/usaidwat.gemspec +1 -2
- metadata +21 -23
- data/lib/usaidwat/ext/snooby.rb +0 -32
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: f691290d3941259d0b2cfdfaa0ac6f256df6bfba
|
4
|
+
data.tar.gz: 4e943e27e1d55d4c3fb08b10c1d8298b24303312
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 568babcb320e2cbddbd18ee4bfb1a3f8a6f33e3a014abe65992d1bfebad0fe5fb9d4199e2df85d8054da83ee36fc80c49087d7d0825ea72cc0a5fc3c2730ef35
|
7
|
+
data.tar.gz: b8532c9d3210713253e8f9a7fd9cf9f5b01bd37babbac633c16454b40bdef0290cdfec2840620acae013958d380e8a46efd512e107801dccc0e67d06a4429783
|
data/Rakefile
CHANGED
@@ -31,6 +31,13 @@ task :release => [:tag, :build] do
|
|
31
31
|
system "gem", "push", GEM
|
32
32
|
end
|
33
33
|
|
34
|
+
desc "Run the test suite"
|
35
|
+
task :test do
|
36
|
+
ok = system "bundle", "exec", "rspec"
|
37
|
+
fail if !ok
|
38
|
+
system "bundle", "exec", "cucumber", "-f", "progress"
|
39
|
+
end
|
40
|
+
|
34
41
|
desc "Clean built products"
|
35
42
|
task :clean do
|
36
43
|
rm Dir.glob("*.gem"), :verbose => true
|
data/bin/usaidwat
CHANGED
@@ -5,7 +5,9 @@ require 'usaidwat'
|
|
5
5
|
require 'usaidwat/ext/mercenary'
|
6
6
|
|
7
7
|
Mercenary.program(:usaidwat) do |p|
|
8
|
-
|
8
|
+
version = USaidWat::VERSION
|
9
|
+
version += " (#{USaidWat::commit_hash})" if USaidWat::VERSION.end_with?('.dev')
|
10
|
+
p.version version
|
9
11
|
p.description 'Answers the age-old question, "Where does a Redditor comment the most?"'
|
10
12
|
p.syntax 'usaidwat <command> [options] <args>'
|
11
13
|
|
@@ -0,0 +1,46 @@
|
|
1
|
+
Feature: Show timeline of comments
|
2
|
+
|
3
|
+
As a Redditor
|
4
|
+
I want to visualize when another Redditor comments most often
|
5
|
+
In order to see what time they like to comment
|
6
|
+
|
7
|
+
Scenario: Show timeline data for user
|
8
|
+
Given the Reddit service returns comments for the user "mipadi"
|
9
|
+
When I run `usaidwat timeline mipadi`
|
10
|
+
Then it should pass with:
|
11
|
+
"""
|
12
|
+
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
|
13
|
+
S * * *
|
14
|
+
M * * * * * * * * *
|
15
|
+
T * * * * * * * * *
|
16
|
+
W * * * * * *
|
17
|
+
T * * * * * * * *
|
18
|
+
F * * * * * * *
|
19
|
+
S * *
|
20
|
+
"""
|
21
|
+
|
22
|
+
Scenario: Show timeline data for a user with no comments
|
23
|
+
Given the Reddit service returns comments for the user "blank"
|
24
|
+
When I run `usaidwat timeline blank`
|
25
|
+
Then it should pass with:
|
26
|
+
"""
|
27
|
+
blank has no comments.
|
28
|
+
"""
|
29
|
+
|
30
|
+
Scenario: Show timeline data without specifying a user
|
31
|
+
Given the Reddit service returns comments for the user "mipadi"
|
32
|
+
When I run `usaidwat timeline testuser`
|
33
|
+
Then the exit status should not be 0
|
34
|
+
And stderr should contain exactly:
|
35
|
+
"""
|
36
|
+
No such user: testuser
|
37
|
+
"""
|
38
|
+
|
39
|
+
Scenario: Show timeline without specifying a user
|
40
|
+
Given the Reddit service returns comments for the user "mipadi"
|
41
|
+
When I run `usaidwat timeline`
|
42
|
+
Then the exit status should not be 0
|
43
|
+
And stderr should contain exactly:
|
44
|
+
"""
|
45
|
+
You must specify a username
|
46
|
+
"""
|
data/lib/usaidwat.rb
CHANGED
data/lib/usaidwat/client.rb
CHANGED
@@ -1,11 +1,13 @@
|
|
1
|
-
require 'snooby'
|
2
1
|
require 'usaidwat/service'
|
3
|
-
require 'usaidwat/ext/snooby'
|
4
2
|
require 'usaidwat/ext/time'
|
5
3
|
|
6
4
|
module USaidWat
|
7
5
|
module Client
|
8
|
-
class ReachabilityError < RuntimeError
|
6
|
+
class ReachabilityError < RuntimeError
|
7
|
+
def initialize(msg = nil)
|
8
|
+
super(msg || 'Reddit unreachable')
|
9
|
+
end
|
10
|
+
end
|
9
11
|
|
10
12
|
class NoSuchUserError < StandardError; end
|
11
13
|
|
@@ -20,20 +22,20 @@ module USaidWat
|
|
20
22
|
user.comments(100)
|
21
23
|
rescue NoMethodError
|
22
24
|
raise NoSuchUserError, username
|
23
|
-
rescue
|
24
|
-
raise ReachabilityError
|
25
|
+
rescue RuntimeError
|
26
|
+
raise ReachabilityError
|
25
27
|
end
|
26
28
|
|
27
29
|
def link_karma
|
28
|
-
about
|
30
|
+
about.link_karma
|
29
31
|
end
|
30
32
|
|
31
33
|
def comment_karma
|
32
|
-
about
|
34
|
+
about.comment_karma
|
33
35
|
end
|
34
36
|
|
35
37
|
def created_at
|
36
|
-
|
38
|
+
about.created_utc
|
37
39
|
end
|
38
40
|
|
39
41
|
def age
|
@@ -48,8 +50,8 @@ module USaidWat
|
|
48
50
|
user.posts
|
49
51
|
rescue NoMethodError
|
50
52
|
raise NoSuchUserError, username
|
51
|
-
rescue
|
52
|
-
raise ReachabilityError
|
53
|
+
rescue RuntimeError
|
54
|
+
raise ReachabilityError
|
53
55
|
end
|
54
56
|
|
55
57
|
private
|
@@ -58,18 +60,16 @@ module USaidWat
|
|
58
60
|
@service.user(username)
|
59
61
|
end
|
60
62
|
|
61
|
-
def about
|
62
|
-
user.about
|
63
|
+
def about
|
64
|
+
user.about
|
63
65
|
rescue NoMethodError
|
64
66
|
raise NoSuchUserError, username
|
65
|
-
rescue TypeError, Net::HTTP::Persistent::Error
|
66
|
-
raise ReachabilityError, "Reddit unreachable"
|
67
67
|
end
|
68
68
|
end
|
69
69
|
|
70
70
|
class Redditor < BaseRedditor
|
71
71
|
def initialize(username)
|
72
|
-
@service =
|
72
|
+
@service = USaidWat::Service::RedditService.new
|
73
73
|
super
|
74
74
|
end
|
75
75
|
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
module USaidWat
|
2
|
+
module Application
|
3
|
+
class Timeline < Command
|
4
|
+
def initialize(prog)
|
5
|
+
prog.command(:timeline) do |c|
|
6
|
+
c.action do |args, options|
|
7
|
+
process(options, args)
|
8
|
+
end
|
9
|
+
end
|
10
|
+
super
|
11
|
+
end
|
12
|
+
|
13
|
+
def process(options, args)
|
14
|
+
raise ArgumentError.new('You must specify a username') if args.empty?
|
15
|
+
username = args.shift
|
16
|
+
|
17
|
+
redditor = client.new(username)
|
18
|
+
comments = redditor.comments
|
19
|
+
|
20
|
+
quit "#{username} has no comments." if comments.empty?
|
21
|
+
|
22
|
+
buckets = comments_by_days_and_hours(comments)
|
23
|
+
times = Array.new(7) { Array.new(24, 0) }
|
24
|
+
buckets.each do |v|
|
25
|
+
d, h = v
|
26
|
+
times[d][h] += 1
|
27
|
+
end
|
28
|
+
|
29
|
+
formatter = USaidWat::CLI::TimelineFormatter.new
|
30
|
+
puts formatter.format(times)
|
31
|
+
rescue USaidWat::Client::NoSuchUserError
|
32
|
+
quit "No such user: #{username}", :no_such_user
|
33
|
+
end
|
34
|
+
|
35
|
+
private
|
36
|
+
|
37
|
+
def comments_by_days_and_hours(comments)
|
38
|
+
comments.map { |c| [c.created_utc.wday, c.created_utc.hour] }
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
data/lib/usaidwat/formatter.rb
CHANGED
@@ -1,187 +1,7 @@
|
|
1
|
-
require '
|
2
|
-
require '
|
3
|
-
require '
|
4
|
-
require '
|
5
|
-
require '
|
6
|
-
require 'stringio'
|
7
|
-
require 'ttycaca'
|
8
|
-
require 'usaidwat/ext/string'
|
9
|
-
require 'usaidwat/ext/time'
|
1
|
+
require 'usaidwat/formatter/base'
|
2
|
+
require 'usaidwat/formatter/comment'
|
3
|
+
require 'usaidwat/formatter/count'
|
4
|
+
require 'usaidwat/formatter/post'
|
5
|
+
require 'usaidwat/formatter/timeline'
|
10
6
|
|
11
7
|
Rainbow.enabled = true unless ENV['USAIDWAT_ENV'] == 'cucumber'
|
12
|
-
|
13
|
-
module USaidWat
|
14
|
-
module CLI
|
15
|
-
class BaseFormatter
|
16
|
-
def initialize(options = {})
|
17
|
-
@options = options
|
18
|
-
@count = 0
|
19
|
-
end
|
20
|
-
|
21
|
-
def pattern
|
22
|
-
@options[:pattern]
|
23
|
-
end
|
24
|
-
|
25
|
-
def pattern?
|
26
|
-
!!@options[:pattern]
|
27
|
-
end
|
28
|
-
|
29
|
-
def raw?
|
30
|
-
!!@options[:raw]
|
31
|
-
end
|
32
|
-
|
33
|
-
def relative_dates?
|
34
|
-
@options[:date_format].nil? || @options[:date_format].to_sym != :absolute
|
35
|
-
end
|
36
|
-
|
37
|
-
protected
|
38
|
-
|
39
|
-
def 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.unescape_html.truncate(cols)}\n".color(:magenta))
|
52
|
-
out.write("#{post_date(post)}".color(:blue))
|
53
|
-
out.write("\n#{post.url}") unless post.url.end_with?(post.permalink)
|
54
|
-
@count += 1
|
55
|
-
out.rewind
|
56
|
-
out.read
|
57
|
-
end
|
58
|
-
|
59
|
-
private
|
60
|
-
|
61
|
-
def post_link(post)
|
62
|
-
"https://www.reddit.com#{post.permalink.split('/')[0..-2].join('/')}"
|
63
|
-
end
|
64
|
-
|
65
|
-
def post_date(post)
|
66
|
-
Time.at(post.created_utc).ago
|
67
|
-
end
|
68
|
-
end
|
69
|
-
|
70
|
-
class CompactPostFormatter < BaseFormatter
|
71
|
-
def format(post)
|
72
|
-
cols = tty.width
|
73
|
-
out = StringIO.new
|
74
|
-
subreddit = post.subreddit
|
75
|
-
cols -= subreddit.length + 1
|
76
|
-
title = post.title.strip.unescape_html.truncate(cols)
|
77
|
-
out.write(subreddit.color(:green))
|
78
|
-
out.write(" #{title}\n")
|
79
|
-
out.rewind
|
80
|
-
out.read
|
81
|
-
end
|
82
|
-
end
|
83
|
-
|
84
|
-
class CommentFormatter < BaseFormatter
|
85
|
-
def format(comment)
|
86
|
-
cols = tty.width
|
87
|
-
out = StringIO.new
|
88
|
-
out.write("\n\n") unless @count == 0
|
89
|
-
out.write("#{comment.subreddit}\n".color(:green))
|
90
|
-
out.write("#{comment_link(comment)}\n".color(:yellow))
|
91
|
-
out.write("#{comment.link_title.strip.unescape_html.truncate(cols)}\n".color(:magenta))
|
92
|
-
out.write("#{comment_date(comment)}".color(:blue))
|
93
|
-
out.write(" \u2022 ".color(:cyan))
|
94
|
-
out.write(sprintf("%+d\n", comment_karma(comment)).color(:blue))
|
95
|
-
out.write("\n")
|
96
|
-
out.write("#{comment_body(comment)}\n")
|
97
|
-
@count += 1
|
98
|
-
out.rewind
|
99
|
-
out.read
|
100
|
-
end
|
101
|
-
|
102
|
-
private
|
103
|
-
def markdown
|
104
|
-
@markdown ||= Redcarpet::Markdown.new(Downterm::Render::Terminal, :autolink => true,
|
105
|
-
:strikethrough => true,
|
106
|
-
:superscript => true)
|
107
|
-
end
|
108
|
-
|
109
|
-
def comment_body(comment)
|
110
|
-
body = comment.body.strip.unescape_html
|
111
|
-
body = markdown.render(body) unless raw?
|
112
|
-
if pattern?
|
113
|
-
body.highlight(pattern)
|
114
|
-
else
|
115
|
-
body
|
116
|
-
end
|
117
|
-
end
|
118
|
-
|
119
|
-
def comment_link(comment)
|
120
|
-
link = comment.link_id.split("_")[-1]
|
121
|
-
"http://www.reddit.com/r/#{comment.subreddit}/comments/#{link}/z/#{comment.id}"
|
122
|
-
end
|
123
|
-
|
124
|
-
def comment_date(comment)
|
125
|
-
d = DateTime.strptime(comment.created_utc.to_s, "%s").to_time.localtime
|
126
|
-
if relative_dates?
|
127
|
-
d.ago
|
128
|
-
else
|
129
|
-
d_part = d.strftime("%a, %-d %b %Y")
|
130
|
-
t_part = d.strftime("%l:%M %p").strip
|
131
|
-
"#{d_part}, #{t_part}"
|
132
|
-
end
|
133
|
-
end
|
134
|
-
|
135
|
-
def comment_karma(comment)
|
136
|
-
comment.ups - comment.downs
|
137
|
-
end
|
138
|
-
end
|
139
|
-
|
140
|
-
class CompactCommentFormatter < BaseFormatter
|
141
|
-
def format(comment)
|
142
|
-
cols = tty.width
|
143
|
-
out = StringIO.new
|
144
|
-
subreddit = comment.subreddit
|
145
|
-
cols -= subreddit.length + 1
|
146
|
-
title = comment.link_title.strip.unescape_html.truncate(cols)
|
147
|
-
key = "#{subreddit} #{title}"
|
148
|
-
if !seen?(key)
|
149
|
-
out.write("#{subreddit}".color(:green))
|
150
|
-
out.write(" #{title}\n")
|
151
|
-
end
|
152
|
-
mark_seen(key)
|
153
|
-
out.rewind
|
154
|
-
out.read
|
155
|
-
end
|
156
|
-
|
157
|
-
private
|
158
|
-
|
159
|
-
def comments
|
160
|
-
@comments ||= Set.new
|
161
|
-
end
|
162
|
-
|
163
|
-
def mark_seen(comment)
|
164
|
-
comments << comment
|
165
|
-
end
|
166
|
-
|
167
|
-
def seen?(comment)
|
168
|
-
comments.include?(comment)
|
169
|
-
end
|
170
|
-
end
|
171
|
-
|
172
|
-
class TallyFormatter < BaseFormatter
|
173
|
-
def format(partition_data)
|
174
|
-
out = StringIO.new
|
175
|
-
longest_subreddit = partition_data.longest
|
176
|
-
subreddits = partition_data.counts
|
177
|
-
subreddits.each do |subreddit_count|
|
178
|
-
subreddit, tally = subreddit_count
|
179
|
-
line = sprintf("%-*s %3d\n", longest_subreddit, subreddit, tally)
|
180
|
-
out.write(line)
|
181
|
-
end
|
182
|
-
out.rewind
|
183
|
-
out.read
|
184
|
-
end
|
185
|
-
end
|
186
|
-
end
|
187
|
-
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
require 'downterm'
|
2
|
+
require 'rainbow/ext/string'
|
3
|
+
require 'redcarpet'
|
4
|
+
require 'ttycaca'
|
5
|
+
|
6
|
+
module USaidWat
|
7
|
+
module CLI
|
8
|
+
module TTYFormatter
|
9
|
+
def tty
|
10
|
+
@tty ||= Ttycaca::Terminal.new
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
class BaseFormatter
|
15
|
+
include TTYFormatter
|
16
|
+
|
17
|
+
def initialize(options = {})
|
18
|
+
@options = options
|
19
|
+
@count = 0
|
20
|
+
end
|
21
|
+
|
22
|
+
def pattern
|
23
|
+
@options[:pattern]
|
24
|
+
end
|
25
|
+
|
26
|
+
def pattern?
|
27
|
+
!!@options[:pattern]
|
28
|
+
end
|
29
|
+
|
30
|
+
def raw?
|
31
|
+
!!@options[:raw]
|
32
|
+
end
|
33
|
+
|
34
|
+
def relative_dates?
|
35
|
+
@options[:date_format].nil? || @options[:date_format].to_sym != :absolute
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|