import re

class MatchNotFound(Exception):pass

class Mapper(object):
    '''
    >>> from mapper import Mapper
    >>> m = Mapper('django.views.generic',(
    ...     ('^article/(?P<year>\d{4})/(?P<month>\d{1,2})/(?P<day>\d{1,2})/','article_detail'),
    ...     ('^article/(?P<id>\d+)/','article_detail_by_id'),
    ...     ('^article/(?P<name>\w+)/','article_detail_by_name'),
    ... ))
    >>> m.match('article/2007/6/12/')
    ('django.views.generic.article_detail', {'month': '6', 'day': '12', 'year': '2007'})
    >>> m.match('article/12/')
    ('django.views.generic.article_detail_by_id', {'id': '12'})
    >>> m.match('article/test/')
    ('django.views.generic.article_detail_by_name', {'name': 'test'})
    '''

    def __init__(self, view_prefix, mapping):
        self.view_prefix=view_prefix
        self.mapping=[]
        for reg, view in mapping:
            self.mapping.append((re.compile(reg), view))

    def match(self, path, default_match_dict={}):
        tried = []
        for reg, view in self.mapping:
            match=reg.search(path)
            if match:
                match_dict = match.groupdict()
                if isinstance(view, Mapper): # view is a sub mapper
                    new_path=path[match.end():]
                    try:
                        return view.match(new_path,default_match_dict=match_dict)
                    except MatchNotFound,e:
                        tried.extend('%s %s'%(reg.pattern,t) for t in e.args[0]['tried'])
                else:
                    view_name = view or match_dict.pop('view','')
                    return '%s.%s'%(self.view_prefix,view_name),\
                            dict(default_match_dict,**match_dict)
            else:
                tried.append(reg.pattern)
        raise MatchNotFound({'tried':tried,'path':path})

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