The three rules of Ruby Quiz: 1. Please do not post any solutions or spoiler discussion for this quiz until 48 hours have passed from the time on this message. 2. Support Ruby Quiz by submitting ideas as often as you can: http://www.rubyquiz.com/ 3. Enjoy! Suggestion: A [QUIZ] in the subject of emails about the problem helps everyone on Ruby Talk follow the discussion. -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= by Caleb Tennis 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: $ ruby current_temp.rb 47201 The temperature in Columbus, Indiana is 32 degrees F. Other locales may want to use their own mailing codes, or city names: $ ruby current_temp.rb madrid The temperature in Madrid, Spain is 12 degrees C. Which arguments you support is up to you.
on 24.02.2006 15:15
on 26.02.2006 16:16
I wanted to allow UK postcodes, and I've had some mad insomnia the past couple of nights. This is some of the nastiest code I've written in a long time, which is pretty liberating if I'm honest... ====[CUT HERE]==== #!/usr/local/bin/ruby # Run a query for temperature based on place name or UK Postcode. # # Uses the BBC weather service (data from the MET office). # See: http://www.bbc.co.uk/weather/ # require 'net/http' require 'uri' unless $0 == __FILE__ raise LoadError, "You don't wanna require this..." end if ARGV.detect { |e| e =~ /--?h(elp)?/ } puts <<-EOM Syntax: ruby weather.rb [-h] [-f] [-x] [-a[select-ids]] search query Options: -h Show this help text. -f Display temperatures in degrees Farenheit (default: Celsius) -x Show eXtended report. -a[ids] Automatically select [ids] where a search returns multiple results. Avoids user input at runtime. Examples: -a - Show temperature for all results -a1 - Show the first result -a'1 3' - Show results 1 and 3 Search Query: The search query is constructed from all non-option arguments, and may be one of: * UK postcode (partial or full) * UK town * UK or International city * Country Examples: ruby weather.rb -f ilkeston - Temp in farenheit for Ilkeston, UK ruby weather.rb -a76 italy - Celsius temp in Rome, Italy ruby weather.rb -a3 de7 - Celsius in Derby, UK ruby weather.rb london - Temp in interactively-selected result for query 'london' ruby weather.rb -f -x -a new york - Extended report in Farenheit for all 'new york' results EOM exit(1) end RESULT_TITLE = /5 Day Forecast in (\w+) for ([^<]+)<\/title>/ MULTI_RESULT_TITLE = /Weather Centre - Search Results<\/title>/ NO_LOCS = /No locations were found for "([^"]*)"/ FIVEDAY = /5day.shtml/ # Extract result from multiple result page EX_RESULT = /<a href="\/weather\/5day(?:_f)?.shtml\?([^"]*)" class="seasonlink"><strong>([^<]*)(?:<\/strong>)?<\/a>/ # Extract from 5day result page EX_OVERVIEW = /">(\w+)<\/span>\s*\d+<abbr title="Temperature/ EX_TEMP = /(\d+)\s*\<abbr title="Temperature in degrees[^"]*"\>/ EX_WIND = /<br \/>(\w+) \((\d+) <abbr title="Miles per/ EX_HUMIDITY = /title="Relative humid[^:]*: (\d+)/ EX_PRESSURE = /title="Pressure in[^:]*: ([^<]+)/ EX_VISIBILITY = /Visibility<\/strong>: ([^<]+)/ # validate input SELECT_INPUT = /^([Aa]|\d+(\s*\d+)*)$/ FARENHEIT = if ARGV.include? '-f' ARGV.reject! { |e| e == '-f' } true end AUTOSELECT = if ARGV.detect(&asp = lambda { |e| e =~ /-a([Aa]|\d+(?:\s*\d+)*)?/ }) a = $1 || 'A' ARGV.reject!(&asp) a end EXTMODE = if ARGV.include? '-x' ARGV.reject! { |e| e == '-x' } true end # Fetch and process a single URI (either search, results or 5day) def fetch_process(uri) case r = fetch(uri) when Net::HTTPSuccess process_result(r.body) else r.error! end end # Actually fetches data from the web. All results ultimately come from # 5day pages (new_search.pl redirects us there). We handle redirects # here and also do URL rewriting to support Farenheit mode. def fetch(uri_str, limit = 10) raise ArgumentError, 'HTTP redirect too deep' if limit == 0 if FARENHEIT and uri_str =~ FIVEDAY uri_str = uri_str.dup uri_str[FIVEDAY] = '5day_f.shtml' end response = Net::HTTP.get_response(URI.parse(uri_str)) case response when Net::HTTPSuccess then response when Net::HTTPRedirection then fetch(response['location'], limit - 1) else response.error! end end # Collects multiple results from a "Search Results" page into an # array of arrays e.g [["Some Place", "id=3309"], ["Etc", "id=2002"]] def collect_results(body) a = [] body.scan(EX_RESULT) { |s| a << [$2, $1] } a end # The main result processing function. This handles all responses. # If it's given a single result (a 5day page) it extracts and outputs # the current temp. If it's a multi result page, the results are # extracted and the user selects from them, with the resulting URL # (a 5day) then passed to fetch_process to handle the fetch and pass # the result back here. def process_result(body) if body =~ RESULT_TITLE # this is a result units, place = $1, $2 if body =~ EX_TEMP temp = $1 out = if EXTMODE overview = ((m = EX_OVERVIEW.match(body)) ? m[1] : '?') wind_dir, wind_speed = ((m = EX_WIND.match(body)) ? m[1,2] : ['?','?']) humidity = ((m = EX_HUMIDITY.match(body)) ? m[1] : '?') pressure = ((m = EX_PRESSURE.match(body)) ? m[1] : '?') visibility = ((m = EX_VISIBILITY.match(body)) ? m[1] : '?') "\n#{place}\n" + " Temp : #{temp} degrees #{units}\n" + " Wind : #{wind_dir} (#{wind_speed} mph)\n" + " Humidity (%) : #{humidity}\n" + " Pressure (mB): #{pressure.chop}\n" + " Visibility : #{visibility}" else "#{place} - #{temp} degrees #{units}" end puts out else puts "No data for #{place}" end elsif body =~ MULTI_RESULT_TITLE # multiple or no result if body =~ NO_LOCS puts "No locations matched '#{$1}'" else a = collect_results(body) if a.length > 0 unless n = AUTOSELECT puts "Multiple results:\n" puts " [0]\tCancel" a.each_with_index do |e,i| puts " [#{i+1}]\t#{e.first}" end puts " [A]\tAll\n\n" begin print "Select (separate with spaces): " n = STDIN.gets.chomp end until n =~ SELECT_INPUT end if n != '0' # 0 is cancel n.split(' ').inject([]) do |ary,i| if i.upcase == 'A' ary + a.map { |e| e.last } else ary << a[i.to_i - 1].last end end.each do |id| fetch_process("http://www.bbc.co.uk/weather/5day.shtml?#{id}") end end else puts "No usable results found" end end else puts "Unknown location" end end def display_temp(q) fetch_process("http://www.bbc.co.uk/cgi-perl/weather/search/new_search.pl?search_query=#{q}") end display_temp(URI.encode(ARGV.empty? ? 'ilkeston' : ARGV.join(' ')))
on 26.02.2006 16:19
Ruby Quiz wrote: > Write a Ruby program such that given a certain argument to the program it > will return the current temperature of that location. I still haven't done last week's metakoans, but I thought to myself, "Dave," (because I always address myself by name in my thoughts). "Dave," I thought, "a universal Ruby-based indicator of the current temperature that works in any location on any Ruby platform would be a great boon not only to you, but to the entire Ruby community. In fact, why stop at the temperature? Ruby has the power to turn almost any device into a fully functional weather station, measuring rain, wind and snow. The world will be amazed." So, thinking of you all, I wrote the code I now humbly present. Cheers, Dave. #!/usr/bin/ruby # # Current Weather # # A response to Ruby Quiz #68 [ruby-talk:181420] # # This script basically turns your Ruby device into a weather machine. It # leverages the latest technology to enable most laptops, PDAs, etc. to capture # meterorological metrics. # # WARNING: this program has a bug resulting in an infinite loop on non-portable # platforms. # # Please ONLY EXECUTE THIS PROGRAM ON PORTABLE DEVICES. # # Author: Dave Burt <dave at burt.id.au> # # Created: 23 Oct 2005 # require 'highline/import' # Work around bug agree("Are you using a portable Ruby device? ") or abort("Sorry, this program has not yet been ported to your platform.") # Calibrate instrumentation begin say "Go outside." end until agree("Are you outside now? ") # Ascertain cloud cover if agree("Is your Ruby device casting a defined shadow? ") say "It's sunny." else say "It's overcast." end # Capture rainfall if agree("Are your Ruby device or your umbrella wet? ") say "It's raining." else say "It's fine." end # Weigh other precipitation if agree("Is your Ruby device becoming white? ") say "It's snowing." else say "It's not snowing." end # Discern current temperature if agree("Are your fingers getting cold? ") say "It's cold." else say "It's warm." end # Measure wind speed if agree("Do you feel lateral forces on your Ruby device? ") say "It's windy." else say "It's calm." end say "This weather report has been brought to you by Ruby, the letter D," say "and the number 42."
on 26.02.2006 16:40
hahaha, thats great. Very entertaining Dave. Sometimes I wish I was that funny :)
on 26.02.2006 17:39
On Feb 26, 2006, at 9:18 AM, Dave Burt wrote: > # Calibrate instrumentation > begin > say "Go outside." > end until agree("Are you outside now? ") This step could prove very difficult for some programmers. ;) James Edward Gray II
on 26.02.2006 18:13
On Mon, 2006-02-27 at 00:18 +0900, Dave Burt wrote:
> So, thinking of you all, I wrote the code I now humbly present.
Very cool :D
on 26.02.2006 18:29
<--- On Feb 24, Ruby Quiz wrote ---> > > $ ruby current_temp.rb madrid > The temperature in Madrid, Spain is 12 degrees C. > > Which arguments you support is up to you. This quiz finally got me going to read Ruby's Net API. It turned out to be very pleasing. Here is my solution. I use the wunderground website to get the weather. The program supports all kinds of search arguments that are supported by wundergroud. I pass on the input arguments to wu website. If a city is found, the search results contain a link to rss feed. Instead of parsing the html document, I get this rss feed and parse it. This method fails sometimes, for small cities outside US. Here is the code # Examples: # $ current_temp.rb 48105 # > The temperature in Ann Arbor, MI is 34 degrees F / 1 degrees C # # $ current_temp.rb Ann Arbor, MI # > The temperature in Ann Arbor, MI is 34 degrees F / 1 degrees C # # $ current_temp DTW # > The temperature in Detroit Metro Wayne County, MI is 36 degrees F / 2 degrees C # # $ current_temp New Delhi, India # > The temperature in New Delhi, India is 77 degrees F / 25 degrees C # # $ current_temp DEL # > The temperature in New Delhi, India is 77 degrees F / 25 degrees C # #--------------------------------------%<-------------------------------------- require 'net/http' require 'uri' require 'rexml/document' if ARGV.length == 0 puts "Usage: ruby current_temp.rb [city, state | zipcode | city, country | airport code]" exit end urlbase = "http://www.wunderground.com/cgi-bin/findweather/getForecast?query=" zipcode = ARGV.join('%20') # Search for the zipcode on wunderground website response = Net::HTTP.get_response URI.parse(urlbase << zipcode) # Parse the result for the link to a rss feed rss_feed = String.new # Get the line with rss feed response.body.each do |line| if line.include?("application/rss+xml") then stop_pos = line.rindex('"') - 1 start_pos = line.rindex('"',stop_pos) + 1 rss_feed = line.slice(start_pos..stop_pos) break end end # Get the feed and parse it for city and weather information # The response is different for US cities and places outside US. # Use appropritate regular expression to parse both simultaneously if rss_feed == "" then puts ARGV.join(' ') << ": No such city" else feed = Net::HTTP.get_response(URI.parse(rss_feed)) document = REXML::Document.new feed.body title = document.elements.to_a("//title")[0].text channel = document.elements.to_a("//channel/item/description")[0].text city = title.gsub(/\s*(Weather from)?\s*Weather Underground\s*(-)?\s*/,"") temp = channel.gsub(/(^Temperature:|\|.*$|\W)/,"") temp = temp.gsub("F", " degrees F / ").gsub("C", " degrees C") # For exact format as asked in the quiz, uncomment the following # temp = temp.gsub("F.*$", "F") puts "The temperature in #{city} is #{temp}" end #--------------------------------------%<-------------------------------------- Thanks for the nice quiz. Aditya
on 26.02.2006 18:36
Here is mine. It only provides temperatures for US zip codes. I've
been doing some HTML scraping like this lately for some utilities of
my own, so this was pretty easy (i.e. the techniques were fresh in my
mind.) Though for my other utilities I've been using WWW:Mechanize and
in this case I decided to go a little lower level.
One problem with this or any other HTML scraping solution, is minor
changes to the HTML can totally break things.
Beware of wrapping, especially on the "parse_list":
require 'open-uri'
if $0 == __FILE__
if ARGV.length < 1
puts "Usage: #$0 <zip code>"
exit(1)
end
parse_list = [[/<B>Local Forecast for (.* \(\d{5}\))<\/B>/, 'Local
temperature for #$1: '],
[/<B CLASS=obsTempTextA>([^&]*)°(.)<\/B>/, '#$1 degrees #$2 '],
[/<B CLASS=obsTextA>Feels Like<BR> ([^&]*)°(.)<\/B>/, '[It
feels like #$1 degrees #$2]']
]
# Blessed be the internet, the great provider of information
open('http://beta.weather.com/weather/local/'+ARGV[0]) do |io|
html = io.read
parse_list.each do |p|
# We don't need no steenkin' HTML parser
if html =~ p[0]
print eval(%Q{"#{p[1]}"})
end
end
puts
end
end
Ryan
on 26.02.2006 18:51
My solution uses yahoo weather like another of the solutions here. It's
easy to use with US zip codes, but for international cities you have to
know the yahoo weather location id. I looked around for a big list of
these but couldn't find it. Anyways it would be nice to have some sort
of searching mechanism to turn a city name into a location id.
Also I used rexml to parse the rss feed. I tried to use the rss library,
but couldn't figure out how to pull out the 'yweather' tags.
Anyways, fun quiz. I enjoyed it a lot.
-----Jay Anderson
require 'rexml/document'
require 'open-uri'
#Returns a hash containing the location and temperature information
#Accepts US zip codes or Yahoo location id's
def yahoo_weather_query(loc_id, units)
h = {}
open("http://xml.weather.yahoo.com/forecastrss?p=#{loc_id}&u=#{units}")
do |http|
response = http.read
doc = REXML::Document.new(response)
root = doc.root
channel = root.elements['channel']
location = channel.elements['yweather:location']
h[:city] = location.attributes["city"]
h[:region] = location.attributes["region"]
h[:country] = location.attributes["country"]
h[:temp] =
channel.elements["item"].elements["yweather:condition"].attributes["temp"]
end
h
end
if ARGV.length < 1 then
puts "usage: #$0 <location> [f|c]"
exit
end
loc_id = ARGV[0]
units = (ARGV[1] || 'f').downcase
units = (units =~ /^(f|c)$/) ? units : 'f'
#An improvement would be to allow searches for the yahoo location id
#loc_id = yahoo_loc_search(loc_id)
weather_info = yahoo_weather_query(loc_id, units)
city = weather_info[:city]
region = weather_info[:region]
country = weather_info[:country]
temp = weather_info[:temp]
puts "The temperature in #{city}, #{region}, #{country} is #{temp}
degrees #{units.upcase}"
on 26.02.2006 18:54
> 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: This is my first submission to rubyquiz. I've been learning ruby for about 6 months now, and it's my first foray into programming. I'd love some feedback. Thanks, # current_temp.rb require 'net/http' require 'rexml/document' require 'optparse' class CurrentTemp include REXML def initialize(loc,u='f') uri = "http://xml.weather.yahoo.com/forecastrss?p=#{loc}&u=#{u}" @doc = Document.new Net::HTTP.get(URI.parse(uri)) raise "Invalid city, #{loc}" if /error/i =~ @doc.elements["//description"].to_s end def method_missing(methodname) XPath.match(@doc,"//*[starts-with(name(), 'yweather')]").each do|elem| return elem.attributes[methodname.to_s] if elem.attributes[methodname.to_s] end Object.method_missing(methodname) end def unit self.temperature end def state self.region end def to_s "The current temperature in #{self.city}, #{self.state} is #{self.temp} degrees #{self.unit}." end end opts = OptionParser.new opts.banner = "Usage:\n\n current_temp.rb city [-u unit]\n\n " opts.banner += "city should be a zip code, or a Yahoo Weather location id.\n\n" opts.on("-uARG", "--unit ARG","Should be f or c", String) {|val| @u = val } opts.on("-h", "--help") {puts opts.to_s ; exit 0} loc = opts.parse! @u ||='f' begin puts CurrentTemp.new(loc,@u) rescue puts $! puts opts.to_s exit 1 end
on 26.02.2006 19:40
On 2/26/06, gordon <gthiesfeld@sbcglobal.net> wrote: > > This is my first submission to rubyquiz. I've been learning ruby for > about 6 months now, and it's my first foray into programming. I'd love > some feedback. I think this is pretty darn slick Gordon, and you should continue in programming. My only suggestion would be that you check for an empty argument list explicitly so that someone will a slow internet connection does not have to wait for the request to go to Yahoo to get an error. Ryan
on 26.02.2006 19:58
On Feb 26, 2006, at 9:53 AM, gordon wrote: > I've been learning ruby for > about 6 months now, and it's my first foray into programming. I'd > love > some feedback. As Ryan said, excellent job! Keep at it. --Steve
on 26.02.2006 20:34
This is also my first Ruby Quiz submission. I was going to neglect this one as well, but I got to thinking how nicely lazy.rb might work with some web services. Once someone actually came across a working SOAP service (xmethods lists quite a few that are iffy), I decided to give it a go. Thanks =) Jeff #!/usr/bin/env ruby require 'lazy' require 'soap/wsdlDriver' require 'rexml/document' $-w = nil $wsdl_loc = "http://www.webservicex.net/globalweather.asmx?WSDL" class WeatherState def initialize(city, country) stub = SOAP::WSDLDriverFactory.new($wsdl_loc).create_rpc_driver @keep_me = promise do conditions = stub.getWeather(:CityName =>city, :CountryName=>country) data = REXML::Document.new(conditions.getWeatherResult.gsub(/< \?.*?>\n/, '')) { :temp => data.elements["//Temperature"].text, loc => data.elements["//Location"].text } end end def temp demand(@keep_me)[:temp] end def loc demand(@keep_me)[:loc] end end if ARGV.length != 2 abort("Usage: weather.rb city country") end # Create Weather Object weatherProxy = WeatherState.new(ARGV[0], ARGV[1]) puts "Location: " + weatherProxy.loc puts "Current Temp: " + weatherProxy.temp.strip
on 26.02.2006 21:23
On 2/24/06, Ruby Quiz <james@grayproductions.net> wrote: > 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="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="2">hello</sometag>, # allows item.sometag ==> hello # and item.sometag.var ==> 2 class SimpleRSSwAttributes < SimpleRSS def clean_content(tag, attrs, content) s= super while n= (attrs =~ /((\w*)="([^"]*)" )/mi) attr_name = 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 results # provides #each_item and #item(title) methods class RSSFeeder def initialize feed_url, extra_tags=[], cache=nil raise 'Invalid URL' unless feed_url =~ /(.*\w*\.\w*\.\w*)(\/.*)/ #separate host, rest @url,@feed = $1, $2 @cache = cache extra_tags.each{|tag| SimpleRSSwAttributes.feed_tags << tag} end #tyields [item,channel] for item with title matching name def item name, &block fetch i=@data.items.find{|item| item.title =~ 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 = Time.parse(f.gets) @data = 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 = Net::HTTP.start(@url).get(@feed).body @data = SimpleRSSwAttributes.parse(text) #try to create a reasonable expiration date. Defaults to 10 mins in future date = @data.lastBuildDate || @data.pubDate || @data.expirationDate || Time.now @timestamp = date + (@data.ttl ? @data.ttl.to_i*60 : 600) @timestamp = Time.now + 600 if @timestamp < Time.now File.open(@cache, "w+"){|f| f.puts @timestamp; f.write text } if @cache end end if __FILE__==$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 = ARGV[0] yahoo_tags = %w(yweather:condition yweather:location yweather:forecast) w = RSSFeeder.new("xml.weather.yahoo.com/forecastrss?p=#{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]=~/f/i #catch errors w.item(/not found/) { |item,chan| puts item.description } #Alternate feed #w2 = RSSFeeder.new("rss.weather.com/weather/rss/local/#{zipcode}?cm_ven=LWO&cm_cat=rss&par=LWO_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]=~/f/i end
on 27.02.2006 00:19
Dave Burt wrote: > to the entire Ruby community. In fact, why stop at the temperature? Ruby has > the power to turn almost any device into a fully functional weather station, > measuring rain, wind and snow. The world will be amazed." Amazing, Dave. A boon to hackers everywhere. But what is this "Outside" you speak of? I don't think I have that installed... Hal
on 27.02.2006 00:40
Hal Fulton <hal9000@hypermetrics.com> writes: > But what is this "Outside" you speak of? I don't think I have > that installed... You need to "require 'pants'" first.
on 27.02.2006 14:20
George Ogata <g_ogata@optushome.com.au> writes: > Hal Fulton <hal9000@hypermetrics.com> writes: > >> But what is this "Outside" you speak of? I don't think I have >> that installed... > > You need to "require 'pants'" first. Alternatively, require 'rubypants' ;-)
on 27.02.2006 18:10
Dave Burt wrote:
> if agree("Are your Ruby device or your umbrella wet? ")
Appears there is a bug here...
on 27.02.2006 19:21
Hello,
I just want you to know my solution. Nothing special, but sharing is
always good :)
I'm looking forward at the summary.
Bye.
-
require 'net/http'
require 'uri'
class Website
def self.get(url)
uri = URI.parse(url)
begin
res = Net::HTTP.start(uri.host, uri.port) do |http|
http.get(uri.request_uri)
end
body = res.body
rescue
raise "Error: Failed to fetch page!"
end
return body
end
end
if ARGV.first =~ /^[0-9]{5}$/
content =
Website.get("http://www.weather.com/weather/local/#{ARGV.first}")
name = content.scan(/<br>([^>]+) \(#{ARGV.first}\)/i).first.first
else
precontent =
Website.get("http://www.weather.com/search/enhanced?what=WeatherLocalUndeclared&lswe=#{ARGV.join('+')}&lswa=WeatherLocalUndeclared&search=search&from=whatwhere&where=#{ARGV.join('+')}&whatprefs=&x=0&y=0")
url, name = precontent.scan(%r#<b>1. <a
href="/([^"]+)">([^<>]+)</a></b>#i).first
content = Website.get("http://www.weather.com/#{url}")
end
begin
temp = content.scan(%r#<b
class="?obsTempTextA"?>([^<>]+)</b>#i).first.first.sub(/°/, '
degrees ')
rescue
puts("Go and check your other geek devices!")
end && puts("The temperatur in #{name} is #{temp}.")
on 27.02.2006 22:59
Jay,
I liked your idea of searching for the location id, so I added it to my
original submission. If you use the -s option, it will give you a menu
of choices. If it only finds one choice it will just return the
temperature.
Examples:
c:\>ruby c:\current_temp.rb madrid -s
1. Madrid, Nebraska, United States
2. Madrid, New York, United States
3. Madrid, Iowa, United States
4. Madrid, Spain
5. General La Madrid, Argentina
6. New Madrid, Missouri, United States
Please choose your location 4
The current temperature in Madrid, is 37 degrees F.
c:\>ruby current_temp.rb "madrid, spain" -s
The current temperature in Madrid, is 37 degrees F.
# current_temp.rb
require 'net/http'
require 'rexml/document'
require 'optparse'
require "rubygems"
require "highline/import"
require 'cgi'
class LocationSearch
attr_reader :loc
def initialize(string)
city = CGI.escape(string)
h = Net::HTTP.new('weather.yahoo.com', 80)
resp, data = h.get("/search/weather2?p=#{city}", nil)
case resp
when Net::HTTPSuccess then @loc = location_menu(
parse_locations(data) )
when Net::HTTPRedirection then @loc =
get_location(resp['location'])
end
end
def location_menu(hash)
choose do |menu|
menu.prompt = "Please choose your location "
hash.each do |key,val|
menu.choice val do return key end
end
end
end
def parse_locations(data)
a = {}
data.split("\n").each do |i|
a[get_location(i)]=strip_html(i) if /a href="\/forecast/ =~ i
end
a
end
def strip_html(str)
str = str.strip || ''
str.gsub(/<(\/|\s)*[^>]*>/,'')
end
def get_location(string)
string.split(/\/|\./)[2]
end
end
class CurrentTemp
include REXML
def initialize(loc,u='f')
uri = "http://xml.weather.yahoo.com/forecastrss?p=#{loc}&u=#{u}"
@doc = Document.new Net::HTTP.get(URI.parse(uri))
raise "Invalid city, \"#{loc}\"" if /error/i =~
@doc.elements["//description"].to_s
end
def method_missing(methodname)
XPath.match(@doc,"//*[starts-with(name(), 'yweather')]").each
do|elem|
return elem.attributes[methodname.to_s] if
elem.attributes[methodname.to_s]
end
Object.method_missing(methodname)
end
def unit
self.temperature
end
def state
self.region
end
def to_s
"The current temperature in #{self.city}, #{self.state} is
#{self.temp} degrees #{self.unit}."
end
end
begin
opts = OptionParser.new
opts.banner = "Usage:\n\n current_temp.rb city [-u unit]\n\n
"
opts.banner += "city should be a zip code, or a Yahoo Weather
location id.\n\n"
opts.on("-uARG", "--unit ARG","Should be f or c", String) {|val| @u
= val }
opts.on("-s", "--search","Search location") {@search = true}
opts.on("-h", "--help") {puts opts.to_s ; exit 0}
loc = opts.parse!.to_s
@u ||='f'
if @search
loc = LocationSearch.new(loc).loc
end
if loc.empty?
raise "Invalid city, \"#{loc}\""
else
puts
puts CurrentTemp.new(loc,@u)
end
rescue
puts $!
puts opts.to_s
exit 1
end
on 28.02.2006 07:10
Cool. I'd tried to do something like that, but couldn't quite get it to
work (because of the yahoo redirect). Looking at your code helped me
figure it out. I looked at net/http and then more at what open-uri
provides. Lo and behold it provides some meta-data that helps with the
redirect problem. Anyways thanks gordon! I added my now fixed function
below.
-----Jay Anderson
require 'rexml/document'
require 'open-uri'
LOC_MATCH = /\/forecast\/([^.]+)\.html/
#Searches Yahoo and returns an array of location ids
def yahoo_loc_search(loc)
return [loc] if loc =~ /\d/ #places usually don't have numbers in
their names
locs = []
open("http://weather.yahoo.com/search/weather2?p=#{URI.escape(loc)}")
do |http|
return [$1] if http.base_uri.to_s =~ LOC_MATCH
http.each {|line| locs << $1 if line =~ LOC_MATCH }
end
locs
end
#Returns a hash containing the location and temperature information
#Accepts US zip codes or Yahoo location id's
def yahoo_weather_query(loc_ids, units)
weather = []
loc_ids.each do |l|
h = {}
open("http://xml.weather.yahoo.com/forecastrss?p=#{l}&u=#{units}") do
|http|
response = http.read
doc = REXML::Document.new(response)
channel = doc.root.elements['channel']
title = channel.elements['title'].text
if title !~ /Error/ then
location = channel.elements['yweather:location']
h[:city] = location.attributes["city"]
h[:region] = location.attributes["region"]
h[:country] = location.attributes["country"]
h[:temp] =
channel.elements["item"].elements["yweather:condition"].attributes["temp"]
weather << h
end
end
end
weather
end
if ARGV.length < 1 then
puts "usage: #$0 <location> [f|c]"
exit
end
loc_id = ARGV[0]
units = (ARGV[1] || 'f').downcase
units = (units =~ /^(f|c)$/) ? units : 'f'
loc_ids = yahoo_loc_search(loc_id)
weather_info = yahoo_weather_query(loc_ids, units)
puts "No matches found" if weather_info.size == 0
weather_info.each do |w|
city = w[:city]
region = w[:region]
country = w[:country]
temp = w[:temp]
final_loc = "#{city}, #{region}#{', ' if region!="" and
country!=""}#{country}"
puts "The temperature in #{final_loc} is #{temp} degrees
#{units.upcase}"
end
on 01.03.2006 03:18
An answer just for the fun of it, showing that you write as obscure and
unmaintainable code in Ruby as in Perl. The challenge was to get all the
functionality in a one liner, in order to see how far you can stretch
ruby
expressions. They seem to stretch pretty well:-)
It uses Google, so it supports only zip codes as input.
require 'net/http'
puts ((ARGV.length != 1) ? "Usage: #$0 <zip code>" : (["The temperature
in"] + (/Weather<\/b> for
<b>(.*)<\/b>.*\D(\d+)°F/.match(Net::HTTP.get(
URI.parse("http://www.google.com/search?hl=en&q=temperature+#{ARGV[0]}")))[1,2].collect!
{|x| " is " + x})).to_s.gsub!(/in is /, "in ") + " degree F")
./temp.rb 94117
The temperature in San Francisco, CA is 57 degree F
./temp.rb
Usage: ./temp.rb <zip code>