yweather 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (42) hide show
  1. data/.gitignore +6 -0
  2. data/CHANGELOG +3 -0
  3. data/Gemfile +2 -0
  4. data/Guardfile +9 -0
  5. data/LICENSE +504 -0
  6. data/README.md +109 -0
  7. data/Rakefile +14 -0
  8. data/bin/yweather +30 -0
  9. data/lib/yweather.rb +22 -0
  10. data/lib/yweather/astronomy.rb +12 -0
  11. data/lib/yweather/atmosphere.rb +34 -0
  12. data/lib/yweather/client.rb +80 -0
  13. data/lib/yweather/condition.rb +71 -0
  14. data/lib/yweather/forecast.rb +22 -0
  15. data/lib/yweather/image.rb +19 -0
  16. data/lib/yweather/location.rb +15 -0
  17. data/lib/yweather/response.rb +87 -0
  18. data/lib/yweather/units.rb +20 -0
  19. data/lib/yweather/utils.rb +7 -0
  20. data/lib/yweather/version.rb +3 -0
  21. data/lib/yweather/wind.rb +15 -0
  22. data/spec/bin/yweather_spec.rb +7 -0
  23. data/spec/cassettes/invalid_weather_transaction.yml +44 -0
  24. data/spec/cassettes/invalid_woeid_conversion_transaction.yml +38 -0
  25. data/spec/cassettes/valid_weather_transaction.yml +66 -0
  26. data/spec/cassettes/valid_weather_transaction_with_zipcode_conversion.yml +101 -0
  27. data/spec/cassettes/valid_woeid_conversion_transaction.yml +38 -0
  28. data/spec/lib/yweather/astronomy_spec.rb +27 -0
  29. data/spec/lib/yweather/atmosphere_spec.rb +25 -0
  30. data/spec/lib/yweather/client_spec.rb +58 -0
  31. data/spec/lib/yweather/condition_spec.rb +25 -0
  32. data/spec/lib/yweather/forecast_spec.rb +25 -0
  33. data/spec/lib/yweather/image_spec.rb +25 -0
  34. data/spec/lib/yweather/location_spec.rb +25 -0
  35. data/spec/lib/yweather/response_spec.rb +49 -0
  36. data/spec/lib/yweather/units_spec.rb +25 -0
  37. data/spec/lib/yweather/wind_spec.rb +25 -0
  38. data/spec/spec_helper.rb +34 -0
  39. data/spec/support/helpers/spec_parse_helpers.rb +25 -0
  40. data/spec/support/helpers/spec_yweather_helpers.rb +16 -0
  41. data/yweather.gemspec +39 -0
  42. metadata +283 -0
@@ -0,0 +1,109 @@
1
+ # Yweather
2
+
3
+ ## Overview
4
+
5
+ This is a rewrite of Walter Korman's <shaper@fatgoose.com>
6
+ [yahoo-weather](http://rubyforge.org/projects/yahoo-weather) gem.
7
+
8
+ The aim of this port/rewrite is to add proper specs, better test-coverage and active
9
+ maintenance. I have ported much of Walter's original intent and LICENSE, and thank him for
10
+ his contribution. My initial motivation is to send Prowl notifications to my iPad, iPhone,
11
+ iPod whenever hazardous weather is coming.
12
+
13
+ Yweather provides an object-oriented Ruby interface to the Yahoo! Weather XML RSS feed
14
+ detailed at [http://developer.yahoo.com/weather](http://developer.yahoo.com/weather).
15
+
16
+ People care a lot about the weather. This may seem ironic given they can just glance out
17
+ the window. However, we can all understand a fascination with details and forecasting.
18
+
19
+ Log the weather information to your database! Graph it to your heart's content! Write a
20
+ widget that emails the weather to your cell phone every five minutes with a link to your
21
+ friend's PayPal account to deposit money if the weather's sunny and you both bet that it
22
+ would be rainy. And the fun doesn't have to stop there.
23
+
24
+ Source code is at [http://github.com/midwire/yweather](http://github.com/midwire/yweather).
25
+
26
+ NOTE: This library was updated as of December 2009 to use a new WOEID-based lookup
27
+ interface. Yahoo has deprecated the older non-WOEID-based lookup API. The archived page
28
+ with the deprecated API details is at:
29
+
30
+ [http://developer.yahoo.com/weather/archive.html](http://developer.yahoo.com/weather/archive.html)
31
+
32
+ ## Installation
33
+
34
+ gem install yweather
35
+
36
+ ## Usage
37
+
38
+ A simple example program:
39
+
40
+ #!/usr/bin/env ruby
41
+ require "yweather"
42
+ require "colorize"
43
+
44
+ zipcode = ARGV.shift
45
+ if zipcode.nil?
46
+ puts ">>> Usage: yweather zipcode"
47
+ exit
48
+ end
49
+
50
+ client = Yweather::Client.new
51
+ response = client.get_response_for_zipcode(zipcode)
52
+
53
+ # TODO: Use a ~/.yweather YAML file to determine format of output
54
+
55
+ print <<edoc
56
+ #{response.title.yellow}
57
+ #{response.condition.temp} degrees
58
+ #{response.condition.text}
59
+ edoc
60
+
61
+ puts "Wind:".yellow
62
+ response.wind_conditions.each_pair do |key, value|
63
+ puts " #{key}: #{value}"
64
+ end
65
+
66
+ puts "Atmosphere:".yellow
67
+ response.atmospheric_conditions.each_pair do |key, value|
68
+ puts " #{key}: #{value}"
69
+ end
70
+
71
+
72
+ Produces output as:
73
+
74
+ Conditions for Beverly Hills, CA at 10:51 am PST
75
+ 61 degrees
76
+ Fair
77
+ Wind:
78
+ chill: 61°
79
+ direction: 0°
80
+ speed: 0 mph
81
+ Atmosphere:
82
+ humidity: 23%
83
+ visibility: 10 mi
84
+ pressure: steady at 30.18in
85
+
86
+ There is a variety of detailed weather information in other attributes of the Yweather::Response object.
87
+
88
+ ## License
89
+
90
+ This library is provided via the GNU LGPL license at http://www.gnu.org/licenses/lgpl.html.
91
+
92
+ ## Author
93
+
94
+ Copyright 2006 - 2009, Walter Korman <shaper@fatgoose.com>,
95
+ http://lemurware.blogspot.com.
96
+
97
+ Copyright 2012, Chris Blackburn <chris you-know-what-goes-here m and-here blackburn@gmail and-here com>,
98
+ http://midwire.github.com
99
+
100
+ Thanks to Walter Korman for the initial hack.
101
+
102
+ ## Notes
103
+
104
+ Pull requests are appreciated. Please use feature/branches or hotfix/branches and add specs for your changes.
105
+
106
+ ## ToDo
107
+
108
+ * Use a ~/.yweather YAML file, or some other templating scheme, to determine format of output when running the binary.
109
+
@@ -0,0 +1,14 @@
1
+ require "bundler/gem_tasks"
2
+ require 'rspec/core/rake_task'
3
+
4
+ spec_tasks = Dir['spec/*/'].map { |d| File.basename(d) }
5
+
6
+ spec_tasks.each do |folder|
7
+ RSpec::Core::RakeTask.new("spec:#{folder}") do |t|
8
+ t.pattern = "./spec/#{folder}/**/*_spec.rb"
9
+ t.rspec_opts = %w(-fs --color)
10
+ end
11
+ end
12
+
13
+ desc "Run complete application spec suite"
14
+ task 'spec' => spec_tasks.map { |f| "spec:#{f}" }
@@ -0,0 +1,30 @@
1
+ #!/usr/bin/env ruby
2
+ require "yweather"
3
+ require "colorize"
4
+
5
+ zipcode = ARGV.shift
6
+ if zipcode.nil?
7
+ puts ">>> Usage: yweather zipcode"
8
+ exit
9
+ end
10
+
11
+ client = Yweather::Client.new
12
+ response = client.get_response_for_zipcode(zipcode)
13
+
14
+ # TODO: Use a ~/.yweather YAML file to determine format of output
15
+
16
+ print <<edoc
17
+ #{response.title.yellow}
18
+ #{response.condition.temp} degrees
19
+ #{response.condition.text}
20
+ edoc
21
+
22
+ puts "Wind:".yellow
23
+ response.wind_conditions.each_pair do |key, value|
24
+ puts " #{key}: #{value}"
25
+ end
26
+
27
+ puts "Atmosphere:".yellow
28
+ response.atmospheric_conditions.each_pair do |key, value|
29
+ puts " #{key}: #{value}"
30
+ end
@@ -0,0 +1,22 @@
1
+ require 'time'
2
+ require 'cgi'
3
+ require 'rest-client'
4
+ require 'yql'
5
+ require 'active_support/core_ext'
6
+ require 'json'
7
+ require "yweather/utils"
8
+ require "yweather/version"
9
+ require "yweather/client"
10
+ require "yweather/response"
11
+ require "yweather/astronomy"
12
+ require "yweather/atmosphere"
13
+ require "yweather/location"
14
+ require "yweather/wind"
15
+ require "yweather/units"
16
+ require "yweather/image"
17
+ require "yweather/condition"
18
+ require "yweather/forecast"
19
+
20
+ module Yweather
21
+ class YweatherException < RuntimeError; end
22
+ end
@@ -0,0 +1,12 @@
1
+ module Yweather
2
+ class Astronomy
3
+ attr_reader :sunrise
4
+ attr_reader :sunset
5
+
6
+ def initialize(data)
7
+ @sunrise = data[:sunrise]
8
+ @sunset = data[:sunset]
9
+ end
10
+
11
+ end
12
+ end
@@ -0,0 +1,34 @@
1
+ module Yweather
2
+ class Atmosphere
3
+
4
+ class Barometer
5
+ STEADY = 'steady'
6
+ RISING = 'rising'
7
+ FALLING = 'falling'
8
+
9
+ # lists all possible barometer constants
10
+ ALL = [ STEADY, RISING, FALLING ]
11
+ end
12
+
13
+ attr_reader :humidity
14
+ attr_reader :visibility
15
+ attr_reader :pressure
16
+ attr_reader :barometer
17
+
18
+ def initialize(data)
19
+ @humidity = data[:humidity].to_i
20
+ @visibility = data[:visibility].to_i
21
+ @pressure = data[:pressure].to_f
22
+ @barometer = nil
23
+ case data[:rising].to_i
24
+ when 0
25
+ @barometer = Barometer::STEADY
26
+ when 1
27
+ @barometer = Barometer::RISING
28
+ when 2
29
+ @barometer = Barometer::FALLING
30
+ end
31
+ end
32
+
33
+ end
34
+ end
@@ -0,0 +1,80 @@
1
+ module Yweather
2
+ class Client
3
+ @@BASE_URL = "http://weather.yahooapis.com/forecastrss"
4
+ attr_reader :response
5
+
6
+ def initialize
7
+ @response = nil
8
+ end
9
+
10
+ def get_feed_for_zipcode(zipcode, options = {})
11
+ woeid = woeid_from_zipcode(zipcode)
12
+ get_feed_for_woeid(woeid, options)
13
+ end
14
+
15
+ def get_feed_for_woeid(woeid, options = {})
16
+ units = options[:units] || 'f'
17
+ format = options[:format] || :json
18
+ url = build_url(woeid, units)
19
+ get(url, format)
20
+ end
21
+
22
+ def get_response_for_woeid(woeid, options = {})
23
+ units = options[:units] || 'f'
24
+ format = options[:format] || :hash
25
+ url = build_url(woeid, units)
26
+ hash = get(url, format)
27
+ @response = Response.new(woeid, nil, url, hash)
28
+ end
29
+
30
+ def get_response_for_zipcode(zipcode, options = {})
31
+ units = options[:units] || 'f'
32
+ format = options[:format] || :hash
33
+ woeid = woeid_from_zipcode(zipcode)
34
+ url = build_url(woeid, units)
35
+ hash = get(url, format)
36
+ @response = Response.new(woeid, zipcode, url, hash)
37
+ end
38
+
39
+ ##################################################
40
+ private
41
+
42
+ def build_url(woeid, units)
43
+ "#{@@BASE_URL}?w=#{CGI.escape(woeid.to_s)}&u=#{CGI.escape(units)}"
44
+ end
45
+
46
+ def woeid_from_zipcode(zipcode)
47
+ yql = Yql::Client.new
48
+ query = Yql::QueryBuilder.new 'geo.places'
49
+ query.select = 'woeid'
50
+ query.conditions = "text = '#{zipcode}, usa'"
51
+ yql.query = query
52
+ yql.format = 'json'
53
+ response = yql.get
54
+ h = ::HashWithIndifferentAccess.new(::JSON::parse(response.body))
55
+ if h[:query][:results][:place].is_a?(Array)
56
+ raise YweatherException, "Cannot determine WOEID from the ZIPCODE [#{zipcode}]"
57
+ return
58
+ end
59
+ h[:query][:results][:place][:woeid]
60
+ end
61
+
62
+ def get(url, format)
63
+ response = RestClient.get(url)
64
+ if response.body =~ /Error/
65
+ hash = ::HashWithIndifferentAccess.new(Hash.from_xml(response.body))
66
+ item = hash[:rss][:channel][:item]
67
+ raise YweatherException, "#{hash[:rss][:channel][:title]} :: #{item[:title]} :: #{item[:description]}"
68
+ return
69
+ end
70
+ if format == :xml
71
+ response.body
72
+ elsif format == :json
73
+ Hash.from_xml(response.body).to_json
74
+ elsif format == :hash
75
+ ::HashWithIndifferentAccess.new(Hash.from_xml(response.body))
76
+ end
77
+ end
78
+
79
+ end
80
+ end
@@ -0,0 +1,71 @@
1
+ module Yweather
2
+ class Condition
3
+ include Yweather::Utils
4
+
5
+ CODES = {
6
+ 0 => "tornado",
7
+ 1 => "tropical storm",
8
+ 2 => "hurricane",
9
+ 3 => "severe thunderstorms",
10
+ 4 => "thunderstorms",
11
+ 5 => "mixed rain and snow",
12
+ 6 => "mixed rain and sleet",
13
+ 7 => "mixed snow and sleet",
14
+ 8 => "freezing drizzle",
15
+ 9 => "drizzle",
16
+ 10 => "freezing rain",
17
+ 11 => "showers",
18
+ 12 => "showers",
19
+ 13 => "snow flurries",
20
+ 14 => "light snow showers",
21
+ 15 => "blowing snow",
22
+ 16 => "snow",
23
+ 17 => "hail",
24
+ 18 => "sleet",
25
+ 19 => "dust",
26
+ 20 => "foggy",
27
+ 21 => "haze",
28
+ 22 => "smoky",
29
+ 23 => "blustery",
30
+ 24 => "windy",
31
+ 25 => "cold",
32
+ 26 => "cloudy",
33
+ 27 => "mostly cloudy (night)",
34
+ 28 => "mostly cloudy (day)",
35
+ 29 => "partly cloudy (night)",
36
+ 30 => "partly cloudy (day)",
37
+ 31 => "clear (night)",
38
+ 32 => "sunny",
39
+ 33 => "fair (night)",
40
+ 34 => "fair (day)",
41
+ 35 => "mixed rain and hail",
42
+ 36 => "hot",
43
+ 37 => "isolated thunderstorms",
44
+ 38 => "scattered thunderstorms",
45
+ 39 => "scattered thunderstorms",
46
+ 40 => "scattered showers",
47
+ 41 => "heavy snow",
48
+ 42 => "scattered snow showers",
49
+ 43 => "heavy snow",
50
+ 44 => "partly cloudy",
51
+ 45 => "thundershowers",
52
+ 46 => "snow showers",
53
+ 47 => "isolated thundershowers",
54
+ 3200 => "not available"
55
+ }
56
+
57
+ # the Yahoo! Weather condition code, as detailed at http://developer.yahoo.com/weather.
58
+ attr_reader :code
59
+ attr_reader :date
60
+ attr_reader :temp
61
+ attr_reader :text
62
+
63
+ def initialize(data)
64
+ @code = data[:code].to_i
65
+ @date = parse_time(data[:date])
66
+ @temp = data[:temp].to_i
67
+ @text = data[:text]
68
+ end
69
+
70
+ end
71
+ end
@@ -0,0 +1,22 @@
1
+ module Yweather
2
+ class Forecast
3
+ include Yweather::Utils
4
+
5
+ attr_reader :day
6
+ attr_reader :date
7
+ attr_reader :low
8
+ attr_reader :high
9
+ attr_reader :text
10
+ attr_reader :code
11
+
12
+ def initialize (data)
13
+ @day = data[:day]
14
+ @date = parse_time(data[:date])
15
+ @low = data[:low].to_i
16
+ @high = data[:high].to_i
17
+ @text = data[:text]
18
+ @code = data[:code].to_i
19
+ end
20
+
21
+ end
22
+ end
@@ -0,0 +1,19 @@
1
+ module Yweather
2
+ class Image
3
+
4
+ attr_reader :height
5
+ attr_reader :link
6
+ attr_reader :title
7
+ attr_reader :url
8
+ attr_reader :width
9
+
10
+ def initialize (data)
11
+ @title = data[:title]
12
+ @link = data[:link]
13
+ @url = data[:url]
14
+ @height = data[:height]
15
+ @width = data[:width]
16
+ end
17
+
18
+ end
19
+ end
@@ -0,0 +1,15 @@
1
+ module Yweather
2
+ class Location
3
+
4
+ attr_reader :city
5
+ attr_reader :country
6
+ attr_reader :region
7
+
8
+ def initialize(data)
9
+ @city = data[:city]
10
+ @country = data[:country]
11
+ @region = data[:region]
12
+ end
13
+
14
+ end
15
+ end
@@ -0,0 +1,87 @@
1
+ # encoding: ascii-8bit
2
+ module Yweather
3
+ class Response
4
+ attr_accessor :astronomy
5
+ attr_accessor :atmosphere
6
+ attr_accessor :condition
7
+ attr_accessor :description
8
+ attr_accessor :forecasts
9
+ attr_accessor :image
10
+ attr_accessor :latitude
11
+ attr_accessor :location
12
+ attr_accessor :longitude
13
+ attr_accessor :page_url
14
+ attr_accessor :request_location
15
+ attr_accessor :request_url
16
+ attr_accessor :title
17
+ attr_accessor :units
18
+ attr_accessor :wind
19
+
20
+ def initialize(woeid, zipcode, url, hash)
21
+ if hash[:rss][:channel][:title] =~ /Error/
22
+ item = hash[:rss][:channel][:item]
23
+ raise YweatherException, "#{hash[:rss][:channel][:title]} :: #{item[:title]} :: #{item[:description]}"
24
+ return
25
+ end
26
+
27
+ @astronomy = Astronomy.new(hash[:rss][:channel][:astronomy])
28
+ @atmosphere = Atmosphere.new(hash[:rss][:channel][:atmosphere])
29
+ @location = Location.new(hash[:rss][:channel][:location])
30
+ @wind = Wind.new(hash[:rss][:channel][:wind])
31
+ @units = Units.new(hash[:rss][:channel][:units])
32
+ @image = Image.new(hash[:rss][:channel][:image])
33
+
34
+ item = hash[:rss][:channel][:item]
35
+ @condition = Condition.new(item[:condition])
36
+ @forecasts = []
37
+ item[:forecast].each do |forecast|
38
+ @forecasts << Forecast.new(forecast)
39
+ end
40
+ @latitude = item[:lat]
41
+ @longitude = item[:long]
42
+ @page_url = item[:link]
43
+ @title = item[:title]
44
+ @description = item[:description]
45
+ end
46
+
47
+ def atmospheric_conditions(options = {})
48
+ pressure = (@atmosphere.barometer == "steady") ?
49
+ "#{@atmosphere.barometer} at #{@atmosphere.pressure}#{@units.pressure}" :
50
+ "#{@atmosphere.pressure}#{@units.pressure} and #{@atmosphere.barometer}"
51
+
52
+ h = ::HashWithIndifferentAccess.new({
53
+ :humidity => "#{@atmosphere.humidity}%",
54
+ :visibility => "#{@atmosphere.visibility} #{@units.distance}",
55
+ :pressure => "#{pressure}"
56
+ })
57
+
58
+ return_content(h, options[:format])
59
+ end
60
+
61
+ def wind_conditions(options = {})
62
+ # chill: wind chill in degrees (integer)
63
+ # direction: wind direction, in degrees (integer)
64
+ # speed: wind speed, in the units specified in the speed attribute of the yweather:units element (mph or kph). (integer)
65
+ h = ::HashWithIndifferentAccess.new({
66
+ :chill => "#{@wind.chill}°",
67
+ :direction => "#{@wind.direction}°",
68
+ :speed => "#{@wind.speed} #{@units.speed}"
69
+ })
70
+ return_content(h, options[:format])
71
+ end
72
+
73
+ ##################################################
74
+ private
75
+
76
+ def return_content(hash, format)
77
+ if format == :json
78
+ hash.to_json
79
+ elsif format == :xml
80
+ hash.to_xml
81
+ else
82
+ hash
83
+ end
84
+ end
85
+
86
+ end
87
+ end