1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
1.2 +++ b/tests/qualifiers.py Sat Oct 04 00:06:07 2014 +0200
1.3 @@ -0,0 +1,305 @@
1.4 +#!/usr/bin/env python
1.5 +
1.6 +from vRecurrence import *
1.7 +
1.8 +def show(l):
1.9 + for x in l:
1.10 + print x
1.11 + print
1.12 +
1.13 +qualifiers = [
1.14 + ("YEARLY", {"interval" : 1})
1.15 + ]
1.16 +
1.17 +l = order_qualifiers(qualifiers)
1.18 +show(l)
1.19 +dt = (1997, 11, 2)
1.20 +l = get_datetime_structure(dt)
1.21 +show(l)
1.22 +l = combine_datetime_with_qualifiers(dt, qualifiers)
1.23 +show(l)
1.24 +
1.25 +s = process(l)
1.26 +l = s.materialise((2003, 12, 24))
1.27 +print len(l) == 7, len(l)
1.28 +print l[0] == (1997, 11, 2), l[0]
1.29 +print l[-1] == (2003, 11, 2), l[-1]
1.30 +print
1.31 +
1.32 +qualifiers = [
1.33 + ("YEARLY", {"interval" : 2}),
1.34 + ("BYMONTH", {"values" : [1]}),
1.35 + ("BYDAY", {"values" : [6]}),
1.36 + ("BYHOUR", {"values" : [8, 9]}),
1.37 + ("BYMINUTE", {"values" : [30]})
1.38 + ]
1.39 +
1.40 +l = order_qualifiers(qualifiers)
1.41 +show(l)
1.42 +dt = (1997, 1, 5, 8, 30, 0)
1.43 +l = get_datetime_structure(dt)
1.44 +show(l)
1.45 +l = combine_datetime_with_qualifiers(dt, qualifiers)
1.46 +show(l)
1.47 +
1.48 +s = process(l)
1.49 +l = s.materialise((2003, 12, 24, 0, 0, 0))
1.50 +print len(l) == 34, len(l)
1.51 +print l[0] == (1997, 1, 4, 8, 30, 0), l[0]
1.52 +print l[-1] == (2003, 1, 25, 9, 30, 0), l[-1]
1.53 +print
1.54 +
1.55 +qualifiers = [
1.56 + ("DAILY", {"interval" : 1})
1.57 + ]
1.58 +
1.59 +l = order_qualifiers(qualifiers)
1.60 +show(l)
1.61 +dt = (1997, 9, 2, 9, 0, 0)
1.62 +l = get_datetime_structure(dt)
1.63 +show(l)
1.64 +l = combine_datetime_with_qualifiers(dt, qualifiers)
1.65 +show(l)
1.66 +
1.67 +s = process(l)
1.68 +l = s.materialise((1997, 12, 24), 10)
1.69 +print len(l) == 10, len(l)
1.70 +print l[0] == (1997, 9, 2, 9, 0, 0), l[0]
1.71 +print l[-1] == (1997, 9, 11, 9, 0, 0), l[-1]
1.72 +print
1.73 +
1.74 +qualifiers = [
1.75 + ("DAILY", {"interval" : 1})
1.76 + ]
1.77 +
1.78 +l = order_qualifiers(qualifiers)
1.79 +show(l)
1.80 +dt = (1997, 9, 2, 9, 0, 0)
1.81 +l = get_datetime_structure(dt)
1.82 +show(l)
1.83 +l = combine_datetime_with_qualifiers(dt, qualifiers)
1.84 +show(l)
1.85 +
1.86 +s = process(l)
1.87 +l = s.materialise((1997, 12, 24, 0, 0, 0))
1.88 +print len(l) == 113, len(l)
1.89 +print l[0] == (1997, 9, 2, 9, 0, 0), l[0]
1.90 +print l[-1] == (1997, 12, 23, 9, 0, 0), l[-1]
1.91 +print
1.92 +
1.93 +qualifiers = [
1.94 + ("DAILY", {"interval" : 2})
1.95 + ]
1.96 +
1.97 +l = order_qualifiers(qualifiers)
1.98 +show(l)
1.99 +dt = (1997, 9, 2, 9, 0, 0)
1.100 +l = get_datetime_structure(dt)
1.101 +show(l)
1.102 +l = combine_datetime_with_qualifiers(dt, qualifiers)
1.103 +show(l)
1.104 +
1.105 +s = process(l)
1.106 +l = s.materialise((1997, 12, 24, 0, 0, 0))
1.107 +print len(l) == 57, len(l)
1.108 +print l[0] == (1997, 9, 2, 9, 0, 0), l[0]
1.109 +print l[-1] == (1997, 12, 23, 9, 0, 0), l[-1]
1.110 +print
1.111 +
1.112 +qualifiers = [
1.113 + ("WEEKLY", {"interval" : 1})
1.114 + ]
1.115 +
1.116 +l = order_qualifiers(qualifiers)
1.117 +show(l)
1.118 +dt = (1997, 9, 2, 9, 0, 0)
1.119 +l = get_datetime_structure(dt)
1.120 +show(l)
1.121 +l = combine_datetime_with_qualifiers(dt, qualifiers)
1.122 +show(l)
1.123 +
1.124 +s = process(l)
1.125 +l = s.materialise((1997, 12, 24, 0, 0, 0))
1.126 +print len(l) == 17, len(l)
1.127 +print l[0] == (1997, 9, 2, 9, 0, 0), l[0]
1.128 +print l[-1] == (1997, 12, 23, 9, 0, 0), l[-1]
1.129 +print
1.130 +
1.131 +qualifiers = [
1.132 + ("DAILY", {"interval" : 10})
1.133 + ]
1.134 +
1.135 +l = order_qualifiers(qualifiers)
1.136 +show(l)
1.137 +dt = (1997, 9, 2, 9, 0, 0)
1.138 +l = get_datetime_structure(dt)
1.139 +show(l)
1.140 +l = combine_datetime_with_qualifiers(dt, qualifiers)
1.141 +show(l)
1.142 +
1.143 +s = process(l)
1.144 +l = s.materialise((1997, 12, 24, 0, 0, 0), 5)
1.145 +print len(l) == 5, len(l)
1.146 +print l[0] == (1997, 9, 2, 9, 0, 0), l[0]
1.147 +print l[-1] == (1997, 10, 12, 9, 0, 0), l[-1]
1.148 +print
1.149 +
1.150 +qualifiers = [
1.151 + ("YEARLY", {"interval" : 1}),
1.152 + ("BYMONTH", {"values" : [1]}),
1.153 + ("BYDAY", {"values" : [1, 2, 3, 4, 5, 6, 7]})
1.154 + ]
1.155 +
1.156 +l = order_qualifiers(qualifiers)
1.157 +show(l)
1.158 +dt = (1998, 1, 1, 9, 0, 0)
1.159 +l = get_datetime_structure(dt)
1.160 +show(l)
1.161 +l = combine_datetime_with_qualifiers(dt, qualifiers)
1.162 +show(l)
1.163 +
1.164 +s = process(l)
1.165 +l = s.materialise((2000, 1, 31, 14, 0, 0))
1.166 +print len(l) == 93, len(l)
1.167 +print l[0] == (1998, 1, 1, 9, 0, 0), l[0]
1.168 +print l[-1] == (2000, 1, 31, 9, 0, 0), l[-1]
1.169 +print
1.170 +
1.171 +qualifiers = [
1.172 + ("DAILY", {"interval" : 1}),
1.173 + ("BYMONTH", {"values" : [1]})
1.174 + ]
1.175 +
1.176 +l = order_qualifiers(qualifiers)
1.177 +show(l)
1.178 +dt = (1998, 1, 1, 9, 0, 0)
1.179 +l = get_datetime_structure(dt)
1.180 +show(l)
1.181 +l = combine_datetime_with_qualifiers(dt, qualifiers)
1.182 +show(l)
1.183 +
1.184 +s = process(l)
1.185 +l = s.materialise((2000, 1, 31, 14, 0, 0))
1.186 +print len(l) == 93, len(l)
1.187 +print l[0] == (1998, 1, 1, 9, 0, 0), l[0]
1.188 +print l[-1] == (2000, 1, 31, 9, 0, 0), l[-1]
1.189 +print
1.190 +
1.191 +qualifiers = [
1.192 + ("WEEKLY", {"interval" : 1})
1.193 + ]
1.194 +
1.195 +l = order_qualifiers(qualifiers)
1.196 +show(l)
1.197 +dt = (1997, 9, 2, 9, 0, 0)
1.198 +l = get_datetime_structure(dt)
1.199 +show(l)
1.200 +l = combine_datetime_with_qualifiers(dt, qualifiers)
1.201 +show(l)
1.202 +
1.203 +s = process(l)
1.204 +l = s.materialise((1997, 12, 24, 0, 0, 0), 10)
1.205 +print len(l) == 10, len(l)
1.206 +print l[0] == (1997, 9, 2, 9, 0, 0), l[0]
1.207 +print l[-1] == (1997, 11, 4, 9, 0, 0), l[-1]
1.208 +print
1.209 +
1.210 +qualifiers = [
1.211 + ("WEEKLY", {"interval" : 1})
1.212 + ]
1.213 +
1.214 +l = order_qualifiers(qualifiers)
1.215 +show(l)
1.216 +dt = (1997, 9, 2, 9, 0, 0)
1.217 +l = get_datetime_structure(dt)
1.218 +show(l)
1.219 +l = combine_datetime_with_qualifiers(dt, qualifiers)
1.220 +show(l)
1.221 +
1.222 +s = process(l)
1.223 +l = s.materialise((1997, 12, 24, 0, 0, 0))
1.224 +print len(l) == 17, len(l)
1.225 +print l[0] == (1997, 9, 2, 9, 0, 0), l[0]
1.226 +print l[-1] == (1997, 12, 23, 9, 0, 0), l[-1]
1.227 +print
1.228 +
1.229 +qualifiers = [
1.230 + ("WEEKLY", {"interval" : 2})
1.231 + ]
1.232 +
1.233 +l = order_qualifiers(qualifiers)
1.234 +show(l)
1.235 +dt = (1997, 9, 2, 9, 0, 0)
1.236 +l = get_datetime_structure(dt)
1.237 +show(l)
1.238 +l = combine_datetime_with_qualifiers(dt, qualifiers)
1.239 +show(l)
1.240 +
1.241 +s = process(l)
1.242 +l = s.materialise((1998, 2, 20, 0, 0, 0))
1.243 +print len(l) == 13, len(l)
1.244 +print l[0] == (1997, 9, 2, 9, 0, 0), l[0]
1.245 +print l[-1] == (1998, 2, 17, 9, 0, 0), l[-1]
1.246 +print
1.247 +
1.248 +qualifiers = [
1.249 + ("WEEKLY", {"interval" : 1}),
1.250 + ("BYDAY", {"values" : [2, 4]})
1.251 + ]
1.252 +
1.253 +l = order_qualifiers(qualifiers)
1.254 +show(l)
1.255 +dt = (1997, 9, 2, 9, 0, 0)
1.256 +l = get_datetime_structure(dt)
1.257 +show(l)
1.258 +l = combine_datetime_with_qualifiers(dt, qualifiers)
1.259 +show(l)
1.260 +
1.261 +s = process(l)
1.262 +l = s.materialise((1997, 10, 7, 9, 0, 0))
1.263 +print len(l) == 10, len(l)
1.264 +print l[0] == (1997, 9, 2, 9, 0, 0), l[0]
1.265 +print l[-1] == (1997, 10, 2, 9, 0, 0), l[-1]
1.266 +print
1.267 +
1.268 +qualifiers = [
1.269 + ("WEEKLY", {"interval" : 1}),
1.270 + ("BYDAY", {"values" : [2, 4]})
1.271 + ]
1.272 +
1.273 +l = order_qualifiers(qualifiers)
1.274 +show(l)
1.275 +dt = (1997, 9, 2, 9, 0, 0)
1.276 +l = get_datetime_structure(dt)
1.277 +show(l)
1.278 +l = combine_datetime_with_qualifiers(dt, qualifiers)
1.279 +show(l)
1.280 +
1.281 +s = process(l)
1.282 +l = s.materialise((1997, 12, 24, 0, 0, 0), 10)
1.283 +print len(l) == 10, len(l)
1.284 +print l[0] == (1997, 9, 2, 9, 0, 0), l[0]
1.285 +print l[-1] == (1997, 10, 2, 9, 0, 0), l[-1]
1.286 +print
1.287 +
1.288 +qualifiers = [
1.289 + ("WEEKLY", {"interval" : 2}),
1.290 + ("BYDAY", {"values" : [1, 3, 5]})
1.291 + ]
1.292 +
1.293 +l = order_qualifiers(qualifiers)
1.294 +show(l)
1.295 +dt = (1997, 9, 1, 9, 0, 0)
1.296 +l = get_datetime_structure(dt)
1.297 +show(l)
1.298 +l = combine_datetime_with_qualifiers(dt, qualifiers)
1.299 +show(l)
1.300 +
1.301 +s = process(l)
1.302 +l = s.materialise((1997, 12, 24, 0, 0, 0))
1.303 +print len(l) == 25, len(l)
1.304 +print l[0] == (1997, 9, 1, 9, 0, 0), l[0]
1.305 +print l[-1] == (1997, 12, 22, 9, 0, 0), l[-1]
1.306 +print
1.307 +
1.308 +# vim: tabstop=4 expandtab shiftwidth=4
2.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
2.2 +++ b/vRecurrence.py Sat Oct 04 00:06:07 2014 +0200
2.3 @@ -0,0 +1,364 @@
2.4 +#!/usr/bin/env python
2.5 +
2.6 +"""
2.7 +Recurrence rule calculation.
2.8 +
2.9 +Copyright (C) 2014 Paul Boddie <paul@boddie.org.uk>
2.10 +
2.11 +This program is free software; you can redistribute it and/or modify it under
2.12 +the terms of the GNU General Public License as published by the Free Software
2.13 +Foundation; either version 3 of the License, or (at your option) any later
2.14 +version.
2.15 +
2.16 +This program is distributed in the hope that it will be useful, but WITHOUT
2.17 +ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
2.18 +FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
2.19 +details.
2.20 +
2.21 +You should have received a copy of the GNU General Public License along with
2.22 +this program. If not, see <http://www.gnu.org/licenses/>.
2.23 +
2.24 +----
2.25 +
2.26 +References:
2.27 +
2.28 +RFC 5545: Internet Calendaring and Scheduling Core Object Specification
2.29 + (iCalendar)
2.30 + http://tools.ietf.org/html/rfc5545
2.31 +
2.32 +----
2.33 +
2.34 +FREQ defines the selection resolution.
2.35 +DTSTART defines the start of the selection.
2.36 +INTERVAL defines the step of the selection.
2.37 +COUNT defines a number of instances; UNTIL defines a limit to the selection.
2.38 +
2.39 +BY... qualifiers select instances within each outer selection instance according
2.40 +to the recurrence of instances of the next highest resolution. For example,
2.41 +BYDAY selects days in weeks. Thus, if no explicit week recurrence is indicated,
2.42 +all weeks are selected within the selection of the next highest explicitly
2.43 +specified resolution, whether this is months or years.
2.44 +
2.45 +BYSETPOS in conjunction with BY... qualifiers permit the selection of specific
2.46 +instances.
2.47 +
2.48 +Within the FREQ resolution, BY... qualifiers refine selected instances.
2.49 +
2.50 +Outside the FREQ resolution, BY... qualifiers select instances at the resolution
2.51 +concerned.
2.52 +
2.53 +Thus, FREQ and BY... qualifiers need to be ordered in terms of increasing
2.54 +resolution (or decreasing scope).
2.55 +"""
2.56 +
2.57 +from datetime import date, datetime, timedelta
2.58 +import operator
2.59 +
2.60 +# Frequency levels, specified by FREQ in iCalendar.
2.61 +
2.62 +freq_levels = (
2.63 + "SECONDLY",
2.64 + "MINUTELY",
2.65 + "HOURLY",
2.66 + "DAILY",
2.67 + "WEEKLY",
2.68 + "MONTHLY",
2.69 + "YEARLY"
2.70 + )
2.71 +
2.72 +# Enumeration levels, employed by BY... qualifiers.
2.73 +
2.74 +enum_levels = (
2.75 + ("BYSECOND",),
2.76 + ("BYMINUTE",),
2.77 + ("BYHOUR",),
2.78 + ("BYDAY", "BYMONTHDAY", "BYYEARDAY"),
2.79 + ("BYWEEKNO",),
2.80 + ("BYMONTH",)
2.81 + )
2.82 +
2.83 +special_enum_levels = ("BYDAY", "BYMONTHDAY", "BYYEARDAY")
2.84 +
2.85 +# Map from levels to lengths of datetime tuples.
2.86 +
2.87 +lengths = [6, 5, 4, 3, 3, 2, 1]
2.88 +positions = [l-1 for l in lengths]
2.89 +
2.90 +# Map from qualifiers to interval units. Here, weeks are defined as 7 days.
2.91 +
2.92 +units = {"WEEKLY" : 7}
2.93 +
2.94 +# Map from tuple positions to base values at each resolution.
2.95 +
2.96 +bases = [0, 1, 1, 0, 0, 0]
2.97 +
2.98 +def basevalue(resolution):
2.99 + return bases[positions[resolution]]
2.100 +
2.101 +# Make dictionaries mapping qualifiers to levels.
2.102 +
2.103 +freq = dict([(level, i) for (i, level) in enumerate(freq_levels)])
2.104 +enum = dict([(level, i) for (i, levels) in enumerate(enum_levels) for level in levels])
2.105 +
2.106 +# Functions for structuring the recurrences.
2.107 +
2.108 +def get_next(it):
2.109 + try:
2.110 + return it.next()
2.111 + except StopIteration:
2.112 + return None
2.113 +
2.114 +def order_qualifiers(qualifiers):
2.115 +
2.116 + "Return the 'qualifiers' in order of increasing resolution."
2.117 +
2.118 + l = []
2.119 +
2.120 + for qualifier, args in qualifiers:
2.121 + if enum.has_key(qualifier):
2.122 + level = enum[qualifier]
2.123 + if qualifier in special_enum_levels:
2.124 + args["interval"] = 1
2.125 + selector = PatternFilter
2.126 + else:
2.127 + selector = Enum
2.128 + else:
2.129 + level = freq[qualifier]
2.130 + selector = Pattern
2.131 +
2.132 + pos = positions[level]
2.133 + l.append(selector(pos, args, qualifier))
2.134 +
2.135 + l.sort(key=lambda x: x.pos)
2.136 + return l
2.137 +
2.138 +def get_datetime_structure(datetime):
2.139 +
2.140 + "Return the structure of 'datetime' for recurrence production."
2.141 +
2.142 + l = []
2.143 + for pos, value in enumerate(datetime):
2.144 + l.append(Enum(pos, {"values" : [value]}, "DT"))
2.145 + return l
2.146 +
2.147 +def combine_datetime_with_qualifiers(datetime, qualifiers):
2.148 +
2.149 + """
2.150 + Combine 'datetime' with 'qualifiers' to produce a structure for recurrence
2.151 + production.
2.152 + """
2.153 +
2.154 + iter_dt = iter(get_datetime_structure(datetime))
2.155 + iter_q = iter(order_qualifiers(qualifiers))
2.156 +
2.157 + l = []
2.158 +
2.159 + from_dt = get_next(iter_dt)
2.160 + from_q = get_next(iter_q)
2.161 +
2.162 + have_q = False
2.163 + context = []
2.164 +
2.165 + # Consume from both lists, merging entries.
2.166 +
2.167 + while from_dt and from_q:
2.168 + _pos = from_dt.pos
2.169 + pos = from_q.pos
2.170 +
2.171 + # Datetime value at wider resolution.
2.172 +
2.173 + if _pos < pos:
2.174 + if have_q:
2.175 + l.append(from_dt)
2.176 + else:
2.177 + context.append(from_dt.args["values"][0])
2.178 + from_dt = get_next(iter_dt)
2.179 +
2.180 + # Qualifier at wider or same resolution as datetime value.
2.181 +
2.182 + else:
2.183 + if not have_q:
2.184 + if isinstance(from_q, Enum) and pos > 0:
2.185 + repeat = Pattern(pos - 1, {"interval" : 1, "values" : [context[-1]]}, "REPEAT")
2.186 + repeat.context = tuple(context[:-1])
2.187 + l.append(repeat)
2.188 + else:
2.189 + from_q.context = tuple(context)
2.190 + have_q = True
2.191 +
2.192 + # Either introduce the qualifier first.
2.193 +
2.194 + if _pos > pos:
2.195 + l.append(from_q)
2.196 +
2.197 + # Or combine the qualifier and value details.
2.198 +
2.199 + else:
2.200 + l.append(combine_value_with_qualifier(from_dt, from_q))
2.201 + from_dt = get_next(iter_dt)
2.202 +
2.203 + from_q = get_next(iter_q)
2.204 +
2.205 + # Complete the list.
2.206 +
2.207 + while from_dt:
2.208 + l.append(from_dt)
2.209 + from_dt = get_next(iter_dt)
2.210 +
2.211 + while from_q:
2.212 + if not have_q:
2.213 + if isinstance(from_q, Enum) and pos > 0:
2.214 + repeat = Pattern(pos - 1, {"interval" : 1, "values" : [context[-1]]}, "REPEAT")
2.215 + repeat.context = tuple(context[:-1])
2.216 + l.append(repeat)
2.217 + else:
2.218 + from_q.context = tuple(context)
2.219 + have_q = True
2.220 + l.append(from_q)
2.221 + from_q = get_next(iter_q)
2.222 +
2.223 + return l
2.224 +
2.225 +def combine_value_with_qualifier(from_dt, from_q):
2.226 +
2.227 + """
2.228 + Combine value information supplied by 'from_dt' (a datetime) and 'from_q'
2.229 + (a qualifier), imposing the datetime value information on any qualifiers.
2.230 + """
2.231 +
2.232 + if not from_q.args.has_key("values") and from_dt.args.has_key("values"):
2.233 + from_q.args["values"] = from_dt.args["values"]
2.234 + return from_q
2.235 +
2.236 +# Datetime arithmetic.
2.237 +
2.238 +def combine(t1, t2):
2.239 + return tuple(map(lambda x, y: x or y, t1, t2))
2.240 +
2.241 +def scale(interval, pos):
2.242 + return (0,) * pos + (interval,)
2.243 +
2.244 +def get_seconds(t):
2.245 +
2.246 + "Convert the sub-day part of 't' into seconds."
2.247 +
2.248 + t = t + (0,) * (6 - len(t))
2.249 + return (t[3] * 60 + t[4]) * 60 + t[5]
2.250 +
2.251 +def update(t, step):
2.252 +
2.253 + "Update 't' by 'step' at the resolution of 'step'."
2.254 +
2.255 + i = len(step)
2.256 +
2.257 + # Years only.
2.258 +
2.259 + if i == 1:
2.260 + return (t[0] + step[0],) + tuple(t[1:])
2.261 +
2.262 + # Years and months.
2.263 +
2.264 + elif i == 2:
2.265 + month = t[1] + step[1]
2.266 + return (t[0] + step[0] + (month - 1) / 12, (month - 1) % 12 + 1) + tuple(t[2:])
2.267 +
2.268 + # Dates and datetimes.
2.269 +
2.270 + else:
2.271 + updated_for_months = update(t, step[:2])
2.272 +
2.273 + # Dates only.
2.274 +
2.275 + if i == 3:
2.276 + d = datetime(*updated_for_months)
2.277 + s = timedelta(step[2])
2.278 +
2.279 + # Datetimes.
2.280 +
2.281 + else:
2.282 + d = datetime(*updated_for_months)
2.283 + s = timedelta(step[2], get_seconds(step))
2.284 +
2.285 + return (d + s).timetuple()[:len(t)]
2.286 +
2.287 +# Classes for producing instances from recurrence structures.
2.288 +
2.289 +class Selector:
2.290 + def __init__(self, pos, args, qualifier, selecting=None):
2.291 + self.pos = pos
2.292 + self.args = args
2.293 + self.qualifier = qualifier
2.294 + self.context = ()
2.295 + self.selecting = selecting
2.296 +
2.297 + def __repr__(self):
2.298 + return "%s(%r, %r, %r, %r)" % (self.__class__.__name__, self.pos, self.args, self.qualifier, self.context)
2.299 +
2.300 + def materialise(self, end, count=None):
2.301 + counter = count and [0, count]
2.302 + return self.materialise_items(self.context, end, counter)
2.303 +
2.304 + def materialise_item(self, current, next, counter):
2.305 + if self.selecting:
2.306 + return self.selecting.materialise_items(current, next, counter)
2.307 + elif counter is None or counter[0] < counter[1]:
2.308 + if counter is not None:
2.309 + counter[0] += 1
2.310 + return [current]
2.311 + else:
2.312 + return []
2.313 +
2.314 +class Pattern(Selector):
2.315 + def materialise_items(self, context, end, counter):
2.316 + start = self.args["values"][0]
2.317 + interval = self.args.get("interval", 1) * units.get(self.qualifier, 1)
2.318 + step = scale(interval, self.pos)
2.319 + unit_interval = units.get(self.qualifier, 1)
2.320 + unit_step = scale(unit_interval, self.pos)
2.321 + current = combine(context, scale(start, self.pos))
2.322 + results = []
2.323 + while current < end and (counter is None or counter[0] < counter[1]):
2.324 + next = update(current, step)
2.325 + current_end = update(current, unit_step)
2.326 + results += self.materialise_item(current, min(current_end, end), counter)
2.327 + current = next
2.328 + return results
2.329 +
2.330 +class PatternFilter(Selector):
2.331 + def materialise_items(self, context, end, counter):
2.332 + start = bases[self.pos]
2.333 + step = scale(1, self.pos)
2.334 + current = combine(context, scale(start, self.pos))
2.335 + results = []
2.336 + while current < end and (counter is None or counter[0] < counter[1]):
2.337 + next = update(current, step)
2.338 + results += self.materialise_item(current, min(next, end), counter)
2.339 + current = next
2.340 + return results
2.341 +
2.342 + def materialise_item(self, current, next, counter):
2.343 + values = self.args["values"]
2.344 + if self.qualifier == "BYDAY":
2.345 + if datetime(*current).isoweekday() in values:
2.346 + return Selector.materialise_item(self, current, next, counter)
2.347 + return []
2.348 +
2.349 +class Enum(Selector):
2.350 + def materialise_items(self, context, end, counter):
2.351 + step = scale(1, self.pos)
2.352 + results = []
2.353 + for value in self.args["values"]:
2.354 + current = combine(context, scale(value, self.pos))
2.355 + if current < end and (counter is None or counter[0] < counter[1]):
2.356 + next = update(current, step)
2.357 + results += self.materialise_item(current, min(next, end), counter)
2.358 + return results
2.359 +
2.360 +def process(selectors):
2.361 + current = selectors[0]
2.362 + for selector in selectors[1:]:
2.363 + current.selecting = selector
2.364 + current = selector
2.365 + return selectors[0]
2.366 +
2.367 +# vim: tabstop=4 expandtab shiftwidth=4