Script to grab stop codes from allstops.xml and sort
[ottawa-travel-planner.git] / CommandParser.py
blobe44ce50ccb26b958399df8d9b9fc039e4e840059
2 # vi: set softtabstop=4 shiftwidth=4 tabstop=8 expandtab:
4 """Splits a command into source, destination, and time."""
6 import re
7 import time
9 import PlanTime
10 import PlanLocation
11 import LandmarkMatcher
13 class Command:
14 def __init__(self):
15 self.start = None
16 self.end = None
17 self.time = None
19 class CommandParseException(Exception):
20 pass
22 class CommandParser:
23 def __init__(self, str):
24 self.cmd = None
25 self.landmarkMatcher = None
27 self.parse(str)
29 def parse(self, str):
30 # Look for start and end at the start of the string.
31 match = _full_rx.match(str)
32 if not match:
33 raise CommandParseException("Failed to extract source and "
34 "destination from command '%s'" % str);
35 self.cmd = Command()
36 self.cmd.start = self.parseLocation(match.group("start"))
37 self.cmd.end = self.parseLocation(match.group("end"))
39 # Now look for time constraints.
40 rule = match.group("rule")
41 timestr = match.group("time")
42 if rule and timestr:
43 ruleMap = {
44 "by": PlanTime.MUST_ARRIVE_BEFORE,
45 "at": PlanTime.MUST_LEAVE_AFTER,
47 self.cmd.time \
48 = PlanTime.PlanTime(self.decodeTime(timestr), ruleMap[rule])
50 def parseLocation(self, str):
51 match = _intersection_rx.match(str)
52 if match:
53 return PlanLocation.IntersectionLocation(match.group(1),
54 match.group(2))
56 match = _stop_rx.match(str)
57 if match:
58 return PlanLocation.StopLocation(match.group(1))
60 match = _address_rx.match(str)
61 if match:
62 return PlanLocation.AddressLocation(str)
64 # Try a landmark.
65 if self.landmarkMatcher is None:
66 self.landmarkMatcher = LandmarkMatcher.LandmarkMatcher()
67 return PlanLocation.LandmarkLocation(self.landmarkMatcher.match(str))
69 def decodeTime(self, str):
70 match = _time_rx.match(str)
71 if not match:
72 raise CommandParseException("Unable to decode timespec '%s'" % str)
74 hour = int(match.group("hour"))
76 min = 0
77 minstr = match.group("min")
78 if minstr is not None:
79 min = int(minstr)
81 mod = match.group("mod")
83 if hour < 0 or hour > 23 or min < 0 or min > 59:
84 raise CommandParseException("Invalid numbers in time '%s'" % str)
86 if mod is not None and hour < 12 and mod.lower() == "pm":
87 hour += 12
89 t = list(time.localtime())
90 t[3] = hour
91 t[4] = min
92 return time.mktime(t)
94 _full_re = ("(?i)^\s*(?P<start>.*?)\s+to\s+(?P<end>.*?)\s*"
95 # at this point the pattern either ends, or there's a timespec
96 # Require the time to have at least one digit so people can still
97 # use "at" for intersections (although "and" is better)
98 "(?:$|(?P<rule>by|at)\s+(?P<time>\d.*?)\s*$)")
99 _full_rx = re.compile(_full_re)
101 _intersection_re = "(?i)^(.+?)\s+(?:at|and)\s*(.+?)$"
102 _intersection_rx = re.compile(_intersection_re)
104 # "stop 6037" or just "6037"
105 _stop_re = "(?i)^(?:stop)?\s*(\d{4})$"
106 _stop_rx = re.compile(_stop_re)
108 # "123 some street"
109 _address_re = '(?i)^\d+\s+\S.*'
110 _address_rx = re.compile(_address_re)
112 # 11:30
113 # 11:30 pm
114 # 1130 pm
115 # 130 pm
116 # 130
117 _time_re = ("^(?P<hour>\d{1,2}):?(?P<min>\d\d)?\s*(?i)(?P<mod>am?|pm?)?$")
118 _time_rx = re.compile(_time_re)