kovid 0.6.6 β†’ 0.6.11

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: 6abc6282e6ec84a393eee2b5383d70f88643d7633f8bd7537c207dae341ead1b
4
- data.tar.gz: a93e83c437e3b1f740fe7471cca72ad2e83c837d0a637c5fe8c71636bdd545dd
3
+ metadata.gz: 14f3598372d551bf07a3acd856b1eaa965aa6802794d8e7020fd9a234bb14df9
4
+ data.tar.gz: 5717fd5599aa8b3a6521c2dda5ccbe4e74580df794a0613d30e4bb09071abd9e
5
5
  SHA512:
6
- metadata.gz: dbc95ba0e85745d9369e15b0e81b6f5d36a7199b1227aaaec56eb57b8aac067750e4e2c71f937a97b333fdf57693547d6f98a09e70d214a5300b5b09cd5eeb25
7
- data.tar.gz: a2a458068aefa1f001ad85426ef5eba0317ba23a0320fab970eed818a9009186cc966f757c0a4f1b693a85cf668098ffa477acc3587849ed5864dcfc53a3b5fe
6
+ metadata.gz: 355d8fcc18cc07dd0e080722aa8d45a034b5da18c0ade97d2b052fb7603f4e2463d98447ea3dc985ec5bfb8682b12b88e7e74c0b26d2a55c99bad2f1fbaf04b0
7
+ data.tar.gz: d80ec1720c3b47b6906fe4be773e696a494b2f3877dc851b60ce8b6d8d8a00653b39cda43682082bbee3afd76a2bf08f1028d18d35056332b8d7ed71513939ae
@@ -2,3 +2,6 @@ inherit_from: .rubocop_todo.yml
2
2
 
3
3
  AllCops:
4
4
  TargetRubyVersion: 2.6
5
+
6
+ Style/Documentation:
7
+ Enabled: false
@@ -1,8 +1,8 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- kovid (0.6.5)
5
- ascii_charts (~> 0.9.1)
4
+ kovid (0.6.10)
5
+ carmen (~> 1.1.3)
6
6
  rainbow (~> 3.0)
7
7
  terminal-table (~> 1.8)
8
8
  thor (~> 1.0)
@@ -11,12 +11,23 @@ PATH
11
11
  GEM
12
12
  remote: https://rubygems.org/
13
13
  specs:
14
- ascii_charts (0.9.1)
14
+ activesupport (6.0.2.2)
15
+ concurrent-ruby (~> 1.0, >= 1.0.2)
16
+ i18n (>= 0.7, < 2)
17
+ minitest (~> 5.1)
18
+ tzinfo (~> 1.1)
19
+ zeitwerk (~> 2.2)
20
+ carmen (1.1.3)
21
+ activesupport (>= 3.0.0)
22
+ concurrent-ruby (1.1.6)
15
23
  diff-lcs (1.3)
16
24
  docile (1.3.2)
17
25
  ethon (0.12.0)
18
26
  ffi (>= 1.3.0)
19
27
  ffi (1.12.2)
28
+ i18n (1.8.2)
29
+ concurrent-ruby (~> 1.0)
30
+ minitest (5.14.0)
20
31
  rainbow (3.0.0)
21
32
  rake (12.3.3)
22
33
  rspec (3.9.0)
@@ -39,9 +50,13 @@ GEM
39
50
  terminal-table (1.8.0)
40
51
  unicode-display_width (~> 1.1, >= 1.1.1)
41
52
  thor (1.0.1)
53
+ thread_safe (0.3.6)
42
54
  typhoeus (1.3.1)
43
55
  ethon (>= 0.9.0)
56
+ tzinfo (1.2.7)
57
+ thread_safe (~> 0.1)
44
58
  unicode-display_width (1.7.0)
59
+ zeitwerk (2.3.0)
45
60
 
46
61
  PLATFORMS
47
62
  ruby
data/README.md CHANGED
@@ -46,6 +46,8 @@ You can fetch US state-specific data:
46
46
  * `kovid state STATE` OR `kovid state "STATE NAME"`.
47
47
  * `kovid states --all` or `kovid states -a` for data on all US states.
48
48
 
49
+ You can also use USPS abbreviations. Example: `kovid state me`
50
+
49
51
  Provinces
50
52
 
51
53
  You can fetch province specific data:
@@ -64,7 +66,7 @@ You can compare as many countries as you want; `kovid compare FOO BAR BAZ` OR `k
64
66
  πŸ‡ΊπŸ‡ΈπŸ‡ΊπŸ‡ΈπŸ‡ΊπŸ‡Έ
65
67
 
66
68
  You can compare US states with:
67
- * `kovid states STATE STATE` Example: `kovid states illinois "new york" california`
69
+ * `kovid states STATE STATE` Example: `kovid states illinois "new york" california` OR `kovid states il ny ca`
68
70
 
69
71
  You can compare provicnes with:
70
72
  * `kovid provinces PROVINCE PROVINCE` Example: `kovid provinces ontario manitoba`
@@ -72,6 +74,7 @@ ___
72
74
  😷 **History**
73
75
  * `kovid history COUNTRY` (full history).
74
76
  * `kovid history COUNTRY N` (history in the last N days).
77
+ * `kovid history STATE --usa`
75
78
  ___
76
79
 
77
80
  **NOTE:** If you find it irritating to have to type `kovid state STATE`, `covid state STATE` works as well.
@@ -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
-
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.
@@ -6,8 +6,6 @@ require 'kovid/request'
6
6
  module Kovid
7
7
  require 'kovid/helpers'
8
8
 
9
- class Error < StandardError; end
10
-
11
9
  module_function
12
10
 
13
11
  def eu_aggregate
@@ -70,8 +68,12 @@ module Kovid
70
68
  Kovid::Request.cases
71
69
  end
72
70
 
73
- def history(country, last)
74
- 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)
75
77
  end
76
78
 
77
79
  def histogram(country, date)
@@ -1,7 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Kovid
4
- # Sends info about continents to Tablelize
5
4
  module Aggregators
6
5
  def eu_aggregate(eu_data)
7
6
  aggregated_table(eu_data, 'The EU', Kovid::Request::EU_ISOS, 'πŸ‡ͺπŸ‡Ί')
@@ -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
@@ -1,11 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'typhoeus'
4
-
5
- # rubocop:disable Style/Documentation
6
4
  module Kovid
7
- # rubocop:enable Style/Documentation
8
- # Caches HTTP requests
9
5
  class Cache
10
6
  def initialize
11
7
  @memory = {}
@@ -4,8 +4,6 @@ require 'thor'
4
4
  require 'kovid'
5
5
 
6
6
  module Kovid
7
- # Describes the commands that can be run by the user
8
- # Descriptions are returned to STDOUT
9
7
  class CLI < Thor
10
8
  def self.exit_on_failure?
11
9
  true
@@ -77,13 +75,15 @@ module Kovid
77
75
  data_source
78
76
  end
79
77
 
80
- desc 'history COUNTRY or history COUNTRY N',
81
- 'Return history of incidents of COUNTRY (in the last N days)'
82
- def history(*params)
83
- if params.size == 2
84
- 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)
85
85
  else
86
- puts Kovid.history(params.first, nil)
86
+ puts Kovid.history(location, days)
87
87
  end
88
88
  data_source
89
89
  end
@@ -147,8 +147,8 @@ module Kovid
147
147
  source = <<~TEXT
148
148
  #{Time.now}
149
149
  Sources:
150
- * JHU CSSE GISand Data
151
- * https://www.worldometers.info/coronavirus/
150
+ * Johns Hopkins University
151
+ * https://www.worldometers.info/coronavirus
152
152
  TEXT
153
153
  puts source
154
154
  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,
@@ -74,7 +81,8 @@ module Kovid
74
81
  'Cases Today'.paint_white,
75
82
  'Deaths'.paint_red,
76
83
  'Deaths Today'.paint_red,
77
- 'Active'.paint_yellow
84
+ 'Active'.paint_yellow,
85
+ 'Tests'.paint_white
78
86
  ].freeze
79
87
 
80
88
  COMPARE_STATES_HEADINGS = [
@@ -93,7 +101,13 @@ module Kovid
93
101
  'Recovered'.paint_green
94
102
  ].freeze
95
103
 
96
- FOOTER_LINE = [
104
+ FOOTER_LINE_THREE_COLUMNS = [
105
+ '------------',
106
+ '------------',
107
+ '------------'
108
+ ].freeze
109
+
110
+ FOOTER_LINE_FOUR_COLUMNS = [
97
111
  '------------',
98
112
  '------------',
99
113
  '------------',
@@ -30,4 +30,10 @@ module Kovid
30
30
  data.map! { |number| Kovid.comma_delimit(number) }
31
31
  end
32
32
  end
33
+
34
+ def lookup_us_state(state)
35
+ us = Carmen::Country.coded('USA')
36
+ lookup = us.subregions.coded(state) || us.subregions.named(state)
37
+ lookup ? lookup.name : state
38
+ end
33
39
  end
@@ -1,28 +1,17 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Kovid
4
- # Constructs history data for specified country
5
4
  module Historians
6
5
  include Constants
6
+ include AsciiCharts
7
7
 
8
- def history(country, last)
9
- # Write checks for when country is spelt wrong.
10
- headings = DATE_CASES_DEATHS_RECOVERED
8
+ def history(location, days)
11
9
  rows = []
12
10
 
13
- stats = if last
14
- Kovid.format_country_history_numbers(country).last(last.to_i)
15
- else
16
- Kovid.format_country_history_numbers(country)
17
- end
11
+ stats = Kovid.format_country_history_numbers(location).last(days.to_i)
12
+ dates = location['timeline']['cases'].keys.last(days.to_i)
18
13
 
19
- dates = if last
20
- country['timeline']['cases'].keys.last(last.to_i)
21
- else
22
- country['timeline']['cases'].keys
23
- end
24
-
25
- unless last
14
+ if days.to_i >= 30
26
15
  stats = stats.reject { |stat| stat[0].to_i.zero? && stat[1].to_i.zero? }
27
16
  dates = dates.last(stats.count)
28
17
  end
@@ -33,14 +22,25 @@ module Kovid
33
22
  rows << row
34
23
  end
35
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
+
36
36
  if stats.size > 10
37
- rows << FOOTER_LINE
38
- rows << DATE_CASES_DEATHS_RECOVERED
37
+ rows << footer
38
+ rows << col_format
39
39
  end
40
40
 
41
41
  Terminal::Table.new(
42
- title: country['country'].upcase,
43
- headings: headings,
42
+ title: title,
43
+ headings: col_format,
44
44
  rows: rows
45
45
  )
46
46
  end
@@ -90,7 +90,7 @@ module Kovid
90
90
  dates.each_with_index do |val, index|
91
91
  data << [val, positive_cases_figures[index]]
92
92
  end
93
- y_range = AsciiCharts::Cartesian.new(
93
+ y_range = Kovid::AsciiCharts::Cartesian.new(
94
94
  data, bar: true, hide_zero: true
95
95
  ).y_range
96
96
 
@@ -1,8 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'rainbow'
4
-
5
- # Adds text color functionalities to String class
6
4
  class String
7
5
  def paint_white
8
6
  Rainbow(self).white.bg(:black).bold
@@ -1,33 +1,35 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'json'
4
+ require 'carmen'
4
5
  require_relative 'tablelize'
5
6
  require_relative 'cache'
6
7
  require_relative 'uri_builder'
7
8
 
8
9
  module Kovid
9
- # Makes requests to external source to collect cases data
10
10
  class Request
11
- COUNTRIES_PATH = UriBuilder.new('/countries').url
12
- STATES_URL = UriBuilder.new('/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
@@ -110,7 +112,8 @@ module Kovid
110
112
  end
111
113
 
112
114
  def state(state)
113
- response = fetch_state(state)
115
+ response = fetch_state(Kovid.lookup_us_state(state))
116
+
114
117
  if response.nil?
115
118
  not_found(state)
116
119
  else
@@ -122,7 +125,6 @@ module Kovid
122
125
 
123
126
  def states(states)
124
127
  compared_states = fetch_compared_states(states)
125
-
126
128
  Kovid::Tablelize.compare_us_states(compared_states)
127
129
  rescue JSON::ParserError
128
130
  puts SERVER_DOWN
@@ -151,7 +153,7 @@ module Kovid
151
153
 
152
154
  def cases
153
155
  response = JSON.parse(
154
- Typhoeus.get(UriBuilder.new('/all').url, cache_ttl: 900).response_body
156
+ Typhoeus.get(UriBuilder.new('/v2/all').url, cache_ttl: 900).response_body
155
157
  )
156
158
 
157
159
  Kovid::Tablelize.cases(response)
@@ -159,15 +161,53 @@ module Kovid
159
161
  puts SERVER_DOWN
160
162
  end
161
163
 
162
- def history(country, last)
164
+ def history(country, days)
163
165
  history_path = UriBuilder.new('/v2/historical').url
166
+
164
167
  response = JSON.parse(
165
168
  Typhoeus.get(
166
169
  history_path + "/#{country}", cache_ttl: 900
167
170
  ).response_body
168
171
  )
169
172
 
170
- 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)
171
211
  rescue JSON::ParserError
172
212
  puts SERVER_DOWN
173
213
  end
@@ -189,9 +229,17 @@ module Kovid
189
229
 
190
230
  private
191
231
 
192
- def not_found(country)
193
- rows = [["Wrong spelling/No reported cases on #{country.upcase}."]]
194
- Terminal::Table.new title: "You checked: #{country.upcase}", rows: rows
232
+ def not_found(location)
233
+ rows = []
234
+ default_warning = "Wrong spelling/No reported cases on #{location.upcase}."
235
+
236
+ rows << if block_given?
237
+ [yield(location)]
238
+ else
239
+ [default_warning]
240
+ end
241
+
242
+ Terminal::Table.new title: "You checked: #{location.upcase}", rows: rows
195
243
  end
196
244
 
197
245
  def fetch_countries(list)
@@ -205,10 +253,10 @@ module Kovid
205
253
  end
206
254
 
207
255
  def fetch_compared_states(submitted_states)
208
- state_data = fetch_state_data
256
+ submitted_states.map! { |s| Kovid.lookup_us_state(s) }
209
257
 
210
- state_data.select do |state|
211
- submitted_states.include?(state['state'].downcase)
258
+ fetch_state_data.select do |state|
259
+ submitted_states.include?(state['state'])
212
260
  end
213
261
  end
214
262
 
@@ -218,9 +266,7 @@ module Kovid
218
266
 
219
267
  def fetch_country(country_name)
220
268
  # TODO: Match ISOs to full country names
221
- if country_name == "netherlands"
222
- country_name = "nl"
223
- end
269
+ country_name = 'nl' if country_name == 'netherlands'
224
270
  country_url = COUNTRIES_PATH + "/#{country_name}"
225
271
 
226
272
  JSON.parse(Typhoeus.get(country_url, cache_ttl: 900).response_body)
@@ -267,7 +313,7 @@ module Kovid
267
313
 
268
314
  def countries_request
269
315
  Typhoeus.get(
270
- UriBuilder.new('/countries').url, cache_ttl: 900
316
+ UriBuilder.new('/v2/countries').url, cache_ttl: 900
271
317
  ).response_body
272
318
  end
273
319
 
@@ -281,6 +327,16 @@ module Kovid
281
327
  end
282
328
  end.compact
283
329
  end
330
+
331
+ def usacounties_aggregator(data, key = nil)
332
+ data.inject({}) do |base, other|
333
+ base.merge(other['timeline'][key]) do |_k, l, r|
334
+ l ||= 0
335
+ l ||= 0
336
+ l + r
337
+ end
338
+ end.compact
339
+ end
284
340
  end
285
341
  end
286
342
  end
@@ -2,14 +2,13 @@
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'
9
9
  require_relative 'historians'
10
10
 
11
11
  module Kovid
12
- # Constructs the tables according to specified input
13
12
  class Tablelize
14
13
  extend Kovid::Constants
15
14
  extend Kovid::Aggregators
@@ -128,7 +127,8 @@ module Kovid
128
127
  Kovid.add_plus_sign(data['todayCases']),
129
128
  Kovid.comma_delimit(data['deaths']),
130
129
  Kovid.add_plus_sign(data['todayDeaths']),
131
- Kovid.comma_delimit(data['recovered'])
130
+ Kovid.comma_delimit(data['active']),
131
+ Kovid.comma_delimit(data['tests'])
132
132
  ]
133
133
  end
134
134
 
@@ -3,7 +3,6 @@
3
3
  require 'uri'
4
4
 
5
5
  module Kovid
6
- # Prepares base URI before requests
7
6
  class UriBuilder
8
7
  attr_reader :path
9
8
 
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Kovid
4
- VERSION = '0.6.6'
4
+ VERSION = '0.6.11'
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.6
4
+ version: 0.6.11
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-11 00:00:00.000000000 Z
11
+ date: 2020-05-07 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
@@ -121,6 +121,7 @@ files:
121
121
  - kovid.gemspec
122
122
  - lib/kovid.rb
123
123
  - lib/kovid/aggregators.rb
124
+ - lib/kovid/ascii_charts.rb
124
125
  - lib/kovid/cache.rb
125
126
  - lib/kovid/cli.rb
126
127
  - lib/kovid/constants.rb