smartypants-2.0.0/0000755000175000017500000000000013030653711016441 5ustar livibetterlivibetter00000000000000smartypants-2.0.0/smartypants0000755000175000017500000000634713030652045020765 0ustar livibetterlivibetter00000000000000#!/usr/bin/env python # Copyright (c) 2013, 2014 Yu-Jie Lin # Licensed under the BSD License, for detailed license information, see COPYING """ ============ Command-line ============ smartypants =========== ``smartypants`` provides a command-line interface for :py:mod:`smartypants` module, which is named as same as the module. It takes input from either standard input or files and output the result to standard output. Usage ===== Syntax:: smartypants [-h] [-v] [-a ATTR] [-s SKIP] [FILE [FILE ...]] Some examples:: $ smartypants inputfile $ command-foo inputfile | command-bar | smartypants Options ======= ``-a``, ``--attr``: processe attrbutes tells smartypants how to translate, The attributes is a string of characters, which are taken from the names of attributes of :py:class:`smartypants.Attr `. For example, the default attribute is :py:attr:`smartypants.Attr.set1 `:: smartypants -a 1 If you want :py:attr:`smartypants.Attr.q ` and :py:attr:`smartypants.Attr.w ` then it would be invoked as:: smartypants -a qw ``-s``, ``--skip``: skip specified HTML elements. It is a comma-separated string. For example:: smartypants -s tag1,tag2,tag3 ``FILE``: files to be processed. If no ``FILE`` is specified, the input is taken from standard input. """ from __future__ import print_function import argparse import sys import warnings import smartypants def _str_attr_to_int(str_attr): """ Convert str-type attr into int >>> f = _str_attr_to_int >>> f('q') == Attr.q True >>> f('1') == Attr.set1 True >>> with warnings.catch_warnings(record=True) as w: ... f('bz') ... len(w) ... print(w[-1].message) 2 1 Unknown attribute: z """ attr = 0 for c in str_attr: if '0' <= c <= '3': c = 'set' + c if not hasattr(smartypants.Attr, c): warnings.warn('Unknown attribute: %s' % c, Warning) continue attr |= getattr(smartypants.Attr, c) return attr def main(): parser = argparse.ArgumentParser(description=smartypants.__description__) parser.add_argument('-v', '--version', action='version', version=smartypants.__version__) parser.add_argument('-a', '--attr', default='1', help='processing attributes (Default: %(default)s)') parser.add_argument('-s', '--skip', default=','.join(smartypants.tags_to_skip), help='skip HTML elements (Default: %(default)s)') parser.add_argument('files', metavar='FILE', type=argparse.FileType('r'), nargs='*', help='files to be processed ') args = parser.parse_args() with warnings.catch_warnings(record=True) as w: attr = _str_attr_to_int(args.attr) if len(w): print(w[-1].message) sys.exit(1) smartypants.tags_to_skip = args.skip.split(',') if args.files: for f in args.files: print(smartypants.smartypants(f.read(), attr), end='') else: print(smartypants.smartypants(sys.stdin.read(), attr), end='') if __name__ == '__main__': main() smartypants-2.0.0/setup.py0000755000175000017500000001720613027617606020175 0ustar livibetterlivibetter00000000000000#!/usr/bin/env python # Copyright (C) 2013, 2014 by Yu-Jie Lin # For detail license information, See COPYING from __future__ import print_function import codecs import sys from distutils.core import Command, setup from unittest import TestLoader, TextTestRunner try: from wheel.bdist_wheel import bdist_wheel except ImportError: bdist_wheel = None try: from sphinx.setup_command import BuildDoc except ImportError: # No need of Sphinx for normal users BuildDoc = None try: from sphinx_pypi_upload import UploadDoc except ImportError: # Sphinx-PyPI-upload not compatible with Python 3 UploadDoc = None CLI_script = 'smartypants' module_name = 'smartypants' module_file = 'smartypants.py' CHECK_FILES = ('.', CLI_script) # scripts to be exculded from checking EXCLUDE_SCRIPTS = ( 'conf.py', # <- docs/conf.py is a auto-generated disaster of PEP8 'smartypants_command.py', ) # ============================================================================ # https://groups.google.com/d/msg/comp.lang.python/pAeiF0qwtY0/H9Ki0WOctBkJ # Work around mbcs bug in distutils. # http://bugs.python.org/issue10945 try: codecs.lookup('mbcs') except LookupError: ascii = codecs.lookup('ascii') func = lambda name, enc=ascii: {True: enc}.get(name == 'mbcs') codecs.register(func) # ============================================================================ class cmd_test(Command): description = 'run tests' user_options = [] def initialize_options(self): pass def finalize_options(self): pass def run(self): loader = TestLoader() tests = loader.discover(start_dir='tests') runner = TextTestRunner(verbosity=2) runner.run(tests) class cmd_isort(Command): description = 'run isort' user_options = [] def initialize_options(self): pass def finalize_options(self): pass def run(self): try: import isort except ImportError: print(('Cannot import isort, you forgot to install?\n' 'run `pip install isort` to install.'), file=sys.stderr) sys.exit(1) from glob import glob print() print('Options') print('=======') print() print('Exclude:', EXCLUDE_SCRIPTS) print() files = ['setup.py', CLI_script, module_file] + glob('tests/*.py') print('Results') print('=======') print() fails = 0 for f in files: # unfortunately, we have to do it twice if isort.SortImports(f, check=True).incorrectly_sorted: fails += 1 print() isort.SortImports(f, show_diff=True) print() print() print('Statistics') print('==========') print() print('%d files failed to pass' % fails) class cmd_pep8(Command): description = 'run pep8' user_options = [] def initialize_options(self): pass def finalize_options(self): pass def run(self): try: import pep8 except ImportError: print(('Cannot import pep8, you forgot to install?\n' 'run `pip install pep8` to install.'), file=sys.stderr) sys.exit(1) p8 = pep8.StyleGuide() # do not include code not written in b.py p8.options.exclude += EXCLUDE_SCRIPTS # ignore four-space indentation error p8.options.ignore += () print() print('Options') print('=======') print() print('Exclude:', p8.options.exclude) print('Ignore :', p8.options.ignore) print() print('Results') print('=======') print() report = p8.check_files(CHECK_FILES) print() print('Statistics') print('==========') print() report.print_statistics() print('%-7d Total errors and warnings' % report.get_count()) class cmd_pyflakes(Command): description = 'run Pyflakes' user_options = [] def initialize_options(self): pass def finalize_options(self): pass def run(self): try: from pyflakes import api from pyflakes import reporter as modReporter except ImportError: print(('Cannot import pyflakes, you forgot to install?\n' 'run `pip install pyflakes` to install.'), file=sys.stderr) sys.exit(1) from os.path import basename reporter = modReporter._makeDefaultReporter() # monkey patch for exclusion of pathes api_iterSourceCode = api.iterSourceCode def _iterSourceCode(paths): for path in api_iterSourceCode(paths): if basename(path) not in EXCLUDE_SCRIPTS: yield path api.iterSourceCode = _iterSourceCode print() print('Options') print('=======') print() print('Exclude:', EXCLUDE_SCRIPTS) print() print('Results') print('=======') print() warnings = api.checkRecursive(CHECK_FILES, reporter) print() print('Total warnings: %d' % warnings) class cmd_pylint(Command): description = 'run Pylint' user_options = [] def initialize_options(self): pass def finalize_options(self): pass def run(self): from glob import glob try: from pylint import lint except ImportError: print(('Cannot import pylint, you forgot to install?\n' 'run `pip install pylint` to install.'), file=sys.stderr) sys.exit(1) print() print('Options') print('=======') print() print('Exclude:', EXCLUDE_SCRIPTS) files = ['setup.py', CLI_script, module_file] + glob('tests/*.py') args = [ '--ignore=%s' % ','.join(EXCLUDE_SCRIPTS), '--output-format=colorized', '--include-ids=y', '--indent-string=" "', ] + files print() lint.Run(args) # ============================================================================ with codecs.open(module_file, encoding='utf-8') as f: meta = dict( (k.strip(' _'), eval(v)) for k, v in # There will be a '\n', with eval(), it's safe to ignore (line.split('=') for line in f if line.startswith('__')) ) # keep these meta_keys = ['name', 'description', 'version', 'license', 'url', 'author', 'author_email'] meta = dict([m for m in meta.items() if m[0] in meta_keys]) with codecs.open('README.rst', encoding='utf-8') as f: long_description = f.read() classifiers = [ 'Development Status :: 5 - Production/Stable', 'Intended Audience :: Developers', 'Intended Audience :: End Users/Desktop', 'License :: OSI Approved :: BSD License', 'Operating System :: OS Independent', 'Programming Language :: Python :: 2', 'Programming Language :: Python :: 3', 'Topic :: Text Processing :: Filters', ] setup_d = dict( name=module_name, long_description=long_description, cmdclass={ 'isort': cmd_isort, 'pep8': cmd_pep8, 'pyflakes': cmd_pyflakes, 'pylint': cmd_pylint, 'test': cmd_test, }, classifiers=classifiers, py_modules=[module_name], scripts=[CLI_script], **meta ) if bdist_wheel: setup_d['cmdclass']['bdist_wheel'] = bdist_wheel if BuildDoc: setup_d['cmdclass']['build_sphinx'] = BuildDoc if UploadDoc: setup_d['cmdclass']['upload_sphinx'] = UploadDoc if __name__ == '__main__': setup(**setup_d) smartypants-2.0.0/PKG-INFO0000644000175000017500000000433213030653711017540 0ustar livibetterlivibetter00000000000000Metadata-Version: 1.1 Name: smartypants Version: 2.0.0 Summary: Python with the SmartyPants Home-page: https://bitbucket.org/livibetter/smartypants.py Author: Yu-Jie Lin Author-email: livibetter@gmail.com License: BSD License Description: smartypants =========== smartypants_ is a Python fork of SmartyPants__. .. _smartypants: https://bitbucket.org/livibetter/smartypants.py __ SmartyPantsPerl_ .. _SmartyPantsPerl: http://daringfireball.net/projects/smartypants/ .. important:: As of 2016-12-28, smartypants is looking for new maintainer to take over, please contact project owner on Bitbucket. Installation ------------ To install it: .. code:: sh pip install smartypants Quick usage ----------- To use it as a module: .. code:: python import smartypants text = '"SmartyPants" is smart, so is smartypants -- a Python port' print(smartypants.smartypants(text)) To use the command-line script ``smartypants``: .. code:: sh echo '"SmartyPants" is smart, so is smartypants -- a Python port' | smartypants Both produce:: “SmartyPants” is smart, so is smartypants — a Python port More information ---------------- * Documentation_ * `Source code`_ * PyPI_ .. _documentation: http://pythonhosted.org/smartypants/ .. _Source code: smartypants_ .. _PyPI: https://pypi.python.org/pypi/smartypants/ Platform: UNKNOWN Classifier: Development Status :: 5 - Production/Stable Classifier: Intended Audience :: Developers Classifier: Intended Audience :: End Users/Desktop Classifier: License :: OSI Approved :: BSD License Classifier: Operating System :: OS Independent Classifier: Programming Language :: Python :: 2 Classifier: Programming Language :: Python :: 3 Classifier: Topic :: Text Processing :: Filters smartypants-2.0.0/setup.cfg0000644000175000017500000000022013027617606020265 0ustar livibetterlivibetter00000000000000[build_sphinx] source-dir = docs/ build-dir = docs/_build all_files = 1 [upload_sphinx] upload-dir = docs/_build/html [wheel] universal = 1 smartypants-2.0.0/README.rst0000644000175000017500000000217713030652045020136 0ustar livibetterlivibetter00000000000000smartypants =========== smartypants_ is a Python fork of SmartyPants__. .. _smartypants: https://bitbucket.org/livibetter/smartypants.py __ SmartyPantsPerl_ .. _SmartyPantsPerl: http://daringfireball.net/projects/smartypants/ .. important:: As of 2016-12-28, smartypants is looking for new maintainer to take over, please contact project owner on Bitbucket. Installation ------------ To install it: .. code:: sh pip install smartypants Quick usage ----------- To use it as a module: .. code:: python import smartypants text = '"SmartyPants" is smart, so is smartypants -- a Python port' print(smartypants.smartypants(text)) To use the command-line script ``smartypants``: .. code:: sh echo '"SmartyPants" is smart, so is smartypants -- a Python port' | smartypants Both produce:: “SmartyPants” is smart, so is smartypants — a Python port More information ---------------- * Documentation_ * `Source code`_ * PyPI_ .. _documentation: http://pythonhosted.org/smartypants/ .. _Source code: smartypants_ .. _PyPI: https://pypi.python.org/pypi/smartypants/ smartypants-2.0.0/COPYING0000644000175000017500000000601713027617606017511 0ustar livibetterlivibetter00000000000000========= Copyright ========= SmartyPants =========== :: Copyright (c) 2003 John Gruber (http://daringfireball.net/) All rights reserved. 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. * Neither the name "SmartyPants" nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. This software is provided by the copyright holders and contributors "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 copyright owner or contributors 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. smartypants =========== :: smartypants is a derivative work of SmartyPants. Copyright (c) 2013, 2014 Yu-Jie Lin Copyright (c) 2004, 2005, 2007, 2013 Chad Miller 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. This software is provided by the copyright holders and contributors "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 copyright owner or contributors 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. smartypants-2.0.0/smartypants.py0000755000175000017500000004475313030653630021420 0ustar livibetterlivibetter00000000000000#!/usr/bin/python # -*- coding: utf-8 -*- # Copyright (c) 2013, 2014, 2016 Yu-Jie Lin # Copyright (c) 2004, 2005, 2007, 2013 Chad Miller # Copyright (c) 2003 John Gruber # Licensed under the BSD License, for detailed license information, see COPYING """ smartypants module ================== :func:`smartypants` is the core of smartypants module. """ __author__ = 'Yu-Jie Lin' __author_email__ = 'livibetter@gmail.com' __version__ = '2.0.0' __license__ = 'BSD License' __url__ = 'https://bitbucket.org/livibetter/smartypants.py' __description__ = 'Python with the SmartyPants' import re class _Attr(object): """ class for instantiation of module attribute :attr:`Attr`. """ q = 1 << 0 """ flag for normal quotes (``"``) and (``'``) to curly ones. .. seealso:: :func:`convert_quotes` """ b = 1 << 1 """ flag for double quotes (````backticks''``) to curly ones. .. seealso:: :func:`convert_backticks` """ B = 1 << 2 | b """ flag for double quotes (````backticks''``) and single quotes (```single'``) to curly ones. .. seealso:: :func:`convert_backticks` and :func:`convert_single_backticks` """ mask_b = b | B d = 1 << 3 """ flag for dashes (``--``) to em-dashes. .. seealso:: :func:`convert_dashes` """ D = 1 << 4 | d """ flag for old-school typewriter dashes (``--``) to en-dashes and dashes (``---``) to em-dashes. .. seealso:: :func:`convert_dashes_oldschool` """ i = 1 << 5 | d """ flag for inverted old-school typewriter dashes (``--``) to em-dashes and dashes (``---``) to en-dashes. .. seealso:: :func:`convert_dashes_oldschool_inverted` """ mask_d = d | D | i e = 1 << 6 """ flag for dashes (``...``) to ellipses. .. seealso:: :func:`convert_ellipses` """ w = 1 << 7 """ flag for dashes (``"``) to ASCII double quotes (``"``). This should be of no interest to most people, but of particular interest to anyone who writes their posts using Dreamweaver, as Dreamweaver inexplicably uses this entity to represent a literal double-quote character. SmartyPants only educates normal quotes, not entities (because ordinarily, entities are used for the explicit purpose of representing the specific character they represent). The "w" option must be used in conjunction with one (or both) of the other quote options ("q" or "b"). Thus, if you wish to apply all SmartyPants transformations (quotes, en- and em-dashes, and ellipses) and also convert ``"`` entities into regular quotes so SmartyPants can educate them. """ u = 0 << 9 | 1 << 8 """ Output Unicode characters instead of numeric character references, for example, from ``“`` to left double quotation mark (``“``) (U+201C). .. seealso:: :func:`convert_entities` """ h = 1 << 9 | 0 << 8 """ Output HTML named entities instead of numeric character references, for example, from ``“`` to ``“``. .. seealso:: :func:`convert_entities` """ s = 1 << 9 | 1 << 8 """ Output ASCII equivalents instead of numeric character references, for example, from ``—`` to ``--``. .. seealso:: :func:`convert_entities` """ mask_o = u | h | s set0 = 0 "suppress all transformations. (Do nothing.)" set1 = q | b | d | e "equivalent to :attr:`q` | :attr:`b` | :attr:`d` | :attr:`e`" set2 = q | b | D | e """ equivalent to :attr:`q` | :attr:`b` | :attr:`D` | :attr:`e` For old school en- and em- dash. """ set3 = q | b | i | e """ equivalent to :attr:`q` | :attr:`b` | :attr:`i` | :attr:`e` For inverted old school en & em- dash." """ @property def default(self): "Default value of attributes, same value as :attr:`set1`" global default_smartypants_attr return default_smartypants_attr @default.setter def default(self, attr): global default_smartypants_attr default_smartypants_attr = attr Attr = _Attr() """ Processing attributes, which tells :func:`smartypants` what to convert .. seealso:: :class:`_Attr` """ default_smartypants_attr = Attr.set1 tags_to_skip = ['pre', 'samp', 'code', 'tt', 'kbd', 'script', 'style', 'math'] """ Skipped HTML elements .. seealso:: :ref:`skip-html` """ def _tags_to_skip_regex(tags=None): """ Convert a list of skipped tags into regular expression The default *tags* are :attr:`tags_to_skip`. >>> f = _tags_to_skip_regex >>> print(f(['foo', 'bar']).pattern) <(/)?(foo|bar)[^>]*> """ if tags is None: tags = tags_to_skip if isinstance(tags, (list, tuple)): tags = '|'.join(tags) return re.compile('<(/)?(%s)[^>]*>' % tags, re.I) def smartypants(text, attr=None): """ SmartyPants function >>> print(smartypants('"foo" -- bar')) “foo” — bar >>> print(smartypants('"foo" -- bar', Attr.d)) "foo" — bar """ skipped_tag_stack = [] if attr is None: attr = Attr.default do_quotes = attr & Attr.q do_backticks = attr & Attr.mask_b do_dashes = attr & Attr.mask_d do_ellipses = attr & Attr.e do_entities = attr & Attr.mask_o convert_quot = attr & Attr.w tokens = _tokenize(text) result = [] in_pre = False prev_token_last_char = "" # This is a cheat, used to get some context # for one-character tokens that consist of # just a quote char. What we do is remember # the last character of the previous text # token, to use as context to curl single- # character quote tokens correctly. tags_to_skip_regex = _tags_to_skip_regex() for cur_token in tokens: if cur_token[0] == "tag": # Don't mess with quotes inside some tags. This does not handle # self tags! result.append(cur_token[1]) skip_match = tags_to_skip_regex.match(cur_token[1]) if skip_match: if not skip_match.group(1): skipped_tag_stack.append(skip_match.group(2).lower()) in_pre = True else: if len(skipped_tag_stack) > 0: _tag = skipped_tag_stack[-1] if skip_match.group(2).lower() == _tag: skipped_tag_stack.pop() else: pass # This close doesn't match the open. This isn't # XHTML. We should barf here. if len(skipped_tag_stack) == 0: in_pre = False else: t = cur_token[1] # Remember last char of this token before processing. last_char = t[-1:] if not in_pre: t = process_escapes(t) if convert_quot: t = re.sub('"', '"', t) if do_dashes: if do_dashes == Attr.d: t = convert_dashes(t) if do_dashes == Attr.D: t = convert_dashes_oldschool(t) if do_dashes == Attr.i: t = convert_dashes_oldschool_inverted(t) if do_ellipses: t = convert_ellipses(t) # Note: backticks need to be processed before quotes. if do_backticks == Attr.b: t = convert_backticks(t) if do_backticks == Attr.B: t = convert_single_backticks(t) if do_quotes: if t == "'": # Special case: single-character ' token if re.match("\S", prev_token_last_char): t = "’" else: t = "‘" elif t == '"': # Special case: single-character " token if re.match("\S", prev_token_last_char): t = "”" else: t = "“" else: # Normal case: t = convert_quotes(t) if do_entities: mode = (0 if do_entities == Attr.u else 1 if do_entities == Attr.h else 2 if do_entities == Attr.s else 3) # would result in key error t = convert_entities(t, mode) prev_token_last_char = last_char result.append(t) return "".join(result) def convert_quotes(text): """ Convert quotes in *text* into HTML curly quote entities. >>> print(convert_quotes('"Isn\\'t this fun?"')) “Isn’t this fun?” """ punct_class = r"""[!"#\$\%'()*+,-.\/:;<=>?\@\[\\\]\^_`{|}~]""" # Special case if the very first character is a quote # followed by punctuation at a non-word-break. Close the quotes by brute # force: text = re.sub(r"""^'(?=%s\\B)""" % (punct_class,), '’', text) text = re.sub(r"""^"(?=%s\\B)""" % (punct_class,), '”', text) # Special case for double sets of quotes, e.g.: #

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

text = re.sub(r""""'(?=\w)""", '“‘', text) text = re.sub(r"""'"(?=\w)""", '‘“', text) # Special case for decade abbreviations (the '80s): text = re.sub(r"""\b'(?=\d{2}s)""", '’', text) close_class = r'[^\ \t\r\n\[\{\(\-]' dec_dashes = '–|—' # 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) text = opening_single_quotes_regex.sub(r'\1‘', text) closing_single_quotes_regex = re.compile(r""" (%s) ' (?!\s | s\b | \d) """ % (close_class,), re.VERBOSE) text = closing_single_quotes_regex.sub(r'\1’', text) closing_single_quotes_regex = re.compile(r""" (%s) ' (\s | s\b) """ % (close_class,), re.VERBOSE) text = closing_single_quotes_regex.sub(r'\1’\2', text) # Any remaining single quotes should be opening ones: text = re.sub("'", '‘', text) # 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) text = opening_double_quotes_regex.sub(r'\1“', text) # Double closing quotes: closing_double_quotes_regex = re.compile(r""" #(%s)? # character that indicates the quote should be closing " (?=\s) """ % (close_class,), re.VERBOSE) text = closing_double_quotes_regex.sub('”', text) closing_double_quotes_regex = re.compile(r""" (%s) # character that indicates the quote should be closing " """ % (close_class,), re.VERBOSE) text = closing_double_quotes_regex.sub(r'\1”', text) # Any remaining quotes should be opening ones. text = re.sub('"', '“', text) return text def convert_backticks(text): """ Convert ````backticks''``-style double quotes in *text* into HTML curly quote entities. >>> print(convert_backticks("``Isn't this fun?''")) “Isn't this fun?” """ text = re.sub('``', '“', text) text = re.sub("''", '”', text) return text def convert_single_backticks(text): """ Convert ```backticks'``-style single quotes in *text* into HTML curly quote entities. >>> print(convert_single_backticks("`Isn't this fun?'")) ‘Isn’t this fun?’ """ text = re.sub('`', '‘', text) text = re.sub("'", '’', text) return text def convert_dashes(text): """ Convert ``--`` in *text* into em-dash HTML entities. >>> quote = 'Nothing endures but change. -- Heraclitus' >>> print(convert_dashes(quote)) Nothing endures but change. — Heraclitus """ text = re.sub('--', '—', text) return text def convert_dashes_oldschool(text): """ Convert ``--`` and ``---`` in *text* into en-dash and em-dash HTML entities, respectively. >>> quote = 'Life itself is the proper binge. --- Julia Child (1912--2004)' >>> print(convert_dashes_oldschool(quote)) Life itself is the proper binge. — Julia Child (1912–2004) """ text = re.sub('---', '—', text) # em (yes, backwards) text = re.sub('--', '–', text) # en (yes, backwards) return text def convert_dashes_oldschool_inverted(text): """ Convert ``--`` and ``---`` in *text* into em-dash and en-dash HTML entities, respectively. Two reasons why: * First, unlike the en- and em-dash syntax supported by :func:`convert_dashes_oldschool`, 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.) >>> quote = 'Dare to be naïve. -- Buckminster Fuller (1895---1983)' >>> print(convert_dashes_oldschool_inverted(quote)) Dare to be naïve. — Buckminster Fuller (1895–1983) """ text = re.sub('---', '–', text) # em text = re.sub('--', '—', text) # en return text def convert_ellipses(text): """ Convert ``...`` in *text* into ellipsis HTML entities >>> print(convert_ellipses('Huh...?')) Huh…? """ text = re.sub(r"""\.\.\.""", '…', text) text = re.sub(r"""\. \. \.""", '…', text) return text def convert_entities(text, mode): """ Convert numeric character references to, if *mode* is - *0*: Unicode characters - *1*: HTML named entities - *2*: ASCII equivalents >>> print(convert_entities('‘', 0)) ‘ >>> print(convert_entities('‘SmartyPants’', 1)) ‘SmartyPants’ >>> print(convert_entities('“Hello — world.”', 2)) "Hello -- world." """ CTBL = { '–': ('–', '–', '-'), '—': ('—', '—', '--'), '‘': ('‘', '‘', "'"), '’': ('’', '’', "'"), '“': ('“', '“', '"'), '”': ('”', '”', '"'), '…': ('…', '…', '...'), } for k, v in CTBL.items(): text = text.replace(k, v[mode]) return text def process_escapes(text): r""" Processe the following backslash escape sequences in *text*. This is useful if you want to force a "dumb" quote or other character to appear. +--------+-----------+-----------+ | Escape | Value | Character | +========+===========+===========+ | ``\\`` | ``\`` | ``\`` | +--------+-----------+-----------+ | ``\"`` | ``"`` | ``"`` | +--------+-----------+-----------+ | ``\'`` | ``'`` | ``'`` | +--------+-----------+-----------+ | ``\.`` | ``.`` | ``.`` | +--------+-----------+-----------+ | ``\-`` | ``-`` | ``-`` | +--------+-----------+-----------+ | ``\``` | ````` | ``\``` | +--------+-----------+-----------+ >>> print(process_escapes(r'\\')) \ >>> print(smartypants(r'"smarty" \"pants\"')) “smarty” "pants" """ text = re.sub(r'\\\\', '\', text) text = re.sub(r'\\"', '"', text) text = re.sub(r"\\'", ''', text) text = re.sub(r'\\\.', '.', text) text = re.sub(r'\\-', '-', text) text = re.sub(r'\\`', '`', text) return text def _tokenize(text): """ 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`__. __ http://www.bradchoate.com/past/mtregex.php """ tokens = [] tag_soup = re.compile(r'([^<]*)(