1.1 --- a/RecurrenceSupport.py Sat Jul 13 01:33:22 2013 +0200
1.2 +++ b/RecurrenceSupport.py Sat Jul 13 23:53:29 2013 +0200
1.3 @@ -17,6 +17,11 @@
1.4 repeating-recurrence = every [ <qualifier> ] <interval>
1.5 [ from <specific-recurrence> ]
1.6 [ until <specific-recurrence> ]
1.7 +
1.8 +Constraints:
1.9 +
1.10 + repeating-recurrence: if <qualifier> is not "single":
1.11 + from and/or until must be specified
1.12 """
1.13
1.14 from DateSupport import weekday_labels, month_labels
1.15 @@ -31,6 +36,12 @@
1.16 }
1.17
1.18 def isQualifier(qualifier, qualifiers):
1.19 +
1.20 + """
1.21 + Return whether the 'qualifier' is one of the given 'qualifiers', returning
1.22 + the level of the qualifier.
1.23 + """
1.24 +
1.25 if qualifiers.has_key(qualifier):
1.26 return qualifiers[qualifier]
1.27 if qualifier.endswith("th") or qualifier.endswith("nd") or qualifier.endswith("st"):
1.28 @@ -94,6 +105,9 @@
1.29 class ParseError(Exception):
1.30 pass
1.31
1.32 +class VerifyError(Exception):
1.33 + pass
1.34 +
1.35 class ParseIterator:
1.36 def __init__(self, iterator):
1.37 self.iterator = iterator
1.38 @@ -159,7 +173,7 @@
1.39 self.recurrence_type, self.qualifier, self.interval,
1.40 self.from_datetime and " from {%s}" % self.from_datetime or "",
1.41 self.until_datetime and " until {%s}" % self.until_datetime or "",
1.42 - self.qualified_by and ", in %s" % self.qualified_by or "")
1.43 + self.qualified_by and ", selecting %s" % self.qualified_by or "")
1.44
1.45 def __repr__(self):
1.46 return "Selector(%s, %s, %s%s%s%s>" % (
1.47 @@ -194,6 +208,15 @@
1.48 return current
1.49
1.50 def parseRecurrence(words, current):
1.51 +
1.52 + """
1.53 + Using the incoming 'words' and given the 'current' selector, parse a
1.54 + recurrence that can be either a repeating recurrence (starting with "every")
1.55 + or a specific recurrence (starting with "the").
1.56 +
1.57 + The active selector is returned as a result of parsing the recurrence.
1.58 + """
1.59 +
1.60 words.next()
1.61 if words.have("every"):
1.62 parseRepeatingRecurrence(words, current)
1.63 @@ -205,30 +228,52 @@
1.64 raise ParseError, words.tokens
1.65
1.66 def parseSpecificRecurrence(words, current):
1.67 +
1.68 + """
1.69 + Using the incoming 'words' and given the 'current' selector, parse and
1.70 + return a specific recurrence.
1.71 + """
1.72 +
1.73 + # Handle the qualifier and interval.
1.74 +
1.75 qualifier = words.next()
1.76 qualifier_level = isSpecificQualifier(qualifier)
1.77 if not qualifier_level:
1.78 raise ParseError, words.tokens
1.79 interval = words.next()
1.80 - interval_level = intervals.has_key(interval)
1.81 + interval_level = intervals.get(interval)
1.82 if not interval_level:
1.83 raise ParseError, words.tokens
1.84
1.85 current.add_details("the", qualifier, qualifier_level, interval, interval_level)
1.86
1.87 + # Detect the intervals becoming smaller.
1.88 +
1.89 + if current.qualified_by and current.interval_level < current.qualified_by.interval_level:
1.90 + raise ParseError, words.tokens
1.91 +
1.92 + # Define selectors that are being qualified by the above qualifier and
1.93 + # interval, returning the least specific selector.
1.94 +
1.95 words.want()
1.96 if words.have("in"):
1.97 current = Selector(current)
1.98 words.need("the")
1.99 - parseSpecificRecurrence(words, current)
1.100 + current = parseSpecificRecurrence(words, current)
1.101
1.102 return current
1.103
1.104 def parseRepeatingRecurrence(words, current):
1.105 +
1.106 + """
1.107 + Using the incoming 'words' and given the 'current' selector, parse and
1.108 + return a repeating recurrence.
1.109 + """
1.110 +
1.111 qualifier = words.next()
1.112 if intervals.has_key(qualifier):
1.113 interval = qualifier
1.114 - interval_level = intervals.has_key(interval)
1.115 + interval_level = intervals.get(interval)
1.116 qualifier = "single"
1.117 qualifier_level = isRepeatingQualifier(qualifier)
1.118 else:
1.119 @@ -236,17 +281,36 @@
1.120 if not qualifier_level:
1.121 raise ParseError, words.tokens
1.122 interval = words.next()
1.123 - interval_level = intervals.has_key(interval)
1.124 + interval_level = intervals.get(interval)
1.125 if not interval_level:
1.126 raise ParseError, words.tokens
1.127
1.128 current.add_details("every", qualifier, qualifier_level, interval, interval_level)
1.129
1.130 + # Detect the intervals becoming smaller.
1.131 +
1.132 + if current.qualified_by and current.interval_level < current.qualified_by.interval_level:
1.133 + raise ParseError, words.tokens
1.134 +
1.135 def parseOptionalLimits(words, current):
1.136 +
1.137 + """
1.138 + Using the incoming 'words' and given the 'current' selector, parse any
1.139 + optional limits, where these only apply to repeating occurrences.
1.140 + """
1.141 +
1.142 if current.recurrence_type == "every":
1.143 parseLimits(words, current)
1.144
1.145 def parseLimits(words, current):
1.146 +
1.147 + """
1.148 + Using the incoming 'words', parse any limits applying to the 'current'
1.149 + selector.
1.150 + """
1.151 +
1.152 + from_datetime = until_datetime = None
1.153 +
1.154 if words.have("from"):
1.155 words.need("the")
1.156 from_datetime = Selector()
1.157 @@ -259,4 +323,10 @@
1.158 until_datetime = parseSpecificRecurrence(words, until_datetime)
1.159 current.set_until(until_datetime)
1.160
1.161 + # Where the selector refers to a interval repeating at a frequency greater
1.162 + # than one, some limit must be specified to "anchor" the occurrences.
1.163 +
1.164 + elif not from_datetime and current.qualifier_level != 1:
1.165 + raise ParseError, words.tokens
1.166 +
1.167 # vim: tabstop=4 expandtab shiftwidth=4
2.1 --- a/tests/test_recurrence.py Sat Jul 13 01:33:22 2013 +0200
2.2 +++ b/tests/test_recurrence.py Sat Jul 13 23:53:29 2013 +0200
2.3 @@ -1,12 +1,91 @@
2.4 #!/usr/bin/env python
2.5
2.6 -from RecurrenceSupport import getRecurrence
2.7 +from RecurrenceSupport import getRecurrence, ParseError
2.8 +
2.9 +# Good recurrences.
2.10 +
2.11 +s = "every single day of the second month"
2.12 +print s
2.13 +print "->", getRecurrence(s)
2.14 +s = "every single day of the second month of every year"
2.15 +print s
2.16 +print "->", getRecurrence(s)
2.17 +s = "every other day from the first day of every other month from the third month of every year"
2.18 +print s
2.19 +print "->", getRecurrence(s)
2.20 +s = "the second day of every other month from the first month"
2.21 +print s
2.22 +print "->", getRecurrence(s)
2.23 +s = "the second hour in the second day in the second month of every year"
2.24 +print s
2.25 +print "->", getRecurrence(s)
2.26 +s = "every day from the second day until the 10th day of every other month until the fifth month"
2.27 +print s
2.28 +print "->", getRecurrence(s)
2.29 +s = "every day from the 10th day in the second month until the 10th day in the 10th month of every third month from the first month"
2.30 +print s
2.31 +print "->", getRecurrence(s)
2.32 +s = "every day of every third month from the 10th day in the second month until the 10th day in the 10th month"
2.33 +print s
2.34 +print "->", getRecurrence(s)
2.35 +
2.36 +# Bad recurrences.
2.37 +
2.38 +print
2.39 +
2.40 +# No start or end points.
2.41 +
2.42 +s = "every other day of every other month"
2.43 +print s
2.44 +try:
2.45 + print "->", getRecurrence(s)
2.46 +except ParseError, exc:
2.47 + print exc
2.48
2.49 -print getRecurrence("every other day of every other month")
2.50 -print getRecurrence("the second day of every other month")
2.51 -print getRecurrence("every other day of every other month from the third month of every year")
2.52 -print getRecurrence("every day from the second day until the 10th day of every other month")
2.53 -print getRecurrence("every day from the 10th day in the second month until the 10th day in the 10th month of every third month")
2.54 -print getRecurrence("every day of every third month from the 10th day in the second month until the 10th day in the 10th month")
2.55 +s = "every other day of every other month from the third month of every year"
2.56 +print s
2.57 +try:
2.58 + print "->", getRecurrence(s)
2.59 +except ParseError, exc:
2.60 + print exc
2.61 +
2.62 +s = "the second day of every other month"
2.63 +print s
2.64 +try:
2.65 + print "->", getRecurrence(s)
2.66 +except ParseError, exc:
2.67 + print exc
2.68 +
2.69 +s = "every day from the second day until the 10th day of every other month"
2.70 +print s
2.71 +try:
2.72 + print "->", getRecurrence(s)
2.73 +except ParseError, exc:
2.74 + print exc
2.75 +
2.76 +s = "every day from the 10th day in the second month until the 10th day in the 10th month of every third month"
2.77 +print s
2.78 +try:
2.79 + print "->", getRecurrence(s)
2.80 +except ParseError, exc:
2.81 + print exc
2.82 +
2.83 +# Syntax error: need to use "of" instead of "in".
2.84 +
2.85 +s = "every single day in the second month"
2.86 +print s
2.87 +try:
2.88 + print "->", getRecurrence(s)
2.89 +except ParseError, exc:
2.90 + print exc
2.91 +
2.92 +# Out of order: days do not contain months.
2.93 +
2.94 +s = "every other month from the first month of every other day from the first day"
2.95 +print s
2.96 +try:
2.97 + print "->", getRecurrence(s)
2.98 +except ParseError, exc:
2.99 + print exc
2.100
2.101 # vim: tabstop=4 expandtab shiftwidth=4