""" flightCal -- make hCalendar from text flight info :Author: `Dan Connolly`_ :Version: $Revision: 1.15 $ of $Date: 2006/04/21 07:29:00 $ :Copyright: `W3C Open Source License`_ Share and enjoy. .. _Dan Connolly: http://www.w3.org/People/Connolly/ .. _W3C Open Source License: http://www.w3.org/Consortium/Legal/2002/copyright-software-20021231 Note: we use str.rsplit() which is new in python2.4 Usage ----- Run a la:: python2.4 title flightinfo.html where flightinfo.txt is a text flight itinerary in a format produced, I think, by Sabre_. .. _Sabre: http://en.wikipedia.org/wiki/Sabre_%28computer_system%29 Testing and Colophon -------------------- The examples in the docstrings below are executable doctest_ unit tests. Check them a la:: python flightCal.py --test .. _doctest: http://www.python.org/doc/lib/module-doctest.html This module is documented in rst_ format for use with epydoc_. .. _epydoc: http://epydoc.sourceforge.net/ .. _rst: http://docutils.sourceforge.net/docs/user/rst/quickstart.html """ import re from xml.sax.saxutils import escape as xmldata import aptdata # from http://dev.w3.org/cvsweb/2001/palmagent/ __version__ = "$Id: flightCal.py,v 1.15 2006/04/21 07:29:00 connolly Exp $" __docformat__ = 'restructuredtext en' TestData = """ 25 FEB 06 - SATURDAY AIR AMERICAN AIRLINES FLT:1557 ECONOMY LV KANSAS CITY INTL 512P EQP: MD-80 DEPART: TERMINAL BUILDING C 01HR 33MIN AR CHICAGO OHARE 645P NON-STOP ARRIVE: TERMINAL 3 REF: JKLWXS CONNOLLY/DANIEL SEAT-26F AA-XDW5282 AIR AMERICAN AIRLINES FLT:98 ECONOMY MULTI MEALS LV CHICAGO OHARE 1025P EQP: BOEING 777 DEPART: TERMINAL 3 07HR 35MIN 26 FEB 06 - SUNDAY AR LONDON HEATHROW 1200N NON-STOP ARRIVE: TERMINAL 3 REF: JKLWXS CONNOLLY/DANIEL SEAT-34J AA-XDW5282 AIR AMERICAN AIRLINES FLT:6580 ECONOMY MEALS OPERATED BY BRITISH AIRWAYS LV LONDON HEATHROW 415P EQP: BOEING 757 DEPART: TERMINAL 1 02HR 00MIN AR NICE 715P NON-STOP ARRIVE: AEROGARE 1 REF: JKLWXS """ class Template(object): def __init__(self, title, events): self.title = title self.events = events def generate(self): yield DocTop % {'title': xmldata(self.title)} for e in self.events: yield "\n" e['url_x'] = xmldata(e['url']) # a bit of a kludge... yield DateCell % e if e['ARLV'] == 'LV': yield DepartCell % e else: if not e.has_key('SEAT'): e['SEAT'] = '?' yield ArriveCell % e if e.has_key('location_vcard'): where = e['location_vcard'] where['geo_lat'] = where['geo']['latitude'] # kludge again... where['geo_lon'] = where['geo']['longitude'] where['extended-address'] = where.get('adr', {}).get('extended-address', '') yield GeoCell % where else: yield LocationCell % e yield "\n" yield DocBottom % self DocTop = u""" %(title)s\

%(title)s

""" DateCell = u""" """ DepartCell = u""" """ ArriveCell = u""" """ GeoCell = u""" """ LocationCell = u""" """ DocBottom = u"""
DateSummaryLocation
%(date)s %(carrier)s FLT:%(FLT)d
%(ARLV)s
%(day)s %(time)s
%(EQP)s
%(carrier)s FLT:%(FLT)d
%(ARLV)s
%(day)s %(time)s
SEAT: %(SEAT)s
%(org)s (%(nickname)s)
%(extended-address)s
%(location)s
generated using flightCal.py
See Makefile and other nearby files for details.
""" def progress(*args): import sys for a in args: sys.stderr.write('%s ' % a) sys.stderr.write("\n") def flights(lines, web=None): overnight = {} for date, day, daylines in splitDays(lines): for ev in splitEvents(date, day, daylines, overnight): fill(ev, web) yield ev overnight = ev def fill(ev, web=None): ev['dtstart'] = "%sT%s" % (ev['date'], stdTime(ev['time'])) ev['url'] = fltlink(ev['carrier'], ev['FLT']) airport = {'org': ev['location'], 'fn': ev['location']} iata_nick(airport) if web and airport.has_key('nickname'): aptdata.airportCard(web, airport['nickname'], airport) progress("looked up airport:", airport) if airport.has_key('geo'): where = airport txt = ev.get('DEPART', None) or ev.get('ARRIVE', None) or None if txt: where = {'adr': {}} # new, more specialized location where.update(airport) # would be nice to fill in country-name from wikipedia too where['adr']['extended-address'] = txt ev['location_vcard'] = where # hmm... we throw away url if geo not found. maybe keep it? def iata_nick(card): """Set nickname to the iata code if the org field of a card is a known airport name """ org = card['org'] for iata, n in aptdata.Airports: if org == n: card['nickname'] = iata def locationCell(w, loc): for iata, n in aptdata.Airports: if loc == n: where = aptdata.airportCard(iata) where['geo_lat'] = where['geo']['latitude'] where['geo_lon'] = where['geo']['longitude'] w(" %(org)s (%(nickname)s)\n" % where) return else: w(" %s\n" % \ (loc,)) def stdDate(ddmmyy): """ >>> stdDate("04 MAR 06") '2006-03-04' """ day, month, year = ddmmyy.split() year = 2000 + int(year) month = ['JAN', 'FEB', 'MAR', 'APR', 'MAY', 'JUN', 'JUL', 'AUG', 'SEP', 'OCT', 'NOV', 'DEC'].index(month) + 1 day = int(day) return "%04d-%02d-%02d" % (year, month, day) def stdTime(hhmm): """ >>> stdTime("423P") '16:23:00' """ min = int(hhmm[-3:-1]) hr = int(hhmm[:-3]) if hhmm.endswith("P"): hr += 12 sec = 0 tz = '' # local time return "%02d:%02d:%02d%s" % (hr, min, sec, tz) def whenElt(ddmmyy, hhmm, elt='abbr', prop='dtstart'): """ >>> whenElt("04 MAR 06", "423P") "423P" note: we assume post-y2k dates and local time """ day, month, year = ddmmyy.split() year = 2000 + int(year) month = ['JAN', 'FEB', 'MAR', 'APR', 'MAY', 'JUN', 'JUL', 'AUG', 'SEP', 'OCT', 'NOV', 'DEC'].index(month) + 1 day = int(day) min = int(hhmm[-3:-1]) hr = int(hhmm[:-3]) if hhmm.endswith("P"): hr += 12 sec = 0 tz = '' # local time return "<%s class='%s' title='%04d-%02d-%02dT%02d:%02d:%02d%s'>%s" % \ (elt, prop, year, month, day, hr, min, sec, tz, hhmm, elt) def fltlink(carrier, num): """ >>> fltlink('AMERICAN AIRLINES', 1557) 'https://bwi.flightview.com/fvSabreVT/fvCPL.exe?vthost=1W&acid=1557&qtype=htm&AL=AA&Find1=Track+Flight' """ airlines = {'AMERICAN AIRLINES': 'AA', # http://en.wikipedia.org/wiki/Midwest_Airlines 'MIDWEST AIRLINES': 'YX' } airlineCode = airlines[carrier] action = 'https://bwi.flightview.com/fvSabreVT/fvCPL.exe' params = {'AL': airlineCode, 'acid': str(num), 'qtype': 'htm', 'Find1': 'Track+Flight', 'vthost': '1W' } q = '&'.join(['%s=%s' % (n, v) for n, v in params.iteritems()]) return "%s?%s" % (action, q) def splitDays(lines): r""" split lines in to (yyyy_mm_dd, day, daylines) tuples >>> len(list(splitDays(TestData.split("\n")))) 2 >>> len(splitDays(TestData.split("\n")).next()[2]) 10 >>> splitDays(TestData.split("\n")).next()[0] '2006-02-25' >>> i = splitDays(TestData.split("\n")); dummy=i.next(); i.next()[0] '2006-02-26' """ date, day, daylines = None, None, [] for ln in lines: m = re.search("(\d\d \w\w\w \d\d) - ([A-Z]+)$", ln) if m: if daylines: yield (date, day, daylines) daylines = [] date, day = stdDate(m.group(1)), m.group(2) elif date: daylines.append(ln) if daylines: yield (date, day, daylines) def splitEvents(date, day, lines, flt={}): r""" split daylines into AR/LV events >>> days = splitDays(TestData.split("\n")); \ ... date, day, lines = days.next(); \ ... events = splitEvents(date, day, lines); \ ... e=events.next(); (e['carrier'], e['FLT'], e['note'], e['dur']) ('AMERICAN AIRLINES', 1557, 'ECONOMY', '01HR 33MIN') >>> days = splitDays(TestData.split("\n")); \ ... date, day, lines = days.next(); \ ... events = splitEvents(date, day, lines); \ ... d=events.next(),events.next(); e=events.next();e['FLT'] 98 """ lines = iter(lines) while 1: try: ln = lines.next() except StopIteration: return ln = ln.strip() if ln.startswith("AIR"): flt = flightLine(ln) flt['date'] = date flt['day'] = day elif ln.startswith("LV"): e = dict([(k, flt[k]) for k in ('carrier', 'FLT', 'note', 'date', 'day')]) e.update(lvLine(ln)) depline = lines.next() e['dur'] = depline.strip()[-10:] e['DEPART'] = depline[depline.index(':')+1:-11].strip() e['ARLV'] = 'LV' yield e elif ln.startswith("AR"): e = dict([(k, flt[k]) for k in ('carrier', 'FLT', 'note', 'date', 'day')]) e.update(arLine(ln)) txt, e['REF'] = lines.next().strip().split("REF: ") e['ARRIVE'] = txt.strip().split("ARRIVE: ")[1] txt = lines.next() if txt.find('SEAT-') >= 0: e['SEAT'] = txt.split('SEAT-')[1].split(' ', 1)[0] e['note'] = txt + e['note'] e['ARLV'] = 'AR' yield e def flightLine(ln): """ >>> flightLine("AIR AMERICAN AIRLINES FLT:1557 ECONOMY") == \\ ... {'carrier': 'AMERICAN AIRLINES', 'FLT': 1557, 'note': 'ECONOMY'} True >>> flightLine("AIR AMERICAN AIRLINES FLT:98 ECONOMY MULTI MEALS") == \\ ... {'carrier': 'AMERICAN AIRLINES', 'FLT': 98, 'note': 'ECONOMY MULTI MEALS'} True """ i = ln[4:].index("FLT:") carrier = ln[4:i].strip() flt = ln[i:].split()[0] notes = ln[i+4+len(flt):].strip() flt = int(flt.split(':')[1]) return {'carrier': carrier, 'FLT': flt, 'note': notes} def lvLine(ln): r""" >>> lvLine("LV KANSAS CITY INTL 512P EQP: MD-80") ==\ ... {'location': 'KANSAS CITY INTL', 'time': '512P', 'EQP': 'MD-80'} True """ i = ln.index("EQP:") eqp = ln[i+5:] apt, when = ln[3:i].strip().rsplit(None, 1) return {'location': apt, 'time': when, 'EQP': eqp} def arLine(ln): """ >>> arLine("AR CHICAGO OHARE 645P NON-STOP") == \\ ... {'location': 'CHICAGO OHARE', 'time': '645P', 'note': 'NON-STOP'} True """ apt, when, cls = ln[3:].rsplit(None, 2) return {'location': apt, 'time': when, 'note': cls} def _test(): import doctest doctest.testmod() if __name__ == '__main__': import sys if '--test' in sys.argv: _test() else: events = flights(sys.stdin, aptdata.WebCache("wikipedia-cache").get) t = Template(sys.argv[1], events) for s in t.generate(): sys.stdout.write(s.encode('utf-8'))