diff options
53 files changed, 719 insertions, 256 deletions
diff --git a/docs/_template.html b/docs/_template.html index 202fefb..d3780dd 100644 --- a/docs/_template.html +++ b/docs/_template.html @@ -62,7 +62,7 @@ <h3>Navigation</h3> <ul> <li class="right" style="margin-right: 10px"> - <a href="siteindex.html" title="General Index">index</a></li> + <a href="%(base)ssiteindex.html" title="General Index">index</a></li> <li class="right"> <a href="%(next_url)s" title="%(next_title)s" accesskey="N">next</a> |</li> diff --git a/docs/basic.css b/docs/basic.css index 2b47622..3f7ad80 100644 --- a/docs/basic.css +++ b/docs/basic.css @@ -246,18 +246,21 @@ div.body p.centered { /* -- tables ---------------------------------------------------------------- */ -table.docutils { +table { border: 0 solid #dce; border-collapse: collapse; } -table.docutils td, table.docutils th { +table td, table th { padding: 2px 5px 2px 5px; - border-left: 0; - background-color: #eef; } -table.docutils td p.last, table.docutils th p.last { +table td { + border: 1px solid #ddd; + background-color: #eef; +} + +table td p.last, table th p.last { margin-bottom: 0; } @@ -269,8 +272,8 @@ table.footnote td, table.footnote th { border: 0 !important; } -table.docutils th { - border-top: 1px solid #cac; +table th { + border: 1px solid #cac; background-color: #ede; } @@ -280,7 +283,7 @@ th { } th.head { - text-align: center; + text-align: center; } /* -- other body styles ----------------------------------------------------- */ @@ -371,6 +374,10 @@ pre { overflow-y: hidden; } +code { + font-size: 1.1em; +} + td.linenos pre { padding: 5px 0px; border: 0; diff --git a/docs/change_log.txt b/docs/change_log.txt index 3265aef..ef559e0 100644 --- a/docs/change_log.txt +++ b/docs/change_log.txt @@ -1,12 +1,18 @@ title: Change Log prev_title: Test Suite prev_url: test_suite.html -next_title: Release Notes for v2.2.0 -next_url: release-2.2.0.html +next_title: Release Notes for v2.3 +next_url: release-2.3.html Python-Markdown Changelog ========================= +__________: Released version 2.3.0 ([Notes](release-2.3.html)) + +Nov 4, 2012: Released version 2.2.1 ([Notes](release-2.2.1.html)). + +Jul 5, 2012: Released version 2.2.0 ([Notes](release-2.2.0.html)). + Jan 22, 2012: Released version 2.1.1 ([Notes](release-2.1.1.html)). Nov 24, 2011: Released version 2.1.0 ([Notes](release-2.1.0.html)). diff --git a/docs/extensions/attr_list.txt b/docs/extensions/attr_list.txt index 11c6a28..4134a82 100644 --- a/docs/extensions/attr_list.txt +++ b/docs/extensions/attr_list.txt @@ -73,7 +73,7 @@ The above results in the following output: To define attributes on inline elements, the attribute list should be defined immediately after the inline element with no whitespace. - [link](http://example.com){: class="foo bar" title="Some title! } + [link](http://example.com){: class="foo bar" title="Some title!" } The above results in the following output: diff --git a/docs/extensions/index.txt b/docs/extensions/index.txt index 610fe21..c9ee005 100644 --- a/docs/extensions/index.txt +++ b/docs/extensions/index.txt @@ -15,12 +15,12 @@ actual source files. To use an extension, pass it's name to markdown with the `extensions` keyword. See the [Library Reference](../reference.html#extensions) for more details. - markdown.markdown(some_text, extensions=['extra', 'nl2br']) + markdown.markdown(some_text, extensions=['footnotes', 'nl2br']) From the command line, specify an extension with the `-x` option. See the [Command Line docs](../cli.html) or use the `--help` option for more details. - python -m markdown -x extra input.txt > output.html + python -m markdown -x footnotes -x tables input.txt > output.html Officially Supported Extensions ------------------------------- @@ -31,24 +31,59 @@ maintained here and all bug reports should be made to the project. If you have a typical install of Python-Markdown, these extensions are already available to you. -* [Extra](extra.html) - * [Abbreviations](abbreviations.html) - * [Attribute Lists](attr_list.html) - * [Definition Lists](definition_lists.html) - * [Fenced Code Blocks](fenced_code_blocks.html) - * [Footnotes](footnotes.html) - * [Tables](tables.html) - * [Smart Strong](smart_strong.html) -* [Admonition](admonition.html) -* [CodeHilite](code_hilite.html) -* [HTML Tidy](html_tidy.html) -* [HeaderId](header_id.html) -* [Meta-Data](meta_data.html) -* [New Line to Break](nl2br.html) -* [RSS](rss.html) -* [Sane Lists](sane_lists.html) -* [Table of Contents](toc.html) -* [WikiLinks](wikilinks.html) +### Markdown Extra + +You can enable **all** these extensions just as if it was a single +`extra` extension. Example: + + markdown.markdown(some_text, extensions=['extra', 'codehilite']) + +Extension | Name in Python-Markdown +--------- | ----------------------- +[Abbreviations][] | `abbr` +[Attribute Lists][] | `attr_list` +[Definition Lists][] | `def_list` +[Fenced Code Blocks][] | `fenced_code` +[Footnotes][] | `footnotes` +[Tables][] | `tables` +[Smart Strong][] | `smart_strong` + +[Abbreviations]: abbreviations.html +[Attribute Lists]: attr_list.html +[Definition Lists]: definition_lists.html +[Fenced Code Blocks]: fenced_code_blocks.html +[Footnotes]: footnotes.html +[Tables]: tables.html +[Smart Strong]: smart_strong.html + +### Other extensions + +There are also some extensions that are not included in Markdown Extra +but come in the standard Python-Markdown library. + +Extension | Name in Python-Markdown +--------- | ----------------------- +[Admonition][] | `admonition` +[CodeHilite][] | `codehilite` +[HTML Tidy][] | `html_tidy` +[HeaderId] | `headerid` +[Meta-Data] | `meta` +[New Line to Break] | `nl2br` +[RSS] | `rss` +[Sane Lists] | `sane_lists` +[Table of Contents] | `toc` +[WikiLinks] | `wikilinks` + +[Admonition]: admonition.html +[CodeHilite]: code_hilite.html +[HTML Tidy]: html_tidy.html +[HeaderId]: header_id.html +[Meta-Data]: meta_data.html +[New Line to Break]: nl2br.html +[RSS]: rss.html +[Sane Lists]: sane_lists.html +[Table of Contents]: toc.html +[WikiLinks]: wikilinks.html Third Party Extensions ---------------------- diff --git a/docs/extensions/wikilinks.txt b/docs/extensions/wikilinks.txt index b5e2b04..5f6d20e 100644 --- a/docs/extensions/wikilinks.txt +++ b/docs/extensions/wikilinks.txt @@ -99,7 +99,7 @@ could also pass in a callable which must accept three arguments (``label``, The option is also provided to change or remove the class attribute. >>> html = markdown.markdown(text, - ... ['wikilink(base_url=myclass)'] + ... ['wikilink(html_class=myclass)'] ... ) Would cause all wikilinks to be assigned to the class `myclass`. diff --git a/docs/index.txt b/docs/index.txt index eb245e6..b44f5ae 100644 --- a/docs/index.txt +++ b/docs/index.txt @@ -9,12 +9,26 @@ Python-Markdown This is a Python implementation of John Gruber's [Markdown](http://daringfireball.net/projects/markdown/). It is almost completely compliant with the reference implementation, -though there are a few very minor differences. See John's +though there are a few very minor [differences](#differences). See John's [Syntax Documentation](http://daringfireball.net/projects/markdown/syntax) for the syntax rules. See the [installation instructions](install.html) to get started. +Goals +----- + +The Python-Markdown project is developed with the following goals in mind: + +* Maintain a Python 2 *and* Python 3 library (with an optional CLI wrapper) + suited to use in web server environments (never raise an exception, never + write to stdout, etc.) as an implementation of the markdown parser that + follows the [syntax rules](http://daringfireball.net/projects/markdown/syntax) + and the behavior of the original (markdown.pl) implementation as reasonably + as possible (see [differences](#differences) for a few exceptions). +* Provide an [Extension API](extensions/api.html) which allows any behaviors of + the parser to be overridden/changed/added. + Features -------- @@ -27,13 +41,6 @@ features: supported by Unicode including bi-directional text. In fact the test suite includes documents written in Russian and Arabic. -* __Middle-Word Emphasis__ - - Python-Markdown defaults to ignoring middle-word emphasis. In other words, - `some_long_filename.txt` will not become `some<em>long</em>filename.txt`. - This can be switched off if desired. See the - [Library Reference](reference.html#smart_emphasis) for details. - * __Extensions__ Various [extensions](extensions/index.html) are provided (including @@ -58,6 +65,50 @@ features: In addition to being a Python Library, a [command line script](cli.html) is available for your convenience. +Differences +----------- + +While Python-Markdown strives to fully implement markdown as described in the +[syntax rules](http://daringfireball.net/projects/markdown/syntax), the rules +can be interpreted in different ways and different implementations +occasionally vary in their behavior (see the +[Babelmark FAQ](http://johnmacfarlane.net/babelmark2/faq.html#what-are-some-examples-of-interesting-divergences-between-implementations) +for some examples). Known and intentional differences found in Python-Markdown +are summarized below: + +* __Middle-Word Emphasis__ + + Python-Markdown defaults to ignoring middle-word emphasis. In other words, + `some_long_filename.txt` will not become `some<em>long</em>filename.txt`. + This can be switched off if desired. See the + [Library Reference](reference.html#smart_emphasis) for details. + +* __Indentation/Tab Length__ + + The [syntax rules](http://daringfireball.net/projects/markdown/syntax#list) + clearly state that when a list item consists of multiple paragraphs, "each + subsequent paragraph in a list item **must** be indented by either 4 spaces + or one tab" (emphasis added). However, many implementations do not enforce + this rule and allow less than 4 spaces of indentation. The implementers of + Python-Markdown consider it a bug to not enforce this rule, and therefore, + subsequent paragraphs of a list **must** be indented by four spaces or one + tab. + + In the event that one would prefer different behavior, + [tab_length](reference.html#tab_length) can be set to whatever length is + desired. Be warned however, as this will affect indentation for all aspects + of the syntax (including code blocks). + +* __Consecutive Lists__ + + While the syntax rules are not clear on this, many implementations (including + the original) do not end one list and start a second list when the list marker + (asterisks, pluses, hyphens, and numbers) changes. For consistency, + Python-Markdown maintains the same behavior with no plans to change in the + foreseeable future. That said, the [Sane List Extension](extensions/sane_lists.html) + is available to provide a less surprising behavior. + + Support ------- diff --git a/docs/reference.txt b/docs/reference.txt index bd837ff..8117e69 100644 --- a/docs/reference.txt +++ b/docs/reference.txt @@ -55,24 +55,60 @@ The following options are available on the `markdown.markdown` function: * __`extensions`__{: #extensions }: A list of extensions. - Python-Markdown provides an API for third parties to write extensions to - the parser adding their own additions or changes to the syntax. A few - commonly used extensions are shipped with the markdown library. See - the [extension documentation](extensions/index.html) for a list of - available extensions. - - The list of extensions may contain instances of extensions or strings of - extension names. If an extension name is provided as a string, the - extension must be importable as a python module either within the - `markdown.extensions` package or on your PYTHONPATH with a name starting - with `mdx_`, followed by the name of the extension. Thus, - `extensions=['extra']` will first look for the module - `markdown.extensions.extra`, then a module named `mdx_extra`. + Python-Markdown provides an [API](extensions/api.html) for third parties to + write extensions to the parser adding their own additions or changes to the + syntax. A few commonly used extensions are shipped with the markdown + library. See the [extension documentation](extensions/index.html) for a + list of available extensions. + + The list of extensions may contain instances of extensions and/or strings + of extension names. + + extensions=[MyExtension(), 'path.to.my.ext', 'extra'] + + When passing in extension instances, each class instance must be a subclass + of `markdown.extensions.Extension` and any configuration options should be + defined when initiating the class instance rather than using the + [extension_configs](#extension_configs) keyword. For example: + + from markdown.extensions import Extension + class MyExtension(Extension): + # define your extension here... + + markdown.markdown(text, extensions=[MyExtension(configs={'option': 'value'})) + + If an extension name is provided as a string, the extension must be + importable as a python module on your PYTHONPATH. Python's dot notation is + supported. Therefore, to import the 'extra' extension, one could do + `extensions=['markdown.extensions.extra']` However, if no dots are provided + in the string (`extensions=['extra']`) Markdown will first look for the + module `markdown.extensions.extra` (the built-in extension), then a module + named `mdx_extra` ('mdx_' will be appended to the beginning of the string) + at the root of your PYTHONPATH. + + When loading an extension by name (as a sting), you may either pass in + configuration settings to the extension using the + [extension_configs](#extension_configs) keyword or by appending the + settings to the name in the following format: + + extensions=['name(option1=value,option2=value)'] + + Note that there are no quotes or whitespace in the above format, which + severely limits how it can be used. For more complex settings, it is + suggested that the [extension_configs](#extension_configs) keyword + be used or an instance of a class be passed in. + + See the documentation of the [Extension API](extensions/api.html) for + assistance in creating extensions. * __`extension_configs`__{: #extension_configs }: A dictionary of configuration settings for extensions. - The dictionary must be of the following format: + Any configuration settings will only be passed to extensions loaded by name + (as a string). When loading extensions as class instances, pass the + configuration settings directly to the class when initializing it. + + The dictionary of configuration settings must be in the following format: extension_configs = {'extension_name_1': [ diff --git a/docs/release-2.2.0.txt b/docs/release-2.2.0.txt index d7081eb..59ac78f 100644 --- a/docs/release-2.2.0.txt +++ b/docs/release-2.2.0.txt @@ -1,6 +1,6 @@ title: Release Notes for v2.2.0
-prev_title: Change Log
-prev_url: change_log.html
+prev_title: Release Notes for v2.2.1
+prev_url: release-2.2.1.html
next_title: Release Notes for v2.1.1
next_url: release-2.1.1.html
diff --git a/docs/release-2.2.1.txt b/docs/release-2.2.1.txt new file mode 100644 index 0000000..fe27c71 --- /dev/null +++ b/docs/release-2.2.1.txt @@ -0,0 +1,12 @@ +title: Release Notes for v2.2.1 +prev_title: Release Notes for v2.3 +prev_url: release-2.3.html +next_title: Release Notes for v2.2.0 +next_url: release-2.2.0.html + +Python-Markdown 2.2.1 Release Notes +=================================== + +Python-Markdown 2.2.1 is a bug-fix release. No new features have been added. +However, at least one bug fix does not work in Python 2.4 so that version of +Python is no longer supported. For a full list of changes, see the git log. diff --git a/docs/release-2.3.txt b/docs/release-2.3.txt new file mode 100644 index 0000000..9c53d12 --- /dev/null +++ b/docs/release-2.3.txt @@ -0,0 +1,37 @@ +title: Release Notes for v2.3 +prev_title: Change Log +prev_url: change_log.html +next_title: Release Notes for v2.2.1 +next_url: release-2.2.1.html + +Python-Markdown 2.3 Release Notes +================================= + +We are pleased to release Python-Markdown 2.3 which ... + +Python-Markdown supports Python versions 2.6, 2.7, 3.1, 3.3, and 3.3. + +Backwards-incompatible Changes +------------------------------ + +* Support has been dropped for Python 2.5. No guarantees are made that the +library will work in any version of Python lower than 2.6. + +* "safe_mode" has been further restricted. Markdown formated links must be +of a known whitelisted scheme when in "safe_mode" or the url is discarded. +The whitelesited schemes are: 'http', 'https', 'ftp', 'ftps', 'mailto', +'news'. Schemeless urls are also permitted, but are checked in other ways - +as they have been for some time. + +* The ids assigned to footnotes now contain a dash (`-`) rather than a colon +(`:`) when `output_format` it set to "html5" or "xhtml5". If you are making +reference to those ids in your JavaScript or CSS and using the HTML5 output, +you will need to update your code accordingly. No changes are necessary if +you are outputing XHTML (the default) or HTML4. + +What's New in Python-Markdown 2.3 +--------------------------------- + +Various bug fixes have been made. See the +[commit log](https://github.com/waylan/Python-Markdown/commits/master) +for a complete history of the changes. @@ -10,7 +10,7 @@ from sys import platform def _get_versions(): """ Find and comfirm all supported versions of Python. """ vs = [] - for v in ['2.4', '2.5', '2.6', '2.7', '3.1', '3.2']: + for v in ['2.5', '2.6', '2.7', '3.1', '3.2']: with settings( hide('warnings', 'running', 'stdout', 'stderr'), warn_only=True @@ -58,8 +58,7 @@ def build_tests(version=_pyversion[:3]): if version.startswith('3'): # Do 2to3 conversion local('2to3-%s -w -d build/test.%s/markdown' % (version, version)) - local('2to3-%s -w build/test.%s/tests' % (version, version)) - local('2to3-%s -w build/test.%s/run-tests.py' % (version, version)) + def generate_test(file): """ Generate a given test. """ @@ -95,7 +94,7 @@ def build_envs(): def build_release(): """ Build a package for distribution. """ - ans = prompt('Have you updated the version in both setup.py and __init__.py?', default='Y') + ans = prompt('Have you updated the version_info in __version__.py?', default='Y') if ans.lower() == 'y': local('./setup.py sdist --formats zip,gztar') if platform == 'win32': @@ -108,65 +107,3 @@ def deploy_release(): build_release() local('./setup.py register') local('./setup.py upload') - upload_to_github() - -def upload_to_github(): - """ Upload release to Github. """ - # We need github API v3 but no python lib exists yet. So do it manually. - import os - import urllib2 - import base64 - import simplejson - import getpass - # Setup Auth - url = 'https://api.github.com/repos/waylan/Python-Markdown/downloads' - user = prompt('Github username:', default=getpass.getuser()) - password = prompt('Github password:') - authstring = base64.encodestring('%s:%s' % (user, password)) - # Loop through files and upload - base = 'dist/' - for file in os.listdir(base): - file = os.path.join(base, file) - if os.path.isfile(file): - ans = prompt('Upload: %s' % file, default='Y') - if ans.lower() == 'y': - # Create document entry on github - desc = prompt('Description for %s:' % file) - data1 = simplejson.dumps({ - 'name': os.path.basename(file), - 'size': os.path.getsize(file), - 'description' : desc, - #'content_type': 'text/plain' # <- let github determine - }) - req = urllib2.Request(url, data1, - {'Content-type': 'application/json'}) - req.add_header('Authorization', 'Basic %s' % authstring) - try: - response = urllib2.urlopen(req) - except urllib2.HTTPError, e: - error = simplejson.loads(e.read()) - if error['errors'][0]['code'] == 'already_exists': - print 'Already_exists, skipping...' - continue - else: - print e.read() - raise - data2 = simplejson.loads(response.read()) - response.close() - # Upload document (using curl because it is easier) - data2['file'] = file - curl = """curl \\ - -F "key=%(path)s" \\ - -F "acl=%(acl)s" \\ - -F "success_action_status=201" \\ - -F "Filename=%(name)s" \\ - -F "AWSAccessKeyId=%(accesskeyid)s" \\ - -F "Policy=%(policy)s" \\ - -F "Signature=%(signature)s" \\ - -F "Content-Type=%(mime_type)s" \\ - -F "file=@%(file)s" \\ - %(s3_url)s""" % data2 - print 'Uploading...' - local(curl) - else: - print 'Skipping...' diff --git a/markdown/__init__.py b/markdown/__init__.py index 81404e0..aceaf60 100644 --- a/markdown/__init__.py +++ b/markdown/__init__.py @@ -30,14 +30,11 @@ Copyright 2004 Manfred Stienstra (the original version) License: BSD (see LICENSE for details). """ -version = "2.2.0" -version_info = (2,2,0, "final") - +from __version__ import version, version_info import re import codecs import sys import logging -import warnings import util from preprocessors import build_preprocessors from blockprocessors import build_block_parser @@ -135,9 +132,9 @@ class Markdown: self.references = {} self.htmlStash = util.HtmlStash() + self.set_output_format(kwargs.get('output_format', 'xhtml1')) self.registerExtensions(extensions=kwargs.get('extensions', []), configs=kwargs.get('extension_configs', {})) - self.set_output_format(kwargs.get('output_format', 'xhtml1')) self.reset() def build_parser(self): @@ -284,11 +281,6 @@ class Markdown: e.reason += '. -- Note: Markdown only accepts unicode input!' raise - source = source.replace(util.STX, "").replace(util.ETX, "") - source = source.replace("\r\n", "\n").replace("\r", "\n") + "\n\n" - source = re.sub(r'\n\s+\n', '\n\n', source) - source = source.expandtabs(self.tab_length) - # Split into lines and run the line preprocessors. self.lines = source.split("\n") for prep in self.preprocessors.values(): @@ -379,15 +371,14 @@ class Markdown: output_file.write(html) # Don't close here. User may want to write more. else: - if sys.stdout.encoding: - # If we are in Python 3 or if we are not piping output: + # Encode manually and write bytes to stdout. + html = html.encode(encoding, "xmlcharrefreplace") + try: + # Write bytes directly to buffer (Python 3). + sys.stdout.buffer.write(html) + except AttributeError: + # Probably Python 2, which works with bytes by default. sys.stdout.write(html) - else: - # In python 2.x if you pipe output on command line, - # sys.stdout.encoding is None. So lets set it: - writer = codecs.getwriter(encoding) - stdout = writer(sys.stdout, errors="xmlcharrefreplace") - stdout.write(html) return self diff --git a/markdown/__version__.py b/markdown/__version__.py new file mode 100644 index 0000000..bbe1b3f --- /dev/null +++ b/markdown/__version__.py @@ -0,0 +1,28 @@ +# +# markdown/__version__.py +# +# version_info should conform to PEP 386 +# (major, minor, micro, alpha/beta/rc/final, #) +# (1, 1, 2, 'alpha', 0) => "1.1.2.dev" +# (1, 2, 0, 'beta', 2) => "1.2b2" +version_info = (2, 3, 0, 'alpha', 0) + +def _get_version(): + " Returns a PEP 386-compliant version number from version_info. " + assert len(version_info) == 5 + assert version_info[3] in ('alpha', 'beta', 'rc', 'final') + + parts = 2 if version_info[2] == 0 else 3 + main = '.'.join(map(str, version_info[:parts])) + + sub = '' + if version_info[3] == 'alpha' and version_info[4] == 0: + # TODO: maybe append some sort of git info here?? + sub = '.dev' + elif version_info[3] != 'final': + mapping = {'alpha': 'a', 'beta': 'b', 'rc': 'c'} + sub = mapping[version_info[3]] + str(version_info[4]) + + return str(main + sub) + +version = _get_version() diff --git a/markdown/blockprocessors.py b/markdown/blockprocessors.py index 7b14a85..b41df6a 100644 --- a/markdown/blockprocessors.py +++ b/markdown/blockprocessors.py @@ -485,7 +485,7 @@ class HRProcessor(BlockProcessor): # Recursively parse lines before hr so they get parsed first. self.parser.parseBlocks(parent, [prelines]) # create hr - hr = util.etree.SubElement(parent, 'hr') + util.etree.SubElement(parent, 'hr') # check for lines in block after hr. postlines = block[self.match.end():].lstrip('\n') if postlines: @@ -499,7 +499,7 @@ class EmptyBlockProcessor(BlockProcessor): # Detect a block that only contains whitespace # or only whitespace on the first line. - RE = re.compile(r'^\s*\n') + RE = re.compile(r'^ *(\n|$)') def test(self, parent, block): return bool(self.RE.match(block)) @@ -508,13 +508,14 @@ class EmptyBlockProcessor(BlockProcessor): block = blocks.pop(0) m = self.RE.match(block) if m: - # Add remaining line to master blocks for later. - blocks.insert(0, block[m.end():]) + theRest = block[m.end():] + if theRest: + # Add remaining lines to master blocks for later. + blocks.insert(0, theRest) sibling = self.lastChild(parent) - if sibling and sibling.tag == 'pre' and sibling[0] and \ - sibling[0].tag == 'code': + if sibling and sibling.tag == 'pre' and len(sibling) and sibling[0].tag == 'code': # Last block is a codeblock. Append to preserve whitespace. - sibling[0].text = util.AtomicString('%s/n/n/n' % sibling[0].text ) + sibling[0].text = util.AtomicString('%s\n' % sibling[0].text ) class ParagraphProcessor(BlockProcessor): diff --git a/markdown/extensions/attr_list.py b/markdown/extensions/attr_list.py index 0aa18e0..3a79d85 100644 --- a/markdown/extensions/attr_list.py +++ b/markdown/extensions/attr_list.py @@ -67,10 +67,13 @@ class AttrListTreeprocessor(markdown.treeprocessors.Treeprocessor): HEADER_RE = re.compile(r'[ ]*%s[ ]*$' % BASE_RE) BLOCK_RE = re.compile(r'\n[ ]*%s[ ]*$' % BASE_RE) INLINE_RE = re.compile(r'^%s' % BASE_RE) + NAME_RE = re.compile(r'[^A-Z_a-z\u00c0-\u00d6\u00d8-\u00f6\u00f8-\u02ff\u0370-\u037d' + r'\u037f-\u1fff\u200c-\u200d\u2070-\u218f\u2c00-\u2fef' + r'\u3001-\ud7ff\uf900-\ufdcf\ufdf0-\ufffd\u10000-\ueffff' + r'\-\.0-9\u00b7\u0300-\u036f\u203f-\u2040]+') def run(self, doc): for elem in doc.getiterator(): - #import pdb; pdb.set_trace() if isBlockLevel(elem.tag): # Block level: check for attrs on last line of text RE = self.BLOCK_RE @@ -114,18 +117,20 @@ class AttrListTreeprocessor(markdown.treeprocessors.Treeprocessor): else: elem.set('class', v) else: - # assing attr k with v - elem.set(k, v) + # assign attr k with v + elem.set(self.sanitize_name(k), v) + + def sanitize_name(self, name): + """ + Sanitize name as 'an XML Name, minus the ":"'. + See http://www.w3.org/TR/REC-xml-names/#NT-NCName + """ + return self.NAME_RE.sub('_', name) class AttrListExtension(markdown.extensions.Extension): def extendMarkdown(self, md, md_globals): - if 'headerid' in md.treeprocessors.keys(): - # insert after 'headerid' treeprocessor - md.treeprocessors.add('attr_list', AttrListTreeprocessor(md), '>headerid') - else: - # insert after 'inline' treeprocessor - md.treeprocessors.add('attr_list', AttrListTreeprocessor(md), '>inline') + md.treeprocessors.add('attr_list', AttrListTreeprocessor(md), '>prettify') def makeExtension(configs={}): diff --git a/markdown/extensions/def_list.py b/markdown/extensions/def_list.py index da1726a..382445c 100644 --- a/markdown/extensions/def_list.py +++ b/markdown/extensions/def_list.py @@ -34,10 +34,11 @@ class DefListProcessor(markdown.blockprocessors.BlockProcessor): return bool(self.RE.search(block)) def run(self, parent, blocks): - block = blocks.pop(0) - m = self.RE.search(block) - terms = [l.strip() for l in block[:m.start()].split('\n') if l.strip()] - block = block[m.end():] + + raw_block = blocks.pop(0) + m = self.RE.search(raw_block) + terms = [l.strip() for l in raw_block[:m.start()].split('\n') if l.strip()] + block = raw_block[m.end():] no_indent = self.NO_INDENT_RE.match(block) if no_indent: d, theRest = (block, None) @@ -48,6 +49,11 @@ class DefListProcessor(markdown.blockprocessors.BlockProcessor): else: d = m.group(2) sibling = self.lastChild(parent) + if not terms and sibling is None: + # This is not a definition item. Most likely a paragraph that + # starts with a colon at the begining of a document or list. + blocks.insert(0, raw_block) + return False if not terms and sibling.tag == 'p': # The previous paragraph contains the terms state = 'looselist' diff --git a/markdown/extensions/extra.py b/markdown/extensions/extra.py index 2c7915e..ba646f5 100644 --- a/markdown/extensions/extra.py +++ b/markdown/extensions/extra.py @@ -45,8 +45,9 @@ class ExtraExtension(markdown.Extension): def extendMarkdown(self, md, md_globals): """ Register extension instances. """ md.registerExtensions(extensions, self.config) - # Turn on processing of markdown text within raw html - md.preprocessors['html_block'].markdown_in_raw = True + if not md.safeMode: + # Turn on processing of markdown text within raw html + md.preprocessors['html_block'].markdown_in_raw = True def makeExtension(configs={}): return ExtraExtension(configs=dict(configs)) diff --git a/markdown/extensions/fenced_code.py b/markdown/extensions/fenced_code.py index 9a1284f..76d644f 100755..100644 --- a/markdown/extensions/fenced_code.py +++ b/markdown/extensions/fenced_code.py @@ -95,7 +95,7 @@ class FencedCodeExtension(markdown.Extension): md.preprocessors.add('fenced_code_block', FencedBlockPreprocessor(md), - "_begin") + ">normalize_whitespace") class FencedBlockPreprocessor(markdown.preprocessors.Preprocessor): diff --git a/markdown/extensions/footnotes.py b/markdown/extensions/footnotes.py index cfe41ed..0a0ddea 100644 --- a/markdown/extensions/footnotes.py +++ b/markdown/extensions/footnotes.py @@ -62,6 +62,9 @@ class FootnoteExtension(markdown.Extension): md.registerExtension(self) self.parser = md.parser self.md = md + self.sep = ':' + if self.md.output_format in ['html5', 'xhtml5']: + self.sep = '-' # Insert a preprocessor before ReferencePreprocessor md.preprocessors.add("footnote", FootnotePreprocessor(self), "<reference") @@ -106,16 +109,16 @@ class FootnoteExtension(markdown.Extension): def makeFootnoteId(self, id): """ Return footnote link id. """ if self.getConfig("UNIQUE_IDS"): - return 'fn:%d-%s' % (self.unique_prefix, id) + return 'fn%s%d-%s' % (self.sep, self.unique_prefix, id) else: - return 'fn:%s' % id + return 'fn%s%s' % (self.sep, id) def makeFootnoteRefId(self, id): """ Return footnote back-link id. """ if self.getConfig("UNIQUE_IDS"): - return 'fnref:%d-%s' % (self.unique_prefix, id) + return 'fnref%s%d-%s' % (self.sep, self.unique_prefix, id) else: - return 'fnref:%s' % id + return 'fnref%s%s' % (self.sep, id) def makeFootnotesDiv(self, root): """ Return div of footnotes as et Element. """ @@ -125,7 +128,7 @@ class FootnoteExtension(markdown.Extension): div = etree.Element("div") div.set('class', 'footnote') - hr = etree.SubElement(div, "hr") + etree.SubElement(div, "hr") ol = etree.SubElement(div, "ol") for id in self.footnotes.keys(): @@ -171,7 +174,6 @@ class FootnotePreprocessor(markdown.preprocessors.Preprocessor): """ newlines = [] i = 0 - #import pdb; pdb.set_trace() #for i, line in enumerate(lines): while True: m = DEF_RE.match(lines[i]) if m: diff --git a/markdown/extensions/headerid.py b/markdown/extensions/headerid.py index e86ab15..b6a12e8 100644 --- a/markdown/extensions/headerid.py +++ b/markdown/extensions/headerid.py @@ -77,9 +77,7 @@ Dependencies: """ import markdown -from markdown.util import etree import re -from string import ascii_lowercase, digits, punctuation import logging import unicodedata @@ -135,7 +133,7 @@ class HeaderIdTreeprocessor(markdown.treeprocessors.Treeprocessor): if elem.tag in ['h1', 'h2', 'h3', 'h4', 'h5', 'h6']: if force_id: if "id" in elem.attrib: - id = elem.id + id = elem.get('id') else: id = slugify(u''.join(itertext(elem)), sep) elem.set('id', unique(id, self.IDs)) @@ -185,8 +183,12 @@ class HeaderIdExtension (markdown.Extension): self.processor = HeaderIdTreeprocessor() self.processor.md = md self.processor.config = self.getConfigs() - # Replace existing hasheader in place. - md.treeprocessors.add('headerid', self.processor, '>inline') + if 'attr_list' in md.treeprocessors.keys(): + # insert after attr_list treeprocessor + md.treeprocessors.add('headerid', self.processor, '>attr_list') + else: + # insert after 'prettify' treeprocessor. + md.treeprocessors.add('headerid', self.processor, '>prettify') def reset(self): self.processor.IDs = [] diff --git a/markdown/extensions/smart_strong.py b/markdown/extensions/smart_strong.py index 3ed3560..7166989 100644 --- a/markdown/extensions/smart_strong.py +++ b/markdown/extensions/smart_strong.py @@ -22,7 +22,6 @@ Copyright 2011 ''' -import re import markdown from markdown.inlinepatterns import SimpleTagPattern diff --git a/markdown/inlinepatterns.py b/markdown/inlinepatterns.py index d3ef4e0..f64aa58 100644 --- a/markdown/inlinepatterns.py +++ b/markdown/inlinepatterns.py @@ -45,7 +45,6 @@ import util import odict import re from urlparse import urlparse, urlunparse -import sys # If you see an ImportError for htmlentitydefs after using 2to3 to convert for # use by Python3, then you are probably using the buggy version from Python 3.0. # We recomend using the tool from Python 3.1 even if you will be running the @@ -69,7 +68,6 @@ def build_inlinepatterns(md_instance, **kwargs): ReferencePattern(SHORT_REF_RE, md_instance) inlinePatterns["autolink"] = AutolinkPattern(AUTOLINK_RE, md_instance) inlinePatterns["automail"] = AutomailPattern(AUTOMAIL_RE, md_instance) - inlinePatterns["linebreak2"] = SubstituteTagPattern(LINE_BREAK_2_RE, 'br') inlinePatterns["linebreak"] = SubstituteTagPattern(LINE_BREAK_RE, 'br') if md_instance.safeMode != 'escape': inlinePatterns["html"] = HtmlPattern(HTML_RE, md_instance) @@ -119,7 +117,6 @@ AUTOMAIL_RE = r'<([^> \!]*@[^> ]*)>' # <me@example.com> HTML_RE = r'(\<([a-zA-Z/][^\>]*?|\!--.*?--)\>)' # <...> ENTITY_RE = r'(&[\#a-zA-Z0-9]*;)' # & LINE_BREAK_RE = r' \n' # two spaces at end of line -LINE_BREAK_2_RE = r' $' # two spaces at end of text def dequote(string): @@ -191,10 +188,27 @@ class Pattern: stash = self.markdown.treeprocessors['inline'].stashed_nodes except KeyError: return text + def itertext(el): + ' Reimplement Element.itertext for older python versions ' + tag = el.tag + if not isinstance(tag, basestring) and tag is not None: + return + if el.text: + yield el.text + for e in el: + for s in itertext(e): + yield s + if e.tail: + yield e.tail def get_stash(m): id = m.group(1) if id in stash: - return stash.get(id) + value = stash.get(id) + if isinstance(value, basestring): + return value + else: + # An etree Element - return text content only + return ''.join(itertext(value)) return util.INLINE_PLACEHOLDER_RE.sub(get_stash, text) @@ -328,6 +342,7 @@ class LinkPattern(Pattern): `username:password@host:port`. """ + url = url.replace(' ', '%20') if not self.markdown.safeMode: # Return immediately bipassing parsing. return url @@ -339,14 +354,18 @@ class LinkPattern(Pattern): return '' locless_schemes = ['', 'mailto', 'news'] + allowed_schemes = locless_schemes + ['http', 'https', 'ftp', 'ftps'] + if scheme not in allowed_schemes: + # Not a known (allowed) scheme. Not safe. + return '' + if netloc == '' and scheme not in locless_schemes: - # This fails regardless of anything else. - # Return immediately to save additional proccessing + # This should not happen. Treat as suspect. return '' for part in url[2:]: if ":" in part: - # Not a safe url + # A colon in "path", "parameters", "query" or "fragment" is suspect. return '' # Url passes all tests. Return url as-is. @@ -372,7 +391,7 @@ class ImagePattern(LinkPattern): else: truealt = m.group(2) - el.set('alt', truealt) + el.set('alt', self.unescape(truealt)) return el class ReferencePattern(LinkPattern): @@ -417,7 +436,11 @@ class ImageReferencePattern(ReferencePattern): el.set("src", self.sanitize_url(href)) if title: el.set("title", title) - el.set("alt", text) + + if self.markdown.enable_attributes: + text = handleAttributes(text, el) + + el.set("alt", self.unescape(text)) return el diff --git a/markdown/odict.py b/markdown/odict.py index d77d701..02864bf 100644 --- a/markdown/odict.py +++ b/markdown/odict.py @@ -119,7 +119,7 @@ class OrderedDict(dict): """ Return the index of a given key. """ try: return self.keyOrder.index(key) - except ValueError, e: + except ValueError: raise ValueError("Element '%s' was not found in OrderedDict" % key) def index_for_location(self, location): diff --git a/markdown/preprocessors.py b/markdown/preprocessors.py index e7743fb..3751264 100644 --- a/markdown/preprocessors.py +++ b/markdown/preprocessors.py @@ -14,6 +14,7 @@ import odict def build_preprocessors(md_instance, **kwargs): """ Build the default set of preprocessors used by Markdown. """ preprocessors = odict.OrderedDict() + preprocessors['normalize_whitespace'] = NormalizeWhitespace(md_instance) if md_instance.safeMode != 'escape': preprocessors["html_block"] = HtmlBlockPreprocessor(md_instance) preprocessors["reference"] = ReferencePreprocessor(md_instance) @@ -41,6 +42,18 @@ class Preprocessor(util.Processor): pass +class NormalizeWhitespace(Preprocessor): + """ Normalize whitespace for consistant parsing. """ + + def run(self, lines): + source = '\n'.join(lines) + source = source.replace(util.STX, "").replace(util.ETX, "") + source = source.replace("\r\n", "\n").replace("\r", "\n") + "\n\n" + source = source.expandtabs(self.markdown.tab_length) + source = re.sub(r'\n +\n', '\n\n', source) + return source.split('\n') + + class HtmlBlockPreprocessor(Preprocessor): """Remove html blocks from the text and store them for later retrieval.""" @@ -127,7 +140,7 @@ class HtmlBlockPreprocessor(Preprocessor): def run(self, lines): text = "\n".join(lines) new_blocks = [] - text = text.split("\n\n") + text = text.rsplit("\n\n") items = [] left_tag = '' right_tag = '' diff --git a/markdown/treeprocessors.py b/markdown/treeprocessors.py index 841fe0a..b5eedbd 100644 --- a/markdown/treeprocessors.py +++ b/markdown/treeprocessors.py @@ -1,4 +1,3 @@ -import re import inlinepatterns import util import odict @@ -358,3 +357,8 @@ class PrettifyTreeprocessor(Treeprocessor): br.tail = '\n' else: br.tail = '\n%s' % br.tail + # Clean up extra empty lines at end of code blocks. + pres = root.getiterator('pre') + for pre in pres: + if len(pre) and pre[0].tag == 'code': + pre[0].text = pre[0].text.rstrip() + '\n' diff --git a/markdown/util.py b/markdown/util.py index 13cbff2..12dcbd5 100644 --- a/markdown/util.py +++ b/markdown/util.py @@ -1,7 +1,5 @@ # -*- coding: utf-8 -*- import re -from logging import CRITICAL - import etree_loader @@ -20,7 +18,7 @@ BLOCK_LEVEL_ELEMENTS = re.compile("^(p|div|h[1-6]|blockquote|pre|table|dl|ol|ul" "|hr|hr/|style|li|dt|dd|thead|tbody" "|tr|th|td|section|footer|header|group|figure" "|figcaption|aside|article|canvas|output" - "|progress|video)$") + "|progress|video)$", re.IGNORECASE) # Placeholders STX = u'\u0002' # Use STX ("Start of text") for start-of-placeholder ETX = u'\u0003' # Use ETX ("End of text") for end-of-placeholder @@ -8,6 +8,7 @@ from distutils.command.build import build from distutils.core import Command from distutils.util import change_root, newer import codecs +import imp # Try to run 2to3 automaticaly when building in Python 3.x try: @@ -17,7 +18,29 @@ except ImportError: raise ImportError("build_py_2to3 is required to build in Python 3.x.") from distutils.command.build_py import build_py -version = '2.2.0' +def get_version(): + " Get version & version_info without importing markdown.__init__ " + path = os.path.join(os.path.dirname(__file__), 'markdown') + fp, pathname, desc = imp.find_module('__version__', [path]) + try: + v = imp.load_module('__version__', fp, pathname, desc) + return v.version, v.version_info + finally: + fp.close() + +version, version_info = get_version() + +# Get development Status for classifiers +dev_status_map = { + 'alpha': '3 - Alpha', + 'beta' : '4 - Beta', + 'rc' : '4 - Beta', + 'final': '5 - Production/Stable' +} +if version_info[3] == 'alpha' and version_info[4] == 0: + DEVSTATUS = '2 - Pre-Alpha' +else: + DEVSTATUS = dev_status_map[version_info[3]] # The command line script name. Currently set to "markdown_py" so as not to # conflict with the perl implimentation (which uses "markdown"). We can't use @@ -160,18 +183,50 @@ class build_docs(Command): class md_build(build): """ Run "build_docs" command from "build" command. """ + + user_options = build.user_options + [ + ('no-build-docs', None, 'do not build documentation'), + ] + + boolean_options = build.boolean_options + ['build-docs'] + + def initialize_options(self): + build.initialize_options(self) + self.no_build_docs = False + def has_docs(self): - return True + return not self.no_build_docs sub_commands = build.sub_commands + [('build_docs', has_docs)] +long_description = \ +'''This is a Python implementation of John Gruber's Markdown_. +It is almost completely compliant with the reference implementation, +though there are a few known issues. See Features_ for information +on what exactly is supported and what is not. Additional features are +supported by the `Available Extensions`_. -data = dict( +.. _Markdown: http://daringfireball.net/projects/markdown/ +.. _Features: http://packages.python.org/Markdown/index.html#Features +.. _`Available Extensions`: http://packages.python.org/Markdown/extensions/index.html + +Support +======= + +You may ask for help and discuss various other issues on the +`mailing list`_ and report bugs on the `bug tracker`_. + +.. _`mailing list`: http://lists.sourceforge.net/lists/listinfo/python-markdown-discuss +.. _`bug tracker`: http://github.com/waylan/Python-Markdown/issues +''' + +setup( name = 'Markdown', version = version, url = 'http://packages.python.org/Markdown/', download_url = 'http://pypi.python.org/packages/source/M/Markdown/Markdown-%s.tar.gz' % version, description = 'Python implementation of Markdown.', + long_description = long_description, author = 'Manfred Stienstra, Yuri takhteyev and Waylan limberg', author_email = 'markdown [at] freewisdom.org', maintainer = 'Waylan Limberg', @@ -183,12 +238,11 @@ data = dict( 'build_py': build_py, 'build_docs': build_docs, 'build': md_build}, - classifiers = ['Development Status :: 5 - Production/Stable', + classifiers = ['Development Status :: %s' % DEVSTATUS, 'License :: OSI Approved :: BSD License', 'Operating System :: OS Independent', 'Programming Language :: Python', 'Programming Language :: Python :: 2', - 'Programming Language :: Python :: 2.4', 'Programming Language :: Python :: 2.5', 'Programming Language :: Python :: 2.6', 'Programming Language :: Python :: 2.7', @@ -204,9 +258,3 @@ data = dict( 'Topic :: Text Processing :: Markup :: HTML', ], ) - -if sys.version[:3] < '2.5': - data['install_requires'] = ['elementtree'] - -setup(**data) - diff --git a/tests/__init__.py b/tests/__init__.py index c790b09..bb56bd4 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -1,4 +1,4 @@ -from __future__ import with_statement + import os import markdown import codecs @@ -6,11 +6,11 @@ import difflib try: import nose except ImportError: - raise ImportError, "The nose testing framework is required to run " \ + raise ImportError("The nose testing framework is required to run " \ "Python-Markdown tests. Run `easy_install nose` " \ - "to install the latest version." -import util -from plugins import HtmlOutput, Markdown + "to install the latest version.") +from . import util +from .plugins import HtmlOutput, Markdown try: import tidy except ImportError: @@ -84,21 +84,22 @@ class CheckSyntax(object): """ Compare expected output to actual output and report result. """ cfg_section = get_section(file, config) if config.get(cfg_section, 'skip'): - raise nose.plugins.skip.SkipTest, 'Test skipped per config.' + raise nose.plugins.skip.SkipTest('Test skipped per config.') input_file = file + config.get(cfg_section, 'input_ext') with codecs.open(input_file, encoding="utf-8") as f: input = f.read() output_file = file + config.get(cfg_section, 'output_ext') with codecs.open(output_file, encoding="utf-8") as f: - expected_output = f.read() + # Normalize line endings (on windows, git may have altered line endings). + expected_output = f.read().replace("\r\n", "\n") output = markdown.markdown(input, **get_args(file, config)) if tidy and config.get(cfg_section, 'normalize'): - # Normalize whitespace before comparing. + # Normalize whitespace with Tidy before comparing. expected_output = normalize(expected_output) output = normalize(output) elif config.get(cfg_section, 'normalize'): # Tidy is not available. Skip this test. - raise nose.plugins.skip.SkipTest, 'Test skipped. Tidy not available in system.' + raise nose.plugins.skip.SkipTest('Test skipped. Tidy not available in system.') diff = [l for l in difflib.unified_diff(expected_output.splitlines(True), output.splitlines(True), output_file, @@ -124,17 +125,17 @@ def generate(file, config): """ Write expected output file for given input. """ cfg_section = get_section(file, config) if config.get(cfg_section, 'skip'): - print 'Skipping:', file + print('Skipping:', file) return None input_file = file + config.get(cfg_section, 'input_ext') output_file = file + config.get(cfg_section, 'output_ext') if not os.path.isfile(output_file) or \ os.path.getmtime(output_file) < os.path.getmtime(input_file): - print 'Generating:', file + print('Generating:', file) markdown.markdownFromFile(input=input_file, output=output_file, encoding='utf-8', **get_args(file, config)) else: - print 'Already up-to-date:', file + print('Already up-to-date:', file) def generate_all(): """ Generate expected output for all outdated tests. """ diff --git a/tests/basic/angle-links-and-img.html b/tests/basic/angle-links-and-img.html index 1ca3b0b..255c299 100644 --- a/tests/basic/angle-links-and-img.html +++ b/tests/basic/angle-links-and-img.html @@ -1,4 +1,4 @@ -<p><a href="simple link" title="title">link</a> +<p><a href="simple%20link" title="title">link</a> <img alt="image" src="http://example.com/image.jpg" /> <a href="http://example.com/(()((())923)(">link</a> <img alt="image" src="link(()))(" /></p>
\ No newline at end of file diff --git a/tests/extensions/attr_list.html b/tests/extensions/attr_list.html index 1e9c182..f50cd6a 100644 --- a/tests/extensions/attr_list.html +++ b/tests/extensions/attr_list.html @@ -14,4 +14,5 @@ And a <strong class="nest">nested <a class="linky2" href="http://example.com" ti {: #someid .someclass } </code></pre> <h3 id="hash3">No colon for compatability with Headerid ext</h3> -<p id="the_end">Also a codespan: <code class="foo">{: .someclass}</code>.</p>
\ No newline at end of file +<p id="the_end">Also a codespan: <code class="foo">{: .someclass}</code>.</p> +<h3 _:="{:" id="hash5">Bad Syntax</h3>
\ No newline at end of file diff --git a/tests/extensions/attr_list.txt b/tests/extensions/attr_list.txt index d7ed274..cd7f398 100644 --- a/tests/extensions/attr_list.txt +++ b/tests/extensions/attr_list.txt @@ -33,3 +33,4 @@ Now test overrides Also a codespan: `{: .someclass}`{: .foo}. {: #the_end} +### Bad Syntax { {: #hash5 } diff --git a/tests/extensions/extra/def-in-list.html b/tests/extensions/extra/def-in-list.html new file mode 100644 index 0000000..21cddaa --- /dev/null +++ b/tests/extensions/extra/def-in-list.html @@ -0,0 +1,25 @@ +<p>: a paragraph that starts with a colon</p> +<ul> +<li>A List item</li> +<li> +<dl> +<dt>A def term</dt> +<dd>A def item</dd> +<dd>a second</dd> +</dl> +</li> +<li> +<dl> +<dt>Another def term</dt> +<dd> +<p>a loose item</p> +</dd> +<dd> +<p>a second</p> +</dd> +</dl> +</li> +<li> +<p>: a list item that starts with a colon</p> +</li> +</ul>
\ No newline at end of file diff --git a/tests/extensions/extra/def-in-list.txt b/tests/extensions/extra/def-in-list.txt new file mode 100644 index 0000000..7a292ab --- /dev/null +++ b/tests/extensions/extra/def-in-list.txt @@ -0,0 +1,15 @@ +: a paragraph that starts with a colon + +* A List item +* + A def term + : A def item + : a second + +* Another def term + + : a loose item + + : a second + +* : a list item that starts with a colon diff --git a/tests/extensions/nl2br_w_attr_list.html b/tests/extensions/nl2br_w_attr_list.html new file mode 100644 index 0000000..e5e7eb2 --- /dev/null +++ b/tests/extensions/nl2br_w_attr_list.html @@ -0,0 +1 @@ +<p id="bar">Foo<br /></p>
\ No newline at end of file diff --git a/tests/extensions/nl2br_w_attr_list.txt b/tests/extensions/nl2br_w_attr_list.txt new file mode 100644 index 0000000..4b520b5 --- /dev/null +++ b/tests/extensions/nl2br_w_attr_list.txt @@ -0,0 +1,2 @@ +Foo +{: #bar}
\ No newline at end of file diff --git a/tests/extensions/test.cfg b/tests/extensions/test.cfg index e83aa62..4efd837 100644 --- a/tests/extensions/test.cfg +++ b/tests/extensions/test.cfg @@ -30,5 +30,8 @@ extensions=fenced_code [sane_lists] extensions=sane_lists +[nl2br_w_attr_list] +extensions=nl2br,attr_list + [admonition] extensions=admonition diff --git a/tests/misc/attributes-image-ref.html b/tests/misc/attributes-image-ref.html new file mode 100644 index 0000000..6974420 --- /dev/null +++ b/tests/misc/attributes-image-ref.html @@ -0,0 +1 @@ +<p><img alt="img" id="foo" src="http://example.com/i.jpg" /></p>
\ No newline at end of file diff --git a/tests/misc/attributes-image-ref.txt b/tests/misc/attributes-image-ref.txt new file mode 100644 index 0000000..a216971 --- /dev/null +++ b/tests/misc/attributes-image-ref.txt @@ -0,0 +1,4 @@ +![img{@id=foo}][img] + + [img]: http://example.com/i.jpg + diff --git a/tests/misc/blank_lines_in_codeblocks.html b/tests/misc/blank_lines_in_codeblocks.html new file mode 100644 index 0000000..57a4c36 --- /dev/null +++ b/tests/misc/blank_lines_in_codeblocks.html @@ -0,0 +1,61 @@ +<p>Preserve blank lines in code blocks with tabs:</p> +<pre><code>a code block + +two tabbed lines + + +three tabbed lines + + + +four tabbed lines + + + + +five tabbed lines + + + + + +six tabbed lines + + + + + + +End of tabbed block +</code></pre> +<p>And without tabs:</p> +<pre><code>a code block + +two blank lines + + +three blank lines + + + +four blank lines + + + + +five blank lines + + + + + +six blank lines + + + + + + +End of block +</code></pre> +<p>End of document</p>
\ No newline at end of file diff --git a/tests/misc/blank_lines_in_codeblocks.txt b/tests/misc/blank_lines_in_codeblocks.txt new file mode 100644 index 0000000..e7ae102 --- /dev/null +++ b/tests/misc/blank_lines_in_codeblocks.txt @@ -0,0 +1,73 @@ +Preserve blank lines in code blocks with tabs: + + a code block + + two tabbed lines + + + three tabbed lines + + + + four tabbed lines + + + + + five tabbed lines + + + + + + six tabbed lines + + + + + + + End of tabbed block + + + + + + +And without tabs: + + a code block + + two blank lines + + + three blank lines + + + + four blank lines + + + + + five blank lines + + + + + + six blank lines + + + + + + + End of block + + + + + + +End of document
\ No newline at end of file diff --git a/tests/misc/div.html b/tests/misc/div.html index 7b68854..cb6a759 100644 --- a/tests/misc/div.html +++ b/tests/misc/div.html @@ -2,4 +2,9 @@ _foo_ -</div>
\ No newline at end of file +</div> + +<p>And now in uppercase:</p> +<DIV> +foo +</DIV>
\ No newline at end of file diff --git a/tests/misc/div.txt b/tests/misc/div.txt index ca87745..4ff972e 100644 --- a/tests/misc/div.txt +++ b/tests/misc/div.txt @@ -3,3 +3,9 @@ _foo_ </div> + +And now in uppercase: + +<DIV> +foo +</DIV> diff --git a/tests/misc/html.html b/tests/misc/html.html index c72bb81..1eb6a97 100644 --- a/tests/misc/html.html +++ b/tests/misc/html.html @@ -1,7 +1,6 @@ <h1>Block level html</h1> -<p>Some inline <b>stuff<b>.<br /> -</p> +<p>Some inline <b>stuff<b>. </p> <p>Now some <arbitrary>arbitrary tags</arbitrary>.</p> <div>More block level html.</div> diff --git a/tests/misc/two-spaces.html b/tests/misc/two-spaces.html index 102d1db..97b54b4 100644 --- a/tests/misc/two-spaces.html +++ b/tests/misc/two-spaces.html @@ -4,14 +4,12 @@ but this line has three <br /> and this is the second from last line in this test message</p> <ul> -<li>This list item has two spaces.<br /> -</li> +<li>This list item has two spaces. </li> <li> <p>This has none. This line has three. <br /> This line has none. - And this line two.<br /> -</p> + And this line two. </p> <p>This line has none.</p> </li> <li> diff --git a/tests/misc/url_spaces.html b/tests/misc/url_spaces.html index ebacb75..f9c91b3 100644 --- a/tests/misc/url_spaces.html +++ b/tests/misc/url_spaces.html @@ -1,2 +1,2 @@ -<p><a href="http://wikipedia.org/wiki/Dawn of War">Dawn of War</a></p> -<p><a href="http://wikipedia.org/wiki/Dawn of War" title="Dawn of War">Dawn of War</a></p>
\ No newline at end of file +<p><a href="http://wikipedia.org/wiki/Dawn%20of%20War">Dawn of War</a></p> +<p><a href="http://wikipedia.org/wiki/Dawn%20of%20War" title="Dawn of War">Dawn of War</a></p>
\ No newline at end of file diff --git a/tests/plugins.py b/tests/plugins.py index 6e72024..bbeed60 100644 --- a/tests/plugins.py +++ b/tests/plugins.py @@ -1,5 +1,5 @@ import traceback -from util import MarkdownSyntaxError +from .util import MarkdownSyntaxError from nose.plugins import Plugin from nose.plugins.errorclass import ErrorClass, ErrorClassPlugin @@ -73,7 +73,7 @@ or "")) self.html.extend(['<span>FAILED (', 'failures=%d ' % len(result.failures), 'errors=%d' % len(result.errors)]) - for cls in result.errorClasses.keys(): + for cls in list(result.errorClasses.keys()): storage, label, isfail = result.errorClasses[cls] if len(storage): self.html.append(' %ss=%d' % (label, len(storage))) diff --git a/tests/safe_mode/inline-html-simple.html b/tests/safe_mode/inline-html-simple.html index 981c3a2..1e5df17 100644 --- a/tests/safe_mode/inline-html-simple.html +++ b/tests/safe_mode/inline-html-simple.html @@ -29,8 +29,7 @@ Blah <pre><code><!-- Comment --> </code></pre> <p>Just plain comment, with trailing spaces on the line:</p> -<p><!-- foo --> <br /> -</p> +<p><!-- foo --> </p> <p>Code:</p> <pre><code><hr /> </code></pre> @@ -38,10 +37,8 @@ Blah <p><hr></p> <p><hr/></p> <p><hr /></p> -<p><hr> <br /> -</p> -<p><hr/><br /> -</p> +<p><hr> </p> +<p><hr/> </p> <p><hr /> </p> <p><hr class="foo" id="bar" /></p> <p><hr class="foo" id="bar"/></p> diff --git a/tests/safe_mode/link-targets.html b/tests/safe_mode/link-targets.html new file mode 100644 index 0000000..768ae5b --- /dev/null +++ b/tests/safe_mode/link-targets.html @@ -0,0 +1,2 @@ +<p><a href="">XSS</a> +See http://security.stackexchange.com/q/30330/1261 for details.</p>
\ No newline at end of file diff --git a/tests/safe_mode/link-targets.txt b/tests/safe_mode/link-targets.txt new file mode 100644 index 0000000..10eebda --- /dev/null +++ b/tests/safe_mode/link-targets.txt @@ -0,0 +1,3 @@ +[XSS](javascript://%0Aalert%28'XSS'%29;) +See http://security.stackexchange.com/q/30330/1261 for details. + diff --git a/tests/test_apis.py b/tests/test_apis.py index b39664a..e0f7a03 100644 --- a/tests/test_apis.py +++ b/tests/test_apis.py @@ -7,6 +7,7 @@ Tests of the various APIs with the python markdown lib. """ +from __future__ import unicode_literals import unittest import os import sys @@ -14,6 +15,8 @@ import types import markdown import warnings +PY3 = sys.version_info[0] == 3 + class TestMarkdownBasics(unittest.TestCase): """ Tests basics of the Markdown class. """ @@ -140,51 +143,51 @@ class TestOrderedDict(unittest.TestCase): def testValues(self): """ Test output of OrderedDict.values(). """ - self.assertEqual(self.odict.values(), ['This', 'a', 'self', 'test']) + self.assertEqual(list(self.odict.values()), ['This', 'a', 'self', 'test']) def testKeys(self): """ Test output of OrderedDict.keys(). """ - self.assertEqual(self.odict.keys(), + self.assertEqual(list(self.odict.keys()), ['first', 'third', 'fourth', 'fifth']) def testItems(self): """ Test output of OrderedDict.items(). """ - self.assertEqual(self.odict.items(), + self.assertEqual(list(self.odict.items()), [('first', 'This'), ('third', 'a'), ('fourth', 'self'), ('fifth', 'test')]) def testAddBefore(self): """ Test adding an OrderedDict item before a given key. """ self.odict.add('second', 'is', '<third') - self.assertEqual(self.odict.items(), + self.assertEqual(list(self.odict.items()), [('first', 'This'), ('second', 'is'), ('third', 'a'), ('fourth', 'self'), ('fifth', 'test')]) def testAddAfter(self): """ Test adding an OrderDict item after a given key. """ self.odict.add('second', 'is', '>first') - self.assertEqual(self.odict.items(), + self.assertEqual(list(self.odict.items()), [('first', 'This'), ('second', 'is'), ('third', 'a'), ('fourth', 'self'), ('fifth', 'test')]) def testAddAfterEnd(self): """ Test adding an OrderedDict item after the last key. """ self.odict.add('sixth', '.', '>fifth') - self.assertEqual(self.odict.items(), + self.assertEqual(list(self.odict.items()), [('first', 'This'), ('third', 'a'), ('fourth', 'self'), ('fifth', 'test'), ('sixth', '.')]) def testAdd_begin(self): """ Test adding an OrderedDict item using "_begin". """ self.odict.add('zero', 'CRAZY', '_begin') - self.assertEqual(self.odict.items(), + self.assertEqual(list(self.odict.items()), [('zero', 'CRAZY'), ('first', 'This'), ('third', 'a'), ('fourth', 'self'), ('fifth', 'test')]) def testAdd_end(self): """ Test adding an OrderedDict item using "_end". """ self.odict.add('sixth', '.', '_end') - self.assertEqual(self.odict.items(), + self.assertEqual(list(self.odict.items()), [('first', 'This'), ('third', 'a'), ('fourth', 'self'), ('fifth', 'test'), ('sixth', '.')]) @@ -196,20 +199,20 @@ class TestOrderedDict(unittest.TestCase): def testDeleteItem(self): """ Test deletion of an OrderedDict item. """ del self.odict['fourth'] - self.assertEqual(self.odict.items(), + self.assertEqual(list(self.odict.items()), [('first', 'This'), ('third', 'a'), ('fifth', 'test')]) def testChangeValue(self): """ Test OrderedDict change value. """ self.odict['fourth'] = 'CRAZY' - self.assertEqual(self.odict.items(), + self.assertEqual(list(self.odict.items()), [('first', 'This'), ('third', 'a'), ('fourth', 'CRAZY'), ('fifth', 'test')]) def testChangeOrder(self): """ Test OrderedDict change order. """ self.odict.link('fourth', '<third') - self.assertEqual(self.odict.items(), + self.assertEqual(list(self.odict.items()), [('first', 'This'), ('fourth', 'self'), ('third', 'a'), ('fifth', 'test')]) @@ -217,7 +220,7 @@ class TestOrderedDict(unittest.TestCase): """ Test OrderedDict change order with bad location. """ self.assertRaises(ValueError, self.odict.link('fourth', '<bad')) # Check for data integrity ("fourth" wasn't deleted).' - self.assertEqual(self.odict.items(), + self.assertEqual(list(self.odict.items()), [('first', 'This'), ('third', 'a'), ('fourth', 'self'), ('fifth', 'test')]) @@ -267,6 +270,9 @@ class TestErrors(unittest.TestCase): def _create_fake_extension(name, has_factory_func=True, is_wrong_type=False): """ Create a fake extension module for testing. """ mod_name = '_'.join(['mdx', name]) + if not PY3: + # mod_name must be bytes in Python 2.x + mod_name = bytes(mod_name) ext_mod = types.ModuleType(mod_name) def makeExtension(configs=None): if is_wrong_type: @@ -332,7 +338,7 @@ class testAtomicString(unittest.TestCase): """ Test that a regular string is parsed. """ tree = markdown.util.etree.Element('div') p = markdown.util.etree.SubElement(tree, 'p') - p.text = u'some *text*' + p.text = 'some *text*' new = self.inlineprocessor.run(tree) self.assertEqual(markdown.serializers.to_html_string(new), '<div><p>some <em>text</em></p></div>') @@ -341,7 +347,7 @@ class testAtomicString(unittest.TestCase): """ Test that a simple AtomicString is not parsed. """ tree = markdown.util.etree.Element('div') p = markdown.util.etree.SubElement(tree, 'p') - p.text = markdown.util.AtomicString(u'some *text*') + p.text = markdown.util.AtomicString('some *text*') new = self.inlineprocessor.run(tree) self.assertEqual(markdown.serializers.to_html_string(new), '<div><p>some *text*</p></div>') @@ -350,16 +356,16 @@ class testAtomicString(unittest.TestCase): """ Test that a nested AtomicString is not parsed. """ tree = markdown.util.etree.Element('div') p = markdown.util.etree.SubElement(tree, 'p') - p.text = markdown.util.AtomicString(u'*some* ') + p.text = markdown.util.AtomicString('*some* ') span1 = markdown.util.etree.SubElement(p, 'span') - span1.text = markdown.util.AtomicString(u'*more* ') + span1.text = markdown.util.AtomicString('*more* ') span2 = markdown.util.etree.SubElement(span1, 'span') - span2.text = markdown.util.AtomicString(u'*text* ') + span2.text = markdown.util.AtomicString('*text* ') span3 = markdown.util.etree.SubElement(span2, 'span') - span3.text = markdown.util.AtomicString(u'*here*') - span3.tail = markdown.util.AtomicString(u' *to*') - span2.tail = markdown.util.AtomicString(u' *test*') - span1.tail = markdown.util.AtomicString(u' *with*') + span3.text = markdown.util.AtomicString('*here*') + span3.tail = markdown.util.AtomicString(' *to*') + span2.tail = markdown.util.AtomicString(' *test*') + span1.tail = markdown.util.AtomicString(' *with*') new = self.inlineprocessor.run(tree) self.assertEqual(markdown.serializers.to_html_string(new), '<div><p>*some* <span>*more* <span>*text* <span>*here*</span> ' diff --git a/tests/test_extensions.py b/tests/test_extensions.py index 7661347..fd77e5e 100644 --- a/tests/test_extensions.py +++ b/tests/test_extensions.py @@ -7,6 +7,7 @@ continue to work as advertised. This used to be accomplished by doctests. """ +from __future__ import unicode_literals import unittest import markdown @@ -47,7 +48,7 @@ class TestCodeHilite(unittest.TestCase): '</pre></div>') else: self.assertEqual(self.md.convert(text), - '<pre><code>Code\n' + '<pre class="codehilite"><code># A Code Comment' '</code></pre>') @@ -171,6 +172,18 @@ header_forceid: Off self.assertEqual(markdown.markdown(text, ['headerid', 'meta']), '<h2>A Header</h2>') + def testHeaderIdWithAttr_List(self): + """ Test HeaderIDs with Attr_List extension. """ + + text = '# Header1 {: #foo }\n# Header2 {: .bar }' + self.assertEqual(markdown.markdown(text, ['headerid', 'attr_list']), + '<h1 id="foo">Header1</h1>\n' + '<h1 class="bar" id="header2">Header2</h1>') + # Switch order extensions are loaded - should be no change in behavior. + self.assertEqual(markdown.markdown(text, ['attr_list', 'headerid']), + '<h1 id="foo">Header1</h1>\n' + '<h1 class="bar" id="header2">Header2</h1>') + class TestMetaData(unittest.TestCase): """ Test MetaData extension. """ diff --git a/tests/util.py b/tests/util.py index 6690eed..bbf7aea 100644 --- a/tests/util.py +++ b/tests/util.py @@ -1,4 +1,8 @@ -from ConfigParser import SafeConfigParser +import sys +if sys.version_info[0] == 3: + from configparser import SafeConfigParser +else: + from ConfigParser import SafeConfigParser class MarkdownSyntaxError(Exception): pass |