barometer 0.1.0 → 0.2.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/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