# HG changeset patch # User Paul Boddie # Date 1373752409 -7200 # Node ID eba69418c451d9a0442a79da84f746a322e99323 # Parent 66479525a9fc3c0144974f64eed6a15add31d06c Fixed interval level setting on selectors and retention of specific recurrence details. Added detection of inappropriate interval qualification (where a broader selector qualifies a narrower selector). diff -r 66479525a9fc -r eba69418c451 RecurrenceSupport.py --- a/RecurrenceSupport.py Sat Jul 13 01:33:22 2013 +0200 +++ b/RecurrenceSupport.py Sat Jul 13 23:53:29 2013 +0200 @@ -17,6 +17,11 @@ repeating-recurrence = every [ ] [ from ] [ until ] + +Constraints: + + repeating-recurrence: if is not "single": + from and/or until must be specified """ from DateSupport import weekday_labels, month_labels @@ -31,6 +36,12 @@ } def isQualifier(qualifier, qualifiers): + + """ + Return whether the 'qualifier' is one of the given 'qualifiers', returning + the level of the qualifier. + """ + if qualifiers.has_key(qualifier): return qualifiers[qualifier] if qualifier.endswith("th") or qualifier.endswith("nd") or qualifier.endswith("st"): @@ -94,6 +105,9 @@ class ParseError(Exception): pass +class VerifyError(Exception): + pass + class ParseIterator: def __init__(self, iterator): self.iterator = iterator @@ -159,7 +173,7 @@ self.recurrence_type, self.qualifier, self.interval, self.from_datetime and " from {%s}" % self.from_datetime or "", self.until_datetime and " until {%s}" % self.until_datetime or "", - self.qualified_by and ", in %s" % self.qualified_by or "") + self.qualified_by and ", selecting %s" % self.qualified_by or "") def __repr__(self): return "Selector(%s, %s, %s%s%s%s>" % ( @@ -194,6 +208,15 @@ return current def parseRecurrence(words, current): + + """ + Using the incoming 'words' and given the 'current' selector, parse a + recurrence that can be either a repeating recurrence (starting with "every") + or a specific recurrence (starting with "the"). + + The active selector is returned as a result of parsing the recurrence. + """ + words.next() if words.have("every"): parseRepeatingRecurrence(words, current) @@ -205,30 +228,52 @@ raise ParseError, words.tokens def parseSpecificRecurrence(words, current): + + """ + Using the incoming 'words' and given the 'current' selector, parse and + return a specific recurrence. + """ + + # Handle the qualifier and interval. + qualifier = words.next() qualifier_level = isSpecificQualifier(qualifier) if not qualifier_level: raise ParseError, words.tokens interval = words.next() - interval_level = intervals.has_key(interval) + interval_level = intervals.get(interval) if not interval_level: raise ParseError, words.tokens current.add_details("the", qualifier, qualifier_level, interval, interval_level) + # Detect the intervals becoming smaller. + + if current.qualified_by and current.interval_level < current.qualified_by.interval_level: + raise ParseError, words.tokens + + # Define selectors that are being qualified by the above qualifier and + # interval, returning the least specific selector. + words.want() if words.have("in"): current = Selector(current) words.need("the") - parseSpecificRecurrence(words, current) + current = parseSpecificRecurrence(words, current) return current def parseRepeatingRecurrence(words, current): + + """ + Using the incoming 'words' and given the 'current' selector, parse and + return a repeating recurrence. + """ + qualifier = words.next() if intervals.has_key(qualifier): interval = qualifier - interval_level = intervals.has_key(interval) + interval_level = intervals.get(interval) qualifier = "single" qualifier_level = isRepeatingQualifier(qualifier) else: @@ -236,17 +281,36 @@ if not qualifier_level: raise ParseError, words.tokens interval = words.next() - interval_level = intervals.has_key(interval) + interval_level = intervals.get(interval) if not interval_level: raise ParseError, words.tokens current.add_details("every", qualifier, qualifier_level, interval, interval_level) + # Detect the intervals becoming smaller. + + if current.qualified_by and current.interval_level < current.qualified_by.interval_level: + raise ParseError, words.tokens + def parseOptionalLimits(words, current): + + """ + Using the incoming 'words' and given the 'current' selector, parse any + optional limits, where these only apply to repeating occurrences. + """ + if current.recurrence_type == "every": parseLimits(words, current) def parseLimits(words, current): + + """ + Using the incoming 'words', parse any limits applying to the 'current' + selector. + """ + + from_datetime = until_datetime = None + if words.have("from"): words.need("the") from_datetime = Selector() @@ -259,4 +323,10 @@ until_datetime = parseSpecificRecurrence(words, until_datetime) current.set_until(until_datetime) + # Where the selector refers to a interval repeating at a frequency greater + # than one, some limit must be specified to "anchor" the occurrences. + + elif not from_datetime and current.qualifier_level != 1: + raise ParseError, words.tokens + # vim: tabstop=4 expandtab shiftwidth=4 diff -r 66479525a9fc -r eba69418c451 tests/test_recurrence.py --- a/tests/test_recurrence.py Sat Jul 13 01:33:22 2013 +0200 +++ b/tests/test_recurrence.py Sat Jul 13 23:53:29 2013 +0200 @@ -1,12 +1,91 @@ #!/usr/bin/env python -from RecurrenceSupport import getRecurrence +from RecurrenceSupport import getRecurrence, ParseError + +# Good recurrences. + +s = "every single day of the second month" +print s +print "->", getRecurrence(s) +s = "every single day of the second month of every year" +print s +print "->", getRecurrence(s) +s = "every other day from the first day of every other month from the third month of every year" +print s +print "->", getRecurrence(s) +s = "the second day of every other month from the first month" +print s +print "->", getRecurrence(s) +s = "the second hour in the second day in the second month of every year" +print s +print "->", getRecurrence(s) +s = "every day from the second day until the 10th day of every other month until the fifth month" +print s +print "->", getRecurrence(s) +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" +print s +print "->", getRecurrence(s) +s = "every day of every third month from the 10th day in the second month until the 10th day in the 10th month" +print s +print "->", getRecurrence(s) + +# Bad recurrences. + +print + +# No start or end points. + +s = "every other day of every other month" +print s +try: + print "->", getRecurrence(s) +except ParseError, exc: + print exc -print getRecurrence("every other day of every other month") -print getRecurrence("the second day of every other month") -print getRecurrence("every other day of every other month from the third month of every year") -print getRecurrence("every day from the second day until the 10th day of every other month") -print getRecurrence("every day from the 10th day in the second month until the 10th day in the 10th month of every third month") -print getRecurrence("every day of every third month from the 10th day in the second month until the 10th day in the 10th month") +s = "every other day of every other month from the third month of every year" +print s +try: + print "->", getRecurrence(s) +except ParseError, exc: + print exc + +s = "the second day of every other month" +print s +try: + print "->", getRecurrence(s) +except ParseError, exc: + print exc + +s = "every day from the second day until the 10th day of every other month" +print s +try: + print "->", getRecurrence(s) +except ParseError, exc: + print exc + +s = "every day from the 10th day in the second month until the 10th day in the 10th month of every third month" +print s +try: + print "->", getRecurrence(s) +except ParseError, exc: + print exc + +# Syntax error: need to use "of" instead of "in". + +s = "every single day in the second month" +print s +try: + print "->", getRecurrence(s) +except ParseError, exc: + print exc + +# Out of order: days do not contain months. + +s = "every other month from the first month of every other day from the first day" +print s +try: + print "->", getRecurrence(s) +except ParseError, exc: + print exc # vim: tabstop=4 expandtab shiftwidth=4