usaidwat 1.4.5 → 1.5.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,92 @@
1
+ module USaidWat
2
+ module CLI
3
+ class CommentFormatter < BaseFormatter
4
+ def format(comment)
5
+ cols = tty.width
6
+ out = StringIO.new
7
+ out.write("\n\n") unless @count == 0
8
+ out.write("#{comment.subreddit}\n".color(:green))
9
+ out.write("#{comment_link(comment)}\n".color(:yellow))
10
+ out.write("#{comment.link_title.strip.unescape_html.truncate(cols)}\n".color(:magenta))
11
+ out.write("#{comment_date(comment)}".color(:blue))
12
+ out.write(" \u2022 ".color(:cyan))
13
+ out.write(sprintf("%+d\n", comment_karma(comment)).color(:blue))
14
+ out.write("\n")
15
+ out.write("#{comment_body(comment)}\n")
16
+ @count += 1
17
+ out.rewind
18
+ out.read
19
+ end
20
+
21
+ private
22
+
23
+ def markdown
24
+ @markdown ||= Redcarpet::Markdown.new(Downterm::Render::Terminal, :autolink => true,
25
+ :strikethrough => true,
26
+ :superscript => true)
27
+ end
28
+
29
+ def comment_body(comment)
30
+ body = comment.body.strip.unescape_html
31
+ body = markdown.render(body) unless raw?
32
+ if pattern?
33
+ body.highlight(pattern)
34
+ else
35
+ body
36
+ end
37
+ end
38
+
39
+ def comment_link(comment)
40
+ link = comment.link_id.split("_")[-1]
41
+ "http://www.reddit.com/r/#{comment.subreddit}/comments/#{link}/z/#{comment.id}"
42
+ end
43
+
44
+ def comment_date(comment)
45
+ d = comment.created_utc.localtime
46
+ if relative_dates?
47
+ d.ago
48
+ else
49
+ d_part = d.strftime("%a, %-d %b %Y")
50
+ t_part = d.strftime("%l:%M %p").strip
51
+ "#{d_part}, #{t_part}"
52
+ end
53
+ end
54
+
55
+ def comment_karma(comment)
56
+ comment.ups - comment.downs
57
+ end
58
+ end
59
+
60
+ class CompactCommentFormatter < BaseFormatter
61
+ def format(comment)
62
+ cols = tty.width
63
+ out = StringIO.new
64
+ subreddit = comment.subreddit
65
+ cols -= subreddit.length + 1
66
+ title = comment.link_title.strip.unescape_html.truncate(cols)
67
+ key = "#{subreddit} #{title}"
68
+ if !seen?(key)
69
+ out.write("#{subreddit}".color(:green))
70
+ out.write(" #{title}\n")
71
+ end
72
+ mark_seen(key)
73
+ out.rewind
74
+ out.read
75
+ end
76
+
77
+ private
78
+
79
+ def comments
80
+ @comments ||= Set.new
81
+ end
82
+
83
+ def mark_seen(comment)
84
+ comments << comment
85
+ end
86
+
87
+ def seen?(comment)
88
+ comments.include?(comment)
89
+ end
90
+ end
91
+ end
92
+ end
@@ -0,0 +1,20 @@
1
+ module USaidWat
2
+ module CLI
3
+ class TallyFormatter
4
+ include TTYFormatter
5
+
6
+ def format(partition_data)
7
+ out = StringIO.new
8
+ longest_subreddit = partition_data.longest
9
+ subreddits = partition_data.counts
10
+ subreddits.each do |subreddit_count|
11
+ subreddit, tally = subreddit_count
12
+ line = sprintf("%-*s %3d\n", longest_subreddit, subreddit, tally)
13
+ out.write(line)
14
+ end
15
+ out.rewind
16
+ out.read
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,43 @@
1
+ module USaidWat
2
+ module CLI
3
+ class PostFormatter < BaseFormatter
4
+ def format(post)
5
+ cols = tty.width
6
+ out = StringIO.new
7
+ out.write("\n\n\n") unless @count == 0
8
+ out.write("#{post.subreddit}\n".color(:green))
9
+ out.write("#{post_link(post)}\n".color(:yellow))
10
+ out.write("#{post.title.strip.unescape_html.truncate(cols)}\n".color(:magenta))
11
+ out.write("#{post_date(post)}".color(:blue))
12
+ out.write("\n#{post.url}") unless post.url.end_with?(post.permalink)
13
+ @count += 1
14
+ out.rewind
15
+ out.read
16
+ end
17
+
18
+ private
19
+
20
+ def post_link(post)
21
+ "https://www.reddit.com#{post.permalink.split('/')[0..-2].join('/')}"
22
+ end
23
+
24
+ def post_date(post)
25
+ post.created_utc.ago
26
+ end
27
+ end
28
+
29
+ class CompactPostFormatter < BaseFormatter
30
+ def format(post)
31
+ cols = tty.width
32
+ out = StringIO.new
33
+ subreddit = post.subreddit
34
+ cols -= subreddit.length + 1
35
+ title = post.title.strip.unescape_html.truncate(cols)
36
+ out.write(subreddit.color(:green))
37
+ out.write(" #{title}\n")
38
+ out.rewind
39
+ out.read
40
+ end
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,32 @@
1
+ module USaidWat
2
+ module CLI
3
+ class TimelineFormatter
4
+ include TTYFormatter
5
+
6
+ def format(comment_data)
7
+ out = StringIO.new
8
+ out.write(' ')
9
+ (0..23).each { |h| out.write(sprintf '%3s', h) }
10
+ out.write("\n")
11
+
12
+ comment_data.each_with_index do |day, i|
13
+ out.write(day_map(i))
14
+ day.each do |hour|
15
+ mark = hour > 0 ? '*' : ' '
16
+ out.write(sprintf "%3s", mark)
17
+ end
18
+ out.write("\n")
19
+ end
20
+
21
+ out.rewind
22
+ out.read
23
+ end
24
+
25
+ private
26
+
27
+ def day_map(i)
28
+ return %W{S M T W T F S}[i]
29
+ end
30
+ end
31
+ end
32
+ end
@@ -1,66 +1,50 @@
1
+ require 'requests'
2
+
1
3
  module USaidWat
2
4
  module Service
3
- class MockComment
4
- attr_reader :subreddit, :body, :id, :link_id, :created_utc, :link_title, :ups, :downs
5
-
6
- def initialize(dict)
7
- data = dict['data']
8
- @subreddit = data['subreddit']
9
- @body = data['body']
10
- @id = data['id']
11
- @link_id = data['link_id']
12
- @created_utc = data['created_utc']
13
- @link_title = data['link_title']
14
- @ups = data['ups']
15
- @downs = data['downs']
16
- end
17
- end
18
-
19
- class MockSubmission
20
- attr_reader :subreddit, :title, :created_utc, :permalink, :url
21
-
22
- def initialize(dict)
23
- data = dict['data']
24
- @subreddit = data['subreddit']
25
- @title = data['title']
26
- @created_utc = data['created_utc']
27
- @permalink = data['permalink']
28
- @url = data['url']
5
+ class RedditService
6
+ def user(username)
7
+ data = %w{about comments submitted}.reduce({}) { |memo, obj| memo.update(obj.to_sym => get_page(username, obj)) }
8
+ USaidWat::Thing::User.new(username, data[:about], data[:comments], data[:submitted])
29
9
  end
30
- end
31
10
 
32
- class MockUser
33
- def initialize(username)
34
- @username = username
35
- end
11
+ private
36
12
 
37
- def about
38
- load_data("user_#{@username}.json")['data']
13
+ def get_page(username, page)
14
+ url = "https://www.reddit.com/user/#{username}/#{page}.json"
15
+ url += '?limit=100' if ['comments', 'submitted'].include?(page)
16
+ get(url)
39
17
  end
40
18
 
41
- def comments(n)
42
- json = load_data("#{@username}.json")
43
- json['data']['children'].map { |d| MockComment.new(d) }
19
+ def get(uri)
20
+ hdrs = {'User-Agent' => "usaidwat v#{USaidWat::VERSION}"}
21
+ Requests.request('GET', uri, :headers => hdrs).json
22
+ rescue Timeout::Error
23
+ :server_error
24
+ rescue Requests::Error => e
25
+ case e.response.code.to_i
26
+ when 404 then :no_such_user
27
+ when 500 then :server_error
28
+ else :nil
29
+ end
44
30
  end
31
+ end
45
32
 
46
- def posts
47
- json = load_data("submissions_#{@username}.json")
48
- json['data']['children'].map { |d| MockSubmission.new(d) }
33
+ class MockService
34
+ def user(username)
35
+ USaidWat::Thing::User.new(username,
36
+ load_data("user_#{username}.json"),
37
+ load_data("#{username}.json"),
38
+ load_data("submissions_#{username}.json"))
49
39
  end
50
40
 
51
41
  private
52
42
 
53
43
  def load_data(data_file)
54
44
  path = File.join(File.dirname(__FILE__), "..", "..", "features", "fixtures", data_file)
55
- raise USaidWat::Client::NoSuchUserError, @username unless File.exists?(path)
45
+ return :no_such_user unless File.exists?(path)
56
46
  JSON.parse(IO.read(path))
57
47
  end
58
48
  end
59
-
60
- class MockService
61
- def user(username)
62
- MockUser.new(username)
63
- end
64
- end
65
49
  end
66
50
  end
@@ -0,0 +1,85 @@
1
+ module USaidWat
2
+ module Thing
3
+ module Timestampable
4
+ def created_utc
5
+ Time.at(@created_utc)
6
+ end
7
+ end
8
+
9
+ module HashBackedIvars
10
+ def method_missing(symbol, *args, &block)
11
+ res = @data[symbol.to_s]
12
+ return res unless res.nil?
13
+ super
14
+ end
15
+ end
16
+
17
+ class User
18
+ def initialize(username, user_data, comment_data, post_data)
19
+ @username = username
20
+ @user_data = user_data
21
+ @comment_data = comment_data
22
+ @post_data = post_data
23
+ end
24
+
25
+ def about
26
+ raise USaidWat::Client::NoSuchUserError, @username if @user_data == :no_such_user
27
+ raise USaidWat::Client::ReachabilityError if @user_data == :server_error
28
+ @about ||= About.new(@user_data)
29
+ end
30
+
31
+ def comments(n)
32
+ comment_data['children'].map { |d| Comment.new(d) }
33
+ end
34
+
35
+ def posts
36
+ post_data['children'].map { |d| Submission.new(d) }
37
+ end
38
+
39
+ def method_missing(symbol, *args, &block)
40
+ if symbol.to_s =~ /_data$/
41
+ begin
42
+ res = instance_variable_get("@#{symbol}")
43
+ raise USaidWat::Client::NoSuchUserError, @username if res == :no_such_user
44
+ raise USaidWat::Client::ReachabilityError if res == :server_error
45
+ res['data']
46
+ rescue NameError
47
+ super
48
+ end
49
+ else
50
+ super
51
+ end
52
+ end
53
+ end
54
+
55
+ class About
56
+ include Timestampable
57
+ include HashBackedIvars
58
+
59
+ def initialize(dict)
60
+ @data = dict['data']
61
+ @created_utc = @data['created_utc']
62
+ end
63
+ end
64
+
65
+ class Comment
66
+ include Timestampable
67
+ include HashBackedIvars
68
+
69
+ def initialize(dict)
70
+ @data = dict['data']
71
+ @created_utc = @data['created_utc']
72
+ end
73
+ end
74
+
75
+ class Submission
76
+ include Timestampable
77
+ include HashBackedIvars
78
+
79
+ def initialize(dict)
80
+ @data = dict['data']
81
+ @created_utc = @data['created_utc']
82
+ end
83
+ end
84
+ end
85
+ end
@@ -1,3 +1,8 @@
1
1
  module USaidWat
2
- VERSION = "1.4.5"
2
+ VERSION = "1.5.0"
3
+
4
+ def self.commit_hash
5
+ spec = Gem.loaded_specs['usaidwat']
6
+ spec.metadata['commit_hash'][0...7]
7
+ end
3
8
  end
@@ -23,11 +23,11 @@ module USaidWat
23
23
  WebMock.disable_net_connect!
24
24
  WebMock.reset!
25
25
  root = File.expand_path("../../../features/fixtures", __FILE__)
26
- stub_request(:get, "https://www.reddit.com/user/mipadi/comments.json?after=&limit=100").
26
+ stub_request(:get, "https://www.reddit.com/user/mipadi/comments.json?limit=100").
27
27
  to_return(:body => IO.read(File.join(root, "mipadi.json")))
28
28
  stub_request(:get, "https://www.reddit.com/user/mipadi/about.json").
29
29
  to_return(:body => IO.read(File.join(root, "user_mipadi.json")))
30
- stub_request(:get, "https://www.reddit.com/user/mipadi/submitted.json?after=&limit=25").
30
+ stub_request(:get, "https://www.reddit.com/user/mipadi/submitted.json?limit=100").
31
31
  to_return(:body => IO.read(File.join(root, "submissions_mipadi.json")))
32
32
 
33
33
  Timecop.freeze(Time.new(2015, 9, 15, 11, 14, 30, "-07:00"))
@@ -81,11 +81,11 @@ module USaidWat
81
81
  WebMock.disable_net_connect!
82
82
  WebMock.reset!
83
83
  root = File.expand_path("../../../features/fixtures", __FILE__)
84
- stub_request(:get, "https://www.reddit.com/user/testuser/comments.json?after=&limit=100").
84
+ stub_request(:get, "https://www.reddit.com/user/testuser/comments.json?limit=100").
85
85
  to_return(:status => 404, :body => IO.read(File.join(root, "testuser.json")))
86
86
  stub_request(:get, "https://www.reddit.com/user/testuser/about.json").
87
87
  to_return(:status => 404, :body => IO.read(File.join(root, "user_testuser.json")))
88
- stub_request(:get, "https://www.reddit.com/user/testuser/submitted.json?after=&limit=25").
88
+ stub_request(:get, "https://www.reddit.com/user/testuser/submitted.json?limit=100").
89
89
  to_return(:status => 404, :body => IO.read(File.join(root, "submissions_testuser.json")))
90
90
  end
91
91
 
@@ -130,9 +130,9 @@ module USaidWat
130
130
  before(:each) do
131
131
  WebMock.disable_net_connect!
132
132
  WebMock.reset!
133
- stub_request(:get, "https://www.reddit.com/user/mipadi/comments.json?after=&limit=100").to_timeout
133
+ stub_request(:get, "https://www.reddit.com/user/mipadi/comments.json?limit=100").to_timeout
134
134
  stub_request(:get, "https://www.reddit.com/user/mipadi/about.json").to_timeout
135
- stub_request(:get, "https://www.reddit.com/user/mipadi/submitted.json?after=&limit=25").to_timeout
135
+ stub_request(:get, "https://www.reddit.com/user/mipadi/submitted.json?limit=100").to_timeout
136
136
  end
137
137
 
138
138
  describe "#posts" do
@@ -170,11 +170,11 @@ module USaidWat
170
170
  before(:each) do
171
171
  WebMock.disable_net_connect!
172
172
  WebMock.reset!
173
- stub_request(:get, "https://www.reddit.com/user/mipadi/comments.json?after=&limit=100").
173
+ stub_request(:get, "https://www.reddit.com/user/mipadi/comments.json?limit=100").
174
174
  to_return(:status => 500)
175
175
  stub_request(:get, "https://www.reddit.com/user/mipadi/about.json").
176
176
  to_return(:status => 500)
177
- stub_request(:get, "https://www.reddit.com/user/mipadi/submitted.json?after=&limit=25").
177
+ stub_request(:get, "https://www.reddit.com/user/mipadi/submitted.json?limit=100").
178
178
  to_return(:status => 500)
179
179
  end
180
180
 
@@ -105,7 +105,7 @@ module USaidWat
105
105
  expect(post).to receive(:subreddit).and_return("Games")
106
106
  expect(post).to receive(:permalink).twice.and_return("/r/Games/comments/3ovldc/the_xbox_one_is_garbage_and_the_future_is_bullshit/")
107
107
  expect(post).to receive(:title).and_return("The Xbox One Is Garbage And The Future Is Bullshit")
108
- expect(post).to receive(:created_utc).and_return(1444928064)
108
+ expect(post).to receive(:created_utc).and_return(Time.at(1444928064))
109
109
  expect(post).to receive(:url).twice.and_return("http://adequateman.deadspin.com/the-xbox-one-is-garbage-and-the-future-is-bullshit-1736054579")
110
110
  expected = <<-EXPECTED
111
111
  Games
@@ -125,7 +125,7 @@ EXPECTED
125
125
  expect(post).to receive(:subreddit).and_return("Games")
126
126
  expect(post).to receive(:permalink).twice.and_return(permalink)
127
127
  expect(post).to receive(:title).and_return("The Xbox One Is Garbage And The Future Is Bullshit")
128
- expect(post).to receive(:created_utc).and_return(1444928064)
128
+ expect(post).to receive(:created_utc).and_return(Time.at(1444928064))
129
129
  expect(post).to receive(:url).and_return("https://www.reddit.com#{permalink}")
130
130
  expected = <<-EXPECTED
131
131
  Games
@@ -143,13 +143,13 @@ EXPECTED
143
143
  expect(post1).to receive(:subreddit).and_return("Games")
144
144
  expect(post1).to receive(:permalink).twice.and_return("/r/Games/comments/3ovldc/the_xbox_one_is_garbage_and_the_future_is_bullshit/")
145
145
  expect(post1).to receive(:title).and_return("The Xbox One Is Garbage And The Future Is Bullshit")
146
- expect(post1).to receive(:created_utc).and_return(1444928064)
146
+ expect(post1).to receive(:created_utc).and_return(Time.at(1444928064))
147
147
  expect(post1).to receive(:url).twice.and_return("http://adequateman.deadspin.com/the-xbox-one-is-garbage-and-the-future-is-bullshit-1736054579")
148
148
  post2 = double("second post")
149
149
  expect(post2).to receive(:subreddit).and_return("technology")
150
150
  expect(post2).to receive(:permalink).twice.and_return("/r/technology/comments/3o0vrh/mozilla_lays_out_a_proposed_set_of_rules_for/")
151
151
  expect(post2).to receive(:title).and_return("Mozilla lays out a proposed set of rules for content blockers")
152
- expect(post2).to receive(:created_utc).and_return(1444340278)
152
+ expect(post2).to receive(:created_utc).and_return(Time.at(1444340278))
153
153
  expect(post2).to receive(:url).twice.and_return("https://blog.mozilla.org/blog/2015/10/07/proposed-principles-for-content-blocking/")
154
154
  s = formatter.format(post1)
155
155
  s = formatter.format(post2)
@@ -165,7 +165,7 @@ EXPECTED
165
165
  expect(post).to receive(:subreddit).and_return("webdev")
166
166
  expect(post).to receive(:permalink).twice.and_return("/r/webdev/comments/29og3m/sick_of_ruby_dynamic_typing_side_effects_and/")
167
167
  expect(post).to receive(:title).and_return("Sick of Ruby, dynamic typing, side effects, and basically object-oriented programming")
168
- expect(post).to receive(:created_utc).and_return(1404331670)
168
+ expect(post).to receive(:created_utc).and_return(Time.at(1404331670))
169
169
  expect(post).to receive(:url).twice.and_return("https://blog.abevoelker.com/sick-of-ruby-dynamic-typing-side-effects-object-oriented-programming/")
170
170
  expected = <<-EXPECTED
171
171
  webdev
@@ -199,7 +199,7 @@ EXPECTED
199
199
  expect(comment).to receive(:link_id).and_return("t3_13f783")
200
200
  expect(comment).to receive(:id).and_return("c73qhxi")
201
201
  expect(comment).to receive(:link_title).and_return("Why Brit Ruby 2013 was cancelled and why this is not ok - Gist")
202
- expect(comment).to receive(:created_utc).and_return(1433378314.0)
202
+ expect(comment).to receive(:created_utc).and_return(Time.at(1433378314.0))
203
203
  expect(comment).to receive(:ups).and_return(12)
204
204
  expect(comment).to receive(:downs).and_return(1)
205
205
  expect(comment).to receive(:body).and_return("Welcome to the wonderful world of Python drama!")
@@ -220,7 +220,7 @@ EXPECTED
220
220
  expect(comment1).to receive(:subreddit).twice.and_return("programming")
221
221
  expect(comment1).to receive(:link_id).and_return("t3_13f783")
222
222
  expect(comment1).to receive(:id).and_return("c73qhxi")
223
- expect(comment1).to receive(:created_utc).and_return(1433378314.0)
223
+ expect(comment1).to receive(:created_utc).and_return(Time.at(1433378314.0))
224
224
  expect(comment1).to receive(:ups).and_return(12)
225
225
  expect(comment1).to receive(:downs).and_return(1)
226
226
  expect(comment1).to receive(:link_title).and_return("Why Brit Ruby 2013 was cancelled and why this is not ok - Gist")
@@ -229,7 +229,7 @@ EXPECTED
229
229
  expect(comment2).to receive(:subreddit).twice.and_return("programming")
230
230
  expect(comment2).to receive(:link_id).and_return("t3_13f783")
231
231
  expect(comment2).to receive(:id).and_return("c73qhxi")
232
- expect(comment2).to receive(:created_utc).and_return(1433378314.0)
232
+ expect(comment2).to receive(:created_utc).and_return(Time.at(1433378314.0))
233
233
  expect(comment2).to receive(:ups).and_return(12)
234
234
  expect(comment2).to receive(:downs).and_return(1)
235
235
  expect(comment2).to receive(:link_title).and_return("Why Brit Ruby 2013 was cancelled and why this is not ok - Gist")
@@ -247,7 +247,7 @@ EXPECTED
247
247
  expect(comment).to receive(:subreddit).twice.and_return("test")
248
248
  expect(comment).to receive(:link_id).and_return("t3_13f783")
249
249
  expect(comment).to receive(:id).and_return("c73qhxi")
250
- expect(comment).to receive(:created_utc).and_return(1433378314.0)
250
+ expect(comment).to receive(:created_utc).and_return(Time.at(1433378314.0))
251
251
  expect(comment).to receive(:ups).and_return(12)
252
252
  expect(comment).to receive(:downs).and_return(1)
253
253
  expect(comment).to receive(:link_title).and_return("Why Brit Ruby 2013 was cancelled and why this is not ok - Gist")
@@ -262,7 +262,7 @@ EXPECTED
262
262
  expect(comment).to receive(:subreddit).twice.and_return("guitars")
263
263
  expect(comment).to receive(:link_id).and_return("t3_13f783")
264
264
  expect(comment).to receive(:id).and_return("c73qhxi")
265
- expect(comment).to receive(:created_utc).and_return(1433378314.0)
265
+ expect(comment).to receive(:created_utc).and_return(Time.at(1433378314.0))
266
266
  expect(comment).to receive(:ups).and_return(12)
267
267
  expect(comment).to receive(:downs).and_return(1)
268
268
  expect(comment).to receive(:link_title).and_return("[GEAR] My 06 Fender EJ Strat, an R&amp;D prototype sold at NAMM")
@@ -356,5 +356,33 @@ EOS
356
356
  end
357
357
  end
358
358
  end
359
+
360
+ describe TimelineFormatter do
361
+ let (:data) { [[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
362
+ [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 6, 1, 11, 0, 2, 1, 4, 0, 0, 0, 0, 0, 0],
363
+ [0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0],
364
+ [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9, 2, 7, 4, 1, 6, 1, 2, 2, 0, 0, 0, 0, 1],
365
+ [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 7, 5, 5, 0, 0, 0, 3, 2, 0, 0, 1, 0, 0, 0],
366
+ [0, 0, 0, 0, 0, 0, 0, 0, 0, 5, 2, 0, 0, 0, 0, 0, 0, 2, 0, 3, 0, 0, 0, 0],
367
+ [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]] }
368
+ let (:formatter) { TimelineFormatter.new }
369
+
370
+ describe '#format' do
371
+ it 'should format a timeline' do
372
+ expected = <<EOS
373
+ 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
374
+ S
375
+ M * * * * * * *
376
+ T * * *
377
+ W * * * * * * * * * *
378
+ T * * * * * *
379
+ F * * * *
380
+ S
381
+ EOS
382
+ actual = formatter.format(data)
383
+ expect(actual).to eq(expected)
384
+ end
385
+ end
386
+ end
359
387
  end
360
388
  end