Initial revision
[ottawa-travel-planner.git] / Planner.py
blobd625f7125d4704c75bf87335c1a1396a6fd69e6c
2 # vi: set softtabstop=4 shiftwidth=4 tabstop=8 expandtab:
4 """A frontend for the OC Transpo Travel Planner."""
6 import cookielib
7 import urllib2
8 import urllib
9 import sre
11 def plan(start, end, time):
12 """Plans a route between two Locations at a certain PlanTime."""
13 planner = TravelPlannerClient()
14 planner.feedStartLocation(start)
15 planner.feedEndLocation(end)
16 planner.feedTime(time)
18 class TravelPlannerException(Exception):
19 """Generic parent exception for anything thrown by us, as opposed to
20 things thrown by the network layer."""
22 def _init_(self, value):
23 Exception._init_(self, value)
25 class InvalidLocationException(TravelPlannerException):
26 """Thrown when a source or destination can't be found.
28 The "args" value of the exception is the original Location."""
30 def _init(self, location):
31 TravelPlannerException._init_(self, location)
33 class TravelPlannerClient:
34 def _init_(self):
35 # Set up a cookie-aware client.
36 self.cj = cookielib.CookieJar()
37 self.opener = urllib2.build_opener(urllib2.HTTPCookieProcessor(self.cj))
39 # Initialize the session.
40 r = self._sendRequest(self.START_PAGE, None)
42 def feedStartLocation(self, loc):
43 # name="tp_fromAddress" method="get" action="FromAddress.oci"
44 # fromAddress text
45 # streetType=Other
46 params = (("fromAddress", loc), ("streetType", "Other"))
47 r = self._sendRequest("FromAddress.oci", params)
49 def feedEndLocation(self, loc):
50 params = (("toAddress", loc), ("streetType", "Other"))
51 r = self._sendRequest("ToAddress.oci", params)
53 def feedTime(self, time):
54 # name="tp_time" action="SelectTime.oci"
55 # dateSelect="
56 # requestCode=3 for must leave after
57 # time
58 # timePeriod=am or pm
59 r = self._sendRequest("SelectTime.oci", time.toPlannerParams())
61 for line in r:
62 print line.rstrip()
64 def _sendRequest(self, page, params):
65 url = self.URL_BASE + page
66 if params is not None:
67 url += "?" + urllib.urlencode(params)
69 print dir(self)
70 response = self.opener.open(url)
71 self._checkObviousBadness(response)
73 html = self._grabLimitedResponse(response)
74 self._scanForError(html)
75 return html
77 def _checkObviousBadness(self, response):
78 if response.code != 200:
79 raise TravelPlannerException("Got HTTP " + response.code
80 + " error from server")
81 if "errorPage.oci" in response.geturl():
82 raise TravelPlannerException("Redirected to error page")
84 def _grabLimitedResponse(self, response):
85 count = 0
87 accum = ""
88 for line in response:
89 count += len(line)
90 if (count <= self.RESPONSE_SIZE_LIMIT):
91 accum += line
92 else:
93 break
94 return accum
98 # Regular expressions: probably the worst way to parse HTML
99 # Probably the best thing to put in your breakfast cereal.
101 def _scanForError(self, text):
102 match = error_rx.search(text)
103 if match:
104 raise TravelPlannerException(match.group("msg"))
106 URL_BASE = "http://www.octranspo.com/tps/jnot/"
107 START_PAGE = "startEN.oci"
108 RESPONSE_SIZE_LIMIT = 100000
112 # Scan for an error.
113 # <table cellpadding="0" cellspacing="0" summary="Warning message" class="warning" width="85%">
114 # <tr>
115 # <td><img src="tripPlanning/images/imgWarning.gif"></td>
116 # <td>The address you specified was not found. Please enter another.</td>
117 # </tr>
118 # </table>
119 _error_re = ('<table[^>]*class="(?:warning|error)[^>]*>\s*'
120 '<tr>(?:\s*<td>\s*<img[^>]*>\s*</td>)?'
121 # ?s: DOTALL: . matches \n
122 # *? is non-greedy
123 '\s*<td>(?s)\s*(?P<msg>[\d\D]*?)\s*</td>')
124 _error_rx = sre.compile(_error_re)