barometer 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.
Files changed (61) hide show
  1. data/LICENSE +20 -0
  2. data/README.rdoc +266 -0
  3. data/VERSION.yml +4 -0
  4. data/bin/barometer +63 -0
  5. data/lib/barometer.rb +52 -0
  6. data/lib/barometer/base.rb +52 -0
  7. data/lib/barometer/data.rb +15 -0
  8. data/lib/barometer/data/current.rb +93 -0
  9. data/lib/barometer/data/distance.rb +131 -0
  10. data/lib/barometer/data/forecast.rb +66 -0
  11. data/lib/barometer/data/geo.rb +98 -0
  12. data/lib/barometer/data/location.rb +20 -0
  13. data/lib/barometer/data/measurement.rb +161 -0
  14. data/lib/barometer/data/pressure.rb +133 -0
  15. data/lib/barometer/data/speed.rb +147 -0
  16. data/lib/barometer/data/sun.rb +35 -0
  17. data/lib/barometer/data/temperature.rb +164 -0
  18. data/lib/barometer/data/units.rb +55 -0
  19. data/lib/barometer/data/zone.rb +124 -0
  20. data/lib/barometer/extensions/graticule.rb +50 -0
  21. data/lib/barometer/extensions/httparty.rb +21 -0
  22. data/lib/barometer/query.rb +228 -0
  23. data/lib/barometer/services.rb +6 -0
  24. data/lib/barometer/services/google.rb +146 -0
  25. data/lib/barometer/services/noaa.rb +6 -0
  26. data/lib/barometer/services/service.rb +324 -0
  27. data/lib/barometer/services/weather_bug.rb +6 -0
  28. data/lib/barometer/services/weather_dot_com.rb +6 -0
  29. data/lib/barometer/services/wunderground.rb +285 -0
  30. data/lib/barometer/services/yahoo.rb +274 -0
  31. data/lib/barometer/weather.rb +187 -0
  32. data/spec/barometer_spec.rb +162 -0
  33. data/spec/data_current_spec.rb +225 -0
  34. data/spec/data_distance_spec.rb +336 -0
  35. data/spec/data_forecast_spec.rb +150 -0
  36. data/spec/data_geo_spec.rb +90 -0
  37. data/spec/data_location_spec.rb +59 -0
  38. data/spec/data_measurement_spec.rb +411 -0
  39. data/spec/data_pressure_spec.rb +336 -0
  40. data/spec/data_speed_spec.rb +374 -0
  41. data/spec/data_sun_spec.rb +76 -0
  42. data/spec/data_temperature_spec.rb +396 -0
  43. data/spec/data_zone_spec.rb +133 -0
  44. data/spec/fixtures/current_calgary_ab.xml +1 -0
  45. data/spec/fixtures/forecast_calgary_ab.xml +1 -0
  46. data/spec/fixtures/geocode_40_73.xml +1 -0
  47. data/spec/fixtures/geocode_90210.xml +1 -0
  48. data/spec/fixtures/geocode_T5B4M9.xml +1 -0
  49. data/spec/fixtures/geocode_calgary_ab.xml +1 -0
  50. data/spec/fixtures/geocode_newyork_ny.xml +1 -0
  51. data/spec/fixtures/google_calgary_ab.xml +1 -0
  52. data/spec/fixtures/yahoo_90210.xml +1 -0
  53. data/spec/query_spec.rb +469 -0
  54. data/spec/service_google_spec.rb +144 -0
  55. data/spec/service_wunderground_spec.rb +330 -0
  56. data/spec/service_yahoo_spec.rb +299 -0
  57. data/spec/services_spec.rb +1106 -0
  58. data/spec/spec_helper.rb +14 -0
  59. data/spec/units_spec.rb +101 -0
  60. data/spec/weather_spec.rb +265 -0
  61. metadata +119 -0
@@ -0,0 +1,187 @@
1
+ module Barometer
2
+
3
+ class Weather
4
+
5
+ # hash of measurements indexed by :source
6
+ attr_accessor :measurements
7
+
8
+ def initialize
9
+ @measurements = []
10
+ end
11
+
12
+ # the default source is the first source/measurement that we
13
+ # have successful results for
14
+ def default
15
+ return nil unless self.sources
16
+ self.source(self.sources.first)
17
+ end
18
+
19
+ # find the measurement for the given source, if it exists
20
+ def source(source)
21
+ raise ArgumentError unless (source.is_a?(String) || source.is_a?(Symbol))
22
+ @measurements.each do |measurement|
23
+ return measurement if measurement.source == source.to_sym
24
+ end
25
+ nil
26
+ end
27
+
28
+ # list successful sources
29
+ def sources
30
+ @measurements.collect {|m| m.source.to_sym if m.success?}.compact
31
+ end
32
+
33
+ #
34
+ # Quick access methods
35
+ #
36
+
37
+ def current
38
+ (default = self.default) ? default.current : nil
39
+ end
40
+
41
+ def forecast
42
+ (default = self.default) ? default.forecast : nil
43
+ end
44
+
45
+ def now
46
+ self.current
47
+ end
48
+
49
+ def today
50
+ default = self.default
51
+ default && default.forecast ? default.forecast[0] : nil
52
+ end
53
+
54
+ def tomorrow
55
+ default = self.default
56
+ default && default.forecast ? default.forecast[1] : nil
57
+ end
58
+
59
+ def for(query)
60
+ default = self.default
61
+ default && default.forecast ? default.for(query) : nil
62
+ end
63
+
64
+ #
65
+ # helper methods
66
+ #
67
+ # these are handy methods that can average values for successful weather
68
+ # sources, or answer a simple question (ie: weather.windy?)
69
+ #
70
+
71
+ #
72
+ # averages
73
+ #
74
+
75
+ # average of all humidity values
76
+ # def humidity
77
+ # end
78
+ #
79
+ # # average of all temperature values
80
+ # def temperature
81
+ # end
82
+ #
83
+ # # average of all wind speed values
84
+ # def wind
85
+ # end
86
+ #
87
+ # # average of all pressure values
88
+ # def pressure
89
+ # end
90
+ #
91
+ # # average of all dew_point values
92
+ # def dew_point
93
+ # end
94
+ #
95
+ # # average of all heat_index values
96
+ # def heat_index
97
+ # end
98
+ #
99
+ # # average of all wind_chill values
100
+ # def wind_chill
101
+ # end
102
+ #
103
+ # # average of all visibility values
104
+ # def visibility
105
+ # end
106
+
107
+ #
108
+ # quick access methods
109
+ #
110
+
111
+ # what is the current local time and date?
112
+ # def time
113
+ # end
114
+
115
+ # def icon
116
+ # self.current.icon
117
+ # end
118
+
119
+ #
120
+ # simple questions
121
+ # pass the question on to each successful measurement until we get an answer
122
+ #
123
+
124
+ def windy?(threshold=10, utc_time=Time.now.utc)
125
+ raise ArgumentError unless (threshold.is_a?(Fixnum) || threshold.is_a?(Float))
126
+ raise ArgumentError unless utc_time.is_a?(Time)
127
+
128
+ is_windy = nil
129
+ @measurements.each do |measurement|
130
+ if measurement.success?
131
+ is_windy = measurement.windy?(threshold, utc_time)
132
+ return is_windy if !is_windy.nil?
133
+ end
134
+ end
135
+ is_windy
136
+ end
137
+
138
+ def wet?(threshold=50, utc_time=Time.now.utc)
139
+ raise ArgumentError unless (threshold.is_a?(Fixnum) || threshold.is_a?(Float))
140
+ raise ArgumentError unless utc_time.is_a?(Time)
141
+
142
+ is_wet = nil
143
+ @measurements.each do |measurement|
144
+ if measurement.success?
145
+ is_wet = measurement.wet?(threshold, utc_time)
146
+ return is_wet if !is_wet.nil?
147
+ end
148
+ end
149
+ is_wet
150
+ end
151
+
152
+ def day?(utc_time=Time.now.utc)
153
+ raise ArgumentError unless utc_time.is_a?(Time)
154
+
155
+ is_day = nil
156
+ @measurements.each do |measurement|
157
+ if measurement.success?
158
+ is_day = measurement.day?(utc_time)
159
+ return is_day if !is_day.nil?
160
+ end
161
+ end
162
+ is_day
163
+ end
164
+
165
+ def night?(utc_time=Time.now.utc)
166
+ raise ArgumentError unless utc_time.is_a?(Time)
167
+ is_day = self.day?(utc_time)
168
+ is_day.nil? ? nil : !is_day
169
+ end
170
+
171
+ def sunny?(utc_time=Time.now.utc)
172
+ raise ArgumentError unless utc_time.is_a?(Time)
173
+
174
+ is_sunny = nil
175
+ @measurements.each do |measurement|
176
+ if measurement.success?
177
+ return false if self.day?(utc_time) == false
178
+ is_sunny = measurement.sunny?(utc_time)
179
+ return is_sunny if !is_sunny.nil?
180
+ end
181
+ end
182
+ is_sunny
183
+ end
184
+
185
+ end
186
+
187
+ end
@@ -0,0 +1,162 @@
1
+ require 'spec_helper'
2
+
3
+ describe "Barometer" do
4
+
5
+ before(:each) do
6
+ @preference_hash = { 1 => [:wunderground] }
7
+ @key = "ABQIAAAAq8TH4offRcGrok8JVY_MyxRi_j0U6kJrkFvY4-OX2XYmEAa76BSFwMlSow1YgX8BOPUeve_shMG7xw"
8
+ end
9
+
10
+ describe "and class methods" do
11
+
12
+ it "defines selection" do
13
+ Barometer::Base.respond_to?("selection").should be_true
14
+ Barometer::Base.selection.should == { 1 => [:wunderground] }
15
+ Barometer::Base.selection = { 1 => [:yahoo] }
16
+ Barometer::Base.selection.should == { 1 => [:yahoo] }
17
+ Barometer.selection = @preference_hash
18
+ end
19
+
20
+ it "returns a Weather Service driver" do
21
+ Barometer.source(:wunderground).should == Barometer::Wunderground
22
+ end
23
+
24
+ it "sets the Graticule Google geocoding API key" do
25
+ Barometer.respond_to?("google_geocode_key").should be_true
26
+ Barometer.google_geocode_key.should be_nil
27
+ Barometer.google_geocode_key = @key
28
+ Barometer.google_geocode_key.should == @key
29
+ end
30
+
31
+ it "skips the use of Graticule" do
32
+ Barometer.respond_to?("skip_graticule").should be_true
33
+ Barometer.skip_graticule.should be_false
34
+ Barometer.skip_graticule = true
35
+ Barometer.skip_graticule.should be_true
36
+ end
37
+
38
+ it "forces the geocoding of queries" do
39
+ Barometer.respond_to?("force_geocode").should be_true
40
+ Barometer.force_geocode.should be_false
41
+ Barometer.force_geocode = true
42
+ Barometer.force_geocode.should be_true
43
+ end
44
+
45
+ it "forces the geocoding of queries" do
46
+ Barometer.respond_to?("force_geocode!").should be_true
47
+ Barometer.force_geocode = false
48
+ Barometer.force_geocode.should be_false
49
+ Barometer.force_geocode!
50
+ Barometer.force_geocode.should be_true
51
+ end
52
+
53
+ it "set the global service timeout" do
54
+ Barometer.respond_to?("timeout").should be_true
55
+ Barometer.timeout.should == 15
56
+ Barometer.timeout = 5
57
+ Barometer.timeout.should == 5
58
+ end
59
+
60
+ end
61
+
62
+ describe "when initialized" do
63
+
64
+ before(:each) do
65
+ @barometer_direct = Barometer.new
66
+ @barometer = Barometer::Base.new
67
+ end
68
+
69
+ it "responds to query" do
70
+ @barometer_direct.respond_to?("query").should be_true
71
+ @barometer.respond_to?("query").should be_true
72
+ end
73
+
74
+ it "sets the query" do
75
+ query = "query"
76
+ barometer = Barometer.new(query)
77
+ barometer.query.is_a?(Barometer::Query)
78
+ barometer.query.q.should == query
79
+
80
+ barometer = Barometer::Base.new(query)
81
+ barometer.query.is_a?(Barometer::Query)
82
+ barometer.query.q.should == query
83
+ end
84
+
85
+ it "responds to weather" do
86
+ @barometer.weather.is_a?(Barometer::Weather).should be_true
87
+ @barometer_direct.weather.is_a?(Barometer::Weather).should be_true
88
+ end
89
+
90
+ it "responds to success" do
91
+ @barometer.success.should be_false
92
+ @barometer_direct.success.should be_false
93
+ @barometer_direct.success?.should be_false
94
+ @barometer_direct.success = true
95
+ @barometer_direct.success?.should be_true
96
+ end
97
+
98
+ end
99
+
100
+ describe "when measuring" do
101
+
102
+ before(:each) do
103
+ Barometer.google_geocode_key = @key
104
+ query_term = "Calgary,AB"
105
+ @barometer = Barometer::Base.new(query_term)
106
+ @time = Time.now
107
+ FakeWeb.register_uri(:get,
108
+ "http://api.wunderground.com/auto/wui/geo/WXCurrentObXML/index.xml?query=#{CGI.escape(query_term)}",
109
+ :string => File.read(File.join(File.dirname(__FILE__),
110
+ 'fixtures',
111
+ 'current_calgary_ab.xml')
112
+ )
113
+ )
114
+ FakeWeb.register_uri(:get,
115
+ "http://api.wunderground.com/auto/wui/geo/ForecastXML/index.xml?query=#{CGI.escape(query_term)}",
116
+ :string => File.read(File.join(File.dirname(__FILE__),
117
+ 'fixtures',
118
+ 'forecast_calgary_ab.xml')
119
+ )
120
+ )
121
+ FakeWeb.register_uri(:get,
122
+ "http://maps.google.com:80/maps/geo?gl=&q=Calgary%2CAB&output=xml&key=#{@key}",
123
+ :string => File.read(File.join(File.dirname(__FILE__),
124
+ 'fixtures',
125
+ 'geocode_calgary_ab.xml')
126
+ )
127
+ )
128
+
129
+
130
+ FakeWeb.register_uri(:get,
131
+ "http://api.wunderground.com/auto/wui/geo/WXCurrentObXML/index.xml?query=51.055149%2C-114.062438",
132
+ :string => File.read(File.join(File.dirname(__FILE__),
133
+ 'fixtures',
134
+ 'current_calgary_ab.xml')
135
+ )
136
+ )
137
+ FakeWeb.register_uri(:get,
138
+ "http://api.wunderground.com/auto/wui/geo/ForecastXML/index.xml?query=51.055149%2C-114.062438",
139
+ :string => File.read(File.join(File.dirname(__FILE__),
140
+ 'fixtures',
141
+ 'forecast_calgary_ab.xml')
142
+ )
143
+ )
144
+
145
+ end
146
+
147
+ it "responds to measure" do
148
+ @barometer.respond_to?("measure").should be_true
149
+ end
150
+
151
+ it "returns a Barometer::Weather object" do
152
+ @barometer.measure.is_a?(Barometer::Weather).should be_true
153
+ end
154
+
155
+ it "raises OutOfSources if no services successful" do
156
+ Barometer::Base.selection = { 1 => [] }
157
+ lambda { @barometer.measure }.should raise_error(Barometer::OutOfSources)
158
+ end
159
+
160
+ end
161
+
162
+ end
@@ -0,0 +1,225 @@
1
+ require 'spec_helper'
2
+
3
+ describe "Current Measurement" do
4
+
5
+ describe "when initialized" do
6
+
7
+ before(:each) do
8
+ @current = Barometer::CurrentMeasurement.new
9
+ end
10
+
11
+ it "responds to time" do
12
+ @current.time.should be_nil
13
+ end
14
+
15
+ it "responds to humidity" do
16
+ @current.humidity.should be_nil
17
+ end
18
+
19
+ it "responds to icon" do
20
+ @current.icon.should be_nil
21
+ end
22
+
23
+ it "responds to condition" do
24
+ @current.condition.should be_nil
25
+ end
26
+
27
+ it "responds to temperature" do
28
+ @current.temperature.should be_nil
29
+ end
30
+
31
+ it "responds to dew_point" do
32
+ @current.dew_point.should be_nil
33
+ end
34
+
35
+ it "responds to heat_index" do
36
+ @current.heat_index.should be_nil
37
+ end
38
+
39
+ it "responds to wind_chill" do
40
+ @current.wind_chill.should be_nil
41
+ end
42
+
43
+ it "responds to wind" do
44
+ @current.wind.should be_nil
45
+ end
46
+
47
+ it "responds to pressure" do
48
+ @current.pressure.should be_nil
49
+ end
50
+
51
+ it "responds to visibility" do
52
+ @current.pressure.should be_nil
53
+ end
54
+
55
+ it "responds to sun" do
56
+ @current.sun.should be_nil
57
+ end
58
+
59
+ end
60
+
61
+ describe "when writing data" do
62
+
63
+ before(:each) do
64
+ @current = Barometer::CurrentMeasurement.new
65
+ end
66
+
67
+ # it "only accepts Time for time" do
68
+ # invalid_data = 1
69
+ # invalid_data.class.should_not == Time
70
+ # lambda { @current.time = invalid_data }.should raise_error(ArgumentError)
71
+ #
72
+ # valid_data = Time.new
73
+ # valid_data.class.should == Time
74
+ # lambda { @current.time = valid_data }.should_not raise_error(ArgumentError)
75
+ # end
76
+
77
+ it "only accepts Fixnum or Float for humidity" do
78
+ invalid_data = "invalid"
79
+ invalid_data.class.should_not == Fixnum
80
+ invalid_data.class.should_not == Float
81
+ lambda { @current.humidity = invalid_data }.should raise_error(ArgumentError)
82
+
83
+ valid_data = 1.to_i
84
+ valid_data.class.should == Fixnum
85
+ lambda { @current.humidity = valid_data }.should_not raise_error(ArgumentError)
86
+
87
+ valid_data = 1.0.to_f
88
+ valid_data.class.should == Float
89
+ lambda { @current.humidity = valid_data }.should_not raise_error(ArgumentError)
90
+ end
91
+
92
+ it "only accepts String for icon" do
93
+ invalid_data = 1
94
+ invalid_data.class.should_not == String
95
+ lambda { @current.icon = invalid_data }.should raise_error(ArgumentError)
96
+
97
+ valid_data = "valid"
98
+ valid_data.class.should == String
99
+ lambda { @current.icon = valid_data }.should_not raise_error(ArgumentError)
100
+ end
101
+
102
+ it "only accepts String for condition" do
103
+ invalid_data = 1
104
+ invalid_data.class.should_not == String
105
+ lambda { @current.condition = invalid_data }.should raise_error(ArgumentError)
106
+
107
+ valid_data = "valid"
108
+ valid_data.class.should == String
109
+ lambda { @current.condition = valid_data }.should_not raise_error(ArgumentError)
110
+ end
111
+
112
+ it "only accepts Barometer::Temperature for temperature" do
113
+ invalid_data = 1
114
+ invalid_data.class.should_not == Barometer::Temperature
115
+ lambda { @current.temperature = invalid_data }.should raise_error(ArgumentError)
116
+
117
+ valid_data = Barometer::Temperature.new
118
+ valid_data.class.should == Barometer::Temperature
119
+ lambda { @current.temperature = valid_data }.should_not raise_error(ArgumentError)
120
+ end
121
+
122
+ it "only accepts Barometer::Temperature for dew_point" do
123
+ invalid_data = 1
124
+ invalid_data.class.should_not == Barometer::Temperature
125
+ lambda { @current.dew_point = invalid_data }.should raise_error(ArgumentError)
126
+
127
+ valid_data = Barometer::Temperature.new
128
+ valid_data.class.should == Barometer::Temperature
129
+ lambda { @current.dew_point = valid_data }.should_not raise_error(ArgumentError)
130
+ end
131
+
132
+ it "only accepts Barometer::Temperature for heat_index" do
133
+ invalid_data = 1
134
+ invalid_data.class.should_not == Barometer::Temperature
135
+ lambda { @current.heat_index = invalid_data }.should raise_error(ArgumentError)
136
+
137
+ valid_data = Barometer::Temperature.new
138
+ valid_data.class.should == Barometer::Temperature
139
+ lambda { @current.heat_index = valid_data }.should_not raise_error(ArgumentError)
140
+ end
141
+
142
+ it "only accepts Barometer::Temperature for wind_chill" do
143
+ invalid_data = 1
144
+ invalid_data.class.should_not == Barometer::Temperature
145
+ lambda { @current.wind_chill = invalid_data }.should raise_error(ArgumentError)
146
+
147
+ valid_data = Barometer::Temperature.new
148
+ valid_data.class.should == Barometer::Temperature
149
+ lambda { @current.wind_chill = valid_data }.should_not raise_error(ArgumentError)
150
+ end
151
+
152
+ it "only accepts Barometer::Speed for wind" do
153
+ invalid_data = 1
154
+ invalid_data.class.should_not == Barometer::Speed
155
+ lambda { @current.wind = invalid_data }.should raise_error(ArgumentError)
156
+
157
+ valid_data = Barometer::Speed.new
158
+ valid_data.class.should == Barometer::Speed
159
+ lambda { @current.wind = valid_data }.should_not raise_error(ArgumentError)
160
+ end
161
+
162
+ it "only accepts Barometer::Pressure for pressure" do
163
+ invalid_data = 1
164
+ invalid_data.class.should_not == Barometer::Pressure
165
+ lambda { @current.pressure = invalid_data }.should raise_error(ArgumentError)
166
+
167
+ valid_data = Barometer::Pressure.new
168
+ valid_data.class.should == Barometer::Pressure
169
+ lambda { @current.pressure = valid_data }.should_not raise_error(ArgumentError)
170
+ end
171
+
172
+ it "only accepts Barometer::Distance for visibility" do
173
+ invalid_data = 1
174
+ invalid_data.class.should_not == Barometer::Distance
175
+ lambda { @current.visibility = invalid_data }.should raise_error(ArgumentError)
176
+
177
+ valid_data = Barometer::Distance.new
178
+ valid_data.class.should == Barometer::Distance
179
+ lambda { @current.visibility = valid_data }.should_not raise_error(ArgumentError)
180
+ end
181
+
182
+ it "only accepts Barometer::Sun for sun" do
183
+ invalid_data = 1
184
+ invalid_data.class.should_not == Barometer::Sun
185
+ lambda { @current.sun = invalid_data }.should raise_error(ArgumentError)
186
+
187
+ valid_data = Barometer::Sun.new
188
+ valid_data.class.should == Barometer::Sun
189
+ lambda { @current.sun = valid_data }.should_not raise_error(ArgumentError)
190
+ end
191
+
192
+ end
193
+
194
+ describe "method missing" do
195
+
196
+ before(:each) do
197
+ @current = Barometer::CurrentMeasurement.new
198
+ end
199
+
200
+ it "responds to method + ?" do
201
+ valid_method = "humidity"
202
+ @current.respond_to?(valid_method).should be_true
203
+ lambda { @current.send(valid_method + "?") }.should_not raise_error(NoMethodError)
204
+ end
205
+
206
+ it "ignores non_method + ?" do
207
+ invalid_method = "humid"
208
+ @current.respond_to?(invalid_method).should be_false
209
+ lambda { @current.send(invalid_method + "?") }.should raise_error(NoMethodError)
210
+ end
211
+
212
+ it "returns true if set" do
213
+ @current.humidity = 10
214
+ @current.humidity.should_not be_nil
215
+ @current.humidity?.should be_true
216
+ end
217
+
218
+ it "returns false if not set" do
219
+ @current.humidity.should be_nil
220
+ @current.humidity?.should be_false
221
+ end
222
+
223
+ end
224
+
225
+ end