pax_global_header00006660000000000000000000000064136130600300014502gustar00rootroot0000000000000052 comment=3a0b2faa1a5dc2475f0b4f4321ec125789ff9404 python-textile-4.0.1/000077500000000000000000000000001361306003000145015ustar00rootroot00000000000000python-textile-4.0.1/.coveragerc000066400000000000000000000001531361306003000166210ustar00rootroot00000000000000[run] branch = True source = textile parallel = True [report] show_missing = True omit = textile/tests/*python-textile-4.0.1/.gitignore000066400000000000000000000002651361306003000164740ustar00rootroot00000000000000*.pyc *.orig *.rej *~ *.pyo *.egg-info .cache/ .coverage .eggs/ .noseids* .pytest_cache docs/build docs/coverage build bin dist eggs htmlcov parts develop-eggs .DS_Store *.swp .tox python-textile-4.0.1/.travis.yml000066400000000000000000000007521361306003000166160ustar00rootroot00000000000000dist: xenial # required for Python >= 3.7 language: python env: - IMAGESIZE=true - IMAGESIZE=false python: - "3.5" - "3.6" - "3.7" - "3.8" # PyPy versions - "pypy3" # command to install dependencies install: - imagesize='' - pip install -U coveralls pytest pytest-cov coverage codecov - if [[ $IMAGESIZE == true ]] ; then imagesize='[imagesize]' ; fi - pip install -e ".${imagesize}" # command to run tests script: py.test after_success: - coveralls - codecov python-textile-4.0.1/CHANGELOG.textile000066400000000000000000000164211361306003000173740ustar00rootroot00000000000000h1. Textile Changelog h2. Version 4.0.1 * Bugfixes: ** SyntaxWarnings with Python 3.8 i("#71":https://github.com/textile/python-textile/issues/71) ** testsuite: internal error with coverage 5.0.X ("#72":https://github.com/textile/python-textile/issues/72) ** DeprecationWarnings about invalid escape sequences ("#73":https://github.com/textile/python-textile/issues/73) h2. Version 4.0.0 * Drop support for Python 2, hence the version bump. Update list of PY3K versions to currently-supported versions. If you need to use textile on Python 2.7 or Python 3.3 or 3.4, please use textile Version 3.0.4. * For use in PyPy environments, textile used to work well with the regex package. Lately, it's running into trouble. Please uninstall regex if this is the case for you. h2. Version 3.0.4 * BUGFIX: Restricted mode strips out CSS attributes again. * Update travis to more current versions and test against current Pillow version. h2. Version 3.0.3 * BUGFIX: Improve handling code block following extended p block ("#63":https://github.com/textile/python-textile/pull/63) h2. Version 3.0.2 * BUGFIX: Fix for multiple multi-line paragraphs. ("#62":https://github.com/textile/python-textile/pull/62) h2. Version 3.0.1 * BUGFIX: Fix improper handling of extended code blocks. ("#61":https://github.com/textile/python-textile/pull/61) h2. Version 3.0.0 * Drop support for Python 2.6 and 3.2. * Update to the current version of html5lib * Bugfixes: ** Fix handling of HTML entities in extended pre blocks. ("#55":https://github.com/textile/python-textile/issues/55) ** Empty definitions in definition lists raised an exception ("#56":https://github.com/textile/python-textile/issues/56) ** Fix handling of unicode in img attributes ("#58":https://github.com/textile/python-textile/issues/58) h2. Version 2.3.16 * Bugfixes: ** Fix processing of extended code blocks ("#50":https://github.com/textile/python-textile/issues/50) ** Don't break when links fail to include "http:" ("#51":https://github.com/textile/python-textile/issues/51) ** Better handling of poorly-formatted tables ("#52":https://github.com/textile/python-textile/issues/52) h2. Version 2.3.15 * Bugfix: Don't break on unicode characters in the fragment of a url. h2. Version 2.3.14 * Bugfix: Fix textile on Python 2.6 ("#48":https://github.com/textile/python-textile/issues/48) h2. Version 2.3.13 * Remove extraneous arguments from textile method. These were originally added long ago to work with django, but markup languages are long gone from django. * Bugfix: Don't mangle percent-encoded URLs so much. ("#45":https://github.com/textile/python-textile/issues/45) * Bugfix: More fixes for poorly-formatted lists. ("#46":https://github.com/textile/python-textile/issues/46) * Bugfix: Improve handling of whitespace in pre-formatted blocks. This now matches php-textile's handling of pre blocks much more closely. ("#47":https://github.com/textile/python-textile/issues/47) h2. Version 2.3.12 * Bugfix: Don't die on pre blocks with unicode characters. ("#43":https://github.com/textile/python-textile/issues/43) * Bugfix: Fix regressions introduced into the code between 2.2.2 and 2.3.11. (Special thanks to "@adam-iris":https://github.com/adam-iris for providing pull request "#44":https://github.com/textile/python-textile/pull/44) * Bugfix: Don't just die when processing poorly-formatted textile lists. ("#37":https://github.com/textile/python-textile/issues/37) * Add Python 3.6 to testing. * Add a "print the version string and exit" argument to the cli tool: @pytextile -v@ h2. Version 2.3.11 * Bugfix: Don't strip leading dot from image URIs ("#42":https://github.com/textile/python-textile/issues/42) h2. Version 2.3.10 * Packaging: cleanup in MANIFEST.IN leads to better linux packaging, and smaller wheel size. h2. Version 2.3.9 * Packaging: remove extraneous files from the source distribution upload. * Remove a lingering file from a feature branch for overhauling list handling. This brings coverage back up to 100% h2. Version 2.3.8 * Bugfix: Fix process of string containing only whitespaces ("#40":https://github.com/textile/python-textile/issues/40) * Bugfix: Fix process of formatted text after lists ("#37":https://github.com/textile/python-textile/issues/37) * Test: Use sys.executable instead of 'python' to test the CLI ("#38":https://github.com/textile/python-textile/issues/38) h2. Version 2.3.7 * Bugfix: Don't assume pytest is available to be imported in setup.py ("#39":https://github.com/textile/python-textile/issues/39) h2. Version 2.3.6 * Packaging: @tests@ directory is correctly included in source-tarball. ("#33":https://github.com/textile/python-textile/issues/33) h2. Version 2.3.5 * Bugfix: Correctly handle unicode text in url query-strings. ("#36":https://github.com/textile/python-textile/issues/36) h2. Version 2.3.4 * Bugfix: fix an issue with extended block code * Remove misplaced shebang on non-callable files. * Packaging: Add test-command to setup.py directly. * Packaging: Included the tests/ directory for source-tarballs, useful for packaging checks. ("#33":https://github.com/textile/python-textile/issues/33) * Add a cli tool @pytextile@ which takes textile input and prints html output. See @pytextile -h@ for details. h2. Version 2.3.3 * Bugfix: Unicode in URL titles no longer break everything ("#30":https://github.com/textile/python-textile/issues/30) * Display DeprecationWarning when using textile on Python 2.6. h2. Version 2.3.2 * Bugfix: properly handle @":"@ as text, not a link. h2. Version 2.3.1 * Regression bugfix: empty string input returns empty string again. h2. Version 2.3.0 * Bugfixes: ** Support data URIs in img tags ** Fix autolink urls with image references ("#17":https://github.com/textile/python-textile/issues/17) ** Fix textile links containing parentheses ("#20":https://github.com/textile/python-textile/issues/20) ** Fix double-encoding of code blocks ("#21":https://github.com/textile/python-textile/issues/21) ** Fix handling of scheme in self-linked URLs ("#16":https://github.com/textile/python-textile/issues/16) ** Fix Markup not parsed if followed by certain characters ("#22":Markup not parsed if followed by certain characters) * Convert testing over to "py.test":http://pytest.org/, improving unicode testing * Update functionality for tables, notelists, and footnotes. This involved a major reworking of parts of the code, but it should now match php-textile and txstyle.org precisely. Please file an issue for any bugs you come across. * Remove @head_offset@ option from parse. I'm not sure it ever existed in php-textile. h2. Version 2.2.2 * bugfix: "regex":https://pypi.python.org/pypi/regex is now an optional dependency h2. Version 2.2.1 * drop textilefactory support for html. * Various development-related bugfixes. * Added this changelog. h2. Version 2.2.0 * Started refactoring the code to be less repetitive. @textile.Textile().parse()@ is a little more friendly than @textile.Textile().textile()@ There may be more work to be done on this front to make the flow a little smoother. * We now support versions 2.6 - 3.4 (including 3.2) using the same codebase. Many thanks to Radek Czajka for this. * Drop support for html4. We now only output xhtml or html5. * Various development-related bugfixes. h2. Version 2.1.8 * Add support for html5 output. * Lots of new functionality added bringing us in line with the official Textile 2.4 python-textile-4.0.1/CONTRIBUTORS.txt000066400000000000000000000002171361306003000171770ustar00rootroot00000000000000Dennis Burke Radek Czajka Roberto A. F. De Almeida Matt Layman Mark Pilgrim Alex Shiels Jason Samsa Kurt Raschke Dave Brondsema Dmitry Shachnevpython-textile-4.0.1/LICENSE.txt000066400000000000000000000026451361306003000163330ustar00rootroot00000000000000L I C E N S E ============= 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 Textile 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.python-textile-4.0.1/MANIFEST.in000066400000000000000000000000661361306003000162410ustar00rootroot00000000000000include MANIFEST.in include tests/fixtures/README.txt python-textile-4.0.1/README.textile000066400000000000000000000030031361306003000170320ustar00rootroot00000000000000!https://travis-ci.org/textile/python-textile.svg!:https://travis-ci.org/textile/python-textile !https://coveralls.io/repos/github/textile/python-textile/badge.svg!:https://coveralls.io/github/textile/python-textile?branch=master !https://codecov.io/github/textile/python-textile/coverage.svg!:https://codecov.io/github/textile/python-textile !https://img.shields.io/pypi/pyversions/textile! !https://img.shields.io/pypi/wheel/textile! h1. python-textile python-textile is a Python port of "Textile":http://txstyle.org/, Dean Allen's humane web text generator. h2. Installation @pip install textile@ Dependencies: * "html5lib":https://pypi.org/project/html5lib/ * "regex":https://pypi.org/project/regex/ (The regex package causes problems with PyPy, and is not installed as a dependency in such environments. If you are upgrading a textile install on PyPy which had regex previously included, you may need to uninstall it.) Optional dependencies include: * "PIL/Pillow":http://python-pillow.github.io/ (for checking image sizes). If needed, install via @pip install 'textile[imagesize]'@ h2. Usage bc.. import textile >>> s = """ ... _This_ is a *test.* ... ... * One ... * Two ... * Three ... ... Link to "Slashdot":http://slashdot.org/ ... """ >>> html = textile.textile(s) >>> print html

This is a test.

Link to Slashdot

>>> h3. Notes: * Active development supports Python 3.5 or later. python-textile-4.0.1/TODO.textile000066400000000000000000000005571361306003000166550ustar00rootroot00000000000000TODO * Improve documentation, both of the code and Textile syntax. ** Not all functions have docstrings or adequate docstrings. ** Because the Textile syntax implemented by PyTextile has deviated from the syntax implemented by other implementations of Textile, PyTextile-specific documentation needs to be produced for end-users. * Update to comply with Textile 2.5 python-textile-4.0.1/pytest.ini000066400000000000000000000001531361306003000165310ustar00rootroot00000000000000[pytest] testpaths = tests addopts = --cov=textile --cov-report=html --cov-append --cov-report=term-missingpython-textile-4.0.1/setup.cfg000066400000000000000000000000261361306003000163200ustar00rootroot00000000000000[aliases] test=pytest python-textile-4.0.1/setup.py000066400000000000000000000043551361306003000162220ustar00rootroot00000000000000from setuptools import setup, find_packages from setuptools.command.test import test as TestCommand import os import sys class PyTest(TestCommand): user_options = [('pytest-args=', 'a', "Arguments to pass to pytest")] def initialize_options(self): TestCommand.initialize_options(self) self.pytest_args = [] def run_tests(self): import shlex #import here, cause outside the eggs aren't loaded import pytest errno = pytest.main(shlex.split(self.pytest_args)) sys.exit(errno) def get_version(): basedir = os.path.dirname(__file__) with open(os.path.join(basedir, 'textile/version.py')) as f: variables = {} exec(f.read(), variables) return variables.get('VERSION') raise RuntimeError('No version info found.') setup( name='textile', version=get_version(), author='Dennis Burke', author_email='ikirudennis@gmail.com', description='Textile processing for python.', url='http://github.com/textile/python-textile', packages=find_packages(), classifiers=[ 'Development Status :: 5 - Production/Stable', 'Environment :: Web Environment', 'Intended Audience :: Developers', 'License :: OSI Approved :: BSD License', 'Operating System :: OS Independent', 'Programming Language :: Python', 'Programming Language :: Python :: 3', 'Programming Language :: Python :: 3 :: Only', 'Programming Language :: Python :: 3.5', 'Programming Language :: Python :: 3.6', 'Programming Language :: Python :: 3.7', 'Programming Language :: Python :: 3.8', 'Topic :: Software Development :: Libraries :: Python Modules', ], keywords='textile,text,html markup', install_requires=[ 'html5lib>=1.0.1', 'regex>1.0; implementation_name != "pypy"', ], extras_require={ 'develop': ['pytest', 'pytest-cov'], 'imagesize': ['Pillow>=3.0.0'], }, entry_points={'console_scripts': ['pytextile=textile.__main__:main']}, setup_requires=['pytest-runner'], tests_require=['pytest', 'pytest-cov'], cmdclass = {'test': PyTest}, include_package_data=True, zip_safe=False, python_requires='~=3.5', ) python-textile-4.0.1/tests/000077500000000000000000000000001361306003000156435ustar00rootroot00000000000000python-textile-4.0.1/tests/fixtures/000077500000000000000000000000001361306003000175145ustar00rootroot00000000000000python-textile-4.0.1/tests/fixtures/README.txt000066400000000000000000000041211361306003000212100ustar00rootroot00000000000000

python-textile

python-textile is a Python port of Textile, Dean Allen’s humane web text generator.

Installation

pip install textile

Dependencies:

Optional dependencies include:

Usage

import textile
>>> s = """
... _This_ is a *test.*
...
... * One
... * Two
... * Three
...
... Link to "Slashdot":http://slashdot.org/
... """
>>> html = textile.textile(s)
>>> print html
	<p><em>This</em> is a <strong>test.</strong></p>

	<ul>
		<li>One</li>
		<li>Two</li>
		<li>Three</li>
	</ul>

	<p>Link to <a href="http://slashdot.org/">Slashdot</a></p>
>>>

Notes:

python-textile-4.0.1/tests/test_attributes.py000066400000000000000000000015241361306003000214440ustar00rootroot00000000000000from textile.utils import parse_attributes import re def test_parse_attributes(): assert parse_attributes('\\1', element='td') == {'colspan': '1'} assert parse_attributes('/1', element='td') == {'rowspan': '1'} assert parse_attributes('^', element='td') == {'style': 'vertical-align:top;'} assert parse_attributes('{color: blue}') == {'style': 'color: blue;'} assert parse_attributes('[en]') == {'lang': 'en'} assert parse_attributes('(cssclass)') == {'class': 'cssclass'} assert parse_attributes('(') == {'style': 'padding-left:1em;'} assert parse_attributes(')') == {'style': 'padding-right:1em;'} assert parse_attributes('<') == {'style': 'text-align:left;'} assert parse_attributes('(c#i)') == {'class': 'c', 'id': 'i'} assert parse_attributes('\\2 100', element='col') == {'span': '2', 'width': '100'} python-textile-4.0.1/tests/test_block.py000066400000000000000000000056621361306003000203570ustar00rootroot00000000000000from __future__ import unicode_literals import textile from textile.objects import Block try: from collections import OrderedDict except ImportError: from ordereddict import OrderedDict def test_block(): t = textile.Textile() result = t.block('h1. foobar baby') expect = '\t

foobar baby

' assert result == expect b = Block(t, "bq", "", None, "", "Hello BlockQuote") expect = ('blockquote', OrderedDict(), 'p', OrderedDict(), 'Hello BlockQuote') result = (b.outer_tag, b.outer_atts, b.inner_tag, b.inner_atts, b.content) assert result == expect b = Block(t, "bq", "", None, "http://google.com", "Hello BlockQuote") citation = '{0}1:url'.format(t.uid) expect = ('blockquote', OrderedDict([('cite', '{0.uid}{0.refIndex}:url'.format(t))]), 'p', OrderedDict(), 'Hello BlockQuote') result = (b.outer_tag, b.outer_atts, b.inner_tag, b.inner_atts, b.content) assert result == expect b = Block(t, "bc", "", None, "", 'printf "Hello, World";') # the content of text will be turned shelved, so we'll asert only the # deterministic portions of the expected values, below expect = ('pre', OrderedDict(), 'code', OrderedDict()) result = (b.outer_tag, b.outer_atts, b.inner_tag, b.inner_atts) assert result == expect b = Block(t, "h1", "", None, "", "foobar") expect = ('h1', OrderedDict(), '', OrderedDict(), 'foobar') result = (b.outer_tag, b.outer_atts, b.inner_tag, b.inner_atts, b.content) assert result == expect def test_block_tags_false(): t = textile.Textile(block_tags=False) assert t.block_tags is False result = t.parse('test') expect = 'test' assert result == expect def test_blockcode_extended(): input = 'bc.. text\nmoretext\n\nevenmoretext\n\nmoremoretext\n\np. test' expect = '
text\nmoretext\n\nevenmoretext\n\nmoremoretext
\n\n\t

test

' t = textile.Textile() result = t.parse(input) assert result == expect def test_blockcode_in_README(): with open('README.textile') as f: readme = ''.join(f.readlines()) result = textile.textile(readme) with open('tests/fixtures/README.txt') as f: expect = ''.join(f.readlines()) assert result == expect def test_blockcode_comment(): input = '###.. block comment\nanother line\n\np. New line' expect = '\t

New line

' t = textile.Textile() result = t.parse(input) assert result == expect def test_extended_pre_block_with_many_newlines(): """Extra newlines in an extended pre block should not get cut down to only two.""" text = '''pre.. word another word yet anothe word''' expect = '''
word

another

word


yet anothe word
''' result = textile.textile(text) assert result == expect text = 'p. text text\n\n\nh1. Hello\n' expect = '\t

text text

\n\n\n\t

Hello

' result = textile.textile(text) assert result == expect python-textile-4.0.1/tests/test_cli.py000066400000000000000000000017231361306003000200260ustar00rootroot00000000000000import subprocess import sys import textile def test_console_script(): command = [sys.executable, '-m', 'textile', 'README.textile'] try: result = subprocess.check_output(command) except AttributeError: command[2] = 'textile.__main__' result = subprocess.Popen(command, stdout=subprocess.PIPE).communicate()[0] with open('tests/fixtures/README.txt') as f: expect = ''.join(f.readlines()) if type(result) == bytes: result = result.decode('utf-8') assert result == expect def test_version_string(): command = [sys.executable, '-m', 'textile', '-v'] try: result = subprocess.check_output(command) except AttributeError: command[2] = 'textile.__main__' result = subprocess.Popen(command, stdout=subprocess.PIPE).communicate()[0] if type(result) == bytes: result = result.decode('utf-8') assert result.strip() == textile.__version__ python-textile-4.0.1/tests/test_footnoteRef.py000066400000000000000000000003741361306003000215520ustar00rootroot00000000000000from textile import Textile import re def test_footnoteRef(): t = Textile() result = t.footnoteRef('foo[1]') expect = 'foo1'.format(t.linkPrefix) assert expect == result python-textile-4.0.1/tests/test_getRefs.py000066400000000000000000000004401361306003000206510ustar00rootroot00000000000000from textile import Textile def test_getRefs(): t = Textile() result = t.getRefs("some text [Google]http://www.google.com") expect = 'some text ' assert result == expect result = t.urlrefs expect = {'Google': 'http://www.google.com'} assert result == expect python-textile-4.0.1/tests/test_getimagesize.py000066400000000000000000000005131361306003000217300ustar00rootroot00000000000000from textile.tools.imagesize import getimagesize import pytest PIL = pytest.importorskip('PIL') def test_imagesize(): assert getimagesize("http://www.google.com/intl/en_ALL/images/logo.gif") == (276, 110) assert getimagesize("http://bad.domain/") == '' assert getimagesize("http://www.google.com/robots.txt") is None python-textile-4.0.1/tests/test_github_issues.py000066400000000000000000000354041361306003000221370ustar00rootroot00000000000000# -*- coding: utf-8 -*- from __future__ import unicode_literals import textile def test_github_issue_16(): result = textile.textile('"$":http://google.com "$":https://google.com "$":mailto:blackhole@sun.comet') expect = '\t

google.com google.com blackhole@sun.comet

' assert result == expect def test_github_issue_17(): result = textile.textile('!http://www.ox.ac.uk/favicon.ico!') expect = '\t

' assert result == expect def test_github_issue_20(): text = 'This is a link to a ["Wikipedia article about Textile":http://en.wikipedia.org/wiki/Textile_(markup_language)].' result = textile.textile(text) expect = '\t

This is a link to a Wikipedia article about Textile.

' assert result == expect def test_github_issue_21(): text = '''h1. xml example bc. bar ''' result = textile.textile(text) expect = '\t

xml example

\n\n
\n<foo>\n  bar\n</foo>
' assert result == expect def test_github_issue_22(): text = '''_(artist-name)Ty Segall_’s''' result = textile.textile(text) expect = '\t

Ty Segall’s

' assert result == expect def test_github_issue_26(): text = '' result = textile.textile(text) expect = '' assert result == expect def test_github_issue_27(): test = """* Folders with ":" in their names are displayed with a forward slash "/" instead. (Filed as "#4581709":/test/link, which was considered "normal behaviour" - quote: "Please note that Finder presents the 'Carbon filesystem' view, regardless of the underlying filesystem.")""" result = textile.textile(test) expect = """\t""" assert result == expect def test_github_issue_28(): test = """So here I am porting my ancient "newspipe":newspipe "front-end":blog/2006/09/30/0950 to "Snakelets":Snakelets and "Python":Python, and I've just trimmed down over 20 lines of "PHP":PHP down to essentially one line of "BeautifulSoup":BeautifulSoup retrieval:
def parseWapProfile(self, url):
  result = fetch.fetchURL(url)
  soup = BeautifulStoneSoup(result['data'], convertEntities=BeautifulStoneSoup.HTML_ENTITIES)
  try:
    width, height = soup('prf:screensize')[0].contents[0].split('x')
  except:
    width = height = None
  return {"width": width, "height": height}
Of course there's a lot more error handling to do (and useful data to glean off the "XML":XML), but being able to cut through all the usual parsing crap is immensely gratifying.""" result = textile.textile(test) expect = ("""\t

So here I am porting my ancient newspipe front-end to Snakelets and Python, and I’ve just trimmed down over 20 lines of PHP down to essentially one line of BeautifulSoup retrieval:

def parseWapProfile(self, url):
  result = fetch.fetchURL(url)
  soup = BeautifulStoneSoup(result['data'], convertEntities=BeautifulStoneSoup.HTML_ENTITIES)
  try:
    width, height = soup('prf:screensize')[0].contents[0].split('x')
  except:
    width = height = None
  return {"width": width, "height": height}
\t

Of course there’s a lot more error handling to do (and useful data to glean off the XML), but being able to cut through all the usual parsing crap is immensely gratifying.

""") assert result == expect def test_github_issue_30(): text ='"Tëxtíle (Tëxtíle)":http://lala.com' result = textile.textile(text) expect = '\t

Tëxtíle

' assert result == expect text ='!http://lala.com/lol.gif(♡ imáges)!' result = textile.textile(text) expect = '\t

♡ imáges

' assert result == expect def test_github_issue_36(): text = '"Chögyam Trungpa":https://www.google.com/search?q=Chögyam+Trungpa' result = textile.textile(text) expect = '\t

Chögyam Trungpa

' assert result == expect def test_github_issue_37(): text = '# xxx\n# yyy\n*blah*' result = textile.textile(text) expect = '\t

\t

    \n\t\t
  1. xxx
  2. \n\t\t
  3. yyy
  4. \n\t

\nblah

' assert result == expect text = '*Highlights*\n\n* UNITEK Y-3705A Type-C Universal DockingStation Pro\n* USB3.0/RJ45/EARPHONE/MICROPHONE/HDMI 6 PORT HUB 1.2m Data Cable 5V 4A Power Adaptor\n*\n* Dimensions: 25cm x 13cm x 9cm\n* Weight: 0.7kg' result = textile.textile(text) expect = '''\t

Highlights

\t * \t''' assert result == expect def test_github_issue_40(): text = '\r\n' result = textile.textile(text) expect = '\r\n' assert result == expect def test_github_issue_42(): text = '!./image.png!' result = textile.textile(text) expect = '\t

' assert result == expect def test_github_issue_43(): text = 'pre. smart ‘quotes’ are not smart!' result = textile.textile(text) expect = '
smart ‘quotes’ are not smart!
' assert result == expect def test_github_issue_45(): """Incorrect transform unicode url""" text = '"test":https://myabstractwiki.ru/index.php/%D0%97%D0%B0%D0%B3%D0%BB%D0%B0%D0%B2%D0%BD%D0%B0%D1%8F_%D1%81%D1%82%D1%80%D0%B0%D0%BD%D0%B8%D1%86%D0%B0' result = textile.textile(text) expect = '\t

test

' assert result == expect def test_github_issue_46(): """Key error on mal-formed numbered lists. CAUTION: both the input and the ouput are ugly.""" text = '# test\n### test\n## test' expect = ('\t
    \n\t\t
  1. test\n\t\t\t
      \n\t\t\t\t
    1. test
    2. ' '\n\t\t\t
  2. \n\t\t
      \n\t\t\t
    1. test
    2. ' '\n\t\t
    \n\t\t
') result = textile.textile(text) assert result == expect def test_github_issue_47(): """Incorrect wrap pre-formatted value""" text = '''pre.. word another word yet anothe word''' result = textile.textile(text) expect = '''
word

another

word

yet anothe word
''' assert result == expect def test_github_issue_49(): """Key error on russian hash-route link""" s = '"link":https://ru.vuejs.org/v2/guide/components.html#Входные-параметры' result = textile.textile(s) expect = '\t

link

' assert result == expect def test_github_issue_50(): """Incorrect wrap code with Java generics in pre""" test = ('pre.. public class Tynopet {}\n\nfinal ' 'List> multipleList = new ArrayList<>();') result = textile.textile(test) expect = ('
public class Tynopet<T extends Framework> {}\n\n'
              'final List<List<String>> multipleList = new '
              'ArrayList<>();
') assert result == expect def test_github_issue_51(): """Link build with $ sign without "http" prefix broken.""" test = '"$":www.google.com.br' result = textile.textile(test) expect = '\t

www.google.com.br

' assert result == expect def test_github_issue_52(): """Table build without space after aligment raise a AttributeError.""" test = '|=.First Header |=. Second Header |' result = textile.textile(test) expect = ('\t\n\t\t\n\t\t\t\n\t\t\t' '\n\t\t\n\t
=.First Header ' 'Second Header
') assert result == expect def test_github_issue_55(): """Incorrect handling of quote entities in extended pre block""" test = ('pre.. this is the first line\n\nbut "quotes" in an extended pre ' 'block need to be handled properly.') result = textile.textile(test) expect = ('
this is the first line\n\nbut "quotes" in an '
              'extended pre block need to be handled properly.
') assert result == expect # supplied input test = ('pre.. import org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;' '\nimport ru.onyma.job.Context;\nimport ru.onyma.job.' 'RescheduleTask;\n\nimport java.util.concurrent.' 'ScheduledExecutorService;\nimport java.util.concurrent.TimeUnit;' '\n\n/**\n* @author ustits\n*/\npublic abstract class ' 'MainService extends RescheduleTask implements Context {\n\n' 'private static final Logger log = LoggerFactory.getLogger(' 'MainService.class);\nprivate final ScheduledExecutorService ' 'scheduler;\n\nprivate boolean isFirstRun = true;\nprivate T ' 'configs;\n\npublic MainService(final ScheduledExecutorService ' 'scheduler) {\nsuper(scheduler);\nthis.scheduler = scheduler;\n}\n' '\n@Override\npublic void setConfig(final T configs) {\nthis.' 'configs = configs;\nif (isFirstRun) {\nscheduler.schedule(this, ' '0, TimeUnit.SECONDS);\nisFirstRun = false;\n}\n}\n\n@Override\n' 'public void stop() {\nsuper.stop();\nscheduler.shutdown();\ntry {' '\nscheduler.awaitTermination(Long.MAX_VALUE, TimeUnit.DAYS);\n} ' 'catch (InterruptedException ie) {\nlog.warn("Unable to wait for ' 'syncs termination", ie);\nThread.currentThread().interrupt();\n}' '\n}\n\nprotected final T getConfigs() {\nreturn configs;\n}\n}') result = textile.textile(test) expect = ('
import org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;'
              '\nimport ru.onyma.job.Context;\nimport ru.onyma.job.'
              'RescheduleTask;\n\nimport java.util.concurrent.'
              'ScheduledExecutorService;\nimport java.util.concurrent.'
              'TimeUnit;\n\n/**\n* @author ustits\n*/\npublic abstract class '
              'MainService<T> extends RescheduleTask implements '
              'Context<T> {\n\nprivate static final Logger log = '
              'LoggerFactory.getLogger(MainService.class);\nprivate final '
              'ScheduledExecutorService scheduler;\n\nprivate boolean '
              'isFirstRun = true;\nprivate T configs;\n\npublic MainService('
              'final ScheduledExecutorService scheduler) {\nsuper(scheduler);'
              '\nthis.scheduler = scheduler;\n}\n\n@Override\npublic void '
              'setConfig(final T configs) {\nthis.configs = configs;\nif ('
              'isFirstRun) {\nscheduler.schedule(this, 0, TimeUnit.SECONDS);'
              '\nisFirstRun = false;\n}\n}\n\n@Override\npublic void stop() {'
              '\nsuper.stop();\nscheduler.shutdown();\ntry {\nscheduler.'
              'awaitTermination(Long.MAX_VALUE, TimeUnit.DAYS);\n} catch '
              '(InterruptedException ie) {\nlog.warn("Unable to wait '
              'for syncs termination", ie);\nThread.currentThread().'
              'interrupt();\n}\n}\n\nprotected final T getConfigs() {\n'
              'return configs;\n}\n}
') assert result == expect def test_github_issue_56(): """Empty description lists throw error""" result = textile.textile("- :=\n-") expect = '
\n
' assert result == expect def test_github_pull_61(): """Fixed code block multiline encoding on quotes/span""" test = '''bc.. This is some TEXT inside a "Code BLOCK" { if (JSON) { return {"JSON":"value"} } } Back to 10-4 CAPS p.. Some multiline Paragragh Here is some output!!! "Some" CAPS''' expect = '''
This is some TEXT inside a "Code BLOCK"

{
  if (JSON) {

    return {"JSON":"value"}
  }
}

Back to 10-4 CAPS 

Some multiline Paragragh

Here is some output!!! “Some” CAPS

''' t = textile.Textile() result = t.parse(test) assert result == expect def test_github_pull_62(): """Fix for paragraph multiline, only last paragraph is rendered correctly""" test = '''p.. First one 'is' ESCAPED "bad" p.. Second one 'is' ESCAPED "bad" p.. Third one 'is' ESCAPED "bad" p.. Last one 'is' ESCAPED "good" test''' expect = '''

First one ‘is’

ESCAPED “bad”

Second one ‘is’

ESCAPED “bad”

Third one ‘is’

ESCAPED “bad”

Last one ‘is’

ESCAPED “good” test

''' t = textile.Textile() result = t.parse(test) assert result == expect def test_github_pull_63(): """Forgot to set multiline_para to False""" test = '''p.. First one 'is' ESCAPED "bad" bc.. { First code BLOCK {"JSON":'value'} } p.. Second one 'is' ESCAPED "bad" p.. Third one 'is' ESCAPED "bad" bc.. { Last code BLOCK {"JSON":'value'} } p.. Last one 'is' ESCAPED "good" test''' expect = '''

First one ‘is’

ESCAPED “bad”

{
 First code BLOCK

 {"JSON":'value'}
}

Second one ‘is’

ESCAPED “bad”

Third one ‘is’

ESCAPED “bad”

{
 Last code BLOCK

 {"JSON":'value'}
}

Last one ‘is’

ESCAPED “good” test

''' t = textile.Textile() result = t.parse(test) assert result == expect python-textile-4.0.1/tests/test_glyphs.py000066400000000000000000000012661361306003000205670ustar00rootroot00000000000000from textile import Textile def test_glyphs(): t = Textile() result = t.glyphs("apostrophe's") expect = 'apostrophe’s' assert result == expect result = t.glyphs("back in '88") expect = 'back in ’88' assert result == expect result = t.glyphs('foo ...') expect = 'foo …' assert result == expect result = t.glyphs('--') expect = '—' assert result == expect result = t.glyphs('FooBar[tm]') expect = 'FooBar™' assert result == expect result = t.glyphs("

Cat's Cradle by Vonnegut

") expect = '

Cat’s Cradle by Vonnegut

' assert result == expect python-textile-4.0.1/tests/test_image.py000066400000000000000000000014071361306003000203400ustar00rootroot00000000000000from textile import Textile def test_image(): t = Textile() result = t.image('!/imgs/myphoto.jpg!:http://jsamsa.com') expect = (''.format( t.uid)) assert result == expect assert t.refCache[1] == 'http://jsamsa.com' assert t.refCache[2] == '/imgs/myphoto.jpg' result = t.image('!'.format(t.uid)) assert result == expect python-textile-4.0.1/tests/test_imagesize.py000066400000000000000000000005141361306003000212310ustar00rootroot00000000000000import textile def test_imagesize(): imgurl = 'http://www.google.com/intl/en_ALL/images/srpr/logo1w.png' result = textile.tools.imagesize.getimagesize(imgurl) try: import PIL expect = (275, 95) assert result == expect except ImportError: expect = '' assert result == expect python-textile-4.0.1/tests/test_lists.py000066400000000000000000000003511361306003000204110ustar00rootroot00000000000000from textile import Textile def test_lists(): t = Textile() result = t.textileLists("* one\n* two\n* three") expect = '\t
    \n\t\t
  • one
  • \n\t\t
  • two
  • \n\t\t
  • three
  • \n\t
' assert result == expect python-textile-4.0.1/tests/test_retrieve.py000066400000000000000000000002061361306003000210770ustar00rootroot00000000000000from textile import Textile def test_retrieve(): t = Textile() id = t.shelve("foobar") assert t.retrieve(id) == 'foobar' python-textile-4.0.1/tests/test_span.py000066400000000000000000000011661361306003000202210ustar00rootroot00000000000000from textile import Textile def test_span(): t = Textile() result = t.span("hello %(bob)span *strong* and **bold**% goodbye") expect = ('hello span strong and ' 'bold goodbye') assert result == expect result = t.span('%:http://domain.tld test%') expect = 'test' assert result == expect t = Textile() # cover the partial branch where we exceed the max_span_depth. t.max_span_depth = 2 result = t.span('_-*test*-_') expect = '*test*' assert result == expect python-textile-4.0.1/tests/test_subclassing.py000066400000000000000000000007551361306003000216000ustar00rootroot00000000000000import textile def test_change_glyphs(): class TextilePL(textile.Textile): glyph_definitions = dict(textile.Textile.glyph_definitions, quote_double_open = '„' ) test = 'Test "quotes".' expect = '\t

Test „quotes”.

' result = TextilePL().parse(test) assert expect == result # Base Textile is unchanged. expect = '\t

Test “quotes”.

' result = textile.textile(test) assert expect == result python-textile-4.0.1/tests/test_table.py000066400000000000000000000014471361306003000203510ustar00rootroot00000000000000from textile import Textile def test_table(): t = Textile() result = t.table('(rowclass). |one|two|three|\n|a|b|c|') expect = '\t\n\t\t\n\t\t\t\n\t\t\t\n\t\t\t\n\t\t\n\t\t\n\t\t\t\n\t\t\t\n\t\t\t\n\t\t\n\t
onetwothree
abc
\n\n' assert result == expect t = Textile(lite=True) result = t.table('(lite). |one|two|three|\n|a|b|c|\n| * test\n* test|1|2|') expect = '\t\n\t\t\n\t\t\t\n\t\t\t\n\t\t\t\n\t\t\n\t\t\n\t\t\t\n\t\t\t\n\t\t\t\n\t\t\n\t\t\n\t\t\t\n\t\t\t\n\t\t\t\n\t\t\n\t
onetwothree
abc
* test\n* test12
\n\n' assert result == expect python-textile-4.0.1/tests/test_textile.py000066400000000000000000000453531361306003000207440ustar00rootroot00000000000000# -*- coding: utf-8 -*- from __future__ import unicode_literals import pytest import re import textile def test_FootnoteReference(): html = textile.textile('YACC[1]') assert re.search(r'^\t

YACC1

', html) is not None def test_Footnote(): html = textile.textile('This is covered elsewhere[1].\n\nfn1. Down here, in fact.\n\nfn2. Here is another footnote.') assert re.search(r'^\t

This is covered elsewhere1.

\n\n\t

1 Down here, in fact.

\n\n\t

2 Here is another footnote.

$', html) is not None html = textile.textile('''See[1] for details -- or perhaps[100] at a push.\n\nfn1. Here are the details.\n\nfn100(footy#otherid). A totally unrelated footnote.''') assert re.search(r'^\t

See1 for details — or perhaps100 at a push.

\n\n\t

1 Here are the details.

\n\n\t

100 A totally unrelated footnote.

$', html) is not None html = textile.textile('''See[2] for details, and later, reference it again[2].\n\nfn2^(footy#otherid)[en]. Here are the details.''') assert re.search(r'^\t

See2 for details, and later, reference it again2.

\n\n\t

2 Here are the details.

$', html) is not None html = textile.textile('''See[3!] for details.\n\nfn3. Here are the details.''') assert re.search(r'^\t

See3 for details.

\n\n\t

3 Here are the details.

$', html) is not None html = textile.textile('''See[4!] for details.\n\nfn4^. Here are the details.''') assert re.search(r'^\t

See4 for details.

\n\n\t

4 Here are the details.

$', html) is not None def test_issue_35(): result = textile.textile('"z"') expect = '\t

“z”

' assert result == expect result = textile.textile('" z"') expect = '\t

“ z”

' assert result == expect def test_restricted(): #Note that the HTML is escaped, thus rendering the " result = textile.textile_restricted(test) expect = "\t

Here is some text.
\n<script>alert(‘hello world’)</script>

" assert result == expect test = "Here's some text." result = textile.textile_restricted(test) expect = "\t

Here’s some <!— commented out —> text.

" assert result == expect test = "p[fr]. Partir, c'est toujours mourir un peu." result = textile.textile_restricted(test) expect = '\t

Partir, c’est toujours mourir un peu.

' assert result == expect test = "p{color:blue}. is this blue?" result = textile.textile_restricted(test) expect = '\t

is this blue?

' assert result == expect test = """\ table{border:1px solid black}. |={color:gray}. Your caption goes here |~. |{position:absolute}. A footer | foo | |-. |_{font-size:xxlarge}. header|_=. centered header| |~. bottom aligned|{background:red;width:200px}. asfd|""" result = textile.textile_restricted(test, lite=False) # styles from alignment hints like =. and ~. are ok expect = '''\ \t \t \t \t \t\t \t\t\t \t\t\t \t\t \t \t \t\t \t\t\t \t\t\t \t\t \t\t \t\t\t \t\t\t \t\t \t \t
Your caption goes here
A footer foo
headercentered header
bottom alignedasfd
''' assert result == expect def test_unicode_footnote(): html = textile.textile('текст[1]') assert re.compile(r'^\t

текст1

$', re.U).search(html) is not None def test_autolinking(): test = """some text "test":http://www.google.com http://www.google.com "$":http://www.google.com""" result = """\t

some text test http://www.google.com www.google.com

""" expect = textile.textile(test) assert result == expect def test_sanitize(): test = "a paragraph of benign text" result = "\t

a paragraph of benign text

" expect = textile.Textile().parse(test, sanitize=True) assert result == expect test = """

a paragraph of evil text

""" result = '

a paragraph of evil text

' expect = textile.Textile().parse(test, sanitize=True) assert result == expect test = """

a paragraph of benign text
and more text

""" result = '

a paragraph of benign text
\nand more text

' expect = textile.Textile(html_type='html5').parse(test, sanitize=True) assert result == expect def test_imagesize(): PIL = pytest.importorskip('PIL') test = "!http://www.google.com/intl/en_ALL/images/srpr/logo1w.png!" result = '\t

' expect = textile.Textile(get_sizes=True).parse(test) assert result == expect def test_endnotes_simple(): test = """Scientists say the moon is slowly shrinking[#my_first_label].\n\nnotelist!.\n\nnote#my_first_label Over the past billion years, about a quarter of the moon's 4.5 billion-year lifespan, it has shrunk about 200 meters (700 feet) in diameter.""" html = textile.textile(test) result_pattern = r"""\t

Scientists say the moon is slowly shrinking1.

\n\n\t
    \n\t\t
  1. Over the past billion years, about a quarter of the moon’s 4.5 billion-year lifespan, it has shrunk about 200 meters \(700 feet\) in diameter.
  2. \n\t
$""" result_re = re.compile(result_pattern) assert result_re.search(html) is not None def test_endnotes_complex(): test = """Tim Berners-Lee is one of the pioneer voices in favour of Net Neutrality[#netneutral] and has expressed the view that ISPs should supply "connectivity with no strings attached"[#netneutral!] [#tbl_quote]\n\nBerners-Lee admitted that the forward slashes ("//") in a web address were actually unnecessary. He told the newspaper that he could easily have designed URLs not to have the forward slashes. "... it seemed like a good idea at the time,"[#slashes]\n\nnote#netneutral. "Web creator rejects net tracking":http://news.bbc.co.uk/2/hi/technology/7613201.stm. BBC. 15 September 2008\n\nnote#tbl_quote. "Web inventor's warning on spy software":http://www.telegraph.co.uk/news/uknews/1581938/Web-inventor%27s-warning-on-spy-software.html. The Daily Telegraph (London). 25 May 2008\n\nnote#slashes. "Berners-Lee 'sorry' for slashes":http://news.bbc.co.uk/1/hi/technology/8306631.stm. BBC. 14 October 2009\n\nnotelist.""" html = textile.textile(test) result_pattern = r"""\t

Tim Berners-Lee is one of the pioneer voices in favour of Net Neutrality1 and has expressed the view that ISPs should supply “connectivity with no strings attached”1 2

\n\n\t

Berners-Lee admitted that the forward slashes \(“//”\) in a web address were actually unnecessary. He told the newspaper that he could easily have designed URLs not to have the forward slashes. “… it seemed like a good idea at the time,”3

\n\n\t
    \n\t\t
  1. a b Web creator rejects net tracking. BBC. 15 September 2008
  2. \n\t\t
  3. a Web inventor’s warning on spy software. The Daily Telegraph \(London\). 25 May 2008
  4. \n\t\t
  5. a Berners-Lee ‘sorry’ for slashes. BBC. 14 October 2009
  6. \n\t
$""" result_re = re.compile(result_pattern) assert result_re.search(html) is not None def test_endnotes_unreferenced_note(): test = """Scientists say[#lavader] the moon is quite small. But I, for one, don't believe them. Others claim it to be made of cheese[#aardman]. If this proves true I suspect we are in for troubled times[#apollo13] as people argue over their "share" of the moon's cheese. In the end, its limited size[#lavader] may prove problematic.\n\nnote#lavader(noteclass). "Proof of the small moon hypothesis":http://antwrp.gsfc.nasa.gov/apod/ap080801.html. Copyright(c) Laurent Laveder\n\nnote#aardman(#noteid). "Proof of a cheese moon":http://www.imdb.com/title/tt0104361\n\nnote#apollo13. After all, things do go "wrong":http://en.wikipedia.org/wiki/Apollo_13#The_oxygen_tank_incident.\n\nnotelist{padding:1em; margin:1em; border-bottom:1px solid gray}.\n\nnotelist{padding:1em; margin:1em; border-bottom:1px solid gray}:§^.\n\nnotelist{padding:1em; margin:1em; border-bottom:1px solid gray}:‡""" html = textile.textile(test) result_pattern = r"""\t

Scientists say1 the moon is quite small. But I, for one, don’t believe them. Others claim it to be made of cheese2. If this proves true I suspect we are in for troubled times3 as people argue over their “share” of the moon’s cheese. In the end, its limited size1 may prove problematic.

\n\n\t
    \n\t\t
  1. a b Proof of the small moon hypothesis. Copyright© Laurent Laveder
  2. \n\t\t
  3. a Proof of a cheese moon
  4. \n\t\t
  5. a After all, things do go wrong.
  6. \n\t
\n\n\t
    \n\t\t
  1. § Proof of the small moon hypothesis. Copyright© Laurent Laveder
  2. \n\t\t
  3. § Proof of a cheese moon
  4. \n\t\t
  5. § After all, things do go wrong.
  6. \n\t
\n\n\t
    \n\t\t
  1. Proof of the small moon hypothesis. Copyright© Laurent Laveder
  2. \n\t\t
  3. Proof of a cheese moon
  4. \n\t\t
  5. After all, things do go wrong.
  6. \n\t
""" result_re = re.compile(result_pattern, re.U) assert result_re.search(html) is not None def test_endnotes_malformed(): test = """Scientists say[#lavader] the moon is quite small. But I, for one, don't believe them. Others claim it to be made of cheese[#aardman]. If this proves true I suspect we are in for troubled times[#apollo13!] as people argue over their "share" of the moon's cheese. In the end, its limited size[#lavader] may prove problematic.\n\nnote#unused An unreferenced note.\n\nnote#lavader^ "Proof of the small moon hypothesis":http://antwrp.gsfc.nasa.gov/apod/ap080801.html. Copyright(c) Laurent Laveder\n\nnote#aardman^ "Proof of a cheese moon":http://www.imdb.com/title/tt0104361\n\nnote#apollo13^ After all, things do go "wrong":http://en.wikipedia.org/wiki/Apollo_13#The_oxygen_tank_incident.\n\nnotelist{padding:1em; margin:1em; border-bottom:1px solid gray}:α!+""" html = textile.textile(test) result_pattern = r"""^\t

Scientists say1 the moon is quite small. But I, for one, don’t believe them. Others claim it to be made of cheese2. If this proves true I suspect we are in for troubled times3 as people argue over their “share” of the moon’s cheese. In the end, its limited size1 may prove problematic.

\n\n\t
    \n\t\t
  1. α Proof of the small moon hypothesis. Copyright© Laurent Laveder
  2. \n\t\t
  3. α Proof of a cheese moon
  4. \n\t\t
  5. α After all, things do go wrong.
  6. \n\t\t
  7. An unreferenced note.
  8. \n\t
$""" result_re = re.compile(result_pattern, re.U) assert result_re.search(html) is not None def test_endnotes_undefined_note(): test = """Scientists say the moon is slowly shrinking[#my_first_label].\n\nnotelist!.""" html = textile.textile(test) result_pattern = r"""\t

Scientists say the moon is slowly shrinking1.

\n\n\t
    \n\t\t
  1. Undefined Note \[#my_first_label\].
  2. \n\t
$""" result_re = re.compile(result_pattern) assert result_re.search(html) is not None def test_encode_url(): # I tried adding these as doctests, but the unicode tests weren't # returning the correct results. t = textile.Textile() url = 'http://www.example.local' result = 'http://www.example.local' eurl = t.encode_url(url) assert eurl == result url = 'http://user@www.example.local' result = 'http://user@www.example.local' eurl = t.encode_url(url) assert eurl == result url = 'http://user:password@www.example.local' result = 'http://user:password@www.example.local' eurl = t.encode_url(url) assert eurl == result url = 'http://user:password@www.example.local/Ubermensch' result = 'http://user:password@www.example.local/Ubermensch' eurl = t.encode_url(url) assert eurl == result url = "http://user:password@www.example.local/Übermensch" result = "http://user:password@www.example.local/%C3%9Cbermensch" eurl = t.encode_url(url) assert eurl == result url = 'http://user:password@www.example.local:8080/Übermensch' result = 'http://user:password@www.example.local:8080/%C3%9Cbermensch' eurl = t.encode_url(url) assert eurl == result def test_footnote_crosslink(): html = textile.textile('''See[2] for details, and later, reference it again[2].\n\nfn2^(footy#otherid)[en]. Here are the details.''') searchstring = r'\t

See2 for details, and later, reference it again2.

\n\n\t

2 Here are the details.

$' assert re.compile(searchstring).search(html) is not None def test_footnote_without_reflink(): html = textile.textile('''See[3!] for details.\n\nfn3. Here are the details.''') searchstring = r'^\t

See3 for details.

\n\n\t

3 Here are the details.

$' assert re.compile(searchstring).search(html) is not None def testSquareBrackets(): html = textile.textile("""1[^st^], 2[^nd^], 3[^rd^]. 2 log[~n~]\n\nA close[!http://textpattern.com/favicon.ico!]image.\nA tight["text":http://textpattern.com/]link.\nA ["footnoted link":http://textpattern.com/][182].""") searchstring = r'^\t

1st, 2nd, 3rd. 2 logn

\n\n\t

A closeimage.
\nA tighttextlink.
\nA footnoted link182.

' assert re.compile(searchstring).search(html) is not None def test_html5(): """docstring for testHTML5""" test = 'We use CSS(Cascading Style Sheets).' result = '\t

We use CSS.

' expect = textile.textile(test, html_type="html5") assert result == expect def test_relURL(): t = textile.Textile() t.restricted = True assert t.relURL("gopher://gopher.com/") == '#' python-textile-4.0.1/tests/test_textilefactory.py000066400000000000000000000017511361306003000223260ustar00rootroot00000000000000from textile import textilefactory import pytest def test_TextileFactory(): f = textilefactory.TextileFactory() result = f.process("some text here") expect = '\t

some text here

' assert result == expect f = textilefactory.TextileFactory(restricted=True) result = f.process("more text here") expect = '\t

more text here

' assert result == expect f = textilefactory.TextileFactory(noimage=True) result = f.process("this covers a partial branch.") expect = '\t

this covers a partial branch.

' assert result == expect # Certain parameter values are not permitted because they are illogical: with pytest.raises(ValueError) as ve: f = textilefactory.TextileFactory(lite=True) assert 'lite can only be enabled in restricted mode' in str(ve.value) with pytest.raises(ValueError) as ve: f = textilefactory.TextileFactory(html_type='invalid') assert "html_type must be 'xhtml' or 'html5'" in str(ve.value) python-textile-4.0.1/tests/test_urls.py000066400000000000000000000046171361306003000202510ustar00rootroot00000000000000# -*- coding: utf-8 -*- from textile import Textile import re def test_urls(): t = Textile() assert t.relURL("http://www.google.com/") == 'http://www.google.com/' result = t.links('fooobar "Google":http://google.com/foobar/ and hello world "flickr":http://flickr.com/photos/jsamsa/ ') expect = 'fooobar {0}2:shelve and hello world {0}4:shelve '.format(t.uid) assert result == expect result = t.links('""Open the door, HAL!"":https://xkcd.com/375/') expect = '{0}6:shelve'.format(t.uid) assert result == expect result = t.links('"$":http://domain.tld/test_[brackets]') expect = '{0}8:shelve'.format(t.uid) assert result == expect result = t.links('"$":http://domain.tld/test_') expect = '{0}10:shelve'.format(t.uid) assert result == expect expect = '"":test' result = t.links(expect) assert result == expect expect = '"$":htt://domain.tld' result = t.links(expect) assert result == expect result = t.shelveURL('') expect = '' assert result == expect result = t.retrieveURLs('{0}2:url'.format(t.uid)) expect = '' assert result == expect result = t.encode_url('http://domain.tld/übermensch') expect = 'http://domain.tld/%C3%BCbermensch' assert result == expect result = t.parse('A link that starts with an h is "handled":/test/ incorrectly.') expect = '\t

A link that starts with an h is handled incorrectly.

' assert result == expect result = t.parse('A link that starts with a space" raises":/test/ an exception.') expect = '\t

A link that starts with a space” raises an exception.

' assert result == expect result = t.parse('A link that "contains a\nnewline":/test/ raises an exception.') expect = '\t

A link that contains a\nnewline raises an exception.

' assert result == expect def test_rel_attribute(): t = Textile(rel='nofollow') result = t.parse('"$":http://domain.tld') expect = '\t

domain.tld

' assert result == expect def test_quotes_in_link_text(): """quotes in link text are tricky.""" test = '""this is a quote in link text"":url' t = Textile() result = t.parse(test) expect = '\t

“this is a quote in link text”

' assert result == expect python-textile-4.0.1/tests/test_utils.py000066400000000000000000000021361361306003000204160ustar00rootroot00000000000000# -*- coding: utf-8 -*- from __future__ import unicode_literals from textile import utils def test_encode_html(): result = utils.encode_html('''this is a "test" of text that's safe to ''' 'put in an attribute.') expect = ('this is a "test" of text that's safe to put in ' 'an <html> attribute.') assert result == expect def test_has_raw_text(): assert utils.has_raw_text('

foo bar biz baz

') is False assert utils.has_raw_text(' why yes, yes it does') is True def test_is_rel_url(): assert utils.is_rel_url("http://www.google.com/") is False assert utils.is_rel_url("/foo") is True def test_generate_tag(): result = utils.generate_tag('span', 'inner text', {'class': 'test'}) expect = 'inner text' assert result == expect text = 'Übermensch' attributes = {'href': 'http://de.wikipedia.org/wiki/%C3%C9bermensch'} expect = 'Übermensch' result = utils.generate_tag('a', text, attributes) assert result == expect python-textile-4.0.1/tests/test_values.py000066400000000000000000000777411361306003000205730ustar00rootroot00000000000000# -*- coding: utf-8 -*- from __future__ import unicode_literals import textile import pytest xhtml_known_values = ( ('hello, world', '\t

hello, world

'), ('A single paragraph.\n\nFollowed by another.', '\t

A single paragraph.

\n\n\t

Followed by another.

'), ('I am very serious.\n\n
\nI am very serious.\n
', '\t

I am very serious.

\n\n
\nI am <b>very</b> serious.\n
'), ('I spoke.\nAnd none replied.', '\t

I spoke.
\nAnd none replied.

'), ('"Observe!"', '\t

“Observe!”

'), ('Observe -- very nice!', '\t

Observe — very nice!

'), ('Observe - tiny and brief.', '\t

Observe – tiny and brief.

'), ('Observe...', '\t

Observe…

'), ('Observe ...', '\t

Observe …

'), ('Observe: 2 x 2.', '\t

Observe: 2 × 2.

'), ('one(TM), two(R), three(C).', '\t

one™, two®, three©.

'), ('h1. Header 1', '\t

Header 1

'), ('h2. Header 2', '\t

Header 2

'), ('h3. Header 3', '\t

Header 3

'), ('An old text\n\nbq. A block quotation.\n\nAny old text''', '\t

An old text

\n\n\t
\n\t\t

A block quotation.

\n\t
\n\n\t

Any old text

'), ('I _believe_ every word.', '\t

I believe every word.

'), ('And then? She *fell*!', '\t

And then? She fell!

'), ('I __know__.\nI **really** __know__.', '\t

I know.
\nI really know.

'), ("??Cat's Cradle?? by Vonnegut", '\t

Cat’s Cradle by Vonnegut

'), ('Convert with @str(foo)@', '\t

Convert with str(foo)

'), ('I\'m -sure- not sure.', '\t

I’m sure not sure.

'), ('You are a +pleasant+ child.', '\t

You are a pleasant child.

'), ('a ^2^ + b ^2^ = c ^2^', '\t

a 2 + b 2 = c 2

'), ('log ~2~ x', '\t

log 2 x

'), ('I\'m %unaware% of most soft drinks.', '\t

I’m unaware of most soft drinks.

'), ("I'm %{color:red}unaware%\nof most soft drinks.", '\t

I’m unaware
\nof most soft drinks.

'), ('p(example1). An example', '\t

An example

'), ('p(#big-red). Red here', '\t

Red here

'), ('p(example1#big-red2). Red here', '\t

Red here

'), ('p{color:blue;margin:30px}. Spacey blue', '\t

Spacey blue

'), ('p[fr]. rouge', '\t

rouge

'), ('I seriously *{color:red}blushed*\nwhen I _(big)sprouted_ that\ncorn stalk from my\n%[es]cabeza%.', '\t

I seriously blushed
\nwhen I sprouted' ' that
\ncorn stalk from my
\ncabeza.

'), ('p<. align left', '\t

align left

'), ('p>. align right', '\t

align right

'), ('p=. centered', '\t

centered

'), ('p<>. justified', '\t

justified

'), ('p(. left ident 1em', '\t

left ident 1em

'), ('p((. left ident 2em', '\t

left ident 2em

'), ('p))). right ident 3em', '\t

right ident 3em

'), ('h2()>. Bingo.', '\t

Bingo.

'), ('h3()>[no]{color:red}. Bingo', '\t

Bingo

'), ('
\n\na.gsub!( /\n
', '
\n\na.gsub!( /</, "" )\n\n
'), ('
\n\nh3. Sidebar\n\n"Hobix":http://hobix.com/\n"Ruby":http://ruby-lang.org/\n\n
\n\n' 'The main text of the\npage goes here and will\nstay to the left of the\nsidebar.', '\t

\n\n\t

Sidebar

\n\n\t

Hobix
\n' 'Ruby

\n\n\t

\n\n\t

The main text of the
\n' 'page goes here and will
\nstay to the left of the
\nsidebar.

'), ('# A first item\n# A second item\n# A third', '\t
    \n\t\t
  1. A first item
  2. \n\t\t
  3. A second item
  4. \n\t\t
  5. A third
  6. \n\t
'), ('# Fuel could be:\n## Coal\n## Gasoline\n## Electricity\n# Humans need only:\n## Water\n## Protein', '\t
    \n\t\t
  1. Fuel could be:\n\t\t
      \n\t\t\t
    1. Coal
    2. \n\t\t\t
    3. Gasoline
    4. \n\t\t\t
    5. Electricity
    6. \n\t\t
  2. \n\t\t
  3. Humans need only:\n\t\t
      \n\t\t\t
    1. Water
    2. \n\t\t\t
    3. Protein
    4. \n\t\t
  4. \n\t\t
'), ('* A first item\n* A second item\n* A third', '\t
    \n\t\t
  • A first item
  • \n\t\t
  • A second item
  • \n\t\t
  • A third
  • \n\t
'), ('* Fuel could be:\n** Coal\n** Gasoline\n** Electricity\n* Humans need only:\n** Water\n** Protein', '\t
    \n\t\t
  • Fuel could be:\n\t\t
      \n\t\t\t
    • Coal
    • \n\t\t\t
    • Gasoline
    • \n\t\t\t
    • Electricity
    • \n\t\t
  • \n\t\t
  • Humans need only:\n\t\t
      \n\t\t\t
    • Water
    • \n\t\t\t
    • Protein
    • \n\t\t
  • \n\t\t
'), ('I searched "Google":http://google.com.', '\t

I searched Google.

'), ('I searched "a search engine (Google)":http://google.com.', '\t

I searched a search engine.

'), ('I am crazy about "Hobix":hobix\nand "it\'s":hobix "all":hobix I ever\n"link to":hobix!\n\n[hobix]http://hobix.com', '\t

I am crazy about Hobix
\nand it’s ' 'all I ever
\nlink to!

'), ('!http://hobix.com/sample.jpg!', '\t

'), ('!openwindow1.gif(Bunny.)!', '\t

Bunny.

'), ('!openwindow1.gif!:http://hobix.com/', '\t

'), ('!>obake.gif!\n\nAnd others sat all round the small\nmachine and paid it to sing to them.', '\t

\n\n\t' '

And others sat all round the small
\nmachine and paid it to sing to them.

'), ('We use CSS(Cascading Style Sheets).', '\t

We use CSS.

'), ('|one|two|three|\n|a|b|c|', '\t\n\t\t\n\t\t\t\n\t\t\t\n\t\t\t\n\t\t' '\n\t\t\n\t\t\t\n\t\t\t\n\t\t\t\n\t\t\n\t
onetwothree
abc
'), ('| name | age | sex |\n| joan | 24 | f |\n| archie | 29 | m |\n| bella | 45 | f |', '\t\n\t\t\n\t\t\t\n\t\t\t\n\t\t\t\n\t\t' '\n\t\t\n\t\t\t\n\t\t\t\n\t\t\t\n\t\t' '\n\t\t\n\t\t\t\n\t\t\t\n\t\t\t\n\t\t' '\n\t\t\n\t\t\t\n\t\t\t\n\t\t\t\n\t\t\n\t
name age sex
joan 24 f
archie 29 m
bella 45 f
'), ('|_. name |_. age |_. sex |\n| joan | 24 | f |\n| archie | 29 | m |\n| bella | 45 | f |', '\t\n\t\t\n\t\t\t\n\t\t\t\n\t\t\t\n\t\t' '\n\t\t\n\t\t\t\n\t\t\t\n\t\t\t\n\t\t' '\n\t\t\n\t\t\t\n\t\t\t\n\t\t\t\n\t\t' '\n\t\t\n\t\t\t\n\t\t\t\n\t\t\t\n\t\t\n\t
name age sex
joan 24 f
archie 29 m
bella 45 f
'), ('', '\t

'), ('pre.. Hello\n\nHello Again\n\np. normal text', '
Hello\n\nHello Again
\n\n\t

normal text

'), ('
this is in a pre tag
', '
this is in a pre tag
'), ('"test1":http://foo.com/bar--baz\n\n"test2":http://foo.com/bar---baz\n\n"test3":http://foo.com/bar-17-18-baz', '\t

test1

\n\n\t' '

test2

\n\n\t' '

test3

'), ('"foo ==(bar)==":#foobar', '\t

foo (bar)

'), ('!http://render.mathim.com/A%5EtAx%20%3D%20A%5Et%28Ax%29.!', '\t

'), ('* Point one\n* Point two\n## Step 1\n## Step 2\n## Step 3\n* Point three\n** Sub point 1\n** Sub point 2', '\t
    \n\t\t
  • Point one
  • \n\t\t
  • Point two\n\t\t
      \n\t\t\t
    1. Step 1
    2. \n\t\t\t
    3. Step 2
    4. \n\t\t\t
    5. Step 3
    6. \n\t\t
  • \n\t\t
  • Point three\n\t\t
      \n\t\t\t
    • Sub point 1
    • \n\t\t\t
    • Sub point 2
    • \n\t\t
  • \n\t\t
'), ('@array[4] = 8@', '\t

array[4] = 8

'), ('#{color:blue} one\n# two\n# three', '\t
    \n\t\t
  1. one
  2. \n\t\t
  3. two
  4. \n\t\t
  5. three
  6. \n\t
'), ('Links (like "this":http://foo.com), are now mangled in 2.1.0, whereas 2.0 parsed them correctly.', '\t

Links (like this), are now mangled in 2.1.0, whereas 2.0 parsed them correctly.

'), ('@monospaced text@, followed by text', '\t

monospaced text, followed by text

'), ('h2. A header\n\n\n\n\n\nsome text', '\t

A header

\n\n\n\n\n\n\t

some text

'), ('pre.. foo bar baz\nquux', '
foo bar baz\nquux
'), ('line of text\n\n leading spaces', '\t

line of text

\n\n leading spaces'), ('"some text":http://www.example.com/?q=foo%20bar and more text', '\t

some text and more text

'), ('(??some text??)', '\t

(some text)

'), ('(*bold text*)', '\t

(bold text)

'), ('H[~2~]O', '\t

H2O

'), ("p=. Où est l'école, l'église s'il vous plaît?", """\t

Où est l’école, l’église s’il vous plaît?

"""), ("p=. *_The_* _*Prisoner*_", """\t

The Prisoner

"""), ("""p=. "An emphasised _word._" & "*A spanned phrase.*" """, """\t

“An emphasised word.” & “A spanned phrase.

"""), ("""p=. "*Here*'s a word!" """, """\t

Here’s a word!”

"""), ("""p=. "Please visit our "Textile Test Page":http://textile.sitemonks.com" """, """\t

“Please visit our Textile Test Page

"""), ("""| Foreign EXPÓŅÉNTIAL |""", """\t\n\t\t\n\t\t\t\n\t\t\n\t
Foreign EXPÓŅÉNTIAL
"""), ("""Piękne ŹDŹBŁO""", """\t

Piękne ŹDŹBŁO

"""), ("""p=. Tell me, what is AJAX(Asynchronous Javascript and XML), please?""", """\t

Tell me, what is AJAX, please?

"""), ('p{font-size:0.8em}. *TxStyle* is a documentation project of Textile 2.4 for "Textpattern CMS":http://texpattern.com.', '\t

TxStyle is a documentation project of Textile 2.4 for Textpattern CMS.

'), (""""Übermensch":http://de.wikipedia.org/wiki/Übermensch""", """\t

Übermensch

"""), ("""Here is some text with a block.\n\n\n\n\n\nbc. """, """\t

Here is some text with a block.

\n\n\t

\n\n\t

\n\n
<!-- Here is a comment block in a code block. -->
"""), (""""Textile(c)" is a registered(r) 'trademark' of Textpattern(tm) -- or TXP(That's textpattern!) -- at least it was - back in '88 when 2x4 was (+/-)5(o)C ... QED!\n\np{font-size: 200%;}. 2(1/4) 3(1/2) 4(3/4)""", """\t

“Textile©” is a registered® ‘trademark’ of Textpattern™ — or TXP — at least it was – back in ’88 when 2×4 was ±5°C … QED!

\n\n\t

2¼ 3½ 4¾

"""), ("""|=. Testing colgroup and col syntax\n|:\\5. 80\n|a|b|c|d|e|\n\n|=. Testing colgroup and col syntax|\n|:\\5. 80|\n|a|b|c|d|e|""", """\t\n\t\n\t\n\t\n\t\t\n\t\t\t\n\t\t\t\n\t\t\t\n\t\t\t\n\t\t\t\n\t\t\n\t
Testing colgroup and col syntax
abcde
\n\n\t\n\t\n\t\n\t\n\t\t\n\t\t\t\n\t\t\t\n\t\t\t\n\t\t\t\n\t\t\t\n\t\t\n\t
Testing colgroup and col syntax
abcde
"""), ("""table(#dvds){border-collapse:collapse}. Great films on DVD employing Textile summary, caption, thead, tfoot, two tbody elements and colgroups\n|={font-size:140%;margin-bottom:15px}. DVDs with two Textiled tbody elements\n|:\\3. 100 |{background:#ddd}|250||50|300|\n|^(header).\n|_. Title |_. Starring |_. Director |_. Writer |_. Notes |\n|~(footer).\n|\\5=. This is the tfoot, centred |\n|-(toplist){background:#c5f7f6}.\n| _The Usual Suspects_ | Benicio Del Toro, Gabriel Byrne, Stephen Baldwin, Kevin Spacey | Bryan Singer | Chris McQaurrie | One of the finest films ever made |\n| _Se7en_ | Morgan Freeman, Brad Pitt, Kevin Spacey | David Fincher | Andrew Kevin Walker | Great psychological thriller |\n| _Primer_ | David Sullivan, Shane Carruth | Shane Carruth | Shane Carruth | Amazing insight into trust and human psychology
rather than science fiction. Terrific! |\n| _District 9_ | Sharlto Copley, Jason Cope | Neill Blomkamp | Neill Blomkamp, Terri Tatchell | Social commentary layered on thick,\nbut boy is it done well |\n|-(medlist){background:#e7e895;}.\n| _Arlington Road_ | Tim Robbins, Jeff Bridges | Mark Pellington | Ehren Kruger | Awesome study in neighbourly relations |\n| _Phone Booth_ | Colin Farrell, Kiefer Sutherland, Forest Whitaker | Joel Schumacher | Larry Cohen | Edge-of-the-seat stuff in this\nshort but brilliantly executed thriller |""", """\t\n\t\n\t\n\t\n\t\n\t\n\t\n\t\n\t\n\t\n\t\t\n\t\t\t\n\t\t\t\n\t\t\t\n\t\t\t\n\t\t\t\n\t\t\n\t\n\t\n\t\t\n\t\t\t\n\t\t\n\t\n\t\n\t\t\n\t\t\t\n\t\t\t\n\t\t\t\n\t\t\t\n\t\t\t\n\t\t\n\t\t\n\t\t\t\n\t\t\t\n\t\t\t\n\t\t\t\n\t\t\t\n\t\t\n\t\t\n\t\t\t\n\t\t\t\n\t\t\t\n\t\t\t\n\t\t\t\n\t\t\n\t\t\n\t\t\t\n\t\t\t\n\t\t\t\n\t\t\t\n\t\t\t\n\t\t\n\t\n\t\n\t\t\n\t\t\t\n\t\t\t\n\t\t\t\n\t\t\t\n\t\t\t\n\t\t\n\t\t\n\t\t\t\n\t\t\t\n\t\t\t\n\t\t\t\n\t\t\t\n\t\t\n\t\n\t
DVDs with two Textiled tbody elements
Title Starring Director Writer Notes
This is the tfoot, centred
The Usual Suspects Benicio Del Toro, Gabriel Byrne, Stephen Baldwin, Kevin Spacey Bryan Singer Chris McQaurrie One of the finest films ever made
Se7en Morgan Freeman, Brad Pitt, Kevin Spacey David Fincher Andrew Kevin Walker Great psychological thriller
Primer David Sullivan, Shane Carruth Shane Carruth Shane Carruth Amazing insight into trust and human psychology
\nrather than science fiction. Terrific!
District 9 Sharlto Copley, Jason Cope Neill Blomkamp Neill Blomkamp, Terri Tatchell Social commentary layered on thick,
\nbut boy is it done well
Arlington Road Tim Robbins, Jeff Bridges Mark Pellington Ehren Kruger Awesome study in neighbourly relations
Phone Booth Colin Farrell, Kiefer Sutherland, Forest Whitaker Joel Schumacher Larry Cohen Edge-of-the-seat stuff in this
\nshort but brilliantly executed thriller
"""), ("""-(hot) *coffee* := Hot _and_ black\n-(hot#tea) tea := Also hot, but a little less black\n-(cold) milk := Nourishing beverage for baby cows.\nCold drink that goes great with cookies. =:\n\n-(hot) coffee := Hot and black\n-(hot#tea) tea := Also hot, but a little less black\n-(cold) milk :=\nNourishing beverage for baby cows.\nCold drink that goes great with cookies. =:""", """
\n\t
coffee
\n\t
Hot and black
\n\t
tea
\n\t
Also hot, but a little less black
\n\t
milk
\n\t
Nourishing beverage for baby cows.
\nCold drink that goes great with cookies.
\n
\n\n
\n\t
coffee
\n\t
Hot and black
\n\t
tea
\n\t
Also hot, but a little less black
\n\t
milk
\n\t

Nourishing beverage for baby cows.
\nCold drink that goes great with cookies.

\n
"""), (""";(class#id) Term 1\n: Def 1\n: Def 2\n: Def 3""", """\t
\n\t\t
Term 1
\n\t\t
Def 1
\n\t\t
Def 2
\n\t\t
Def 3
\n\t
"""), ("""*Here is a comment*\n\nHere is *(class)a comment*\n\n*(class)Here is a class* that is a little extended and is\n*followed* by a strong word!\n\nbc. ; Content-type: text/javascript\n; Cache-Control: no-store, no-cache, must-revalidate, pre-check=0, post-check=0, max-age=0\n; Expires: Sat, 24 Jul 2003 05:00:00 GMT\n; Last-Modified: Wed, 1 Jan 2025 05:00:00 GMT\n; Pragma: no-cache\n\n*123 test*\n\n*test 123*\n\n**123 test**\n\n**test 123**""", """\t

Here is a comment

\n\n\t

Here is a comment

\n\n\t

Here is a class that is a little extended and is
\nfollowed by a strong word!

\n\n
; Content-type: text/javascript\n; Cache-Control: no-store, no-cache, must-revalidate, pre-check=0, post-check=0, max-age=0\n; Expires: Sat, 24 Jul 2003 05:00:00 GMT\n; Last-Modified: Wed, 1 Jan 2025 05:00:00 GMT\n; Pragma: no-cache
\n\n\t

123 test

\n\n\t

test 123

\n\n\t

123 test

\n\n\t

test 123

"""), ("""#_(first#list) one\n# two\n# three\n\ntest\n\n#(ordered#list2).\n# one\n# two\n# three\n\ntest\n\n#_(class_4).\n# four\n# five\n# six\n\ntest\n\n#_ seven\n# eight\n# nine\n\ntest\n\n# one\n# two\n# three\n\ntest\n\n#22 22\n# 23\n# 24""", """\t
    \n\t\t
  1. one
  2. \n\t\t
  3. two
  4. \n\t\t
  5. three
  6. \n\t
\n\n\t

test

\n\n\t
    \n\t\t
  1. one
  2. \n\t\t
  3. two
  4. \n\t\t
  5. three
  6. \n\t
\n\n\t

test

\n\n\t
    \n\t\t
  1. four
  2. \n\t\t
  3. five
  4. \n\t\t
  5. six
  6. \n\t
\n\n\t

test

\n\n\t
    \n\t\t
  1. seven
  2. \n\t\t
  3. eight
  4. \n\t\t
  5. nine
  6. \n\t
\n\n\t

test

\n\n\t
    \n\t\t
  1. one
  2. \n\t\t
  3. two
  4. \n\t\t
  5. three
  6. \n\t
\n\n\t

test

\n\n\t
    \n\t\t
  1. 22
  2. \n\t\t
  3. 23
  4. \n\t\t
  5. 24
  6. \n\t
"""), ("""# one\n##3 one.three\n## one.four\n## one.five\n# two\n\ntest\n\n#_(continuation#section2).\n# three\n# four\n##_ four.six\n## four.seven\n# five\n\ntest\n\n#21 twenty-one\n# twenty-two""", """\t
    \n\t\t
  1. one\n\t\t
      \n\t\t\t
    1. one.three
    2. \n\t\t\t
    3. one.four
    4. \n\t\t\t
    5. one.five
    6. \n\t\t
  2. \n\t\t
  3. two
  4. \n\t
\n\n\t

test

\n\n\t
    \n\t\t
  1. three
  2. \n\t\t
  3. four\n\t\t
      \n\t\t\t
    1. four.six
    2. \n\t\t\t
    3. four.seven
    4. \n\t\t
  4. \n\t\t
  5. five
  6. \n\t
\n\n\t

test

\n\n\t
    \n\t\t
  1. twenty-one
  2. \n\t\t
  3. twenty-two
  4. \n\t
"""), ("""|* Foo[^2^]\n* _bar_\n* ~baz~ |\n|#4 *Four*\n# __Five__ |\n|-(hot) coffee := Hot and black\n-(hot#tea) tea := Also hot, but a little less black\n-(cold) milk :=\nNourishing beverage for baby cows.\nCold drink that goes great with cookies. =:\n|""", """\t\n\t\t\n\t\t\t\n\t\t\n\t\t\n\t\t\t\n\t\t\n\t\t\n\t\t\t\n\t\t\n\t
\t
    \n\t\t
  • Foo2
  • \n\t\t
  • bar
  • \n\t\t
  • baz
  • \n\t
\t
    \n\t\t
  1. Four
  2. \n\t\t
  3. Five
  4. \n\t
\n\t
coffee
\n\t
Hot and black
\n\t
tea
\n\t
Also hot, but a little less black
\n\t
milk
\n\t

Nourishing beverage for baby cows.
\nCold drink that goes great with cookies.


\n
"""), ("""h4. A more complicated table\n\ntable(tableclass#tableid){color:blue}.\n|_. table |_. more |_. badass |\n|\\3. Horizontal span of 3|\n(firstrow). |first|HAL(open the pod bay doors)|1|\n|some|{color:green}. styled|content|\n|/2. spans 2 rows|this is|quite a|\n| deep test | don't you think?|\n(lastrow). |fifth|I'm a lumberjack|5|\n|sixth| _*bold italics*_ |6|""", """\t

A more complicated table

\n\n\t\n\t\t\n\t\t\t\n\t\t\t\n\t\t\t\n\t\t\n\t\t\n\t\t\t\n\t\t\n\t\t\n\t\t\t\n\t\t\t\n\t\t\t\n\t\t\n\t\t\n\t\t\t\n\t\t\t\n\t\t\t\n\t\t\n\t\t\n\t\t\t\n\t\t\t\n\t\t\t\n\t\t\n\t\t\n\t\t\t\n\t\t\t\n\t\t\n\t\t\n\t\t\t\n\t\t\t\n\t\t\t\n\t\t\n\t\t\n\t\t\t\n\t\t\t\n\t\t\t\n\t\t\n\t
table more badass
Horizontal span of 3
firstHAL1
somestyledcontent
spans 2 rowsthis isquite a
deep test don’t you think?
fifthI’m a lumberjack5
sixth bold italics 6
"""), ("""| *strong* |\n\n| _em_ |\n\n| Inter-word -dashes- | ZIP-codes are 5- or 9-digit codes |""", """\t\n\t\t\n\t\t\t\n\t\t\n\t
strong
\n\n\t\n\t\t\n\t\t\t\n\t\t\n\t
em
\n\n\t\n\t\t\n\t\t\t\n\t\t\t\n\t\t\n\t
Inter-word dashes ZIP-codes are 5- or 9-digit codes
"""), ("""|_. attribute list |\n|<. align left |\n|>. align right|\n|=. center |\n|<>. justify me|\n|^. valign top |\n|~. bottom |""", """\t\n\t\t\n\t\t\t\n\t\t\n\t\t\n\t\t\t\n\t\t\n\t\t\n\t\t\t\n\t\t\n\t\t\n\t\t\t\n\t\t\n\t\t\n\t\t\t\n\t\t\n\t\t\n\t\t\t\n\t\t\n\t\t\n\t\t\t\n\t\t\n\t
attribute list
align left
align right
center
justify me
valign top
bottom
"""), ("""h2. A definition list\n\n;(class#id) Term 1\n: Def 1\n: Def 2\n: Def 3\n;; Center\n;; NATO(Why Em Cee Ayy)\n:: Subdef 1\n:: Subdef 2\n;;; SubSub Term\n::: SubSub Def 1\n::: SubSub Def 2\n::: Subsub Def 3\nWith newline\n::: Subsub Def 4\n:: Subdef 3\n: DEF 4\n; Term 2\n: Another def\n: And another\n: One more\n:: A def without a term\n:: More defness\n; Third term for good measure\n: My definition of a boombastic jazz""", """\t

A definition list

\n\n\t
\n\t\t
Term 1
\n\t\t
Def 1
\n\t\t
Def 2
\n\t\t
Def 3\n\t\t
\n\t\t\t
Center
\n\t\t\t
NATO
\n\t\t\t
Subdef 1
\n\t\t\t
Subdef 2\n\t\t\t
\n\t\t\t\t
SubSub Term
\n\t\t\t\t
SubSub Def 1
\n\t\t\t\t
SubSub Def 2
\n\t\t\t\t
Subsub Def 3
\nWith newline
\n\t\t\t\t
Subsub Def 4
\n\t\t\t
\n\t\t\t
Subdef 3
\n\t\t
\n\t\t
DEF 4
\n\t\t
Term 2
\n\t\t
Another def
\n\t\t
And another
\n\t\t
One more\n\t\t
\n\t\t\t
A def without a term
\n\t\t\t
More defness
\n\t\t
\n\t\t
Third term for good measure
\n\t\t
My definition of a boombastic jazz
\n\t
"""), ("""###. Here's a comment.\n\nh3. Hello\n\n###. And\nanother\none.\n\nGoodbye.""", """\t

Hello

\n\n\t

Goodbye.

"""), ("""h2. A Definition list which covers the instance where a new definition list is created with a term without a definition\n\n- term :=\n- term2 := def""", """\t

A Definition list which covers the instance where a new definition list is created with a term without a definition

\n\n
\n\t
term2
\n\t
def
\n
"""), ('!{height:20px;width:20px;}https://1.gravatar.com/avatar/!', '\t

'), ('& test', '\t

& test

'), ) # A few extra cases for HTML4 html_known_values = ( ('I spoke.\nAnd none replied.', '\t

I spoke.
\nAnd none replied.

'), ('I __know__.\nI **really** __know__.', '\t

I know.
\nI really know.

'), ("I'm %{color:red}unaware%\nof most soft drinks.", '\t

I’m unaware
\nof most soft drinks.

'), ('I seriously *{color:red}blushed*\nwhen I _(big)sprouted_ that\ncorn stalk from my\n%[es]cabeza%.', '\t

I seriously blushed
\nwhen I sprouted' ' that
\ncorn stalk from my
\ncabeza.

'), ('
\n\na.gsub!( /\n
', '
\n\na.gsub!( /</, "" )\n\n
'), ('
\n\nh3. Sidebar\n\n"Hobix":http://hobix.com/\n"Ruby":http://ruby-lang.org/\n\n
\n\n' 'The main text of the\npage goes here and will\nstay to the left of the\nsidebar.', '\t

\n\n\t

Sidebar

\n\n\t

Hobix
\n' 'Ruby

\n\n\t

\n\n\t

The main text of the
\n' 'page goes here and will
\nstay to the left of the
\nsidebar.

'), ('I am crazy about "Hobix":hobix\nand "it\'s":hobix "all":hobix I ever\n"link to":hobix!\n\n[hobix]http://hobix.com', '\t

I am crazy about Hobix
\nand it’s ' 'all I ever
\nlink to!

'), ('!http://hobix.com/sample.jpg!', '\t

'), ('!openwindow1.gif(Bunny.)!', '\t

Bunny.

'), ('!openwindow1.gif!:http://hobix.com/', '\t

'), ('!>obake.gif!\n\nAnd others sat all round the small\nmachine and paid it to sing to them.', '\t

\n\n\t' '

And others sat all round the small
\nmachine and paid it to sing to them.

'), ('!http://render.mathim.com/A%5EtAx%20%3D%20A%5Et%28Ax%29.!', '\t

'), ('notextile. foo bar baz\n\np. quux\n', ' foo bar baz\n\n\t

quux

'), ('"foo":http://google.com/one--two', '\t

foo

'), # issue 24 colspan ('|\\2. spans two cols |\n| col 1 | col 2 |', '\t\n\t\t\n\t\t\t\n\t\t\n\t\t\n\t\t\t\n\t\t\t\n\t\t\n\t
spans two cols
col 1 col 2
'), # issue 2 escaping ('"foo ==(bar)==":#foobar', '\t

foo (bar)

'), # issue 14 newlines in extended pre blocks ("pre.. Hello\n\nAgain\n\np. normal text", '
Hello\n\nAgain
\n\n\t

normal text

'), # url with parentheses ('"python":http://en.wikipedia.org/wiki/Python_(programming_language)', '\t

python

'), # table with hyphen styles ('table(linkblog-thumbnail).\n|(linkblog-thumbnail-cell). apple|bear|', '\t\n\t\t\n\t\t\t\n\t\t\t\n\t\t\n\t
applebear
'), # issue 32 empty table cells ("|thing|||otherthing|", "\t\n\t\t\n\t\t\t\n\t\t\t\n\t\t\t\n\t\t\t\n\t\t\n\t
thingotherthing
"), # issue 36 link reference names http and https ('"signup":signup\n[signup]http://myservice.com/signup', '\t

signup

'), ('"signup":signup\n[signup]https://myservice.com/signup', '\t

signup

'), # nested formatting ("*_test text_*", "\t

test text

"), ("_*test text*_", "\t

test text

"), # quotes in code block ("'quoted string'", "\t

'quoted string'

"), ("
some preformatted text
other text", "\t

some preformatted text
other text

"), # at sign and notextile in table ("|@@|@@ @@|\n|*B1*|*B2* *B3*|", "\t\n\t\t\n\t\t\t\n\t\t\t\n\t\t\n\t\t\n\t\t\t\n\t\t\t\n\t\t\n\t
<A1><A2> <A3>
*B1**B2* *B3*
"), # cite attribute ('bq.:http://textism.com/ Text...', '\t
\n\t\t

Text…

\n\t
'), ('Hello ["(Mum) & dad"]', '\t

Hello [“(Mum) & dad”]

'), ) @pytest.mark.parametrize("input, expected_output", xhtml_known_values) def test_KnownValuesXHTML(input, expected_output): # XHTML output = textile.textile(input, html_type='xhtml') assert output == expected_output @pytest.mark.parametrize("input, expected_output", html_known_values) def test_KnownValuesHTML(input, expected_output): # HTML5 output = textile.textile(input, html_type='html5') assert output == expected_output python-textile-4.0.1/textile/000077500000000000000000000000001361306003000161575ustar00rootroot00000000000000python-textile-4.0.1/textile/__init__.py000066400000000000000000000003351361306003000202710ustar00rootroot00000000000000from __future__ import unicode_literals import sys import warnings from .core import textile, textile_restricted, Textile from .version import VERSION __all__ = ['textile', 'textile_restricted'] __version__ = VERSION python-textile-4.0.1/textile/__main__.py000066400000000000000000000026141361306003000202540ustar00rootroot00000000000000import argparse import sys import textile def main(): """A CLI tool in the style of python's json.tool. In fact, this is mostly copied directly from that module. This allows us to create a stand-alone tool as well as invoking it via `python -m textile`.""" prog = 'textile' description = ('A simple command line interface for textile module ' 'to convert textile input to HTML output. This script ' 'accepts input as a file or stdin and can write out to ' 'a file or stdout.') parser = argparse.ArgumentParser(prog=prog, description=description) parser.add_argument('-v', '--version', action='store_true', help='show the version number and exit') parser.add_argument('infile', nargs='?', type=argparse.FileType(), help='a textile file to be converted') parser.add_argument('outfile', nargs='?', type=argparse.FileType('w'), help='write the output of infile to outfile') options = parser.parse_args() if options.version: print(textile.VERSION) sys.exit() infile = options.infile or sys.stdin outfile = options.outfile or sys.stdout with infile: output = textile.textile(''.join(infile.readlines())) with outfile: outfile.write(output) if __name__ == '__main__': #pragma: no cover main() python-textile-4.0.1/textile/core.py000066400000000000000000001621761361306003000174760ustar00rootroot00000000000000# -*- coding: utf-8 -*- from __future__ import unicode_literals __copyright__ = """ Copyright (c) 2009, Jason Samsa, http://jsamsa.com/ Copyright (c) 2010, Kurt Raschke Copyright (c) 2004, Roberto A. F. De Almeida, http://dealmeida.net/ Copyright (c) 2003, Mark Pilgrim, http://diveintomark.org/ Original PHP Version: Copyright (c) 2003-2004, Dean Allen All rights reserved. Thanks to Carlo Zottmann for refactoring Textile's procedural code into a class framework Additions and fixes Copyright (c) 2006 Alex Shiels http://thresholdstate.com/ """ import uuid from urllib.parse import urlparse, urlsplit, urlunsplit, quote, unquote from collections import OrderedDict from textile.tools import sanitizer, imagesize from textile.regex_strings import (align_re_s, cls_re_s, pnct_re_s, regex_snippets, syms_re_s, table_span_re_s) from textile.utils import (decode_high, encode_high, encode_html, generate_tag, has_raw_text, is_rel_url, is_valid_url, list_type, normalize_newlines, parse_attributes, pba) from textile.objects import Block, Table try: import regex as re except ImportError: import re class Textile(object): restricted_url_schemes = ('http', 'https', 'ftp', 'mailto') unrestricted_url_schemes = restricted_url_schemes + ('file', 'tel', 'callto', 'sftp', 'data') btag = ('bq', 'bc', 'notextile', 'pre', 'h[1-6]', r'fn\d+', 'p', '###') btag_lite = ('bq', 'bc', 'p') note_index = 1 doctype_whitelist = ['xhtml', 'html5'] glyph_definitions = { 'quote_single_open': '‘', 'quote_single_close': '’', 'quote_double_open': '“', 'quote_double_close': '”', 'apostrophe': '’', 'prime': '′', 'prime_double': '″', 'ellipsis': '…', 'ampersand': '&', 'emdash': '—', 'endash': '–', 'dimension': '×', 'trademark': '™', 'registered': '®', 'copyright': '©', 'half': '½', 'quarter': '¼', 'threequarters': '¾', 'degrees': '°', 'plusminus': '±', } def __init__(self, restricted=False, lite=False, noimage=False, get_sizes=False, html_type='xhtml', rel='', block_tags=True): """Textile properties that are common to regular textile and textile_restricted""" self.restricted = restricted self.lite = lite self.noimage = noimage self.get_sizes = get_sizes self.fn = {} self.urlrefs = {} self.shelf = {} self.rel = rel self.html_type = html_type self.max_span_depth = 5 self.span_depth = 0 uid = uuid.uuid4().hex self.uid = 'textileRef:{0}:'.format(uid) self.linkPrefix = '{0}-'.format(uid) self.linkIndex = 0 self.refCache = {} self.refIndex = 0 self.block_tags = block_tags cur = r'' if regex_snippets['cur']: # pragma: no branch cur = r'(?:[{0}]{1}*)?'.format(regex_snippets['cur'], regex_snippets['space']) # We'll be searching for characters that need to be HTML-encoded to # produce properly valid html. These are the defaults that work in # most cases. Below, we'll copy this and modify the necessary pieces # to make it work for characters at the beginning of the string. self.glyph_search = [ # apostrophe's re.compile(r"(^|{0}|\))'({0})".format(regex_snippets['wrd']), flags=re.U), # back in '88 re.compile(r"({0})'(\d+{1}?)\b(?![.]?[{1}]*?')".format( regex_snippets['space'], regex_snippets['wrd']), flags=re.U), # single opening following an open bracket. re.compile(r"([([{])'(?=\S)", flags=re.U), # single closing re.compile(r"(^|\S)'(?={0}|{1}|<|$)".format( regex_snippets['space'], pnct_re_s), flags=re.U), # single opening re.compile(r"'", re.U), # double opening following an open bracket. Allows things like # Hello ["(Mum) & dad"] re.compile(r'([([{])"(?=\S)', flags=re.U), # double closing re.compile(r'(^|\S)"(?={0}|{1}|<|$)'.format( regex_snippets['space'], pnct_re_s), re.U), # double opening re.compile(r'"'), # ellipsis re.compile(r'([^.]?)\.{3}'), # ampersand re.compile(r'(\s?)&(\s)', re.U), # em dash re.compile(r'(\s?)--(\s?)'), # en dash re.compile(r' - '), # dimension sign re.compile(r'([0-9]+[\])]?[\'"]? ?)[x]( ?[\[(]?)' r'(?=[+-]?{0}[0-9]*\.?[0-9]+)'.format(cur), flags=re.I | re.U), # trademark re.compile(r'(\b ?|{0}|^)[([]TM[])]'.format(regex_snippets['space'] ), flags=re.I | re.U), # registered re.compile(r'(\b ?|{0}|^)[([]R[])]'.format(regex_snippets['space'] ), flags=re.I | re.U), # copyright re.compile(r'(\b ?|{0}|^)[([]C[])]'.format(regex_snippets['space'] ), flags=re.I | re.U), # 1/2 re.compile(r'[([]1\/2[])]'), # 1/4 re.compile(r'[([]1\/4[])]'), # 3/4 re.compile(r'[([]3\/4[])]'), # degrees re.compile(r'[([]o[])]'), # plus/minus re.compile(r'[([]\+\/-[])]'), # 3+ uppercase acronym re.compile(r'\b([{0}][{1}]{{2,}})\b(?:[(]([^)]*)[)])'.format( regex_snippets['abr'], regex_snippets['acr']), flags=re.U), # 3+ uppercase re.compile(r'({space}|^|[>(;-])([{abr}]{{3,}})([{nab}]*)' '(?={space}|{pnct}|<|$)(?=[^">]*?(<|$))'.format(**{ 'space': regex_snippets['space'], 'abr': regex_snippets['abr'], 'nab': regex_snippets['nab'], 'pnct': pnct_re_s}), re.U), ] # These are the changes that need to be made for characters that occur # at the beginning of the string. self.glyph_search_initial = list(self.glyph_search) # apostrophe's self.glyph_search_initial[0] = re.compile(r"({0}|\))'({0})".format( regex_snippets['wrd']), flags=re.U) # single closing self.glyph_search_initial[3] = re.compile(r"(\S)'(?={0}|{1}|$)".format( regex_snippets['space'], pnct_re_s), re.U) # double closing self.glyph_search_initial[6] = re.compile(r'(\S)"(?={0}|{1}|<|$)'.format( regex_snippets['space'], pnct_re_s), re.U) self.glyph_replace = [x.format(**self.glyph_definitions) for x in ( r'\1{apostrophe}\2', # apostrophe's r'\1{apostrophe}\2', # back in '88 r'\1{quote_single_open}', # single opening after bracket r'\1{quote_single_close}', # single closing r'{quote_single_open}', # single opening r'\1{quote_double_open}', # double opening after bracket r'\1{quote_double_close}', # double closing r'{quote_double_open}', # double opening r'\1{ellipsis}', # ellipsis r'\1{ampersand}\2', # ampersand r'\1{emdash}\2', # em dash r' {endash} ', # en dash r'\1{dimension}\2', # dimension sign r'\1{trademark}', # trademark r'\1{registered}', # registered r'\1{copyright}', # copyright r'{half}', # 1/2 r'{quarter}', # 1/4 r'{threequarters}', # 3/4 r'{degrees}', # degrees r'{plusminus}', # plus/minus r'\1', # 3+ uppercase acronym r'\1{0}:glyph:\2' # 3+ uppercase r'\3'.format(self.uid), )] if self.html_type == 'html5': self.glyph_replace[21] = r'\1' if self.restricted is True: self.url_schemes = self.restricted_url_schemes else: self.url_schemes = self.unrestricted_url_schemes def parse(self, text, rel=None, sanitize=False): """Parse the input text as textile and return html output.""" self.notes = OrderedDict() self.unreferencedNotes = OrderedDict() self.notelist_cache = OrderedDict() if text.strip() == '': return text if self.restricted: text = encode_html(text, quotes=False) text = normalize_newlines(text) text = text.replace(self.uid, '') if self.block_tags: if self.lite: self.blocktag_whitelist = ['bq', 'p'] text = self.block(text) else: self.blocktag_whitelist = [ 'bq', 'p', 'bc', 'notextile', 'pre', 'h[1-6]', 'fn{0}+'.format(regex_snippets['digit']), '###'] text = self.block(text) text = self.placeNoteLists(text) else: # Inline markup (em, strong, sup, sub, del etc). text = self.span(text) # Glyph level substitutions (mainly typographic -- " & ' => curly # quotes, -- => em-dash etc. text = self.glyphs(text) if rel: self.rel = ' rel="{0}"'.format(rel) text = self.getRefs(text) if not self.lite: text = self.placeNoteLists(text) text = self.retrieve(text) text = text.replace('{0}:glyph:'.format(self.uid), '') if sanitize: text = sanitizer.sanitize(text) text = self.retrieveURLs(text) # if the text contains a break tag (
or
) not followed by # a newline, replace it with a new style break tag and a newline. text = re.sub(r'(?!\n)', '
\n', text) text = text.rstrip('\n') return text def table(self, text): text = "{0}\n\n".format(text) pattern = re.compile(r'^(?:table(?P_?{s}{a}{c})\.' r'(?P.*?)\n)?^(?P{a}{c}\.? ?\|.*\|)' r'[\s]*\n\n'.format(**{'s': table_span_re_s, 'a': align_re_s, 'c': cls_re_s}), flags=re.S | re.M | re.U) match = pattern.search(text) if match: table = Table(self, **match.groupdict()) return table.process() return text def textileLists(self, text): pattern = re.compile(r'^((?:[*;:]+|[*;:#]*#(?:_|\d+)?){0}[ .].*)$' r'(?![^#*;:])'.format(cls_re_s), re.U | re.M | re.S) return pattern.sub(self.fTextileList, text) def fTextileList(self, match): text = re.split(r'\n(?=[*#;:])', match.group(), flags=re.M) pt = '' result = [] ls = OrderedDict() for i, line in enumerate(text): try: nextline = text[i + 1] except IndexError: nextline = '' m = re.search(r"^(?P[#*;:]+)(?P_|\d+)?(?P{0})[ .]" "(?P.*)$".format(cls_re_s), line, re.S) if m: tl, start, atts, content = m.groups() content = content.strip() else: result.append(line) continue nl = '' ltype = list_type(tl) tl_tags = {';': 'dt', ':': 'dd'} litem = tl_tags.get(tl[0], 'li') showitem = len(content) > 0 # handle list continuation/start attribute on ordered lists if ltype == 'o': if not hasattr(self, 'olstarts'): self.olstarts = {tl: 1} # does the first line of this ol have a start attribute if len(tl) > len(pt): # no, set it to 1. if start is None: self.olstarts[tl] = 1 # yes, set it to the given number elif start != '_': self.olstarts[tl] = int(start) # we won't need to handle the '_' case, we'll just # print out the number when it's needed # put together the start attribute if needed if len(tl) > len(pt) and start is not None: start = ' start="{0}"'.format(self.olstarts[tl]) # This will only increment the count for list items, not # definition items if showitem: # Assume properly formatted input try: self.olstarts[tl] = self.olstarts[tl] + 1 # if we get here, we've got some poor textile formatting. # add this type of list to olstarts and assume we'll start # it at 1. expect screwy output. except KeyError: self.olstarts[tl] = 1 nm = re.match(r"^(?P[#\*;:]+)(_|[\d]+)?{0}" r"[ .].*".format(cls_re_s), nextline) if nm: nl = nm.group('nextlistitem') # We need to handle nested definition lists differently. If # the next tag is a dt (';') of a lower nested level than the # current dd (':'), if ';' in pt and ':' in tl: ls[tl] = 2 atts = pba(atts, restricted=self.restricted) tabs = '\t' * len(tl) # If start is still None, set it to '', else leave the value that # we've already formatted. start = start or '' # if this item tag isn't in the list, create a new list and # item, else just create the item if tl not in ls: ls[tl] = 1 itemtag = ("\n{0}\t<{1}>{2}".format(tabs, litem, content) if showitem else '') line = "<{0}l{1}{2}>{3}".format(ltype, atts, start, itemtag) else: line = ("\t<{0}{1}>{2}".format(litem, atts, content) if showitem else '') line = '{0}{1}'.format(tabs, line) if len(nl) <= len(tl): if showitem: line = "{0}".format(line, litem) # work backward through the list closing nested lists/items for k, v in reversed(list(ls.items())): if len(k) > len(nl): if v != 2: line = "{0}\n{1}".format(line, tabs, list_type(k)) if len(k) > 1 and v != 2: line = "{0}".format(line, litem) del ls[k] # Remember the current Textile tag: pt = tl # This else exists in the original php version. I'm not sure how # to come up with a case where the line would not match. I think # it may have been necessary due to the way php returns matches. # else: #line = "{0}\n".format(line) result.append(line) return self.doTagBr(litem, "\n".join(result)) def doTagBr(self, tag, input): return re.compile(r'<({0})([^>]*?)>(.*)()'.format(re.escape(tag)), re.S).sub(self.doBr, input) def doPBr(self, in_): return re.compile(r'<(p)([^>]*?)>(.*)()', re.S).sub(self.doBr, in_) def doBr(self, match): content = re.sub(r'(.+)(?:(?)|(?))\n(?![#*;:\s|])', r'\1
', match.group(3)) return '<{0}{1}>{2}{3}'.format(match.group(1), match.group(2), content, match.group(4)) def block(self, text): if not self.lite: tre = '|'.join(self.btag) else: tre = '|'.join(self.btag_lite) # split the text by two or more newlines, retaining the newlines in the # split list text = re.split(r'(\n{2,})', text) # some blocks, when processed, will ask us to output nothing, if that's # the case, we'd want to drop the whitespace which follows it. eat_whitespace = False # check to see if previous block has already been escaped escaped = False # check if multiline paragraph (p..) tags

..

are added to line multiline_para = False tag = 'p' atts = cite = ext = '' out = [] for line in text: # the line is just whitespace, add it to the output, and move on if not line.strip(): if not eat_whitespace: out.append(line) continue eat_whitespace = False pattern = (r'^(?P{0})(?P{1}{2})\.(?P\.?)' r'(?::(?P\S+))? (?P.*)$'.format(tre, align_re_s, cls_re_s)) match = re.search(pattern, line, flags=re.S | re.U) # tag specified on this line. if match: # if we had a previous extended tag but not this time, close up # the tag if ext and out: # it's out[-2] because the last element in out is the # whitespace that preceded this line if not escaped: content = encode_html(out[-2], quotes=True) escaped = True else: content = out[-2] if not multiline_para: content = generate_tag(block.inner_tag, content, block.inner_atts) content = generate_tag(block.outer_tag, content, block.outer_atts) out[-2] = content tag, atts, ext, cite, content = match.groups() block = Block(self, **match.groupdict()) inner_block = generate_tag(block.inner_tag, block.content, block.inner_atts) # code tags and raw text won't be indented inside outer_tag. if block.inner_tag != 'code' and not has_raw_text(inner_block): inner_block = "\n\t\t{0}\n\t".format(inner_block) if ext: line = block.content else: line = generate_tag(block.outer_tag, inner_block, block.outer_atts) # pre tags and raw text won't be indented. if block.outer_tag != 'pre' and not has_raw_text(line): line = "\t{0}".format(line) # set having paragraph tags to false if block.tag == 'p' and ext: multiline_para = False # no tag specified else: # if we're inside an extended block, add the text from the # previous line to the front. if ext and out: if block.tag == 'p': line = generate_tag(block.tag, line, block.outer_atts) multiline_para = True line = '{0}{1}'.format(out.pop(), line) # the logic in the if statement below is a bit confusing in # php-textile. I'm still not sure I understand what the php # code is doing. Something tells me it's a phpsadness. Anyway, # this works, and is much easier to understand: if we're not in # an extension, and the line doesn't begin with a space, treat # it like a block to insert. Lines that begin with a space are # not processed as a block. if not ext and not line[0] == ' ': block = Block(self, tag, atts, ext, cite, line) # if the block contains html tags, generate_tag would # mangle it, so process as is. if block.tag == 'p' and not has_raw_text(block.content): line = block.content else: line = generate_tag(block.outer_tag, block.content, block.outer_atts) line = "\t{0}".format(line) else: if block.tag == 'pre' or block.inner_tag == 'code': line = self.shelve(encode_html(line, quotes=True)) else: line = self.graf(line) if block.tag == 'p': escaped = True if block.tag == 'p' and ext and not multiline_para: line = generate_tag(block.tag, line, block.outer_atts) multiline_para = True else: line = self.doPBr(line) if not block.tag == 'p': multiline_para = False line = line.replace('
', '
') # if we're in an extended block, and we haven't specified a new # tag, join this line to the last item of the output if ext and not match: last_item = out.pop() out.append('{0}{1}'.format(last_item, line)) elif not block.eat: # or if it's a type of block which indicates we shouldn't drop # it, add it to the output. out.append(line) if not ext: tag = 'p' atts = '' cite = '' # if it's a block we should drop, don't keep the whitespace which # will come after it. if block.eat: eat_whitespace = True # at this point, we've gone through all the lines. if there's still an # extension in effect, we close it here if ext and out and not block.tag == 'p': block.content = out.pop() block.process() final = generate_tag(block.outer_tag, block.content, block.outer_atts) out.append(final) return ''.join(out) def footnoteRef(self, text): # somehow php-textile gets away with not capturing the space. return re.compile(r'(?<=\S)\[(?P{0}+)(?P!?)\]' r'(?P{1}?)'.format(regex_snippets['digit'], regex_snippets['space']), re.U).sub(self.footnoteID, text) def footnoteID(self, m): fn_att = OrderedDict({'class': 'footnote'}) if m.group('id') not in self.fn: self.fn[m.group('id')] = '{0}{1}'.format(self.linkPrefix, self._increment_link_index()) fnid = self.fn[m.group('id')] fn_att['id'] = 'fnrev{0}'.format(fnid) fnid = self.fn[m.group('id')] footref = generate_tag('a', m.group('id'), {'href': '#fn{0}'.format( fnid)}) if '!' == m.group('nolink'): footref = m.group('id') footref = generate_tag('sup', footref, fn_att) return '{0}{1}'.format(footref, m.group('space')) def glyphs(self, text): """ Because of the split command, the regular expressions are different for when the text at the beginning and the rest of the text. for example: let's say the raw text provided is "*Here*'s some textile" before it gets to this glyphs method, the text has been converted to "Here's some textile" When run through the split, we end up with ["", "Here", "", "'s some textile"]. The re.search that follows tells it not to ignore html tags. If the single quote is the first character on the line, it's an open single quote. If it's the first character of one of those splits, it's an apostrophe or closed single quote, but the regex will bear that out. A similar situation occurs for double quotes as well. So, for the first pass, we use the glyph_search_initial set of regexes. For all remaining passes, we use glyph_search """ text = text.rstrip('\n') result = [] searchlist = self.glyph_search_initial # split the text by any angle-bracketed tags for i, line in enumerate(re.compile(r'(<[\w\/!?].*?>)', re.U).split( text)): if not i % 2: for s, r in zip(searchlist, self.glyph_replace): line = s.sub(r, line) result.append(line) if i == 0: searchlist = self.glyph_search return ''.join(result) def getRefs(self, text): """Capture and store URL references in self.urlrefs.""" pattern = re.compile(r'(?:(?<=^)|(?<=\s))\[(.+)\]((?:http(?:s?):\/\/|\/)\S+)(?=\s|$)', re.U) text = pattern.sub(self.refs, text) return text def refs(self, match): flag, url = match.groups() self.urlrefs[flag] = url return '' def relURL(self, url): scheme = urlparse(url)[0] if scheme and scheme not in self.url_schemes: return '#' return url def shelve(self, text): self.refIndex = self.refIndex + 1 itemID = '{0}{1}:shelve'.format(self.uid, self.refIndex) self.shelf[itemID] = text return itemID def retrieve(self, text): while True: old = text for k, v in self.shelf.items(): text = text.replace(k, v) if text == old: break return text def graf(self, text): if not self.lite: text = self.noTextile(text) text = self.code(text) text = self.getHTMLComments(text) text = self.getRefs(text) text = self.links(text) if not self.noimage: text = self.image(text) if not self.lite: text = self.table(text) text = self.redcloth_list(text) text = self.textileLists(text) text = self.span(text) text = self.footnoteRef(text) text = self.noteRef(text) text = self.glyphs(text) return text.rstrip('\n') def links(self, text): """For some reason, the part of the regex below that matches the url does not match a trailing parenthesis. It gets caught by tail, and we check later to see if it should be included as part of the url.""" text = self.markStartOfLinks(text) return self.replaceLinks(text) def markStartOfLinks(self, text): """Finds and marks the start of well formed links in the input text.""" # Slice text on '":' boundaries. These always occur in # inline links between the link text and the url part and are much more # infrequent than '"' characters so we have less possible links to # process. slice_re = re.compile(r'":(?={0})'.format(regex_snippets['char'])) slices = slice_re.split(text) output = [] if len(slices) > 1: # There are never any start of links in the last slice, so pop it # off (we'll glue it back later). last_slice = slices.pop() for s in slices: # If there is no possible start quote then this slice is not # a link if '"' not in s: output.append(s) continue # Cut this slice into possible starting points wherever we find # a '"' character. Any of these parts could represent the start # of the link text - we have to find which one. possible_start_quotes = s.split('"') # Start our search for the start of the link with the closest # prior quote mark. possibility = possible_start_quotes.pop() # Init the balanced count. If this is still zero at the end of # our do loop we'll mark the " that caused it to balance as the # start of the link and move on to the next slice. balanced = 0 linkparts = [] i = 0 while balanced != 0 or i == 0: # pragma: no branch # Starting at the end, pop off the previous part of the # slice's fragments. # Add this part to those parts that make up the link text. linkparts.append(possibility) if len(possibility) > 0: # did this part inc or dec the balanced count? if re.search(r'^\S|=$', possibility, flags=re.U): # pragma: no branch balanced = balanced - 1 if re.search(r'\S$', possibility, flags=re.U): # pragma: no branch balanced = balanced + 1 try: possibility = possible_start_quotes.pop() except IndexError: break else: # If quotes occur next to each other, we get zero # length strings. eg. ...""Open the door, # HAL!"":url... In this case we count a zero length in # the last position as a closing quote and others as # opening quotes. if i == 0: balanced = balanced + 1 else: balanced = balanced - 1 i = i + 1 try: possibility = possible_start_quotes.pop() except IndexError: # pragma: no cover # If out of possible starting segments we back the # last one from the linkparts array linkparts.pop() break # If the next possibility is empty or ends in a space # we have a closing ". if (possibility == '' or possibility.endswith(' ')): # force search exit balanced = 0; if balanced <= 0: possible_start_quotes.append(possibility) break # Rebuild the link's text by reversing the parts and sticking # them back together with quotes. linkparts.reverse() link_content = '"'.join(linkparts) # Rebuild the remaining stuff that goes before the link but # that's already in order. pre_link = '"'.join(possible_start_quotes) # Re-assemble the link starts with a specific marker for the # next regex. o = '{0}{1}linkStartMarker:"{2}'.format(pre_link, self.uid, link_content) output.append(o) # Add the last part back output.append(last_slice) # Re-assemble the full text with the start and end markers text = '":'.join(output) return text def replaceLinks(self, text): """Replaces links with tokens and stores them on the shelf.""" stopchars = r"\s|^'\"*" pattern = r""" (?P
\[)?           # Optionally open with a square bracket eg. Look ["here":url]
            {0}linkStartMarker:"   # marks start of the link
            (?P(?:.|\n)*?)  # grab the content of the inner "..." part of the link, can be anything but
                                   # do not worry about matching class, id, lang or title yet
            ":                     # literal ": marks end of atts + text + title block
            (?P[^{1}]*)      # url upto a stopchar
        """.format(self.uid, stopchars)
        text = re.compile(pattern, flags=re.X | re.U).sub(self.fLink, text)
        return text

    def fLink(self, m):
        in_ = m.group()
        pre, inner, url = m.groups()
        pre = pre or ''

        if inner == '':
            return '{0}"{1}":{2}'.format(pre, inner, url)

        m = re.search(r'''^
            (?P{0})                # $atts (if any)
            {1}*                         # any optional spaces
            (?P                    # $text is...
                (!.+!)                   #     an image
            |                            #   else...
                .+?                      #     link text
            )                            # end of $text
            (?:\((?P[^)]+?)\))?   # $title (if any)
            $'''.format(cls_re_s, regex_snippets['space']), inner,
                flags=re.X | re.U)

        atts = (m and m.group('atts')) or ''
        text = (m and m.group('text')) or inner
        title = (m and m.group('title')) or ''

        pop, tight = '', ''
        counts = { '[': None, ']': url.count(']'), '(': None, ')': None }

        # Look for footnotes or other square-bracket delimited stuff at the end
        # of the url...
        #
        # eg. "text":url][otherstuff... will have "[otherstuff" popped back
        # out.
        #
        # "text":url?q[]=x][123]    will have "[123]" popped off the back, the
        # remaining closing square brackets will later be tested for balance
        if (counts[']']):
            m = re.search(r'(?P<url>^.*\])(?P<tight>\[.*?)$', url, flags=re.U)
            if m:
                url, tight = m.groups()

        # Split off any trailing text that isn't part of an array assignment.
        # eg. "text":...?q[]=value1&q[]=value2 ... is ok
        # "text":...?q[]=value1]following  ... would have "following" popped
        # back out and the remaining square bracket will later be tested for
        # balance
        if (counts[']']):
            m = re.search(r'(?P<url>^.*\])(?!=)(?P<end>.*?)$', url, flags=re.U)
            url = m.group('url')
            tight = '{0}{1}'.format(m.group('end'), tight)

        # Now we have the array of all the multi-byte chars in the url we will
        # parse the  uri backwards and pop off  any chars that don't belong
        # there (like . or , or unmatched brackets of various kinds).
        first = True
        popped = True

        counts[']'] = url.count(']')
        url_chars = list(url)

        def _endchar(c, pop, popped, url_chars, counts, pre):
            """Textile URL shouldn't end in these characters, we pop them off
            the end and push them out the back of the url again."""
            pop = '{0}{1}'.format(c, pop)
            url_chars.pop()
            popped = True
            return pop, popped, url_chars, counts, pre

        def _rightanglebracket(c, pop, popped, url_chars, counts, pre):
            url_chars.pop()
            urlLeft = ''.join(url_chars)

            m = re.search(r'(?P<url_chars>.*)(?P<tag><\/[a-z]+)$', urlLeft)
            url_chars = m.group('url_chars')
            pop = '{0}{1}{2}'.format(m.group('tag'), c, pop)
            popped = True
            return pop, popped, url_chars, counts, pre

        def _closingsquarebracket(c, pop, popped, url_chars, counts, pre):
            """If we find a closing square bracket we are going to see if it is
            balanced.  If it is balanced with matching opening bracket then it
            is part of the URL else we spit it back out of the URL."""
            # If counts['['] is None, count the occurrences of '['
            counts['['] = counts['['] or url.count('[')

            if counts['['] == counts[']']:
                # It is balanced, so keep it
                url_chars.append(c)
            else:
                # In the case of un-matched closing square brackets we just eat
                # it
                popped = True
                url_chars.pop()
                counts[']'] = counts[']'] - 1;
                if first: # pragma: no branch
                    pre = ''
            return pop, popped, url_chars, counts, pre

        def _closingparenthesis(c, pop, popped, url_chars, counts, pre):
            if counts[')'] is None: # pragma: no branch
                counts['('] = url.count('(')
                counts[')'] = url.count(')')

            if counts['('] != counts[')']:
                # Unbalanced so spit it out the back end
                popped = True
                pop = '{0}{1}'.format(url_chars.pop(), pop)
                counts[')'] = counts[')'] - 1
            return pop, popped, url_chars, counts, pre

        def _casesdefault(c, pop, popped, url_chars, counts, pre):
            return pop, popped, url_chars, counts, pre

        cases = {
                '!': _endchar,
                '?': _endchar,
                ':': _endchar,
                ';': _endchar,
                '.': _endchar,
                ',': _endchar,
                '>': _rightanglebracket,
                ']': _closingsquarebracket,
                ')': _closingparenthesis,
                }
        for c in url_chars[-1::-1]: # pragma: no branch
            popped = False
            pop, popped, url_chars, counts, pre = cases.get(c,
                    _casesdefault)(c, pop, popped, url_chars, counts, pre)
            first = False
            if popped is False:
                break

        url = ''.join(url_chars)
        uri_parts = urlsplit(url)

        scheme_in_list = uri_parts.scheme in self.url_schemes
        valid_scheme = (uri_parts.scheme and scheme_in_list)
        if not is_valid_url(url) and not valid_scheme:
            return in_.replace('{0}linkStartMarker:'.format(self.uid), '')

        if text == '$':
            text = url
            if "://" in text:
                text = text.split("://")[1]
            elif ":" in text:
                text = text.split(":")[1]

        text = text.strip()
        title = encode_html(title)

        if not self.noimage: # pragma: no branch
            text = self.image(text)
        text = self.span(text)
        text = self.glyphs(text)
        url = self.shelveURL(self.encode_url(urlunsplit(uri_parts)))
        attributes = parse_attributes(atts, restricted=self.restricted)
        attributes['href'] = url
        if title:
            # if the title contains unicode data, it is annoying to get Python
            # 2.6 and all the latter versions working properly.  But shelving
            # the title is a quick and dirty solution.
            attributes['title'] = self.shelve(title)
        if self.rel:
            attributes['rel'] = self.rel
        a_text = generate_tag('a', text, attributes)
        a_shelf_id = self.shelve(a_text)

        out = '{0}{1}{2}{3}'.format(pre, a_shelf_id, pop, tight)

        return out

    def encode_url(self, url):
        """
        Converts a (unicode) URL to an ASCII URL, with the domain part
        IDNA-encoded and the path part %-encoded (as per RFC 3986).

        Fixed version of the following code fragment from Stack Overflow:
            http://stackoverflow.com/a/804380/72656
        """
        # parse it
        parsed = urlsplit(url)

        if parsed.netloc:
            # divide the netloc further
            netloc_pattern = re.compile(r"""
                (?:(?P<user>[^:@]+)(?::(?P<password>[^:@]+))?@)?
                (?P<host>[^:]+)
                (?::(?P<port>[0-9]+))?
            """, re.X | re.U)
            netloc_parsed = netloc_pattern.match(parsed.netloc).groupdict()
        else:
            netloc_parsed = {'user': '', 'password': '', 'host': '', 'port':
                    ''}

        # encode each component
        scheme = parsed.scheme
        user = netloc_parsed['user'] and quote(netloc_parsed['user'])
        password = (netloc_parsed['password'] and
                    quote(netloc_parsed['password']))
        host = netloc_parsed['host']
        port = netloc_parsed['port'] and netloc_parsed['port']
        # the below splits the path portion of the url by slashes, translates
        # percent-encoded characters back into strings, then re-percent-encodes
        # what's necessary. Sounds screwy, but the url could include encoded
        # slashes, and this is a way to clean that up. It branches for PY2/3
        # because the quote and unquote functions expects different input
        # types: unicode strings for PY2 and str for PY3.
        path_parts = (quote(unquote(pce), b'') for pce in
                parsed.path.split('/'))
        path = '/'.join(path_parts)

        # put it back together
        netloc = ''
        if user:
            netloc = '{0}{1}'.format(netloc, user)
            if password:
                netloc = '{0}:{1}'.format(netloc, password)
            netloc = '{0}@'.format(netloc)
        netloc = '{0}{1}'.format(netloc, host)
        if port:
            netloc = '{0}:{1}'.format(netloc, port)
        return urlunsplit((scheme, netloc, path, parsed.query, parsed.fragment))

    def span(self, text):
        qtags = (r'\*\*', r'\*', r'\?\?', r'\-', r'__',
                 r'_', r'%', r'\+', r'~', r'\^')
        pnct = r""".,"'?!;:‹›«»„“”‚‘’"""
        self.span_depth = self.span_depth + 1

        if self.span_depth <= self.max_span_depth:
            for tag in qtags:
                pattern = re.compile(r"""
                    (?P<pre>^|(?<=[\s>{pnct}\(])|[{{[])
                    (?P<tag>{tag})(?!{tag})
                    (?P<atts>{cls})
                    (?!{tag})
                    (?::(?P<cite>\S+[^{tag}]{space}))?
                    (?P<content>[^{space}{tag}]+|\S.*?[^\s{tag}\n])
                    (?P<end>[{pnct}]*)
                    {tag}
                    (?P<tail>$|[\[\]}}<]|(?=[{pnct}]{{1,2}}[^0-9]|\s|\)))
                """.format(**{'tag': tag, 'cls': cls_re_s, 'pnct': pnct,
                    'space': regex_snippets['space']}), flags=re.X | re.U)
                text = pattern.sub(self.fSpan, text)
        self.span_depth = self.span_depth - 1
        return text

    def fSpan(self, match):
        pre, tag, atts, cite, content, end, tail = match.groups()

        qtags = {
            '*':  'strong',
            '**': 'b',
            '??': 'cite',
            '_':  'em',
            '__': 'i',
            '-':  'del',
            '%':  'span',
            '+':  'ins',
            '~':  'sub',
            '^':  'sup'
        }

        tag = qtags[tag]
        atts = pba(atts, restricted=self.restricted)
        if cite:
            atts = '{0} cite="{1}"'.format(atts, cite.rstrip())

        content = self.span(content)

        out = "<{0}{1}>{2}{3}</{4}>".format(tag, atts, content, end, tag)
        if pre and not tail or tail and not pre:
            out = '{0}{1}{2}'.format(pre, out, tail)
        return out

    def image(self, text):
        pattern = re.compile(r"""
            (?:[\[{{])?         # pre
            \!                  # opening !
            (\<|\=|\>)?         # optional alignment atts
            ({0})               # optional style,class atts
            (?:\.\s)?           # optional dot-space
            ([^\s(!]+)          # presume this is the src
            \s?                 # optional space
            (?:\(([^\)]+)\))?   # optional title
            \!                  # closing
            (?::(\S+))?         # optional href
            (?:[\]}}]|(?=\s|$)) # lookahead: space or end of string
        """.format(cls_re_s), re.U | re.X)
        return pattern.sub(self.fImage, text)

    def fImage(self, match):
        # (None, '', '/imgs/myphoto.jpg', None, None)
        align, attributes, url, title, href = match.groups()
        atts = OrderedDict()
        size = None

        alignments = {'<': 'left', '=': 'center', '>': 'right'}

        if not title:
            title = ''

        if not is_rel_url(url) and self.get_sizes:
            size = imagesize.getimagesize(url)

        if href:
            href = self.shelveURL(href)

        url = self.shelveURL(url)

        if align:
            atts.update(align=alignments[align])
        atts.update(alt=title)
        if size:
            atts.update(height="{0}".format(size[1]))
        atts.update(src=url)
        if attributes:
            atts.update(parse_attributes(attributes, restricted=self.restricted))
        if title:
            atts.update(title=title)
        if size:
            atts.update(width="{0}".format(size[0]))
        img = generate_tag('img', ' /', atts)
        if href:
            a_atts = OrderedDict(href=href)
            if self.rel:
                a_atts.update(rel=self.rel)
            img = generate_tag('a', img, a_atts)
        return img

    def code(self, text):
        text = self.doSpecial(text, '<code>', '</code>', self.fCode)
        text = self.doSpecial(text, '@', '@', self.fCode)
        text = self.doSpecial(text, '<pre>', '</pre>', self.fPre)
        return text

    def fCode(self, match):
        before, text, after = match.groups()
        after = after or ''
        # text needs to be escaped
        text = encode_html(text, quotes=False)
        return ''.join([before, self.shelve('<code>{0}</code>'.format(text)), after])

    def fPre(self, match):
        before, text, after = match.groups()
        if after is None:
            after = ''
        # text needs to be escaped
        text = encode_html(text)
        return ''.join([before, '<pre>', self.shelve(text), '</pre>', after])

    def doSpecial(self, text, start, end, method):
        pattern = re.compile(r'(^|\s|[\[({{>|]){0}(.*?){1}($|[\])}}])?'.format(
            re.escape(start), re.escape(end)), re.M | re.S)
        return pattern.sub(method, text)

    def noTextile(self, text):
        text = self.doSpecial(text, '<notextile>', '</notextile>',
                              self.fTextile)
        return self.doSpecial(text, '==', '==', self.fTextile)

    def fTextile(self, match):
        before, notextile, after = match.groups()
        if after is None: # pragma: no branch
            after = ''
        return ''.join([before, self.shelve(notextile), after])

    def getHTMLComments(self, text):
        """Search the string for HTML comments, e.g. <!-- comment text -->.  We
        send the text that matches this to fParseHTMLComments."""
        return self.doSpecial(text, '<!--', '-->', self.fParseHTMLComments)

    def fParseHTMLComments(self, match):
        """If self.restricted is True, clean the matched contents of the HTML
        comment.  Otherwise, return the comments unchanged.
        The original php had an if statement in here regarding restricted mode.
        nose reported that this line wasn't covered.  It's correct.  In
        restricted mode, the html comment tags have already been converted to
        <!*#8212; and —> so they don't match in getHTMLComments,
        and never arrive here.
        """
        before, commenttext, after = match.groups()
        commenttext = self.shelve(commenttext)
        return '{0}<!--{1}-->'.format(before, commenttext)

    def redcloth_list(self, text):
        """Parse the text for definition lists and send them to be
        formatted."""
        pattern = re.compile(r"^([-]+{0}[ .].*:=.*)$(?![^-])".format(cls_re_s),
                re.M | re.U | re.S)
        return pattern.sub(self.fRCList, text)

    def fRCList(self, match):
        """Format a definition list."""
        out = []
        text = re.split(r'\n(?=[-])', match.group(), flags=re.M)
        for line in text:
            # parse the attributes and content
            m = re.match(r'^[-]+({0})[ .](.*)$'.format(cls_re_s), line,
                    flags=re.M | re.S)
            if not m:
                continue

            atts, content = m.groups()
            # cleanup
            content = content.strip()
            atts = pba(atts, restricted=self.restricted)

            # split the content into the term and definition
            xm = re.match(r'^(.*?)[\s]*:=(.*?)[\s]*(=:|:=)?[\s]*$', content,
                          re.S)
            term, definition, ending = xm.groups()
            # cleanup
            term = term.strip()
            definition = definition.strip(' ')

            # if this is the first time through, out as a bool is False
            if not out:
                if definition == '':
                    dltag = "<dl{0}>".format(atts)
                else:
                    dltag = "<dl>"
                out.append(dltag)

            if definition != '' and term != '':
                if definition.startswith('\n'):
                    definition = '<p>{0}</p>'.format(definition.lstrip())
                definition = definition.replace('\n', '<br />').strip()

                term = self.graf(term)
                definition = self.graf(definition)

                out.extend(['\t<dt{0}>{1}</dt>'.format(atts, term),
                    '\t<dd>{0}</dd>'.format(definition)])

        out.append('</dl>')
        out = '\n'.join(out)
        return out

    def placeNoteLists(self, text):
        """Parse the text for endnotes."""
        if self.notes:
            o = OrderedDict()
            for label, info in self.notes.items():
                if 'seq' in info:
                    i = info['seq']
                    info['seq'] = label
                    o[i] = info
                else:
                    self.unreferencedNotes[label] = info

            if o: # pragma: no branch
                # sort o by key
                o = OrderedDict(sorted(o.items(), key=lambda t: t[0]))
            self.notes = o
        text_re = re.compile(r'<p>notelist({0})(?:\:([\w|{1}]))?([\^!]?)(\+?)'
                r'\.?[\s]*</p>'.format(cls_re_s, syms_re_s), re.U)
        text = text_re.sub(self.fNoteLists, text)
        return text

    def fNoteLists(self, match):
        """Given the text that matches as a note, format it into HTML."""
        att, start_char, g_links, extras = match.groups()
        start_char = start_char or 'a'
        index = '{0}{1}{2}'.format(g_links, extras, start_char)
        result = ''

        if index not in self.notelist_cache: # pragma: no branch
            o = []
            if self.notes: # pragma: no branch
                for seq, info in self.notes.items():
                    links = self.makeBackrefLink(info, g_links, start_char)
                    atts = ''
                    if 'def' in info:
                        infoid = info['id']
                        atts = info['def']['atts']
                        content = info['def']['content']
                        li = ('\t\t<li{0}>{1}<span id="note{2}"> '
                                '</span>{3}</li>').format(atts, links, infoid,
                                        content)
                    else:
                        li = ('\t\t<li{0}>{1} Undefined Note [#{2}].<li>'
                                ).format(atts, links, info['seq'])
                    o.append(li)
            if '+' == extras and self.unreferencedNotes:
                for seq, info in self.unreferencedNotes.items():
                    atts = info['def']['atts']
                    content = info['def']['content']
                    li = '\t\t<li{0}>{1}</li>'.format(atts, content)
                    o.append(li)
            self.notelist_cache[index] = "\n".join(o)
            result = self.notelist_cache[index]
        list_atts = pba(att, restricted=self.restricted)
        result = '<ol{0}>\n{1}\n\t</ol>'.format(list_atts, result)
        return result

    def makeBackrefLink(self, info, g_links, i):
        """Given the pieces of a back reference link, create an <a> tag."""
        atts, content, infoid, link = '', '', '', ''
        if 'def' in info:
            link = info['def']['link']
        backlink_type = link or g_links
        i_ = encode_high(i)
        allow_inc = i not in syms_re_s
        i_ = int(i_)

        if backlink_type == "!":
            return ''
        elif backlink_type == '^':
            return """<sup><a href="#noteref{0}">{1}</a></sup>""".format(
                info['refids'][0], i)
        else:
            result = []
            for refid in info['refids']:
                i_entity = decode_high(i_)
                sup = """<sup><a href="#noteref{0}">{1}</a></sup>""".format(
                        refid, i_entity)
                if allow_inc:
                    i_ = i_ + 1
                result.append(sup)
            result = ' '.join(result)
            return result

    def fParseNoteDefs(self, m):
        """Parse the note definitions and format them as HTML"""
        label = m.group('label')
        link = m.group('link')
        att = m.group('att')
        content = m.group('content')

        # Assign an id if the note reference parse hasn't found the label yet.
        if label not in self.notes:
            self.notes[label] = {'id': '{0}{1}'.format(self.linkPrefix,
                self._increment_link_index())}

        # Ignores subsequent defs using the same label
        if 'def' not in self.notes[label]: # pragma: no branch
            self.notes[label]['def'] = {'atts': pba(att, restricted=self.restricted), 'content':
                    self.graf(content), 'link': link}
        return ''

    def noteRef(self, text):
        """Search the text looking for note references."""
        text_re = re.compile(r"""
        \[          # start
        ({0})       # !atts
        \#
        ([^\]!]+)   # !label
        ([!]?)      # !nolink
        \]""".format(cls_re_s), re.X)
        text = text_re.sub(self.fParseNoteRefs, text)
        return text

    def fParseNoteRefs(self, match):
        """Parse and format the matched text into note references.
        By the time this function is called, all the defs will have been
        processed into the notes array. So now we can resolve the link numbers
        in the order we process the refs..."""
        atts, label, nolink = match.groups()
        atts = pba(atts, restricted=self.restricted)
        nolink = nolink == '!'

        # Assign a sequence number to this reference if there isn't one already
        if label in self.notes:
            num = self.notes[label]['seq']
        else:
            self.notes[label] = {
                'seq': self.note_index, 'refids': [], 'id': ''
            }
            num = self.note_index
            self.note_index = self.note_index + 1

        # Make our anchor point and stash it for possible use in backlinks when
        # the note list is generated later...
        refid = '{0}{1}'.format(self.linkPrefix, self._increment_link_index())
        self.notes[label]['refids'].append(refid)

        # If we are referencing a note that hasn't had the definition parsed
        # yet, then assign it an ID...
        if not self.notes[label]['id']:
            self.notes[label]['id'] = '{0}{1}'.format(self.linkPrefix,
                    self._increment_link_index())
        labelid = self.notes[label]['id']

        # Build the link (if any)...
        result = '<span id="noteref{0}">{1}</span>'.format(refid, num)
        if not nolink:
            result = '<a href="#note{0}">{1}</a>'.format(labelid, result)

        # Build the reference...
        result = '<sup{0}>{1}</sup>'.format(atts, result)
        return result

    def shelveURL(self, text):
        if text == '':
            return ''
        self.refIndex = self.refIndex + 1
        self.refCache[self.refIndex] = text
        output = '{0}{1}{2}'.format(self.uid, self.refIndex, ':url')
        return output

    def retrieveURLs(self, text):
        return re.sub(r'{0}(?P<token>[0-9]+):url'.format(self.uid), self.retrieveURL, text)

    def retrieveURL(self, match):
        url = self.refCache.get(int(match.group('token')), '')
        if url == '':
            return url

        if url in self.urlrefs:
            url = self.urlrefs[url]

        return url

    def _increment_link_index(self):
        """The self.linkIndex property needs to be incremented in various
        places.  Don't Repeat Yourself."""
        self.linkIndex = self.linkIndex + 1
        return self.linkIndex


def textile(text, html_type='xhtml'):
    """
    Apply Textile to a block of text.

    This function takes the following additional parameters:

    html_type - 'xhtml' or 'html5' style tags (default: 'xhtml')

    """
    return Textile(html_type=html_type).parse(text)


def textile_restricted(text, lite=True, noimage=True, html_type='xhtml'):
    """
    Apply Textile to a block of text, with restrictions designed for weblog
    comments and other untrusted input.  Raw HTML is escaped, style attributes
    are disabled, and rel='nofollow' is added to external links.

    This function takes the following additional parameters:

    html_type - 'xhtml' or 'html5' style tags (default: 'xhtml')
    lite - restrict block tags to p, bq, and bc, disable tables (default: True)
    noimage - disable image tags (default: True)

    """
    return Textile(restricted=True, lite=lite, noimage=noimage,
            html_type=html_type, rel='nofollow').parse(
                    text)
��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������python-textile-4.0.1/textile/objects/���������������������������������������������������������������0000775�0000000�0000000�00000000000�13613060030�0017610�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������python-textile-4.0.1/textile/objects/__init__.py����������������������������������������������������0000664�0000000�0000000�00000000120�13613060030�0021712�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������from .block import Block
from .table import Table

__all__ = ['Block', 'Table']
������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������python-textile-4.0.1/textile/objects/block.py�������������������������������������������������������0000664�0000000�0000000�00000010702�13613060030�0021254�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# -*- coding: utf-8 -*-
from __future__ import unicode_literals

from collections import OrderedDict
try:
    import regex as re
except ImportError:
    import re

from textile.regex_strings import cls_re_s, regex_snippets
from textile.utils import encode_html, generate_tag, parse_attributes


class Block(object):
    def __init__(self, textile, tag, atts, ext, cite, content):
        self.textile = textile
        self.tag = tag
        self.atts = atts
        self.ext = ext
        self.cite = cite
        self.content = content

        self.attributes = parse_attributes(atts, restricted=self.textile.restricted)
        self.outer_tag = ''
        self.inner_tag = ''
        self.outer_atts = OrderedDict()
        self.inner_atts = OrderedDict()
        self.eat = False
        self.process()

    def process(self):
        if self.tag == 'p':
            # is this an anonymous block with a note definition?
            notedef_re = re.compile(r"""
            ^note\#                               # start of note def marker
            (?P<label>[^%<*!@\#^([{{ {space}.]+)  # label
            (?P<link>[*!^]?)                      # link
            (?P<att>{cls})                        # att
            \.?                                   # optional period.
            [{space}]+                            # whitespace ends def marker
            (?P<content>.*)$                      # content""".format(
                space=regex_snippets['space'], cls=cls_re_s),
            flags=re.X | re.U)
            notedef = notedef_re.sub(self.textile.fParseNoteDefs, self.content)

            # It will be empty if the regex matched and ate it.
            if '' == notedef:
                self.content = notedef
                self.eat = True

        fns = re.search(r'fn(?P<fnid>{0}+)'.format(regex_snippets['digit']),
                self.tag, flags=re.U)
        if fns:
            self.tag = 'p'
            fnid = self.textile.fn.get(fns.group('fnid'), None)
            if fnid is None:
                fnid = '{0}{1}'.format(self.textile.linkPrefix,
                        self.textile._increment_link_index())

            # If there is an author-specified ID goes on the wrapper & the
            # auto-id gets pushed to the <sup>
            supp_id = OrderedDict()

            # if class has not been previously specified, set it to "footnote"
            if 'class' not in self.attributes:
                self.attributes.update({'class': 'footnote'})

            # if there's no specified id, use the generated one.
            if 'id' not in self.attributes:
                self.attributes.update({'id': 'fn{0}'.format(fnid)})
            else:
                supp_id = parse_attributes('(#fn{0})'.format(fnid), restricted=self.textile.restricted)


            if '^' not in self.atts:
                sup = generate_tag('sup', fns.group('fnid'), supp_id)
            else:
                fnrev = generate_tag('a', fns.group('fnid'), {'href':
                    '#fnrev{0}'.format(fnid)})
                sup = generate_tag('sup', fnrev, supp_id)

            self.content = '{0} {1}'.format(sup, self.content)

        if self.tag == 'bq':
            if self.cite:
                self.cite = self.textile.shelveURL(self.cite)
                cite_att = OrderedDict(cite=self.cite)
                self.cite = ' cite="{0}"'.format(self.cite)
            else:
                self.cite = ''
                cite_att = OrderedDict()
            cite_att.update(self.attributes)
            self.outer_tag = 'blockquote'
            self.outer_atts = cite_att
            self.inner_tag = 'p'
            self.inner_atts = self.attributes
            self.eat = False

        elif self.tag == 'bc' or self.tag == 'pre':
            i_tag = ''
            if self.tag == 'bc':
                i_tag = 'code'
            content = encode_html(self.content)
            self.content = self.textile.shelve(content)
            self.outer_tag = 'pre'
            self.outer_atts = self.attributes
            self.inner_tag = i_tag
            self.inner_atts = self.attributes
            self.eat = False

        elif self.tag == 'notextile':
            self.content = self.textile.shelve(self.content)

        elif self.tag == '###':
            self.eat = True

        else:
            self.outer_tag = self.tag
            self.outer_atts = self.attributes

        if not self.eat:
            self.content = self.textile.graf(self.content)
        else:
            self.content = ''
��������������������������������������������������������������python-textile-4.0.1/textile/objects/table.py�������������������������������������������������������0000664�0000000�0000000�00000021412�13613060030�0021251�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# -*- coding: utf-8 -*-
from __future__ import unicode_literals

from xml.etree import ElementTree

from textile.regex_strings import (align_re_s, cls_re_s, regex_snippets,
        table_span_re_s, valign_re_s)
from textile.utils import encode_html, generate_tag, parse_attributes

try:
    import regex as re
except ImportError:
    import re


class Table(object):
    def __init__(self, textile, tatts, rows, summary):
        self.textile = textile
        self.attributes = parse_attributes(tatts, 'table', restricted=self.textile.restricted)
        if summary:
            self.attributes.update(summary=summary.strip())
        self.input = rows
        self.caption = ''
        self.colgroup = ''
        self.content = []

    def process(self):
        rgrp = None
        groups = []
        if self.input[-1] == '|': # pragma: no branch
            self.input = '{0}\n'.format(self.input)
        split = self.input.split('|\n')
        for i, row in enumerate([x for x in split if x]):
            row = row.lstrip()

            # Caption -- only occurs on row 1, otherwise treat '|=. foo |...'
            # as a normal center-aligned cell.
            if i == 0 and row[:2] == '|=':
                captionpattern = (r"^\|\=(?P<capts>{s}{a}{c})\. "
                                  r"(?P<cap>[^\n]*)(?P<row>.*)".format(**{
                                      's': table_span_re_s, 'a': align_re_s,
                                      'c': cls_re_s}))
                caption_re = re.compile(captionpattern, re.S)
                cmtch = caption_re.match(row)
                if cmtch:
                    caption = Caption(restricted=self.textile.restricted, **cmtch.groupdict())
                    self.caption = '\n{0}'.format(caption.caption)
                    row = cmtch.group('row').lstrip()
                    if row == '':
                        continue

            # Colgroup -- A colgroup row will not necessarily end with a |.
            # Hence it may include the next row of actual table data.
            if row[:2] == '|:':
                if '\n' in row:
                    colgroup_data, row = row[2:].split('\n')
                else:
                    colgroup_data, row = row[2:], ''
                colgroup_atts, cols = colgroup_data, None
                if '|' in colgroup_data:
                    colgroup_atts, cols = colgroup_data.split('|', 1)
                colgrp = Colgroup(cols, colgroup_atts, restricted=self.textile.restricted)
                self.colgroup = colgrp.process()
                if row == '':
                    continue

            # search the row for a table group - thead, tfoot, or tbody
            grpmatchpattern = (r"(:?^\|(?P<part>{v})(?P<rgrpatts>{s}{a}{c})"
                    r"\.\s*$\n)?^(?P<row>.*)").format(**{'v': valign_re_s, 's':
                        table_span_re_s, 'a': align_re_s, 'c': cls_re_s})
            grpmatch_re = re.compile(grpmatchpattern, re.S | re.M)
            grpmatch = grpmatch_re.match(row.lstrip())

            grptypes = {'^': Thead, '~': Tfoot, '-': Tbody}
            if grpmatch.group('part'):
                # we're about to start a new group, so process the current one
                # and add it to the output
                if rgrp:
                    groups.append('\n\t{0}'.format(rgrp.process()))
                rgrp = grptypes[grpmatch.group('part')](grpmatch.group(
                    'rgrpatts'), restricted=self.textile.restricted)
            row = grpmatch.group('row')

            rmtch = re.search(r'^(?P<ratts>{0}{1}\. )(?P<row>.*)'.format(
                align_re_s, cls_re_s), row.lstrip())
            if rmtch:
                row_atts = parse_attributes(rmtch.group('ratts'), 'tr', restricted=self.textile.restricted)
                row = rmtch.group('row')
            else:
                row_atts = {}

            # create a row to hold the cells.
            r = Row(row_atts, row)
            for cellctr, cell in enumerate(row.split('|')[1:]):
                ctag = 'td'
                if cell.startswith('_'):
                    ctag = 'th'

                cmtch = re.search(r'^(?P<catts>_?{0}{1}{2}\. )'
                        '(?P<cell>.*)'.format(table_span_re_s, align_re_s,
                            cls_re_s), cell, flags=re.S)
                if cmtch:
                    catts = cmtch.group('catts')
                    cell_atts = parse_attributes(catts, 'td', restricted=self.textile.restricted)
                    cell = cmtch.group('cell')
                else:
                    cell_atts = {}

                if not self.textile.lite:
                    a_pattern = r'(?P<space>{0}*)(?P<cell>.*)'.format(
                            regex_snippets['space'])
                    a = re.search(a_pattern, cell, flags=re.S)
                    cell = self.textile.redcloth_list(a.group('cell'))
                    cell = self.textile.textileLists(cell)
                    cell = '{0}{1}'.format(a.group('space'), cell)

                # create a cell
                c = Cell(ctag, cell, cell_atts)
                cline_tag = '\n\t\t\t{0}'.format(c.process())
                # add the cell to the row
                r.cells.append(self.textile.doTagBr(ctag, cline_tag))

            # if we're in a group, add it to the group's rows, else add it
            # directly to the content
            if rgrp:
                rgrp.rows.append(r.process())
            else:
                self.content.append(r.process())

        # if there's still an rgrp, process it and add it to the output
        if rgrp:
            groups.append('\n\t{0}'.format(rgrp.process()))

        content = '{0}{1}{2}{3}\n\t'.format(self.caption, self.colgroup,
                ''.join(groups), ''.join(self.content))
        tbl = generate_tag('table', content, self.attributes)
        return '\t{0}\n\n'.format(tbl)


class Caption(object):
    def __init__(self, capts, cap, row, restricted):
        self.attributes = parse_attributes(capts, restricted=restricted)
        self.caption = self.process(cap)

    def process(self, cap):
        tag = generate_tag('caption', cap, self.attributes)
        return '\t{0}\n\t'.format(tag)


class Colgroup(object):
    def __init__(self, cols, atts, restricted):
        self.row = ''
        self.attributes = atts
        self.cols = cols
        self.restricted = restricted

    def process(self):
        enc = 'unicode'

        group_atts = parse_attributes(self.attributes, 'col', restricted=self.restricted)
        colgroup = ElementTree.Element('colgroup', attrib=group_atts)
        colgroup.text = '\n\t'
        if self.cols is not None:
            has_newline = "\n" in self.cols
            match_cols = self.cols.replace('.', '').split('|')
            # colgroup is the first item in match_cols, the remaining items are
            # cols.
            for idx, col in enumerate(match_cols):
                col_atts = parse_attributes(col.strip(), 'col', restricted=self.restricted)
                ElementTree.SubElement(colgroup, 'col', col_atts)
        colgrp = ElementTree.tostring(colgroup, encoding=enc)
        # cleanup the extra xml declaration if it exists, (python versions
        # differ) and then format the resulting string accordingly: newline and
        # tab between cols and a newline at the end
        xml_declaration = "<?xml version='1.0' encoding='UTF-8'?>\n"
        colgrp = colgrp.replace(xml_declaration, '')
        return colgrp.replace('><', '>\n\t<')


class Row(object):
    def __init__(self, attributes, row):
        self.tag = 'tr'
        self.attributes = attributes
        self.cells = []

    def process(self):
        output = []
        for c in self.cells:
            output.append(c)
        cell_data = '{0}\n\t\t'.format(''.join(output))
        tag = generate_tag('tr', cell_data, self.attributes)
        return '\n\t\t{0}'.format(tag)


class Cell(object):
    def __init__(self, tag, content, attributes):
        self.tag = tag
        self.content = content
        self.attributes = attributes

    def process(self):
        return generate_tag(self.tag, self.content, self.attributes)


class _TableSection(object):
    def __init__(self, tag, attributes, restricted):
        self.tag = tag
        self.attributes = parse_attributes(attributes, restricted=restricted)
        self.rows = []

    def process(self):
        return generate_tag(self.tag, '{0}\n\t'.format(''.join(self.rows)), self.attributes)


class Thead(_TableSection):
    def __init__(self, attributes, restricted):
        super(Thead, self).__init__('thead', attributes, restricted)


class Tbody(_TableSection):
    def __init__(self, attributes, restricted):
        super(Tbody, self).__init__('tbody', attributes, restricted)


class Tfoot(_TableSection):
    def __init__(self, attributes, restricted):
        super(Tfoot, self).__init__('tfoot', attributes, restricted)
������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������python-textile-4.0.1/textile/regex_strings.py�������������������������������������������������������0000664�0000000�0000000�00000003504�13613060030�0021416�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# -*- coding: utf-8 -*-
from __future__ import unicode_literals

try:
    # Use regex module for matching uppercase characters if installed,
    # otherwise fall back to finding all the uppercase chars in a loop.
    import regex as re
    upper_re_s = r'\p{Lu}'
    regex_snippets = {
        'acr': r'\p{Lu}\p{Nd}',
        'abr': r'\p{Lu}',
        'nab': r'\p{Ll}',
        'wrd': r'(?:\p{L}|\p{M}|\p{N}|\p{Pc})',
        'cur': r'\p{Sc}',
        'digit': r'\p{N}',
        'space': r'(?:\p{Zs}|\v)',
        'char': r'(?:[^\p{Zs}\v])',
        }
except ImportError:
    from sys import maxunicode
    upper_re_s = "".join(
                [chr(c) for c in range(maxunicode) if chr(c).isupper()]
            )
    regex_snippets = {
        'acr': r'{0}0-9'.format(upper_re_s),
        'abr': r'{0}'.format(upper_re_s),
        'nab': r'a-z',
        'wrd': r'\w',
        'cur': r'',
        'digit': r'\d',
        'space': r'(?:\s|\v)',
        'char': r'\S',
        }

halign_re_s = r'(?:\<(?!>)|(?<!<)\>|\<\>|\=|[()]+(?! ))'
valign_re_s = r'[\-^~]'
class_re_s = r'(?:\([^)\n]+\))'       # Don't allow classes/ids,
language_re_s = r'(?:\[[^\]\n]+\])'   # languages,
style_re_s = r'(?:\{[^}\n]+\})'       # or styles to span across newlines
colspan_re_s = r'(?:\\\d+)'
rowspan_re_s = r'(?:\/\d+)'
align_re_s = r'(?:{0}|{1})*'.format(halign_re_s, valign_re_s)
table_span_re_s = r'(?:{0}|{1})*'.format(colspan_re_s, rowspan_re_s)
# regex string to match class, style and language attributes
cls_re_s = (r'(?:'
               r'{c}(?:{l}(?:{s})?|{s}(?:{l})?)?|'
               r'{l}(?:{c}(?:{s})?|{s}(?:{c})?)?|'
               r'{s}(?:{c}(?:{l})?|{l}(?:{c})?)?'
            r')?'
           ).format(c=class_re_s, s=style_re_s, l=language_re_s)
pnct_re_s = r'[-!"#$%&()*+,/:;<=>?@\'\[\\\]\.^_`{|}~]'
syms_re_s = '¤§µ¶†‡•∗∴◊♠♣♥♦'
��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������python-textile-4.0.1/textile/textilefactory.py������������������������������������������������������0000664�0000000�0000000�00000002410�13613060030�0021574�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������from __future__ import unicode_literals
from .core import Textile


class TextileFactory(object):
    """ Use TextileFactory to create a Textile object which can be re-used to
    process multiple strings with the same settings."""

    def __init__(self, restricted=False, lite=False, sanitize=False,
                 noimage=None, get_sizes=False, html_type='xhtml'):

        self.class_parms = {}
        self.method_parms = {}

        if lite and not restricted:
            raise ValueError("lite can only be enabled in restricted mode")

        if restricted:
            self.class_parms['restricted'] = True
            self.class_parms['lite'] = lite
            self.method_parms['rel'] = 'nofollow'

        if noimage is None:
            if restricted:
                noimage = True
            else:
                noimage = False

        self.class_parms['noimage'] = noimage
        self.method_parms['sanitize'] = sanitize
        self.class_parms['get_sizes'] = get_sizes

        if html_type not in ['xhtml', 'html5']:
            raise ValueError("html_type must be 'xhtml' or 'html5'")
        else:
            self.class_parms['html_type'] = html_type

    def process(self, text):
        return Textile(**self.class_parms).parse(text, **self.method_parms)
��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������python-textile-4.0.1/textile/tools/�����������������������������������������������������������������0000775�0000000�0000000�00000000000�13613060030�0017317�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������python-textile-4.0.1/textile/tools/__init__.py������������������������������������������������������0000664�0000000�0000000�00000000000�13613060030�0021416�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������python-textile-4.0.1/textile/tools/imagesize.py�����������������������������������������������������0000664�0000000�0000000�00000001201�13613060030�0021640�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������def getimagesize(url):
    """
    Attempts to determine an image's width and height, and returns a tuple,
    (width, height), in pixels or an empty string in case of failure.
    Requires that PIL is installed.

    """

    try:
        from PIL import ImageFile
    except ImportError:
        return ''

    from urllib.request import urlopen

    try:
        p = ImageFile.Parser()
        f = urlopen(url)
        while True:
            s = f.read(1024)
            if not s:
                break
            p.feed(s)
            if p.image:
                return p.image.size
    except (IOError, ValueError):
        return ''
�����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������python-textile-4.0.1/textile/tools/sanitizer.py�����������������������������������������������������0000664�0000000�0000000�00000000550�13613060030�0021701�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������def sanitize(string):
    """
    Ensure that the text does not contain any malicious HTML code which might
    break the page.
    """
    from html5lib import parseFragment, serialize

    parsed = parseFragment(string)
    clean = serialize(parsed, sanitize=True, omit_optional_tags=False,
                      quote_attr_values='always')
    return clean
��������������������������������������������������������������������������������������������������������������������������������������������������������python-textile-4.0.1/textile/utils.py���������������������������������������������������������������0000664�0000000�0000000�00000014266�13613060030�0017702�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������from __future__ import unicode_literals

try:
    import regex as re
except ImportError:
    import re

from urllib.parse import urlparse
import html

from collections import OrderedDict

from xml.etree import ElementTree

from textile.regex_strings import valign_re_s, halign_re_s


def decode_high(text):
    """Decode encoded HTML entities."""
    text = '&#{0};'.format(text)
    return html.unescape(text)

def encode_high(text):
    """Encode the text so that it is an appropriate HTML entity."""
    return ord(text)

def encode_html(text, quotes=True):
    """Return text that's safe for an HTML attribute."""
    a = (
        ('&', '&'),
        ('<', '<'),
        ('>', '>'))

    if quotes:
        a = a + (("'", '''),
                 ('"', '"'))

    for k, v in a:
        text = text.replace(k, v)
    return text

def generate_tag(tag, content, attributes=None):
    """Generate a complete html tag using the ElementTree module.  tag and
    content are strings, the attributes argument is a dictionary.  As
    a convenience, if the content is ' /', a self-closing tag is generated."""
    enc = 'unicode'
    if not tag:
        return content
    element = ElementTree.Element(tag, attrib=attributes)
    # Sort attributes for Python 3.8+, as suggested in
    # https://docs.python.org/3/library/xml.etree.elementtree.html
    if len(element.attrib) > 1:
        # adjust attribute order, e.g. by sorting
        attribs = sorted(element.attrib.items())
        element.attrib.clear()
        element.attrib.update(attribs)
    # FIXME: Kind of an ugly hack.  There *must* be a cleaner way.  I tried
    # adding text by assigning it to element_tag.text.  That results in
    # non-ascii text being html-entity encoded.  Not bad, but not entirely
    # matching php-textile either.
    element_tag = ElementTree.tostringlist(element, encoding=enc,
            method='html')
    element_tag.insert(len(element_tag) - 1, content)
    element_text = ''.join(element_tag)
    return element_text

def has_raw_text(text):
    """checks whether the text has text not already enclosed by a block tag"""
    # The php version has orders the below list of tags differently.  The
    # important thing to note here is that the pre must occur before the p or
    # else the regex module doesn't properly match pre-s. It only matches the
    # p in pre.
    r = re.compile(r'<(pre|p|blockquote|div|form|table|ul|ol|dl|h[1-6])[^>]*?>.*</\1>',
                   re.S).sub('', text.strip()).strip()
    r = re.compile(r'<(hr|br)[^>]*?/>').sub('', r)
    return '' != r

def is_rel_url(url):
    """Identify relative urls."""
    (scheme, netloc) = urlparse(url)[0:2]
    return not scheme and not netloc

def is_valid_url(url):
    parsed = urlparse(url)
    if parsed.scheme == '':
        return True
    return False

def list_type(list_string):
    listtypes = {
        list_string.startswith('*'): 'u',
        list_string.startswith('#'): 'o',
        (not list_string.startswith('*') and not list_string.startswith('#')):
        'd'
    }
    return listtypes.get(True, False)

def normalize_newlines(string):
    out = string.strip()
    out = re.sub(r'\r\n?', '\n', out)
    out = re.compile(r'^[ \t]*\n', flags=re.M).sub('\n', out)
    out = re.sub(r'"$', '" ', out)
    return out

def parse_attributes(block_attributes, element=None, include_id=True, restricted=False):
    vAlign = {'^': 'top', '-': 'middle', '~': 'bottom'}
    hAlign = {'<': 'left', '=': 'center', '>': 'right', '<>': 'justify'}
    style = []
    aclass = ''
    lang = ''
    colspan = ''
    rowspan = ''
    block_id = ''
    span = ''
    width = ''
    result = OrderedDict()

    if not block_attributes:
        return result

    matched = block_attributes
    if element == 'td':
        m = re.search(r'\\(\d+)', matched)
        if m:
            colspan = m.group(1)

        m = re.search(r'/(\d+)', matched)
        if m:
            rowspan = m.group(1)

    if element == 'td' or element == 'tr':
        m = re.search(r'(^{0})'.format(valign_re_s), matched)
        if m:
            style.append("vertical-align:{0}".format(vAlign[m.group(1)]))

    if not restricted:
        m = re.search(r'\{([^}]*)\}', matched)
        if m:
            style.extend(m.group(1).rstrip(';').split(';'))
            matched = matched.replace(m.group(0), '')

    m = re.search(r'\[([^\]]+)\]', matched, re.U)
    if m:
        lang = m.group(1)
        matched = matched.replace(m.group(0), '')

    m = re.search(r'\(([^()]+)\)', matched, re.U)
    if m:
        aclass = m.group(1)
        matched = matched.replace(m.group(0), '')

    m = re.search(r'([(]+)', matched)
    if m:
        style.append("padding-left:{0}em".format(len(m.group(1))))
        matched = matched.replace(m.group(0), '')

    m = re.search(r'([)]+)', matched)
    if m:
        style.append("padding-right:{0}em".format(len(m.group(1))))
        matched = matched.replace(m.group(0), '')

    m = re.search(r'({0})'.format(halign_re_s), matched)
    if m:
        style.append("text-align:{0}".format(hAlign[m.group(1)]))

    m = re.search(r'^(.*)#(.*)$', aclass)
    if m:
        block_id = m.group(2)
        aclass = m.group(1)

    if element == 'col':
        pattern = r'(?:\\(\d+)\.?)?\s*(\d+)?'
        csp = re.match(pattern, matched)
        span, width = csp.groups()

    if colspan:
        result['colspan'] = colspan

    if style:
        # Previous splits that created style may have introduced extra
        # whitespace into the list elements.  Clean it up.
        style = [x.strip() for x in style]
        result['style'] = '{0};'.format("; ".join(style))
    if aclass:
        result['class'] = aclass
    if block_id and include_id:
        result['id'] = block_id
    if lang:
        result['lang'] = lang
    if rowspan:
        result['rowspan'] = rowspan
    if span:
        result['span'] = span
    if width:
        result['width'] = width
    return result

def pba(block_attributes, element=None, include_id=True, restricted=False):
    """Parse block attributes."""
    attrs = parse_attributes(block_attributes, element, include_id, restricted)
    if not attrs:
        return ''
    result = ' '.join(['{0}="{1}"'.format(k, v) for k, v in attrs.items()])
    return ' {0}'.format(result)
������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������python-textile-4.0.1/textile/version.py�������������������������������������������������������������0000664�0000000�0000000�00000000022�13613060030�0020210�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������VERSION = '4.0.1'
������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������