usaidwat 1.4.5 → 1.5.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.
@@ -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