Welcome, guest | Sign In | My Account | Store | Cart
from collections import deque


class BrowserHistory(object):
    '''Class for keeping track of the history while moving to different locations,
    as implemented in Web browsers.
    
    Both back and forward history are supported and each of them can be bounded
    or unbounded.
        
    >>> h = BrowserHistory(max_back=3, max_forward=3)
    >>> h.location = 'http://www.google.com/'
    >>> h.location = 'http://www.python.org/'
    >>> h.location = 'http://xkcd.com/'
    >>> h.location = 'http://www.slashdot.org/'
    >>> h.location = 'http://www.digg.com/'    
    >>> h.back()
    'http://www.slashdot.org/'
    >>> h.forward()
    'http://www.digg.com/'
    >>> h.go(-2)
    'http://xkcd.com/'
    >>> # max_back=3 so any result more than 3 pages back is deleted
    >>> for i_loc in h.indexed_locations():
    ...     print '%+d: %s' % i_loc
    +2: http://www.digg.com/
    +1: http://www.slashdot.org/
    +0: http://xkcd.com/
    -1: http://www.python.org/
    >>> # visiting a new location erases the forward history
    >>> h.location = 'http://en.wikipedia.org/'
    >>> for i_loc in h.indexed_locations():
    ...     print '%+d: %s' % i_loc
    +0: http://en.wikipedia.org/
    -1: http://xkcd.com/
    -2: http://www.python.org/
    '''
    
    def __init__(self, location=None, max_back=None, max_forward=None):
        '''Initialize a new BrowserHistory instance.
        
        :param location: If not None, the initial location.
        :param max_back: The maximum number of back locations to remember
            (default: no limit)
        :param max_forward: The maximum number of forward locations to remember
            (default: no limit)
        '''
        if max_back is not None:
            max_back += 1   # +1 for storing the current location
        self._back = deque(maxlen=max_back)
        self._forward = deque(maxlen=max_forward)
        if location is not None:
            self.location = location
    
    @property
    def location(self):
        '''The current location, i.e. the last location set or went to by
        :meth:`back`, :meth:`forward` or :meth:`go`.
        '''
        if self._back:
            return self._back[-1]
        raise AttributeError('location has not been set')

    @location.setter
    def location(self, value):
        '''Go to a new location. Any existing forward history is erased.'''
        self._back.append(value)
        self._forward.clear()
    
    def back(self, i=1):
        '''Jump to a location in history ``i`` steps back.
        
        :param i: The number of steps to jump back.
        :type i: positive int
        :returns: The current :attr:`location`.
        '''
        if i > 0:
            push_forward = self._forward.appendleft
            pop_back = self._back.pop
            for _ in xrange(min(i, len(self._back)-1)):
                push_forward(pop_back())
        return self.location

    def forward(self, i=1):
        '''Jump to a location in history ``i`` steps forward.
        
        :param i: The number of steps to jump forward.
        :type i: positive int
        :returns: The current :attr:`location`.
        '''
        if i > 0:
            push_back = self._back.append
            pop_forward = self._forward.popleft
            for _ in xrange(min(i, len(self._forward))):
                push_back(pop_forward())
        return self.location    

    def go(self, i):
        '''Jump to a location in history ``i`` steps back or forward.
        
        :param i: The number of steps to jump forward (if positive) or back (if negative).
        :type i: int
        :returns: The current :attr:`location`.
        '''
        if i < 0:
            return self.back(-i)
        if i > 0:
            return self.forward(i)
        return self.location

    def indexed_locations(self):
        '''Return a list of ``(index,location)`` tuples for each location in history.
        
        The index ``i`` of a location ``l`` is defined so that ``go(i) == l``.
        Therefore indexes are positive for forward locations, negative for back
        locations and zero for the current :attr:`location`.
        '''
        result = []
        # back and current locations
        n = len(self._back)-1
        result.extend((i-n, location) for i,location in enumerate(self._back))
        # forward locations
        result.extend((i+1, location) for i,location in enumerate(self._forward))
        result.reverse()
        return result


if __name__ == '__main__':
    import doctest; doctest.testmod()

History

  • revision 6 (14 years ago)
  • previous revisions are not available