aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--docs/_template.html2
-rw-r--r--docs/basic.css23
-rw-r--r--docs/change_log.txt10
-rw-r--r--docs/extensions/attr_list.txt2
-rw-r--r--docs/extensions/index.txt75
-rw-r--r--docs/extensions/wikilinks.txt2
-rw-r--r--docs/index.txt67
-rw-r--r--docs/reference.txt64
-rw-r--r--docs/release-2.2.0.txt4
-rw-r--r--docs/release-2.2.1.txt12
-rw-r--r--docs/release-2.3.txt37
-rw-r--r--fabfile.py69
-rw-r--r--markdown/__init__.py27
-rw-r--r--markdown/__version__.py28
-rw-r--r--markdown/blockprocessors.py15
-rw-r--r--markdown/extensions/attr_list.py23
-rw-r--r--markdown/extensions/def_list.py14
-rw-r--r--markdown/extensions/extra.py5
-rw-r--r--[-rwxr-xr-x]markdown/extensions/fenced_code.py2
-rw-r--r--markdown/extensions/footnotes.py14
-rw-r--r--markdown/extensions/headerid.py12
-rw-r--r--markdown/extensions/smart_strong.py1
-rw-r--r--markdown/inlinepatterns.py41
-rw-r--r--markdown/odict.py2
-rw-r--r--markdown/preprocessors.py15
-rw-r--r--markdown/treeprocessors.py6
-rw-r--r--markdown/util.py4
-rwxr-xr-xsetup.py70
-rw-r--r--tests/__init__.py25
-rw-r--r--tests/basic/angle-links-and-img.html2
-rw-r--r--tests/extensions/attr_list.html3
-rw-r--r--tests/extensions/attr_list.txt1
-rw-r--r--tests/extensions/extra/def-in-list.html25
-rw-r--r--tests/extensions/extra/def-in-list.txt15
-rw-r--r--tests/extensions/nl2br_w_attr_list.html1
-rw-r--r--tests/extensions/nl2br_w_attr_list.txt2
-rw-r--r--tests/extensions/test.cfg3
-rw-r--r--tests/misc/attributes-image-ref.html1
-rw-r--r--tests/misc/attributes-image-ref.txt4
-rw-r--r--tests/misc/blank_lines_in_codeblocks.html61
-rw-r--r--tests/misc/blank_lines_in_codeblocks.txt73
-rw-r--r--tests/misc/div.html7
-rw-r--r--tests/misc/div.txt6
-rw-r--r--tests/misc/html.html3
-rw-r--r--tests/misc/two-spaces.html6
-rw-r--r--tests/misc/url_spaces.html4
-rw-r--r--tests/plugins.py4
-rw-r--r--tests/safe_mode/inline-html-simple.html9
-rw-r--r--tests/safe_mode/link-targets.html2
-rw-r--r--tests/safe_mode/link-targets.txt3
-rw-r--r--tests/test_apis.py48
-rw-r--r--tests/test_extensions.py15
-rw-r--r--tests/util.py6
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.
diff --git a/fabfile.py b/fabfile.py
index 472c769..b50f007 100644
--- a/fabfile.py
+++ b/fabfile.py
@@ -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]*;)' # &amp;
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
diff --git a/setup.py b/setup.py
index 3491300..3e6beb7 100755
--- a/setup.py
+++ b/setup.py
@@ -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>&lt;!-- Comment --&gt;
</code></pre>
<p>Just plain comment, with trailing spaces on the line:</p>
-<p>&lt;!-- foo --&gt; <br />
-</p>
+<p>&lt;!-- foo --&gt; </p>
<p>Code:</p>
<pre><code>&lt;hr /&gt;
</code></pre>
@@ -38,10 +37,8 @@ Blah
<p>&lt;hr&gt;</p>
<p>&lt;hr/&gt;</p>
<p>&lt;hr /&gt;</p>
-<p>&lt;hr&gt; <br />
-</p>
-<p>&lt;hr/&gt;<br />
-</p>
+<p>&lt;hr&gt; </p>
+<p>&lt;hr/&gt; </p>
<p>&lt;hr /&gt; </p>
<p>&lt;hr class="foo" id="bar" /&gt;</p>
<p>&lt;hr class="foo" id="bar"/&gt;</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