usaidwat 1.3.0 → 1.4.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.
@@ -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