164 lines
4.1 KiB
Python
164 lines
4.1 KiB
Python
"""
|
|
Helper for looping over sequences, particular in templates.
|
|
|
|
Often in a loop in a template it's handy to know what's next up,
|
|
previously up, if this is the first or last item in the sequence, etc.
|
|
These can be awkward to manage in a normal Python loop, but using the
|
|
looper you can get a better sense of the context. Use like::
|
|
|
|
>>> for loop, item in looper(['a', 'b', 'c']):
|
|
... print loop.number, item
|
|
... if not loop.last:
|
|
... print '---'
|
|
1 a
|
|
---
|
|
2 b
|
|
---
|
|
3 c
|
|
|
|
"""
|
|
|
|
import sys
|
|
from Cython.Tempita.compat3 import basestring_
|
|
|
|
__all__ = ['looper']
|
|
|
|
|
|
class looper(object):
|
|
"""
|
|
Helper for looping (particularly in templates)
|
|
|
|
Use this like::
|
|
|
|
for loop, item in looper(seq):
|
|
if loop.first:
|
|
...
|
|
"""
|
|
|
|
def __init__(self, seq):
|
|
self.seq = seq
|
|
|
|
def __iter__(self):
|
|
return looper_iter(self.seq)
|
|
|
|
def __repr__(self):
|
|
return '<%s for %r>' % (
|
|
self.__class__.__name__, self.seq)
|
|
|
|
|
|
class looper_iter(object):
|
|
|
|
def __init__(self, seq):
|
|
self.seq = list(seq)
|
|
self.pos = 0
|
|
|
|
def __iter__(self):
|
|
return self
|
|
|
|
def __next__(self):
|
|
if self.pos >= len(self.seq):
|
|
raise StopIteration
|
|
result = loop_pos(self.seq, self.pos), self.seq[self.pos]
|
|
self.pos += 1
|
|
return result
|
|
|
|
if sys.version < "3":
|
|
next = __next__
|
|
|
|
|
|
class loop_pos(object):
|
|
|
|
def __init__(self, seq, pos):
|
|
self.seq = seq
|
|
self.pos = pos
|
|
|
|
def __repr__(self):
|
|
return '<loop pos=%r at %r>' % (
|
|
self.seq[self.pos], self.pos)
|
|
|
|
def index(self):
|
|
return self.pos
|
|
index = property(index)
|
|
|
|
def number(self):
|
|
return self.pos + 1
|
|
number = property(number)
|
|
|
|
def item(self):
|
|
return self.seq[self.pos]
|
|
item = property(item)
|
|
|
|
def __next__(self):
|
|
try:
|
|
return self.seq[self.pos + 1]
|
|
except IndexError:
|
|
return None
|
|
__next__ = property(__next__)
|
|
|
|
if sys.version < "3":
|
|
next = __next__
|
|
|
|
def previous(self):
|
|
if self.pos == 0:
|
|
return None
|
|
return self.seq[self.pos - 1]
|
|
previous = property(previous)
|
|
|
|
def odd(self):
|
|
return not self.pos % 2
|
|
odd = property(odd)
|
|
|
|
def even(self):
|
|
return self.pos % 2
|
|
even = property(even)
|
|
|
|
def first(self):
|
|
return self.pos == 0
|
|
first = property(first)
|
|
|
|
def last(self):
|
|
return self.pos == len(self.seq) - 1
|
|
last = property(last)
|
|
|
|
def length(self):
|
|
return len(self.seq)
|
|
length = property(length)
|
|
|
|
def first_group(self, getter=None):
|
|
"""
|
|
Returns true if this item is the start of a new group,
|
|
where groups mean that some attribute has changed. The getter
|
|
can be None (the item itself changes), an attribute name like
|
|
``'.attr'``, a function, or a dict key or list index.
|
|
"""
|
|
if self.first:
|
|
return True
|
|
return self._compare_group(self.item, self.previous, getter)
|
|
|
|
def last_group(self, getter=None):
|
|
"""
|
|
Returns true if this item is the end of a new group,
|
|
where groups mean that some attribute has changed. The getter
|
|
can be None (the item itself changes), an attribute name like
|
|
``'.attr'``, a function, or a dict key or list index.
|
|
"""
|
|
if self.last:
|
|
return True
|
|
return self._compare_group(self.item, self.__next__, getter)
|
|
|
|
def _compare_group(self, item, other, getter):
|
|
if getter is None:
|
|
return item != other
|
|
elif (isinstance(getter, basestring_)
|
|
and getter.startswith('.')):
|
|
getter = getter[1:]
|
|
if getter.endswith('()'):
|
|
getter = getter[:-2]
|
|
return getattr(item, getter)() != getattr(other, getter)()
|
|
else:
|
|
return getattr(item, getter) != getattr(other, getter)
|
|
elif hasattr(getter, '__call__'):
|
|
return getter(item) != getter(other)
|
|
else:
|
|
return item[getter] != other[getter]
|