def test_parse_exclusions_list_spaces(self): """Tests the simple case of exclusion parsing""" expression = "PT1H! (T03, T06, T09) " sequence, exclusion = parse_exclusion(expression) self.assertEqual(sequence, "PT1H") self.assertEqual(exclusion, ['T03', 'T06', 'T09'])
def test_parse_exclusion_simple(self): """Tests the simple case of exclusion parsing""" expression = "PT1H!20000101T02Z" sequence, exclusion = parse_exclusion(expression) self.assertEqual(sequence, "PT1H") self.assertEqual(exclusion, ['20000101T02Z'])
def test_parse_exclusions_list(self): """Tests the simple case of exclusion parsing""" expression = "PT1H!(T03, T06, T09)" sequence, exclusion = parse_exclusion(expression) self.assertEqual(sequence, "PT1H") self.assertEqual(exclusion, ['T03', 'T06', 'T09'])
def parse_recurrence(self, expression, context_start_point=None, context_end_point=None): """Parse an expression in abbrev. or full ISO recurrence format.""" expression, exclusion = parse_exclusion(expression) if context_start_point is None: context_start_point = self.context_start_point if context_end_point is None: context_end_point = self.context_end_point for rec_object, format_num in self._recur_format_recs: result = rec_object.search(expression) if not result: continue props = {} repetitions = result.groupdict().get("reps") if repetitions is not None: repetitions = int(repetitions) start = result.groupdict().get("start") end = result.groupdict().get("end") start_required = (format_num in [1, 3]) end_required = (format_num in [1, 4]) start_point, start_offset = self._get_point_from_expression( start, context_start_point, is_required=start_required, allow_truncated=True) try: end_point, end_offset = self._get_point_from_expression( end, context_end_point, is_required=end_required, allow_truncated=True) except CylcMissingContextPointError: raise CylcMissingFinalCyclePointError( "This suite requires a final cycle point.") exclusion_point = None if exclusion: exclusion_point, excl_off = self._get_point_from_expression( exclusion, None, is_required=False, allow_truncated=False) if excl_off: exclusion_point += excl_off intv = result.groupdict().get("intv") intv_context_truncated_point = None if start_point is not None and start_point.truncated: intv_context_truncated_point = start_point if end_point is not None and end_point.truncated: intv_context_truncated_point = end_point interval = self._get_interval_from_expression( intv, context=intv_context_truncated_point) if format_num == 1: interval = None if repetitions == 1: # Set arbitrary interval (does not matter). interval = self.duration_parser.parse("P0Y") if start_point is not None: if start_point.truncated: start_point += context_start_point if start_offset is not None: start_point += start_offset if end_point is not None: if end_point.truncated: end_point += context_end_point if end_offset is not None: end_point += end_offset if (start_point is None and repetitions is None and interval is not None and context_start_point is not None): # isodatetime only reverses bounded end-point recurrences. # This is unbounded, and will come back in reverse order. # We need to reverse it. start_point = end_point repetitions = 1 while start_point > context_start_point: start_point -= interval repetitions += 1 end_point = None return isodatetime.data.TimeRecurrence( repetitions=repetitions, start_point=start_point, duration=interval, end_point=end_point), exclusion_point raise CylcTimeSyntaxError("Could not parse %s" % expression)
def __init__(self, dep_section, p_context_start, p_context_stop=None): """Parse state (start, stop, interval) from a graph section heading. The start and stop points are always on-sequence, context points might not be. If computed start and stop points are out of bounds, they will be set to None. Context is used only initially to define the sequence bounds.""" SequenceBase.__init__(self, dep_section, p_context_start, p_context_stop) # start context always exists self.p_context_start = IntegerPoint(p_context_start) # stop context may exist if p_context_stop: self.p_context_stop = IntegerPoint(p_context_stop) else: self.p_context_stop = None # state variables: start, stop, and step self.p_start = None self.p_stop = None self.i_step = None # offset must be stored to compute the runahead limit self.i_offset = IntegerInterval('P0') self.exclusions = None matched_recurrence = False expression, excl_points = parse_exclusion(dep_section) for rec, format_num in RECURRENCE_FORMAT_RECS: results = rec.match(expression) if not results: continue matched_recurrence = True reps = results.groupdict().get("reps") if reps is not None: reps = int(reps) start = results.groupdict().get("start") stop = results.groupdict().get("end") intv = results.groupdict().get("intv") if not start: start = None if not stop: stop = None if not intv: intv = None start_required = (format_num in [1, 3]) end_required = (format_num in [1, 4]) break if not matched_recurrence: raise ValueError("ERROR, bad integer cycling format: %s" % expression) self.p_start = get_point_from_expression(start, self.p_context_start, is_required=start_required) self.p_stop = get_point_from_expression(stop, self.p_context_stop, is_required=end_required) if intv: self.i_step = IntegerInterval(intv) if format_num == 3: # REPEAT/START/PERIOD if not intv or reps is not None and reps <= 1: # one-off self.i_step = None self.p_stop = self.p_start else: self.i_step = IntegerInterval(intv) if reps: self.p_stop = self.p_start + self.i_step * (reps - 1) elif self.p_context_stop: # stop at the point <= self.p_context_stop # use p_start as an on-sequence reference remainder = (int(self.p_context_stop - self.p_start) % int(self.i_step)) self.p_stop = (self.p_context_stop - IntegerInterval.from_integer(remainder)) elif format_num == 1: # REPEAT/START/STOP if reps == 1: # one-off: ignore stop point self.i_step = None self.p_stop = self.p_start else: self.i_step = IntegerInterval.from_integer( int(self.p_stop - self.p_start) / (reps - 1)) else: # This means that format_num == 4. # REPEAT/PERIOD/STOP if reps is not None: if reps <= 1: # one-off self.p_start = self.p_stop self.i_step = None else: self.i_step = IntegerInterval(intv) self.p_start = (self.p_stop - self.i_step * (reps - 1)) else: remainder = (int(self.p_context_stop - self.p_start) % int(self.i_step)) self.p_start = (self.p_context_start - IntegerInterval.from_integer(remainder)) if self.i_step and self.i_step < IntegerInterval.get_null(): # (TODO - this should be easy to handle but needs testing) raise ValueError( "ERROR, negative intervals not supported yet: %s" % self.i_step) if self.i_step and self.p_start < self.p_context_start: # start from first point >= context start remainder = (int(self.p_context_start - self.p_start) % int(self.i_step)) self.p_start = (self.p_context_start + IntegerInterval.from_integer(remainder)) # if i_step is None here, points will just be None (out of bounds) if (self.i_step and self.p_stop and self.p_context_stop and self.p_stop > self.p_context_stop): # stop at first point <= context stop remainder = (int(self.p_context_stop - self.p_start) % int(self.i_step)) self.p_stop = (self.p_context_stop - self.i_step + IntegerInterval.from_integer(remainder)) # if i_step is None here, points will just be None (out of bounds) # Create a list of multiple exclusion points, if there are any. if excl_points: self.exclusions = IntegerExclusions(excl_points, self.p_start, self.p_stop) else: self.exclusions = None
def __init__(self, dep_section, p_context_start, p_context_stop=None): """Parse state (start, stop, interval) from a graph section heading. The start and stop points are always on-sequence, context points might not be. If computed start and stop points are out of bounds, they will be set to None. Context is used only initially to define the sequence bounds.""" SequenceBase.__init__( self, dep_section, p_context_start, p_context_stop) # start context always exists self.p_context_start = IntegerPoint(p_context_start) # stop context may exist if p_context_stop: self.p_context_stop = IntegerPoint(p_context_stop) else: self.p_context_stop = None # state variables: start, stop, and step self.p_start = None self.p_stop = None self.i_step = None # offset must be stored to compute the runahead limit self.i_offset = IntegerInterval('P0') self.exclusions = None matched_recurrence = False expression, excl_points = parse_exclusion(dep_section) for rec, format_num in RECURRENCE_FORMAT_RECS: results = rec.match(expression) if not results: continue matched_recurrence = True reps = results.groupdict().get("reps") if reps is not None: reps = int(reps) start = results.groupdict().get("start") stop = results.groupdict().get("end") intv = results.groupdict().get("intv") if not start: start = None if not stop: stop = None if not intv: intv = None start_required = (format_num in [1, 3]) end_required = (format_num in [1, 4]) break if not matched_recurrence: raise ValueError( "ERROR, bad integer cycling format: %s" % expression) self.p_start = get_point_from_expression( start, self.p_context_start, is_required=start_required) self.p_stop = get_point_from_expression( stop, self.p_context_stop, is_required=end_required) if intv: self.i_step = IntegerInterval(intv) if format_num == 3: # REPEAT/START/PERIOD if not intv or reps is not None and reps <= 1: # one-off self.i_step = None self.p_stop = self.p_start else: self.i_step = IntegerInterval(intv) if reps: self.p_stop = self.p_start + self.i_step * (reps - 1) elif self.p_context_stop: # stop at the point <= self.p_context_stop # use p_start as an on-sequence reference remainder = (int(self.p_context_stop - self.p_start) % int(self.i_step)) self.p_stop = ( self.p_context_stop - IntegerInterval.from_integer( remainder) ) elif format_num == 1: # REPEAT/START/STOP if reps == 1: # one-off: ignore stop point self.i_step = None self.p_stop = self.p_start else: self.i_step = IntegerInterval.from_integer( int(self.p_stop - self.p_start) / (reps - 1) ) else: # This means that format_num == 4. # REPEAT/PERIOD/STOP if reps is not None: if reps <= 1: # one-off self.p_start = self.p_stop self.i_step = None else: self.i_step = IntegerInterval(intv) self.p_start = ( self.p_stop - self.i_step * (reps - 1)) else: remainder = (int(self.p_context_stop - self.p_start) % int(self.i_step)) self.p_start = ( self.p_context_start - IntegerInterval.from_integer( remainder) ) if self.i_step and self.i_step < IntegerInterval.get_null(): # (TODO - this should be easy to handle but needs testing) raise ValueError( "ERROR, negative intervals not supported yet: %s" % self.i_step ) if self.i_step and self.p_start < self.p_context_start: # start from first point >= context start remainder = ( int(self.p_context_start - self.p_start) % int(self.i_step)) self.p_start = ( self.p_context_start + IntegerInterval.from_integer( remainder) ) # if i_step is None here, points will just be None (out of bounds) if (self.i_step and self.p_stop and self.p_context_stop and self.p_stop > self.p_context_stop): # stop at first point <= context stop remainder = ( int(self.p_context_stop - self.p_start) % int(self.i_step)) self.p_stop = ( self.p_context_stop - self.i_step + IntegerInterval.from_integer(remainder) ) # if i_step is None here, points will just be None (out of bounds) # Create a list of multiple exclusion points, if there are any. if excl_points: self.exclusions = IntegerExclusions(excl_points, self.p_start, self.p_stop) else: self.exclusions = None
def parse_recurrence(self, expression, context_start_point=None, context_end_point=None): """Parse an expression in abbrev. or full ISO recurrence format.""" expression, exclusion = parse_exclusion(expression) if context_start_point is None: context_start_point = self.context_start_point if context_end_point is None: context_end_point = self.context_end_point for rec_object, format_num in self._recur_format_recs: result = rec_object.search(expression) if not result: continue props = {} repetitions = result.groupdict().get("reps") if repetitions is not None: repetitions = int(repetitions) start = result.groupdict().get("start") end = result.groupdict().get("end") start_required = format_num in [1, 3] end_required = format_num in [1, 4] start_point, start_offset = self._get_point_from_expression( start, context_start_point, is_required=start_required, allow_truncated=True ) try: end_point, end_offset = self._get_point_from_expression( end, context_end_point, is_required=end_required, allow_truncated=True ) except CylcMissingContextPointError: raise CylcMissingFinalCyclePointError("This suite requires a final cycle point.") exclusion_point = None if exclusion: exclusion_point, excl_off = self._get_point_from_expression( exclusion, None, is_required=False, allow_truncated=False ) if excl_off: exclusion_point += excl_off intv = result.groupdict().get("intv") intv_context_truncated_point = None if start_point is not None and start_point.truncated: intv_context_truncated_point = start_point if end_point is not None and end_point.truncated: intv_context_truncated_point = end_point interval = self._get_interval_from_expression(intv, context=intv_context_truncated_point) if format_num == 1: interval = None if repetitions == 1: # Set arbitrary interval (does not matter). interval = self.duration_parser.parse("P0Y") if start_point is not None: if start_point.truncated: start_point += context_start_point if start_offset is not None: start_point += start_offset if end_point is not None: if end_point.truncated: end_point += context_end_point if end_offset is not None: end_point += end_offset if start_point is None and repetitions is None and interval is not None and context_start_point is not None: # isodatetime only reverses bounded end-point recurrences. # This is unbounded, and will come back in reverse order. # We need to reverse it. start_point = end_point repetitions = 1 while start_point > context_start_point: start_point -= interval repetitions += 1 end_point = None return ( isodatetime.data.TimeRecurrence( repetitions=repetitions, start_point=start_point, duration=interval, end_point=end_point ), exclusion_point, ) raise CylcTimeSyntaxError("Could not parse %s" % expression)