From ccde62e1d2c86be183b2e546957b2e77087ecde6 Mon Sep 17 00:00:00 2001 From: "luxagraf@c63593aa-01b0-44d9-8516-4b9c7e931d7f" Date: Mon, 23 Nov 2009 20:34:40 +0000 Subject: added externals and lib --- lib/templatetags/__init__.py | 0 lib/templatetags/templatetags/__init__.py | 0 lib/templatetags/templatetags/get_latest.py | 1 + lib/templatetags/templatetags/get_latest_pub.py | 1 + lib/templatetags/templatetags/markdown.py | 9 + lib/templatetags/templatetags/slugify_under.py | 15 + lib/templatetags/templatetags/smartypants.py | 878 +++++++++++++++++++++++ lib/templatetags/templatetags/truncateletters.py | 24 + lib/templatetags/templatetags/typogrify.py | 216 ++++++ 9 files changed, 1144 insertions(+) create mode 100644 lib/templatetags/__init__.py create mode 100644 lib/templatetags/templatetags/__init__.py create mode 100644 lib/templatetags/templatetags/get_latest.py create mode 100644 lib/templatetags/templatetags/get_latest_pub.py create mode 100644 lib/templatetags/templatetags/markdown.py create mode 100644 lib/templatetags/templatetags/slugify_under.py create mode 100644 lib/templatetags/templatetags/smartypants.py create mode 100644 lib/templatetags/templatetags/truncateletters.py create mode 100644 lib/templatetags/templatetags/typogrify.py (limited to 'lib/templatetags') diff --git a/lib/templatetags/__init__.py b/lib/templatetags/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/lib/templatetags/templatetags/__init__.py b/lib/templatetags/templatetags/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/lib/templatetags/templatetags/get_latest.py b/lib/templatetags/templatetags/get_latest.py new file mode 100644 index 0000000..6c9f9fa --- /dev/null +++ b/lib/templatetags/templatetags/get_latest.py @@ -0,0 +1 @@ +from django.template import Library, Node from django.db.models import get_model register = Library() class LatestContentNode(Node): def __init__(self, model, num, varname): self.num, self.varname = num, varname self.model = get_model(*model.split('.')) def render(self, context): context[self.varname] = self.model._default_manager.all()[:self.num] return '' def get_latest(parser, token): bits = token.contents.split() if len(bits) != 5: raise TemplateSyntaxError, "get_latest tag takes exactly four arguments" if bits[3] != 'as': raise TemplateSyntaxError, "third argument to get_latest tag must be 'as'" return LatestContentNode(bits[1], bits[2], bits[4]) get_latest = register.tag(get_latest) \ No newline at end of file diff --git a/lib/templatetags/templatetags/get_latest_pub.py b/lib/templatetags/templatetags/get_latest_pub.py new file mode 100644 index 0000000..151befa --- /dev/null +++ b/lib/templatetags/templatetags/get_latest_pub.py @@ -0,0 +1 @@ +from django.template import Library, Node from django.db.models import get_model register = Library() class LatestContentNode(Node): def __init__(self, model, num, varname): self.num, self.varname = num, varname self.model = get_model(*model.split('.')) def render(self, context): context[self.varname] = self.model._default_manager.filter(status__exact=1)[:self.num] return '' def get_latest_pub(parser, token): bits = token.contents.split() if len(bits) != 5: raise TemplateSyntaxError, "get_latest tag takes exactly four arguments" if bits[3] != 'as': raise TemplateSyntaxError, "third argument to get_latest tag must be 'as'" return LatestContentNode(bits[1], bits[2], bits[4]) get_latest_pub = register.tag(get_latest_pub) \ No newline at end of file diff --git a/lib/templatetags/templatetags/markdown.py b/lib/templatetags/templatetags/markdown.py new file mode 100644 index 0000000..dca51f2 --- /dev/null +++ b/lib/templatetags/templatetags/markdown.py @@ -0,0 +1,9 @@ +from django import template +import markdown2 as markdown + +register = template.Library() + +def do_markdown(text): + return markdown.markdown(text, safe_mode = False) + +register.filter('markdown', do_markdown) \ No newline at end of file diff --git a/lib/templatetags/templatetags/slugify_under.py b/lib/templatetags/templatetags/slugify_under.py new file mode 100644 index 0000000..bbf01d2 --- /dev/null +++ b/lib/templatetags/templatetags/slugify_under.py @@ -0,0 +1,15 @@ +import re +from django import template +from django.utils.safestring import mark_safe +register = template.Library() + +@register.filter +def slugify_under(value): + """ + Normalizes string, converts to lowercase, removes non-alpha characters, + and converts spaces to hyphens. + """ + import unicodedata + value = unicodedata.normalize('NFKD', value).encode('ascii', 'ignore') + value = unicode(re.sub('[^\w\s-]', '', value).strip().lower()) + return mark_safe(re.sub('[-\s]+', '_', value)) diff --git a/lib/templatetags/templatetags/smartypants.py b/lib/templatetags/templatetags/smartypants.py new file mode 100644 index 0000000..07ddd03 --- /dev/null +++ b/lib/templatetags/templatetags/smartypants.py @@ -0,0 +1,878 @@ +r""" +============== +smartypants.py +============== + +---------------------------- +SmartyPants ported to Python +---------------------------- + +Ported by `Chad Miller`_ +Copyright (c) 2004 Chad Miller + +original `SmartyPants`_ by `John Gruber`_ +Copyright (c) 2003 John Gruber + + +Synopsis +======== + +A smart-quotes plugin for Pyblosxom_. + +The priginal "SmartyPants" is a free web publishing plug-in for Movable Type, +Blosxom, and BBEdit that easily translates plain ASCII punctuation characters +into "smart" typographic punctuation HTML entities. + +This software, *smartypants.py*, endeavours to be a functional port of +SmartyPants to Python, for use with Pyblosxom_. + + +Description +=========== + +SmartyPants can perform the following transformations: + +- Straight quotes ( " and ' ) into "curly" quote HTML entities +- Backticks-style quotes (\`\`like this'') into "curly" quote HTML entities +- Dashes (``--`` and ``---``) into en- and em-dash entities +- Three consecutive dots (``...`` or ``. . .``) into an ellipsis entity + +This means you can write, edit, and save your posts using plain old +ASCII straight quotes, plain dashes, and plain dots, but your published +posts (and final HTML output) will appear with smart quotes, em-dashes, +and proper ellipses. + +SmartyPants does not modify characters within ``
``, ````, ````,
+```` or ``

He said, "'Quoted' words in a larger quote."

+ str = re.sub(r""""'(?=\w)""", """“‘""", str) + str = re.sub(r"""'"(?=\w)""", """‘“""", str) + + # Special case for decade abbreviations (the '80s): + str = re.sub(r"""\b'(?=\d{2}s)""", r"""’""", str) + + close_class = r"""[^\ \t\r\n\[\{\(\-]""" + dec_dashes = r"""–|—""" + + # Get most opening single quotes: + opening_single_quotes_regex = re.compile(r""" + ( + \s | # a whitespace char, or +   | # a non-breaking space entity, or + -- | # dashes, or + &[mn]dash; | # named dash entities + %s | # or decimal entities + &\#x201[34]; # or hex + ) + ' # the quote + (?=\w) # followed by a word character + """ % (dec_dashes,), re.VERBOSE) + str = opening_single_quotes_regex.sub(r"""\1‘""", str) + + closing_single_quotes_regex = re.compile(r""" + (%s) + ' + (?!\s | s\b | \d) + """ % (close_class,), re.VERBOSE) + str = closing_single_quotes_regex.sub(r"""\1’""", str) + + closing_single_quotes_regex = re.compile(r""" + (%s) + ' + (\s | s\b) + """ % (close_class,), re.VERBOSE) + str = closing_single_quotes_regex.sub(r"""\1’\2""", str) + + # Any remaining single quotes should be opening ones: + str = re.sub(r"""'""", r"""‘""", str) + + # Get most opening double quotes: + opening_double_quotes_regex = re.compile(r""" + ( + \s | # a whitespace char, or +   | # a non-breaking space entity, or + -- | # dashes, or + &[mn]dash; | # named dash entities + %s | # or decimal entities + &\#x201[34]; # or hex + ) + " # the quote + (?=\w) # followed by a word character + """ % (dec_dashes,), re.VERBOSE) + str = opening_double_quotes_regex.sub(r"""\1“""", str) + + # Double closing quotes: + closing_double_quotes_regex = re.compile(r""" + #(%s)? # character that indicates the quote should be closing + " + (?=\s) + """ % (close_class,), re.VERBOSE) + str = closing_double_quotes_regex.sub(r"""”""", str) + + closing_double_quotes_regex = re.compile(r""" + (%s) # character that indicates the quote should be closing + " + """ % (close_class,), re.VERBOSE) + str = closing_double_quotes_regex.sub(r"""\1”""", str) + + # Any remaining quotes should be opening ones. + str = re.sub(r'"', r"""“""", str) + + return str + + +def educateBackticks(str): + """ + Parameter: String. + Returns: The string, with ``backticks'' -style double quotes + translated into HTML curly quote entities. + Example input: ``Isn't this fun?'' + Example output: “Isn't this fun?” + """ + + str = re.sub(r"""``""", r"""“""", str) + str = re.sub(r"""''""", r"""”""", str) + return str + + +def educateSingleBackticks(str): + """ + Parameter: String. + Returns: The string, with `backticks' -style single quotes + translated into HTML curly quote entities. + + Example input: `Isn't this fun?' + Example output: ‘Isn’t this fun?’ + """ + + str = re.sub(r"""`""", r"""‘""", str) + str = re.sub(r"""'""", r"""’""", str) + return str + + +def educateDashes(str): + """ + Parameter: String. + + Returns: The string, with each instance of "--" translated to + an em-dash HTML entity. + """ + + str = re.sub(r"""---""", r"""–""", str) # en (yes, backwards) + str = re.sub(r"""--""", r"""—""", str) # em (yes, backwards) + return str + + +def educateDashesOldSchool(str): + """ + Parameter: String. + + Returns: The string, with each instance of "--" translated to + an en-dash HTML entity, and each "---" translated to + an em-dash HTML entity. + """ + + str = re.sub(r"""---""", r"""—""", str) # em (yes, backwards) + str = re.sub(r"""--""", r"""–""", str) # en (yes, backwards) + return str + + +def educateDashesOldSchoolInverted(str): + """ + Parameter: String. + + Returns: The string, with each instance of "--" translated to + an em-dash HTML entity, and each "---" translated to + an en-dash HTML entity. Two reasons why: First, unlike the + en- and em-dash syntax supported by + EducateDashesOldSchool(), it's compatible with existing + entries written before SmartyPants 1.1, back when "--" was + only used for em-dashes. Second, em-dashes are more + common than en-dashes, and so it sort of makes sense that + the shortcut should be shorter to type. (Thanks to Aaron + Swartz for the idea.) + """ + str = re.sub(r"""---""", r"""–""", str) # em + str = re.sub(r"""--""", r"""—""", str) # en + return str + + + +def educateEllipses(str): + """ + Parameter: String. + Returns: The string, with each instance of "..." translated to + an ellipsis HTML entity. + + Example input: Huh...? + Example output: Huh…? + """ + + str = re.sub(r"""\.\.\.""", r"""…""", str) + str = re.sub(r"""\. \. \.""", r"""…""", str) + return str + + +def stupefyEntities(str): + """ + Parameter: String. + Returns: The string, with each SmartyPants HTML entity translated to + its ASCII counterpart. + + Example input: “Hello — world.” + Example output: "Hello -- world." + """ + + str = re.sub(r"""–""", r"""-""", str) # en-dash + str = re.sub(r"""—""", r"""--""", str) # em-dash + + str = re.sub(r"""‘""", r"""'""", str) # open single quote + str = re.sub(r"""’""", r"""'""", str) # close single quote + + str = re.sub(r"""“""", r'''"''', str) # open double quote + str = re.sub(r"""”""", r'''"''', str) # close double quote + + str = re.sub(r"""…""", r"""...""", str)# ellipsis + + return str + + +def processEscapes(str): + r""" + Parameter: String. + Returns: The string, with after processing the following backslash + escape sequences. This is useful if you want to force a "dumb" + quote or other character to appear. + + Escape Value + ------ ----- + \\ \ + \" " + \' ' + \. . + \- - + \` ` + """ + str = re.sub(r"""\\\\""", r"""\""", str) + str = re.sub(r'''\\"''', r""""""", str) + str = re.sub(r"""\\'""", r"""'""", str) + str = re.sub(r"""\\\.""", r""".""", str) + str = re.sub(r"""\\-""", r"""-""", str) + str = re.sub(r"""\\`""", r"""`""", str) + + return str + + +def _tokenize(str): + """ + Parameter: String containing HTML markup. + Returns: Reference to an array of the tokens comprising the input + string. Each token is either a tag (possibly with nested, + tags contained therein, such as , or a + run of text between tags. Each element of the array is a + two-element array; the first is either 'tag' or 'text'; + the second is the actual value. + + Based on the _tokenize() subroutine from Brad Choate's MTRegex plugin. + + """ + + pos = 0 + length = len(str) + tokens = [] + + depth = 6 + nested_tags = "|".join(['(?:<(?:[^<>]',] * depth) + (')*>)' * depth) + #match = r"""(?: ) | # comments + # (?: <\? .*? \?> ) | # directives + # %s # nested tags """ % (nested_tags,) + tag_soup = re.compile(r"""([^<]*)(<[^>]*>)""") + + token_match = tag_soup.search(str) + + previous_end = 0 + while token_match is not None: + if token_match.group(1) != "": + tokens.append(['text', token_match.group(1)]) + + tokens.append(['tag', token_match.group(2)]) + + previous_end = token_match.end() + token_match = tag_soup.search(str, token_match.end()) + + if previous_end < len(str): + tokens.append(['text', str[previous_end:]]) + + return tokens + + + +if __name__ == "__main__": + + import locale + + try: + locale.setlocale(locale.LC_ALL, '') + except: + pass + + from docutils.core import publish_string + docstring_html = publish_string(__doc__, writer_name='html') + + print docstring_html + + + # Unit test output goes out stderr. No worries. + import unittest + sp = smartyPants + + class TestSmartypantsAllAttributes(unittest.TestCase): + # the default attribute is "1", which means "all". + + def test_dates(self): + self.assertEqual(sp("1440-80's"), "1440-80’s") + self.assertEqual(sp("1440-'80s"), "1440-‘80s") + self.assertEqual(sp("1440---'80s"), "1440–‘80s") + self.assertEqual(sp("1960s"), "1960s") # no effect. + self.assertEqual(sp("1960's"), "1960’s") + self.assertEqual(sp("one two '60s"), "one two ‘60s") + self.assertEqual(sp("'60s"), "‘60s") + + def test_ordinal_numbers(self): + self.assertEqual(sp("21st century"), "21st century") # no effect. + self.assertEqual(sp("3rd"), "3rd") # no effect. + + def test_educated_quotes(self): + self.assertEqual(sp('''"Isn't this fun?"'''), '''“Isn’t this fun?”''') + + unittest.main() + + + + +__author__ = "Chad Miller " +__version__ = "1.5_1.5: Sat, 13 Aug 2005 15:50:24 -0400" +__url__ = "http://wiki.chad.org/SmartyPantsPy" +__description__ = "Smart-quotes, smart-ellipses, and smart-dashes for weblog entries in pyblosxom" diff --git a/lib/templatetags/templatetags/truncateletters.py b/lib/templatetags/templatetags/truncateletters.py new file mode 100644 index 0000000..c492430 --- /dev/null +++ b/lib/templatetags/templatetags/truncateletters.py @@ -0,0 +1,24 @@ +from django import template +register = template.Library() + +@register.filter +def truncateletters(value, arg): + """ + Truncates a string after a certain number of letters + + Argument: Number of letters to truncate after + """ + try: + length = int(arg) + except ValueError: # invalid literal for int() + return value # Fail silently + if not isinstance(value, basestring): + value = str(value) + + if len(value) > length: + truncated = value[:length] + if not truncated.endswith('...'): + truncated += '...' + return truncated + + return value \ No newline at end of file diff --git a/lib/templatetags/templatetags/typogrify.py b/lib/templatetags/templatetags/typogrify.py new file mode 100644 index 0000000..fa4f0cf --- /dev/null +++ b/lib/templatetags/templatetags/typogrify.py @@ -0,0 +1,216 @@ +# from django.conf import settings +import re +from django.conf import settings +from django import template +register = template.Library() + +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') + 'One & two' + >>> amp('One & two') + 'One & two' + >>> amp('One & two') + 'One & two' + + >>> amp('One & two') + 'One & two' + + It won't mess up & that are already wrapped, in entities or URLs + + >>> amp('One & two') + 'One & two' + >>> amp('“this” & that') + '“this” & that' + """ + amp_finder = re.compile(r"(\s| )(&|&|&\#38;)(\s| )") + return amp_finder.sub(r"""\1&\3""", text) + +def caps(text): + """Wraps multiple capital letters in ```` + so they can be styled with CSS. + + >>> caps("A message from KU") + 'A message from KU' + + Uses the smartypants tokenizer to not screw with HTML or with tags it shouldn't. + + >>> caps("
CAPS
more CAPS") + '
CAPS
more CAPS' + + >>> caps("A message from 2KU2 with digits") + 'A message from 2KU2 with digits' + + >>> 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.' + + >>> caps("D.O.T.HE34TRFID") + 'D.O.T.HE34TRFID' + """ + try: + import smartypants + except ImportError: + if settings.DEBUG: + raise template.TemplateSyntaxError, "Error in {% caps %} filter: The Python SmartyPants library isn't installed." + return text + + 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 + | (\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])) + + return "".join(result) + +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)`` + and also accounts for potential opening inline elements ``a, em, strong, span, b, i`` + + >>> initial_quotes('"With primes"') + '"With primes"' + >>> initial_quotes("'With single primes'") + '\\'With single primes\\'' + + >>> initial_quotes('"With primes and a link"') + '"With primes and a link"' + + >>> initial_quotes('“With smartypanted quotes”') + 'With smartypanted quotes”' + """ + quote_finder = re.compile(r"""((<(p|h[1-6]|li)[^>]*>|^) # start with an opening p, h1-6, li 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) + + return quote_finder.sub(_quote_wrapper, text) + +def smartypants(text): + """Applies smarty pants to curl quotes. + + >>> smartypants('The "Green" man') + 'The “Green” man' + """ + try: + import smartypants + except ImportError: + if settings.DEBUG: + raise template.TemplateSyntaxError, "Error in {% smartypants %} filter: The Python smartypants library isn't installed." + return text + else: + return smartypants.smartyPants(text) + +def typogrify(text): + """The super typography filter + + Applies the following filters: widont, smartypants, caps, amp, initial_quotes + + >>> typogrify('

"Jayhawks" & KU fans act extremely obnoxiously

') + '

Jayhawks” & KU fans act extremely obnoxiously

' + """ + text = amp(text) + text = widont(text) + text = smartypants(text) + text = caps(text) + text = initial_quotes(text) + return text + +def widont(text): + """Replaces the space between the last two words in a string with `` `` + Works in these block tags ``(h1-h6, p, li)`` and also accounts for + potential closing inline elements ``a, em, strong, span, b, i`` + + >>> widont('A very simple test') + 'A very simple test' + + >>> widont('

In a couple of paragraphs

paragraph two

') + '

In a couple of paragraphs

paragraph two

' + + >>> widont('

In a link inside a heading

') + '

In a link inside a heading

' + + >>> widont('

In a link followed by other text

') + '

In a link followed by other text

' + + Empty HTMLs shouldn't error + >>> widont('

') + '

' + + >>> widont('
Divs get no love!
') + '
Divs get no love!
' + + >>> widont('

But divs with paragraphs do!

') + '

But divs with paragraphs do!

' + """ + widont_finder = re.compile(r"""(\s+) # the space to replace + ([^<>\s]+ # must be flollowed by non-tag non-space characters + \s* # optional white space! + (]*>\s*)* # optional closing inline tags with optional white space after each + (