meteorology 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.
@@ -0,0 +1,24 @@
1
+ require 'rubygems'
2
+ require 'libxml'
3
+ require 'time'
4
+ require 'uri'
5
+ require 'net/http'
6
+
7
+ require 'meteorology/weather_collection'
8
+ require 'meteorology/weather'
9
+ require 'meteorology/temperature'
10
+ require 'meteorology/providers'
11
+
12
+ module Meteorology
13
+ def self.included(klass)
14
+ klass.send(:include, InstanceMethods)
15
+ end
16
+
17
+ module InstanceMethods
18
+ def weather
19
+ # Just assumes #latitude and #longitude for now
20
+ # Also just assumes Yr.no
21
+ @weather ||= Meteorology::Providers::Yr.new.load(latitude, longitude)
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,6 @@
1
+ module Meteorology
2
+ module Providers
3
+ end
4
+ end
5
+
6
+ Dir[File.dirname(__FILE__) + '/providers/*.rb'].each{|g| require g}
@@ -0,0 +1,89 @@
1
+ module Meteorology
2
+ module Providers
3
+ class Yr
4
+
5
+ URL = "http://api.yr.no/weatherapi/locationforecast/1.6/?lat=%s;lon=%s"
6
+
7
+ def load(lat, lng)
8
+ parser = LibXML::XML::SaxParser.string(Net::HTTP.get(URI.parse(URL % [lat, lng])))
9
+ parser.callbacks = XmlParser.new
10
+ parser.parse
11
+ return parser.callbacks.forecasts
12
+ end
13
+
14
+ class XmlParser
15
+ attr_accessor :forecasts
16
+
17
+ ATTR_MAPPING = {
18
+ 'fog' => :percent,
19
+ 'pressure' => :value,
20
+ 'highClouds' => :percent,
21
+ 'windDirection' => :name,
22
+ 'mediumClouds' => :percent,
23
+ 'windSpeed' => :mps,
24
+ 'cloudiness' => :percent,
25
+ 'lowClouds' => :percent,
26
+ 'humidity' => :value,
27
+ 'temperature' => :value,
28
+ 'symbol' => :number,
29
+ 'precipitation' => :value,
30
+ 'temperatureProbability' => :value,
31
+ 'windProbability' => :value,
32
+ 'symbolProbability' => :value
33
+ }
34
+
35
+ def initialize
36
+ @forecasts = WeatherCollection.new
37
+ @location = 0
38
+ end
39
+
40
+ def method_missing(*args)
41
+ #dont care
42
+ end
43
+
44
+ def on_start_element(element, attributes)
45
+ case element
46
+ when 'time'
47
+ if new_period?(attributes)
48
+ key = Time.parse(attributes['from'])
49
+ @current_period = @forecasts[key] = Meteorology::Weather.new(key)
50
+ end
51
+ when 'location'
52
+ if @current_period
53
+ @location += 1
54
+ end
55
+ else
56
+ if @current_period && @location <= 2
57
+ read_value(element, attributes)
58
+ end
59
+ end
60
+ end
61
+
62
+ def read_value(element, attributes)
63
+ case element
64
+ when 'temperature'
65
+ @current_period.temperature = Meteorology::Temperature.new(attributes['unit'].to_sym, attributes['value'])
66
+ else
67
+ method = "#{element}="
68
+ @current_period.send(method, attributes[ATTR_MAPPING[element].to_s]) if @current_period.respond_to?(method)
69
+ end
70
+ end
71
+
72
+ def on_end_element(element)
73
+ if element == 'location' && @location == 2
74
+ # Done with one period
75
+ @location = 0
76
+ @current_period = nil
77
+ end
78
+ end
79
+
80
+ def new_period?(attributes)
81
+ attributes['from'] == attributes['to']
82
+ end
83
+
84
+ end
85
+
86
+
87
+ end
88
+ end
89
+ end
@@ -0,0 +1,14 @@
1
+ module Meteorology
2
+ class Temperature
3
+ def initialize(system, degrees)
4
+ raise ArgumentError.new("Need :farenheit or :celcius") unless [:farenheit, :celcius].include? system
5
+
6
+ @degrees = degrees
7
+ @system = system
8
+ end
9
+
10
+ def to_s
11
+ "#{@degrees} #{@system.to_s[0,1].upcase}"
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,10 @@
1
+ module Meteorology
2
+ class Weather
3
+ attr_accessor :time, :temperature
4
+
5
+ def initialize(time)
6
+ @time = time.is_a?(String) ? Time.parse(time) : time
7
+ end
8
+
9
+ end
10
+ end
@@ -0,0 +1,12 @@
1
+ module Meteorology
2
+ class WeatherCollection < Hash
3
+ def current
4
+ self.send("[]", current_time_key)
5
+ end
6
+
7
+ private
8
+ def current_time_key
9
+ Time.at((Time.now.to_f / 3600).round * 3600)
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,33 @@
1
+ require 'spec_helper'
2
+
3
+ describe "Meteorology::Providers::Yr" do
4
+ it "exists, d'uh" do
5
+ lambda{Meteorology::Providers::Yr}.should_not raise_error(NameError)
6
+ end
7
+
8
+ context 'parsing' do
9
+ before(:all) do
10
+ @yr = Meteorology::Providers::Yr.new
11
+ @lat = 59.9
12
+ @lng = 10.75
13
+
14
+ FakeWeb.allow_net_connect = false
15
+ FakeWeb.register_uri(:get,
16
+ Meteorology::Providers::Yr::URL % [@lat, @lng],
17
+ :body => File.read($fixture_dir + '/yr.no/locationforecast-1.6.xml'))
18
+
19
+ end
20
+
21
+ after(:all) do
22
+ FakeWeb.allow_net_connect = true
23
+ FakeWeb.clean_registry
24
+ end
25
+
26
+ it 'parses yr xml files to a WeatherCollection' do
27
+ wc = @yr.load(59.9, 10.75)
28
+ wc.should be_instance_of(Meteorology::WeatherCollection)
29
+ wc.size.should == 87
30
+ end
31
+ end
32
+
33
+ end
@@ -0,0 +1,21 @@
1
+ require 'spec_helper'
2
+
3
+ describe "Meteorology::Temperature" do
4
+ it "exists, d'uh" do
5
+ lambda{Meteorology::Temperature}.should_not raise_error(NameError)
6
+ end
7
+
8
+ it 'is instantiated with :farenheit or :celcuis and the degree' do
9
+ lambda {temp = Meteorology::Temperature.new(:celcius, 10)}.should_not raise_error(ArgumentError)
10
+ lambda {temp = Meteorology::Temperature.new(:farenheit, 100)}.should_not raise_error(ArgumentError)
11
+
12
+ lambda {temp = Meteorology::Temperature.new(:foo, 100)}.should raise_error(ArgumentError)
13
+ lambda {temp = Meteorology::Temperature.new(10)}.should raise_error(ArgumentError)
14
+ end
15
+
16
+ it 'pretty prints' do
17
+ Meteorology::Temperature.new(:celcius, 10).to_s.should == "10 C"
18
+ Meteorology::Temperature.new(:farenheit, 100).to_s.should == "100 F"
19
+ end
20
+
21
+ end
@@ -0,0 +1,42 @@
1
+ require 'spec_helper'
2
+
3
+ describe 'Meteorology::WeatherCollection' do
4
+ it "exists, d'uh" do
5
+ lambda{Meteorology::WeatherCollection}.should_not raise_error(NameError)
6
+ end
7
+
8
+ before(:all) do
9
+ @wc = Meteorology::WeatherCollection.new
10
+ 24.times do |i|
11
+ time = Time.utc(2009, 10, 20, i, 0, 0)
12
+ @wc[time] = Meteorology::Weather.new(time)
13
+ end
14
+ end
15
+
16
+ it 'approximates to closest hour' do
17
+ Time.stub!(:now).and_return(Time.utc(2009, 4, 29, 2, 27, 0))
18
+ @wc.send(:current_time_key).should == Time.utc(2009, 4, 29, 2, 0, 0)
19
+
20
+ Time.stub!(:now).and_return(Time.utc(2009, 4, 29, 2, 31, 0))
21
+ @wc.send(:current_time_key).should == Time.utc(2009, 4, 29, 3, 0, 0)
22
+
23
+ Time.stub!(:now).and_return(Time.utc(2009, 4, 29, 23, 45, 0))
24
+ @wc.send(:current_time_key).should == Time.utc(2009, 4, 30, 0, 0, 0)
25
+
26
+ Time.stub!(:now).and_return(Time.utc(2009, 12, 31, 23, 45, 0))
27
+ @wc.send(:current_time_key).should == Time.utc(2010, 1, 1, 0, 0, 0)
28
+
29
+ end
30
+
31
+ it 'provides the #current weather on closest hour' do
32
+ @wc.should respond_to(:current)
33
+
34
+ time_now = Time.utc(2009, 10, 20, 12, 27, 0)
35
+ Time.stub!(:now).and_return(time_now)
36
+
37
+ current = @wc.current
38
+ current.should be_instance_of(Meteorology::Weather)
39
+ current.time.should == Time.utc(2009, 10, 20, 12, 00, 0)
40
+
41
+ end
42
+ end
@@ -0,0 +1,17 @@
1
+ require 'spec_helper'
2
+
3
+ describe "Meteorology::Weather" do
4
+ it "exists, d'uh" do
5
+ lambda{Meteorology::Weather}.should_not raise_error(NameError)
6
+ end
7
+
8
+ it 'accepts String or Time for time on #initialize' do
9
+ Meteorology::Weather.new("2009-01-01").time.should be_instance_of(Time)
10
+ Meteorology::Weather.new(Time.now).time.should be_instance_of(Time)
11
+ end
12
+
13
+ it 'reports #temperature' do
14
+ w = Meteorology::Weather.new(Time.now.to_s)
15
+ w.should respond_to(:temperature)
16
+ end
17
+ end
@@ -0,0 +1,7 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
2
+
3
+ describe "Meteorology" do
4
+ it "exists, d'uh" do
5
+ lambda { Meteorology }.should_not raise_error(NameError)
6
+ end
7
+ end
@@ -0,0 +1 @@
1
+ --color
@@ -0,0 +1,13 @@
1
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
2
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
3
+ require 'meteorology'
4
+
5
+ require 'spec/autorun'
6
+
7
+ require 'fakeweb'
8
+
9
+ Spec::Runner.configure do |config|
10
+
11
+ end
12
+
13
+ $fixture_dir = File.join(File.dirname(__FILE__), '..', 'fixtures')
metadata ADDED
@@ -0,0 +1,122 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: meteorology
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Rune Botten
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2009-10-20 00:00:00 +02:00
13
+ default_executable:
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: rspec
17
+ type: :development
18
+ version_requirement:
19
+ version_requirements: !ruby/object:Gem::Requirement
20
+ requirements:
21
+ - - ">="
22
+ - !ruby/object:Gem::Version
23
+ version: 1.2.9
24
+ version:
25
+ - !ruby/object:Gem::Dependency
26
+ name: cucumber
27
+ type: :development
28
+ version_requirement:
29
+ version_requirements: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: "0"
34
+ version:
35
+ - !ruby/object:Gem::Dependency
36
+ name: fakeweb
37
+ type: :development
38
+ version_requirement:
39
+ version_requirements: !ruby/object:Gem::Requirement
40
+ requirements:
41
+ - - ">="
42
+ - !ruby/object:Gem::Version
43
+ version: 1.2.6
44
+ version:
45
+ - !ruby/object:Gem::Dependency
46
+ name: libxml-ruby
47
+ type: :runtime
48
+ version_requirement:
49
+ version_requirements: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - ">="
52
+ - !ruby/object:Gem::Version
53
+ version: 1.1.3
54
+ version:
55
+ description: "Adds #weather method to ruby objects that know where they are in the world. Simple."
56
+ email: rbotten@gmail.com
57
+ executables: []
58
+
59
+ extensions: []
60
+
61
+ extra_rdoc_files:
62
+ - LICENSE
63
+ - README.rdoc
64
+ files:
65
+ - .document
66
+ - .gitignore
67
+ - LICENSE
68
+ - README.rdoc
69
+ - Rakefile
70
+ - VERSION
71
+ - features/meteorology.feature
72
+ - features/step_definitions/meteorology_steps.rb
73
+ - features/support/env.rb
74
+ - fixtures/yr.no/locationforecast-1.6.xml
75
+ - lib/meteorology.rb
76
+ - lib/meteorology/providers.rb
77
+ - lib/meteorology/providers/yr.rb
78
+ - lib/meteorology/temperature.rb
79
+ - lib/meteorology/weather.rb
80
+ - lib/meteorology/weather_collection.rb
81
+ - spec/meteorology/providers/yr_spec.rb
82
+ - spec/meteorology/temperature_spec.rb
83
+ - spec/meteorology/weather_collection_spec.rb
84
+ - spec/meteorology/weather_spec.rb
85
+ - spec/meteorology_spec.rb
86
+ - spec/spec.opts
87
+ - spec/spec_helper.rb
88
+ has_rdoc: true
89
+ homepage: http://github.com/runeb/meteorology
90
+ licenses: []
91
+
92
+ post_install_message:
93
+ rdoc_options:
94
+ - --charset=UTF-8
95
+ require_paths:
96
+ - lib
97
+ required_ruby_version: !ruby/object:Gem::Requirement
98
+ requirements:
99
+ - - ">="
100
+ - !ruby/object:Gem::Version
101
+ version: "0"
102
+ version:
103
+ required_rubygems_version: !ruby/object:Gem::Requirement
104
+ requirements:
105
+ - - ">="
106
+ - !ruby/object:Gem::Version
107
+ version: "0"
108
+ version:
109
+ requirements: []
110
+
111
+ rubyforge_project:
112
+ rubygems_version: 1.3.5
113
+ signing_key:
114
+ specification_version: 3
115
+ summary: Come rain or shine, geocoded Ruby objects know it
116
+ test_files:
117
+ - spec/meteorology/providers/yr_spec.rb
118
+ - spec/meteorology/temperature_spec.rb
119
+ - spec/meteorology/weather_collection_spec.rb
120
+ - spec/meteorology/weather_spec.rb
121
+ - spec/meteorology_spec.rb
122
+ - spec/spec_helper.rb