./ 0000755 0001750 0001750 00000000000 11670412542 006771 5 ustar jd jd ./README.markdown 0000644 0001750 0001750 00000006234 11670412422 011474 0 ustar jd jd Note: This fork tries to separate the general purpose logic from django
related code so that it can be used with other template engines like
Jinja2.
typogrify: Filters to make web typography easier
================================================================
This application provides a set of custom filters for the Django
template system which automatically apply various transformations to
plain text in order to yield typographically-improved HTML.
Requirements
============
``typogrify`` is designed to work with `Django`_, and so requires a
functioning installation of Django 0.96 or later. Also requires `the
Python port of John Gruber's SmartyPants`_ for tokenization.
.._ Django: http://www.djangoproject.com/
.._ The Python port of John Gruber's SmartyPants: http://web.chad.org/projects/smartypants.py/
Installation
============
To install a packaged version of ``typogrify``, download `the latest
package from Google Code`_, and -- in the directory in which you
downloaded it -- open a command line and do the following::
tar zxvf typogrify-0.2.tar.gz
cd typogrify-0.2
python setup.py install
This will perform a standard installation of ``typogrify``.
Alternately, you can perform a Subversion checkout of the latest code;
execute the following in a directory that's on your Python path::
svn checkout http://typogrify.googlecode.com/svn/trunk/typogrify/
Once ``typogrify`` is installed on your system, you can add it to the
``INSTALLED_APPS`` setting of any Django project in which you wish to
use it, and then use ``{% load typogrify %}`` in your templates to
load the filters it provides.
.._ the latest package from Google Code: http://typogrify.googlecode.com/files/typogrify-0.1.tar.gz
Included filters
================
``amp``
-------
Wraps ampersands in HTML with ```` so they can be
styled with CSS. Ampersands are also normalized to ``&``. Requires
ampersands to have whitespace or an `` `` on both sides. Will not
change any ampersand which has already been wrapped in this fashion.
``caps``
--------
Wraps multiple capital letters in ```` so they can
be styled with CSS.
``initial_quotes``
------------------
Wraps initial quotes in ```` for double quotes or
```` for single quotes. Works inside these block
elements:
* ``h1``, ``h2``, ``h3``, ``h4``, ``h5``, ``h6``
* ``p``
* ``li``
* ``dt``
* ``dd``
Also accounts for potential opening inline elements: ``a``, ``em``,
``strong``, ``span``, ``b``, ``i``.
``smartypants``
---------------
Applies ``SmartyPants``.
``typogrify``
-------------
Applies all of the following filters, in order:
* ``amp``
* ``widont``
* ``smartypants``
* ``caps``
* ``initial_quotes``
``widont``
----------
Based on Shaun Inman's PHP utility of the same name, replaces the
space between the last two words in a string with `` `` to avoid
a final line of text with only one word.
Works inside these block elements:
* ``h1``, ``h2``, ``h3``, ``h4``, ``h5``, ``h6``
* ``p``
* ``li``
* ``dt``
* ``dd``
Also accounts for potential closing inline elements: ``a``, ``em``,
``strong``, ``span``, ``b``, ``i``.
./typogrify/ 0000755 0001750 0001750 00000000000 11670412422 011022 5 ustar jd jd ./typogrify/templatetags/ 0000755 0001750 0001750 00000000000 11670412422 013514 5 ustar jd jd ./typogrify/templatetags/django_filters.py 0000644 0001750 0001750 00000015630 11670412422 017065 0 ustar jd jd from typogrify import Typogrify, TypogrifyError
from functools import wraps
from django.conf import settings
from django import template
from django.utils.html import conditional_escape
from django.utils.safestring import mark_safe
from django.utils.encoding import force_unicode
register = template.Library()
def make_safe(f):
@wraps(f)
def wrapper(text):
text = force_unicode(text)
f.is_safe = True
out = text
try:
out = f(text)
except TypogrifyError, e:
if settings.DEBUG:
raise template.TemplateError(e.message)
return text
return mark_safe(out)
wrapper.is_safe = True
return wrapper
@make_safe
def amp(text):
"""Wraps apersands in HTML with ```` so they can be
styled with CSS. Apersands are also normalized to ``&``. Requires
ampersands to have whitespace or an `` `` on both sides.
>>> amp('One & two')
u'One & two'
>>> amp('One & two')
u'One & two'
>>> amp('One & two')
u'One & two'
>>> amp('One & two')
u'One & two'
It won't mess up & that are already wrapped, in entities or URLs
>>> amp('One & two')
u'One & two'
>>> amp('“this” & that')
u'“this” & that'
It should ignore standalone amps that are in attributes
>>> amp('xyz')
u'xyz'
"""
return Typogrify.amp(text)
@make_safe
def caps(text):
"""Wraps multiple capital letters in ````
so they can be styled with CSS.
>>> caps("A message from KU")
u'A message from KU'
Uses the smartypants tokenizer to not screw with HTML or with tags it shouldn't.
>>> caps("CAPS
more CAPS")
u'CAPS
more CAPS'
>>> caps("A message from 2KU2 with digits")
u'A message from 2KU2 with digits'
>>> caps("Dotted caps followed by spaces should never include them in the wrap D.O.T. like so.")
u'Dotted caps followed by spaces should never include them in the wrap D.O.T. like so.'
All caps with with apostrophes in them shouldn't break. Only handles dump apostrophes though.
>>> caps("JIMMY'S")
u'JIMMY\\'S'
>>> caps("D.O.T.HE34TRFID")
u'D.O.T.HE34TRFID'
"""
return Typogrify.caps(text)
@make_safe
def initial_quotes(text):
"""Wraps initial quotes in ``class="dquo"`` for double quotes or
``class="quo"`` for single quotes. Works in these block tags ``(h1-h6, p, li, dt, dd)``
and also accounts for potential opening inline elements ``a, em, strong, span, b, i``
>>> initial_quotes('"With primes"')
u'"With primes"'
>>> initial_quotes("'With single primes'")
u'\\'With single primes\\''
>>> initial_quotes('"With primes and a link"')
u'"With primes and a link"'
>>> initial_quotes('“With smartypanted quotes”')
u'“With smartypanted quotes”'
"""
return Typogrify.initial_quotes(text)
@make_safe
def smartypants(text):
"""Applies smarty pants to curl quotes.
>>> smartypants('The "Green" man')
u'The “Green” man'
"""
return Typogrify.smartypants(text)
@make_safe
def titlecase(text):
"""Support for titlecase.py's titlecasing
>>> titlecase("this V that")
u'This v That'
>>> titlecase("this is just an example.com")
u'This Is Just an example.com'
"""
return Typogrify.titlecase(text)
@make_safe
def typogrify(text):
"""The super typography filter
Applies the following filters: widont, smartypants, caps, amp, initial_quotes
>>> typogrify('"Jayhawks" & KU fans act extremely obnoxiously
')
u'“Jayhawks” & KU fans act extremely obnoxiously
'
Each filters properly handles autoescaping.
>>> conditional_escape(typogrify('"Jayhawks" & KU fans act extremely obnoxiously
'))
u'“Jayhawks” & KU fans act extremely obnoxiously
'
"""
text = force_unicode(text)
return Typogrify.typogrify(text)
@make_safe
def widont(text):
"""Replaces the space between the last two words in a string with `` ``
Works in these block tags ``(h1-h6, p, li, dd, dt)`` and also accounts for
potential closing inline elements ``a, em, strong, span, b, i``
>>> widont('A very simple test')
u'A very simple test'
Single word items shouldn't be changed
>>> widont('Test')
u'Test'
>>> widont(' Test')
u' Test'
>>> widont('
In a couple of paragraphs
paragraph two
') u'In a couple of paragraphs
paragraph two
' >>> widont('Neither do PREs') u'
Neither do PREs' >>> widont('
But divs with paragraphs do!
But divs with paragraphs do!
CAPSmore CAPS") Markup(u'
CAPSmore CAPS') >>> caps("A message from 2KU2 with digits") Markup(u'A message from 2KU2 with digits') >>> caps("Dotted caps followed by spaces should never include them in the wrap D.O.T. like so.") Markup(u'Dotted caps followed by spaces should never include them in the wrap D.O.T. like so.') All caps with with apostrophes in them shouldn't break. Only handles dump apostrophes though. >>> caps("JIMMY'S") Markup(u'JIMMY\\'S') >>> caps("D.O.T.HE34TRFID") Markup(u'D.O.T.HE34TRFID') """ return Typogrify.caps(text) @make_safe def initial_quotes(text): """Wraps initial quotes in ``class="dquo"`` for double quotes or ``class="quo"`` for single quotes. Works in these block tags ``(h1-h6, p, li, dt, dd)`` and also accounts for potential opening inline elements ``a, em, strong, span, b, i`` >>> initial_quotes('"With primes"') Markup(u'"With primes"') >>> initial_quotes("'With single primes'") Markup(u'\\'With single primes\\'') >>> initial_quotes('"With primes and a link"') Markup(u'"With primes and a link"') >>> initial_quotes('“With smartypanted quotes”') Markup(u'“With smartypanted quotes”') """ return Typogrify.initial_quotes(text) @make_safe def smartypants(text): """Applies smarty pants to curl quotes. >>> smartypants('The "Green" man') Markup(u'The “Green” man') """ return Typogrify.smartypants(text) @make_safe def titlecase(text): """Support for titlecase.py's titlecasing >>> titlecase("this V that") Markup(u'This v That') >>> titlecase("this is just an example.com") Markup(u'This Is Just an example.com') """ return Typogrify.titlecase(text) @make_safe def typogrify(text): """The super typography filter Applies the following filters: widont, smartypants, caps, amp, initial_quotes >>> typogrify('
In a couple of paragraphs
paragraph two
') Markup(u'In a couple of paragraphs
paragraph two
') >>> widont('Neither do PREs') Markup(u'
Neither do PREs') >>> widont('
But divs with paragraphs do!
But divs with paragraphs do!
CAPSmore CAPS") '
CAPSmore CAPS' >>> Typogrify.caps("A message from 2KU2 with digits") 'A message from 2KU2 with digits' >>> Typogrify.caps("Dotted caps followed by spaces should never include them in the wrap D.O.T. like so.") 'Dotted caps followed by spaces should never include them in the wrap D.O.T. like so.' All caps with with apostrophes in them shouldn't break. Only handles dump apostrophes though. >>> Typogrify.caps("JIMMY'S") 'JIMMY\\'S' >>> Typogrify.caps("D.O.T.HE34TRFID") 'D.O.T.HE34TRFID' """ try: import smartypants except ImportError: raise TypogrifyError, "Error in {% caps %} filter: The Python SmartyPants library isn't installed." tokens = smartypants._tokenize(text) result = [] in_skipped_tag = False cap_finder = re.compile(r"""( (\b[A-Z\d]* # Group 2: Any amount of caps and digits [A-Z]\d*[A-Z] # A cap string much at least include two caps (but they can have digits between them) [A-Z\d']*\b) # Any amount of caps and digits or dumb apostsrophes | (\b[A-Z]+\.\s? # OR: Group 3: Some caps, followed by a '.' and an optional space (?:[A-Z]+\.\s?)+) # Followed by the same thing at least once more (?:\s|\b|$)) """, re.VERBOSE) def _cap_wrapper(matchobj): """This is necessary to keep dotted cap strings to pick up extra spaces""" if matchobj.group(2): return """%s""" % matchobj.group(2) else: if matchobj.group(3)[-1] == " ": caps = matchobj.group(3)[:-1] tail = ' ' else: caps = matchobj.group(3) tail = '' return """%s%s""" % (caps, tail) tags_to_skip_regex = re.compile("<(/)?(?:pre|code|kbd|script|math)[^>]*>", re.IGNORECASE) for token in tokens: if token[0] == "tag": # Don't mess with tags. result.append(token[1]) close_match = tags_to_skip_regex.match(token[1]) if close_match and close_match.group(1) == None: in_skipped_tag = True else: in_skipped_tag = False else: if in_skipped_tag: result.append(token[1]) else: result.append(cap_finder.sub(_cap_wrapper, token[1])) output = "".join(result) return output @staticmethod def initial_quotes(text): """Wraps initial quotes in ``class="dquo"`` for double quotes or ``class="quo"`` for single quotes. Works in these block tags ``(h1-h6, p, li, dt, dd)`` and also accounts for potential opening inline elements ``a, em, strong, span, b, i`` >>> Typogrify.initial_quotes('"With primes"') '"With primes"' >>> Typogrify.initial_quotes("'With single primes'") '\\'With single primes\\'' >>> Typogrify.initial_quotes('"With primes and a link"') '"With primes and a link"' >>> Typogrify.initial_quotes('“With smartypanted quotes”') '“With smartypanted quotes”' """ quote_finder = re.compile(r"""((<(p|h[1-6]|li|dt|dd)[^>]*>|^) # start with an opening p, h1-6, li, dd, dt or the start of the string \s* # optional white space! (<(a|em|span|strong|i|b)[^>]*>\s*)*) # optional opening inline tags, with more optional white space for each. (("|“|&\#8220;)|('|‘|&\#8216;)) # Find me a quote! (only need to find the left quotes and the primes) # double quotes are in group 7, singles in group 8 """, re.VERBOSE) def _quote_wrapper(matchobj): if matchobj.group(7): classname = "dquo" quote = matchobj.group(7) else: classname = "quo" quote = matchobj.group(8) return """%s%s""" % (matchobj.group(1), classname, quote) output = quote_finder.sub(_quote_wrapper, text) return output @staticmethod def smartypants(text): """Applies smarty pants to curl quotes. >>> Typogrify.smartypants('The "Green" man') 'The “Green” man' """ try: import smartypants except ImportError: raise TypogrifyError, "Error in {% smartypants %} filter: The Python smartypants library isn't installed." else: output = smartypants.smartyPants(text) return output @staticmethod def titlecase(text): """Support for titlecase.py's titlecasing >>> Typogrify.titlecase("this V that") 'This v That' >>> Typogrify.titlecase("this is just an example.com") 'This Is Just an example.com' """ try: import titlecase except ImportError: raise TypogrifyError, "Error in {% titlecase %} filter: The titlecase.py library isn't installed." else: return titlecase.titlecase(text) @staticmethod def typogrify(text): """The super typography filter Applies the following filters: widont, smartypants, caps, amp, initial_quotes >>> Typogrify.typogrify('
In a couple of paragraphs
paragraph two
') 'In a couple of paragraphs
paragraph two
' >>> Typogrify.widont('Neither do PREs') '
Neither do PREs' >>> Typogrify.widont('
But divs with paragraphs do!
But divs with paragraphs do!