kovid 0.6.2 → 0.6.3

Sign up to get free protection for your applications and to get access to all the features.
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