bbc_weather 0.1.0
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 +7 -0
- data/lib/bbc_weather.rb +105 -0
- data/lib/day.rb +118 -0
- data/lib/timeslot.rb +22 -0
- data/lib/weather_result.rb +89 -0
- metadata +89 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA1:
|
|
3
|
+
metadata.gz: 917180090c503d03f9d5b0431f6d445034f54766
|
|
4
|
+
data.tar.gz: 4477139c89d314359f4b453400bf23d7d11f9ac6
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: 935321055d6017ecc7094e31a595e29b997895a0996f7c7451fc171716d78ca07885aac5c264b82b610b9e0ecc15c0262784673e9518e17ce2ca1dc35643579d
|
|
7
|
+
data.tar.gz: 45beeea7cb1aa564982bddc13f7d824974b206c12ef05141ebe5e61480e8f94fa781ce24aac1b53b2e88ef7f256872037536e1c55d9554c656e57e74ac499d15
|
data/lib/bbc_weather.rb
ADDED
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
require 'nokogiri'
|
|
2
|
+
require 'net/http'
|
|
3
|
+
require 'json'
|
|
4
|
+
require 'weather_result'
|
|
5
|
+
|
|
6
|
+
class BBCWeather
|
|
7
|
+
$temp_unit = "c"
|
|
8
|
+
$speed_unit = "mph"
|
|
9
|
+
|
|
10
|
+
def self.city(city_id)
|
|
11
|
+
result = {}
|
|
12
|
+
|
|
13
|
+
if city_id.is_a?(Integer) || city_id =~ /^[0-9]+$/
|
|
14
|
+
begin
|
|
15
|
+
result = get_weather_from_bbc_url("http://www.bbc.co.uk/weather/en/#{city_id}")
|
|
16
|
+
rescue ArgumentError => e
|
|
17
|
+
if e.to_s[/404/]
|
|
18
|
+
raise ArgumentError, "City ID: #{city_id} not found"
|
|
19
|
+
end
|
|
20
|
+
raise
|
|
21
|
+
end
|
|
22
|
+
else
|
|
23
|
+
# Convert string location to integer city code
|
|
24
|
+
city_ids = BBCWeather.get_city_id(city_id)
|
|
25
|
+
|
|
26
|
+
if city_ids.length == 0
|
|
27
|
+
raise ArgumentError, "City ID: '#{city_id}' could not be located"
|
|
28
|
+
elsif city_ids.length > 1
|
|
29
|
+
raise ArgumentError, "City ID: '#{city_id}' returned more than one matching city (#{city_ids}). Please refine your search term"
|
|
30
|
+
else
|
|
31
|
+
# Recursive call using integer city code
|
|
32
|
+
return BBCWeather.city(city_ids[0]["id"])
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
return result
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def self.get_city_id(city_name)
|
|
39
|
+
city_name_d = city_name.downcase
|
|
40
|
+
city_ids = JSON.parse(Net::HTTP.get(URI("http://www.bbc.co.uk/locator/default/en-GB/autocomplete.json?search=#{city_name_d}&filter=international")))
|
|
41
|
+
city_id = city_ids.select {|a| a["fullName"].downcase.eql?(city_name_d) || a["fullName"].downcase.eql?("#{city_name_d}, #{city_name_d}") || a["fullName"].downcase.eql?("#{city_name_d}, #{city_name_d} city") }
|
|
42
|
+
|
|
43
|
+
city_id.empty? ? (return city_ids) : (return city_id)
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def self.get_weather_from_bbc_url(url)
|
|
47
|
+
html = {}
|
|
48
|
+
html[:main] = Nokogiri::HTML(Net::HTTP.get(URI(url)))
|
|
49
|
+
|
|
50
|
+
if html[:main].css("title")[0].children[0].text[/not found/i]
|
|
51
|
+
raise ArgumentError, "The given URL returned a 404 error. Please check the city ID and try again"
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
lock = Mutex.new
|
|
55
|
+
connections = []
|
|
56
|
+
html[:main].css("div.daily-window > ul > li > a").each do |day|
|
|
57
|
+
connections << Thread.new {
|
|
58
|
+
day_url = day.attributes["data-ajax-href"].value
|
|
59
|
+
day_html = Nokogiri::HTML(Net::HTTP.get(URI("http://www.bbc.co.uk#{day_url}")))
|
|
60
|
+
lock.synchronize {
|
|
61
|
+
html[day_url[/[0-9]+$/].to_i] = day_html
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
end
|
|
65
|
+
connections.each {|conn| conn.join}
|
|
66
|
+
|
|
67
|
+
return WeatherResult.new(html)
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def self.set_units(*units)
|
|
71
|
+
curr_units = self.units
|
|
72
|
+
count = {:temp => 0, :speed => 0}
|
|
73
|
+
|
|
74
|
+
units.each do |u|
|
|
75
|
+
if u == "c" || u == "celcius"
|
|
76
|
+
$temp_unit = "c"
|
|
77
|
+
count[:temp] += 1
|
|
78
|
+
elsif u == "f" || u == "fahrenheit"
|
|
79
|
+
$temp_unit = "f"
|
|
80
|
+
count[:temp] += 1
|
|
81
|
+
elsif u == "kph" || u == "km/h"
|
|
82
|
+
$speed_unit = "kph"
|
|
83
|
+
count[:speed] += 1
|
|
84
|
+
elsif u == "mph"
|
|
85
|
+
$speed_unit = "mph"
|
|
86
|
+
count[:speed] += 1
|
|
87
|
+
else
|
|
88
|
+
$temp_unit = curr_units[0]
|
|
89
|
+
$speed_unit = curr_units[1]
|
|
90
|
+
raise ArgumentError, "'#{u}' is not a recognised unit of speed/temperature. Unit must be either 'c' or 'f' (celcius or fahrenheit), or 'kph' or 'mph' (kilometers per hour or miles per hour). Units have not been changed"
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
if count[:temp] > 1 || count[:speed] > 1
|
|
94
|
+
$temp_unit = curr_units[0]
|
|
95
|
+
$speed_unit = curr_units[1]
|
|
96
|
+
raise ArgumentError, "Cannot pass in two units of the same type (i.e. #set_units('kph', 'mph')). Units have not been changed"
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
return [$temp_unit, $speed_unit]
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
def self.units
|
|
103
|
+
return [$temp_unit, $speed_unit]
|
|
104
|
+
end
|
|
105
|
+
end
|
data/lib/day.rb
ADDED
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
require 'timeslot'
|
|
2
|
+
require 'date'
|
|
3
|
+
|
|
4
|
+
class Day
|
|
5
|
+
attr_accessor :date, :sunrise, :sunset, :timeslots, :nextday_timeslots
|
|
6
|
+
|
|
7
|
+
def initialize(day_html, date)
|
|
8
|
+
@timeslots = Array.new
|
|
9
|
+
nextday_timeslot_index = nil
|
|
10
|
+
|
|
11
|
+
@date = Date.parse(date)
|
|
12
|
+
@sunrise = day_html.css("span.sunrise")[0].children[0].text[/\d{2}:\d{2}/]
|
|
13
|
+
@sunset = day_html.css("span.sunset")[0].children[0].text[/\d{2}:\d{2}/]
|
|
14
|
+
|
|
15
|
+
###
|
|
16
|
+
### START LOOPS
|
|
17
|
+
###
|
|
18
|
+
# Get times for each TimeSlot
|
|
19
|
+
day_html.css("table.weather tr.time > th.value").each_with_index do |times_html, i|
|
|
20
|
+
nextday_timeslot_index = i if times_html.attributes["class"].value.include?("next-day") && !nextday_timeslot_index
|
|
21
|
+
|
|
22
|
+
# Sort out Timeslot time
|
|
23
|
+
ts = TimeSlot.new
|
|
24
|
+
time = "#{times_html.css("span[class='hour']").text}:#{times_html.css("span[class='mins']").text}"
|
|
25
|
+
ts.time = DateTime.parse("#{@date}T#{time}")
|
|
26
|
+
ts.time += 1 if nextday_timeslot_index
|
|
27
|
+
|
|
28
|
+
# Sort out prev and next Timeslots
|
|
29
|
+
unless @timeslots[-1].nil?
|
|
30
|
+
ts.prev = @timeslots[-1]
|
|
31
|
+
@timeslots[-1].next = ts
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
@timeslots.push(ts)
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
# Get weather conditions for each TimeSlot
|
|
38
|
+
day_html.css("table.weather tr.weather-type > td img").each_with_index do |conditions_html, i|
|
|
39
|
+
@timeslots[i].conditions = conditions_html.attributes["title"].value
|
|
40
|
+
@timeslots[i].icon_url = conditions_html.attributes["src"].value
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
# Get temperature for each TimeSlot
|
|
44
|
+
day_html.css("table.weather tr.temperature > td span[data-unit='c']").each_with_index do |temperature_html, i|
|
|
45
|
+
@timeslots[i].temperature = temperature_html.children[0].text.to_i
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
# Get wind details for each TimeSlot
|
|
49
|
+
day_html.css("table.weather tr.windspeed > td > span.wind").each_with_index do |wind_html, i|
|
|
50
|
+
wind_data = wind_html.attributes["data-tooltip-mph"].value
|
|
51
|
+
@timeslots[i].wind_speed = wind_data[/\d+/].to_i
|
|
52
|
+
@timeslots[i].wind_direction = wind_data.gsub(/[^A-Z]/, "")
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
# Get humidity for each TimeSlot
|
|
56
|
+
day_html.css("table.weather tr.humidity > td.value").each_with_index do |humidity_html, i|
|
|
57
|
+
@timeslots[i].humidity = humidity_html.children[0].text[/\d+/].to_i
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
# Get visibility for each TimeSlot
|
|
61
|
+
day_html.css("table.weather tr.visibility > td.value abbr").each_with_index do |visibility_html, i|
|
|
62
|
+
@timeslots[i].visibility = visibility_html.attributes["title"].value
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
# Get pressure for each TimeSlot
|
|
66
|
+
day_html.css("table.weather tr.pressure > td.value").each_with_index do |pressure_html, i|
|
|
67
|
+
@timeslots[i].pressure = pressure_html.children[0].text[/\d+/].to_i # In Millibars
|
|
68
|
+
end
|
|
69
|
+
###
|
|
70
|
+
### END LOOPS
|
|
71
|
+
###
|
|
72
|
+
# Shift any timeslots after midnight into the nextday_timeslots
|
|
73
|
+
@nextday_timeslots = @timeslots.slice!(nextday_timeslot_index, @timeslots.length-nextday_timeslot_index)
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def high
|
|
77
|
+
@timeslots.max_by {|ts| ts.temperature}.temperature
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def low
|
|
81
|
+
@timeslots.min_by {|ts| ts.temperature}.temperature
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
def timeslot(i)
|
|
85
|
+
return nil unless i.is_a?(Integer)
|
|
86
|
+
@timeslots[i]
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def at(time)
|
|
90
|
+
if time.is_a?(DateTime) || time.is_a?(Time) || (time.is_a?(String) && time =~ /\d{2}:\d{2}/ && time[0..1].to_i >= 0 && time[0..1].to_i <= 23 && time[2..3].to_i >= 0 && time[2..3].to_i <= 59)
|
|
91
|
+
curr_slot = @timeslots[0]
|
|
92
|
+
time = "#{@date}T#{time}" if time.is_a?(String)
|
|
93
|
+
search_time = DateTime.parse(time.to_s).strftime("%s").to_i
|
|
94
|
+
curr_slot_time = curr_slot.time.strftime("%s").to_i
|
|
95
|
+
|
|
96
|
+
curr_diff = (search_time - curr_slot_time).abs
|
|
97
|
+
|
|
98
|
+
loop do
|
|
99
|
+
if search_time > curr_slot_time
|
|
100
|
+
if !curr_slot.next.nil? && (curr_slot.next.time.strftime("%s").to_i - search_time).abs < curr_diff
|
|
101
|
+
curr_slot = curr_slot.next
|
|
102
|
+
curr_diff = (search_time - curr_slot.time.strftime("%s").to_i).abs
|
|
103
|
+
else
|
|
104
|
+
return curr_slot
|
|
105
|
+
end
|
|
106
|
+
elsif search_time <= curr_slot_time
|
|
107
|
+
if !curr_slot.prev.nil? && ((curr_slot.prev.time-1).strftime("%s").to_i - search_time).abs < curr_diff
|
|
108
|
+
return curr_slot.prev
|
|
109
|
+
else
|
|
110
|
+
return curr_slot
|
|
111
|
+
end
|
|
112
|
+
end
|
|
113
|
+
end
|
|
114
|
+
else
|
|
115
|
+
raise ArgumentError, "Time must be in the format 'HH:MM' (00-23) i.e. '23:45' or as a DateTime/Time object"
|
|
116
|
+
end
|
|
117
|
+
end
|
|
118
|
+
end
|
data/lib/timeslot.rb
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
class TimeSlot
|
|
2
|
+
attr_accessor :time, :temperature, :humidity, :visibility, :pressure, :wind_speed, :wind_direction, :conditions, :icon_url, :next, :prev
|
|
3
|
+
|
|
4
|
+
def initialize
|
|
5
|
+
end
|
|
6
|
+
|
|
7
|
+
def temperature
|
|
8
|
+
if $temp_unit.eql?("c")
|
|
9
|
+
return @temperature
|
|
10
|
+
else
|
|
11
|
+
return ((@temperature * 9 / 5) + 32).round
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def wind_speed
|
|
16
|
+
if $speed_unit.eql?("mph")
|
|
17
|
+
return @wind_speed
|
|
18
|
+
else
|
|
19
|
+
return (@wind_speed * 1.609344).round
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
require 'nokogiri'
|
|
2
|
+
require 'date'
|
|
3
|
+
require 'day'
|
|
4
|
+
require 'timeslot'
|
|
5
|
+
|
|
6
|
+
class WeatherResult
|
|
7
|
+
attr_reader :location, :current_time, :current_temp, :current_humidity, :days
|
|
8
|
+
def initialize(html)
|
|
9
|
+
@location = html[:main].css("span.location-name")[0].children[0].text
|
|
10
|
+
@current_temp = html[:main].css("div.observationsRecord span.temperature-value")[0].children[0].text.to_i
|
|
11
|
+
@current_humidity = html[:main].css("div.observationsRecord p.humidity > span")[0].children[0].text[/\d+/].to_i # In %
|
|
12
|
+
|
|
13
|
+
timezone = html[:main].css("div.ack > p")[1].children[0].text[/GMT[+-]\d{4}/]
|
|
14
|
+
@current_time = DateTime.now.new_offset(timezone).to_s
|
|
15
|
+
|
|
16
|
+
@days = []
|
|
17
|
+
nextday_timeslots = []
|
|
18
|
+
|
|
19
|
+
5.times do |i|
|
|
20
|
+
date = html[:main].css("div.daily-window > ul > li > a")[i].attributes["data-ajax-href"].value[/\d{4}-\d{2}-\d{2}/]
|
|
21
|
+
day = Day.new(html[i], date)
|
|
22
|
+
|
|
23
|
+
# Transfer 'yesterdays' next_day timeslots to today and link up next and prev links
|
|
24
|
+
unless nextday_timeslots.empty?
|
|
25
|
+
nextday_timeslots[-1].next = day.timeslots[0]
|
|
26
|
+
day.timeslots[0].prev = nextday_timeslots[-1]
|
|
27
|
+
day.timeslots = nextday_timeslots.concat(day.timeslots)
|
|
28
|
+
end
|
|
29
|
+
nextday_timeslots = day.nextday_timeslots.dup
|
|
30
|
+
day.nextday_timeslots.clear
|
|
31
|
+
@days.push day
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
@days.shift if @days[0].timeslots.empty?
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
# Returns today of the city location, _not_ today of the user
|
|
38
|
+
# i.e. a user in Canada wants the forecast for Auckland, New Zealand..
|
|
39
|
+
# .today then refers to Auckland's today, which may well be one day ahead of Canada
|
|
40
|
+
def today
|
|
41
|
+
return @days[0]
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def tomorrow
|
|
45
|
+
return @days[1]
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def days_forward(i)
|
|
49
|
+
return nil unless i.is_a?(Integer)
|
|
50
|
+
return @days[i]
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def on(day)
|
|
54
|
+
day = Date.parse(day) if day =~ /\d{4}-\d{2}-\d{2}/
|
|
55
|
+
if day.is_a?(String)
|
|
56
|
+
weekdays = ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"]
|
|
57
|
+
day_temp = weekdays.select {|d| d.downcase.include?(day.downcase)}.first
|
|
58
|
+
|
|
59
|
+
raise ArgumentError, "'#{day}' is not a valid day" if day_temp.nil?
|
|
60
|
+
day = day_temp
|
|
61
|
+
|
|
62
|
+
7.times do |i|
|
|
63
|
+
if (Date.today + i).strftime("%A").eql?(day)
|
|
64
|
+
day = Date.today + i
|
|
65
|
+
break
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
elsif day.is_a?(DateTime)
|
|
69
|
+
day = Date.parse(day.to_s)
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
# day var is now a Date object
|
|
73
|
+
|
|
74
|
+
7.times do |i|
|
|
75
|
+
if !@days[i].nil? && @days[i].date.eql?(day)
|
|
76
|
+
return @days[i]
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
raise ArgumentError, "'#{day.to_s}' is not in the forecast range (#{@days.first.date.to_s} - #{@days.last.date.to_s})"
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def current_temp
|
|
83
|
+
if $temp_unit.eql?("c")
|
|
84
|
+
return @current_temp
|
|
85
|
+
else
|
|
86
|
+
return ((@current_temp * 9 / 5) + 32).round
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: bbc_weather
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.1.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Jamie Guthrie
|
|
8
|
+
autorequire:
|
|
9
|
+
bindir: bin
|
|
10
|
+
cert_chain: []
|
|
11
|
+
date: 2017-06-09 00:00:00.000000000 Z
|
|
12
|
+
dependencies:
|
|
13
|
+
- !ruby/object:Gem::Dependency
|
|
14
|
+
name: nokogiri
|
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
|
16
|
+
requirements:
|
|
17
|
+
- - "~>"
|
|
18
|
+
- !ruby/object:Gem::Version
|
|
19
|
+
version: '1.8'
|
|
20
|
+
type: :runtime
|
|
21
|
+
prerelease: false
|
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
23
|
+
requirements:
|
|
24
|
+
- - "~>"
|
|
25
|
+
- !ruby/object:Gem::Version
|
|
26
|
+
version: '1.8'
|
|
27
|
+
- !ruby/object:Gem::Dependency
|
|
28
|
+
name: rake
|
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
|
30
|
+
requirements:
|
|
31
|
+
- - "~>"
|
|
32
|
+
- !ruby/object:Gem::Version
|
|
33
|
+
version: '12.0'
|
|
34
|
+
type: :development
|
|
35
|
+
prerelease: false
|
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
37
|
+
requirements:
|
|
38
|
+
- - "~>"
|
|
39
|
+
- !ruby/object:Gem::Version
|
|
40
|
+
version: '12.0'
|
|
41
|
+
- !ruby/object:Gem::Dependency
|
|
42
|
+
name: rspec
|
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
|
44
|
+
requirements:
|
|
45
|
+
- - "~>"
|
|
46
|
+
- !ruby/object:Gem::Version
|
|
47
|
+
version: '3.6'
|
|
48
|
+
type: :development
|
|
49
|
+
prerelease: false
|
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
51
|
+
requirements:
|
|
52
|
+
- - "~>"
|
|
53
|
+
- !ruby/object:Gem::Version
|
|
54
|
+
version: '3.6'
|
|
55
|
+
description: A simple gem to grab the BBC weather forecast for any given city
|
|
56
|
+
email: jamie.guthrie@gmail.com
|
|
57
|
+
executables: []
|
|
58
|
+
extensions: []
|
|
59
|
+
extra_rdoc_files: []
|
|
60
|
+
files:
|
|
61
|
+
- lib/bbc_weather.rb
|
|
62
|
+
- lib/day.rb
|
|
63
|
+
- lib/timeslot.rb
|
|
64
|
+
- lib/weather_result.rb
|
|
65
|
+
homepage: http://www.github.com/jguthrie100/bbc_weather
|
|
66
|
+
licenses:
|
|
67
|
+
- MIT
|
|
68
|
+
metadata: {}
|
|
69
|
+
post_install_message:
|
|
70
|
+
rdoc_options: []
|
|
71
|
+
require_paths:
|
|
72
|
+
- lib
|
|
73
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
74
|
+
requirements:
|
|
75
|
+
- - ">="
|
|
76
|
+
- !ruby/object:Gem::Version
|
|
77
|
+
version: '0'
|
|
78
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
79
|
+
requirements:
|
|
80
|
+
- - ">="
|
|
81
|
+
- !ruby/object:Gem::Version
|
|
82
|
+
version: '0'
|
|
83
|
+
requirements: []
|
|
84
|
+
rubyforge_project:
|
|
85
|
+
rubygems_version: 2.6.8
|
|
86
|
+
signing_key:
|
|
87
|
+
specification_version: 4
|
|
88
|
+
summary: Get the weather!
|
|
89
|
+
test_files: []
|