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