usaidwat 1.3.0 → 1.4.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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 it should fail with:
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 it should fail with:
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 it should fail with:
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 it should fail with:
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 it should fail with:
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 it should fail with:
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
  """
@@ -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 it should fail with:
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
@@ -1,6 +1,5 @@
1
1
  require "usaidwat/algo"
2
2
  require "usaidwat/application"
3
- require "usaidwat/formatter"
4
3
  require "usaidwat/client"
5
- require "usaidwat/terminal"
4
+ require "usaidwat/formatter"
6
5
  require "usaidwat/version"
@@ -1,8 +1,12 @@
1
- require 'usaidwat/algo'
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 'sysexits'
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
- puts message
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.join(' ').split(/[ ,\+]/).map { |sr| sr.downcase }
98
+ subreddits = args.subreddits
87
99
 
88
100
  redditor = client.new(username)
89
101
  comments = redditor.comments
90
102
 
91
- res = filter_comments(redditor, comments, subreddits) >>
92
- lambda { |r| grep_comments(redditor, r.value, options['grep']) } >>
93
- lambda { |r| limit_comments(redditor, r.value, options['limit']) } >>
94
- lambda { |r| ensure_comments(redditor, r.value) }
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 filter_comments(redditor, comments, subreddits)
112
- return USaidWat::Right.new(comments) if subreddits.empty?
113
- comments = comments.find_all { |c| subreddits.include?(c.subreddit.downcase) }
114
- if comments.empty?
115
- USaidWat::Left.new("No comments by #{redditor.username} for #{subreddits.join(', ')}.")
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 grep_comments(redditor, comments, grep)
122
- return USaidWat::Right.new(comments) if grep.nil?
123
- comments = comments.select { |c| c.body =~ /#{grep}/i }
124
- if comments.empty?
125
- msg = "#{redditor.username} has no comments matching /#{grep}/."
126
- USaidWat::Left.new(msg)
127
- else
128
- USaidWat::Right.new(comments)
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 limit_comments(redditor, comments, n)
133
- return USaidWat::Right.new(comments) if n.nil?
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 ensure_comments(redditor, comments)
139
- if comments.empty?
140
- USaidWat::Left.new("#{redditor.username} has no comments.")
141
- else
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
- def list_comments(comments, options = {})
147
- oneline = options[:oneline]
148
- formatter = (oneline ? USaidWat::CLI::CompactCommentFormatter : USaidWat::CLI::CommentFormatter).new(options)
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
- comments.each { |c| print formatter.format(c) }
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
- # Unfortunately Snooby cannot return comments for a specific
175
- # user in a specific subreddit, so for now we have to sort them
176
- # ourself.
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
@@ -43,6 +43,14 @@ module USaidWat
43
43
  "#{username}"
44
44
  end
45
45
 
46
+ def posts
47
+ user.posts
48
+ rescue NoMethodError
49
+ raise NoSuchUserError, username
50
+ rescue TypeError
51
+ raise ReachabilityError, "Reddit unreachable"
52
+ end
53
+
46
54
  private
47
55
 
48
56
  def user
@@ -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,5 @@
1
+ class Array
2
+ def subreddits
3
+ join(' ').split(/[ ,\+]/).map { |sr| sr.downcase }
4
+ end
5
+ 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
@@ -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 ||= USaidWat::Application::Terminal.new
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