kovid 0.6.8 → 0.6.13

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: 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