<?xml version="1.0" encoding="UTF-8"?>
<commit>
  <added type="array"/>
  <modified type="array">
    <modified>
      <diff>@@ -393,6 +393,7 @@ class RequestHandler(object):
             _=self.locale.translate,
             static_url=self.static_url,
             xsrf_form_html=self.xsrf_form_html,
+            reverse_url=self.application.reverse_url
         )
         args.update(self.ui)
         args.update(kwargs)
@@ -659,6 +660,7 @@ class RequestHandler(object):
         else:
             return base + url_path + path
 
+
     def async_callback(self, callback, *args, **kwargs):
         &quot;&quot;&quot;Wrap callbacks with this if they are used on asynchronous requests.
 
@@ -873,6 +875,7 @@ class Application(object):
         else:
             self.transforms = transforms
         self.handlers = []
+        self.named_handlers = {}
         self.default_host = default_host
         self.settings = settings
         self.ui_modules = {}
@@ -923,6 +926,9 @@ class Application(object):
                 pattern += &quot;$&quot;
             handlers.append((re.compile(pattern), handler, kwargs))
 
+            if isinstance(handler_tuple, URLSpec):
+                self.named_handlers[handler_tuple.name] = handler_tuple
+
     def add_transform(self, transform_class):
         &quot;&quot;&quot;Adds the given OutputTransform to our transform list.&quot;&quot;&quot;
         self.transforms.append(transform_class)
@@ -994,6 +1000,14 @@ class Application(object):
         handler._execute(transforms, *args)
         return handler
 
+    def reverse_url(self, name, *args):
+        &quot;&quot;&quot;Returns a URL path for handler named `name`
+
+        The handler must be added to the application as a URLSpec
+        &quot;&quot;&quot;
+        if name in self.named_handlers:
+            return self.named_handlers[name].reverse(*args)
+        raise KeyError(&quot;%s not found in named urls&quot; % name)
 
 class HTTPError(Exception):
     &quot;&quot;&quot;An exception that will turn into an HTTP error response.&quot;&quot;&quot;
@@ -1222,6 +1236,73 @@ class UIModule(object):
     def render_string(self, path, **kwargs):
         return self.handler.render_string(path, **kwargs)
 
+class URLSpec(object):
+    &quot;&quot;&quot;A tuple like object for specifying handlers in an application
+
+    Rather than hard code URLs in templates, or redirects, HandlerSpecs allow
+    us to give a handler a name which we can then use to lookup and compute the
+    URL.
+    &quot;&quot;&quot;
+    __slots__ = ('pattern', 'handler', 'kwargs', 'name',
+                 'path', 'group_count')
+    def __init__(self, pattern, handler, kwargs=None, name=None):
+        if not pattern.endswith('$'):
+            pattern += '$'
+        self.pattern = pattern
+        self.handler = handler
+        self.kwargs = kwargs
+        self.name = name
+        path, gc = self._find_groups(pattern)
+        self.path = path
+        self.group_count = gc
+    
+    def __len__(self):
+        return 3 if self.kwargs else 2
+
+    def __getitem__(self, i):
+        if not isinstance(i, int):
+            raise TypeError('indices must be integegers')
+        length = len(self)
+        attr = self.__slots__[:length][i]
+
+        return getattr(self, attr)
+
+    def _find_groups(self, pattern):
+        &quot;&quot;&quot;Returns a tuple (reverse string, group count) for a url.
+
+        For example: Given the url pattern /([0-9]{4})/([a-z-]+)/, this method
+        would return ('/%s/%s/', 2).
+        &quot;&quot;&quot;
+        if pattern.startswith('^'):
+            pattern = pattern[1:]
+        if pattern.endswith('$'):
+            pattern = pattern[:-1]
+
+        _pattern = re.compile(pattern)
+        assert _pattern.groups == pattern.count('('), &quot;Pattern's group count&quot;\
+            &quot; does not match the number of right parenthesis&quot;
+
+        pieces = []
+        for fragment in pattern.split('('):
+            if ')' in fragment:
+                paren_loc = fragment.index(')')
+                if paren_loc &gt;= 0:
+                    pieces.append('%s' + fragment[paren_loc + 1:])
+            else:
+                pieces.append(fragment)
+
+        return (''.join(pieces), _pattern.groups)
+
+    def reverse(self, *args):
+        assert len(args) == self.group_count, &quot;required number of arguments &quot;\
+            &quot;not found&quot;
+        import pdb
+        pdb.set_trace()
+        if not len(args):
+            return self.path
+        return self.path % tuple([str(a) for a in args])
+
+url = URLSpec
 
 def _utf8(s):
     if isinstance(s, unicode):</diff>
      <filename>tornado/web.py</filename>
    </modified>
  </modified>
  <removed type="array"/>
  <parents type="array">
    <parent>
      <id>63f92807a431ab513afc8862718eee39e0bedb7a</id>
    </parent>
  </parents>
  <author>
    <name>apgwoz</name>
    <email>git@apgwoz.com</email>
  </author>
  <url>http://github.com/apgwoz/tornado/commit/0b57fbcf992647b6674ad9e433bffffac900f83a</url>
  <id>0b57fbcf992647b6674ad9e433bffffac900f83a</id>
  <committed-date>2009-10-18T15:18:14-07:00</committed-date>
  <authored-date>2009-10-18T15:18:14-07:00</authored-date>
  <message>* first crack at reverse_url, which allows you to name pattern, handler, kwarg
  handler specs to reverse a url in a template, or within an app.</message>
  <tree>c8f0c549e3293096653654c241b819f9a90fb8cd</tree>
  <committer>
    <name>apgwoz</name>
    <email>git@apgwoz.com</email>
  </committer>
</commit>
