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