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