davidjrice-hamweather 0.0.1
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.
- data/Manifest +13 -0
- data/README +73 -0
- data/Rakefile +40 -0
- data/hamweather.gemspec +30 -0
- data/lib/hamweather.rb +44 -0
- data/lib/hamweather/forecast.rb +54 -0
- data/lib/hamweather/forecast/daily.rb +25 -0
- data/lib/hamweather/forecast/hourly.rb +27 -0
- data/lib/hamweather/location.rb +91 -0
- data/spec/daily_spec.rb +56 -0
- data/spec/forecast_spec.rb +52 -0
- data/spec/hamweather_spec.rb +37 -0
- data/spec/hourly_spec.rb +73 -0
- data/spec/location_spec.rb +77 -0
- data/spec/spec_helper.rb +11 -0
- metadata +86 -0
data/Manifest
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
lib/hamweather/forecast/daily.rb
|
2
|
+
lib/hamweather/forecast/hourly.rb
|
3
|
+
lib/hamweather/forecast.rb
|
4
|
+
lib/hamweather/location.rb
|
5
|
+
lib/hamweather.rb
|
6
|
+
Rakefile
|
7
|
+
sample_data.xml
|
8
|
+
spec/daily_spec.rb
|
9
|
+
spec/forecast_spec.rb
|
10
|
+
spec/hamweather_spec.rb
|
11
|
+
spec/hourly_spec.rb
|
12
|
+
spec/spec_helper.rb
|
13
|
+
Manifest
|
data/README
ADDED
@@ -0,0 +1,73 @@
|
|
1
|
+
Hamweather
|
2
|
+
==========
|
3
|
+
|
4
|
+
This is a Ruby API to an XML webservice providing weather detail for global locations.
|
5
|
+
It can handle diverse locations by Zip Code, Canadian Post code or by any international address.
|
6
|
+
|
7
|
+
* It requires a Google Maps API key: http://code.google.com/apis/maps/signup.html
|
8
|
+
* It requires a Hamweather API key, contact Hamweather to discuss: http://www.hamweather.com/
|
9
|
+
|
10
|
+
Installation
|
11
|
+
------------
|
12
|
+
> sudo gem install hamweather
|
13
|
+
|
14
|
+
Usage
|
15
|
+
-----
|
16
|
+
require 'rubygems'
|
17
|
+
require 'hamweather'
|
18
|
+
|
19
|
+
*** Hamweather is a singleton class, it takes the two API keys as such:
|
20
|
+
Hamweather.google_maps_api_key = " ... "
|
21
|
+
Hamweather.api_key = " ... "
|
22
|
+
|
23
|
+
@location = Hamweather.locate('Belfast') # Assuming your location is unambiguous:
|
24
|
+
@forecast = Hamweather.forecast(@location) # Forecast is returned as a Hamweather::Forecast object
|
25
|
+
|
26
|
+
What 'address' to use?
|
27
|
+
----------------------
|
28
|
+
Zip Code, Canadian Postcode, International Address - it doesn't really matter.
|
29
|
+
This API uses the Google Maps API to geolocate any address which doesn't fit either of the former
|
30
|
+
precisely.
|
31
|
+
Which means the user could input "Lodnon" and get the right location as it's parsing is intelligent.
|
32
|
+
|
33
|
+
Using Hamweather::Location
|
34
|
+
--------------------------
|
35
|
+
If the 'address' you enter is ambiguous, the API may return a hash of Location objects which you can
|
36
|
+
use to choose. Check this by: @location.kind_of?(Hash)
|
37
|
+
Otherwise @location.zipcode @location.postcode? @location.geocode? will tell you what type of
|
38
|
+
Location you have.
|
39
|
+
|
40
|
+
Using Hamweather::Forecast
|
41
|
+
--------------------------
|
42
|
+
Assuming @location has been defined, @forecast = Hamweather.forecast(@location) will return a
|
43
|
+
Hamweather::Forecast object. A Forecast object has two available hashes: dailies{} and hourlies{}
|
44
|
+
which give all available Daily forecasts and Hourly forecasts respectively. Each Daily or Hourly
|
45
|
+
forecast is a Hash of Hamweather::Forecast::Daily or Hamweather::Forecast::Hourly objects.
|
46
|
+
|
47
|
+
The Daily object has a list of it's Hourly Forecasts available to you: 'hours'
|
48
|
+
Forecast variables available in a Daily forecast are:
|
49
|
+
:high_farenheit
|
50
|
+
:high_celsius
|
51
|
+
:low_farenheit
|
52
|
+
:low_celsius
|
53
|
+
:day
|
54
|
+
:date
|
55
|
+
:expected_weather
|
56
|
+
:detail
|
57
|
+
:probability_of_preciptiation
|
58
|
+
|
59
|
+
Forecast variables available in an Hourly forecast are:
|
60
|
+
:date
|
61
|
+
:time
|
62
|
+
:expected_weather
|
63
|
+
:temp_celsius
|
64
|
+
:temp_farenheit
|
65
|
+
:probability_of_precipitation
|
66
|
+
:precipitation_millimeters
|
67
|
+
:precipitation_inches
|
68
|
+
:dew_point_celsius
|
69
|
+
:dew_point_farenheit
|
70
|
+
:relative_humidity
|
71
|
+
:wind_speed_knots
|
72
|
+
:wind_speed_mph
|
73
|
+
:wind_direction
|
data/Rakefile
ADDED
@@ -0,0 +1,40 @@
|
|
1
|
+
begin
|
2
|
+
require 'echoe'
|
3
|
+
|
4
|
+
Echoe.new('hamweather', '0.0.1') do |p|
|
5
|
+
p.rubyforge_name = 'hamweather'
|
6
|
+
p.summary = "Hamweather is a Ruby client library for interacting with http://hamweather.com weather data"
|
7
|
+
p.description = "Hamweather is a Ruby client library for interacting with http://hamweather.com weather data"
|
8
|
+
p.url = "http://github.com/davidjrice/hamweather"
|
9
|
+
p.author = ['David Rice']
|
10
|
+
p.email = "david@contrast.ie"
|
11
|
+
p.dependencies = ["json"]
|
12
|
+
end
|
13
|
+
|
14
|
+
rescue LoadError => e
|
15
|
+
puts "You are missing a dependency required for meta-operations on this gem."
|
16
|
+
puts "#{e.to_s.capitalize}."
|
17
|
+
end
|
18
|
+
|
19
|
+
# add spec tasks, if you have rspec installed
|
20
|
+
begin
|
21
|
+
require 'spec/rake/spectask'
|
22
|
+
|
23
|
+
Spec::Rake::SpecTask.new("spec") do |t|
|
24
|
+
t.spec_files = FileList['spec/**/*_spec.rb']
|
25
|
+
t.spec_opts = ['--color']
|
26
|
+
end
|
27
|
+
|
28
|
+
task :test do
|
29
|
+
Rake::Task['spec'].invoke
|
30
|
+
end
|
31
|
+
|
32
|
+
Spec::Rake::SpecTask.new("coverage") do |t|
|
33
|
+
t.spec_files = FileList['spec/**/*_spec.rb']
|
34
|
+
t.spec_opts = ['--color']
|
35
|
+
t.rcov = true
|
36
|
+
t.rcov_opts = ['--exclude', '^spec,/gems/']
|
37
|
+
end
|
38
|
+
|
39
|
+
|
40
|
+
end
|
data/hamweather.gemspec
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
Gem::Specification.new do |s|
|
2
|
+
s.name = "hamweather"
|
3
|
+
s.version = "0.0.1"
|
4
|
+
s.date = "2008-10-13"
|
5
|
+
s.summary = " Ruby Client API for Hamweather Weather Service (http://www.hamweather.com)"
|
6
|
+
s.email = "david@contrast.ie"
|
7
|
+
s.homepage = "http://github.com/davidjrice/hamweather"
|
8
|
+
s.description = "Client API for Hamweather Weather Service (http://www.hamweather.com)"
|
9
|
+
s.has_rdoc = true
|
10
|
+
s.authors = ["David Rice", "David Lowry"]
|
11
|
+
s.files = ["Manifest",
|
12
|
+
"README",
|
13
|
+
"Rakefile",
|
14
|
+
"hamweather.gemspec",
|
15
|
+
"lib/hamweather/forecast/daily.rb",
|
16
|
+
"lib/hamweather/forecast/hourly.rb",
|
17
|
+
"lib/hamweather/forecast.rb",
|
18
|
+
"lib/hamweather/location.rb",
|
19
|
+
"lib/hamweather.rb"]
|
20
|
+
s.test_files = ["spec/daily_spec.rb",
|
21
|
+
"spec/forecast_spec.rb",
|
22
|
+
"spec/hamweather_spec.rb",
|
23
|
+
"spec/hourly_spec.rb",
|
24
|
+
"spec/location_spec.rb",
|
25
|
+
"spec/spec_helper.rb"]
|
26
|
+
s.rdoc_options = ["--main", "README"]
|
27
|
+
s.extra_rdoc_files = ["Manifest", "README"]
|
28
|
+
s.add_dependency("google-geo", ["> 0.0.0"])
|
29
|
+
s.add_dependency("hpricot", ["> 0.0.0"])
|
30
|
+
end
|
data/lib/hamweather.rb
ADDED
@@ -0,0 +1,44 @@
|
|
1
|
+
$:.unshift File.dirname(__FILE__)
|
2
|
+
require 'hamweather/location'
|
3
|
+
require 'hamweather/forecast'
|
4
|
+
require 'hamweather/forecast/daily'
|
5
|
+
require 'hamweather/forecast/hourly'
|
6
|
+
|
7
|
+
require 'date'
|
8
|
+
require 'hpricot'
|
9
|
+
|
10
|
+
module Hamweather
|
11
|
+
|
12
|
+
class ApiKeyException < StandardError; end
|
13
|
+
class GoogleApiKeyException < StandardError; end
|
14
|
+
|
15
|
+
class UnknownAddressError < StandardError; end
|
16
|
+
|
17
|
+
class << self
|
18
|
+
|
19
|
+
attr_accessor :api_key, :google_maps_api_key
|
20
|
+
# Hamweather.api_key = "..."
|
21
|
+
# Hamweather.google_maps_api_key = "..."
|
22
|
+
# locations = Hamweather.locate "Belfast"
|
23
|
+
# Hamweather.forecast(locations.first)
|
24
|
+
|
25
|
+
def locate(place)
|
26
|
+
check_api_key
|
27
|
+
@location = Hamweather::Location.parse(place)
|
28
|
+
end
|
29
|
+
|
30
|
+
def forecast(location)
|
31
|
+
check_api_key
|
32
|
+
Hamweather::Forecast.new(location)
|
33
|
+
end
|
34
|
+
|
35
|
+
protected
|
36
|
+
|
37
|
+
def check_api_key
|
38
|
+
raise ApiKeyException.new("you must provide a Hamweather api key.") if api_key.nil?
|
39
|
+
raise ApiKeyException.new("you must provide a Google Maps api key.") if google_maps_api_key.nil?
|
40
|
+
end
|
41
|
+
|
42
|
+
end
|
43
|
+
|
44
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
require 'net/http'
|
2
|
+
require 'uri'
|
3
|
+
|
4
|
+
module Hamweather
|
5
|
+
class Forecast
|
6
|
+
|
7
|
+
# @forecast.each_day
|
8
|
+
# => day_proxy_object.high_farenheit
|
9
|
+
# @forecast.each_hour
|
10
|
+
# => hour_proxy_object.high_farenheit
|
11
|
+
|
12
|
+
attr_accessor :forecast, :dailies, :hourlies
|
13
|
+
|
14
|
+
def initialize(location)
|
15
|
+
# Construct url from api key, location uri
|
16
|
+
# Request url, get xml then parse
|
17
|
+
xml_data = self.class.fetch(location.to_uri).gsub!(/\n/,'')
|
18
|
+
data = Hpricot.parse(xml_data)
|
19
|
+
|
20
|
+
@dailies = {}
|
21
|
+
@hourlies = {}
|
22
|
+
|
23
|
+
data.at(:wxforecast).children.each do |day|
|
24
|
+
#TODO make ordered hash
|
25
|
+
@dailies[day[:date].to_s] = Daily.new(day)
|
26
|
+
end
|
27
|
+
|
28
|
+
data.at(:wxshortterm).children.each do |hour|
|
29
|
+
#TODO make ordered hash
|
30
|
+
@hourlies[hour[:time]] = Hourly.new(hour)
|
31
|
+
@dailies[hour[:date]].hours[hour[:time]] = Hourly.new(hour)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
# TODO stub out calls to this where possible.
|
36
|
+
# TODO / or raise an error.
|
37
|
+
def self.fetch(location_uri)
|
38
|
+
Net::HTTP.get URI.parse("http://hwlite.hamweather.net/#{Hamweather.api_key}/#{location_uri}")
|
39
|
+
end
|
40
|
+
|
41
|
+
def each_day
|
42
|
+
@dailies.each_pair do |key, value|
|
43
|
+
yield value
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def each_hour
|
48
|
+
@hourlies.each_pair do |key, value|
|
49
|
+
yield value
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
end
|
54
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
module Hamweather
|
2
|
+
class Forecast::Daily
|
3
|
+
#daily_forecast = Hpricot.parse('<FPeriod interval="1" Day="FRI" Date="2008-11-14" Wx="Chance T-storms" Icon="tstorm.gif" HiF="66" HiC="19" LoF="56" LoC="13" Pop="60" Detail="Occasional showers with a chance of thunderstorms. Areas of fog. Some thunderstorms May be severe after midnight. Lows in the mid 50s. South winds 5 to 10 mph shifting to the southwest 10 to 15 mph after midnight. Chance of rain near 100 percent."/>').root
|
4
|
+
attr_accessor :high_farenheit, :high_celsius, :low_farenheit, :low_celsius, :day, :date, :expected_weather, :detail, :probability_of_preciptiation
|
5
|
+
attr_accessor :hours
|
6
|
+
|
7
|
+
def initialize(daily_forecast)
|
8
|
+
@hours = {}
|
9
|
+
@high_farenheit = daily_forecast[:hif].to_i
|
10
|
+
@high_celsius = daily_forecast[:hic].to_i
|
11
|
+
@low_farenheit = daily_forecast[:lof].to_i
|
12
|
+
@low_celsius = daily_forecast[:loc].to_i
|
13
|
+
@date = Date.parse(daily_forecast[:date])
|
14
|
+
@expected_weather = daily_forecast[:wx]
|
15
|
+
@detail = daily_forecast[:detail]
|
16
|
+
@probability_of_preciptiation = daily_forecast[:pop].to_i
|
17
|
+
end
|
18
|
+
|
19
|
+
def each_hour
|
20
|
+
@hours.each_pair do |key, value|
|
21
|
+
yield value
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
module Hamweather
|
2
|
+
class Forecast::Hourly
|
3
|
+
#hourly_forecast = Hpricot.parse('<STPeriod interval="1" Epoch="1228298400" Day="Wed" Date="2008-12-03" Time="10:00" Wx="Partly Cloudy" Icon="pcloudy.gif" TempC="0" TempF="32" Pop="10" QPFmm="" QPFin="" DewPointC="-5" DewPointF="23" RelativeHumidity="70" WindSpeedKnots="8" WindSpeedMPH="9" WindDirectionEng="SW" />').root
|
4
|
+
attr_accessor :date, :time, :expected_weather, :temp_celsius, :temp_farenheit, :probability_of_precipitation, :precipitation_millimeters, :precipitation_inches, :dew_point_celsius, :dew_point_farenheit, :relative_humidity, :wind_speed_knots, :wind_speed_mph, :wind_direction
|
5
|
+
|
6
|
+
# def initialize(xml_data)
|
7
|
+
# hourly_forecast = Hpricot.parse(xml_data).root
|
8
|
+
|
9
|
+
def initialize(hourly_forecast)
|
10
|
+
@date = Date.parse(hourly_forecast[:date])
|
11
|
+
@time = hourly_forecast[:time]
|
12
|
+
@expected_weather = hourly_forecast[:wx]
|
13
|
+
@temp_farenheit = hourly_forecast[:tempf].to_i
|
14
|
+
@temp_celsius = hourly_forecast[:tempc].to_i
|
15
|
+
@probability_of_precipitation = hourly_forecast[:pop].to_i
|
16
|
+
@precipitation_millimeters = hourly_forecast[:qpfmm]
|
17
|
+
@precipitation_inches = hourly_forecast[:qpfin]
|
18
|
+
@dew_point_celsius = hourly_forecast[:dewpointc].to_i
|
19
|
+
@dew_point_farenheit = hourly_forecast[:dewpointf].to_i
|
20
|
+
@relative_humidity = hourly_forecast[:relativehumidity].to_i
|
21
|
+
@wind_speed_knots = hourly_forecast[:windspeedknots].to_i
|
22
|
+
@wind_speed_mph = hourly_forecast[:windspeedmph].to_i
|
23
|
+
@wind_direction = hourly_forecast[:winddirectioneng]
|
24
|
+
end
|
25
|
+
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,91 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'google/geo'
|
3
|
+
|
4
|
+
module Hamweather
|
5
|
+
|
6
|
+
class Location
|
7
|
+
|
8
|
+
attr_reader :string, :address
|
9
|
+
|
10
|
+
def lat
|
11
|
+
@address.latitude
|
12
|
+
end
|
13
|
+
|
14
|
+
def lon
|
15
|
+
@address.longitude
|
16
|
+
end
|
17
|
+
|
18
|
+
def to_uri
|
19
|
+
if zipcode? || postcode?
|
20
|
+
@uri ||= "wx/#{string}.xml"
|
21
|
+
elsif geocode?
|
22
|
+
@uri ||= "wx/nearby.xml?lat=#{lat}&lon=#{lon}"
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def zipcode?
|
27
|
+
@kind == :zipcode
|
28
|
+
end
|
29
|
+
|
30
|
+
def postcode?
|
31
|
+
@kind == :postcode
|
32
|
+
end
|
33
|
+
|
34
|
+
def geocode?
|
35
|
+
@kind == :geocode
|
36
|
+
end
|
37
|
+
|
38
|
+
def self.parse(string)
|
39
|
+
if is_zipcode?(string) || is_canadian_postcode?(string)
|
40
|
+
return self.new(string)
|
41
|
+
else
|
42
|
+
geocode(string)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
def initialize(object)
|
47
|
+
if self.class.is_zipcode?(object)
|
48
|
+
@string = object
|
49
|
+
@kind = :zipcode
|
50
|
+
elsif self.class.is_canadian_postcode?(object)
|
51
|
+
@string = object
|
52
|
+
@kind = :postcode
|
53
|
+
elsif object.kind_of?(Google::Geo::Address)
|
54
|
+
@address = object
|
55
|
+
@kind = :geocode
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
def self.geocode(string)
|
60
|
+
locations = call_geocoder(string).map { |a| self.new(a) }
|
61
|
+
return locations.first if locations.size == 1
|
62
|
+
return locations
|
63
|
+
end
|
64
|
+
|
65
|
+
def self.call_geocoder(string)
|
66
|
+
geo = Google::Geo.new Hamweather.google_maps_api_key
|
67
|
+
|
68
|
+
begin
|
69
|
+
return geo.locate(string)
|
70
|
+
|
71
|
+
rescue Google::Geo::UnknownAddressError, Google::Geo::UnknownError
|
72
|
+
raise Hamweather::UnknownAddressError
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
def self.is_zipcode?(string)
|
77
|
+
#Address: ZIP code (US)
|
78
|
+
zip_codes_regex = /(^[0-9]{5}$)|(^[0-9]{5}-[0-9]{4}$)/
|
79
|
+
return string =~ zip_codes_regex
|
80
|
+
end
|
81
|
+
|
82
|
+
def self.is_canadian_postcode?(string)
|
83
|
+
#Rules: no D, F, I, O, Q, or U anywhere
|
84
|
+
# Basic validation: ^[ABCEGHJ-NPRSTVXY]{1}[0-9]{1}[ABCEGHJ-NPRSTV-Z]{1}[ ]?[0-9]{1}[ABCEGHJ-NPRSTV-Z]{1}[0-9]{1}$
|
85
|
+
# Extended validation: ^(A(0[ABCEGHJ-NPR]|1[ABCEGHK-NSV-Y]|2[ABHNV]|5[A]|8[A])|B(0[CEHJ-NPRSTVW]|1[ABCEGHJ-NPRSTV-Y]|2[ABCEGHJNRSTV-Z]|3[ABEGHJ-NPRSTVZ]|4[ABCEGHNPRV]|5[A]|6[L]|9[A])|C(0[AB]|1[ABCEN])|E(1[ABCEGHJNVWX]|2[AEGHJ-NPRSV]|3[ABCELNVYZ]|4[ABCEGHJ-NPRSTV-Z]|5[ABCEGHJ-NPRSTV]|6[ABCEGHJKL]|7[ABCEGHJ-NP]|8[ABCEGJ-NPRST]|9[ABCEGH])|G(0[ACEGHJ-NPRSTV-Z]|1[ABCEGHJ-NPRSTV-Y]|2[ABCEGJ-N]|3[ABCEGHJ-NZ]|4[ARSTVWXZ]|5[ABCHJLMNRTVXYZ]|6[ABCEGHJKLPRSTVWXZ]|7[ABGHJKNPSTXYZ]|8[ABCEGHJ-NPTVWYZ]|9[ABCHNPRTX])|H(0[HM]|1[ABCEGHJ-NPRSTV-Z]|2[ABCEGHJ-NPRSTV-Z]|3[ABCEGHJ-NPRSTV-Z]|4[ABCEGHJ-NPRSTV-Z]|5[AB]|7[ABCEGHJ-NPRSTV-Y]|8[NPRSTYZ]|9[ABCEGHJKPRSWX])|J(0[ABCEGHJ-NPRSTV-Z]|1[ACEGHJ-NRSTXZ]|2[ABCEGHJ-NRSTWXY]|3[ABEGHLMNPRTVXYZ]|4[BGHJ-NPRSTV-Z]|5[ABCJ-MRTV-Z]|6[AEJKNRSTVWYXZ]|7[ABCEGHJ-NPRTV-Z]|8[ABCEGHLMNPRTVXYZ]|9[ABEHJLNTVXYZ])|K(0[ABCEGHJ-M]|1[ABCEGHJ-NPRSTV-Z]|2[ABCEGHJ-MPRSTVW]|4[ABCKMPR]|6[AHJKTV]|7[ACGHK-NPRSV]|8[ABHNPRV]|9[AHJKLV])|L(0[[ABCEGHJ-NPRS]]|1[ABCEGHJ-NPRSTV-Z]|2[AEGHJMNPRSTVW]|3[BCKMPRSTVXYZ]|4[ABCEGHJ-NPRSTV-Z]|5[ABCEGHJ-NPRSTVW]|6[ABCEGHJ-MPRSTV-Z]|7[ABCEGJ-NPRST]|8[EGHJ-NPRSTVW]|9[ABCGHK-NPRSTVWYZ])|M(1[BCEGHJ-NPRSTVWX]|2[HJ-NPR]|3[ABCHJ-N]|4[ABCEGHJ-NPRSTV-Y]|5[ABCEGHJ-NPRSTVWX]|6[ABCEGHJ-NPRS]|7[AY]|8[V-Z]|9[ABCLMNPRVW])|N(0[ABCEGHJ-NPR]|1[ACEGHKLMPRST]|2[ABCEGHJ-NPRTVZ]|3[ABCEHLPRSTVWY]|4[BGKLNSTVWXZ]|5[ACHLPRV-Z]|6[ABCEGHJ-NP]|7[AGLMSTVWX]|8[AHMNPRSTV-Y]|9[ABCEGHJKVY])|P(0[ABCEGHJ-NPRSTV-Y]|1[ABCHLP]|2[ABN]|3[ABCEGLNPY]|4[NPR]|5[AEN]|6[ABC]|7[ABCEGJKL]|8[NT]|9[AN])|R(0[ABCEGHJ-M]|1[ABN]|2[CEGHJ-NPRV-Y]|3[ABCEGHJ-NPRSTV-Y]|4[AHJKL]|5[AGH]|6[MW]|7[ABCN]|8[AN]|9[A])|S(0[ACEGHJ-NP]|2[V]|3[N]|4[AHLNPRSTV-Z]|6[HJKVWX]|7[HJ-NPRSTVW]|9[AHVX])|T(0[ABCEGHJ-MPV]|1[ABCGHJ-MPRSV-Y]|2[ABCEGHJ-NPRSTV-Z]|3[ABCEGHJ-NPRZ]|4[ABCEGHJLNPRSTVX]|5[ABCEGHJ-NPRSTV-Z]|6[ABCEGHJ-NPRSTVWX]|7[AENPSVXYZ]|8[ABCEGHLNRSVWX]|9[ACEGHJKMNSVWX])|V(0[ABCEGHJ-NPRSTVWX]|1[ABCEGHJ-NPRSTV-Z]|2[ABCEGHJ-NPRSTV-Z]|3[ABCEGHJ-NRSTV-Y]|4[ABCEGK-NPRSTVWXZ]|5[ABCEGHJ-NPRSTV-Z]|6[ABCEGHJ-NPRSTV-Z]|7[ABCEGHJ-NPRSTV-Y]|8[ABCGJ-NPRSTV-Z]|9[ABCEGHJ-NPRSTV-Z])|X(0[ABCGX]|1[A])|Y(0[AB]|1[A]))[ ]?[0-9]{1}[ABCEGHJ-NPRSTV-Z]{1}[0-9]{1}$
|
86
|
+
ca_postcode_regex = /^[A-Z]{1}[\d]{1}[A-Z]{1}[ ]?[\d]{1}[A-Z]{1}[\d]{1}$/
|
87
|
+
return string =~ ca_postcode_regex
|
88
|
+
end
|
89
|
+
|
90
|
+
end
|
91
|
+
end
|
data/spec/daily_spec.rb
ADDED
@@ -0,0 +1,56 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/spec_helper'
|
2
|
+
|
3
|
+
#Hpricot.parse('<FPeriod interval="1" Day="FRI" Date="2008-11-14" Wx="Chance T-storms" Icon="tstorm.gif" HiF="66" HiC="19" LoF="56" LoC="13" Pop="60" Detail="Occasional showers with a chance of thunderstorms. Areas of fog. Some thunderstorms May be severe after midnight. Lows in the mid 50s. South winds 5 to 10 mph shifting to the southwest 10 to 15 mph after midnight. Chance of rain near 100 percent."/>').root
|
4
|
+
#=> {emptyelem <fperiod loc="13" wx="Chance T-storms" hic="19" lof="56" icon="tstorm.gif" date="2008-11-14" hif="66" pop="60" day="FRI" interval="1" detail="Occasional showers with a chance of thunderstorms. Areas of fog. Some thunderstorms May be severe after midnight. Lows in the mid 50s. South winds 5 to 10 mph shifting to the southwest 10 to 15 mph after midnight. Chance of rain near 100 percent.">}
|
5
|
+
|
6
|
+
describe Hamweather::Forecast::Daily do
|
7
|
+
|
8
|
+
def test_forecast_data
|
9
|
+
<<-XML
|
10
|
+
<FPeriod interval="1" Day="FRI" Date="2008-11-14" Wx="Chance T-storms" Icon="tstorm.gif" HiF="66" HiC="19" LoF="56" LoC="13" Pop="60" Detail="Occasional showers with a chance of thunderstorms. Areas of fog. Some thunderstorms May be severe after midnight. Lows in the mid 50s. South winds 5 to 10 mph shifting to the southwest 10 to 15 mph after midnight. Chance of rain near 100 percent."/>
|
11
|
+
XML
|
12
|
+
end
|
13
|
+
|
14
|
+
before(:each) do
|
15
|
+
@daily_forecast = Hamweather::Forecast::Daily.new(Hpricot.parse(test_forecast_data).root)
|
16
|
+
end
|
17
|
+
|
18
|
+
it "@hours should contain only hours belonging to 'today'" do
|
19
|
+
@daily_forecast.each_hour do |hour|
|
20
|
+
hour.date.should_be @daily_forecast.date
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
it "high_farenheit should be 66 degrees" do
|
25
|
+
@daily_forecast.high_farenheit.should == 66
|
26
|
+
end
|
27
|
+
|
28
|
+
it "high_celsius should be 19 degrees" do
|
29
|
+
@daily_forecast.high_celsius.should == 19
|
30
|
+
end
|
31
|
+
|
32
|
+
it "low_farenheit should be 56 degrees" do
|
33
|
+
@daily_forecast.low_farenheit.should == 56
|
34
|
+
end
|
35
|
+
|
36
|
+
it "low_celsius should be 13 degrees" do
|
37
|
+
@daily_forecast.low_celsius.should == 13
|
38
|
+
end
|
39
|
+
|
40
|
+
it "date should be the 14th of November 2008" do
|
41
|
+
@daily_forecast.date.should == Date.parse("2008-11-14")
|
42
|
+
end
|
43
|
+
|
44
|
+
it "expected_weather should be Chance T-storms" do
|
45
|
+
@daily_forecast.expected_weather.should == "Chance T-storms"
|
46
|
+
end
|
47
|
+
|
48
|
+
it "probability_of_preciptiation should be 60 percent" do
|
49
|
+
@daily_forecast.probability_of_preciptiation.should == 60
|
50
|
+
end
|
51
|
+
|
52
|
+
it "detail should be Occasional showers with a chance of thunderstorms. Areas of fog. Some thunderstorms May be severe after midnight. Lows in the mid 50s. South winds 5 to 10 mph shifting to the southwest 10 to 15 mph after midnight. Chance of rain near 100 percent." do
|
53
|
+
@daily_forecast.detail.should == "Occasional showers with a chance of thunderstorms. Areas of fog. Some thunderstorms May be severe after midnight. Lows in the mid 50s. South winds 5 to 10 mph shifting to the southwest 10 to 15 mph after midnight. Chance of rain near 100 percent."
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
@@ -0,0 +1,52 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/spec_helper'
|
2
|
+
|
3
|
+
describe Hamweather::Forecast::Daily do
|
4
|
+
|
5
|
+
before(:each) do
|
6
|
+
setup_hamweather_api_config
|
7
|
+
end
|
8
|
+
|
9
|
+
def test_forecast_data
|
10
|
+
<<-XML
|
11
|
+
<WeatherFeed><Location ID="12834" name="Greenwich" state="NY" country="US" tz="-5" tzname="EST" ><Astro><AstroPeriod Day="WED" Date="2008-12-03" Sunrise="7:06 AM EST" Sunset="4:19 PM EST" Moonrise="11:19 AM EST" Moonset="9:45 PM EST" MoonIllum="44%" MoonAge="6" MoonPhase="Waxing Crescent Moon" MoonIcon="6" /><AstroPeriod Day="WED" Date="2008-12-03" Sunrise="7:07 AM EST" Sunset="4:19 PM EST" Moonrise="11:41 AM EST" Moonset="10:50 PM EST" MoonIllum="50%" MoonAge="7" MoonPhase="First Quarter Moon" MoonIcon="7" /></Astro><WxOb StationID="KGFL" TempC="-4" TempF="25" ApparentC="-7" ApparentF="19" DewPointC="-5" DewPointF="23" RelativeHumidity="93" WindSpeedKnots="4" WindSpeedMPH="5" WindDirection="200" WindDirectionEng="SSW" WindGustKnots="0" WindGustMPH="0" PressureMB="1022" PressureIN="30.15" Wx="Fog/mist" Icon="fog.gif" Visibility="3SM" VisibilityKM="4.83" VisibilityMI="3" ReportEpoch="1228302540" ReportDate="2008-12-03 11:09 UTC" /><WxShortTerm ID="NYZ084"><STPeriod interval="1" Epoch="1228298400" Day="Wed" Date="2008-12-03" Time="10:00" Wx="Partly Cloudy" Icon="pcloudy.gif" TempC="0" TempF="32" Pop="10" QPFmm="" QPFin="" DewPointC="-5" DewPointF="23" RelativeHumidity="70" WindSpeedKnots="8" WindSpeedMPH="9" WindDirectionEng="SW" /></WxShortTerm><WxForecast ID="NYZ084"><FPeriod interval="1" Day="FRI" Date="2008-11-14" Wx="Chance T-storms" Icon="tstorm.gif" HiF="66" HiC="19" LoF="56" LoC="13" Pop="60" Detail="Occasional showers with a chance of thunderstorms. Areas of fog. Some thunderstorms May be severe after midnight. Lows in the mid 50s. South winds 5 to 10 mph shifting to the southwest 10 to 15 mph after midnight. Chance of rain near 100 percent."/></WxForecast></Location></WeatherFeed>
|
12
|
+
XML
|
13
|
+
end
|
14
|
+
|
15
|
+
def test_location_name
|
16
|
+
"Greenwich, NY, 12834, USA"
|
17
|
+
end
|
18
|
+
|
19
|
+
before(:each) do
|
20
|
+
#@forecast = Hamweather::Forecast.new(test_forecast_data)
|
21
|
+
@forecast = Hamweather::Forecast.new(Hamweather::Location.parse(test_location_name))
|
22
|
+
end
|
23
|
+
|
24
|
+
it "dailies{} should not be empty" do
|
25
|
+
@forecast.dailies.empty?.should be_false
|
26
|
+
end
|
27
|
+
|
28
|
+
it "each_day should yield Daily objects" do
|
29
|
+
@forecast.each_day do |day|
|
30
|
+
day.class.should == Hamweather::Forecast::Daily
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
it "each_hour should give back a Hash of Hourly objects" do
|
35
|
+
@forecast.each_day do |day|
|
36
|
+
day.each_hour do |hour|
|
37
|
+
hour.class.should == Hamweather::Forecast::Hourly
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
it "hourlies{} should not be empty" do
|
43
|
+
@forecast.hourlies.empty?.should be_false
|
44
|
+
end
|
45
|
+
|
46
|
+
it "each_hour should yield Hourly objects" do
|
47
|
+
@forecast.each_hour do |hour|
|
48
|
+
hour.class.should == Hamweather::Forecast::Hourly
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/spec_helper'
|
2
|
+
|
3
|
+
describe Hamweather do
|
4
|
+
|
5
|
+
describe "with no configuration" do
|
6
|
+
|
7
|
+
it "should raise an ApiKeyException" do
|
8
|
+
lambda { Hamweather.locate('Belfast') }.should raise_error(Hamweather::ApiKeyException)
|
9
|
+
end
|
10
|
+
|
11
|
+
it "should raise an ApiKeyException" do
|
12
|
+
lambda { Hamweather.forecast(mock(Hamweather::Location)) }.should raise_error(Hamweather::ApiKeyException)
|
13
|
+
end
|
14
|
+
|
15
|
+
end
|
16
|
+
|
17
|
+
describe "with a normal configuration" do
|
18
|
+
|
19
|
+
before(:each) do
|
20
|
+
setup_google_api_config
|
21
|
+
setup_hamweather_api_config
|
22
|
+
@location = Hamweather.locate('Belfast')
|
23
|
+
@forecast = Hamweather.forecast(@location)
|
24
|
+
end
|
25
|
+
|
26
|
+
it "should give a location" do
|
27
|
+
@location.kind_of?(Hamweather::Location).should be_true
|
28
|
+
end
|
29
|
+
|
30
|
+
it "should give a forecast" do
|
31
|
+
@forecast.kind_of?(Hamweather::Forecast).should be_true
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
end
|
36
|
+
|
37
|
+
|
data/spec/hourly_spec.rb
ADDED
@@ -0,0 +1,73 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/spec_helper'
|
2
|
+
|
3
|
+
#Hpricot.parse('<STPeriod interval="1" Epoch="1228298400" Day="Wed" Date="2008-12-03" Time="10:00" Wx="Partly Cloudy" Icon="pcloudy.gif" TempC="0" TempF="32" Pop="10" QPFmm="" QPFin="" DewPointC="-5" DewPointF="23" RelativeHumidity="70" WindSpeedKnots="8" WindSpeedMPH="9" WindDirectionEng="SW" />').root
|
4
|
+
#=> {emptyelem <stperiod qpfmm="" wx="Partly Cloudy" relativehumidity="70" dewpointc="-5" time="10:00" icon="pcloudy.gif" date="2008-12-03" tempc="0" qpfin="" windspeedknots="8" dewpointf="23" pop="10" day="Wed" winddirectioneng="SW" tempf="32" epoch="1228298400" interval="1" windspeedmph="9">}
|
5
|
+
|
6
|
+
describe Hamweather::Forecast::Hourly do
|
7
|
+
|
8
|
+
def test_forecast_data
|
9
|
+
<<-XML
|
10
|
+
<STPeriod interval="1" Epoch="1228298400" Day="Wed" Date="2008-12-03" Time="10:00" Wx="Partly Cloudy" Icon="pcloudy.gif" TempC="0" TempF="32" Pop="10" QPFmm="" QPFin="" DewPointC="-5" DewPointF="23" RelativeHumidity="70" WindSpeedKnots="8" WindSpeedMPH="9" WindDirectionEng="SW" />
|
11
|
+
XML
|
12
|
+
end
|
13
|
+
|
14
|
+
before(:each) do
|
15
|
+
@hourly_forecast = Hamweather::Forecast::Hourly.new(Hpricot.parse(test_forecast_data).root)
|
16
|
+
end
|
17
|
+
|
18
|
+
it "date should be 3rd December 2008" do
|
19
|
+
@hourly_forecast.date.should == Date.parse("2008-12-03")
|
20
|
+
end
|
21
|
+
|
22
|
+
it "time should be 10:00" do
|
23
|
+
@hourly_forecast.time.should == "10:00"
|
24
|
+
end
|
25
|
+
|
26
|
+
it "expected_weather should be Partly Cloudy" do
|
27
|
+
@hourly_forecast.expected_weather.should == "Partly Cloudy"
|
28
|
+
end
|
29
|
+
|
30
|
+
it "temp_farenheit should be 32" do
|
31
|
+
@hourly_forecast.temp_farenheit.should == 32
|
32
|
+
end
|
33
|
+
|
34
|
+
it "temp_celsius should be 0" do
|
35
|
+
@hourly_forecast.temp_celsius.should == 0
|
36
|
+
end
|
37
|
+
|
38
|
+
it "probability_of_precipitation should be 10" do
|
39
|
+
@hourly_forecast.probability_of_precipitation.should == 10
|
40
|
+
end
|
41
|
+
|
42
|
+
it "precipitation_millimeters should be " do
|
43
|
+
@hourly_forecast.precipitation_millimeters.should == ""
|
44
|
+
end
|
45
|
+
|
46
|
+
it "precipitation_inches should be " do
|
47
|
+
@hourly_forecast.precipitation_inches.should == ""
|
48
|
+
end
|
49
|
+
|
50
|
+
it "dew_point_celsius should be -5" do
|
51
|
+
@hourly_forecast.dew_point_celsius.should == -5
|
52
|
+
end
|
53
|
+
|
54
|
+
it "dew_point_farenheit should be 23" do
|
55
|
+
@hourly_forecast.dew_point_farenheit.should == 23
|
56
|
+
end
|
57
|
+
|
58
|
+
it "relative_humidity should be 70" do
|
59
|
+
@hourly_forecast.relative_humidity.should == 70
|
60
|
+
end
|
61
|
+
|
62
|
+
it "wind_speed_knots should be 8" do
|
63
|
+
@hourly_forecast.wind_speed_knots.should == 8
|
64
|
+
end
|
65
|
+
|
66
|
+
it "wind_speed_mph should be 9" do
|
67
|
+
@hourly_forecast.wind_speed_mph.should == 9
|
68
|
+
end
|
69
|
+
|
70
|
+
it "wind_direction should be SW" do
|
71
|
+
@hourly_forecast.wind_direction.should == "SW"
|
72
|
+
end
|
73
|
+
end
|
@@ -0,0 +1,77 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/spec_helper'
|
2
|
+
|
3
|
+
describe Hamweather::Location do
|
4
|
+
|
5
|
+
before(:each) do
|
6
|
+
setup_google_api_config
|
7
|
+
end
|
8
|
+
|
9
|
+
describe "with zipcode 12834" do
|
10
|
+
|
11
|
+
# should be a valid zipcode (Greenwich, NY, USA)
|
12
|
+
|
13
|
+
before(:each) do
|
14
|
+
@location = Hamweather::Location.parse("12834")
|
15
|
+
end
|
16
|
+
|
17
|
+
it ".to_uri should be 'wx/12834.xml'" do
|
18
|
+
@location.to_uri.should == "wx/12834.xml"
|
19
|
+
end
|
20
|
+
|
21
|
+
end
|
22
|
+
|
23
|
+
describe "with postcode H0H0H0" do
|
24
|
+
|
25
|
+
# should be a valid postcode (Santa, Lapland, CA)
|
26
|
+
|
27
|
+
before(:each) do
|
28
|
+
@location = Hamweather::Location.parse("H0H0H0")
|
29
|
+
end
|
30
|
+
|
31
|
+
it ".to_uri should be 'wx/H0H0H0.xml'" do
|
32
|
+
@location.to_uri.should == "wx/H0H0H0.xml"
|
33
|
+
end
|
34
|
+
|
35
|
+
end
|
36
|
+
|
37
|
+
describe "with an unambiguous address, '575 Burton Road, Greenwich, NY, 12834'" do
|
38
|
+
#Returns one option
|
39
|
+
|
40
|
+
before(:each) do
|
41
|
+
@location = Hamweather::Location.parse('575 Burton Road, Greenwich, NY, 12834')
|
42
|
+
end
|
43
|
+
|
44
|
+
it "should return one location object" do
|
45
|
+
#Google::Geo.should_receive(:new).with('thisapikey').and_return(mock(Google::Geo))
|
46
|
+
#so if you are using should_receive i'd put it in the actual it block of the spec
|
47
|
+
#so you should create the mock object first, set an expectation that ".locate" is called on it... then set the expectation that it's instantiated too!
|
48
|
+
@location.kind_of?(Hamweather::Location).should be_true
|
49
|
+
end
|
50
|
+
|
51
|
+
it ".to_uri should be 'wx/nearby.xml?lat=-73.504265&lon=43.0676821'" do
|
52
|
+
@location.to_uri.should == "wx/nearby.xml?lat=43.0676821&lon=-73.504265"
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
describe "with an ambiguous address, 'Greenwich, USA" do
|
57
|
+
#Returns circa 10 options, nb, no 'NY' or 'CT' in search string
|
58
|
+
before(:each) do
|
59
|
+
@location = Hamweather::Location.parse('Greenwich, USA')
|
60
|
+
end
|
61
|
+
|
62
|
+
it "should return an array of location objects" do
|
63
|
+
@location.kind_of?(Array).should be_true
|
64
|
+
@location.should_not be_empty
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
describe "with an unlocatable address" do
|
69
|
+
|
70
|
+
it "should raise a Hamweather::UnknownAddressError" do
|
71
|
+
|
72
|
+
lambda { Hamweather::Location.parse('') }.should raise_error(Hamweather::UnknownAddressError)
|
73
|
+
|
74
|
+
end
|
75
|
+
|
76
|
+
end
|
77
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,11 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require File.dirname(__FILE__) + '/../lib/hamweather'
|
3
|
+
|
4
|
+
|
5
|
+
def setup_google_api_config
|
6
|
+
Hamweather.google_maps_api_key = "ABQIAAAAuC9Wz6AZ_BvsKClq3zThQhT2yXp_ZAY8_ufC3CFXhHIE1NvwkxRAjq1Mt9DYkVx1c-jcuAsAreOT_w"
|
7
|
+
end
|
8
|
+
|
9
|
+
def setup_hamweather_api_config
|
10
|
+
Hamweather.api_key = "wxC1E2gT8AY7"
|
11
|
+
end
|
metadata
ADDED
@@ -0,0 +1,86 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: davidjrice-hamweather
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- David Rice
|
8
|
+
- David Lowry
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
|
13
|
+
date: 2008-10-13 00:00:00 -07:00
|
14
|
+
default_executable:
|
15
|
+
dependencies:
|
16
|
+
- !ruby/object:Gem::Dependency
|
17
|
+
name: google-geo
|
18
|
+
version_requirement:
|
19
|
+
version_requirements: !ruby/object:Gem::Requirement
|
20
|
+
requirements:
|
21
|
+
- - ">"
|
22
|
+
- !ruby/object:Gem::Version
|
23
|
+
version: 0.0.0
|
24
|
+
version:
|
25
|
+
- !ruby/object:Gem::Dependency
|
26
|
+
name: hpricot
|
27
|
+
version_requirement:
|
28
|
+
version_requirements: !ruby/object:Gem::Requirement
|
29
|
+
requirements:
|
30
|
+
- - ">"
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: 0.0.0
|
33
|
+
version:
|
34
|
+
description: Client API for Hamweather Weather Service (http://www.hamweather.com)
|
35
|
+
email: david@contrast.ie
|
36
|
+
executables: []
|
37
|
+
|
38
|
+
extensions: []
|
39
|
+
|
40
|
+
extra_rdoc_files:
|
41
|
+
- Manifest
|
42
|
+
- README
|
43
|
+
files:
|
44
|
+
- Manifest
|
45
|
+
- README
|
46
|
+
- Rakefile
|
47
|
+
- hamweather.gemspec
|
48
|
+
- lib/hamweather/forecast/daily.rb
|
49
|
+
- lib/hamweather/forecast/hourly.rb
|
50
|
+
- lib/hamweather/forecast.rb
|
51
|
+
- lib/hamweather/location.rb
|
52
|
+
- lib/hamweather.rb
|
53
|
+
has_rdoc: true
|
54
|
+
homepage: http://github.com/davidjrice/hamweather
|
55
|
+
post_install_message:
|
56
|
+
rdoc_options:
|
57
|
+
- --main
|
58
|
+
- README
|
59
|
+
require_paths:
|
60
|
+
- lib
|
61
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
62
|
+
requirements:
|
63
|
+
- - ">="
|
64
|
+
- !ruby/object:Gem::Version
|
65
|
+
version: "0"
|
66
|
+
version:
|
67
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
68
|
+
requirements:
|
69
|
+
- - ">="
|
70
|
+
- !ruby/object:Gem::Version
|
71
|
+
version: "0"
|
72
|
+
version:
|
73
|
+
requirements: []
|
74
|
+
|
75
|
+
rubyforge_project:
|
76
|
+
rubygems_version: 1.2.0
|
77
|
+
signing_key:
|
78
|
+
specification_version: 2
|
79
|
+
summary: Ruby Client API for Hamweather Weather Service (http://www.hamweather.com)
|
80
|
+
test_files:
|
81
|
+
- spec/daily_spec.rb
|
82
|
+
- spec/forecast_spec.rb
|
83
|
+
- spec/hamweather_spec.rb
|
84
|
+
- spec/hourly_spec.rb
|
85
|
+
- spec/location_spec.rb
|
86
|
+
- spec/spec_helper.rb
|