kovid 0.6.2 → 0.6.3

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: ba5ca0fe0f0b4f13d4ae3fa0796df0fdda1bd0029c8e1f908a4ac7b79766f928
4
- data.tar.gz: 11328de6b6c9e1a4f4bad7bb42d65ed3575e0efc7677dc4aa916bfe1ba4b5000
3
+ metadata.gz: c21479be2e8fdc2c40fd9f38d37808bd3a3d13ec36080119896fa95cc291d127
4
+ data.tar.gz: 94431cdc81ea6de25240aada27cdf680a4bd6a37a88e060ba51b6197cac015bd
5
5
  SHA512:
6
- metadata.gz: d3ad984c1cef60884ec75512763f759eca655c09d69d7db0a237a015fc78b05e2a2059fd84441d60aa090a08452df388c51cd10de51cf2ce52c2cce8c71da1b6
7
- data.tar.gz: 9277e293560db60a004a4f388789a790d5da74a3a650e39b21a6eca0f01e1431c54ad09a66cf467cf22a3f57c2dce94cba121185ab6e87096410d29a3e1586bf
6
+ metadata.gz: 865a466405a2b6be0068781dc4f11f1573c761fc9bde7994ed32f28573842b95df4cbcbd609436e1c1713c05ac5564fb582755b35e7586af938df145b77e5bf4
7
+ data.tar.gz: b477fc309bd688bc7195e90cb3e4bcfd201a37a38170abbb95aaa018d89e9afa18bae3e35a1dddf7211adab8f00d483c13984597f837bb4a98cdeca9a1c3d00c
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- kovid (0.6.1)
4
+ kovid (0.6.2)
5
5
  ascii_charts (~> 0.9.1)
6
6
  rainbow (~> 3.0)
7
7
  terminal-table (~> 1.8)
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Kovid
4
+ module Aggregators
5
+ def eu_aggregate(eu_data)
6
+ aggregated_table(eu_data, 'The EU', Kovid::Request::EU_ISOS, '🇪🇺')
7
+ end
8
+
9
+ def europe_aggregate(europe_data)
10
+ aggregated_table(europe_data, 'Europe', Kovid::Request::EUROPE_ISOS, '🏰')
11
+ end
12
+
13
+ def africa_aggregate(africa_data)
14
+ aggregated_table(africa_data, 'Africa',
15
+ Kovid::Request::AFRICA_ISOS, '🌍')
16
+ end
17
+
18
+ def south_america_aggregate(south_america_data)
19
+ aggregated_table(south_america_data,
20
+ 'South America',
21
+ Kovid::Request::SOUTH_AMERICA_ISOS, '🌎')
22
+ end
23
+
24
+ def asia_aggregate(asia_data)
25
+ aggregated_table(asia_data, 'Asia', Kovid::Request::ASIA_ISOS, '🌏')
26
+ end
27
+ end
28
+ end
data/lib/kovid/cli.rb CHANGED
@@ -5,8 +5,6 @@ require 'kovid'
5
5
 
6
6
  module Kovid
7
7
  class CLI < Thor
8
- FULL_FLAG = %w[-f --full].freeze
9
-
10
8
  def self.exit_on_failure?
11
9
  true
12
10
  end
@@ -26,16 +24,22 @@ module Kovid
26
24
 
27
25
  desc 'check COUNTRY or check "COUNTRY NAME"', 'Returns reported data on provided country. eg: "kovid check "hong kong".'
28
26
  method_option :full, aliases: '-f'
29
- def check(name)
30
- fetch_country_stats(name)
27
+ def check(*name)
28
+ if name.size == 1
29
+ fetch_country_stats(name.pop)
30
+ elsif options[:full]
31
+ puts Kovid.country_comparison_full(name)
32
+ else
33
+ puts Kovid.country_comparison(name)
34
+ end
31
35
  data_source
32
36
  end
37
+ map country: :check
33
38
 
34
- desc 'country COUNTRY or country "COUNTRY NAME"', 'Returns reported data on provided country. eg: "kovid country "hong kong".'
35
- method_option :full, aliases: '-f'
36
- def country(name)
37
- fetch_country_stats(name)
38
- data_source
39
+ desc 'compare COUNTRY COUNTRY', 'Deprecated. Will be removed in v7.0.0'
40
+ def compare(*_name)
41
+ Kovid.info_table("#compare is deprecated and will be removed in v7.0.0. \
42
+ \nPlease do `kovid check COUNTRY COUNTRY ...` instead.")
39
43
  end
40
44
 
41
45
  desc 'state STATE', 'Return reported data on provided state.'
@@ -44,19 +48,8 @@ module Kovid
44
48
  data_source
45
49
  end
46
50
 
47
- desc 'compare COUNTRY COUNTRY', 'Returns full comparison table for given countries. Accepts multiple countries.'
48
- def compare(*name)
49
- if FULL_FLAG.include?(name.fetch(-1))
50
- puts Kovid.country_comparison_full(name[0..-2])
51
- else
52
- puts Kovid.country_comparison(name)
53
- end
54
- data_source
55
- end
56
-
57
51
  desc 'states STATE STATE', 'Returns full comparison table for the given states. Accepts multiple states.'
58
52
  def states(*states)
59
- # This ensures this command is case insensitive.
60
53
  downcased_states = states.map(&:downcase)
61
54
 
62
55
  puts Kovid.states(downcased_states)
@@ -0,0 +1,92 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Kovid
4
+ module Constants
5
+ CASES_DEATHS_RECOVERED = [
6
+ 'Cases'.paint_white,
7
+ 'Deaths'.paint_red,
8
+ 'Recovered'.paint_green
9
+ ].freeze
10
+
11
+ CASES_DEATHS_RECOVERED_CTODAY_DTODAY = [
12
+ 'Cases'.paint_white,
13
+ 'Cases Today'.paint_white,
14
+ 'Deaths'.paint_red,
15
+ 'Deaths Today'.paint_red,
16
+ 'Recovered'.paint_green
17
+ ].freeze
18
+
19
+ DATE_CASES_DEATHS_RECOVERED = [
20
+ 'Date'.paint_white,
21
+ 'Cases'.paint_white,
22
+ 'Deaths'.paint_red,
23
+ 'Recovered'.paint_green
24
+ ].freeze
25
+
26
+ CONTINENTAL_AGGREGATE_HEADINGS = [
27
+ 'Cases'.paint_white,
28
+ 'Cases Today'.paint_white,
29
+ 'Deaths'.paint_red,
30
+ 'Deaths Today'.paint_red,
31
+ 'Recovered'.paint_green,
32
+ 'Active'.paint_yellow,
33
+ 'Critical'.paint_red
34
+ ].freeze
35
+
36
+ COMPARE_COUNTRY_TABLE_FULL = [
37
+ 'Country'.paint_white,
38
+ 'Cases'.paint_white,
39
+ 'Deaths'.paint_red,
40
+ 'Recovered'.paint_green,
41
+ 'Cases Today'.paint_white,
42
+ 'Deaths Today'.paint_red,
43
+ 'Critical'.paint_yellow,
44
+ 'Cases/Million'.paint_white
45
+ ].freeze
46
+
47
+ COMPARE_COUNTRIES_TABLE_HEADINGS = [
48
+ 'Country'.paint_white,
49
+ 'Cases'.paint_white,
50
+ 'Cases Today'.paint_white,
51
+ 'Deaths'.paint_red,
52
+ 'Deaths Today'.paint_red,
53
+ 'Recovered'.paint_green
54
+ ].freeze
55
+
56
+ FULL_COUNTRY_TABLE_HEADINGS = [
57
+ 'Cases'.paint_white,
58
+ 'Deaths'.paint_red,
59
+ 'Recovered'.paint_green,
60
+ 'Cases Today'.paint_white,
61
+ 'Deaths Today'.paint_red,
62
+ 'Critical'.paint_yellow,
63
+ 'Cases/Million'.paint_white
64
+ ].freeze
65
+
66
+ COMPARE_STATES_HEADINGS = [
67
+ 'State'.paint_white,
68
+ 'Cases'.paint_white,
69
+ 'Cases Today'.paint_white,
70
+ 'Deaths'.paint_red,
71
+ 'Deaths Today'.paint_red,
72
+ 'Active'.paint_yellow
73
+ ].freeze
74
+
75
+ COMPARE_PROVINCES_HEADINGS = [
76
+ 'Province'.paint_white,
77
+ 'Confirmed'.paint_white,
78
+ 'Deaths'.paint_red,
79
+ 'Recovered'.paint_green
80
+ ].freeze
81
+
82
+ FOOTER_LINE = ['------------', '------------', '------------', '------------'].freeze
83
+ COUNTRY_LETTERS = 'A'.upto('Z').with_index(127_462).to_h.freeze
84
+
85
+ RIGHT_ALIGN_COLUMNS = {
86
+ compare_country_table_full: [1, 2, 3, 4, 5, 6, 7],
87
+ compare_country_table: [1, 2, 3, 4, 5],
88
+ compare_us_states: [1, 2, 3, 4, 5],
89
+ compare_provinces: [1, 2, 3]
90
+ }.freeze
91
+ end
92
+ end
@@ -0,0 +1,104 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Kovid
4
+ module Historians
5
+ include Constants
6
+
7
+ def history(country, last)
8
+ # Write checks for when country is spelt wrong.
9
+ headings = DATE_CASES_DEATHS_RECOVERED
10
+ rows = []
11
+
12
+ stats = if last
13
+ transpose(country).last(last.to_i)
14
+ else
15
+ transpose(country)
16
+ end
17
+
18
+ dates = if last
19
+ country['timeline']['cases'].keys.last(last.to_i)
20
+ else
21
+ country['timeline']['cases'].keys
22
+ end
23
+
24
+ unless last
25
+ stats = stats.reject { |stat| stat[0].to_i.zero? && stat[1].to_i.zero? }
26
+ dates = dates.last(stats.count)
27
+ end
28
+
29
+ stats.each_with_index do |val, index|
30
+ val.unshift(Kovid.dateman(dates[index]))
31
+ end.each do |row|
32
+ rows << row
33
+ end
34
+
35
+ if stats.size > 10
36
+ rows << FOOTER_LINE
37
+ rows << DATE_CASES_DEATHS_RECOVERED
38
+ end
39
+
40
+ Terminal::Table.new(
41
+ title: country['country'].upcase,
42
+ headings: headings,
43
+ rows: rows
44
+ )
45
+ end
46
+
47
+ def histogram(country, date_string)
48
+ @date = date_string.split('.')
49
+
50
+ if @date.last.to_i != 20
51
+ Kovid.info_table('Only 2020 histgrams are available.')
52
+ return
53
+ end
54
+
55
+ # From dates where number of !cases.zero?
56
+ positive_cases_figures = country['timeline']['cases'].values.reject(&:zero?)
57
+ dates = country['timeline']['cases'].reject { |_k, v| v.zero? }.keys
58
+ data = []
59
+
60
+ # TODO: Refactor
61
+ # Returns array of days.to_i from the date param
62
+ dates = dates.map do |date|
63
+ date.split('/')
64
+ end.select do |date|
65
+ date.last == @date.last
66
+ end.select do |date|
67
+ date.first == @date.first
68
+ end.map do |array|
69
+ array[1]
70
+ end.map(&:to_i).last(positive_cases_figures.count)
71
+
72
+ # Arranges dates and figures in [x,y] for histogram
73
+ # With x being day, y being number of cases
74
+ if dates.empty?
75
+ if @date.first.to_i > Time.now.month
76
+ msgs = [
77
+ 'Seriously...??! 😏', 'Did you just check the future??',
78
+ 'You just checked the future Morgan.',
79
+ 'Knowing too much of your future is never a good thing.'
80
+ ]
81
+
82
+ Kovid.info_table(msgs.sample)
83
+ else
84
+ Kovid.info_table('Check your spelling/No infections for this month.')
85
+ end
86
+
87
+ else
88
+ dates.each_with_index do |val, index|
89
+ data << [val, positive_cases_figures[index]]
90
+ end
91
+ y_range = AsciiCharts::Cartesian.new(data, bar: true, hide_zero: true).y_range
92
+
93
+ last_two_y = y_range.last 2
94
+ y_interval = last_two_y.last - last_two_y.first
95
+
96
+ scale("Scale on Y: #{y_interval}:#{(y_interval / last_two_y.last.to_f * positive_cases_figures.last).round(2) / y_interval}")
97
+
98
+ puts 'Experimental feature, please report issues.'
99
+
100
+ AsciiCharts::Cartesian.new(data, bar: true, hide_zero: true).draw
101
+ end
102
+ end
103
+ end
104
+ end
data/lib/kovid/request.rb CHANGED
@@ -69,24 +69,26 @@ module Kovid
69
69
 
70
70
  def by_country(country_name)
71
71
  response = fetch_country(country_name)
72
+ Kovid::Tablelize.country_table(response)
72
73
 
73
- if response.values.first.include?('not found')
74
- not_found(country_name)
75
- else
76
- Kovid::Tablelize.country_table(response)
77
- end
74
+ # if response.values.first.include?('not found')
75
+ # not_found(country_name)
76
+ # else
77
+ # Kovid::Tablelize.country_table(response)
78
+ # end
78
79
  rescue JSON::ParserError
79
80
  puts SERVER_DOWN
80
81
  end
81
82
 
82
83
  def by_country_full(country_name)
83
84
  response = fetch_country(country_name)
85
+ Kovid::Tablelize.full_country_table(response)
84
86
 
85
- if response.values.first.include?('not found')
86
- not_found(country_name)
87
- else
88
- Kovid::Tablelize.full_country_table(response)
89
- end
87
+ # if response.values.first.include?('not found')
88
+ # not_found(country_name)
89
+ # else
90
+ # Kovid::Tablelize.full_country_table(response)
91
+ # end
90
92
  rescue JSON::ParserError
91
93
  puts SERVER_DOWN
92
94
  end
@@ -4,97 +4,17 @@ require 'terminal-table'
4
4
  require 'date'
5
5
  require 'ascii_charts'
6
6
  require_relative 'painter'
7
+ require_relative 'constants'
8
+ require_relative 'aggregators'
9
+ require_relative 'historians'
7
10
 
8
11
  module Kovid
9
12
  class Tablelize
10
- class << self
11
- CASES_DEATHS_RECOVERED = [
12
- 'Cases'.paint_white,
13
- 'Deaths'.paint_red,
14
- 'Recovered'.paint_green
15
- ].freeze
16
-
17
- CASES_DEATHS_RECOVERED_CTODAY_DTODAY = [
18
- 'Cases'.paint_white,
19
- 'Cases Today'.paint_white,
20
- 'Deaths'.paint_red,
21
- 'Deaths Today'.paint_red,
22
- 'Recovered'.paint_green
23
- ].freeze
24
-
25
- DATE_CASES_DEATHS_RECOVERED = [
26
- 'Date'.paint_white,
27
- 'Cases'.paint_white,
28
- 'Deaths'.paint_red,
29
- 'Recovered'.paint_green
30
- ].freeze
31
-
32
- CONTINENTAL_AGGREGATE_HEADINGS = [
33
- 'Cases'.paint_white,
34
- 'Cases Today'.paint_white,
35
- 'Deaths'.paint_red,
36
- 'Deaths Today'.paint_red,
37
- 'Recovered'.paint_green,
38
- 'Active'.paint_yellow,
39
- 'Critical'.paint_red
40
- ].freeze
41
-
42
- COMPARE_COUNTRY_TABLE_FULL = [
43
- 'Country'.paint_white,
44
- 'Cases'.paint_white,
45
- 'Deaths'.paint_red,
46
- 'Recovered'.paint_green,
47
- 'Cases Today'.paint_white,
48
- 'Deaths Today'.paint_red,
49
- 'Critical'.paint_yellow,
50
- 'Cases/Million'.paint_white
51
- ].freeze
52
-
53
- COMPARE_COUNTRIES_TABLE_HEADINGS = [
54
- 'Country'.paint_white,
55
- 'Cases'.paint_white,
56
- 'Cases Today'.paint_white,
57
- 'Deaths'.paint_red,
58
- 'Deaths Today'.paint_red,
59
- 'Recovered'.paint_green
60
- ].freeze
61
-
62
- FULL_COUNTRY_TABLE_HEADINGS = [
63
- 'Cases'.paint_white,
64
- 'Deaths'.paint_red,
65
- 'Recovered'.paint_green,
66
- 'Cases Today'.paint_white,
67
- 'Deaths Today'.paint_red,
68
- 'Critical'.paint_yellow,
69
- 'Cases/Million'.paint_white
70
- ].freeze
71
-
72
- COMPARE_STATES_HEADINGS = [
73
- 'State'.paint_white,
74
- 'Cases'.paint_white,
75
- 'Cases Today'.paint_white,
76
- 'Deaths'.paint_red,
77
- 'Deaths Today'.paint_red,
78
- 'Active'.paint_yellow
79
- ].freeze
80
-
81
- COMPARE_PROVINCES_HEADINGS = [
82
- 'Province'.paint_white,
83
- 'Confirmed'.paint_white,
84
- 'Deaths'.paint_red,
85
- 'Recovered'.paint_green
86
- ].freeze
87
-
88
- FOOTER_LINE = ['------------', '------------', '------------', '------------'].freeze
89
- COUNTRY_LETTERS = 'A'.upto('Z').with_index(127_462).to_h.freeze
90
-
91
- RIGHT_ALIGN_COLUMNS = {
92
- compare_country_table_full: [1, 2, 3, 4, 5, 6, 7],
93
- compare_country_table: [1, 2, 3, 4, 5],
94
- compare_us_states: [1, 2, 3, 4, 5],
95
- compare_provinces: [1, 2, 3]
96
- }.freeze
13
+ extend Kovid::Constants
14
+ extend Kovid::Aggregators
15
+ extend Kovid::Historians
97
16
 
17
+ class << self
98
18
  def country_table(data)
99
19
  rows = [
100
20
  [
@@ -162,7 +82,13 @@ module Kovid
162
82
  ]
163
83
 
164
84
  rows = []
165
- rows << [state['cases'], check_if_positve(state['todayCases']), state['deaths'], check_if_positve(state['todayDeaths']), state['active']]
85
+ rows << [
86
+ comma_delimit(state['cases']),
87
+ check_if_positve(state['todayCases']),
88
+ comma_delimit(state['deaths']),
89
+ check_if_positve(state['todayDeaths']),
90
+ comma_delimit(state['active'])
91
+ ]
166
92
 
167
93
  Terminal::Table.new(title: state['state'].upcase, headings: headings, rows: rows)
168
94
  end
@@ -266,126 +192,6 @@ module Kovid
266
192
  Terminal::Table.new(title: '🌍 Total Number of Incidents Worldwide'.upcase, headings: headings, rows: rows)
267
193
  end
268
194
 
269
- def history(country, last)
270
- # Write checks for when country is spelt wrong.
271
- headings = DATE_CASES_DEATHS_RECOVERED
272
- rows = []
273
-
274
- stats = if last
275
- transpose(country).last(last.to_i)
276
- else
277
- transpose(country)
278
- end
279
-
280
- dates = if last
281
- country['timeline']['cases'].keys.last(last.to_i)
282
- else
283
- country['timeline']['cases'].keys
284
- end
285
-
286
- unless last
287
- stats = stats.reject { |stat| stat[0].to_i.zero? && stat[1].to_i.zero? }
288
- dates = dates.last(stats.count)
289
- end
290
-
291
- stats.each_with_index do |val, index|
292
- val.unshift(Kovid.dateman(dates[index]))
293
- end.each do |row|
294
- rows << row
295
- end
296
-
297
- if stats.size > 10
298
- rows << FOOTER_LINE
299
- rows << DATE_CASES_DEATHS_RECOVERED
300
- end
301
-
302
- Terminal::Table.new(
303
- title: country['country'].upcase,
304
- headings: headings,
305
- rows: rows
306
- )
307
- end
308
-
309
- def histogram(country, date_string)
310
- @date = date_string.split('.')
311
-
312
- if @date.last.to_i != 20
313
- Kovid.info_table('Only 2020 histgrams are available.')
314
- return
315
- end
316
-
317
- # From dates where number of !cases.zero?
318
- positive_cases_figures = country['timeline']['cases'].values.reject(&:zero?)
319
- dates = country['timeline']['cases'].reject { |_k, v| v.zero? }.keys
320
- data = []
321
-
322
- # Improve this later, like everything else.
323
- # Returns array of days.to_i from the date param
324
- dates = dates.map do |date|
325
- date.split('/')
326
- end.select do |date|
327
- date.last == @date.last
328
- end.select do |date|
329
- date.first == @date.first
330
- end.map do |array|
331
- array[1]
332
- end.map(&:to_i).last(positive_cases_figures.count)
333
-
334
- # Arranges dates and figures in [x,y] for histogram
335
- # With x being day, y being number of cases
336
- if dates.empty?
337
- if @date.first.to_i > Time.now.month
338
- msgs = [
339
- 'Seriously...??! 😏', 'Did you just check the future??',
340
- 'You just checked the future Morgan.',
341
- 'Knowing too much of your future is never a good thing.'
342
- ]
343
-
344
- Kovid.info_table(msgs.sample)
345
- else
346
- Kovid.info_table('Check your spelling/No infections for this month.')
347
- end
348
-
349
- else
350
- dates.each_with_index do |val, index|
351
- data << [val, positive_cases_figures[index]]
352
- end
353
- y_range = AsciiCharts::Cartesian.new(data, bar: true, hide_zero: true).y_range
354
-
355
- last_two_y = y_range.last 2
356
- y_interval = last_two_y.last - last_two_y.first
357
-
358
- scale("Scale on Y: #{y_interval}:#{(y_interval / last_two_y.last.to_f * positive_cases_figures.last).round(2) / y_interval}")
359
-
360
- puts 'Experimental feature, please report issues.'
361
-
362
- AsciiCharts::Cartesian.new(data, bar: true, hide_zero: true).draw
363
- end
364
- end
365
-
366
- def eu_aggregate(eu_data)
367
- aggregated_table(eu_data, 'The EU', Kovid::Request::EU_ISOS, '🇪🇺')
368
- end
369
-
370
- def europe_aggregate(europe_data)
371
- aggregated_table(europe_data, 'Europe', Kovid::Request::EUROPE_ISOS, '🏰')
372
- end
373
-
374
- def africa_aggregate(africa_data)
375
- aggregated_table(africa_data, 'Africa',
376
- Kovid::Request::AFRICA_ISOS, '🌍')
377
- end
378
-
379
- def south_america_aggregate(south_america_data)
380
- aggregated_table(south_america_data,
381
- 'South America',
382
- Kovid::Request::SOUTH_AMERICA_ISOS, '🌎')
383
- end
384
-
385
- def asia_aggregate(asia_data)
386
- aggregated_table(asia_data, 'Asia', Kovid::Request::ASIA_ISOS, '🌏')
387
- end
388
-
389
195
  private
390
196
 
391
197
  def comma_delimit(number)
data/lib/kovid/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Kovid
4
- VERSION = '0.6.2'
4
+ VERSION = '0.6.3'
5
5
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: kovid
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.6.2
4
+ version: 0.6.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Emmanuel Hayford
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2020-04-06 00:00:00.000000000 Z
11
+ date: 2020-04-09 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: ascii_charts
@@ -118,9 +118,12 @@ files:
118
118
  - exe/kovid
119
119
  - kovid.gemspec
120
120
  - lib/kovid.rb
121
+ - lib/kovid/aggregators.rb
121
122
  - lib/kovid/cache.rb
122
123
  - lib/kovid/cli.rb
124
+ - lib/kovid/constants.rb
123
125
  - lib/kovid/helpers.rb
126
+ - lib/kovid/historians.rb
124
127
  - lib/kovid/painter.rb
125
128
  - lib/kovid/request.rb
126
129
  - lib/kovid/tablelize.rb