1""" 2Helper for looping over sequences, particular in templates. 3 4Often in a loop in a template it's handy to know what's next up, 5previously up, if this is the first or last item in the sequence, etc. 6These can be awkward to manage in a normal Python loop, but using the 7looper you can get a better sense of the context. Use like:: 8 9 >>> for loop, item in looper(['a', 'b', 'c']): 10 ... print loop.number, item 11 ... if not loop.last: 12 ... print '---' 13 1 a 14 --- 15 2 b 16 --- 17 3 c 18 19""" 20 21import sys 22from Cython.Tempita.compat3 import basestring_ 23 24__all__ = ['looper'] 25 26 27class looper(object): 28 """ 29 Helper for looping (particularly in templates) 30 31 Use this like:: 32 33 for loop, item in looper(seq): 34 if loop.first: 35 ... 36 """ 37 38 def __init__(self, seq): 39 self.seq = seq 40 41 def __iter__(self): 42 return looper_iter(self.seq) 43 44 def __repr__(self): 45 return '<%s for %r>' % ( 46 self.__class__.__name__, self.seq) 47 48 49class looper_iter(object): 50 51 def __init__(self, seq): 52 self.seq = list(seq) 53 self.pos = 0 54 55 def __iter__(self): 56 return self 57 58 def __next__(self): 59 if self.pos >= len(self.seq): 60 raise StopIteration 61 result = loop_pos(self.seq, self.pos), self.seq[self.pos] 62 self.pos += 1 63 return result 64 65 if sys.version < "3": 66 next = __next__ 67 68 69class loop_pos(object): 70 71 def __init__(self, seq, pos): 72 self.seq = seq 73 self.pos = pos 74 75 def __repr__(self): 76 return '<loop pos=%r at %r>' % ( 77 self.seq[self.pos], self.pos) 78 79 def index(self): 80 return self.pos 81 index = property(index) 82 83 def number(self): 84 return self.pos + 1 85 number = property(number) 86 87 def item(self): 88 return self.seq[self.pos] 89 item = property(item) 90 91 def __next__(self): 92 try: 93 return self.seq[self.pos + 1] 94 except IndexError: 95 return None 96 __next__ = property(__next__) 97 98 if sys.version < "3": 99 next = __next__ 100 101 def previous(self): 102 if self.pos == 0: 103 return None 104 return self.seq[self.pos - 1] 105 previous = property(previous) 106 107 def odd(self): 108 return not self.pos % 2 109 odd = property(odd) 110 111 def even(self): 112 return self.pos % 2 113 even = property(even) 114 115 def first(self): 116 return self.pos == 0 117 first = property(first) 118 119 def last(self): 120 return self.pos == len(self.seq) - 1 121 last = property(last) 122 123 def length(self): 124 return len(self.seq) 125 length = property(length) 126 127 def first_group(self, getter=None): 128 """ 129 Returns true if this item is the start of a new group, 130 where groups mean that some attribute has changed. The getter 131 can be None (the item itself changes), an attribute name like 132 ``'.attr'``, a function, or a dict key or list index. 133 """ 134 if self.first: 135 return True 136 return self._compare_group(self.item, self.previous, getter) 137 138 def last_group(self, getter=None): 139 """ 140 Returns true if this item is the end of a new group, 141 where groups mean that some attribute has changed. The getter 142 can be None (the item itself changes), an attribute name like 143 ``'.attr'``, a function, or a dict key or list index. 144 """ 145 if self.last: 146 return True 147 return self._compare_group(self.item, self.__next__, getter) 148 149 def _compare_group(self, item, other, getter): 150 if getter is None: 151 return item != other 152 elif (isinstance(getter, basestring_) 153 and getter.startswith('.')): 154 getter = getter[1:] 155 if getter.endswith('()'): 156 getter = getter[:-2] 157 return getattr(item, getter)() != getattr(other, getter)() 158 else: 159 return getattr(item, getter) != getattr(other, getter) 160 elif hasattr(getter, '__call__'): 161 return getter(item) != getter(other) 162 else: 163 return item[getter] != other[getter] 164