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 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
- Ruby-MemCache[http://www.deveiate.org/projects/RMemCache] implementation which you can install using:
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 Ruby-MemCache
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
@@ -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
- if @xml.root.elements['dayf'] and @xml.root.elements['dayf'].elements['lsup']
39
- lsup = @xml.root.elements['dayf'].elements['lsup'].text
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
- REXML::XPath.match(@xml, "//dayf/day").each do |dxml|
43
- dxml.add_attribute "lsup", lsup
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
@@ -5,7 +5,25 @@
5
5
  #
6
6
 
7
7
  require 'net/http'
8
- require 'rexml/document'
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? and xml = cache.get("#{location_id}:#{days}")
53
- else
54
- xml = Net::HTTP.get(host, url)
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
- # TODO: most of the processing time seems to be in parsing xml text to REXML::Document...
58
- # maybe we should try caching some other serialized form? YAML?
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
- host = "xoap.weather.com"
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(host, url);
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 |loc|
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 not cache.servers.dup.delete_if{|s| !s.alive?}.empty?
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
- # We use Ruby-MemCache because it works. Despite my best efforts, I
149
- # couldn't get the memcache-client implementation working properly.
150
- require_gem 'Ruby-MemCache'
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
@@ -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.1.2
7
- date: 2007-01-11 00:00:00 -05:00
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.1.2 RDocs
105
+ - RubyWeather 1.2.0 RDocs
105
106
  - --main
106
107
  - README
107
108
  - --line-numbers