X-Git-Url: https://www.yuggoth.org/gitweb?p=weather.git;a=blobdiff_plain;f=weather.py;h=20c9d5cbd2081662ce2fb9e1c1b0f0c74e2893a8;hp=3ea1f544b61af12020a18f3dd7a228b7d53fbabf;hb=8349654b7c627448b1e56c2943234895724d7857;hpb=eb249a0ae2f1644a67d6231b7cddf292058080e7 diff --git a/weather.py b/weather.py index 3ea1f54..20c9d5c 100644 --- a/weather.py +++ b/weather.py @@ -1,206 +1,323 @@ -# weather.py version 1.3, http://fungi.yuggoth.org/weather/ -# Copyright (c) 2006 Jeremy Stanley , all rights reserved. -# Licensed per terms in the LICENSE file distributed with this software. +# weather.py version 1.4, http://fungi.yuggoth.org/weather/ +# Copyright (c) 2006-2008 Jeremy Stanley . +# Permission to use, copy, modify, and distribute this software is +# granted under terms provided in the LICENSE file distributed with +# this software. """Contains various object definitions needed by the weather utility.""" -version = "1.3" +version = "1.4" class Selections: - """An object to contain selection data.""" - def __init__(self): - """Store the config, options and arguments.""" - self.config = get_config() - self.options, self.arguments = get_options(self.config) - if self.arguments: - self.arguments = [(x.lower()) for x in self.arguments] - else: self.arguments = [ None ] - def get(self, option, argument=None): - """Retrieve data from the config or options.""" - if not argument: return self.options.__dict__[option] - elif not self.config.has_section(argument): - import sys - sys.stderr.write("ERROR: no alias defined for " \ - + argument + "\n") - sys.exit(1) - elif self.config.has_option(argument, option): - return self.config.get(argument, option) - else: return self.options.__dict__[option] - def get_bool(self, option, argument=None): - """Get data and coerce to a boolean if necessary.""" - return bool(self.get(option, argument)) + """An object to contain selection data.""" + def __init__(self): + """Store the config, options and arguments.""" + self.config = get_config() + self.options, self.arguments = get_options(self.config) + if self.arguments: + self.arguments = [(x.lower()) for x in self.arguments] + else: self.arguments = [ None ] + def get(self, option, argument=None): + """Retrieve data from the config or options.""" + if not argument: return self.options.__dict__[option] + elif not self.config.has_section(argument): + import sys + sys.stderr.write("weather: error: no alias defined for " \ + + argument + "\n") + sys.exit(1) + elif self.config.has_option(argument, option): + return self.config.get(argument, option) + else: return self.options.__dict__[option] + def get_bool(self, option, argument=None): + """Get data and coerce to a boolean if necessary.""" + return bool(self.get(option, argument)) def bool(data): - """Coerce data to a boolean value.""" - if type(data) is str: - if eval(data): return True - else: return False - else: - if data: return True - else: return False + """Coerce data to a boolean value.""" + if type(data) is str: + if eval(data): return True + else: return False + else: + if data: return True + else: return False def quote(words): - """Wrap a string in quotes if it contains spaces.""" - if words.find(" ") != -1: words = "\"" + words + "\"" - return words + """Wrap a string in quotes if it contains spaces.""" + if words.find(" ") != -1: words = "\"" + words + "\"" + return words def sorted(data): - """Return a sorted copy of a list.""" - new_copy = data[:] - new_copy.sort() - return new_copy + """Return a sorted copy of a list.""" + new_copy = data[:] + new_copy.sort() + return new_copy def get_url(url): - """Return a string containing the results of a URL GET.""" - import urllib - return urllib.urlopen(url).read() - -def get_metar(id, verbose=False): - """Return a summarized METAR for the specified station.""" - metar = get_url( - "http://weather.noaa.gov/pub/data/observations/metar/decoded/" \ - + id.upper() + ".TXT") - if verbose: return metar - else: - lines = metar.split("\n") - headings = [ - "Relative Humidity", - "Precipitation last hour", - "Sky conditions", - "Temperature", - "Weather", - "Wind" - ] - output = [] - output.append("Current conditions at " \ - + lines[0].split(", ")[1] + " (" \ - + id.upper() +")") - output.append("Last updated " + lines[1]) - for line in lines: - for heading in headings: - if line.startswith(heading + ":"): - if line.endswith(":0"): - line = line[:-2] - output.append(" " + line) - return "\n".join(output) - -def get_forecast(city, st, verbose=False): - """Return the forecast for a specified city/st combination.""" - forecast = get_url("http://weather.noaa.gov/pub/data/forecasts/city/" \ - + st.lower() + "/" + city.lower().replace(" ", "_") \ - + ".txt") - if verbose: return forecast - else: - lines = forecast.split("\n") - output = [] - output.append(lines[2]) - output.append(lines[3]) - for line in lines: - if line.startswith("."): - output.append(line.replace(".", " ", 1)) - return "\n".join(output) + """Return a string containing the results of a URL GET.""" + import urllib2 + try: return urllib2.urlopen(url).read() + except urllib2.URLError: + import sys, traceback + sys.stderr.write("weather: error: failed to retrieve\n " \ + + url + "\n " + \ + traceback.format_exception_only(sys.exc_type, sys.exc_value)[0]) + sys.exit(1) + +def get_metar(id, verbose=False, quiet=False, headers=None, murl=None): + """Return a summarized METAR for the specified station.""" + if not id: + import sys + sys.stderr.write("weather: error: id required for conditions\n") + sys.exit(1) + if not murl: + murl = \ + "http://weather.noaa.gov/pub/data/observations/metar/decoded/%ID%.TXT" + murl = murl.replace("%ID%", id.upper()) + murl = murl.replace("%Id%", id.capitalize()) + murl = murl.replace("%iD%", id) + murl = murl.replace("%id%", id.lower()) + murl = murl.replace(" ", "_") + metar = get_url(murl) + if verbose: return metar + else: + lines = metar.split("\n") + if not headers: + headers = \ + "relative_humidity," \ + + "precipitation_last_hour," \ + + "sky conditions," \ + + "temperature," \ + + "weather," \ + + "wind" + headerlist = headers.lower().replace("_"," ").split(",") + output = [] + if not quiet: + output.append("Current conditions at " \ + + lines[0].split(", ")[1] + " (" \ + + id.upper() +")") + output.append("Last updated " + lines[1]) + for header in headerlist: + for line in lines: + if line.lower().startswith(header + ":"): + if line.endswith(":0"): + line = line[:-2] + if quiet: output.append(line) + else: output.append(" " + line) + return "\n".join(output) + +def get_forecast(city, st, verbose=False, quiet=False, flines="0", furl=None): + """Return the forecast for a specified city/st combination.""" + if not city or not st: + import sys + sys.stderr.write("weather: error: city and st required for forecast\n") + sys.exit(1) + if not furl: + furl = "http://weather.noaa.gov/pub/data/forecasts/city/%st%/%city%.txt" + furl = furl.replace("%CITY%", city.upper()) + furl = furl.replace("%City%", city.capitalize()) + furl = furl.replace("%citY%", city) + furl = furl.replace("%city%", city.lower()) + furl = furl.replace("%ST%", st.upper()) + furl = furl.replace("%St%", st.capitalize()) + furl = furl.replace("%sT%", st) + furl = furl.replace("%st%", st.lower()) + furl = furl.replace(" ", "_") + forecast = get_url(furl) + if verbose: return forecast + else: + lines = forecast.split("\n") + output = [] + if not quiet: output += lines[2:4] + flines = int(flines) + if not flines: flines = len(lines) - 5 + for line in lines[5:flines+5]: + if line.startswith("."): + if quiet: output.append(line.replace(".", "", 1)) + else: output.append(line.replace(".", " ", 1)) + return "\n".join(output) def get_options(config): - """Parse the options passed on the command line.""" - import optparse - usage = "usage: %prog [ options ] [ alias [ alias [...] ] ]" - verstring = "%prog " + version - option_parser = optparse.OptionParser(usage=usage, version=verstring) - if config.has_option("default", "city"): - default_city = config.get("default", "city") - else: default_city = "Raleigh Durham" - option_parser.add_option("-c", "--city", - dest="city", - default=default_city, - help="the city name (ex: \"Raleigh Durham\")") - if config.has_option("default", "forecast"): - default_forecast = bool(config.get("default", "forecast")) - else: default_forecast = False - option_parser.add_option("-f", "--forecast", - dest="forecast", - action="store_true", - default=default_forecast, - help="include a local forecast") - if config.has_option("default", "id"): - default_id = config.get("default", "id") - else: default_id = "KRDU" - option_parser.add_option("-i", "--id", - dest="id", - default=default_id, - help="the METAR station ID (ex: KRDU)") - option_parser.add_option("-l", "--list", - dest="list", - action="store_true", - default=False, - help="print a list of configured aliases") - if config.has_option("default", "conditions"): - default_conditions = bool(config.get("default", "conditions")) - else: default_conditions = True - option_parser.add_option("-n", "--no-conditions", - dest="conditions", - action="store_false", - default=default_conditions, - help="disable output of current conditions (forces -f)") - option_parser.add_option("-o", "--omit-forecast", - dest="forecast", - action="store_false", - default=default_forecast, - help="omit the local forecast (cancels -f)") - if config.has_option("default", "st"): - default_st = config.get("default", "st") - else: default_st = "NC" - option_parser.add_option("-s", "--st", - dest="st", - default=default_st, - help="the state abbreviation (ex: NC)") - if config.has_option("default", "verbose"): - default_verbose = bool(config.get("default", "verbose")) - else: default_verbose = False - option_parser.add_option("-v", "--verbose", - dest="verbose", - action="store_true", - default=default_verbose, - help="show full decoded feeds") - options, arguments = option_parser.parse_args() - return options, arguments + """Parse the options passed on the command line.""" + + # for optparse's builtin -h/--help option + usage = "usage: %prog [ options ] [ alias [ alias [...] ] ]" + + # for optparse's builtin --version option + verstring = "%prog " + version + + # create the parser + import optparse + option_parser = optparse.OptionParser(usage=usage, version=verstring) + + # the -c/--city option + if config.has_option("default", "city"): + default_city = config.get("default", "city") + else: default_city = "" + option_parser.add_option("-c", "--city", + dest="city", + default=default_city, + help="the city name (ex: \"Raleigh Durham\")") + + # the --flines option + if config.has_option("default", "flines"): + default_flines = config.get("default", "flines") + else: default_flines = "0" + option_parser.add_option("--flines", + dest="flines", + default=default_flines, + help="maximum number of forecast lines to show") + + # the -f/--forecast option + if config.has_option("default", "forecast"): + default_forecast = bool(config.get("default", "forecast")) + else: default_forecast = False + option_parser.add_option("-f", "--forecast", + dest="forecast", + action="store_true", + default=default_forecast, + help="include a local forecast") + + # the --furl option + if config.has_option("default", "furl"): + default_furl = config.get("default", "furl") + else: + default_furl = \ + "http://weather.noaa.gov/pub/data/forecasts/city/%st%/%city%.txt" + option_parser.add_option("--furl", + dest="furl", + default=default_furl, + help="forecast URL (including %city% and %st%)") + + # the --headers option + if config.has_option("default", "headers"): + default_headers = config.get("default", "headers") + else: + default_headers = \ + "temperature," \ + + "relative_humidity," \ + + "wind," \ + + "weather," \ + + "sky_conditions," \ + + "precipitation_last_hour" + option_parser.add_option("--headers", + dest="headers", + default=default_headers, + help="the conditions headers to display") + + # the -i/--id option + if config.has_option("default", "id"): + default_id = config.get("default", "id") + else: default_id = "" + option_parser.add_option("-i", "--id", + dest="id", + default=default_id, + help="the METAR station ID (ex: KRDU)") + + # the -l/--list option + option_parser.add_option("-l", "--list", + dest="list", + action="store_true", + default=False, + help="print a list of configured aliases") + + # the --murl option + if config.has_option("default", "murl"): + default_murl = config.get("default", "murl") + else: + default_murl = \ + "http://weather.noaa.gov/pub/data/observations/metar/decoded/%ID%.TXT" + option_parser.add_option("--murl", + dest="murl", + default=default_murl, + help="METAR URL (including %id%)") + + # the -n/--no-conditions option + if config.has_option("default", "conditions"): + default_conditions = bool(config.get("default", "conditions")) + else: default_conditions = True + option_parser.add_option("-n", "--no-conditions", + dest="conditions", + action="store_false", + default=default_conditions, + help="disable output of current conditions (forces -f)") + + # the -o/--omit-forecast option + option_parser.add_option("-o", "--omit-forecast", + dest="forecast", + action="store_false", + default=default_forecast, + help="omit the local forecast (cancels -f)") + + # the -q/--quiet option + if config.has_option("default", "quiet"): + default_quiet = bool(config.get("default", "quiet")) + else: default_quiet = False + option_parser.add_option("-q", "--quiet", + dest="quiet", + action="store_true", + default=default_quiet, + help="skip preambles and don't indent") + + # the -s/--st option + if config.has_option("default", "st"): + default_st = config.get("default", "st") + else: default_st = "" + option_parser.add_option("-s", "--st", + dest="st", + default=default_st, + help="the state abbreviation (ex: NC)") + + # the -v/--verbose option + if config.has_option("default", "verbose"): + default_verbose = bool(config.get("default", "verbose")) + else: default_verbose = False + option_parser.add_option("-v", "--verbose", + dest="verbose", + action="store_true", + default=default_verbose, + help="show full decoded feeds (cancels -q)") + + # separate options object from list of arguments and return both + options, arguments = option_parser.parse_args() + return options, arguments def get_config(): - """Parse the aliases and configuration.""" - import ConfigParser - config = ConfigParser.ConfigParser() - import os.path - rcfiles = [ - "/etc/weatherrc", - os.path.expanduser("~/.weatherrc"), - "weatherrc" - ] - import os - for rcfile in rcfiles: - if os.access(rcfile, os.R_OK): config.read(rcfile) - for section in config.sections(): - if section != section.lower(): - if config.has_section(section.lower()): - config.remove_section(section.lower()) - config.add_section(section.lower()) - for option,value in config.items(section): - config.set(section.lower(), option, value) - return config + """Parse the aliases and configuration.""" + import ConfigParser + config = ConfigParser.ConfigParser() + import os.path + rcfiles = [ + "/etc/weatherrc", + os.path.expanduser("~/.weatherrc"), + "weatherrc" + ] + import os + for rcfile in rcfiles: + if os.access(rcfile, os.R_OK): config.read(rcfile) + for section in config.sections(): + if section != section.lower(): + if config.has_section(section.lower()): + config.remove_section(section.lower()) + config.add_section(section.lower()) + for option,value in config.items(section): + config.set(section.lower(), option, value) + return config def list_aliases(config): - """Return a formatted list of aliases defined in the config.""" - sections = [] - for section in sorted(config.sections()): - if section.lower() not in sections and section != "default": - sections.append(section.lower()) - output = "configured aliases..." - for section in sorted(sections): - output += "\n " \ - + section \ - + ": --id=" \ - + quote(config.get(section, "id")) \ - + " --city=" \ - + quote(config.get(section, "city")) \ - + " --st=" \ - + quote(config.get(section, "st")) - return output + """Return a formatted list of aliases defined in the config.""" + sections = [] + for section in sorted(config.sections()): + if section.lower() not in sections and section != "default": + sections.append(section.lower()) + output = "configured aliases..." + for section in sorted(sections): + output += "\n " \ + + section \ + + ": --id=" \ + + quote(config.get(section, "id")) \ + + " --city=" \ + + quote(config.get(section, "city")) \ + + " --st=" \ + + quote(config.get(section, "st")) + return output