Ollie Rutherfurd avatar Ollie Rutherfurd committed 8fd48e9

initial commit

Comments (0)

Files changed (3)

+syntax: glob
+*~
+*.orig
+*.pyc
+*.rej
+*.swp
+tags
+
+syntax: regexp
+
+^tmp/
+#!/usr/bin/env python
+#
+# Copyright (C) 2009 by Ollie Rutherfurd <oliver@rutherfurd.net>
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
+# 
+# * Redistributions of source code must retain the above copyright notice, 
+#   this list of conditions and the following disclaimer.
+# * Redistributions in binary form must reproduce the above copyright notice,
+#   this list of conditions and the following disclaimer in the documentation
+#   and/or other materials provided with the distribution.
+# * The name of the author may not be used to endorse or promote products 
+#   derived from this software without specific prior written permission.
+# 
+# THIS SOFTWARE IS PROVIDED BY THE AUTHOR "AS IS" AND ANY EXPRESS OR
+# IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+# DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT,
+# INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 
+# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+# STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
+# IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+# POSSIBILITY OF SUCH DAMAGE. 
+#
+r"""
+Makes Django URL patterns easier to read and write.
+
+Compare the following:
+
+.. sourcecode:: python
+
+    # standard
+    urlpatterns += patterns('django.views.generic.date_based',
+        url(r'^(?P<year>\d{4})/(?P<month>[a-z]{3})/(?P<day>\w{1,2})/(?P<slug>[-\w]+)/$',
+            'object_detail', info_dict),
+        url(r'^(?P<year>\d{4})/(?P<month>[a-z]{3})/(?P<day>\d{1,2})/$',
+            'archive_day',   info_dict),
+        url(r'^(?P<year>\d{4})/(?P<month>[a-z]{3})/$', 'archive_month', info_dict),
+        url(r'^(?P<year>\d{4})/$', 'archive_year',  info_dict),
+    )
+    
+    # using easyurls
+    from easyurls import regex as p
+    urlpatterns += patterns('django.views.generic.date_based',
+        url(p('<year>/<month:mon>/<day>/<slug>'), 'object_detail', info_dict),
+        url(p('<year>/<month:mon>/<day>'),        'archive_day',   info_dict),
+        url(p('<year>/<month:mon>'),              'archive_month', info_dict),
+        url(p('<year>'),                          'archive_year',  info_dict),
+    )
+
+The second is shorter, easier to read and write, and functionally equivalent to
+the first.  Also, note that urls are automatically prefixed with '^', and '/$'
+are automatically appended, if missing (this can be disabled).
+
+easyurls works by defining names for patterns.  By default, the name of
+the captured variable is the name of the pattern.  This can be
+overriden, as is done above where the "mon" pattern is used for "month".
+
+Here's a list of the default patterns:
+
+.. sourcecode:: pycon
+
+    >>> from easyurls import regex as p
+    >>> for name in sorted(p.patterns):
+    ...     print '%5s: %s' % (name,p.patterns[name])
+      day: \d{1,2}
+       id: \d+
+      mon: [a-z]{3}
+    month: \d{1,2}
+        n: \d+
+     slug: [\w-]+
+      tag: \w+
+     year: \d{4}
+
+To use a different name for a pattern, or different pattern for a name,
+add the pattern after the name, prefixing the pattern with ":".
+
+.. sourcecode:: pycon
+
+    # default for month is \d{1,2}
+    >>> print p('<month>')
+    ^(?P<month>\d{1,2})/$
+
+    # using [a-z]{3} for month
+    >>> print p('<month:mon>')
+    ^(?P<month>[a-z]{3})/$
+
+    # using [a-z]{3} for mmm
+    >>> print p('<mmm:mon>')
+    ^(?P<mmm>[a-z]{3})/$
+
+It's easy to add new or override existing patterns:
+
+.. sourcecode:: pycon
+
+    >>> p['yy'] = r'\d{2}'
+    >>> p['mm'] = r'\d{2}'
+    >>> p['dd'] = r'\d{2}'
+
+    >>> print p('<year:yy>/<month:mm>/<day:dd>')
+    ^(?P<year>\d{2})/(?P<month>\d{2})/(?P<day>\d{2})/$
+
+By default, if no pattern is found, ``\d+`` is assumed.
+
+.. sourcecode:: pycon
+
+    >>> print p('releases/<project_id>')
+    ^releases/(?P<project_id>\d+)/$
+
+For flexibility, you can always use a regular expression.
+
+.. sourcecode:: pycon
+
+    # regex for unknown "zip_code"
+    >>> print p('zip/<zip_code:\d{5}>')
+    ^zip/(?P<zip_code>\d{5})/$
+
+    # override slug, allowing "."
+    >>> print p('<slug:[\w-.]+>')
+    ^(?P<slug>[\w-.]+)/$
+
+For demonstration, and testing, purposes here's how prepending and
+appending or '^', '/', and '$' is handled:
+
+.. sourcecode:: pycon
+
+    >>> print p('')
+    ^$
+    >>> print p('foo$')
+    ^foo$
+    >>> print p('foo/')
+    ^foo/$
+    >>> print p('/')
+    ^/$
+
+Prepending of '^' and appending of '/' and '$' can be disabled.
+
+.. sourcecode:: pycon
+
+    >>> p('foo', anchor=False, terminate=False, append_slash=False)
+    'foo'
+"""
+__version__ = '0.1'
+
+import functools,re
+
+# name: pattern
+PATTERNS = {
+    'day':   r'\d{1,2}',
+    'id':    r'\d+',
+    'month': r'\d{1,2}',
+    'slug':  r'[\w-]+',
+    'tag':   r'\w+',
+    'year':  r'\d{4}',
+    # these defined for the pattern, not name
+    # ex: <month:mon>
+    'mon': r'[a-z]{3}', # jan, feb, etc...
+    'n': r'\d+',        # n=number
+}
+
+# <name[:pattern]>
+VARIABLE = re.compile(r'<(?P<name>\w+)(?::?(?P<pattern>[^>]+))?>')
+
+
+class URLPatternGenerator(object):
+    def __init__(self, patterns=None, default=r'\d+',
+                 append_slash=True, anchor=True, terminate=True):
+        self.patterns = patterns or PATTERNS
+        self.default = default              # default pattern
+        self.append_slash = append_slash    # trailing /
+        self.anchor = anchor                # prepend ^
+        self.terminate = terminate          # append $
+    
+    def add(self, name, pattern):
+        self.patterns[name] = pattern
+    __setitem__ = add
+    
+    def replace(self, match, url):
+        regexp = None
+        name = match.group('name')
+        pattern = match.group('pattern')
+        # pattern may be a name or regexp
+        if pattern:
+            regexp = self.patterns.get(pattern, pattern)
+        # use pattern for name, or default
+        else:
+            regexp = self.patterns.get(name, self.default)
+        segment = '(?P<%s>%s)' % (name, regexp)
+        return segment
+    
+    def __call__(self, url, **kw):
+        r = VARIABLE.sub(functools.partial(self.replace, url=url), url)
+        if kw.get('anchor', self.anchor) and r[:1] != '^':
+            r = '^' + r
+        # special-case so '^$' doesn't end up with a '/' in it
+        if url and kw.get('append_slash', self.append_slash) and r[-1:] not in ('$','/'):
+            r += '/'
+        if kw.get('terminate',self.terminate) and r[-1:] != '$':
+            r += '$'
+        return r
+
+
+# don't require creating an instance of the class
+regex = URLPatternGenerator()
+
+
+if __name__ == '__main__':
+    import doctest; doctest.testmod()
+from distutils.core import setup
+import os,sys
+
+sys.path.insert(0, os.path.dirname(__file__))
+try:
+    import easyurls
+finally:
+    del sys.path[0]
+
+setup(
+    name='django-easyurls',
+    description=easyurls.__doc__.strip().splitlines()[0],
+    author='Ollie Rutherfurd',
+    author_email='oliver@rutherfurd.net',
+    version=easyurls.__version__,
+    license='BSD',
+    py_modules=['easyurls'],
+    classifiers=[
+        'Development Status :: 3 - Alpha',
+        'Environment :: Web Environment',
+        'Framework :: Django',
+        'Intended Audience :: Developers',
+        'License :: OSI Approved :: BSD License',
+        'Operating System :: OS Independent',
+        'Programming Language :: Python',
+    ],
+    long_description=easyurls.__doc__,
+    platforms=['any'],
+)
Tip: Filter by directory path e.g. /media app.js to search for public/media/app.js.
Tip: Use camelCasing e.g. ProjME to search for ProjectModifiedEvent.java.
Tip: Filter by extension type e.g. /repo .js to search for all .js files in the /repo directory.
Tip: Separate your search with spaces e.g. /ssh pom.xml to search for src/ssh/pom.xml.
Tip: Use ↑ and ↓ arrow keys to navigate and return to view the file.
Tip: You can also navigate files with Ctrl+j (next) and Ctrl+k (previous) and view the file with Ctrl+o.
Tip: You can also navigate files with Alt+j (next) and Alt+k (previous) and view the file with Alt+o.