""" This is a port of a small subset of John Gruber's SmartyPants (http://daringfireball.net/projects/smartypants/). It is compatible in the sense that it does not do anything that SmartyPants would not; however, SmartyPants tries to be considerably more intelligent, at the price of added complexity and (very rarely!) getting it wrong. This extension for Markdown performs the following translations of character sequences into entities (controlled by config['rules']): - ', '' and " into `curly' HTML quotes (‘ etc) - -- and --- into en- and em-dashes - ... into an ellipsis Each of these may be prevented by \\-escaping the relevant character(s) (but note that the second ' in \\'' will still be treated, etc.) """ # Copyright (c) 2008, 2009 Joachim Schipper # # Permission to use, copy, modify, and distribute this software for any # purpose with or without fee is hereby granted, provided that the above # copyright notice and this permission notice appear in all copies. # # THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES # WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF # MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR # ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES # WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN # ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF # OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. import markdown import re class ReplacePattern(markdown.inlinepatterns.Pattern): """ReplacePattern(md, guard, (pattern, replaceby), (pattern, replaceby)) is an inline pattern for Markdown which replaces the patterns by the replacebys (in order), provided that guard matches.""" def __init__(self, md, pattern, rules): # We need the Markdown instance to access md.htmlStash below. self.md = md # Must capture the whole block. self.compiled_re = re.compile("^(.*?)%s(.*)$" % pattern, re.DOTALL) # Compile regular expressions for added speed self.rules = tuple([(re.compile(p[0]), p[1]) for p in rules]) def getCompiledRegExp(self): return self.compiled_re def handleMatch(self, m): """Perform substition on things matched by getCompiledRegExp()""" matched = m.group(2) for p, r in self.rules: if p.match(matched): # Return raw HTML. This seems a rather ugly way # to go about it, but apparently it's expected. return self.md.htmlStash.store(r) assert(('Configuration error: %s, matched by %s, must be matched by any of %s' % (matched, self.compiled_re.pattern, [p[0].pattern for p in self.replace])) == True) class TypographyExtension(markdown.Extension): def __init__(self, configs): # self.config['rules'] is a tuple of (guard, (entity, # reference), (entity, reference)) entries. Substitution will # be tried on the grouped part in each match: anything matching # the regex will be replaced with the corresponding entity, in # the order given. See ReplacePattern. self.config = { 'rules': [((r'(---?|\.\.\.)', # apply rule to group (('---', '—'), # (regex, entity) ('--', '–'), ('\.\.\.', '…'))),# end of rule (r'\B("|\'\'?)\b', (('"|\'\'', '“'), ('\'', '‘'))), (r'(?:\b|(?<=[,.!?]))("|\'\'?)', (('"|\'\'', '”'), ('\'', '’')))), 'translation rules'] } for k, v in configs: assert(self.config.has_key(k)) self.config[k][0] = v def extendMarkdown(self, md, md_globals): try: for (pattern, rules) in reversed(self.config['rules'][0]): p = ReplacePattern(md, pattern, rules) md.inlinePatterns.add(str(p), p, '>html') except AttributeError: # Markdown randomly munges this exception, so print it. import sys, traceback traceback.print_exc(file=sys.stdout) raise def makeExtension(configs={}): return TypographyExtension(configs)