barometer 0.1.0 → 0.2.1

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: 1
2
3
  :major: 0
3
- :minor: 1
4
- :patch: 0
4
+ :minor: 2
data/bin/barometer CHANGED
@@ -1,63 +1,415 @@
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
+ require 'lib/barometer'
57
+
58
+ require 'optparse'
59
+ require 'rdoc/usage'
60
+ require 'ostruct'
61
+ require 'time'
62
+ require 'date'
63
+ require 'yaml'
64
+
65
+ # file where API keys are stored
66
+ KEY_FILE = File.expand_path(File.join('~', '.barometer'))
67
+
68
+ class App
69
+ VERSION = '0.0.1'
70
+
71
+ attr_reader :options
72
+
73
+ def initialize(arguments, stdin)
74
+ @arguments = arguments.dup
75
+
76
+ # Set defaults
77
+ @options = OpenStruct.new
78
+ @options.timeout = 15
79
+ @options.geocode = false
80
+ @options.skip_graticule = false
81
+ @options.metric = true
82
+ @options.sources = [:wunderground]
83
+ @options.verbode = false
84
+ @options.web = false
85
+ @options.at = Time.now.utc
86
+
87
+ # thresholds
88
+ @options.windy_m = 10
89
+ @options.windy_i = 7
90
+ @options.pop = 50
91
+ end
92
+
93
+ # Parse options, check arguments, then process the command
94
+ def run
95
+ if parsed_options? && arguments_valid?
96
+ puts "Start at #{DateTime.now}\n\n" if @options.verbose
97
+ output_options if @options.verbose # [Optional]
98
+
99
+ process_arguments
100
+ process_command
101
+
102
+ puts "\nFinished at #{DateTime.now}" if @options.verbose
103
+ else
104
+ output_usage
105
+ end
106
+ end
107
+
108
+ protected
109
+
110
+ # future options
111
+ #
112
+ # time: -a --at
113
+ #
114
+ def parsed_options?
115
+ # Specify options
116
+ opt = OptionParser.new
117
+ opt.on('-v', '--version') { output_version ; exit 0 }
118
+ opt.on('-h', '--help') { output_help }
119
+ opt.on('-V', '--verbose') { @options.verbose = true }
120
+ opt.on('-a n', '--at n') {|n| @options.at = Time.parse(n.to_s) }
121
+ opt.on('-t n', '--timeout n') {|n| @options.timeout = n }
122
+ opt.on('-w', '--web') { @options.web = true; ARGV.shift }
123
+ opt.on('-g', '--geocode') { @options.geocode = true }
124
+ opt.on('--skip') { @options.skip_graticule = true }
125
+ opt.on('-m', '--metric') { @options.metric = true }
126
+ opt.on('-i', '--imperial') { @options.metric = false }
127
+ opt.on('--no-wunderground') { @options.sources = @options.sources.delete_if{|s| s == :wunderground} }
128
+ opt.on('--yahoo') { @options.sources << :yahoo }
129
+ opt.on('--google') { @options.sources << :google }
130
+ opt.on('-p n', '--pop n') {|n| @options.pop = n.to_i || 50 }
131
+ opt.on('-s n', '--wind n') {|n| @options.metric ? @options.windy_m = n.to_f || 10 : @options.windy_i = n.to_f || 7 }
132
+
133
+ # pass these onto vegas
134
+ opt.on('-k', '--kill') { @options.web = true }
135
+ opt.on('-S', '--status') { @options.web = true }
136
+
137
+ opt.parse!(@arguments) rescue return false
138
+
139
+ process_options
140
+ true
141
+ end
142
+
143
+ # Performs post-parse processing on options
144
+ def process_options
145
+ Barometer.force_geocode = @options.geocode
146
+ Barometer.selection = { 1 => @options.sources.uniq }
147
+ Barometer.skip_graticule = @options.skip_graticule
148
+ Barometer.timeout = @options.timeout
149
+ end
150
+
151
+ def output_options
152
+ puts "Options:\n"
153
+
154
+ @options.marshal_dump.each do |name, val|
155
+ puts " #{name} = #{val}"
156
+ end
157
+ puts
158
+ end
159
+
160
+ # True if required arguments were provided
161
+ def arguments_valid?
162
+ true if (@arguments.length >= 1 || @options.web)
163
+ end
164
+
165
+ # Setup the arguments
166
+ def process_arguments
167
+ #puts @arguments.inspect
168
+ end
169
+
170
+ def output_help
171
+ output_version
172
+ RDoc::usage() #exits app
173
+ end
174
+
175
+ def output_usage
176
+ RDoc::usage('usage') # gets usage from comments above
177
+ end
178
+
179
+ def output_version
180
+ puts "#{File.basename(__FILE__)} version #{VERSION}"
181
+ end
182
+
183
+ def process_command
184
+ if @options.web
185
+ run_web_mode(@arguments.join(" "))
186
+ else
187
+ barometer = Barometer.new(@arguments.join(" "))
188
+ begin
189
+ barometer.measure(@options.metric) if barometer
190
+ pretty_output(barometer) if barometer.weather
191
+ rescue Barometer::OutOfSources
192
+ puts
193
+ puts " SORRY: your query did not provide any results"
194
+ puts
195
+ end
196
+ end
197
+ end
35
198
  end
36
-
37
- barometer = Barometer.new(ARGV[0])
38
- weather = barometer.measure(true)
199
+
200
+ #
201
+ # HELPERS
202
+ #
203
+
204
+ @level = 1
39
205
 
40
206
  def y(value)
41
207
  value ? "yes" : "no"
42
208
  end
43
209
 
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}"
210
+ def div(char="#")
211
+ puts char*50
212
+ end
213
+
214
+ def title(title, level=1)
215
+ @level = level
216
+ puts "#{" " * @level}-- #{title} --"
217
+ end
218
+
219
+ def value(title, value)
220
+ puts "#{" " * @level}#{title}: #{value}" unless value.nil?
221
+ end
222
+
223
+ def blank
52
224
  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?)}"
225
+ end
226
+
227
+ def section(title, level=1, show_blank=true)
228
+ @level = level
229
+ title(title, level); yield; blank if show_blank
230
+ end
231
+
232
+ def pretty_hash(hash)
233
+ return unless hash.is_a?(Hash)
234
+ hash.each { |k,v| value(k,v) }
235
+ end
236
+
237
+ def pretty_summary(s)
238
+ return unless s
239
+ section("AVERAGES") do
240
+ pretty_hash({
241
+ "humidity" => s.humidity.to_i, "temperature" => s.temperature })
242
+ end
243
+ section("SUMMARY#{ " (@ #{@options.at})" if @options.at }") do
244
+ pretty_hash({
245
+ "day?" => s.day?(@options.at), "sunny?" => s.sunny?(@options.at),
246
+ "windy?" => s.windy?(@options.metric ? @options.windy_m : @options.windy_i, @options.at),
247
+ "wet?" => s.wet?(@options.pop,@options.at) })
248
+ end
249
+ end
250
+
251
+ def pretty_query(q)
252
+ return unless q
253
+ section("QUERY", 2) do
254
+ pretty_hash({"Format" => q.format})
255
+ pretty_hash({
256
+ "Address" => q.geo.address,
257
+ "Locality" => q.geo.locality, "Region" => q.geo.region,
258
+ "Country" => q.geo.country, "Country Code" => q.geo.country_code,
259
+ "Latitude" => q.geo.latitude, "Longitude" => q.geo.longitude }) if q.geo
260
+ end
261
+ end
262
+
263
+ def pretty_location(l)
264
+ return unless l
265
+ section("LOCATION", 2) do
266
+ pretty_hash({
267
+ "ID" => l.id, "Name" => l.name,
268
+ "City" => l.city, "State Name" => l.state_name,
269
+ "State Code" => l.state_code, "Country" => l.country,
270
+ "Country Code" => l.country_code, "Zip Code" => l.zip_code,
271
+ "Latitude" => l.latitude, "Longitude" => l.longitude })
272
+ end
273
+ end
274
+
275
+ def pretty_station(s)
276
+ return unless s
277
+ section("STATION", 2) do
278
+ pretty_hash({
279
+ "ID" => s.id, "Name" => s.name,
280
+ "City" => s.city, "State Name" => s.state_name,
281
+ "State Code" => s.state_code, "Country" => s.country,
282
+ "Country Code" => s.country_code, "Zip Code" => s.zip_code,
283
+ "Latitude" => s.latitude, "Longitude" => s.longitude })
284
+ end
285
+ end
286
+
287
+ def pretty_timezone(t)
288
+ return unless t
289
+ section("TIMEZONE", 2) do
290
+ pretty_hash({ "Long" => t.timezone, "Code" => t.code,"DST?" => t.dst? })
291
+ end
292
+ end
293
+
294
+ def pretty_current(c)
295
+ return unless c
296
+ section("CURRENT", 2) do
297
+ pretty_hash({
298
+ "Time" => c.time, "Local Time" => c.local_time,
299
+ "Humidity" => c.humidity, "Icon" => c.icon,
300
+ "Condition" => c.condition, "Temperature" => c.temperature,
301
+ "Dew Point" => c.dew_point, "Heat Index" => c.heat_index,
302
+ "Pressure" => c.pressure, "Visibility" => c.visibility })
303
+ pretty_hash({ "Wind Chill" => c.wind_chill, "Wind" => c.wind,
304
+ "Wind Direction" => c.wind.direction, "Degrees" => c.wind.degrees }) if c.wind
305
+ pretty_hash({ "Sun Rise" => c.sun.rise, "Sun Set" => c.sun.set }) if c.sun
306
+ end
307
+ end
308
+
309
+ def pretty_forecast(f)
310
+ return unless f
311
+ section("FOR: #{f.date}", 3) do
312
+ pretty_hash({
313
+ "Date" => f.date, "Icon" => f.icon,
314
+ "Condition" => f.condition, "High" => f.high,
315
+ "Low" => f.low, "POP" => f.pop })
316
+ pretty_hash({ "Sun Rise" => f.sun.rise, "Sun Set" => f.sun.set }) if f.sun
317
+ end
318
+ end
319
+
320
+ def pretty_forecasts(forecasts)
321
+ return unless forecasts
322
+ section("FORECAST", 3, false) do
323
+ blank
324
+ forecasts.each do |forecast|
325
+ pretty_forecast(forecast)
326
+ end
327
+ end
328
+ end
329
+
330
+ def pretty_measurement(m)
331
+ return unless m
332
+ section(m.source.to_s, 1) do
333
+ pretty_hash({
334
+ "Source" => m.source, "Time" => m.time,
335
+ "Metric" => m.metric?, "Success" => m.success? })
336
+ end
337
+ pretty_location(m.location)
338
+ pretty_station(m.station)
339
+ pretty_timezone(m.timezone)
340
+ pretty_current(m.current)
341
+ pretty_forecasts(m.forecast)
342
+ end
343
+
344
+ def pretty_measurements(w)
345
+ return unless w
346
+ section("MEASUREMENTS", 1) do
347
+ blank
348
+ w.measurements.each do |m|
349
+ pretty_measurement(m)
350
+ end
351
+ end
352
+ end
353
+
354
+ def pretty_info
355
+ title("INFO", 1)
356
+ value("GitHub", "http://github.com/attack/barometer")
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
+ raise "This is currently disabled"
379
+
380
+ #require File.expand_path(File.dirname(__FILE__) + '/../lib/webometer/webometer.rb')
381
+ #require 'vegas'
382
+
383
+ #Vegas::Runner.new(Webometer, 'webometer') do |opts, app|
384
+ # opts is an option parser object
385
+ # app is your app class
386
+ #end
387
+ end
388
+
389
+ def geocode_google_key_message
58
390
  puts
391
+ puts "Please update the key_file '#{KEY_FILE}' with your google api key"
392
+ puts "Get it here: http://code.google.com/apis/maps/signup.html"
393
+ puts "The, add this line to the file:"
394
+ puts "geocode_google: YOUR_KEY_KERE"
59
395
  puts
60
- puts " -- INFO --"
61
- puts " http://github.com/attack/barometer"
62
- puts "---------------------------------------------------"
63
- end
396
+ end
397
+
398
+ # set API keys
399
+ if File.exists?(KEY_FILE)
400
+ keys = YAML.load_file(KEY_FILE)
401
+ if keys["geocode_google"]
402
+ Barometer.google_geocode_key = keys["geocode_google"]
403
+ else
404
+ geocode_google_key_message
405
+ exit
406
+ end
407
+ else
408
+ File.open(KEY_FILE, 'w') {|f| f << "geocode_google: YOUR_KEY_KERE" }
409
+ geocode_google_key_message
410
+ exit
411
+ end
412
+
413
+ # Create and run the application
414
+ app = App.new(ARGV, STDIN)
415
+ app.run