[3] | 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 | |
---|