15f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)"""
25f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)Helper for looping over sequences, particular in templates.
35f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)
45f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)Often in a loop in a template it's handy to know what's next up,
55f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)previously up, if this is the first or last item in the sequence, etc.
65f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)These can be awkward to manage in a normal Python loop, but using the
75f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)looper you can get a better sense of the context.  Use like::
85f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)
95f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)    >>> for loop, item in looper(['a', 'b', 'c']):
105f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)    ...     print loop.number, item
115f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)    ...     if not loop.last:
125f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)    ...         print '---'
135f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)    1 a
145f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)    ---
155f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)    2 b
165f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)    ---
175f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)    3 c
185f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)
195f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)"""
205f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)
215f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)import sys
225f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)from Cython.Tempita.compat3 import basestring_
235f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)
245f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)__all__ = ['looper']
255f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)
265f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)
275f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)class looper(object):
285f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)    """
295f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)    Helper for looping (particularly in templates)
305f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)
315f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)    Use this like::
325f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)
335f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)        for loop, item in looper(seq):
345f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)            if loop.first:
355f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)                ...
365f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)    """
375f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)
385f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)    def __init__(self, seq):
395f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)        self.seq = seq
405f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)
415f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)    def __iter__(self):
425f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)        return looper_iter(self.seq)
435f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)
445f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)    def __repr__(self):
455f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)        return '<%s for %r>' % (
465f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)            self.__class__.__name__, self.seq)
475f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)
485f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)
495f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)class looper_iter(object):
505f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)
515f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)    def __init__(self, seq):
525f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)        self.seq = list(seq)
535f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)        self.pos = 0
545f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)
555f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)    def __iter__(self):
565f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)        return self
575f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)
585f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)    def __next__(self):
595f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)        if self.pos >= len(self.seq):
605f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)            raise StopIteration
615f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)        result = loop_pos(self.seq, self.pos), self.seq[self.pos]
625f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)        self.pos += 1
635f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)        return result
645f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)
655f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)    if sys.version < "3":
665f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)        next = __next__
675f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)
685f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)
695f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)class loop_pos(object):
705f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)
715f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)    def __init__(self, seq, pos):
725f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)        self.seq = seq
735f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)        self.pos = pos
745f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)
755f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)    def __repr__(self):
765f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)        return '<loop pos=%r at %r>' % (
775f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)            self.seq[self.pos], self.pos)
785f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)
795f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)    def index(self):
805f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)        return self.pos
815f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)    index = property(index)
825f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)
835f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)    def number(self):
845f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)        return self.pos + 1
855f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)    number = property(number)
865f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)
875f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)    def item(self):
885f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)        return self.seq[self.pos]
895f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)    item = property(item)
905f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)
915f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)    def __next__(self):
925f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)        try:
935f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)            return self.seq[self.pos + 1]
945f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)        except IndexError:
955f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)            return None
965f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)    __next__ = property(__next__)
975f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)
985f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)    if sys.version < "3":
995f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)        next = __next__
1005f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)
1015f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)    def previous(self):
1025f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)        if self.pos == 0:
1035f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)            return None
1045f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)        return self.seq[self.pos - 1]
1055f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)    previous = property(previous)
1065f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)
1075f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)    def odd(self):
1085f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)        return not self.pos % 2
1095f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)    odd = property(odd)
1105f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)
1115f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)    def even(self):
1125f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)        return self.pos % 2
1135f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)    even = property(even)
1145f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)
1155f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)    def first(self):
1165f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)        return self.pos == 0
1175f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)    first = property(first)
1185f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)
1195f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)    def last(self):
1205f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)        return self.pos == len(self.seq) - 1
1215f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)    last = property(last)
1225f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)
1235f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)    def length(self):
1245f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)        return len(self.seq)
1255f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)    length = property(length)
1265f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)
1275f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)    def first_group(self, getter=None):
1285f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)        """
1295f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)        Returns true if this item is the start of a new group,
1305f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)        where groups mean that some attribute has changed.  The getter
1315f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)        can be None (the item itself changes), an attribute name like
1325f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)        ``'.attr'``, a function, or a dict key or list index.
1335f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)        """
1345f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)        if self.first:
1355f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)            return True
1365f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)        return self._compare_group(self.item, self.previous, getter)
1375f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)
1385f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)    def last_group(self, getter=None):
1395f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)        """
1405f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)        Returns true if this item is the end of a new group,
1415f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)        where groups mean that some attribute has changed.  The getter
1425f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)        can be None (the item itself changes), an attribute name like
1435f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)        ``'.attr'``, a function, or a dict key or list index.
1445f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)        """
1455f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)        if self.last:
1465f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)            return True
1475f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)        return self._compare_group(self.item, self.__next__, getter)
1485f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)
1495f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)    def _compare_group(self, item, other, getter):
1505f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)        if getter is None:
1515f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)            return item != other
1525f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)        elif (isinstance(getter, basestring_)
1535f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)              and getter.startswith('.')):
1545f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)            getter = getter[1:]
1555f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)            if getter.endswith('()'):
1565f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)                getter = getter[:-2]
1575f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)                return getattr(item, getter)() != getattr(other, getter)()
1585f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)            else:
1595f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)                return getattr(item, getter) != getattr(other, getter)
1605f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)        elif hasattr(getter, '__call__'):
1615f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)            return getter(item) != getter(other)
1625f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)        else:
1635f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)            return item[getter] != other[getter]
164