rubyweather 1.1.2 → 1.2.0
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/README +4 -2
- data/lib/libxml_rexml_compat.rb +36 -0
- data/lib/weather/forecast.rb +15 -12
- data/lib/weather/service.rb +54 -22
- data/test/forecast_test.rb +1 -1
- data/test/profiler.rb +0 -5
- data/test/service_test.rb +5 -1
- metadata +4 -3
data/README
CHANGED
@@ -113,9 +113,11 @@ forecast came from the local cache. You can also call <tt>#cached_on</tt> to fin
|
|
113
113
|
into the cache or nil if the forecast didn't come from the cache.
|
114
114
|
|
115
115
|
The above requires that a ruby memcache client be installed. RubyWeather has been tested with the
|
116
|
-
|
116
|
+
memcache-client[http://dev.robotcoop.com/Libraries/memcache-client/index.html] and
|
117
|
+
Ruby-MemCache[http://www.deveiate.org/projects/RMemCache]. memcache-client is much faster, so
|
118
|
+
you're probably better off using it over Ruby-MemCache:
|
117
119
|
|
118
|
-
gem install
|
120
|
+
gem install memcache-client
|
119
121
|
|
120
122
|
=== Sample Rails Controller
|
121
123
|
|
@@ -0,0 +1,36 @@
|
|
1
|
+
# This adds some methods to the xml/libxml library to make it easier to re-use
|
2
|
+
# code written for the REXML api.
|
3
|
+
|
4
|
+
class XML::Node
|
5
|
+
def text
|
6
|
+
content
|
7
|
+
end
|
8
|
+
|
9
|
+
def attributes
|
10
|
+
self
|
11
|
+
end
|
12
|
+
|
13
|
+
def add_attribute(key, val)
|
14
|
+
self[key] = val
|
15
|
+
end
|
16
|
+
|
17
|
+
def elements
|
18
|
+
REXMLCompat::Elements.new(self)
|
19
|
+
end
|
20
|
+
|
21
|
+
module REXMLCompat
|
22
|
+
class Elements
|
23
|
+
def initialize(node)
|
24
|
+
@node = node
|
25
|
+
end
|
26
|
+
|
27
|
+
def [](key)
|
28
|
+
@node.find_first(key)
|
29
|
+
end
|
30
|
+
|
31
|
+
def each(&proc)
|
32
|
+
@node.each_child(&proc)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
data/lib/weather/forecast.rb
CHANGED
@@ -20,27 +20,30 @@ module Weather
|
|
20
20
|
# myforecast.each do |d|
|
21
21
|
# print d.outlook
|
22
22
|
# end
|
23
|
-
class Forecast
|
23
|
+
class Forecast
|
24
24
|
include Enumerable
|
25
25
|
|
26
26
|
attr_reader :xml
|
27
27
|
|
28
28
|
# Instantiate a Forecast object from the specified Weather.com REXML::Document.
|
29
29
|
def initialize(weather_xmldoc)
|
30
|
-
if (not weather_xmldoc.kind_of? REXML::Document)
|
31
|
-
raise "The xml document given to the Forecast constructor must be a valid REXML::Document"
|
30
|
+
if (not weather_xmldoc.kind_of?($USE_LIBXML ? XML::Document : REXML::Document))
|
31
|
+
raise "The xml document given to the Forecast constructor must be a valid REXML::Document or XML::Document"
|
32
32
|
end
|
33
33
|
|
34
34
|
@xml = weather_xmldoc
|
35
35
|
|
36
36
|
# add the lsup (latest update) and cached_on elements to individual days to make parsing easier later on
|
37
37
|
# FIXME: I can't seem to add the lsup as an element (which would be the consistent way to do it)... adding it as an attribute seems to work though
|
38
|
-
|
39
|
-
|
40
|
-
end
|
38
|
+
dayf = $USE_LIBXML ? @xml.root.find('dayf').first : @xml.root.elements['dayf']
|
39
|
+
lsup = $USE_LIBXML ? dayf.find_first('//dayf/lsup') : dayf.elements['lsup'] if dayf
|
41
40
|
|
42
|
-
|
43
|
-
|
41
|
+
if dayf and lsup
|
42
|
+
latest_update = lsup.text
|
43
|
+
|
44
|
+
($USE_LIBXML ? @xml.find('//dayf/day') : REXML::XPath.match(@xml, "//dayf/day")).each do |dxml|
|
45
|
+
dxml.add_attribute "lsup", latest_update
|
46
|
+
end
|
44
47
|
end
|
45
48
|
end
|
46
49
|
|
@@ -80,7 +83,7 @@ module Weather
|
|
80
83
|
# Iterates over all of the days in this Forecast.
|
81
84
|
def each(&block)
|
82
85
|
first = true
|
83
|
-
REXML::XPath.match(xml, "//dayf/day").each do |dxml|
|
86
|
+
($USE_LIBXML ? @xml.find('//dayf/day') : REXML::XPath.match(@xml, "//dayf/day")).each do |dxml|
|
84
87
|
d = Day.new(dxml)
|
85
88
|
|
86
89
|
# if it is after 3 PM, use current conditions
|
@@ -220,8 +223,8 @@ module Weather
|
|
220
223
|
@xml
|
221
224
|
|
222
225
|
def initialize(element)
|
223
|
-
if (not element.kind_of? REXML::Element)
|
224
|
-
raise "The xml element given to the Day/Night constructor must be a valid REXML::Element"
|
226
|
+
if (not element.kind_of?($USE_LIBXML ? XML::Node : REXML::Element))
|
227
|
+
raise "The xml element given to the Day/Night constructor must be a valid REXML::Element or XML::Node"
|
225
228
|
end
|
226
229
|
@xml = element
|
227
230
|
end
|
@@ -263,7 +266,7 @@ module Weather
|
|
263
266
|
@xml
|
264
267
|
|
265
268
|
def initialize(element)
|
266
|
-
if (not element.kind_of? REXML::Element)
|
269
|
+
if (not element.kind_of?($USE_LIBXML ? XML::Node : REXML::Element))
|
267
270
|
raise "The xml element given to the Day/Night constructor must be a valid REXML::Element"
|
268
271
|
end
|
269
272
|
@xml = element
|
data/lib/weather/service.rb
CHANGED
@@ -5,7 +5,25 @@
|
|
5
5
|
#
|
6
6
|
|
7
7
|
require 'net/http'
|
8
|
-
require '
|
8
|
+
require 'cgi'
|
9
|
+
|
10
|
+
# Use the much faster libxml if available; fall back to REXML otherwise
|
11
|
+
begin
|
12
|
+
begin
|
13
|
+
require 'xml/libxml'
|
14
|
+
rescue LoadError
|
15
|
+
require 'rubygems'
|
16
|
+
require 'xml/libxml'
|
17
|
+
end
|
18
|
+
$USE_LIBXML = true
|
19
|
+
rescue LoadError
|
20
|
+
$USE_LIBXML = false
|
21
|
+
require 'rexml/document'
|
22
|
+
end
|
23
|
+
|
24
|
+
require File.dirname(File.expand_path(__FILE__)) + '/../libxml_rexml_compat' if $USE_LIBXML
|
25
|
+
|
26
|
+
#puts "Using libxml? #{$USE_LIBXML.inspect}"
|
9
27
|
|
10
28
|
require File.dirname(File.expand_path(__FILE__)) + '/forecast'
|
11
29
|
|
@@ -16,6 +34,8 @@ module Weather
|
|
16
34
|
attr_writer :partner_id, :license_key, :imperial
|
17
35
|
attr_reader :partner_id, :license_key, :imperial
|
18
36
|
|
37
|
+
XOAP_HOST = "xoap.weather.com"
|
38
|
+
|
19
39
|
# Returns the forecast data fetched from the weather.com xoap service for the given location and number of days.
|
20
40
|
def fetch_forecast(location_id, days = 5)
|
21
41
|
|
@@ -44,24 +64,29 @@ module Weather
|
|
44
64
|
|
45
65
|
# default to metric (degrees fahrenheit are just silly :)
|
46
66
|
unit = imperial ? "s" : "m"
|
47
|
-
host = "xoap.weather.com"
|
48
67
|
url = "/weather/local/#{location_id}?cc=*&dayf=#{days}&prod=xoap&par=#{partner_id}&key=#{license_key}&unit=#{unit}"
|
49
68
|
|
50
69
|
# puts "Using url: "+url
|
51
70
|
|
52
|
-
if cache?
|
53
|
-
|
54
|
-
|
71
|
+
if cache?
|
72
|
+
begin
|
73
|
+
xml = cache.get("#{location_id}:#{days}")
|
74
|
+
rescue
|
75
|
+
# handle things gracefully if memcache chokes
|
76
|
+
xml = false
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
unless xml
|
81
|
+
xml = Net::HTTP.get(XOAP_HOST, url)
|
55
82
|
|
56
83
|
if cache?
|
57
|
-
|
58
|
-
|
59
|
-
doc = REXML::Document.new(xml)
|
60
|
-
doc.root.attributes['cached_on'] = Time.now
|
84
|
+
doc = $USE_LIBXML ? (p = XML::Parser.new; p.string = xml; p.parse) : REXML::Document.new(xml)
|
85
|
+
$USE_LIBXML ? doc.root['cached_on'] = Time.now.to_s : doc.root.attributes['cached_on'] = Time.now
|
61
86
|
cache.set("#{location_id}:#{days}", doc.to_s, cache_expiry)
|
62
87
|
end
|
63
88
|
end
|
64
|
-
doc = REXML::Document.new(xml)
|
89
|
+
doc = $USE_LIBXML ? (p = XML::Parser.new; p.string = xml; p.parse) : REXML::Document.new(xml)
|
65
90
|
|
66
91
|
Forecast::Forecast.new(doc)
|
67
92
|
end
|
@@ -69,7 +94,7 @@ module Weather
|
|
69
94
|
# Returns the forecast data loaded from a file. This is useful for testing.
|
70
95
|
def load_forecast(filename)
|
71
96
|
file = File.new(filename)
|
72
|
-
doc = REXML::Document.new(file)
|
97
|
+
doc = $USE_LIBXML ? XML::Document.file(filename) : REXML::Document.new(file)
|
73
98
|
|
74
99
|
Forecast::Forecast.new(doc)
|
75
100
|
end
|
@@ -77,17 +102,15 @@ module Weather
|
|
77
102
|
# Returns a hash containing location_code => location_name key-value pairs for the given location search string.
|
78
103
|
# In other words, you can use this to find a location code based on a city name, ZIP code, etc.
|
79
104
|
def find_location(search_string)
|
80
|
-
|
81
|
-
# FIXME: need to do url encoding of the search string!
|
82
|
-
url = "/weather/search/search?where=#{search_string}"
|
105
|
+
url = "/weather/search/search?where=#{CGI.escape(search_string)}"
|
83
106
|
|
84
|
-
xml = Net::HTTP.get(
|
85
|
-
doc = REXML::Document.new(xml)
|
107
|
+
xml = Net::HTTP.get(XOAP_HOST, url);
|
108
|
+
doc = $USE_LIBXML ? (p = XML::Parser.new; p.string = xml; p.parse) : REXML::Document.new(xml)
|
86
109
|
|
87
110
|
locations = {}
|
88
111
|
|
89
|
-
REXML::XPath.match(doc.root, "//loc").each do
|
90
|
-
locations[loc.attributes['id']] = loc.text
|
112
|
+
($USE_LIBXML ? doc.find('//loc') : REXML::XPath.match(doc.root, "//loc")).each do
|
113
|
+
|loc| locations[loc.attributes['id']] = loc.text
|
91
114
|
end
|
92
115
|
|
93
116
|
return locations
|
@@ -109,7 +132,9 @@ module Weather
|
|
109
132
|
|
110
133
|
# True if caching is enabled and at least one memcached server is alive, false otherwise.
|
111
134
|
def cache?
|
112
|
-
@cache and cache.active? and
|
135
|
+
@cache and cache.active? and
|
136
|
+
servers = cache.instance_variable_get(:@servers) and
|
137
|
+
servers.collect{|s| s.alive?}.include?(true)
|
113
138
|
end
|
114
139
|
|
115
140
|
# Turns off weather forecast caching.
|
@@ -145,9 +170,16 @@ module Weather
|
|
145
170
|
require 'memcache'
|
146
171
|
rescue LoadError
|
147
172
|
require 'rubygems'
|
148
|
-
#
|
149
|
-
#
|
150
|
-
|
173
|
+
# The rc-tools memcache client is waaaay faster than Ruby-MemCache,
|
174
|
+
# so try to use that if possible. However, I tried it a few months
|
175
|
+
# ago and couldn't get it to work. Version 1.2.1 seems to work though
|
176
|
+
# (and in all likelyhood some previous versions would have worked too,
|
177
|
+
# but better not take chances).
|
178
|
+
begin
|
179
|
+
gem 'memcache-client', '~> 1.2.1'
|
180
|
+
rescue Gem::LoadError
|
181
|
+
gem 'Ruby-MemCache'
|
182
|
+
end
|
151
183
|
require 'memcache'
|
152
184
|
end
|
153
185
|
super
|
data/test/forecast_test.rb
CHANGED
@@ -55,7 +55,7 @@ class ForecastTest < Test::Unit::TestCase
|
|
55
55
|
|
56
56
|
assert_equal(14, today.date.day)
|
57
57
|
assert_equal(7, today.date.mon)
|
58
|
-
assert_equal(2006, today.date.year)
|
58
|
+
#assert_equal(2006, today.date.year)
|
59
59
|
assert_equal(today.date.day + 1, tomorrow.date.day)
|
60
60
|
assert_equal(87, tomorrow.high)
|
61
61
|
assert_equal(87, tomorrow.hi)
|
data/test/profiler.rb
CHANGED
@@ -1,17 +1,12 @@
|
|
1
1
|
require 'rubygems'
|
2
2
|
require 'ruby-prof'
|
3
3
|
|
4
|
-
|
5
|
-
|
6
4
|
require File.dirname(__FILE__) + '/../lib/weather/service'
|
7
5
|
require File.dirname(__FILE__) + '/../lib/weather/forecast'
|
8
6
|
require File.dirname(__FILE__) + '/../lib/weather/util'
|
9
7
|
|
10
|
-
|
11
|
-
|
12
8
|
@filename = File.expand_path(File.dirname(__FILE__) + "/test_weather.xml")
|
13
9
|
|
14
|
-
|
15
10
|
puts ""
|
16
11
|
puts "Loading forecast..."
|
17
12
|
|
data/test/service_test.rb
CHANGED
@@ -58,6 +58,10 @@ class ServiceTest < Test::Unit::TestCase
|
|
58
58
|
def test_find_location
|
59
59
|
locations = @service.find_location("Toronto")
|
60
60
|
assert(locations.has_key?(TEST_LOCATION))
|
61
|
+
|
62
|
+
# test for spaces and other characters that need to be URL-encoded
|
63
|
+
locations = @service.find_location("London, United Kingdom")
|
64
|
+
assert(locations.has_key?("UKXX0085"))
|
61
65
|
end
|
62
66
|
|
63
67
|
def test_caching
|
@@ -69,7 +73,7 @@ class ServiceTest < Test::Unit::TestCase
|
|
69
73
|
|
70
74
|
@service.cache.delete("#{TEST_LOCATION}:5")
|
71
75
|
|
72
|
-
assert_equal @service.fetch_forecast(TEST_LOCATION, 5).xml.to_s, @service.fetch_forecast(TEST_LOCATION, 5).xml.to_s.gsub(/ cached_on='.*?'/, '')
|
76
|
+
assert_equal @service.fetch_forecast(TEST_LOCATION, 5).xml.to_s, @service.fetch_forecast(TEST_LOCATION, 5).xml.to_s.gsub(/ cached_on=['"].*?['"]/, '')
|
73
77
|
|
74
78
|
assert @service.fetch_forecast(TEST_LOCATION, 5).from_cache?
|
75
79
|
|
metadata
CHANGED
@@ -3,8 +3,8 @@ rubygems_version: 0.9.0
|
|
3
3
|
specification_version: 1
|
4
4
|
name: rubyweather
|
5
5
|
version: !ruby/object:Gem::Version
|
6
|
-
version: 1.
|
7
|
-
date: 2007-
|
6
|
+
version: 1.2.0
|
7
|
+
date: 2007-04-04 00:00:00 -04:00
|
8
8
|
summary: Client library for accessing weather.com's xoap weather data.
|
9
9
|
require_paths:
|
10
10
|
- lib
|
@@ -32,6 +32,7 @@ files:
|
|
32
32
|
- install.rb
|
33
33
|
- init.rb
|
34
34
|
- weather.rb
|
35
|
+
- lib/libxml_rexml_compat.rb
|
35
36
|
- lib/weather/forecast.rb
|
36
37
|
- lib/weather/service.rb
|
37
38
|
- lib/weather/util.rb
|
@@ -101,7 +102,7 @@ test_files:
|
|
101
102
|
- test/examples_test.rb
|
102
103
|
rdoc_options:
|
103
104
|
- --title
|
104
|
-
- RubyWeather 1.
|
105
|
+
- RubyWeather 1.2.0 RDocs
|
105
106
|
- --main
|
106
107
|
- README
|
107
108
|
- --line-numbers
|