Imported from archive.
[weather.git] / weather.py
1 # weather.py version 1.1, http://fungi.yuggoth.org/weather/
2 # Copyright (c) 2006 Jeremy Stanley <fungi@yuggoth.org>, all rights reserved.
3 # Licensed per terms in the LICENSE file distributed with this software.
4
5 """Contains various object definitions needed by the weather utility."""
6
7 version = "1.1"
8
9 class Selections:
10         """An object to contain selection data."""
11         def __init__(self):
12                 """Store the config, options and arguments."""
13                 self.config = get_config()
14                 self.options, self.arguments = get_options(self.config)
15                 if self.arguments:
16                         self.arguments = [(x.lower()) for x in self.arguments]
17                 else: self.arguments = [ None ]
18         def get(self, option, argument=None):
19                 """Retrieve data from the config or options."""
20                 if not argument: return self.options.__dict__[option]
21                 elif not self.config.has_section(argument):
22                         import sys
23                         sys.stderr.write("ERROR: no alias defined for " \
24                                 + argument + "\n")
25                         sys.exit(1)
26                 elif self.config.has_option(argument, option):
27                         return self.config.get(argument, option)
28                 else: return self.options.__dict__[option]
29         def get_bool(self, option, argument=None):
30                 """Get data and coerce to a boolean if necessary."""
31                 return bool(self.get(option, argument))
32
33 def bool(data):
34         """Coerce data to a boolean value."""
35         if type(data) is str:
36                 if eval(data): return True
37                 else: return False
38         else:
39                 if data: return True
40                 else: return False
41
42 def quote(words):
43         """Wrap a string in quotes if it contains spaces."""
44         if words.find(" ") != -1: words = "\"" + words + "\""
45         return words
46
47 def sorted(data):
48         """Return a sorted copy of a list."""
49         new_copy = data[:]
50         new_copy.sort()
51         return new_copy
52
53 def get_url(url):
54         """Return a string containing the results of a URL GET."""
55         import urllib
56         return urllib.urlopen(url).read()
57
58 def get_metar(id, verbose=False):
59         """Return a summarized METAR for the specified station."""
60         metar = get_url(
61                 "http://weather.noaa.gov/pub/data/observations/metar/decoded/" \
62                         + id.upper() + ".TXT")
63         if verbose: return metar
64         else:
65                 lines = metar.split("\n")
66                 headings = [
67                         "Relative Humidity",
68                         "Precipitation last hour",
69                         "Sky conditions",
70                         "Temperature",
71                         "Weather",
72                         "Wind" 
73                         ]
74                 output = []
75                 output.append("Current conditions at " \
76                         + lines[0].split(", ")[1] + " (" \
77                         + id.upper() +")")
78                 output.append("Last updated " + lines[1])
79                 for line in lines:
80                         for heading in headings:
81                                 if line.startswith(heading + ":"):
82                                         if line.endswith(":0"):
83                                                 line = line[:-2]
84                                         output.append("   " + line)
85                 return "\n".join(output)
86
87 def get_forecast(city, st, verbose=False):
88         """Return the forecast for a specified city/st combination."""
89         forecast = get_url("http://weather.noaa.gov/pub/data/forecasts/city/" \
90                 + st.lower() + "/" + city.lower().replace(" ", "_") \
91                 + ".txt")
92         if verbose: return forecast
93         else:
94                 lines = forecast.split("\n")
95                 output = []
96                 output.append(lines[2])
97                 output.append(lines[3])
98                 for line in lines:
99                         if line.startswith("."):
100                                 output.append(line.replace(".", "   ", 1))
101                 return "\n".join(output)
102
103 def get_options(config):
104         """Parse the options passed on the command line."""
105         import optparse
106         usage = "usage: %prog [ options ] [ alias [ alias [...] ] ]"
107         verstring = "%prog " + version
108         option_parser = optparse.OptionParser(usage=usage, version=verstring)
109         if config.has_option("default", "city"):
110                 default_city = config.get("default", "city")
111         else: default_city = "Raleigh Durham"
112         option_parser.add_option("-c", "--city",
113                 dest="city",
114                 default=default_city,
115                 help="the city name (ex: \"Raleigh Durham\")")
116         if config.has_option("default", "forecast"):
117                 default_forecast = bool(config.get("default", "forecast"))
118         else: default_forecast = False
119         option_parser.add_option("-f", "--forecast",
120                 dest="forecast",
121                 action="store_true",
122                 default=default_forecast,
123                 help="include a local forecast")
124         if config.has_option("default", "id"):
125                 default_id = config.get("default", "id")
126         else: default_id = "KRDU"
127         option_parser.add_option("-i", "--id",
128                 dest="id",
129                 default=default_id,
130                 help="the METAR station ID (ex: KRDU)")
131         option_parser.add_option("-l", "--list",
132                 dest="list",
133                 action="store_true",
134                 default=False,
135                 help="print a list of configured aliases")
136         if config.has_option("default", "conditions"):
137                 default_conditions = bool(config.get("default", "conditions"))
138         else: default_conditions = True
139         option_parser.add_option("-n", "--no-conditions",
140                 dest="conditions",
141                 action="store_false",
142                 default=default_conditions,
143                 help="disable output of current conditions (forces -f)")
144         option_parser.add_option("-o", "--omit-forecast",
145                 dest="forecast",
146                 action="store_false",
147                 default=default_forecast,
148                 help="omit the local forecast (cancels -f)")
149         if config.has_option("default", "st"):
150                 default_st = config.get("default", "st")
151         else: default_st = "NC"
152         option_parser.add_option("-s", "--st",
153                 dest="st",
154                 default=default_st,
155                 help="the state abbreviation (ex: NC)")
156         if config.has_option("default", "verbose"):
157                 default_verbose = bool(config.get("default", "verbose"))
158         else: default_verbose = False
159         option_parser.add_option("-v", "--verbose",
160                 dest="verbose",
161                 action="store_true",
162                 default=default_verbose,
163                 help="show full decoded feeds")
164         options, arguments = option_parser.parse_args()
165         return options, arguments
166
167 def get_config():
168         """Parse the aliases and configuration."""
169         import ConfigParser
170         config = ConfigParser.ConfigParser()
171         import os.path
172         rcfiles = [
173                 "/etc/weatherrc",
174                 os.path.expanduser("~/.weatherrc"),
175                 "weatherrc"
176                 ]
177         import os
178         for rcfile in rcfiles:
179                 if os.access(rcfile, os.R_OK): config.read(rcfile)
180         for section in config.sections():
181                 if section != section.lower():
182                         if config.has_section(section.lower()):
183                                 config.remove_section(section.lower())
184                         config.add_section(section.lower())
185                         for option,value in config.items(section):
186                                 config.set(section.lower(), option, value)
187         return config
188
189 def list_aliases(config):
190         """Return a formatted list of aliases defined in the config."""
191         sections = []
192         for section in sorted(config.sections()):
193                 if section.lower() not in sections and section != "default":
194                         sections.append(section.lower())
195         output = "configured aliases..."
196         for section in sorted(sections):
197                 output += "\n   " \
198                         + section \
199                         + ": --id=" \
200                         + quote(config.get(section, "id")) \
201                         + " --city=" \
202                         + quote(config.get(section, "city")) \
203                         + " --st=" \
204                         + quote(config.get(section, "st"))
205         return output
206