kovid 0.6.8 → 0.6.13

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: 68cf72c0e99add59625ce1a9763db32b5eeecb69220d78b236e929f08adb1dec
4
- data.tar.gz: 84d95391d43e67b62f865f6e0ab585700aae3790e1f968793d11a99bf02e6ce8
3
+ metadata.gz: 4c0bc9550073aaff0f2e6fb4a4175b4147bacf6c43d4d47d0177771845d30f01
4
+ data.tar.gz: 5e960a7772a01ad8c67c77284eaebc46cb55827522e6890f62b935b87d6e3494
5
5
  SHA512:
6
- metadata.gz: daf6517f2d15ab2670bb9ad1124faa683db07eb3308a37d057b6af08b0d5a3f2ada1a4f76f54048556878df14435a71a82632a2995c28a6c830755aff7263338
7
- data.tar.gz: 77862be6a1adf08d40568326bcf24a4aa1ae0c60ad5f0b462b7ef3e5e1b64292c812e0c96b47d70702dad2635ee3640739883a5afbd28b2e2411b2488a23c279
6
+ metadata.gz: 7f65a6ea777bde11958ab2c3d43f73b63d83d23b18f5c21757be1e46e8ebda407b762714b32d1cab819cde75a41344735799670e41b17bd9e5a175d2e52892a0
7
+ data.tar.gz: d542e777ce15d0d0236faf62264d7fd2679b4655295e51be4871ba2f1c4990fb658f9d02fc6824357a73cb94e8cd54013b8a5106819e21ded1ad6d9c07b0dbcd
data/Gemfile CHANGED
@@ -6,4 +6,4 @@ source 'https://rubygems.org'
6
6
  gemspec
7
7
 
8
8
  gem 'rake', '~> 12.0'
9
- gem 'rspec', '~> 3.0'
9
+ gem 'rspec', '~> 3.0'
data/Gemfile.lock CHANGED
@@ -1,8 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- kovid (0.6.8)
5
- ascii_charts (~> 0.9.1)
4
+ kovid (0.6.12)
6
5
  carmen (~> 1.1.3)
7
6
  rainbow (~> 3.0)
8
7
  terminal-table (~> 1.8)
@@ -18,7 +17,6 @@ GEM
18
17
  minitest (~> 5.1)
19
18
  tzinfo (~> 1.1)
20
19
  zeitwerk (~> 2.2)
21
- ascii_charts (0.9.1)
22
20
  carmen (1.1.3)
23
21
  activesupport (>= 3.0.0)
24
22
  concurrent-ruby (1.1.6)
data/README.md CHANGED
@@ -74,6 +74,13 @@ ___
74
74
  😷 **History**
75
75
  * `kovid history COUNTRY` (full history).
76
76
  * `kovid history COUNTRY N` (history in the last N days).
77
+ * `kovid history STATE --usa`
78
+ ___
79
+ 😷 **Top N (by cases/deaths for countries and US States)**
80
+ * `kovid top N` (top N countries in number of cases).
81
+ * `kovid top N -d` OR `kovid top N --deaths` (top N countries in number of deaths).
82
+ * `kovid top N --states` (top N US states in number of cases).
83
+ * `kovid top N --states -d` (top N countries in number of deaths).
77
84
  ___
78
85
 
79
86
  **NOTE:** If you find it irritating to have to type `kovid state STATE`, `covid state STATE` works as well.
@@ -138,6 +145,32 @@ To check for total figures:
138
145
 
139
146
  ![kovid](https://i.gyazo.com/e01f4769a2b9e31ce50cec212e55810c.png "Covid data.")
140
147
 
148
+ To fetch top 5 countries in number of cases or deaths:
149
+
150
+ `kovid top`
151
+
152
+ ![kovid](https://i.gyazo.com/79443079a6c834094fc21c90dd02b78c.png "Covid data.")
153
+
154
+ `kovid top --deaths` OR `kovid top -d`
155
+
156
+ ![kovid](https://i.gyazo.com/8136a7acc2cb67d1621b3db0df822cd5.png "Covid data.")
157
+
158
+ It is also possible to fetch top US states in number of cases or deaths:
159
+
160
+ `kovid top --states`
161
+
162
+ ![kovid](https://i.gyazo.com/7ee5a1e6affdec838783183024c4604d.png "Covid data.")
163
+
164
+ `kovid top --states --deaths` OR `kovid top --states -d`
165
+
166
+ ![kovid](https://i.gyazo.com/2c3cb7e1218deff44c9d440dab93a3b1.png "Covid data.")
167
+
168
+ To fetch more number of countries or US states you can pass N. eg:
169
+
170
+ `kovid top 10`
171
+
172
+ ![kovid](https://i.gyazo.com/64663ff25c1ff61701e84871948640f4.png "Covid data.")
173
+
141
174
  ## 👩🏾‍🔬 Experimental Feature
142
175
 
143
176
  `kovid histogram italy 3.20` tries to build a histogram on number of cases. In the example here `3.20` is date in the format of `M.YY`. If you can please play with it and report issues on how this could be improved.
data/kovid.gemspec CHANGED
@@ -22,12 +22,11 @@ Gem::Specification.new do |spec|
22
22
  spec.metadata['source_code_uri'] = 'https://github.com/siaw23/kovid'
23
23
  spec.metadata['changelog_uri'] = 'https://github.com/siaw23/kovid'
24
24
 
25
- spec.add_dependency 'ascii_charts', '~> 0.9.1'
25
+ spec.add_dependency 'carmen', '~> 1.1.3'
26
26
  spec.add_dependency 'rainbow', '~> 3.0'
27
27
  spec.add_dependency 'terminal-table', '~> 1.8'
28
28
  spec.add_dependency 'thor', '~> 1.0'
29
29
  spec.add_dependency 'typhoeus', '~> 1.3'
30
- spec.add_dependency 'carmen', '~> 1.1.3'
31
30
  spec.add_development_dependency 'simplecov', '~> 0.18'
32
31
 
33
32
  # Specify which files should be added to the gem when it is released.
data/lib/kovid.rb CHANGED
@@ -68,11 +68,19 @@ module Kovid
68
68
  Kovid::Request.cases
69
69
  end
70
70
 
71
- def history(country, last)
72
- Kovid::Request.history(country, last)
71
+ def history(country, days = 30)
72
+ Kovid::Request.history(country, days)
73
+ end
74
+
75
+ def history_us_state(state, days = 30)
76
+ Kovid::Request.history_us_state(state, days)
73
77
  end
74
78
 
75
79
  def histogram(country, date)
76
80
  Kovid::Request.histogram(country, date)
77
81
  end
82
+
83
+ def top(count, options = { location: :countries, incident: :cases })
84
+ Kovid::Request.top(count, options)
85
+ end
78
86
  end
@@ -0,0 +1,276 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Copyright (c) 2011 Ben Lund
4
+
5
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ # of this software and associated documentation files (the "Software"), to deal
7
+ # in the Software without restriction, including without limitation the rights
8
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ # copies of the Software, and to permit persons to whom the Software is
10
+ # furnished to do so, subject to the following conditions:
11
+
12
+ # The above copyright notice and this permission notice shall be included in
13
+ # all copies or substantial portions of the Software.
14
+
15
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ # THE SOFTWARE.
22
+
23
+ module Kovid
24
+ module AsciiCharts
25
+ VERSION = '0.9.1'
26
+
27
+ class Chart
28
+ attr_reader :options, :data
29
+
30
+ DEFAULT_MAX_Y_VALS = 20
31
+ DEFAULT_MIN_Y_VALS = 10
32
+
33
+ # data is a sorted array of [x, y] pairs
34
+
35
+ def initialize(data, options = {})
36
+ @data = data
37
+ @options = options
38
+ end
39
+
40
+ def rounded_data
41
+ @rounded_data ||= data.map { |pair| [pair[0], round_value(pair[1])] }
42
+ end
43
+
44
+ def step_size
45
+ unless defined? @step_size
46
+ if options[:y_step_size]
47
+ @step_size = options[:y_step_size]
48
+ else
49
+ max_y_vals = options[:max_y_vals] || DEFAULT_MAX_Y_VALS
50
+ min_y_vals = options[:max_y_vals] || DEFAULT_MIN_Y_VALS
51
+ y_span = (max_yval - min_yval).to_f
52
+
53
+ step_size = nearest_step(y_span.to_f / (data.size + 1))
54
+
55
+ if @all_ints && (step_size < 1)
56
+ step_size = 1
57
+ else
58
+ while (y_span / step_size) < min_y_vals
59
+ candidate_step_size = next_step_down(step_size)
60
+ if @all_ints && (candidate_step_size < 1)
61
+ break
62
+ end ## don't go below one
63
+
64
+ step_size = candidate_step_size
65
+ end
66
+ end
67
+
68
+ # go up if we undershot, or were never over
69
+ while (y_span / step_size) > max_y_vals
70
+ step_size = next_step_up(step_size)
71
+ end
72
+ @step_size = step_size
73
+ end
74
+ if !@all_ints && @step_size.is_a?(Integer)
75
+ @step_size = @step_size.to_f
76
+ end
77
+ end
78
+ @step_size
79
+ end
80
+
81
+ STEPS = [1, 2, 5].freeze
82
+
83
+ def from_step(val)
84
+ if val == 0
85
+ [0, 0]
86
+ else
87
+ order = Math.log10(val).floor.to_i
88
+ num = (val / (10**order))
89
+ [num, order]
90
+ end
91
+ end
92
+
93
+ def to_step(num, order)
94
+ s = num * (10**order)
95
+ if order < 0
96
+ s.to_f
97
+ else
98
+ s
99
+ end
100
+ end
101
+
102
+ def nearest_step(val)
103
+ num, order = from_step(val)
104
+ to_step(2, order) # #@@
105
+ end
106
+
107
+ def next_step_up(val)
108
+ num, order = from_step(val)
109
+ next_index = STEPS.index(num.to_i) + 1
110
+ if STEPS.size == next_index
111
+ next_index = 0
112
+ order += 1
113
+ end
114
+ to_step(STEPS[next_index], order)
115
+ end
116
+
117
+ def next_step_down(val)
118
+ num, order = from_step(val)
119
+ next_index = STEPS.index(num.to_i) - 1
120
+ if next_index == -1
121
+ STEPS.size - 1
122
+ order -= 1
123
+ end
124
+ to_step(STEPS[next_index], order)
125
+ end
126
+
127
+ # round to nearest step size, making sure we curtail precision to same order of magnitude as the step size to avoid 0.4 + 0.2 = 0.6000000000000001
128
+ def round_value(val)
129
+ remainder = val % step_size
130
+ unprecised = if (remainder * 2) >= step_size
131
+ (val - remainder) + step_size
132
+ else
133
+ val - remainder
134
+ end
135
+ if step_size < 1
136
+ precision = -Math.log10(step_size).floor
137
+ (unprecised * (10**precision)).to_i.to_f / (10**precision)
138
+ else
139
+ unprecised
140
+ end
141
+ end
142
+
143
+ def max_yval
144
+ scan_data unless defined? @max_yval
145
+ @max_yval
146
+ end
147
+
148
+ def min_yval
149
+ scan_data unless defined? @min_yval
150
+ @min_yval
151
+ end
152
+
153
+ def all_ints
154
+ scan_data unless defined? @all_ints
155
+ @all_ints
156
+ end
157
+
158
+ def scan_data
159
+ @max_yval = 0
160
+ @min_yval = 0
161
+ @all_ints = true
162
+
163
+ @max_xval_width = 1
164
+
165
+ data.each do |pair|
166
+ @max_yval = pair[1] if pair[1] > @max_yval
167
+ @min_yval = pair[1] if pair[1] < @min_yval
168
+ @all_ints = false if @all_ints && !pair[1].is_a?(Integer)
169
+
170
+ if (xw = pair[0].to_s.length) > @max_xval_width
171
+ @max_xval_width = xw
172
+ end
173
+ end
174
+ end
175
+
176
+ def max_xval_width
177
+ scan_data unless defined? @max_xval_width
178
+ @max_xval_width
179
+ end
180
+
181
+ def max_yval_width
182
+ scan_y_range unless defined? @max_yval_width
183
+ @max_yval_width
184
+ end
185
+
186
+ def scan_y_range
187
+ @max_yval_width = 1
188
+
189
+ y_range.each do |yval|
190
+ if (yw = yval.to_s.length) > @max_yval_width
191
+ @max_yval_width = yw
192
+ end
193
+ end
194
+ end
195
+
196
+ def y_range
197
+ unless defined? @y_range
198
+ @y_range = []
199
+ first_y = round_value(min_yval)
200
+ first_y -= step_size if first_y > min_yval
201
+ last_y = round_value(max_yval)
202
+ last_y += step_size if last_y < max_yval
203
+ current_y = first_y
204
+ while current_y <= last_y
205
+ @y_range << current_y
206
+ current_y = round_value(current_y + step_size) ## to avoid fp arithmetic oddness
207
+ end
208
+ end
209
+ @y_range
210
+ end
211
+
212
+ def lines
213
+ raise 'lines must be overridden'
214
+ end
215
+
216
+ def draw
217
+ lines.join("\n")
218
+ end
219
+
220
+ def to_string
221
+ draw
222
+ end
223
+ end
224
+
225
+ class Cartesian < Chart
226
+ def lines
227
+ return [[' ', options[:title], ' ', '|', '+-', ' ']] if data.empty?
228
+
229
+ lines = [' ']
230
+
231
+ bar_width = max_xval_width + 1
232
+
233
+ lines << (' ' * max_yval_width) + ' ' + rounded_data.map { |pair| pair[0].to_s.center(bar_width) }.join('')
234
+
235
+ y_range.each_with_index do |current_y, i|
236
+ yval = current_y.to_s
237
+ bar = if i == 0
238
+ '+'
239
+ else
240
+ '|'
241
+ end
242
+ current_line = [(' ' * (max_yval_width - yval.length)) + "#{current_y}#{bar}"]
243
+
244
+ rounded_data.each do |pair|
245
+ marker = if (i == 0) && options[:hide_zero]
246
+ '-'
247
+ else
248
+ '*'
249
+ end
250
+ filler = if i == 0
251
+ '-'
252
+ else
253
+ ' '
254
+ end
255
+ comparison = if options[:bar]
256
+ current_y <= pair[1]
257
+ else
258
+ current_y == pair[1]
259
+ end
260
+ current_line << if comparison
261
+ marker.center(bar_width, filler)
262
+ else
263
+ filler * bar_width
264
+ end
265
+ end
266
+ lines << current_line.join('')
267
+ current_y += step_size
268
+ end
269
+ lines << ' '
270
+ lines << options[:title].center(lines[1].length) if options[:title]
271
+ lines << ' '
272
+ lines.reverse
273
+ end
274
+ end
275
+ end
276
+ end
data/lib/kovid/cli.rb CHANGED
@@ -75,13 +75,15 @@ module Kovid
75
75
  data_source
76
76
  end
77
77
 
78
- desc 'history COUNTRY or history COUNTRY N',
79
- 'Return history of incidents of COUNTRY (in the last N days)'
80
- def history(*params)
81
- if params.size == 2
82
- puts Kovid.history(params.first, params.last)
78
+ desc 'history COUNTRY or history COUNTRY N or' \
79
+ 'history STATE --usa',
80
+ 'Return history of incidents of COUNTRY|STATE (in the last N days)'
81
+ option :usa, type: :boolean
82
+ def history(location, days = 30)
83
+ if options[:usa]
84
+ puts Kovid.history_us_state(location, days)
83
85
  else
84
- puts Kovid.history(params.first, nil)
86
+ puts Kovid.history(location, days)
85
87
  end
86
88
  data_source
87
89
  end
@@ -131,6 +133,20 @@ module Kovid
131
133
  end
132
134
  end
133
135
 
136
+ desc 'top N',
137
+ 'Returns top N countries or states in an incident (number of cases or
138
+ deaths).'
139
+ method_option :countries
140
+ method_option :states
141
+ method_option :cases, aliases: '-c'
142
+ method_option :deaths, aliases: '-d'
143
+ def top(count = 5)
144
+ count = count.to_i
145
+ count = 5 if count.zero?
146
+ puts Kovid.top(count, prepare_top_params(options))
147
+ data_source
148
+ end
149
+
134
150
  private
135
151
 
136
152
  def fetch_country_stats(country)
@@ -145,10 +161,26 @@ module Kovid
145
161
  source = <<~TEXT
146
162
  #{Time.now}
147
163
  Sources:
148
- * JHU CSSE GISand Data
149
- * https://www.worldometers.info/coronavirus/
164
+ * worldometers.info/coronavirus
165
+ * Johns Hopkins University
150
166
  TEXT
151
167
  puts source
152
168
  end
169
+
170
+ def prepare_top_params(options)
171
+ params = {
172
+ location: :countries,
173
+ incident: :cases
174
+ }
175
+
176
+ if !options[:states].nil? && options[:countries].nil?
177
+ params[:location] = :states
178
+ end
179
+
180
+ if !options[:deaths].nil? && options[:cases].nil?
181
+ params[:incident] = :deaths
182
+ end
183
+ params
184
+ end
153
185
  end
154
186
  end
@@ -13,7 +13,8 @@ module Kovid
13
13
  'Cases Today'.paint_white,
14
14
  'Deaths'.paint_red,
15
15
  'Deaths Today'.paint_red,
16
- 'Recovered'.paint_green
16
+ 'Recovered'.paint_green,
17
+ 'Tests'.paint_white,
17
18
  ].freeze
18
19
 
19
20
  DATE_CASES_DEATHS_RECOVERED = [
@@ -23,6 +24,12 @@ module Kovid
23
24
  'Recovered'.paint_green
24
25
  ].freeze
25
26
 
27
+ DATE_CASES_DEATHS = [
28
+ 'Date'.paint_white,
29
+ 'Cases'.paint_white,
30
+ 'Deaths'.paint_red
31
+ ].freeze
32
+
26
33
  CONTINENTAL_AGGREGATE_HEADINGS = [
27
34
  'Cases'.paint_white,
28
35
  'Cases Today'.paint_white,
@@ -50,7 +57,8 @@ module Kovid
50
57
  'Cases Today'.paint_white,
51
58
  'Deaths'.paint_red,
52
59
  'Deaths Today'.paint_red,
53
- 'Recovered'.paint_green
60
+ 'Recovered'.paint_green,
61
+ 'Tests'.paint_white
54
62
  ].freeze
55
63
 
56
64
  FULL_COUNTRY_TABLE_HEADINGS = [
@@ -74,7 +82,8 @@ module Kovid
74
82
  'Cases Today'.paint_white,
75
83
  'Deaths'.paint_red,
76
84
  'Deaths Today'.paint_red,
77
- 'Active'.paint_yellow
85
+ 'Active'.paint_yellow,
86
+ 'Tests'.paint_white
78
87
  ].freeze
79
88
 
80
89
  COMPARE_STATES_HEADINGS = [
@@ -93,13 +102,14 @@ module Kovid
93
102
  'Recovered'.paint_green
94
103
  ].freeze
95
104
 
96
- FOOTER_LINE = [
97
- '------------',
98
- '------------',
99
- '------------',
105
+ FOOTER_LINE_COLUMN = [
100
106
  '------------'
101
107
  ].freeze
102
108
 
109
+ FOOTER_LINE_THREE_COLUMNS = FOOTER_LINE_COLUMN * 3
110
+
111
+ FOOTER_LINE_FOUR_COLUMNS = FOOTER_LINE_COLUMN * 4
112
+
103
113
  COUNTRY_LETTERS = 'A'.upto('Z').with_index(127_462).to_h.freeze
104
114
 
105
115
  RIGHT_ALIGN_COLUMNS = {
@@ -3,24 +3,15 @@
3
3
  module Kovid
4
4
  module Historians
5
5
  include Constants
6
+ include AsciiCharts
6
7
 
7
- def history(country, last)
8
- # TODO: Write checks for when country is spelt wrong.
8
+ def history(location, days)
9
9
  rows = []
10
10
 
11
- stats = if last
12
- Kovid.format_country_history_numbers(country).last(last.to_i)
13
- else
14
- Kovid.format_country_history_numbers(country)
15
- end
11
+ stats = Kovid.format_country_history_numbers(location).last(days.to_i)
12
+ dates = location['timeline']['cases'].keys.last(days.to_i)
16
13
 
17
- dates = if last
18
- country['timeline']['cases'].keys.last(last.to_i)
19
- else
20
- country['timeline']['cases'].keys
21
- end
22
-
23
- unless last
14
+ if days.to_i >= 30
24
15
  stats = stats.reject { |stat| stat[0].to_i.zero? && stat[1].to_i.zero? }
25
16
  dates = dates.last(stats.count)
26
17
  end
@@ -31,14 +22,25 @@ module Kovid
31
22
  rows << row
32
23
  end
33
24
 
25
+ # Title and Column format if Country or US State
26
+ if location['country']
27
+ title = location['country'].try(:upcase)
28
+ col_format = DATE_CASES_DEATHS_RECOVERED
29
+ footer = FOOTER_LINE_FOUR_COLUMNS
30
+ else
31
+ title = location['state'].try(:upcase)
32
+ col_format = DATE_CASES_DEATHS
33
+ footer = FOOTER_LINE_THREE_COLUMNS
34
+ end
35
+
34
36
  if stats.size > 10
35
- rows << FOOTER_LINE
36
- rows << DATE_CASES_DEATHS_RECOVERED
37
+ rows << footer
38
+ rows << col_format
37
39
  end
38
40
 
39
41
  Terminal::Table.new(
40
- title: country['country'].upcase,
41
- headings: DATE_CASES_DEATHS_RECOVERED,
42
+ title: title,
43
+ headings: col_format,
42
44
  rows: rows
43
45
  )
44
46
  end
@@ -88,7 +90,7 @@ module Kovid
88
90
  dates.each_with_index do |val, index|
89
91
  data << [val, positive_cases_figures[index]]
90
92
  end
91
- y_range = AsciiCharts::Cartesian.new(
93
+ y_range = Kovid::AsciiCharts::Cartesian.new(
92
94
  data, bar: true, hide_zero: true
93
95
  ).y_range
94
96
 
data/lib/kovid/request.rb CHANGED
@@ -8,26 +8,28 @@ require_relative 'uri_builder'
8
8
 
9
9
  module Kovid
10
10
  class Request
11
- COUNTRIES_PATH = UriBuilder.new('/v2/countries').url
12
- STATES_URL = UriBuilder.new('/v2/states').url
13
- JHUCSSE_URL = UriBuilder.new('/v2/jhucsse').url
14
-
15
- SERVER_DOWN = 'Server overwhelmed. Please try again in a moment.'
16
-
17
- EU_ISOS = %w[AT BE BG CY CZ DE DK EE ES FI FR GR HR HU IE IT LT \
18
- LU LV MT NL PL PT RO SE SI SK].freeze
19
- EUROPE_ISOS = EU_ISOS + %w[GB IS NO CH MC AD SM VA BA RS ME MK AL \
20
- BY UA RU MD].freeze
21
- AFRICA_ISOS = %w[DZ AO BJ BW BF BI CM CV CF TD KM CD CG CI DJ EG \
22
- GQ ER SZ ET GA GM GH GN GW KE LS LR LY MG MW ML \
23
- MR MU MA MZ NA NE NG RW ST SN SC SL SO ZA SS SD \
24
- TZ TG TN UG ZM ZW EH].freeze
25
- SOUTH_AMERICA_ISOS = %w[AR BO BV BR CL CO EC FK GF GY PY PE GS SR \
26
- UY VE].freeze
27
- ASIA_ISOS = %w[AE AF AM AZ BD BH BN BT CC CN CX GE HK ID IL IN \
28
- IQ IR JO JP KG KH KP KR KW KZ LA LB LK MM MN MO \
29
- MY NP OM PH PK PS QA SA SG SY TH TJ TL TM TR TW \
30
- UZ VN YE].freeze
11
+ COUNTRIES_PATH = UriBuilder.new('/v2/countries').url
12
+ STATES_URL = UriBuilder.new('/v2/states').url
13
+ JHUCSSE_URL = UriBuilder.new('/v2/jhucsse').url
14
+ HISTORICAL_URL = UriBuilder.new('/v2/historical').url
15
+ HISTORICAL_US_URL = UriBuilder.new('/v2/historical/usacounties').url
16
+
17
+ SERVER_DOWN = 'Server overwhelmed. Please try again in a moment.'
18
+
19
+ EU_ISOS = %w[AT BE BG CY CZ DE DK EE ES FI FR GR HR HU IE IT LT \
20
+ LU LV MT NL PL PT RO SE SI SK].freeze
21
+ EUROPE_ISOS = EU_ISOS + %w[GB IS NO CH MC AD SM VA BA RS ME MK AL \
22
+ BY UA RU MD].freeze
23
+ AFRICA_ISOS = %w[DZ AO BJ BW BF BI CM CV CF TD KM CD CG CI DJ EG \
24
+ GQ ER SZ ET GA GM GH GN GW KE LS LR LY MG MW ML \
25
+ MR MU MA MZ NA NE NG RW ST SN SC SL SO ZA SS SD \
26
+ TZ TG TN UG ZM ZW EH].freeze
27
+ SOUTH_AMERICA_ISOS = %w[AR BO BV BR CL CO EC FK GF GY PY PE GS SR \
28
+ UY VE].freeze
29
+ ASIA_ISOS = %w[AE AF AM AZ BD BH BN BT CC CN CX GE HK ID IL IN \
30
+ IQ IR JO JP KG KH KP KR KW KZ LA LB LK MM MN MO \
31
+ MY NP OM PH PK PS QA SA SG SY TH TJ TL TM TR TW \
32
+ UZ VN YE].freeze
31
33
 
32
34
  class << self
33
35
  def eu_aggregate
@@ -111,6 +113,7 @@ module Kovid
111
113
 
112
114
  def state(state)
113
115
  response = fetch_state(Kovid.lookup_us_state(state))
116
+
114
117
  if response.nil?
115
118
  not_found(state)
116
119
  else
@@ -158,15 +161,53 @@ module Kovid
158
161
  puts SERVER_DOWN
159
162
  end
160
163
 
161
- def history(country, last)
164
+ def history(country, days)
162
165
  history_path = UriBuilder.new('/v2/historical').url
166
+
163
167
  response = JSON.parse(
164
168
  Typhoeus.get(
165
169
  history_path + "/#{country}", cache_ttl: 900
166
170
  ).response_body
167
171
  )
168
172
 
169
- Kovid::Tablelize.history(response, last)
173
+ if response.key?('message')
174
+ not_found(country) do |c|
175
+ "Could not find cases for #{c}.\nIf searching United States, add --usa option"
176
+ end
177
+ else
178
+ Kovid::Tablelize.history(response, days)
179
+ end
180
+ rescue JSON::ParserError
181
+ puts SERVER_DOWN
182
+ end
183
+
184
+ def history_us_state(state, days)
185
+ history_path = UriBuilder.new('/v2/historical/usacounties').url
186
+ state = Kovid.lookup_us_state(state).downcase
187
+
188
+ response = JSON.parse(
189
+ Typhoeus.get(
190
+ history_path + "/#{state}", cache_ttl: 900
191
+ ).response_body
192
+ )
193
+
194
+ if response.respond_to?(:key?) && response.key?('message')
195
+ return not_found(state)
196
+ end
197
+
198
+ # API Endpoint returns list of counties for given state, so
199
+ # we aggreage cases for all counties
200
+ # Note: no data for 'Recovered'
201
+ cases = usacounties_aggregator(response, 'cases')
202
+ deaths = usacounties_aggregator(response, 'deaths')
203
+
204
+ # normalize data so we can call Kovid::Tablelize.history on US State data
205
+ response = {
206
+ 'state' => state,
207
+ 'timeline' => { 'cases' => cases, 'deaths' => deaths }
208
+ }
209
+
210
+ Kovid::Tablelize.history(response, days)
170
211
  rescue JSON::ParserError
171
212
  puts SERVER_DOWN
172
213
  end
@@ -182,15 +223,36 @@ module Kovid
182
223
  Kovid::Tablelize.histogram(response, date)
183
224
  end
184
225
 
226
+ def top(count, options)
227
+ response = JSON.parse(
228
+ Typhoeus.get(
229
+ top_url(options[:location]) +
230
+ "?sort=#{options[:incident]}",
231
+ cache_ttl: 900
232
+ ).response_body
233
+ )
234
+
235
+ Kovid::Tablelize.top(response.first(count),
236
+ options.merge({ count: count }))
237
+ end
238
+
185
239
  def capitalize_words(string)
186
240
  string.split.map(&:capitalize).join(' ')
187
241
  end
188
242
 
189
243
  private
190
244
 
191
- def not_found(country)
192
- rows = [["Wrong spelling/No reported cases on #{country.upcase}."]]
193
- Terminal::Table.new title: "You checked: #{country.upcase}", rows: rows
245
+ def not_found(location)
246
+ rows = []
247
+ default_warning = "Wrong spelling/No reported cases on #{location.upcase}."
248
+
249
+ rows << if block_given?
250
+ [yield(location)]
251
+ else
252
+ [default_warning]
253
+ end
254
+
255
+ Terminal::Table.new title: "You checked: #{location.upcase}", rows: rows
194
256
  end
195
257
 
196
258
  def fetch_countries(list)
@@ -278,6 +340,20 @@ module Kovid
278
340
  end
279
341
  end.compact
280
342
  end
343
+
344
+ def usacounties_aggregator(data, key = nil)
345
+ data.inject({}) do |base, other|
346
+ base.merge(other['timeline'][key]) do |_k, l, r|
347
+ l ||= 0
348
+ r ||= 0
349
+ l + r
350
+ end
351
+ end.compact
352
+ end
353
+
354
+ def top_url(location)
355
+ location == :countries ? COUNTRIES_PATH : STATES_URL
356
+ end
281
357
  end
282
358
  end
283
359
  end
@@ -2,7 +2,7 @@
2
2
 
3
3
  require 'terminal-table'
4
4
  require 'date'
5
- require 'ascii_charts'
5
+ require_relative 'ascii_charts'
6
6
  require_relative 'painter'
7
7
  require_relative 'constants'
8
8
  require_relative 'aggregators'
@@ -96,6 +96,22 @@ module Kovid
96
96
  )
97
97
  end
98
98
 
99
+ def top(data, options)
100
+ headings = top_heading(options)
101
+ rows = data.map { |location| top_row(location, options) }
102
+
103
+ if options[:count] > 10
104
+ rows << FOOTER_LINE_COLUMN * headings.count
105
+ rows << headings
106
+ end
107
+
108
+ Terminal::Table.new(
109
+ title: top_title(options),
110
+ headings: headings,
111
+ rows: rows
112
+ )
113
+ end
114
+
99
115
  private
100
116
 
101
117
  def country_title(data)
@@ -127,7 +143,8 @@ module Kovid
127
143
  Kovid.add_plus_sign(data['todayCases']),
128
144
  Kovid.comma_delimit(data['deaths']),
129
145
  Kovid.add_plus_sign(data['todayDeaths']),
130
- Kovid.comma_delimit(data['recovered'])
146
+ Kovid.comma_delimit(data['active']),
147
+ Kovid.comma_delimit(data['tests'])
131
148
  ]
132
149
  end
133
150
 
@@ -222,6 +239,34 @@ module Kovid
222
239
  end
223
240
  table
224
241
  end
242
+
243
+ def top_row(data, options)
244
+ if options[:location] == :countries
245
+ return [
246
+ country_title(data),
247
+ full_country_row(data)
248
+ ].flatten
249
+ end
250
+
251
+ [
252
+ data['state'].upcase,
253
+ country_row(data)
254
+ ].flatten
255
+ end
256
+
257
+ def top_heading(options)
258
+ if options[:location] == :countries
259
+ return ['Country'.paint_white] + FULL_COUNTRY_TABLE_HEADINGS
260
+ end
261
+
262
+ ['State'.paint_white] + FULL_STATE_TABLE_HEADINGS
263
+ end
264
+
265
+ def top_title(options)
266
+ incident = options[:incident].to_s
267
+ location = options[:location].to_s
268
+ "🌍 Top #{options[:count]} #{location} in #{incident}".upcase
269
+ end
225
270
  end
226
271
  end
227
272
  end
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.8'
4
+ VERSION = '0.6.13'
5
5
  end
metadata CHANGED
@@ -1,29 +1,29 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: kovid
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.6.8
4
+ version: 0.6.13
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-17 00:00:00.000000000 Z
11
+ date: 2020-05-13 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
- name: ascii_charts
14
+ name: carmen
15
15
  requirement: !ruby/object:Gem::Requirement
16
16
  requirements:
17
17
  - - "~>"
18
18
  - !ruby/object:Gem::Version
19
- version: 0.9.1
19
+ version: 1.1.3
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
24
  - - "~>"
25
25
  - !ruby/object:Gem::Version
26
- version: 0.9.1
26
+ version: 1.1.3
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: rainbow
29
29
  requirement: !ruby/object:Gem::Requirement
@@ -80,20 +80,6 @@ dependencies:
80
80
  - - "~>"
81
81
  - !ruby/object:Gem::Version
82
82
  version: '1.3'
83
- - !ruby/object:Gem::Dependency
84
- name: carmen
85
- requirement: !ruby/object:Gem::Requirement
86
- requirements:
87
- - - "~>"
88
- - !ruby/object:Gem::Version
89
- version: 1.1.3
90
- type: :runtime
91
- prerelease: false
92
- version_requirements: !ruby/object:Gem::Requirement
93
- requirements:
94
- - - "~>"
95
- - !ruby/object:Gem::Version
96
- version: 1.1.3
97
83
  - !ruby/object:Gem::Dependency
98
84
  name: simplecov
99
85
  requirement: !ruby/object:Gem::Requirement
@@ -135,6 +121,7 @@ files:
135
121
  - kovid.gemspec
136
122
  - lib/kovid.rb
137
123
  - lib/kovid/aggregators.rb
124
+ - lib/kovid/ascii_charts.rb
138
125
  - lib/kovid/cache.rb
139
126
  - lib/kovid/cli.rb
140
127
  - lib/kovid/constants.rb