Imported from archive.
* Release 1.4. * (all): Updated the copyright years for 2008 on some of the files in the current release and added a copyright statement to any files previously lacking one. * LICENSE: Replaced the previous BSD-like license with the one used by the OpenBSD project (modeled after the Internet Software Consortium's, a two-clause BSD license removing language made unnecessary by the Berne convention); this new license is functionally identical to the old one, just more terse and openly recognized. * weather: Clarified function parameters in calls from the wrapper script to ease future ABI changes in the underlying module. * weather, weather.py: Some extra comments were added to the source, indentation style was updated from tab characters to three-space, and lines longer than 79 columns were refactored or otherwise split. * weather.1, weather.5, weather.py: Added an flines option to allow the maximum number of forecast output lines to be shortened. Added furl and murl options to allow overriding of the default current conditions and forecast data retrieval URLs. Added a headers option to allow overriding the default list of header names for current conditions data filtering. Added a quiet option to suppress the preamble lines and indentation for both current conditions and forecast output. * weather.py: Replaced the hardcoded fallback default METAR station ID and forecast city/state abbreviation with error messages to minimize confusion when necessary values are omitted. Adjusted a couple of hard-coded error message strings to be consistent with the output format of the option_parser module. Switched from urllib to urllib2 for retrieving data, providing a simpler means to detect and report retrieval errors. Upped the version to 1.4.
This commit is contained in:
25
FAQ
25
FAQ
@@ -1,7 +1,9 @@
|
|||||||
FREQUENTLY ASKED QUESTIONS ABOUT THE WEATHER UTILITY
|
FREQUENTLY ASKED QUESTIONS ABOUT THE WEATHER UTILITY
|
||||||
|
|
||||||
Copyright (c) 2006 Jeremy Stanley <fungi@yuggoth.org>, all rights reserved.
|
Copyright (c) 2006-2008 Jeremy Stanley <fungi@yuggoth.org>.
|
||||||
Licensed per terms in the LICENSE file distributed with this software.
|
Permission to use, copy, modify, and distribute this software is
|
||||||
|
granted under terms provided in the LICENSE file distributed with
|
||||||
|
this software.
|
||||||
|
|
||||||
|
|
||||||
Table of Contents:
|
Table of Contents:
|
||||||
@@ -44,8 +46,10 @@ state abbreviation to get to a list of cities in that state).
|
|||||||
4. I live outside the USA--can this be made to work for me
|
4. I live outside the USA--can this be made to work for me
|
||||||
anyway?
|
anyway?
|
||||||
|
|
||||||
If you have any recommendations for similar forecast data in
|
METAR station IDs can be found for cities and airports worldwide,
|
||||||
other countries, I will be happy to try and find a way to
|
but forecast data is harder to come by. If you have any
|
||||||
|
recommendations of forecast data for other countries available in a
|
||||||
|
format like NOAA's, I will be happy to try and find a way to
|
||||||
integrate it into the weather utility, but I suspect that some
|
integrate it into the weather utility, but I suspect that some
|
||||||
serious modification would be necessary given that the data is
|
serious modification would be necessary given that the data is
|
||||||
likely to be published in a non-English language, requiring some
|
likely to be published in a non-English language, requiring some
|
||||||
@@ -59,10 +63,9 @@ The -i or --id switch (or the id parameter in an alias definition),
|
|||||||
only tells weather(1) what current conditions to retrieve. If you
|
only tells weather(1) what current conditions to retrieve. If you
|
||||||
specify -f or --forecast on the command line (or forecast=True in
|
specify -f or --forecast on the command line (or forecast=True in
|
||||||
an alias) without providing a city name and state abbreviation
|
an alias) without providing a city name and state abbreviation
|
||||||
(-c/--city and -s/--st, or city and st in an alias), you will
|
(-c/--city and -s/--st, or city and st in an alias) and are seeing
|
||||||
instead see the forecast for the built-in default location (or the
|
an actual forecast, then you probably have a default city and state
|
||||||
city and st defined in the default alias, if you have one). See
|
abbreviation set in your config. See question 3 above for
|
||||||
question 3 above for information on figuring out what city name and
|
information on figuring out what city name and state abbreviation
|
||||||
state abbreviation to use, and the manual for weatherrc(5) for
|
to use, and the manual for weatherrc(5) for information on defining
|
||||||
information on defining aliases.
|
aliases.
|
||||||
|
|
||||||
|
|||||||
7
INSTALL
7
INSTALL
@@ -1,7 +1,9 @@
|
|||||||
BASIC UNIX INSTALLATION INSTRUCTIONS FOR THE WEATHER UTILITY
|
BASIC UNIX INSTALLATION INSTRUCTIONS FOR THE WEATHER UTILITY
|
||||||
|
|
||||||
Copyright (c) 2006 Jeremy Stanley <fungi@yuggoth.org>, all rights reserved.
|
Copyright (c) 2006-2008 Jeremy Stanley <fungi@yuggoth.org>.
|
||||||
Licensed per terms in the LICENSE file distributed with this software.
|
Permission to use, copy, modify, and distribute this software is
|
||||||
|
granted under terms provided in the LICENSE file distributed with
|
||||||
|
this software.
|
||||||
|
|
||||||
|
|
||||||
PREREQUISITES
|
PREREQUISITES
|
||||||
@@ -55,4 +57,3 @@ MANUALS
|
|||||||
Optionally, the weather.1 and weatherrc.5 files can be placed in
|
Optionally, the weather.1 and weatherrc.5 files can be placed in
|
||||||
sane locations for TROFF/NROFF manual files on your system (for
|
sane locations for TROFF/NROFF manual files on your system (for
|
||||||
example, /usr/local/share/man/ or ~/man/).
|
example, /usr/local/share/man/ or ~/man/).
|
||||||
|
|
||||||
|
|||||||
31
LICENSE
31
LICENSE
@@ -1,22 +1,13 @@
|
|||||||
Copyright (c) 2006 Jeremy Stanley <fungi@yuggoth.org>, all rights reserved.
|
Copyright (c) 2006-2008 Jeremy Stanley <fungi@yuggoth.org>
|
||||||
|
|
||||||
Redistribution and use in source and binary forms, with or without
|
Permission to use, copy, modify, and distribute this software for any
|
||||||
modification, are permitted provided that the following conditions are met:
|
purpose with or without fee is hereby granted, provided that the above
|
||||||
|
copyright notice and this permission notice appear in all copies.
|
||||||
- Redistributions of source code must retain the above copyright notice,
|
|
||||||
this list of conditions and the following disclaimer.
|
|
||||||
- Redistributions in binary form must reproduce the above copyright notice,
|
|
||||||
this list of conditions and the following disclaimer in the documentation
|
|
||||||
and/or other materials provided with the distribution.
|
|
||||||
|
|
||||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
|
||||||
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
|
||||||
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
||||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE
|
|
||||||
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
||||||
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
|
||||||
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
|
||||||
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
|
||||||
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
||||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||||
|
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||||
|
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||||
|
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||||
|
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||||
|
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||||
|
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||||
|
|||||||
35
README
35
README
@@ -1,7 +1,9 @@
|
|||||||
GENERAL INFORMATION ABOUT THE WEATHER UTILITY
|
GENERAL INFORMATION ABOUT THE WEATHER UTILITY
|
||||||
|
|
||||||
Copyright (c) 2006 Jeremy Stanley <fungi@yuggoth.org>, all rights reserved.
|
Copyright (c) 2006-2008 Jeremy Stanley <fungi@yuggoth.org>.
|
||||||
Licensed per terms in the LICENSE file distributed with this software.
|
Permission to use, copy, modify, and distribute this software is
|
||||||
|
granted under terms provided in the LICENSE file distributed with
|
||||||
|
this software.
|
||||||
|
|
||||||
|
|
||||||
WHAT?
|
WHAT?
|
||||||
@@ -10,24 +12,24 @@ This command-line utility is intended to provide quick access to
|
|||||||
current weather conditions and forecasts. Presently, it is
|
current weather conditions and forecasts. Presently, it is
|
||||||
capable of returning data for localities throughout the USA by
|
capable of returning data for localities throughout the USA by
|
||||||
retrieving and formatting decoded METARs (Meteorological
|
retrieving and formatting decoded METARs (Meteorological
|
||||||
Aerodrome Reports) from NOAA (the National Oceanic and
|
Aerodrome Reports) from NOAA (the USA National Oceanic and
|
||||||
Atmospheric Administration) and forecasts from the NWS (National
|
Atmospheric Administration) and forecasts from NWS (the USA
|
||||||
Weather Service). The tool is written to function in the same
|
National Weather Service). The tool is written to function in the
|
||||||
spirit as other command-line informational utilities like cal(1),
|
same spirit as other command-line informational utilities like
|
||||||
calendar(1) and dict(1). It can retrieve arbitrary weather data
|
cal(1), calendar(1) and dict(1). It can retrieve arbitrary weather
|
||||||
via specific command-line switches (station ID, city, state), or
|
data via specific command-line switches (station ID, city, state),
|
||||||
aliases can be configured system wide and on a per-user basis. It
|
or aliases can be configured system wide and on a per-user basis.
|
||||||
can be freely used and redistributed under the terms of the BSD
|
It can be freely used and redistributed under the terms of a
|
||||||
License.
|
BSD-like License.
|
||||||
|
|
||||||
|
|
||||||
WHY?
|
WHY?
|
||||||
|
|
||||||
My girlfriend has a long commute to/from work and school, and
|
My girlfriend had a long commute to/from work and school, and
|
||||||
often wants to check the weather both for home and her office.
|
often wanted to check the weather both for home and her office.
|
||||||
Unfortunately, starting a Web browser, pulling up a weather site,
|
Unfortunately, starting a Web browser, pulling up a weather site,
|
||||||
entering multiple ZIP codes and waiting for them to load is
|
entering multiple ZIP codes and waiting for them to load is
|
||||||
time-consuming for the marginally-impatient. Since she tends to
|
time-consuming for the marginally-impatient. Since she tended to
|
||||||
stay logged into a shell server most of the time, I figured I'd
|
stay logged into a shell server most of the time, I figured I'd
|
||||||
install a quick command-line tool to retrieve weather info for
|
install a quick command-line tool to retrieve weather info for
|
||||||
her commute, but to my surprise, a quick search turned up little
|
her commute, but to my surprise, a quick search turned up little
|
||||||
@@ -47,6 +49,5 @@ be had here:
|
|||||||
|
|
||||||
http://fungi.yuggoth.org/weather/src/
|
http://fungi.yuggoth.org/weather/src/
|
||||||
|
|
||||||
Alternatively, Debian Etch (testing) and Sid (unstable) users can
|
Alternatively, Debian users can install the weather-util package
|
||||||
install the weather-util package from any mirror.
|
from any mirror.
|
||||||
|
|
||||||
|
|||||||
45
weather
45
weather
@@ -1,16 +1,18 @@
|
|||||||
#!/usr/bin/env python
|
#!/usr/bin/env python
|
||||||
|
|
||||||
# weather version 1.3, http://fungi.yuggoth.org/weather/
|
# weather version 1.4, http://fungi.yuggoth.org/weather/
|
||||||
# Copyright (c) 2006 Jeremy Stanley <fungi@yuggoth.org>, all rights reserved.
|
# Copyright (c) 2006-2008 Jeremy Stanley <fungi@yuggoth.org>.
|
||||||
# Licensed per terms in the LICENSE file distributed with this software.
|
# Permission to use, copy, modify, and distribute this software is
|
||||||
|
# granted under terms provided in the LICENSE file distributed with
|
||||||
|
# this software.
|
||||||
|
|
||||||
"""Wrapper utility using the weather.py module."""
|
"""Wrapper utility using the weather.py module."""
|
||||||
|
|
||||||
# added so distributors can consistently specify a private module location
|
# added so distributors can consistently specify a private module location
|
||||||
private_module_path = None
|
private_module_path = None
|
||||||
if private_module_path:
|
if private_module_path:
|
||||||
import sys
|
import sys
|
||||||
sys.path.insert(1, private_module_path)
|
sys.path.insert(1, private_module_path)
|
||||||
|
|
||||||
import weather
|
import weather
|
||||||
|
|
||||||
@@ -24,17 +26,22 @@ if get_bool("list"): print weather.list_aliases(selections.config)
|
|||||||
|
|
||||||
# normal operation
|
# normal operation
|
||||||
else:
|
else:
|
||||||
for argument in selections.arguments:
|
for argument in selections.arguments:
|
||||||
if get_bool("conditions", argument):
|
if get_bool("conditions", argument):
|
||||||
print weather.get_metar(
|
print weather.get_metar(
|
||||||
get("id", argument),
|
id=get("id", argument),
|
||||||
get_bool("verbose", argument)
|
verbose=get_bool("verbose", argument),
|
||||||
)
|
quiet=get_bool("quiet", argument),
|
||||||
if not get_bool("conditions", argument) \
|
headers=get("headers", argument),
|
||||||
or get_bool("forecast", argument):
|
murl=get("murl", argument)
|
||||||
print weather.get_forecast(
|
)
|
||||||
get("city", argument),
|
if not get_bool("conditions", argument) \
|
||||||
get("st", argument),
|
or get_bool("forecast", argument):
|
||||||
get_bool("verbose", argument)
|
print weather.get_forecast(
|
||||||
)
|
city=get("city", argument),
|
||||||
|
st=get("st", argument),
|
||||||
|
verbose=get_bool("verbose", argument),
|
||||||
|
quiet=get_bool("quiet", argument),
|
||||||
|
flines=get("flines", argument),
|
||||||
|
furl=get("furl", argument)
|
||||||
|
)
|
||||||
|
|||||||
25
weather.1
25
weather.1
@@ -1,6 +1,8 @@
|
|||||||
.TH WEATHER 1 "March 26, 2006" "" \" -*- nroff -*-
|
.TH WEATHER 1 "July 13, 2008" "" \" -*- nroff -*-
|
||||||
\" Copyright (c) 2006 Jeremy Stanley <fungi@yuggoth.org>, all rights reserved.
|
\" Copyright (c) 2006-2008 Jeremy Stanley <fungi@yuggoth.org>.
|
||||||
\" Licensed per terms in the LICENSE file distributed with this software.
|
\" Permission to use, copy, modify, and distribute this software is
|
||||||
|
\" granted under terms provided in the LICENSE file distributed with
|
||||||
|
\" this software.
|
||||||
.SH NAME
|
.SH NAME
|
||||||
weather \- command\-line tool to obtain weather conditions and forecasts
|
weather \- command\-line tool to obtain weather conditions and forecasts
|
||||||
.SH SYNOPSIS
|
.SH SYNOPSIS
|
||||||
@@ -30,26 +32,41 @@ show a help message and exit
|
|||||||
.B \-cCITY, \-\-city=CITY
|
.B \-cCITY, \-\-city=CITY
|
||||||
the city name (ex: "Raleigh Durham")
|
the city name (ex: "Raleigh Durham")
|
||||||
.TP
|
.TP
|
||||||
|
.B \-\-flines=FLINES
|
||||||
|
maximum number of forecast lines to show
|
||||||
|
.TP
|
||||||
.B \-f, \-\-forecast
|
.B \-f, \-\-forecast
|
||||||
include a local forecast
|
include a local forecast
|
||||||
.TP
|
.TP
|
||||||
|
.B \-\-furl=FURL
|
||||||
|
forecast URL (including %city% and %st%)
|
||||||
|
.TP
|
||||||
|
.B \-\-headers=HEADERS
|
||||||
|
the conditions headers to display
|
||||||
|
.TP
|
||||||
.B \-iID, \-\-id=ID
|
.B \-iID, \-\-id=ID
|
||||||
the METAR station ID (ex: KRDU)
|
the METAR station ID (ex: KRDU)
|
||||||
.TP
|
.TP
|
||||||
.B \-l, \-\-list
|
.B \-l, \-\-list
|
||||||
print a list of configured aliases
|
print a list of configured aliases
|
||||||
.TP
|
.TP
|
||||||
|
.B \-\-murl=MURL
|
||||||
|
METAR URL (including %id%)
|
||||||
|
.TP
|
||||||
.B \-n, \-\-no\-conditions
|
.B \-n, \-\-no\-conditions
|
||||||
disable output of current conditions (forces \-f)
|
disable output of current conditions (forces \-f)
|
||||||
.TP
|
.TP
|
||||||
.B \-o, \-\-omit\-forecast
|
.B \-o, \-\-omit\-forecast
|
||||||
omit the local forecast (cancels \-f)
|
omit the local forecast (cancels \-f)
|
||||||
.TP
|
.TP
|
||||||
|
.B \-\-quiet
|
||||||
|
skip preambles and don't indent
|
||||||
|
.TP
|
||||||
.B \-sST, \-\-st=ST
|
.B \-sST, \-\-st=ST
|
||||||
the state abbreviation (ex: NC)
|
the state abbreviation (ex: NC)
|
||||||
.TP
|
.TP
|
||||||
.B \-v, \-\-verbose
|
.B \-v, \-\-verbose
|
||||||
show full decoded feeds
|
show full decoded feeds (cancels \-q)
|
||||||
.SH FILES
|
.SH FILES
|
||||||
.B weather
|
.B weather
|
||||||
may additionally obtain configuration data from a system\-wide
|
may additionally obtain configuration data from a system\-wide
|
||||||
|
|||||||
485
weather.py
485
weather.py
@@ -1,206 +1,323 @@
|
|||||||
# weather.py version 1.3, http://fungi.yuggoth.org/weather/
|
# weather.py version 1.4, http://fungi.yuggoth.org/weather/
|
||||||
# Copyright (c) 2006 Jeremy Stanley <fungi@yuggoth.org>, all rights reserved.
|
# Copyright (c) 2006-2008 Jeremy Stanley <fungi@yuggoth.org>.
|
||||||
# Licensed per terms in the LICENSE file distributed with this software.
|
# 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."""
|
"""Contains various object definitions needed by the weather utility."""
|
||||||
|
|
||||||
version = "1.3"
|
version = "1.4"
|
||||||
|
|
||||||
class Selections:
|
class Selections:
|
||||||
"""An object to contain selection data."""
|
"""An object to contain selection data."""
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
"""Store the config, options and arguments."""
|
"""Store the config, options and arguments."""
|
||||||
self.config = get_config()
|
self.config = get_config()
|
||||||
self.options, self.arguments = get_options(self.config)
|
self.options, self.arguments = get_options(self.config)
|
||||||
if self.arguments:
|
if self.arguments:
|
||||||
self.arguments = [(x.lower()) for x in self.arguments]
|
self.arguments = [(x.lower()) for x in self.arguments]
|
||||||
else: self.arguments = [ None ]
|
else: self.arguments = [ None ]
|
||||||
def get(self, option, argument=None):
|
def get(self, option, argument=None):
|
||||||
"""Retrieve data from the config or options."""
|
"""Retrieve data from the config or options."""
|
||||||
if not argument: return self.options.__dict__[option]
|
if not argument: return self.options.__dict__[option]
|
||||||
elif not self.config.has_section(argument):
|
elif not self.config.has_section(argument):
|
||||||
import sys
|
import sys
|
||||||
sys.stderr.write("ERROR: no alias defined for " \
|
sys.stderr.write("weather: error: no alias defined for " \
|
||||||
+ argument + "\n")
|
+ argument + "\n")
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
elif self.config.has_option(argument, option):
|
elif self.config.has_option(argument, option):
|
||||||
return self.config.get(argument, option)
|
return self.config.get(argument, option)
|
||||||
else: return self.options.__dict__[option]
|
else: return self.options.__dict__[option]
|
||||||
def get_bool(self, option, argument=None):
|
def get_bool(self, option, argument=None):
|
||||||
"""Get data and coerce to a boolean if necessary."""
|
"""Get data and coerce to a boolean if necessary."""
|
||||||
return bool(self.get(option, argument))
|
return bool(self.get(option, argument))
|
||||||
|
|
||||||
def bool(data):
|
def bool(data):
|
||||||
"""Coerce data to a boolean value."""
|
"""Coerce data to a boolean value."""
|
||||||
if type(data) is str:
|
if type(data) is str:
|
||||||
if eval(data): return True
|
if eval(data): return True
|
||||||
else: return False
|
else: return False
|
||||||
else:
|
else:
|
||||||
if data: return True
|
if data: return True
|
||||||
else: return False
|
else: return False
|
||||||
|
|
||||||
def quote(words):
|
def quote(words):
|
||||||
"""Wrap a string in quotes if it contains spaces."""
|
"""Wrap a string in quotes if it contains spaces."""
|
||||||
if words.find(" ") != -1: words = "\"" + words + "\""
|
if words.find(" ") != -1: words = "\"" + words + "\""
|
||||||
return words
|
return words
|
||||||
|
|
||||||
def sorted(data):
|
def sorted(data):
|
||||||
"""Return a sorted copy of a list."""
|
"""Return a sorted copy of a list."""
|
||||||
new_copy = data[:]
|
new_copy = data[:]
|
||||||
new_copy.sort()
|
new_copy.sort()
|
||||||
return new_copy
|
return new_copy
|
||||||
|
|
||||||
def get_url(url):
|
def get_url(url):
|
||||||
"""Return a string containing the results of a URL GET."""
|
"""Return a string containing the results of a URL GET."""
|
||||||
import urllib
|
import urllib2
|
||||||
return urllib.urlopen(url).read()
|
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):
|
def get_metar(id, verbose=False, quiet=False, headers=None, murl=None):
|
||||||
"""Return a summarized METAR for the specified station."""
|
"""Return a summarized METAR for the specified station."""
|
||||||
metar = get_url(
|
if not id:
|
||||||
"http://weather.noaa.gov/pub/data/observations/metar/decoded/" \
|
import sys
|
||||||
+ id.upper() + ".TXT")
|
sys.stderr.write("weather: error: id required for conditions\n")
|
||||||
if verbose: return metar
|
sys.exit(1)
|
||||||
else:
|
if not murl:
|
||||||
lines = metar.split("\n")
|
murl = \
|
||||||
headings = [
|
"http://weather.noaa.gov/pub/data/observations/metar/decoded/%ID%.TXT"
|
||||||
"Relative Humidity",
|
murl = murl.replace("%ID%", id.upper())
|
||||||
"Precipitation last hour",
|
murl = murl.replace("%Id%", id.capitalize())
|
||||||
"Sky conditions",
|
murl = murl.replace("%iD%", id)
|
||||||
"Temperature",
|
murl = murl.replace("%id%", id.lower())
|
||||||
"Weather",
|
murl = murl.replace(" ", "_")
|
||||||
"Wind"
|
metar = get_url(murl)
|
||||||
]
|
if verbose: return metar
|
||||||
output = []
|
else:
|
||||||
output.append("Current conditions at " \
|
lines = metar.split("\n")
|
||||||
+ lines[0].split(", ")[1] + " (" \
|
if not headers:
|
||||||
+ id.upper() +")")
|
headers = \
|
||||||
output.append("Last updated " + lines[1])
|
"relative_humidity," \
|
||||||
for line in lines:
|
+ "precipitation_last_hour," \
|
||||||
for heading in headings:
|
+ "sky conditions," \
|
||||||
if line.startswith(heading + ":"):
|
+ "temperature," \
|
||||||
if line.endswith(":0"):
|
+ "weather," \
|
||||||
line = line[:-2]
|
+ "wind"
|
||||||
output.append(" " + line)
|
headerlist = headers.lower().replace("_"," ").split(",")
|
||||||
return "\n".join(output)
|
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):
|
def get_forecast(city, st, verbose=False, quiet=False, flines="0", furl=None):
|
||||||
"""Return the forecast for a specified city/st combination."""
|
"""Return the forecast for a specified city/st combination."""
|
||||||
forecast = get_url("http://weather.noaa.gov/pub/data/forecasts/city/" \
|
if not city or not st:
|
||||||
+ st.lower() + "/" + city.lower().replace(" ", "_") \
|
import sys
|
||||||
+ ".txt")
|
sys.stderr.write("weather: error: city and st required for forecast\n")
|
||||||
if verbose: return forecast
|
sys.exit(1)
|
||||||
else:
|
if not furl:
|
||||||
lines = forecast.split("\n")
|
furl = "http://weather.noaa.gov/pub/data/forecasts/city/%st%/%city%.txt"
|
||||||
output = []
|
furl = furl.replace("%CITY%", city.upper())
|
||||||
output.append(lines[2])
|
furl = furl.replace("%City%", city.capitalize())
|
||||||
output.append(lines[3])
|
furl = furl.replace("%citY%", city)
|
||||||
for line in lines:
|
furl = furl.replace("%city%", city.lower())
|
||||||
if line.startswith("."):
|
furl = furl.replace("%ST%", st.upper())
|
||||||
output.append(line.replace(".", " ", 1))
|
furl = furl.replace("%St%", st.capitalize())
|
||||||
return "\n".join(output)
|
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):
|
def get_options(config):
|
||||||
"""Parse the options passed on the command line."""
|
"""Parse the options passed on the command line."""
|
||||||
import optparse
|
|
||||||
usage = "usage: %prog [ options ] [ alias [ alias [...] ] ]"
|
# for optparse's builtin -h/--help option
|
||||||
verstring = "%prog " + version
|
usage = "usage: %prog [ options ] [ alias [ alias [...] ] ]"
|
||||||
option_parser = optparse.OptionParser(usage=usage, version=verstring)
|
|
||||||
if config.has_option("default", "city"):
|
# for optparse's builtin --version option
|
||||||
default_city = config.get("default", "city")
|
verstring = "%prog " + version
|
||||||
else: default_city = "Raleigh Durham"
|
|
||||||
option_parser.add_option("-c", "--city",
|
# create the parser
|
||||||
dest="city",
|
import optparse
|
||||||
default=default_city,
|
option_parser = optparse.OptionParser(usage=usage, version=verstring)
|
||||||
help="the city name (ex: \"Raleigh Durham\")")
|
|
||||||
if config.has_option("default", "forecast"):
|
# the -c/--city option
|
||||||
default_forecast = bool(config.get("default", "forecast"))
|
if config.has_option("default", "city"):
|
||||||
else: default_forecast = False
|
default_city = config.get("default", "city")
|
||||||
option_parser.add_option("-f", "--forecast",
|
else: default_city = ""
|
||||||
dest="forecast",
|
option_parser.add_option("-c", "--city",
|
||||||
action="store_true",
|
dest="city",
|
||||||
default=default_forecast,
|
default=default_city,
|
||||||
help="include a local forecast")
|
help="the city name (ex: \"Raleigh Durham\")")
|
||||||
if config.has_option("default", "id"):
|
|
||||||
default_id = config.get("default", "id")
|
# the --flines option
|
||||||
else: default_id = "KRDU"
|
if config.has_option("default", "flines"):
|
||||||
option_parser.add_option("-i", "--id",
|
default_flines = config.get("default", "flines")
|
||||||
dest="id",
|
else: default_flines = "0"
|
||||||
default=default_id,
|
option_parser.add_option("--flines",
|
||||||
help="the METAR station ID (ex: KRDU)")
|
dest="flines",
|
||||||
option_parser.add_option("-l", "--list",
|
default=default_flines,
|
||||||
dest="list",
|
help="maximum number of forecast lines to show")
|
||||||
action="store_true",
|
|
||||||
default=False,
|
# the -f/--forecast option
|
||||||
help="print a list of configured aliases")
|
if config.has_option("default", "forecast"):
|
||||||
if config.has_option("default", "conditions"):
|
default_forecast = bool(config.get("default", "forecast"))
|
||||||
default_conditions = bool(config.get("default", "conditions"))
|
else: default_forecast = False
|
||||||
else: default_conditions = True
|
option_parser.add_option("-f", "--forecast",
|
||||||
option_parser.add_option("-n", "--no-conditions",
|
dest="forecast",
|
||||||
dest="conditions",
|
action="store_true",
|
||||||
action="store_false",
|
default=default_forecast,
|
||||||
default=default_conditions,
|
help="include a local forecast")
|
||||||
help="disable output of current conditions (forces -f)")
|
|
||||||
option_parser.add_option("-o", "--omit-forecast",
|
# the --furl option
|
||||||
dest="forecast",
|
if config.has_option("default", "furl"):
|
||||||
action="store_false",
|
default_furl = config.get("default", "furl")
|
||||||
default=default_forecast,
|
else:
|
||||||
help="omit the local forecast (cancels -f)")
|
default_furl = \
|
||||||
if config.has_option("default", "st"):
|
"http://weather.noaa.gov/pub/data/forecasts/city/%st%/%city%.txt"
|
||||||
default_st = config.get("default", "st")
|
option_parser.add_option("--furl",
|
||||||
else: default_st = "NC"
|
dest="furl",
|
||||||
option_parser.add_option("-s", "--st",
|
default=default_furl,
|
||||||
dest="st",
|
help="forecast URL (including %city% and %st%)")
|
||||||
default=default_st,
|
|
||||||
help="the state abbreviation (ex: NC)")
|
# the --headers option
|
||||||
if config.has_option("default", "verbose"):
|
if config.has_option("default", "headers"):
|
||||||
default_verbose = bool(config.get("default", "verbose"))
|
default_headers = config.get("default", "headers")
|
||||||
else: default_verbose = False
|
else:
|
||||||
option_parser.add_option("-v", "--verbose",
|
default_headers = \
|
||||||
dest="verbose",
|
"temperature," \
|
||||||
action="store_true",
|
+ "relative_humidity," \
|
||||||
default=default_verbose,
|
+ "wind," \
|
||||||
help="show full decoded feeds")
|
+ "weather," \
|
||||||
options, arguments = option_parser.parse_args()
|
+ "sky_conditions," \
|
||||||
return options, arguments
|
+ "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():
|
def get_config():
|
||||||
"""Parse the aliases and configuration."""
|
"""Parse the aliases and configuration."""
|
||||||
import ConfigParser
|
import ConfigParser
|
||||||
config = ConfigParser.ConfigParser()
|
config = ConfigParser.ConfigParser()
|
||||||
import os.path
|
import os.path
|
||||||
rcfiles = [
|
rcfiles = [
|
||||||
"/etc/weatherrc",
|
"/etc/weatherrc",
|
||||||
os.path.expanduser("~/.weatherrc"),
|
os.path.expanduser("~/.weatherrc"),
|
||||||
"weatherrc"
|
"weatherrc"
|
||||||
]
|
]
|
||||||
import os
|
import os
|
||||||
for rcfile in rcfiles:
|
for rcfile in rcfiles:
|
||||||
if os.access(rcfile, os.R_OK): config.read(rcfile)
|
if os.access(rcfile, os.R_OK): config.read(rcfile)
|
||||||
for section in config.sections():
|
for section in config.sections():
|
||||||
if section != section.lower():
|
if section != section.lower():
|
||||||
if config.has_section(section.lower()):
|
if config.has_section(section.lower()):
|
||||||
config.remove_section(section.lower())
|
config.remove_section(section.lower())
|
||||||
config.add_section(section.lower())
|
config.add_section(section.lower())
|
||||||
for option,value in config.items(section):
|
for option,value in config.items(section):
|
||||||
config.set(section.lower(), option, value)
|
config.set(section.lower(), option, value)
|
||||||
return config
|
return config
|
||||||
|
|
||||||
def list_aliases(config):
|
def list_aliases(config):
|
||||||
"""Return a formatted list of aliases defined in the config."""
|
"""Return a formatted list of aliases defined in the config."""
|
||||||
sections = []
|
sections = []
|
||||||
for section in sorted(config.sections()):
|
for section in sorted(config.sections()):
|
||||||
if section.lower() not in sections and section != "default":
|
if section.lower() not in sections and section != "default":
|
||||||
sections.append(section.lower())
|
sections.append(section.lower())
|
||||||
output = "configured aliases..."
|
output = "configured aliases..."
|
||||||
for section in sorted(sections):
|
for section in sorted(sections):
|
||||||
output += "\n " \
|
output += "\n " \
|
||||||
+ section \
|
+ section \
|
||||||
+ ": --id=" \
|
+ ": --id=" \
|
||||||
+ quote(config.get(section, "id")) \
|
+ quote(config.get(section, "id")) \
|
||||||
+ " --city=" \
|
+ " --city=" \
|
||||||
+ quote(config.get(section, "city")) \
|
+ quote(config.get(section, "city")) \
|
||||||
+ " --st=" \
|
+ " --st=" \
|
||||||
+ quote(config.get(section, "st"))
|
+ quote(config.get(section, "st"))
|
||||||
return output
|
return output
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,8 @@
|
|||||||
|
# Copyright (c) 2006-2008 Jeremy Stanley <fungi@yuggoth.org>.
|
||||||
|
# Permission to use, copy, modify, and distribute this software is
|
||||||
|
# granted under terms provided in the LICENSE file distributed with
|
||||||
|
# this software.
|
||||||
|
|
||||||
[ABE]
|
[ABE]
|
||||||
City = Allentown
|
City = Allentown
|
||||||
ID = KABE
|
ID = KABE
|
||||||
|
|||||||
39
weatherrc.5
39
weatherrc.5
@@ -1,6 +1,8 @@
|
|||||||
.TH WEATHERRC 5 "March 26, 2006" "" \" -*- nroff -*-
|
.TH WEATHERRC 5 "July 13, 2008" "" \" -*- nroff -*-
|
||||||
\" Copyright (c) 2006 Jeremy Stanley <fungi@yuggoth.org>, all rights reserved.
|
\" Copyright (c) 2006-2008 Jeremy Stanley <fungi@yuggoth.org>.
|
||||||
\" Licensed per terms in the LICENSE file distributed with this software.
|
\" Permission to use, copy, modify, and distribute this software is
|
||||||
|
\" granted under terms provided in the LICENSE file distributed with
|
||||||
|
\" this software.
|
||||||
.SH NAME
|
.SH NAME
|
||||||
weatherrc \- configuration file format for the
|
weatherrc \- configuration file format for the
|
||||||
.BR weather (1)
|
.BR weather (1)
|
||||||
@@ -20,20 +22,47 @@ These parameters are supported...
|
|||||||
.B city
|
.B city
|
||||||
the city name (ex: Raleigh Durham)
|
the city name (ex: Raleigh Durham)
|
||||||
.TP
|
.TP
|
||||||
|
.B conditions
|
||||||
|
output current conditions (possible values are False and True or 0 and 1)
|
||||||
|
.TP
|
||||||
|
.B flines
|
||||||
|
maximum number of forecast lines to show (integer value, 0 means unlimited)
|
||||||
|
.TP
|
||||||
.B forecast
|
.B forecast
|
||||||
include a local forecast (possible values are False and True or 0 and 1)
|
include a local forecast (possible values are False and True or 0 and 1)
|
||||||
.TP
|
.TP
|
||||||
|
.B furl
|
||||||
|
forecast URL (ex: http://forecast.org/%st%/%city%.txt)
|
||||||
|
.TP
|
||||||
|
.B headers
|
||||||
|
the conditions headers to display (ex: temperature,wind)
|
||||||
|
.TP
|
||||||
.B id
|
.B id
|
||||||
the METAR station ID (ex: KRDU)
|
the METAR station ID (ex: KRDU)
|
||||||
.TP
|
.TP
|
||||||
.B conditions
|
.B murl
|
||||||
output current conditions (possible values are False and True or 0 and 1)
|
METAR URL (ex: http://metar.org/%id%.txt)
|
||||||
|
.TP
|
||||||
|
.B quiet
|
||||||
|
skip preambles and don't indent (possible values are False and True or 0 and 1)
|
||||||
.TP
|
.TP
|
||||||
.B st
|
.B st
|
||||||
the state abbreviation (ex: NC)
|
the state abbreviation (ex: NC)
|
||||||
.TP
|
.TP
|
||||||
.B verbose
|
.B verbose
|
||||||
show full decoded feeds (possible values are False and True or 0 and 1)
|
show full decoded feeds (possible values are False and True or 0 and 1)
|
||||||
|
.SH URL FORMAT
|
||||||
|
The placeholders %city% and %st% in the furl URL and %id% in the murl URL
|
||||||
|
will be replaced with the city, st and id definitions respectively. If the
|
||||||
|
placeholder has all letters lowercased, the replacement will be forced to
|
||||||
|
all lowercase. If the placeholder has all letters uppercased, the
|
||||||
|
replacement will be forced to all uppercase. If the placeholder has its
|
||||||
|
first letter uppercased and the remainder lowercased, then all words in the
|
||||||
|
replacement will start with an uppercase letter and the rest will be
|
||||||
|
lowercase. If the placeholder has its last letter uppercased and the
|
||||||
|
remainder lowercased, then case will be preserved in the replacement. Also,
|
||||||
|
after replacement, any spaces in the resulting URL will be converted to
|
||||||
|
underscore characters prior to use.
|
||||||
.SH EXAMPLES
|
.SH EXAMPLES
|
||||||
Following is an example
|
Following is an example
|
||||||
.B ~/.weatherrc
|
.B ~/.weatherrc
|
||||||
|
|||||||
Reference in New Issue
Block a user