attack-barometer 0.1.0 → 0.2.3

Sign up to get free protection for your applications and to get access to all the features.
data/LICENSE CHANGED
@@ -1,4 +1,4 @@
1
- Copyright (c) 2009 Mark
1
+ Copyright (c) 2009 Mark G
2
2
 
3
3
  Permission is hereby granted, free of charge, to any person obtaining
4
4
  a copy of this software and associated documentation files (the
data/README.rdoc CHANGED
@@ -8,28 +8,43 @@ information, or they can be used in a hierarchical configuration where lower
8
8
  preferred weather services are only used if previous services are
9
9
  unavailable.
10
10
 
11
+ == version
12
+
13
+ Version 0.1.0 is the current release of this gem.
14
+ The gem is available from github (attack-barometer) or rubyforge (barometer).
15
+ It is fully functional (for three weather service APIs).
16
+
11
17
  == status
12
18
 
13
19
  Currently this project is in development and will only work for a few weather
14
20
  services (wunderground, google, yahoo).
15
21
 
16
- Features to be added before first release:
17
- - gem setup/config, apply to rubyforge
18
-
19
- Features to be added in future releases:
22
+ Features to be added next:
20
23
  - even more weather service drivers (noaa, weather.com, weatherbug)
21
- - ability to query multiple services and combine/average the results
22
- - support iaco as a query format
23
24
 
24
25
  = dependencies
25
26
 
27
+ === Google API key
28
+
29
+ In most cases you will need to have a free google geocode api key.
30
+ Get one here: http://code.google.com/apis/maps/signup.html
31
+ Then put it in the file '~/.barometer' by adding the line:
32
+ geocode_google: YOUR_KEY_HERE
33
+
34
+ You will need this for:
35
+ - using the Barometer gem (unless you use queries that are directly supported
36
+ by the weather source API, ie yahoo will take a zip code directly and doesn't
37
+ require any geocoding)
38
+ - running the Barometer binary
39
+ - running the Barometer Web Demo
40
+
26
41
  === HTTParty
27
42
 
28
43
  Why? HTTParty was created and designed specifically for consuming web services.
29
44
  I choose to use this over using the Net::HTTP library directly to allow for
30
45
  faster development of this project.
31
46
 
32
- HTTParty is also extended to include configurable Timoout support.
47
+ HTTParty is also extended to include configurable Timeout support.
33
48
 
34
49
  === tzinfo
35
50
 
@@ -96,6 +111,18 @@ You can use barometer from the command line.
96
111
  # barometer berlin
97
112
 
98
113
  This will output the weather information for the given query.
114
+ See the help for more command line information.
115
+
116
+ # barometer -h
117
+
118
+ === web demo
119
+
120
+ There is a Sinatra application that demos the functionality of Barometer,
121
+ and provides Barometer information. Start this local demo with:
122
+
123
+ # barometer -w
124
+
125
+ NOTE: This requires the gems "sinatra" and "vegas".
99
126
 
100
127
  === fail
101
128
 
@@ -188,6 +215,25 @@ the data as shown in the above examples.
188
215
 
189
216
  puts weather.source(:wunderground).for(time).low.f
190
217
 
218
+ == averages
219
+
220
+ If you consume more then one weather service, Barometer can provide averages
221
+ for the values (currently only for the 'current' values and not the forecasted
222
+ values).
223
+
224
+ require 'barometer'
225
+
226
+ Barometer.google_geocode_key = "THE_GOOGLE_API_KEY"
227
+ # use yahoo and wunderground
228
+ Barometer.selection = { 1 => [:yahoo, :wunderground] }
229
+
230
+ barometer = Barometer.new("90210")
231
+ weather = barometer.measure
232
+
233
+ puts weather.temperture
234
+
235
+ This will calculate the average temperature as given by :yahoo and :wunderground
236
+
191
237
  == simple answers
192
238
 
193
239
  After you have measured the data, Barometer provides several "simple answer"
data/VERSION.yml CHANGED
@@ -1,4 +1,4 @@
1
1
  ---
2
+ :patch: 3
2
3
  :major: 0
3
- :minor: 1
4
- :patch: 0
4
+ :minor: 2
data/bin/barometer CHANGED
@@ -1,63 +1,417 @@
1
1
  #!/usr/bin/env ruby
2
-
3
- require File.dirname(__FILE__) + '/../lib/barometer'
4
- #require 'rubygems'
5
- #require 'attack-barometer'
6
-
7
- # TODO
8
-
9
- # set default Google Key ... maybe need a config file?
10
- Barometer.google_geocode_key = "ABQIAAAAq8TH4offRcGrok8JVY_MyxRi_j0U6kJrkFvY4-OX2XYmEAa76BSFwMlSow1YgX8BOPUeve_shMG7xw"
11
-
12
- # take command line paramters
13
- # service: --yahoo, --wunderground, --google
14
- # units: -m --metric, -i --imperial
15
- # geocode: -g --geocode (force geocode)
16
- # timeout: -t 15 --timeout 15
17
- # skip: --skip (skip graticule)
18
- # help: -h --help
19
- # wet threshold: -w --wet
20
- # windy threshold: -v --wind
21
- # pop threshold: -p --pop
22
- # time: -a --at
23
-
24
- # prettier output
25
- # show simple answers
26
- # more help
27
- # error display (out of sources, etc.)
28
-
29
- if ARGV.size == 0
30
- puts 'Barometer [Powered by wunderground]'
31
- puts 'USAGE: barometer [query]'
32
- puts 'EXAMPLES:'
33
- puts ' barometer paris'
34
- exit
2
+
3
+ # == Barometer
4
+ # This is the command line interface to the barometer gem.
5
+ #
6
+ # == Examples
7
+ # This command will measure the weather for the given query.
8
+ # barometer berlin
9
+ #
10
+ # Other examples:
11
+ # barometer --yahoo 90210
12
+ # barometer --verbose 'new york'
13
+ #
14
+ # == Local Web Demo
15
+ # You can easily interact directly with barometer with the command:
16
+ # barometer -w
17
+ #
18
+ # This demo has 2 gem requirements:
19
+ # - sinatra (tested with 0.9.1.1)
20
+ # - vegas (tested with 0.0.1)
21
+ #
22
+ # == Usage
23
+ # barometer [options] query
24
+ #
25
+ # For help use: barometer -h
26
+ #
27
+ # Options:
28
+ # -v, --version Display the version, then exit
29
+ # -V, --verbose Verbose output
30
+ # -t, --timeout seconds until service queries will timeout
31
+ # -g, --geocode Force Geocoding of query
32
+ # -m, --metric measure in metric
33
+ # -i, --imperial measure in imperial
34
+ # --no-wunderground DONT use default wunderground as source
35
+ # --yahoo use yahoo as source
36
+ # --google use google as source
37
+ # -p, --pop pop threshold used to determine wet?
38
+ # -s, --wind wind speed threshold used to determine windy?
39
+ # -a, --at time/date used to determine when to calculate summary
40
+ #
41
+ # Web Demo:
42
+ # -w, --web run web-app with barometer demo
43
+ # -k, --kill stop the web demo background process
44
+ # -S, --status show the web demo status
45
+ #
46
+ # == Author
47
+ # Mark G
48
+ # http://github.com/attack/barometer
49
+ #
50
+ # == Copyright
51
+ # Copyright (c) 2009 Mark G. Licensed under the MIT License:
52
+ # http://www.opensource.org/licenses/mit-license.php
53
+
54
+ require 'rubygems'
55
+ require 'barometer'
56
+
57
+ require 'optparse'
58
+ require 'rdoc/usage'
59
+ require 'ostruct'
60
+ require 'time'
61
+ require 'date'
62
+ require 'yaml'
63
+
64
+ # file where API keys are stored
65
+ KEY_FILE = File.expand_path(File.join('~', '.barometer'))
66
+
67
+ class App
68
+ VERSION = '0.2.3'
69
+
70
+ attr_reader :options
71
+
72
+ def initialize(arguments, stdin)
73
+ @arguments = arguments.dup
74
+
75
+ # Set defaults
76
+ @options = OpenStruct.new
77
+ @options.timeout = 15
78
+ @options.geocode = false
79
+ @options.skip_graticule = false
80
+ @options.metric = true
81
+ @options.sources = [:wunderground]
82
+ @options.verbode = false
83
+ @options.web = false
84
+ @options.at = Time.now.utc
85
+
86
+ # thresholds
87
+ @options.windy_m = 10
88
+ @options.windy_i = 7
89
+ @options.pop = 50
90
+ end
91
+
92
+ # Parse options, check arguments, then process the command
93
+ def run
94
+ if parsed_options? && arguments_valid?
95
+ puts "Start at #{DateTime.now}\n\n" if @options.verbose
96
+ output_options if @options.verbose # [Optional]
97
+
98
+ process_arguments
99
+ process_command
100
+
101
+ puts "\nFinished at #{DateTime.now}" if @options.verbose
102
+ else
103
+ output_usage
104
+ end
105
+ end
106
+
107
+ protected
108
+
109
+ # future options
110
+ #
111
+ # time: -a --at
112
+ #
113
+ def parsed_options?
114
+ # Specify options
115
+ opt = OptionParser.new
116
+ opt.on('-v', '--version') { output_version ; exit 0 }
117
+ opt.on('-h', '--help') { output_help }
118
+ opt.on('-V', '--verbose') { @options.verbose = true }
119
+ opt.on('-a n', '--at n') {|n| @options.at = Time.parse(n.to_s) }
120
+ opt.on('-t n', '--timeout n') {|n| @options.timeout = n }
121
+ opt.on('-w', '--web') { @options.web = true; ARGV.shift }
122
+ opt.on('-g', '--geocode') { @options.geocode = true }
123
+ opt.on('--skip') { @options.skip_graticule = true }
124
+ opt.on('-m', '--metric') { @options.metric = true }
125
+ opt.on('-i', '--imperial') { @options.metric = false }
126
+ opt.on('--no-wunderground') { @options.sources = @options.sources.delete_if{|s| s == :wunderground} }
127
+ opt.on('--yahoo') { @options.sources << :yahoo }
128
+ opt.on('--google') { @options.sources << :google }
129
+ opt.on('-p n', '--pop n') {|n| @options.pop = n.to_i || 50 }
130
+ opt.on('-s n', '--wind n') {|n| @options.metric ? @options.windy_m = n.to_f || 10 : @options.windy_i = n.to_f || 7 }
131
+
132
+ # pass these onto vegas
133
+ opt.on('-k', '--kill') { @options.web = true }
134
+ opt.on('-S', '--status') { @options.web = true }
135
+
136
+ opt.parse!(@arguments) rescue return false
137
+
138
+ process_options
139
+ true
140
+ end
141
+
142
+ # Performs post-parse processing on options
143
+ def process_options
144
+ Barometer.force_geocode = @options.geocode
145
+ Barometer.selection = { 1 => @options.sources.uniq }
146
+ Barometer.skip_graticule = @options.skip_graticule
147
+ Barometer.timeout = @options.timeout
148
+ end
149
+
150
+ def output_options
151
+ puts "Options:\n"
152
+
153
+ @options.marshal_dump.each do |name, val|
154
+ puts " #{name} = #{val}"
155
+ end
156
+ puts
157
+ end
158
+
159
+ # True if required arguments were provided
160
+ def arguments_valid?
161
+ true if (@arguments.length >= 1 || @options.web)
162
+ end
163
+
164
+ # Setup the arguments
165
+ def process_arguments
166
+ #puts @arguments.inspect
167
+ end
168
+
169
+ def output_help
170
+ output_version
171
+ RDoc::usage() #exits app
172
+ end
173
+
174
+ def output_usage
175
+ RDoc::usage('usage') # gets usage from comments above
176
+ end
177
+
178
+ def output_version
179
+ puts "#{File.basename(__FILE__)} version #{VERSION}"
180
+ end
181
+
182
+ def process_command
183
+ if @options.web
184
+ run_web_mode(@arguments.join(" "))
185
+ else
186
+ barometer = Barometer.new(@arguments.join(" "))
187
+ begin
188
+ barometer.measure(@options.metric) if barometer
189
+ pretty_output(barometer) if barometer.weather
190
+ rescue Barometer::OutOfSources
191
+ puts
192
+ puts " SORRY: your query did not provide any results"
193
+ puts
194
+ end
195
+ end
196
+ end
35
197
  end
36
-
37
- barometer = Barometer.new(ARGV[0])
38
- weather = barometer.measure(true)
198
+
199
+ #
200
+ # HELPERS
201
+ #
202
+
203
+ @level = 1
39
204
 
40
205
  def y(value)
41
206
  value ? "yes" : "no"
42
207
  end
43
208
 
44
- if weather
45
- puts "###################################################"
46
- puts "# #{weather.default.location.name}"
47
- puts "#"
48
- puts "# (lat: #{weather.default.location.latitude}, long: #{weather.default.location.longitude})"
49
- puts "###################################################"
50
- puts " -- CURRENT --"
51
- puts " temperature: #{weather.now.temperature}"
209
+ def div(char="#")
210
+ puts char*50
211
+ end
212
+
213
+ def title(title, level=1)
214
+ @level = level
215
+ puts "#{" " * @level}-- #{title} --"
216
+ end
217
+
218
+ def value(title, value)
219
+ puts "#{" " * @level}#{title}: #{value}" unless value.nil?
220
+ end
221
+
222
+ def blank
52
223
  puts
53
- puts " -- QUESTIONS --"
54
- puts " day? : #{y(weather.day?)}"
55
- puts " sunny?: #{y(weather.sunny?)}"
56
- puts " windy?: #{y(weather.windy?)}"
57
- puts " wet? : #{y(weather.wet?)}"
224
+ end
225
+
226
+ def section(title, level=1, show_blank=true)
227
+ @level = level
228
+ title(title, level); yield; blank if show_blank
229
+ end
230
+
231
+ def pretty_hash(hash)
232
+ return unless hash.is_a?(Hash)
233
+ hash.each { |k,v| value(k,v) }
234
+ end
235
+
236
+ def pretty_summary(s)
237
+ return unless s
238
+ section("AVERAGES") do
239
+ pretty_hash({
240
+ "humidity" => s.humidity.to_i, "temperature" => s.temperature })
241
+ end
242
+ section("SUMMARY#{ " (@ #{@options.at})" if @options.at }") do
243
+ pretty_hash({
244
+ "day?" => s.day?(@options.at), "sunny?" => s.sunny?(@options.at),
245
+ "windy?" => s.windy?(@options.metric ? @options.windy_m : @options.windy_i, @options.at),
246
+ "wet?" => s.wet?(@options.pop,@options.at) })
247
+ end
248
+ end
249
+
250
+ def pretty_query(q)
251
+ return unless q
252
+ section("QUERY", 2) do
253
+ pretty_hash({"Format" => q.format})
254
+ pretty_hash({
255
+ "Address" => q.geo.address,
256
+ "Locality" => q.geo.locality, "Region" => q.geo.region,
257
+ "Country" => q.geo.country, "Country Code" => q.geo.country_code,
258
+ "Latitude" => q.geo.latitude, "Longitude" => q.geo.longitude }) if q.geo
259
+ end
260
+ end
261
+
262
+ def pretty_location(l)
263
+ return unless l
264
+ section("LOCATION", 2) do
265
+ pretty_hash({
266
+ "ID" => l.id, "Name" => l.name,
267
+ "City" => l.city, "State Name" => l.state_name,
268
+ "State Code" => l.state_code, "Country" => l.country,
269
+ "Country Code" => l.country_code, "Zip Code" => l.zip_code,
270
+ "Latitude" => l.latitude, "Longitude" => l.longitude })
271
+ end
272
+ end
273
+
274
+ def pretty_station(s)
275
+ return unless s
276
+ section("STATION", 2) do
277
+ pretty_hash({
278
+ "ID" => s.id, "Name" => s.name,
279
+ "City" => s.city, "State Name" => s.state_name,
280
+ "State Code" => s.state_code, "Country" => s.country,
281
+ "Country Code" => s.country_code, "Zip Code" => s.zip_code,
282
+ "Latitude" => s.latitude, "Longitude" => s.longitude })
283
+ end
284
+ end
285
+
286
+ def pretty_timezone(t)
287
+ return unless t
288
+ section("TIMEZONE", 2) do
289
+ pretty_hash({ "Long" => t.timezone, "Code" => t.code,"DST?" => t.dst? })
290
+ end
291
+ end
292
+
293
+ def pretty_current(c)
294
+ return unless c
295
+ section("CURRENT", 2) do
296
+ pretty_hash({
297
+ "Time" => c.time, "Local Time" => c.local_time,
298
+ "Humidity" => c.humidity, "Icon" => c.icon,
299
+ "Condition" => c.condition, "Temperature" => c.temperature,
300
+ "Dew Point" => c.dew_point, "Heat Index" => c.heat_index,
301
+ "Pressure" => c.pressure, "Visibility" => c.visibility })
302
+ pretty_hash({ "Wind Chill" => c.wind_chill, "Wind" => c.wind,
303
+ "Wind Direction" => c.wind.direction, "Degrees" => c.wind.degrees }) if c.wind
304
+ pretty_hash({ "Sun Rise" => c.sun.rise, "Sun Set" => c.sun.set }) if c.sun
305
+ end
306
+ end
307
+
308
+ def pretty_forecast(f)
309
+ return unless f
310
+ section("FOR: #{f.date}", 3) do
311
+ pretty_hash({
312
+ "Date" => f.date, "Icon" => f.icon,
313
+ "Condition" => f.condition, "High" => f.high,
314
+ "Low" => f.low, "POP" => f.pop })
315
+ pretty_hash({ "Sun Rise" => f.sun.rise, "Sun Set" => f.sun.set }) if f.sun
316
+ end
317
+ end
318
+
319
+ def pretty_forecasts(forecasts)
320
+ return unless forecasts
321
+ section("FORECAST", 3, false) do
322
+ blank
323
+ forecasts.each do |forecast|
324
+ pretty_forecast(forecast)
325
+ end
326
+ end
327
+ end
328
+
329
+ def pretty_measurement(m)
330
+ return unless m
331
+ section(m.source.to_s, 1) do
332
+ pretty_hash({
333
+ "Source" => m.source, "Time" => m.time,
334
+ "Metric" => m.metric?, "Success" => m.success? })
335
+ end
336
+ pretty_location(m.location)
337
+ pretty_station(m.station)
338
+ pretty_timezone(m.timezone)
339
+ pretty_current(m.current)
340
+ pretty_forecasts(m.forecast)
341
+ end
342
+
343
+ def pretty_measurements(w)
344
+ return unless w
345
+ section("MEASUREMENTS", 1) do
346
+ blank
347
+ w.measurements.each do |m|
348
+ pretty_measurement(m)
349
+ end
350
+ end
351
+ end
352
+
353
+ def pretty_info
354
+ title("INFO", 1)
355
+ value("GitHub", "http://github.com/attack/barometer")
356
+ value("Barometer Version", VERSION)
357
+ end
358
+
359
+ def pretty_output(barometer)
360
+ weather = barometer.weather
361
+ if weather
362
+ div
363
+ puts "#"
364
+ puts "# #{weather.default.location.name || barometer.query.q}"
365
+ puts "#"
366
+ div
367
+ blank
368
+ pretty_summary(weather)
369
+ pretty_query(barometer.query)
370
+ pretty_measurements(weather)
371
+ pretty_info
372
+ div("-")
373
+ end
374
+ end
375
+
376
+ def run_web_mode(query=nil)
377
+
378
+ puts
379
+ puts "This is currently disabled"
380
+ puts
381
+
382
+ #require File.expand_path(File.dirname(__FILE__) + '/../lib/webometer/webometer.rb')
383
+ #require 'vegas'
384
+
385
+ #Vegas::Runner.new(Webometer, 'webometer') do |opts, app|
386
+ # opts is an option parser object
387
+ # app is your app class
388
+ #end
389
+ end
390
+
391
+ def geocode_google_key_message
58
392
  puts
393
+ puts "Please update the key_file '#{KEY_FILE}' with your google api key"
394
+ puts "Get it here: http://code.google.com/apis/maps/signup.html"
395
+ puts "The, add this line to the file:"
396
+ puts "geocode_google: YOUR_KEY_KERE"
59
397
  puts
60
- puts " -- INFO --"
61
- puts " http://github.com/attack/barometer"
62
- puts "---------------------------------------------------"
63
- end
398
+ end
399
+
400
+ # set API keys
401
+ if File.exists?(KEY_FILE)
402
+ keys = YAML.load_file(KEY_FILE)
403
+ if keys["geocode_google"]
404
+ Barometer.google_geocode_key = keys["geocode_google"]
405
+ else
406
+ geocode_google_key_message
407
+ exit
408
+ end
409
+ else
410
+ File.open(KEY_FILE, 'w') {|f| f << "geocode_google: YOUR_KEY_KERE" }
411
+ geocode_google_key_message
412
+ exit
413
+ end
414
+
415
+ # Create and run the application
416
+ app = App.new(ARGV, STDIN)
417
+ app.run
@@ -8,7 +8,7 @@ module Barometer
8
8
  class Geo
9
9
 
10
10
  attr_accessor :latitude, :longitude
11
- attr_accessor :locality, :region, :country, :country_code
11
+ attr_accessor :locality, :region, :country, :country_code, :address
12
12
 
13
13
  #
14
14
  # this will take a Location object (either generated by Graticule
@@ -60,6 +60,7 @@ module Barometer
60
60
  @region = location.region
61
61
  @country = location.country
62
62
  @country_code = location.country_code
63
+ @address = location.address_line
63
64
  end
64
65
 
65
66
  def build_from_httparty(location=nil)
@@ -87,6 +88,7 @@ module Barometer
87
88
  end
88
89
  @country = placemark["AddressDetails"]["Country"]["CountryName"]
89
90
  @country_code = placemark["AddressDetails"]["Country"]["CountryNameCode"]
91
+ @address = placemark["AddressDetails"]["Country"]["AddressLine"]
90
92
  end
91
93
  end
92
94
 
@@ -1,10 +1,10 @@
1
1
  module Graticule
2
2
  class Location
3
3
 
4
- attr_accessor :country_code
4
+ attr_accessor :country_code, :address_line
5
5
 
6
6
  def attributes
7
- [:latitude, :longitude, :street, :locality, :region, :postal_code, :country, :precision, :cuntry_code].inject({}) do |result,attr|
7
+ [:latitude, :longitude, :street, :locality, :region, :postal_code, :country, :precision, :cuntry_code, :address_line].inject({}) do |result,attr|
8
8
  result[attr] = self.send(attr) unless self.send(attr).blank?
9
9
  result
10
10
  end
@@ -39,6 +39,7 @@ module Graticule
39
39
  l.postal_code = value(address.elements['.//PostalCodeNumber/text()'])
40
40
  l.country = value(address.elements['.//CountryName/text()'])
41
41
  l.country_code = value(address.elements['.//CountryNameCode/text()'])
42
+ l.address_line = value(address.elements['.//AddressLine/text()'])
42
43
  l.precision = PRECISION[address.attribute('Accuracy').value.to_i] || :unknown
43
44
  end
44
45
  end
@@ -31,20 +31,23 @@ module Barometer
31
31
  self.analyze!
32
32
  end
33
33
 
34
- # analyze the saved query to determine the format. for the format of
35
- # :zipcode and :postalcode the country_code can also be set
34
+ # analyze the saved query to determine the format.
36
35
  def analyze!
37
36
  return unless @q
38
37
  if Barometer::Query.is_us_zipcode?(@q)
39
38
  @format = :zipcode
39
+ @country_code = Barometer::Query.format_to_country_code(@format)
40
40
  elsif Barometer::Query.is_canadian_postcode?(@q)
41
41
  @format = :postalcode
42
+ @country_code = Barometer::Query.format_to_country_code(@format)
42
43
  elsif Barometer::Query.is_coordinates?(@q)
43
44
  @format = :coordinates
45
+ elsif Barometer::Query.is_icao?(@q)
46
+ @format = :icao
47
+ # @country_code = Barometer::Query.icao_to_country_code(@q)
44
48
  else
45
49
  @format = :geocode
46
50
  end
47
- @country_code = Barometer::Query.format_to_country_code(@format)
48
51
  end
49
52
 
50
53
  # take a list of acceptable (and ordered by preference) formats and convert
@@ -93,6 +96,7 @@ module Barometer
93
96
  def postalcode?; @format == :postalcode; end
94
97
  def coordinates?; @format == :coordinates; end
95
98
  def geocode?; @format == :geocode; end
99
+ def icao?; @format == :icao; end
96
100
 
97
101
  def self.is_us_zipcode?(query)
98
102
  us_zipcode_regex = /(^[0-9]{5}$)|(^[0-9]{5}-[0-9]{4}$)/
@@ -112,12 +116,25 @@ module Barometer
112
116
  return !(query =~ coordinates_regex).nil?
113
117
  end
114
118
 
119
+ def self.is_icao?(query)
120
+ # allow any 3 or 4 letter word ... unfortunately this means some locations
121
+ # (ie Utah, Goa, Kiev, etc) will be detected as ICAO. This won't matter for
122
+ # returning weather results ... it will just effect what happens to the query.
123
+ # For example, wunderground will accept :icao above :coordinates and :geocode,
124
+ # which means that a city like Kiev would normally get converted to :coordinates
125
+ # but in this case it will be detected as :icao so it will be passed as is.
126
+ # Currently, only wunderground accepts ICAO, and they process ICAO the same as a
127
+ # city name, so it doesn't matter.
128
+ icao_regex = /^[A-Za-z]{3,4}$/
129
+ return !(query =~ icao_regex).nil?
130
+ end
131
+
115
132
  #
116
133
  # CONVERTERS
117
134
  #
118
135
 
119
136
  # this will take all query formats and convert them to coordinates
120
- # accepts- :zipcode, :postalcode, :geocode
137
+ # accepts- :zipcode, :postalcode, :geocode, :icao
121
138
  # returns- :coordinates
122
139
  # if the conversion fails, return nil
123
140
  def self.to_coordinates(query, format)
@@ -129,7 +146,7 @@ module Barometer
129
146
  end
130
147
 
131
148
  # this will take all query formats and convert them to coorinates
132
- # accepts- :zipcode, :postalcode, :coordinates
149
+ # accepts- :zipcode, :postalcode, :coordinates, :icao
133
150
  # returns- :geocode
134
151
  def self.to_geocode(query, format)
135
152
  perform_geocode = false
@@ -143,8 +160,17 @@ module Barometer
143
160
  if perform_geocode
144
161
  geo = self.geocode(query, country_code)
145
162
  country_code ||= geo.country_code if geo
146
- return nil unless geo && geo.locality && geo.region && geo.country
147
- return ["#{geo.locality}, #{geo.region}, #{geo.country}", country_code, geo]
163
+ # different formats have different acceptance criteria
164
+ q = nil
165
+ case format
166
+ when :icao
167
+ return nil unless geo && geo.address && geo.country
168
+ q = "#{geo.address}, #{geo.country}"
169
+ else
170
+ return nil unless geo && geo.locality && geo.region && geo.country
171
+ q = "#{geo.locality}, #{geo.region}, #{geo.country}"
172
+ end
173
+ return [q, country_code, geo]
148
174
  else
149
175
  # without geocoding, the best we can do is just make use the given query as
150
176
  # the query for the "geocode" format
@@ -207,7 +233,6 @@ module Barometer
207
233
  },
208
234
  :format => :xml
209
235
  )['kml']['Response']
210
- #puts location.inspect
211
236
  geo = Barometer::Geo.new(location)
212
237
  end
213
238
 
@@ -223,6 +248,30 @@ module Barometer
223
248
  end
224
249
  country_code
225
250
  end
251
+
252
+ # todo, the fist letter in a 4-letter icao can designate country:
253
+ # c=canada
254
+ # k=usa
255
+ # etc...
256
+ # def self.icao_to_country_code(icao_code)
257
+ # return unless icao_code.is_a?(String)
258
+ # country_code = nil
259
+ # if icao_code.size == 4
260
+ # case icao_code.first_letter
261
+ # when "C"
262
+ # country_code = "CA"
263
+ # when "K"
264
+ # country_code = "US"
265
+ # end
266
+ # if coutry_code.nil?
267
+ # case icao_code.first_two_letters
268
+ # when "ET"
269
+ # country_code = "GERMANY"
270
+ # end
271
+ # end
272
+ # end
273
+ # country_code
274
+ # end
226
275
 
227
276
  end
228
277
  end
@@ -39,7 +39,7 @@ module Barometer
39
39
  class Wunderground < Service
40
40
 
41
41
  def self.accepted_formats
42
- [:zipcode, :postalcode, :coordinates, :geocode]
42
+ [:zipcode, :postalcode, :icao, :coordinates, :geocode]
43
43
  end
44
44
 
45
45
  def self.source_name
@@ -48,8 +48,9 @@ module Barometer
48
48
 
49
49
  # these are the icon codes that indicate "wet", used by wet? function
50
50
  def self.wet_icon_codes
51
- %w(flurries rain sleet snow tstorms nt_flurries nt_rain nt_sleet nt_snow nt_tstorms)
51
+ %w(flurries rain sleet snow tstorms nt_flurries nt_rain nt_sleet nt_snow nt_tstorms chancerain)
52
52
  end
53
+ # these are the icon codes that indicate "sun", used by sunny? function
53
54
  def self.sunny_icon_codes
54
55
  %w(clear mostlysunny partlysunny sunny partlycloudy)
55
56
  end
@@ -34,17 +34,9 @@ module Barometer
34
34
  # Quick access methods
35
35
  #
36
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
37
+ def current; (default = self.default) ? default.current : nil; end
38
+ def forecast; (default = self.default) ? default.forecast : nil; end
39
+ def now; self.current; end
48
40
 
49
41
  def today
50
42
  default = self.default
@@ -72,37 +64,48 @@ module Barometer
72
64
  # averages
73
65
  #
74
66
 
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
67
+ # TODO: not tested (except via averages)
68
+ def metric?
69
+ self.default ? self.default.metric? : true
70
+ end
71
+
72
+ # TODO: not tested (except via averages)
73
+ # this assumes calculating for current, and that "to_f" for a value
74
+ # will return the value needed
75
+ # value_name = the name of the value we are averaging
76
+ def current_average(value_name)
77
+ values = []
78
+ @measurements.each do |measurement|
79
+ values << measurement.current.send(value_name).to_f if measurement.success?
80
+ end
81
+ return nil unless values && values.size > 0
82
+ values.inject(0.0) { |sum,v| sum += v } / values.size
83
+ end
84
+
85
+ # TODO: not tested (except via averages)
86
+ def average(value_name, do_average=true, class_name=nil)
87
+ if class_name
88
+ if do_average
89
+ avg = Barometer.const_get(class_name).new(self.metric?)
90
+ avg << self.current_average(value_name)
91
+ else
92
+ avg = self.now.send(value_name)
93
+ end
94
+ else
95
+ avg = (do_average ? self.current_average(value_name) : self.now.send(value_name))
96
+ end
97
+ avg
98
+ end
99
+
100
+ # average of all values
101
+ def humidity(do_average=true); average("humidity",do_average); end
102
+ def temperature(do_average=true); average("temperature",do_average,"Temperature"); end
103
+ def wind(do_average=true); average("wind",do_average,"Speed"); end
104
+ def pressure(do_average=true); average("pressure",do_average,"Pressure"); end
105
+ def dew_point(do_average=true); average("dew_point",do_average,"Temperature"); end
106
+ def heat_index(do_average=true); average("heat_index",do_average,"Temperature"); end
107
+ def wind_chill(do_average=true); average("wind_chill",do_average,"Temperature"); end
108
+ def visibility(do_average=true); average("visibility",do_average,"Distance"); end
106
109
 
107
110
  #
108
111
  # quick access methods
@@ -4,7 +4,7 @@ describe "Barometer" do
4
4
 
5
5
  before(:each) do
6
6
  @preference_hash = { 1 => [:wunderground] }
7
- @key = "ABQIAAAAq8TH4offRcGrok8JVY_MyxRi_j0U6kJrkFvY4-OX2XYmEAa76BSFwMlSow1YgX8BOPUeve_shMG7xw"
7
+ @key = KEY
8
8
  end
9
9
 
10
10
  describe "and class methods" do
@@ -35,6 +35,10 @@ describe "Geo" do
35
35
  @geo.country.should be_nil
36
36
  end
37
37
 
38
+ it "responds to address" do
39
+ @geo.address.should be_nil
40
+ end
41
+
38
42
  it "responds to coordinates" do
39
43
  @geo.longitude = "99.99"
40
44
  @geo.latitude = "88.88"
@@ -0,0 +1 @@
1
+ <?xml version="1.0" encoding="UTF-8" ?> <kml xmlns="http://earth.google.com/kml/2.0"><Response> <name>KSFO</name> <Status> <code>200</code> <request>geocode</request> </Status> <Placemark id="p1"> <address>San Francisco Airport, United States</address> <AddressDetails Accuracy="9" xmlns="urn:oasis:names:tc:ciq:xsdschema:xAL:2.0"><Country><CountryNameCode>US</CountryNameCode><CountryName>USA</CountryName><AddressLine>San Francisco Airport</AddressLine></Country></AddressDetails> <ExtendedData> <LatLonBox north="37.6401700" south="37.6041790" east="-122.3548940" west="-122.4015610" /> </ExtendedData> <Point><coordinates>-122.3899790,37.6152230,0</coordinates></Point> </Placemark> </Response></kml>
data/spec/query_spec.rb CHANGED
@@ -7,6 +7,7 @@ describe "Query" do
7
7
  @postal_code = "T5B 4M9"
8
8
  @coordinates = "40.756054,-73.986951"
9
9
  @geocode = "New York, NY"
10
+ @icao = "KSFO"
10
11
 
11
12
  # actual conversions
12
13
  @zipcode_to_coordinates = "34.1030032,-118.4104684"
@@ -26,18 +27,28 @@ describe "Query" do
26
27
  Barometer::Query.is_us_zipcode?(@zipcode).should be_true
27
28
  Barometer::Query.is_us_zipcode?(@postal_code).should be_false
28
29
  Barometer::Query.is_us_zipcode?(@coordinates).should be_false
30
+ Barometer::Query.is_coordinates?(@icao).should be_false
29
31
  end
30
32
 
31
33
  it "detects a postalcode" do
32
34
  Barometer::Query.is_canadian_postcode?(@postal_code).should be_true
33
35
  Barometer::Query.is_canadian_postcode?(@zipcode).should be_false
34
36
  Barometer::Query.is_canadian_postcode?(@coordinates).should be_false
37
+ Barometer::Query.is_coordinates?(@icao).should be_false
35
38
  end
36
39
 
37
40
  it "detects a coordinates" do
38
41
  Barometer::Query.is_coordinates?(@coordinates).should be_true
39
42
  Barometer::Query.is_coordinates?(@zipcode).should be_false
40
43
  Barometer::Query.is_coordinates?(@postal_code).should be_false
44
+ Barometer::Query.is_coordinates?(@icao).should be_false
45
+ end
46
+
47
+ it "detects an ICAO" do
48
+ Barometer::Query.is_icao?(@coordinates).should be_false
49
+ Barometer::Query.is_icao?(@zipcode).should be_false
50
+ Barometer::Query.is_icao?(@postal_code).should be_false
51
+ Barometer::Query.is_icao?(@icao).should be_true
41
52
  end
42
53
 
43
54
  end
@@ -58,6 +69,7 @@ describe "Query" do
58
69
  @query.country_code.should == "US"
59
70
  @query.zipcode?.should be_true
60
71
  @query.postalcode?.should be_false
72
+ @query.icao?.should be_false
61
73
  @query.coordinates?.should be_false
62
74
  @query.geocode?.should be_false
63
75
  end
@@ -71,6 +83,21 @@ describe "Query" do
71
83
  @query.country_code.should == "CA"
72
84
  @query.zipcode?.should be_false
73
85
  @query.postalcode?.should be_true
86
+ @query.icao?.should be_false
87
+ @query.coordinates?.should be_false
88
+ @query.geocode?.should be_false
89
+ end
90
+
91
+ it "recognizes icao" do
92
+ @query.q = @icao
93
+ @query.format.should be_nil
94
+ @query.analyze!
95
+ @query.format.to_sym.should == :icao
96
+
97
+ @query.country_code.should be_nil
98
+ @query.zipcode?.should be_false
99
+ @query.postalcode?.should be_false
100
+ @query.icao?.should be_true
74
101
  @query.coordinates?.should be_false
75
102
  @query.geocode?.should be_false
76
103
  end
@@ -84,6 +111,7 @@ describe "Query" do
84
111
  @query.country_code.should be_nil
85
112
  @query.zipcode?.should be_false
86
113
  @query.postalcode?.should be_false
114
+ @query.icao?.should be_false
87
115
  @query.coordinates?.should be_true
88
116
  @query.geocode?.should be_false
89
117
  end
@@ -97,6 +125,7 @@ describe "Query" do
97
125
  @query.country_code.should be_nil
98
126
  @query.zipcode?.should be_false
99
127
  @query.postalcode?.should be_false
128
+ @query.icao?.should be_false
100
129
  @query.coordinates?.should be_false
101
130
  @query.geocode?.should be_true
102
131
  end
@@ -168,7 +197,7 @@ describe "Query" do
168
197
  describe "when converting queries" do
169
198
 
170
199
  before(:each) do
171
- @key = "ABQIAAAAq8TH4offRcGrok8JVY_MyxRi_j0U6kJrkFvY4-OX2XYmEAa76BSFwMlSow1YgX8BOPUeve_shMG7xw"
200
+ @key = KEY
172
201
  url_start = "http://maps.google.com/maps/geo?"
173
202
  #
174
203
  # for Graticule and/or HTTParty geocoding
@@ -221,6 +250,13 @@ describe "Query" do
221
250
  'geocode_40_73.xml')
222
251
  )
223
252
  )
253
+ FakeWeb.register_uri(:get,
254
+ "#{url_start}output=xml&q=KSFO&gl=&key=#{@key}",
255
+ :string => File.read(File.join(File.dirname(__FILE__),
256
+ 'fixtures',
257
+ 'geocode_ksfo.xml')
258
+ )
259
+ )
224
260
  end
225
261
 
226
262
  describe "to coordinates," do
@@ -253,6 +289,10 @@ describe "Query" do
253
289
  Barometer::Query.to_coordinates(@postal_code, :postalcode).first.should == "53.570447,-113.456083"
254
290
  end
255
291
 
292
+ it "converts from icao" do
293
+ Barometer::Query.to_coordinates(@icao, :icao).first.should == "37.615223,-122.389979"
294
+ end
295
+
256
296
  end
257
297
 
258
298
  describe "to geocode" do
@@ -275,6 +315,10 @@ describe "Query" do
275
315
  Barometer::Query.to_geocode(@postal_code, :postalcode).first.should == @postal_code
276
316
  end
277
317
 
318
+ it "converts from icao" do
319
+ Barometer::Query.to_geocode(@icao, :icao).first.should == "San Francisco Airport, USA"
320
+ end
321
+
278
322
  end
279
323
 
280
324
  describe "when Graticule disabled," do
@@ -357,7 +401,7 @@ describe "Query" do
357
401
 
358
402
  before(:each) do
359
403
  @query = Barometer::Query.new(@zipcode)
360
- Barometer::Query.google_geocode_key = "ABQIAAAAq8TH4offRcGrok8JVY_MyxRi_j0U6kJrkFvY4-OX2XYmEAa76BSFwMlSow1YgX8BOPUeve_shMG7xw"
404
+ Barometer::Query.google_geocode_key = KEY
361
405
  end
362
406
 
363
407
  it "converts to coordinates" do
@@ -384,7 +428,7 @@ describe "Query" do
384
428
 
385
429
  before(:each) do
386
430
  @query = Barometer::Query.new(@postal_code)
387
- Barometer::Query.google_geocode_key = "ABQIAAAAq8TH4offRcGrok8JVY_MyxRi_j0U6kJrkFvY4-OX2XYmEAa76BSFwMlSow1YgX8BOPUeve_shMG7xw"
431
+ Barometer::Query.google_geocode_key = KEY
388
432
  end
389
433
 
390
434
  it "converts to coordinates" do
@@ -411,7 +455,7 @@ describe "Query" do
411
455
 
412
456
  before(:each) do
413
457
  @query = Barometer::Query.new(@geocode)
414
- Barometer::Query.google_geocode_key = "ABQIAAAAq8TH4offRcGrok8JVY_MyxRi_j0U6kJrkFvY4-OX2XYmEAa76BSFwMlSow1YgX8BOPUeve_shMG7xw"
458
+ Barometer::Query.google_geocode_key = KEY
415
459
  end
416
460
 
417
461
  it "converts to coordinates" do
@@ -438,7 +482,7 @@ describe "Query" do
438
482
 
439
483
  before(:each) do
440
484
  @query = Barometer::Query.new(@coordinates)
441
- Barometer::Query.google_geocode_key = "ABQIAAAAq8TH4offRcGrok8JVY_MyxRi_j0U6kJrkFvY4-OX2XYmEAa76BSFwMlSow1YgX8BOPUeve_shMG7xw"
485
+ Barometer::Query.google_geocode_key = KEY
442
486
  end
443
487
 
444
488
  it "converts to geocode" do
@@ -3,7 +3,7 @@ require 'spec_helper'
3
3
  describe "Wunderground" do
4
4
 
5
5
  before(:each) do
6
- @accepted_formats = [:zipcode, :postalcode, :coordinates, :geocode]
6
+ @accepted_formats = [:zipcode, :postalcode, :icao, :coordinates, :geocode]
7
7
  @base_uri = "http://api.wunderground.com/auto/wui/geo"
8
8
  end
9
9
 
data/spec/spec_helper.rb CHANGED
@@ -2,6 +2,7 @@ require 'rubygems'
2
2
  require 'spec'
3
3
  require 'fakeweb'
4
4
  require 'cgi'
5
+ require 'yaml'
5
6
 
6
7
  $LOAD_PATH.unshift(File.dirname(__FILE__))
7
8
  $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
@@ -9,6 +10,30 @@ require 'barometer'
9
10
 
10
11
  FakeWeb.allow_net_connect = false
11
12
 
13
+ KEY_FILE = File.expand_path(File.join('~', '.barometer'))
14
+
15
+ def geocode_google_key_message
16
+ puts
17
+ puts "Please update the key_file '#{KEY_FILE}' with your google api key"
18
+ puts "example:"
19
+ puts "geocode_google: YOUR_KEY_KERE"
20
+ puts
21
+ end
22
+
23
+ if File.exists?(KEY_FILE)
24
+ keys = YAML.load_file(KEY_FILE)
25
+ if keys["geocode_google"]
26
+ KEY = keys["geocode_google"]
27
+ else
28
+ geocode_google_key_message
29
+ exit
30
+ end
31
+ else
32
+ File.open(KEY_FILE, 'w') {|f| f << "geocode_google: YOUR_KEY_KERE" }
33
+ geocode_google_key_message
34
+ exit
35
+ end
36
+
12
37
  Spec::Runner.configure do |config|
13
38
 
14
39
  end
data/spec/weather_spec.rb CHANGED
@@ -78,6 +78,174 @@ describe "Weather" do
78
78
 
79
79
  end
80
80
 
81
+ describe "when calculating averages" do
82
+
83
+ before(:each) do
84
+ @weather = Barometer::Weather.new
85
+ @wunderground = Barometer::Measurement.new(:wunderground)
86
+ @wunderground.current = Barometer::CurrentMeasurement.new
87
+ @wunderground.success = true
88
+ @yahoo = Barometer::Measurement.new(:yahoo)
89
+ @yahoo.current = Barometer::CurrentMeasurement.new
90
+ @yahoo.success = true
91
+ @google = Barometer::Measurement.new(:google)
92
+ @weather.measurements << @wunderground
93
+ @weather.measurements << @yahoo
94
+ @weather.measurements << @google
95
+ end
96
+
97
+ describe "for temperature" do
98
+
99
+ before(:each) do
100
+ @weather.source(:wunderground).current.temperature = Barometer::Temperature.new
101
+ @weather.source(:wunderground).current.temperature.c = 10
102
+ @weather.source(:yahoo).current.temperature = Barometer::Temperature.new
103
+ @weather.source(:yahoo).current.temperature.c = 6
104
+ end
105
+
106
+ it "returns averages" do
107
+ @weather.temperature.c.should == 8
108
+ end
109
+
110
+ it "returns default when disabled" do
111
+ @weather.temperature(false).c.should == 10
112
+ end
113
+
114
+ end
115
+
116
+ describe "for wind" do
117
+
118
+ before(:each) do
119
+ @weather.source(:wunderground).current.wind = Barometer::Speed.new
120
+ @weather.source(:wunderground).current.wind.kph = 10
121
+ @weather.source(:yahoo).current.wind = Barometer::Speed.new
122
+ @weather.source(:yahoo).current.wind.kph = 6
123
+ end
124
+
125
+ it "returns averages" do
126
+ @weather.wind.kph.should == 8
127
+ end
128
+
129
+ it "returns default when disabled" do
130
+ @weather.wind(false).kph.should == 10
131
+ end
132
+
133
+ end
134
+
135
+ describe "for humidity" do
136
+
137
+ before(:each) do
138
+ @weather.source(:wunderground).current.humidity = 10
139
+ @weather.source(:yahoo).current.humidity = 6
140
+ end
141
+
142
+ it "returns averages" do
143
+ @weather.humidity.should == 8
144
+ end
145
+
146
+ it "returns default when disabled" do
147
+ @weather.humidity(false).should == 10
148
+ end
149
+
150
+ end
151
+
152
+ describe "for pressure" do
153
+
154
+ before(:each) do
155
+ @weather.source(:wunderground).current.pressure = Barometer::Pressure.new
156
+ @weather.source(:wunderground).current.pressure.mb = 10
157
+ @weather.source(:yahoo).current.pressure = Barometer::Pressure.new
158
+ @weather.source(:yahoo).current.pressure.mb = 6
159
+ end
160
+
161
+ it "returns averages" do
162
+ @weather.pressure.mb.should == 8
163
+ end
164
+
165
+ it "returns default when disabled" do
166
+ @weather.pressure(false).mb.should == 10
167
+ end
168
+
169
+ end
170
+
171
+ describe "for dew_point" do
172
+
173
+ before(:each) do
174
+ @weather.source(:wunderground).current.dew_point = Barometer::Temperature.new
175
+ @weather.source(:wunderground).current.dew_point.c = 10
176
+ @weather.source(:yahoo).current.dew_point = Barometer::Temperature.new
177
+ @weather.source(:yahoo).current.dew_point.c = 6
178
+ end
179
+
180
+ it "returns averages" do
181
+ @weather.dew_point.c.should == 8
182
+ end
183
+
184
+ it "returns default when disabled" do
185
+ @weather.dew_point(false).c.should == 10
186
+ end
187
+
188
+ end
189
+
190
+ describe "for heat_index" do
191
+
192
+ before(:each) do
193
+ @weather.source(:wunderground).current.heat_index = Barometer::Temperature.new
194
+ @weather.source(:wunderground).current.heat_index.c = 10
195
+ @weather.source(:yahoo).current.heat_index = Barometer::Temperature.new
196
+ @weather.source(:yahoo).current.heat_index.c = 6
197
+ end
198
+
199
+ it "returns averages" do
200
+ @weather.heat_index.c.should == 8
201
+ end
202
+
203
+ it "returns default when disabled" do
204
+ @weather.heat_index(false).c.should == 10
205
+ end
206
+
207
+ end
208
+
209
+ describe "for wind_chill" do
210
+
211
+ before(:each) do
212
+ @weather.source(:wunderground).current.wind_chill = Barometer::Temperature.new
213
+ @weather.source(:wunderground).current.wind_chill.c = 10
214
+ @weather.source(:yahoo).current.wind_chill = Barometer::Temperature.new
215
+ @weather.source(:yahoo).current.wind_chill.c = 6
216
+ end
217
+
218
+ it "returns averages" do
219
+ @weather.wind_chill.c.should == 8
220
+ end
221
+
222
+ it "returns default when disabled" do
223
+ @weather.wind_chill(false).c.should == 10
224
+ end
225
+
226
+ end
227
+
228
+ describe "for visibility" do
229
+
230
+ before(:each) do
231
+ @weather.source(:wunderground).current.visibility = Barometer::Distance.new
232
+ @weather.source(:wunderground).current.visibility.km = 10
233
+ @weather.source(:yahoo).current.visibility = Barometer::Distance.new
234
+ @weather.source(:yahoo).current.visibility.km = 6
235
+ end
236
+
237
+ it "returns averages" do
238
+ @weather.visibility.km.should == 8
239
+ end
240
+
241
+ it "returns default when disabled" do
242
+ @weather.visibility(false).km.should == 10
243
+ end
244
+
245
+ end
246
+
247
+ end
248
+
81
249
  describe "when answering the simple questions," do
82
250
 
83
251
  before(:each) do
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: attack-barometer
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.2.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Mark G
@@ -9,7 +9,7 @@ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
11
 
12
- date: 2009-04-30 00:00:00 -07:00
12
+ date: 2009-05-02 00:00:00 -07:00
13
13
  default_executable: barometer
14
14
  dependencies: []
15
15
 
@@ -75,6 +75,7 @@ files:
75
75
  - spec/fixtures/geocode_40_73.xml
76
76
  - spec/fixtures/geocode_90210.xml
77
77
  - spec/fixtures/geocode_calgary_ab.xml
78
+ - spec/fixtures/geocode_ksfo.xml
78
79
  - spec/fixtures/geocode_newyork_ny.xml
79
80
  - spec/fixtures/geocode_T5B4M9.xml
80
81
  - spec/fixtures/google_calgary_ab.xml