Write a Ruby program such that given a certain argument to the program it
will return the current temperature of that location. People living in
the United States may be interested in temperature by ZIP code:
I used Yahoo Weather's RSS feed. net/http and simple-rss did most of
the work for me.
But Yahoo keeps some interesting data in the attributes of some custom
tags, like <yweather:condition temp=3D"99"... \>. SimpleRSS wasn't
returning the attribute values (I'm not even sure if Yahoo's method is
compliant RSS). So I extended SimpleRSS to give me the values I want.
Then I added an basic RSSFeeder class which puts Net fetch and RSS
parse together, and adds caching, so that you can run it continuously
without hammering the server. The script takes a zipcode and an
optional -f to get today's forecast, too
-Adam
#------ weatherman.rb -------------
require 'net/http'
require 'simple-rss'
class Object
def metaclass; class << self; self; end; end
end #thanks, _why
#Extends Simple RSS to add tag attributes as methods to the tag object
# given <sometag var=3D"2">hello</sometag>,
# allows item.sometag =3D=3D> hello
# and item.sometag.var =3D=3D> 2
class SimpleRSSwAttributes < SimpleRSS
def clean_content(tag, attrs, content)
s=3D super
while n=3D (attrs =3D~ /((\w*)=3D"([^"]*)" )/mi)
attr_name =3D clean_tag($2)
s.metaclass.send

attr_reader, attr_name)
s.instance_variable_set("@#{attr_name}",unescape($3))
attrs.slice!(n,$1.length)
end
s
end
def method_missing meth
nil
end
end
#Simple RSS feed reader.
# takes url, array of custom tags, and optional filename for caching result=
s
# provides #each_item and #item(title) methods
class RSSFeeder
def initialize feed_url, extra_tags=3D[], cache=3Dnil
raise 'Invalid URL' unless feed_url =3D~ /(.*\w*\.\w*\.\w*)(\/.*)/ =20
#separate host, rest
@url,@feed =3D $1, $2
@cache =3D cache
extra_tags.each{|tag| SimpleRSSwAttributes.feed_tags << tag}
end
#tyields [item,channel] for item with title matching name
def item name, &block
fetch
[email protected]{|item| item.title =3D~ name} if @data
yield [i,@data.channel] if i
end
def each_item &block
fetch
@data.items.each{|item| yield item}
end
private
def time_to_fetch?
@timestamp.nil? || (@timestamp < Time.now)
end
def fetch
#read the cache if we don't have data
if !@data && @cache
File.open(@cache, "r") {|f|
@timestamp =3D Time.parse(f.gets)
@data =3D SimpleRSSwAttributes.parse(f)
} if File.exists?(@cache)
end
#only fetch data from net if current data is expired
time_to_fetch? ? net_fetch : @data
end
def net_fetch
text =3D Net::HTTP.start(@url).get(@feed).body
@data =3D SimpleRSSwAttributes.parse(text)
#try to create a reasonable expiration date. Defaults to 10 mins in fut=
ure
date =3D @data.lastBuildDate || @data.pubDate ||
@data.expirationDate || Time.now
@timestamp =3D date + (@data.ttl ? @data.ttl.to_i*60 : 600)
@timestamp =3D Time.now + 600 if @timestamp < Time.now
File.open(@cache, "w+"){|f|
f.puts @timestamp;
f.write text
} if @cache
end
end
if __FILE__=3D=3D$0
exit(-1+puts("Usage #{$0} zipcode [-f]\nGives current temperature
for zipcode, "+
"-f to get forecast too").to_i) if ARGV.size < 1
zipcode =3D ARGV[0]
yahoo_tags =3D %w(yweather:condition yweather:location yweather:forecast=
)
w =3D RSSFeeder.new("xml.weather.yahoo.com/forecastrss?p=3D#{zipcode}",
yahoo_tags, "yahoo#{zipcode}.xml")
w.item(/Conditions/) { |item,chan|
puts "The #{item.title} are:\n\t#{chan.yweather_condition.temp}F and "+
"#{chan.yweather_condition.text}"
}
w.item(/Conditions/) { |item,chan|
puts "\nThe forecast for #{chan.yweather_location.city}, "+
"#{chan.yweather_location.region} for #{chan.yweather_forecast.day}, "+
"#{chan.yweather_forecast.date} is:\n"+
"\t#{chan.yweather_forecast.text} with a high of
#{chan.yweather_forecast.high} "+
"and a low of #{chan.yweather_forecast.low}"
} if ARGV[1]=3D~/f/i
#catch errors
w.item(/not found/) { |item,chan| puts item.description }
#Alternate feed
#w2 =3D RSSFeeder.new("rss.weather.com/weather/rss/local/#{zipcode}?cm_ve=
n=3DLWO&cm_cat=3Drss&par=3DLWO_rss")
#w2.item(/Current Weather/){|item,rss|
# puts item.title,item.description.gsub(/°/,248.chr)}
#w2.item(/10-Day Forecast/){|item,rss|
# puts item.title,item.description.gsub(/°/,248.chr)} if ARGV[1]=3D=
~/f/i
end