Imported from archive. 1.5
authorJeremy Stanley <fungi@yuggoth.org>
Fri, 19 Mar 2010 13:30:22 +0000 (13:30 +0000)
committerJeremy Stanley <fungi@yuggoth.org>
Fri, 19 Mar 2010 13:30:22 +0000 (13:30 +0000)
* Release 1.5.

* (all): Updated copyright notices for 2010.

* FAQ, INSTALL, LICENSE, README: Reformatted as ReStructuredText.

* FAQ: Updated to mention alternative sources for NOAA's stations
list, in case the recommended one is unavailable (thanks Celejar!).

* NEWS: Renamed to ChangeLog and refactored into GNU format.

* weather: Added some comment padding between the shebang line and
the copyright, so that distributions wishing to carry patches which
modify the interpreter path don't have to refresh them every year
when the copyright line changes in their context.

* weather, weather.1, weatherrc.5, weather.py: Added experimental
alert, atypes, aurl and zones options to support retrieval,
filtering and formatting of unexpired NWS severe weather advisories.

* weather.1, weatherrc.5: Minor cosmetic fixes to option
descriptions.

* weather.1, weatherrc.5, weather.py: Added imperial and metric
options to filter/convert display units (thanks to Andrew Carter for
this suggestion!).

* weather.py: Fixed a METAR parsing error which would trigger an
IndexError exception if the NWS didn't have a station description on
file (thanks to Celejar for reporting the bug!). Fixed METAR title
line parsing to look for human-readable city and state in the first
line--previous code stopped showing the city name after NWS made
slight format mods. Upped the version to 1.5.

* weatherrc: Additional PIE (Saint Petersburg, FL), PNC (Ponca City,
OK), and PNS (Pensacola, FL) aliases.

FAQ
INSTALL
LICENSE
README
weather
weather.1
weather.py
weatherrc
weatherrc.5

diff --git a/FAQ b/FAQ
index d8cc569..54a44d8 100644 (file)
--- a/FAQ
+++ b/FAQ
@@ -1,71 +1,68 @@
-FREQUENTLY ASKED QUESTIONS ABOUT THE WEATHER UTILITY
+======================================================
+ Frequently Asked Questions About the Weather Utility
+======================================================
 
 
-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.
-
-
-Table of Contents:
-
-1. Can I help?
-2. How do I figure out my local METAR station ID?
-3. How do I figure out my local city name and state abbreviation?
-4. I live outside the USA--can this be made to work for me
-   anyway?
-5. Why do I get the wrong forecast when specifying -i or --id?
+:Copyright: (c) 2006-2010 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.
 
 
+.. contents::
 
 1. Can I help?
 
 1. Can I help?
-Sure! Bug reports and feature suggestions are always welcome, but
-fixes and patches are of course preferred. Contact
-fungi@yuggoth.org if desired, but please read this FAQ and the
-included manuals for weather(1) and weatherrc(5) before asking
-questions that might be answered therein. One big way anyone can
-help is to provide me with some additional mappings of METAR
-station ID, city name and state abbreviation for inclusion in the
-default /etc/weatherrc file.
-
+--------------
+Sure! Bug reports and feature suggestions are always welcome, but fixes and
+patches are of course preferred. Contact fungi@yuggoth.org if desired, but
+please read this FAQ and the included manuals for weather(1) and weatherrc(5)
+before asking questions that might be answered therein. One big way anyone can
+help is to provide me with some additional mappings of METAR station ID, city
+name and state abbreviation for inclusion in the default /etc/weatherrc file.
 
 2. How do I figure out my local METAR station ID?
 
 2. How do I figure out my local METAR station ID?
-
-The list of stations is found at
-http://weather.noaa.gov/data/nsd_cccc.gz (it's thousands of lines
-long, so I recommend keyword searching in your browser or using
-grep(1) to find what you're looking for).
-
+-------------------------------------------------
+The list of stations is found at http://weather.noaa.gov/data/nsd_cccc.gz (it's
+thousands of lines long, so I recommend keyword searching in your browser or
+using grep(1) to find what you're looking for). From time to time, the
+compression on their site seems to be failing, resulting in zero-byte files. If
+you run into this issue, you can get uncompressed and zip-compressed versions
+by replacing the "gz" suffix in the URL with "txt" or "zip" respectively. The
+list can also be obtained from the following URLs in a pinch, though they are
+not guaranteed to be up to date (thanks Celejar!):
+
+ * http://www.rap.ucar.edu/weather/surface/stations.txt
+ * http://aviationweather.gov/adds/metars/stations.txt
 
 3. How do I figure out my local city name and state abbreviation?
 
 3. How do I figure out my local city name and state abbreviation?
-
+-----------------------------------------------------------------
 The forecasts can be located starting from
 The forecasts can be located starting from
-http://weather.noaa.gov/pub/data/forecasts/city/ (choose the
-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
-anyway?
-
-METAR station IDs can be found for cities and airports worldwide,
-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
-serious modification would be necessary given that the data is
-likely to be published in a non-English language, requiring some
-additional input from speakers of that language for how to handle
-filtering and formatting of the text.
-
+http://weather.noaa.gov/pub/data/forecasts/city/ (choose the 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 anyway?
+------------------------------------------------------------------
+METAR station IDs can be found for cities and airports worldwide, 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
+serious modification would be necessary given that the data is likely to be
+published in a non-English language, requiring some additional input from
+speakers of that language for how to handle filtering and formatting of the
+text.
 
 5. Why do I get the wrong forecast when specifying -i or --id?
 
 5. Why do I get the wrong forecast when specifying -i or --id?
-
-The -i or --id switch (or the id parameter in an alias definition),
-only tells weather(1) what current conditions to retrieve. If you
-specify -f or --forecast on the command line (or forecast=True in
-an alias) without providing a city name and state abbreviation
-(-c/--city and -s/--st, or city and st in an alias) and are seeing
-an actual forecast, then you probably have a default city and state
-abbreviation set in your config. See question 3 above for
-information on figuring out what city name and state abbreviation
-to use, and the manual for weatherrc(5) for information on defining
-aliases.
+--------------------------------------------------------------
+The -i or --id switch (or the id parameter in an alias definition), only tells
+weather(1) what current conditions to retrieve. If you specify -f or --forecast
+on the command line (or forecast=True in an alias) without providing a city
+name and state abbreviation (-c/--city and -s/--st, or city and st in an alias)
+and are seeing an actual forecast, then you probably have a default city and
+state abbreviation set in your config. See question 3 above for information on
+figuring out what city name and state abbreviation to use, and the manual for
+weatherrc(5) for information on defining aliases.
+
+6. Where can I get a list of the NWS advisory zones for alerts?
+---------------------------------------------------------------
+The lists of advisory zones by region are found aggregated at
+http://weather.noaa.gov/pub/data/zonecatalog.curr.tar (it's several thousand
+files totalling well over a hundred thousand lines of text, so I recommend
+downloading, unpacking and using a recursive grep(1) to find what you're
+looking for).
diff --git a/INSTALL b/INSTALL
index da831b6..2d341ab 100644 (file)
--- a/INSTALL
+++ b/INSTALL
@@ -1,28 +1,28 @@
-BASIC UNIX INSTALLATION INSTRUCTIONS FOR THE WEATHER UTILITY
+==============================================================
+ Basic Unix Installation Instructions for the Weather Utility
+==============================================================
 
 
-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.
+:Copyright: (c) 2006-2010 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.
 
 
+.. contents::
 
 
-PREREQUISITES
+Prerequisites
+-------------
+You need the Python interpreter installed somewhere in your path (most modern
+UNIX derivatives come with one already). If you need to get Python, it can be
+obtained from http://www.python.org/ (but chances are your operating system at
+least provides some sort of native package for it, which you should probably
+install in whatever means is recommended by your OS vendor/distributor).
 
 
-You need the Python interpreter installed somewhere in your path
-(most modern UNIX derivatives come with one already). If you need to
-get Python, it can be obtained from http://www.python.org/ (but
-chances are your operating system at least provides some sort of
-native package for it, which you should probably install in whatever
-means is recommended by your OS vendor/distributor).
-
-
-RUNNING IN PLACE
-
-An easy way to try it out is to unpack the tarball and change to the
-resulting directory:
+Running in Place
+----------------
+An easy way to try it out is to unpack the tarball and change to the resulting
+directory::
 
    tar xzf weather-*.tar.gz
 
    tar xzf weather-*.tar.gz
-   cd weather
+   cd weather-*
    ./weather --version
    ./weather --help
    man ./weather.1
    ./weather --version
    ./weather --help
    man ./weather.1
@@ -30,30 +30,27 @@ resulting directory:
    ./weather --forecast --no-conditions --city=charlotte --st=nc
    ./weather ord sea
 
    ./weather --forecast --no-conditions --city=charlotte --st=nc
    ./weather ord sea
 
-...and so on. The weather utility, included Python module and
-documentation are all fully functional when kept together in one
-directory, if somewhat inconvenient.
-
-
-INSTALLING THE UTILITY
+...and so on. The weather utility, included Python module and documentation are
+all fully functional when kept together in one directory, if somewhat
+inconvenient.
 
 
-The file named weather should be made executable and put somewhere
-in your path (/usr/local/bin/ or ~/bin/ for example). Similarly,
-weather.py needs to be somewhere in Python's include path. You can
-see your Python interpreter's default include path by running:
+Installing the Utility
+----------------------
+The file named weather should be made executable and put somewhere in your path
+(/usr/local/bin/ or ~/bin/ for example). Similarly, weather.py needs to be
+somewhere in Python's include path. You can see your Python interpreter's
+default include path by running::
 
    python -c "import sys ; print sys.path"
 
 
    python -c "import sys ; print sys.path"
 
-
-CONFIGURATION
-
-The weatherrc file should go in /etc/ or you can save it in your
-home directory as a dotfile (~/.weatherrc) to support user-specific
-alias configuration and overrides of the global /etc/weatherrc file.
-
-
-MANUALS
-
-Optionally, the weather.1 and weatherrc.5 files can be placed in
-sane locations for TROFF/NROFF manual files on your system (for
-example, /usr/local/share/man/ or ~/man/).
+Configuration
+-------------
+The weatherrc file should go in /etc/ or you can save it in your home directory
+as a dotfile (~/.weatherrc) to support user-specific alias configuration and
+overrides of the global /etc/weatherrc file.
+
+Manuals
+-------
+Optionally, the weather.1 and weatherrc.5 files can be placed in sane locations
+for TROFF/NROFF manual files on your system (for example, /usr/local/share/man/
+or ~/man/).
diff --git a/LICENSE b/LICENSE
index 87142ff..22c1666 100644 (file)
--- a/LICENSE
+++ b/LICENSE
@@ -1,13 +1,28 @@
-Copyright (c) 2006-2008 Jeremy Stanley <fungi@yuggoth.org>
-
-Permission to use, copy, modify, and distribute this software for any
-purpose with or without fee is hereby granted, provided that the above
-copyright notice and this permission notice appear in all copies.
-
-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.
+=================
+ Weather License
+=================
+
+The weather project employs the ISC license, a permissive free software license
+written by the Internet Systems Consortium and functionally equivalent to the
+2-clause BSD license. Initially used for the ISC's own software releases, it
+has since become the preferred license of many projects (OpenBSD, for example).
+
+Copyright Notice
+----------------
+Copyright (c) 2006-2010 Jeremy Stanley <fungi@yuggoth.org>
+
+Permission Notice
+-----------------
+Permission to use, copy, modify, and distribute this software for any purpose
+with or without fee is hereby granted, provided that the above copyright notice
+and this permission notice appear in all copies.
+
+Disclaimer
+----------
+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.
diff --git a/README b/README
index a564e31..8ed74c1 100644 (file)
--- a/README
+++ b/README
@@ -1,53 +1,48 @@
-GENERAL INFORMATION ABOUT THE WEATHER UTILITY
-
-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.
-
-
-WHAT?
-
-This command-line utility is intended to provide quick access to
-current weather conditions and forecasts. Presently, it is
-capable of returning data for localities throughout the USA by
-retrieving and formatting decoded METARs (Meteorological
-Aerodrome Reports) from NOAA (the USA National Oceanic and
-Atmospheric Administration) and forecasts from NWS (the USA
-National Weather Service). The tool is written to function in the
-same spirit as other command-line informational utilities like
-cal(1), calendar(1) and dict(1). It can retrieve arbitrary weather
-data via specific command-line switches (station ID, city, state),
-or aliases can be configured system wide and on a per-user basis.
-It can be freely used and redistributed under the terms of a
-BSD-like License.
-
-
-WHY?
-
-My girlfriend had a long commute to/from work and school, and
-often wanted to check the weather both for home and her office.
-Unfortunately, starting a Web browser, pulling up a weather site,
-entering multiple ZIP codes and waiting for them to load is
-time-consuming for the marginally-impatient. Since she tended to
-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
-her commute, but to my surprise, a quick search turned up little
-that met my basic requirements:
-
-  * retrieve current data on-demand
-  * provide both current conditions and short-term forecasts
-  * simple, human-readable output
-  * easy to configure and use
-  * flexible command-line switches and options
-
-
-WHERE?
-
-A tarball for the most recent version of the weather utility can
-be had here:
-
-   http://fungi.yuggoth.org/weather/src/
-
-Alternatively, Debian users can install the weather-util package
+===============================================
+ General Information About the Weather Utility
+===============================================
+
+:Copyright: (c) 2006-2010 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.
+
+.. contents::
+
+What?
+-----
+This command-line utility is intended to provide quick access to current
+weather conditions and forecasts. Presently, it is capable of returning data
+for localities throughout the USA by retrieving and formatting decoded METARs
+(Meteorological Aerodrome Reports) from NOAA (the USA National Oceanic and
+Atmospheric Administration) and forecasts from NWS (the USA National Weather
+Service). The tool is written to function in the same spirit as other
+command-line informational utilities like cal(1), calendar(1) and dict(1). It
+can retrieve arbitrary weather data via specific command-line switches (station
+ID, city, state), or aliases can be configured system wide and on a per-user
+basis. It can be freely used and redistributed under the terms of a BSD-like
+License.
+
+Why?
+----
+My girlfriend had a long commute to/from work and school, and often wanted to
+check the weather both for home and her office. Unfortunately, starting a Web
+browser, pulling up a weather site, entering multiple ZIP codes and waiting for
+them to load is time-consuming for the marginally-impatient. Since she tended
+to 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 her commute, but to my
+surprise, a quick search turned up little that met my basic requirements:
+
+ * retrieve current data on-demand
+ * provide both current conditions and short-term forecasts
+ * simple, human-readable output
+ * easy to configure and use
+ * flexible command-line switches and options
+
+Where?
+------
+A tarball for the most recent version of the weather utility can be had here:
+
+ * http://fungi.yuggoth.org/weather/src/
+
+Alternatively, Debian and Ubuntu users can install the weather-util package
 from any mirror.
 from any mirror.
diff --git a/weather b/weather
index 5e620b8..dc0601c 100755 (executable)
--- a/weather
+++ b/weather
@@ -1,10 +1,10 @@
 #!/usr/bin/env python
 #!/usr/bin/env python
+# distributions may wish to edit the above to refer to a specific interpreter
+# path, such as #!/usr/bin/python
 
 
-# weather version 1.4, http://fungi.yuggoth.org/weather/
-# 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.
+# Copyright (c) 2006-2010 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.
 
 """Wrapper utility using the weather.py module."""
 
 
 """Wrapper utility using the weather.py module."""
 
@@ -26,22 +26,52 @@ if get_bool("list"): print weather.list_aliases(selections.config)
 
 # normal operation
 else:
 
 # normal operation
 else:
+   output = ""
    for argument in selections.arguments:
    for argument in selections.arguments:
-      if get_bool("conditions", argument):
-         print weather.get_metar(
+      if get_bool("conditions", argument) or not (
+         get_bool("alert", argument) or get_bool("forecast", argument)
+      ):
+         partial = weather.get_metar(
             id=get("id", argument),
             verbose=get_bool("verbose", argument),
             quiet=get_bool("quiet", argument),
             headers=get("headers", argument),
             id=get("id", argument),
             verbose=get_bool("verbose", argument),
             quiet=get_bool("quiet", argument),
             headers=get("headers", argument),
-            murl=get("murl", argument)
-            )
-      if not get_bool("conditions", argument) \
-         or get_bool("forecast", argument):
-         print weather.get_forecast(
+            murl=get("murl", argument),
+            imperial=get_bool("imperial", argument),
+            metric=get_bool("metric", argument)
+         )
+         if partial: output += partial + "\n"
+      if get_bool("forecast", argument) or not (
+         get_bool("alert", argument) or get_bool("conditions", argument)
+      ):
+         partial = 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),
             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)
-            )
+            furl=get("furl", argument),
+            imperial=get_bool("imperial", argument),
+            metric=get_bool("metric", argument)
+         )
+         if partial: output += partial + "\n"
+      if get_bool("alert", argument) or not (
+         get_bool("conditions", argument) or get_bool("forecast", argument)
+      ):
+         alert_text = ""
+         for atype in get("atypes", argument).split(","):
+            for zone in get("zones", argument).split(","):
+               partial = weather.get_alert(
+                  zone=zone,
+                  verbose=get_bool("verbose", argument),
+                  quiet=get_bool("quiet", argument),
+                  atype=atype,
+                  aurl=get("aurl", argument),
+                  imperial=get_bool("imperial", argument),
+                  metric=get_bool("metric", argument)
+               )
+               if partial: alert_text += partial + "\n"
+         if not alert_text: alert_text = "(no current alerts for your zones)\n"
+         output += alert_text
+      output = output.strip()
+      if output: print( output )
index 171a5c4..bbffa8c 100644 (file)
--- a/weather.1
+++ b/weather.1
@@ -1,5 +1,5 @@
-.TH WEATHER 1 "July 13, 2008" "" \" -*- nroff -*-
-\" Copyright (c) 2006-2008 Jeremy Stanley <fungi@yuggoth.org>.
+.TH WEATHER 1 "March 15, 2010" "" \" -*- nroff -*-
+\" Copyright (c) 2006-2010 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.
 \" Permission to use, copy, modify, and distribute this software is
 \" granted under terms provided in the LICENSE file distributed with
 \" this software.
@@ -29,7 +29,16 @@ show program's version number and exit
 .B \-h, \-\-help
 show a help message and exit
 .TP
 .B \-h, \-\-help
 show a help message and exit
 .TP
-.B \-cCITY, \-\-city=CITY
+.B \-a, \-\-alert
+include local alert notices
+.TP
+.B \-\-atypes=ATYPES
+alert notification types to display
+.TP
+.B \-\-aurl=AURL
+alert URL (including %atype% and %zone%)
+.TP
+.B \-c CITY, \-\-city=CITY
 the city name (ex: "Raleigh Durham")
 .TP
 .B \-\-flines=FLINES
 the city name (ex: "Raleigh Durham")
 .TP
 .B \-\-flines=FLINES
@@ -44,12 +53,18 @@ forecast URL (including %city% and %st%)
 .B \-\-headers=HEADERS
 the conditions headers to display
 .TP
 .B \-\-headers=HEADERS
 the conditions headers to display
 .TP
-.B \-iID, \-\-id=ID
+.B \-i ID, \-\-id=ID
 the METAR station ID (ex: KRDU)
 .TP
 the METAR station ID (ex: KRDU)
 .TP
+.B \-\-imperial
+filter/convert for US/UK units
+.TP
 .B \-l, \-\-list
 print a list of configured aliases
 .TP
 .B \-l, \-\-list
 print a list of configured aliases
 .TP
+.B \-m, \-\-metric
+filter/convert for metric units
+.TP
 .B \-\-murl=MURL
 METAR URL (including %id%)
 .TP
 .B \-\-murl=MURL
 METAR URL (including %id%)
 .TP
@@ -62,11 +77,14 @@ omit the local forecast (cancels \-f)
 .B \-\-quiet
 skip preambles and don't indent
 .TP
 .B \-\-quiet
 skip preambles and don't indent
 .TP
-.B \-sST, \-\-st=ST
+.B \-s ST, \-\-st=ST
 the state abbreviation (ex: NC)
 .TP
 .B \-v, \-\-verbose
 show full decoded feeds (cancels \-q)
 the state abbreviation (ex: NC)
 .TP
 .B \-v, \-\-verbose
 show full decoded feeds (cancels \-q)
+.TP
+.B \-z ZONES, \-\-zones=ZONES
+alert zones (ex: nc/ncc183,nc/ncz041)
 .SH FILES
 .B weather
 may additionally obtain configuration data from a system\-wide
 .SH FILES
 .B weather
 may additionally obtain configuration data from a system\-wide
index 20c9d5c..4920a3a 100644 (file)
@@ -1,12 +1,10 @@
-# weather.py version 1.4, http://fungi.yuggoth.org/weather/
-# 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.
+# Copyright (c) 2006-2010 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.
 
 """Contains various object definitions needed by the weather utility."""
 
 
 """Contains various object definitions needed by the weather utility."""
 
-version = "1.4"
+version = "1.5"
 
 class Selections:
    """An object to contain selection data."""
 
 class Selections:
    """An object to contain selection data."""
@@ -46,24 +44,121 @@ def quote(words):
    if words.find(" ") != -1: words = "\"" + words + "\""
    return words
 
    if words.find(" ") != -1: words = "\"" + words + "\""
    return words
 
+def titlecap(words):
+   """Perform English-language title capitalization."""
+   words = words.lower().strip()
+   for separator in [" ", "-", "'"]:
+      newwords = []
+      wordlist = words.split(separator)
+      for word in wordlist:
+         if word:
+            newwords.append(word[0].upper() + word[1:])
+      words = separator.join(newwords)
+   end = len(words)
+   for prefix in ["Mac", "Mc"]:
+      position = 0
+      offset = len(prefix)
+      while position < end:
+         position = words.find(prefix, position)
+         if position == -1:
+            position = end
+         position += offset
+         import string
+         if position < end and words[position] in string.letters:
+            words = words[:position] \
+               + words[position].upper() \
+               + words[position+1:]
+   return words
+
+def filter_units(line, units="imperial"):
+   """Filter or convert units in a line of text between US/UK and metric."""
+   import re
+   # filter lines with both pressures in the form of "X inches (Y hPa)" or
+   # "X in. Hg (Y hPa)"
+   dual_p = re.match(
+      "(.* )(\d*(\.\d+)? (inches|in\. Hg)) \((\d*(\.\d+)? hPa)\)(.*)",
+      line
+   )
+   if dual_p:
+      preamble, in_hg, i_fr, i_un, hpa, h_fr, trailer = dual_p.groups()
+      if units == "imperial": line = preamble + in_hg + trailer
+      elif units == "metric": line = preamble + hpa + trailer
+   # filter lines with both temperatures in the form of "X F (Y C)"
+   dual_t = re.match(
+      "(.* )(\d*(\.\d+)? F) \((\d*(\.\d+)? C)\)(.*)",
+      line
+   )
+   if dual_t:
+      preamble, fahrenheit, f_fr, celsius, c_fr, trailer = dual_t.groups()
+      if units == "imperial": line = preamble + fahrenheit + trailer
+      elif units == "metric": line = preamble + celsius + trailer
+   # if metric is desired, convert distances in the form of "X mile(s)" to
+   # "Y kilometer(s)"
+   if units == "metric":
+      imperial_d = re.match(
+         "(.* )(\d+)( mile\(s\))(.*)",
+         line
+      )
+      if imperial_d:
+         preamble, mi, m_u, trailer = imperial_d.groups()
+         line = preamble + str(int(round(int(mi)*1.609344))) \
+            + " kilometer(s)" + trailer
+   # filter speeds in the form of "X MPH (Y KT)" to just "X MPH"; if metric is
+   # desired, convert to "Z KPH"
+   imperial_s = re.match(
+      "(.* )(\d+)( MPH)( \(\d+ KT\))(.*)",
+      line
+   )
+   if imperial_s:
+      preamble, mph, m_u, kt, trailer = imperial_s.groups()
+      if units == "imperial": line = preamble + mph + m_u + trailer
+      elif units == "metric": 
+         line = preamble + str(int(round(int(mph)*1.609344))) + " KPH" + \
+            trailer
+   # if imperial is desired, qualify given forcast temperatures like "X F"; if
+   # metric is desired, convert to "Y C"
+   imperial_t = re.match(
+      "(.* )(High |high |Low |low )(\d+)(\.|,)(.*)",
+      line
+   )
+   if imperial_t:
+      preamble, parameter, fahrenheit, sep, trailer = imperial_t.groups()
+      if units == "imperial":
+         line = preamble + parameter + fahrenheit + " F" + sep + trailer
+      elif units == "metric":
+         line = preamble + parameter \
+            + str(int(round((int(fahrenheit)-32)*5/9))) + " C" + sep + trailer
+   # hand off the resulting line
+   return line
+
 def sorted(data):
    """Return a sorted copy of a list."""
    new_copy = data[:]
    new_copy.sort()
    return new_copy
 
 def sorted(data):
    """Return a sorted copy of a list."""
    new_copy = data[:]
    new_copy.sort()
    return new_copy
 
-def get_url(url):
+def get_url(url, ignore_fail=False):
    """Return a string containing the results of a URL GET."""
    import urllib2
    try: return urllib2.urlopen(url).read()
    except urllib2.URLError:
    """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)
+      if ignore_fail: return ""
+      else:
+         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):
+def get_metar(
+   id,
+   verbose=False,
+   quiet=False,
+   headers=None,
+   murl=None,
+   imperial=False,
+   metric=False
+):
    """Return a summarized METAR for the specified station."""
    if not id:
       import sys
    """Return a summarized METAR for the specified station."""
    if not id:
       import sys
@@ -92,20 +187,92 @@ def get_metar(id, verbose=False, quiet=False, headers=None, murl=None):
       headerlist = headers.lower().replace("_"," ").split(",")
       output = []
       if not quiet:
       headerlist = headers.lower().replace("_"," ").split(",")
       output = []
       if not quiet:
-         output.append("Current conditions at " \
-            + lines[0].split(", ")[1] + " (" \
-            + id.upper() +")")
+         title = "Current conditions at %s"
+         place = lines[0].split(", ")
+         if len(place) > 1:
+            place = "%s, %s (%s)" % (titlecap(place[0]), place[1], id.upper())
+         else: place = id.upper()
+         output.append(title%place)
          output.append("Last updated " + lines[1])
       for header in headerlist:
          for line in lines:
             if line.lower().startswith(header + ":"):
          output.append("Last updated " + lines[1])
       for header in headerlist:
          for line in lines:
             if line.lower().startswith(header + ":"):
-               if line.endswith(":0"):
+               if line.endswith(":0") or line.endswith(":1"):
                   line = line[:-2]
                   line = line[:-2]
+               if imperial: line = filter_units(line, units="imperial")
+               elif metric: line = filter_units(line, units="metric")
                if quiet: output.append(line)
                else: output.append("   " + line)
       return "\n".join(output)
 
                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):
+def get_alert(
+   zone,
+   verbose=False,
+   quiet=False,
+   atype=None,
+   aurl=None,
+   imperial=False,
+   metric=False
+):
+   """Return alert notice for the specified zone and type."""
+   if not zone:
+      import sys
+      sys.stderr.write("weather: error: zone required for alerts\n")
+      sys.exit(1)
+   if not atype: atype = "severe_weather_stmt"
+   if not aurl:
+      aurl = \
+         "http://weather.noaa.gov/pub/data/watches_warnings/%atype%/%zone%.txt"
+   aurl = aurl.replace("%ATYPE%", atype.upper())
+   aurl = aurl.replace("%Atype%", atype.capitalize())
+   aurl = aurl.replace("%atypE%", atype)
+   aurl = aurl.replace("%atype%", atype.lower())
+   aurl = aurl.replace("%ZONE%", zone.upper())
+   aurl = aurl.replace("%Zone%", zone.capitalize())
+   aurl = aurl.replace("%zonE%", zone)
+   aurl = aurl.replace("%zone%", zone.lower())
+   aurl = aurl.replace(" ", "_")
+   alert = get_url(aurl, ignore_fail=True).strip()
+   if alert:
+      if verbose: return alert
+      else:
+         lines = alert.split("\n")
+         muted = True
+         import calendar, re, time
+         valid_time = time.strftime("%Y%m%d%H%M")
+         #if not quiet: output = [ lines[3], lines[5] ]
+         #if not quiet: output = [ lines[8], lines[10] ]
+         #else: output = []
+         output = []
+         for line in lines:
+            if line.startswith("Expires:") and "Expires:"+valid_time > line:
+               return ""
+            if muted and line.find("...") != -1:
+               muted = False
+            if line == "$$" \
+               or line.startswith("LAT...LON") \
+               or line.startswith("TIME...MOT...LOC"):
+               muted = True
+            if line and not (
+               muted \
+               or line == "&&"
+               or re.match("^/.*/$", line) \
+               or re.match("^"+zone.split("/")[1][:3].upper()+".*", line)
+            ):
+               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,
+   imperial=False,
+   metric=False
+):
    """Return the forecast for a specified city/st combination."""
    if not city or not st:
       import sys
    """Return the forecast for a specified city/st combination."""
    if not city or not st:
       import sys
@@ -131,6 +298,8 @@ def get_forecast(city, st, verbose=False, quiet=False, flines="0", furl=None):
       flines = int(flines)
       if not flines: flines = len(lines) - 5
       for line in lines[5:flines+5]:
       flines = int(flines)
       if not flines: flines = len(lines) - 5
       for line in lines[5:flines+5]:
+         if imperial: line = filter_units(line, units="imperial")
+         elif metric: line = filter_units(line, units="metric")
          if line.startswith("."):
             if quiet: output.append(line.replace(".", "", 1))
             else: output.append(line.replace(".", "   ", 1))
          if line.startswith("."):
             if quiet: output.append(line.replace(".", "", 1))
             else: output.append(line.replace(".", "   ", 1))
@@ -149,6 +318,50 @@ def get_options(config):
    import optparse
    option_parser = optparse.OptionParser(usage=usage, version=verstring)
 
    import optparse
    option_parser = optparse.OptionParser(usage=usage, version=verstring)
 
+   # the -a/--alert option
+   if config.has_option("default", "alert"):
+      default_alert = bool(config.get("default", "alert"))
+   else: default_alert = False
+   option_parser.add_option("-a", "--alert",
+      dest="alert",
+      action="store_true",
+      default=default_alert,
+      help="include local alert notices")
+
+   # the --atypes option
+   if config.has_option("default", "atypes"):
+      default_atypes = config.get("default", "atypes")
+   else:
+      default_atypes = \
+         "flash_flood/statement," \
+         + "flash_flood/warning," \
+         + "flash_flood/watch," \
+         + "flood/coastal," \
+         + "flood/statement," \
+         + "flood/warning," \
+         + "non_precip," \
+         + "severe_weather_stmt," \
+         + "special_weather_stmt," \
+         + "thunderstorm," \
+         + "tornado," \
+         + "urgent_weather_message"
+   option_parser.add_option("--atypes",
+      dest="atypes",
+      default=default_atypes,
+      help="alert notification types to display")
+
+   # the --aurl option
+   if config.has_option("default", "aurl"):
+      default_aurl = config.get("default", "aurl")
+   else:
+      default_aurl = \
+         "http://weather.noaa.gov/pub/data/watches_warnings/%atype%/%zone%.txt"
+   option_parser.add_option("--aurl",
+      dest="aurl",
+      default=default_aurl,
+      help="alert URL (including %atype% and %zone%)")
+
+   # separate options object from list of arguments and return both
    # the -c/--city option
    if config.has_option("default", "city"):
       default_city = config.get("default", "city")
    # the -c/--city option
    if config.has_option("default", "city"):
       default_city = config.get("default", "city")
@@ -213,6 +426,16 @@ def get_options(config):
       default=default_id,
       help="the METAR station ID (ex: KRDU)")
 
       default=default_id,
       help="the METAR station ID (ex: KRDU)")
 
+   # the --imperial option
+   if config.has_option("default", "imperial"):
+      default_imperial = bool(config.get("default", "imperial"))
+   else: default_imperial = False
+   option_parser.add_option("--imperial",
+      dest="imperial",
+      action="store_true",
+      default=default_imperial,
+      help="filter/convert for US/UK units")
+
    # the -l/--list option
    option_parser.add_option("-l", "--list",
       dest="list",
    # the -l/--list option
    option_parser.add_option("-l", "--list",
       dest="list",
@@ -220,6 +443,16 @@ def get_options(config):
       default=False,
       help="print a list of configured aliases")
 
       default=False,
       help="print a list of configured aliases")
 
+   # the -m/--metric option
+   if config.has_option("default", "metric"):
+      default_metric = bool(config.get("default", "metric"))
+   else: default_metric = False
+   option_parser.add_option("-m", "--metric",
+      dest="metric",
+      action="store_true",
+      default=default_metric,
+      help="filter/convert for metric units")
+
    # the --murl option
    if config.has_option("default", "murl"):
       default_murl = config.get("default", "murl")
    # the --murl option
    if config.has_option("default", "murl"):
       default_murl = config.get("default", "murl")
@@ -277,7 +510,15 @@ def get_options(config):
       default=default_verbose,
       help="show full decoded feeds (cancels -q)")
 
       default=default_verbose,
       help="show full decoded feeds (cancels -q)")
 
-   # separate options object from list of arguments and return both
+   # the -z/--zones option
+   if config.has_option("default", "zones"):
+      default_zones = config.get("default", "zones")
+   else: default_zones = ""
+   option_parser.add_option("-z", "--zones",
+      dest="zones",
+      default=default_zones,
+      help="alert zones (ex: nc/ncc183,nc/ncz041)")
+
    options, arguments = option_parser.parse_args()
    return options, arguments
 
    options, arguments = option_parser.parse_args()
    return options, arguments
 
index 60e7342..3fa719b 100644 (file)
--- a/weatherrc
+++ b/weatherrc
@@ -1,7 +1,6 @@
-# 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.
+# Copyright (c) 2006-2010 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]
 City = Allentown
 
 [ABE]
 City = Allentown
@@ -838,6 +837,11 @@ City = Peoria
 ID = KPIA
 St = IL
 
 ID = KPIA
 St = IL
 
+[PIE]
+City = Saint Petersburg
+ID = KPIE
+St = FL
+
 [PIH]
 City = Pocatello
 ID = KPIH
 [PIH]
 City = Pocatello
 ID = KPIH
@@ -848,6 +852,16 @@ City = Pittsburgh
 ID = KPIT
 St = PA
 
 ID = KPIT
 St = PA
 
+[PNC]
+City = Ponca City
+ID = KPNC
+St = OK
+
+[PNS]
+City = Pensacola
+ID = KPNS
+St = FL
+
 [PQI]
 City = Presque Isle
 ID = KPQI
 [PQI]
 City = Presque Isle
 ID = KPQI
index e5be871..d490d76 100644 (file)
@@ -1,5 +1,5 @@
-.TH WEATHERRC 5 "July 13, 2008" "" \" -*- nroff -*-
-\" Copyright (c) 2006-2008 Jeremy Stanley <fungi@yuggoth.org>.
+.TH WEATHERRC 5 "March 15, 2010" "" \" -*- nroff -*-
+\" Copyright (c) 2006-2010 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.
 \" Permission to use, copy, modify, and distribute this software is
 \" granted under terms provided in the LICENSE file distributed with
 \" this software.
@@ -19,6 +19,15 @@ Multi-word values do not need quoting.
 .SH PARAMETERS
 These parameters are supported...
 .TP
 .SH PARAMETERS
 These parameters are supported...
 .TP
+.B alert
+include local alert notices
+.TP
+.B atypes
+alert notification types to display
+.TP
+.B aurl
+alert URL (including %atype% and %zone%)
+.TP
 .B city
 the city name (ex: Raleigh Durham)
 .TP
 .B city
 the city name (ex: Raleigh Durham)
 .TP
@@ -40,6 +49,12 @@ the conditions headers to display (ex: temperature,wind)
 .B id
 the METAR station ID (ex: KRDU)
 .TP
 .B id
 the METAR station ID (ex: KRDU)
 .TP
+.B imperial
+filter/convert for US/UK units
+.TP
+.B metric
+filter/convert for metric units
+.TP
 .B murl
 METAR URL (ex: http://metar.org/%id%.txt)
 .TP
 .B murl
 METAR URL (ex: http://metar.org/%id%.txt)
 .TP
@@ -51,6 +66,9 @@ the state abbreviation (ex: NC)
 .TP
 .B verbose
 show full decoded feeds (possible values are False and True or 0 and 1)
 .TP
 .B verbose
 show full decoded feeds (possible values are False and True or 0 and 1)
+.TP
+.B zones
+alert zones (ex: nc/ncc183,nc/ncz041)
 .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
 .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