[QUIZ] Current Temperature (#68)

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.

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(' ')))

--
Ross Bamford - rosco@roscopeco.REMOVE.co.uk

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."

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

···

<--- On Feb 24, Ruby Quiz wrote --->

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.

#
# $ 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 = "Local Weather Forecast, News and Conditions | Weather Underground;
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

--
Aditya Mahajan, EECS Systems, University of Michigan
http://www.eecs.umich.edu/~adityam || Ph: 7342624008

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}"

···

--
Posted via http://www.ruby-forum.com/.

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\}&quot;
    @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

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(/&deg;/,248.chr)}
  #w2.item(/10-Day Forecast/){|item,rss|
  # puts item.title,item.description.gsub(/&deg;/,248.chr)} if ARGV[1]=~/f/i

end

···

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:

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

I've been lurking for a bit over 2 months as I've been reading
here and there about Ruby.

Anyway my first quiz submission (which is also my second Ruby script):

require 'net/http'
require 'rexml/document'

zip = ARGV[0].to_s

if zip.length > 0 then
   h = Net::HTTP.new('rss.weather.com', 80)
   resp, data = h.get('/weather/rss/local/'+zip, nil)

   doc = REXML::Document.new data
   doc.elements.each('rss/channel/item/title') { |element|
     if element.text[0,7] == 'Current' then
       puts element.text
       desc = element.get_elements('../description').to_s.strip
       puts desc.slice(22,desc.length-57).sub('&deg;', 'degrees')
     end
     }
else
   puts 'Need a ZIP code as a command line parameter.'
end

Hello,

I just want you to know my solution. Nothing special, but sharing is always good :slight_smile:

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(/&deg;/, ' degrees ')
rescue
  puts("Go and check your other geek devices!")
end && puts("The temperatur in #{name} is #{temp}.")

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+)&deg;F/.match(Net::HTTP.get(
URI.parse("temperature - Google Search)))[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>

···

On 2/24/06, Ruby Quiz <james@grayproductions.net> wrote:

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.

--
Patrick Chanezon, AdWords API evangelist
http://blog.chanezon.com/

hahaha, thats great. Very entertaining Dave. Sometimes I wish I was
that funny :slight_smile:

···

On 2/26/06, Dave Burt <dave@burt.id.au> wrote:

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."

--
http://PhpGirl.blogger.com

This step could prove very difficult for some programmers. :wink:

James Edward Gray II

···

On Feb 26, 2006, at 9:18 AM, Dave Burt wrote:

# Calibrate instrumentation
begin
    say "Go outside."
end until agree("Are you outside now? ")

Very cool :smiley:

···

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.

--
Ross Bamford - rosco@roscopeco.REMOVE.co.uk

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 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.

As Ryan said, excellent job! Keep at it.

--Steve

···

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.

Dave Burt wrote:

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."

Amazing, Dave. A boon to hackers everywhere.

But what is this "Outside" you speak of? I don't think I have
that installed...

Hal

Let me be one of the first to welcome you then! Script looks great for the second on, by the way.

James Edward Gray II

···

On Feb 27, 2006, at 10:46 AM, Harley Pebley wrote:

I've been lurking for a bit over 2 months as I've been reading
here and there about Ruby.

Anyway my first quiz submission (which is also my second Ruby script):

Dave Burt wrote:

if agree("Are your Ruby device or your umbrella wet? ")

Appears there is a bug here...

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

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>([^&]*)&deg;(.)<\/B>/, '#$1 degrees #$2 '],
    [/<B CLASS=obsTextA>Feels Like<BR> ([^&]*)&deg;(.)<\/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