diff --git a/.coveragerc b/.coveragerc
index 11e64537a..9a02e1fdf 100644
--- a/.coveragerc
+++ b/.coveragerc
@@ -1,5 +1,4 @@
 [run]
 omit=
-    markdown/__version__.py
     *site-packages*
     tests/*
diff --git a/.gitignore b/.gitignore
index a542fb3a8..b00351794 100644
--- a/.gitignore
+++ b/.gitignore
@@ -12,5 +12,6 @@ MANIFEST
 *#*
 .tox/*
 .coverage
+*.egg-info/
 htmlcov/*
 *komodo*
\ No newline at end of file
diff --git a/.travis.yml b/.travis.yml
index cd0bbef20..7384f3d96 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -1,3 +1,4 @@
+sudo: false
 language: python
 env:
   - TOXENV=py27
@@ -7,10 +8,11 @@ env:
   - TOXENV=pypy
   - TOXENV=flake8
   - TOXENV=checkspelling
-before_install:
-  - sudo apt-get update -qq
-  - sudo apt-get install libtidy-0.99-0
-  - sudo apt-get install aspell
+addons:
+  apt:
+    packages:
+    - libtidy-0.99-0
+    - aspell
 install:
   - pip install tox
   - pip install coveralls
@@ -18,3 +20,7 @@ script:
   - tox
 after_success:
   - coveralls
+branches:
+  only:
+    - master
+    - md3
diff --git a/INSTALL.md b/INSTALL.md
index 6e6ab8c74..95fbfd125 100644
--- a/INSTALL.md
+++ b/INSTALL.md
@@ -5,5 +5,5 @@ As an Admin/Root user on your system do:
 
     pip install markdown
 
-Or for more specific instructions, view the documentation in `docs/install.txt`
-or on the website at <https://pythonhosted.org/Markdown/install.html>.
+Or for more specific instructions, view the documentation in `docs/install.md`
+or on the website at <https://pythonhosted.org/Markdown/install/>.
diff --git a/MANIFEST.in b/MANIFEST.in
index 95a3ce28e..25ad6b38d 100644
--- a/MANIFEST.in
+++ b/MANIFEST.in
@@ -1,10 +1,7 @@
-recursive-include bin *
 recursive-include markdown *.py
 recursive-include docs *
 recursive-include tests *.txt *.html *.cfg *.py
 include setup.py
-include setup.cfg
-include run-tests.py
 include tox.ini
 include makefile
 include LICENSE.md
diff --git a/README.md b/README.md
index a7f855029..5e57f59de 100644
--- a/README.md
+++ b/README.md
@@ -1,11 +1,21 @@
-[Python-Markdown][]
-===================
+Python-Markdown
+===============
 
-[![Build Status](http://img.shields.io/travis/waylan/Python-Markdown.svg)](https://travis-ci.org/waylan/Python-Markdown)
-[![Coverage Status](https://img.shields.io/coveralls/waylan/Python-Markdown.svg)](https://coveralls.io/r/waylan/Python-Markdown?branch=master)
+[![Build Status](https://travis-ci.org/Python-Markdown/markdown.svg?branch=master)](https://travis-ci.org/Python-Markdown/markdown)
+[![Coverage Status](https://coveralls.io/repos/Python-Markdown/markdown/badge.svg?branch=master)](https://coveralls.io/r/Python-Markdown/markdown?branch=master)
 [![Downloads](http://img.shields.io/pypi/dm/Markdown.svg)](https://pypi.python.org/pypi/Markdown#downloads)
 [![Latest Version](http://img.shields.io/pypi/v/Markdown.svg)](http://pypi.python.org/pypi/Markdown)
 [![BSD License](http://img.shields.io/badge/license-BSD-yellow.svg)](http://opensource.org/licenses/BSD-3-Clause)
+[![Issue Stats](http://issuestats.com/github/waylan/Python-Markdown/badge/issue?style=flat)](http://issuestats.com/github/waylan/Python-Markdown)
+
+***WARNING:*** This is **alpha** software. This is the development repo for the upcoming version 3.0. 
+For a stable version of Python-Markdown see the 2.x repo at [waylan/Python-Markdown].
+If you are interested in what changes to expect in 3.0 you may want to checkout the [roadmap].
+If you would like to help, pull requests are welcome. If you would like to discuss future features
+and/or changes, start a discussion on the [mailing list].
+
+[waylan/Python-Markdown]: https://github.com/waylan/Python-Markdown
+[roadmap]: https://github.com/waylan/Python-Markdown/issues/391
 
 This is a Python implementation of John Gruber's [Markdown][]. 
 It is almost completely compliant with the reference implementation,
@@ -13,10 +23,9 @@ 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][].
 
-[Python-Markdown]: https://pythonhosted.org/Markdown/
 [Markdown]: http://daringfireball.net/projects/markdown/
-[Features]: https://pythonhosted.org/Markdown/index.html#Features
-[Available Extensions]: https://pythonhosted.org/Markdown/extensions/index.html
+[Features]: https://pythonhosted.org/Markdown/index/#Features
+[Available Extensions]: https://pythonhosted.org/Markdown/extensions/
 
 
 Documentation
@@ -32,4 +41,4 @@ 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 
+[bug tracker]: http://github.com/Python-Markdown/markdown/issues 
diff --git a/bin/markdown_py b/bin/markdown_py
deleted file mode 100755
index 220c666a0..000000000
--- a/bin/markdown_py
+++ /dev/null
@@ -1,34 +0,0 @@
-#!/usr/bin/env python
-"""
-Python Markdown, the Command Line Script
-========================================
-
-This is the command line script for Python Markdown.
-
-Basic use from the command line:
-
-    markdown source.txt > destination.html
-
-Run "markdown --help" to see more options.
-
-See markdown/__init__.py for information on using Python Markdown as a module.
-
-## Authors and License
-
-Started by [Manfred Stienstra](http://www.dwerg.net/).  Continued and
-maintained  by [Yuri Takhteyev](http://www.freewisdom.org), [Waylan
-Limberg](http://achinghead.com/) and [Artem Yunusov](http://blog.splyer.com).
-
-Contact: markdown@freewisdom.org
-
-Copyright 2007, 2008 The Python Markdown Project (v. 1.7 and later)
-Copyright 200? Django Software Foundation (OrderedDict implementation)
-Copyright 2004, 2005, 2006 Yuri Takhteyev (v. 0.2-1.6b)
-Copyright 2004 Manfred Stienstra (the original version)
-
-License: BSD (see docs/LICENSE for details).
-"""
-
-if __name__ == '__main__':
-    from markdown.__main__ import run
-    run()
diff --git a/checkspelling.sh b/checkspelling.sh
index 4ae20cc85..654fef061 100755
--- a/checkspelling.sh
+++ b/checkspelling.sh
@@ -1,14 +1,14 @@
 #!/bin/bash
 
 echo "Building docs..."
-python setup.py --quiet build_docs --force
+mkdocs build --clean
 echo "Compiling Dictionary..."
 aspell --lang=en create master ./tmp <.spell-dict
-echo "Checking spelling...\n"
+echo "Checking spelling..."
 
 let "fails=0"
 
-for file in $(find build/docs/ -type f -name "*.html"); do
+for file in $(find site/ -type f -name "*.html"); do
     words=$(aspell list --lang=en --mode=html --add-html-skip=code --extra-dicts=./tmp  <$file)
     if [ "$words" ]; then
         uniquewords=$(tr ' ' '\n' <<< "${words[@]}" | sort -u | tr '\n' ' ')
@@ -22,7 +22,7 @@ for file in $(find build/docs/ -type f -name "*.html"); do
     fi
 done
 rm -f ./tmp
-rm -rf build
+rm -rf site
 
 if [ $fails -gt 0 ]; then
     echo "$fails files with misspelled words."
diff --git a/docs/_template.html b/doc_theme/_template.html
similarity index 100%
rename from docs/_template.html
rename to doc_theme/_template.html
diff --git a/docs/basic.css b/doc_theme/basic.css
similarity index 100%
rename from docs/basic.css
rename to doc_theme/basic.css
diff --git a/docs/default.css b/doc_theme/default.css
similarity index 100%
rename from docs/default.css
rename to doc_theme/default.css
diff --git a/docs/authors.md b/docs/authors.md
new file mode 100644
index 000000000..9b75b23cb
--- /dev/null
+++ b/docs/authors.md
@@ -0,0 +1,58 @@
+title:      Authors
+
+Primary Authors
+===============
+
+*   __[Waylan Limberg](http://achinghead.com/)__
+
+    Waylan is the current maintainer of the code and has written much of the current 
+    code base, included a complete refactor of the core.  He started out by 
+    authoring many of the available extensions and later was asked to join Yuri, 
+    where he began fixing numerous bugs, adding documentation and making general 
+    improvements to the existing code base.
+
+
+*   __[Yuri Takteyev](http://freewisdom.org/)__
+
+    Yuri wrote most of the code found in version 1.x while procrastinating his Ph.D.
+    Various pieces of his code still exist, most notably the basic structure.
+
+*   __[Manfed Stienstra](http://www.dwerg.net/)__
+
+    Manfed wrote the original version of the script and is responsible for 
+    various parts of the existing code base.
+
+*   __Artem Yunusov__
+
+    Artem, who as part of a 2008 GSoC project, refactored inline patterns, 
+    replaced the NanoDOM with ElementTree support and made various other 
+    improvements.
+
+*   __David Wolever__
+
+    David refactored the extension API and made other improvements
+    as he helped to integrate Markdown into Dr.Project.
+
+Other Contributors
+==================
+
+The incomplete list of individuals below have provided patches or otherwise 
+contributed to the project in various ways. We would like to thank everyone 
+who has contributed to the project in any way.
+
+* Eric Abrahamsen
+* Jeff Balogh
+* Sergej Chodarev
+* Chris Clark
+* Tiago Cogumbreiro
+* Kjell Magne Fauske
+* G. Clark Haynes
+* Daniel Krech
+* Steward Midwinter
+* Jack Miller
+* Neale Pickett
+* Paul Stansifer
+* John Szakmeister
+* Malcolm Tredinnick
+* Ben Wilson
+* and many others who helped by reporting bugs
diff --git a/docs/authors.txt b/docs/authors.txt
deleted file mode 100644
index 84c69d001..000000000
--- a/docs/authors.txt
+++ /dev/null
@@ -1,60 +0,0 @@
-title:      Authors
-prev_title: Release Notes for v2.0
-prev_url:   release-2.0.html
-next_title: Table of Contents
-next_url:   siteindex.html
-
-Primary Authors
-===============
-
-[Yuri Takteyev](http://freewisdom.org/)
-
-: Yuri has written much of the current code while procrastinating his Ph.D.
-
-[Waylan Limberg](http://achinghead.com/)
-
-: Waylan is the current maintainer of the code and has written much the current 
-  code base, included a complete refactor of the core.  He started out by 
-  authoring many of the available extensions and later was asked to join Yuri, 
-  where he began fixing numerous bugs, adding documentation and making general 
-  improvements to the existing code base.
-
-Artem Yunusov
-
-: Artem, who as part of a 2008 GSoC project, refactored inline patterns, 
-  replaced the NanoDOM with ElementTree support and made various other 
-  improvements.
-
-[Manfed Stienstra](http://www.dwerg.net/)
-
-: Manfed wrote the original version of the script and is responsible for 
-  various parts of the existing code base.
-
-David Wolever
-
-: David refactored the extension API and made other improvements
-  as he helped to integrate Markdown into Dr.Project.
-
-Other Contributors
-==================
-
-The incomplete list of individuals below have provided patches or otherwise 
-contributed to the project in various ways. We would like to thank everyone 
-who has contributed to the project in any way.
-
-* Eric Abrahamsen
-* Jeff Balogh
-* Sergej Chodarev
-* Chris Clark
-* Tiago Cogumbreiro
-* Kjell Magne Fauske
-* G. Clark Haynes
-* Daniel Krech
-* Steward Midwinter
-* Jack Miller
-* Neale Pickett
-* Paul Stansifer
-* John Szakmeister
-* Malcolm Tredinnick
-* Ben Wilson
-* and many others who helped by reporting bugs
diff --git a/docs/change_log.txt b/docs/change_log/index.md
similarity index 88%
rename from docs/change_log.txt
rename to docs/change_log/index.md
index 2e190b59c..efc25482f 100644
--- a/docs/change_log.txt
+++ b/docs/change_log/index.md
@@ -1,47 +1,49 @@
 title:      Change Log
-prev_title: Test Suite
-prev_url:   test_suite.html
-next_title: Release Notes for v2.6
-next_url:   release-2.6.html
 
 Python-Markdown Change Log
 =========================
 
+Nov 24, 2015: Released version 2.6.5 (a bug-fix release).
+
+Nov 6, 2015: Released version 2.6.4 (a bug-fix release).
+
+Oct 26, 2015: Released version 2.6.3 (a bug-fix release).
+
 Apr 20, 2015: Released version 2.6.2 (a bug-fix release).
 
 Mar 8, 2015: Released version 2.6.1 (a bug-fix release). The (new)
 `yaml` option has been removed from the Meta-Data Extension as it was buggy
 (see [#390](https://github.com/waylan/Python-Markdown/issues/390)).
 
-Feb 19, 2015: Released version 2.6 ([Notes](release-2.6.html)).
+Feb 19, 2015: Released version 2.6 ([Notes](release-2.6.md)).
 
 Nov 19, 2014: Released version 2.5.2 (a bug-fix release).
 
 Sept 26, 2014: Released version 2.5.1 (a bug-fix release).
 
-Sept 12, 2014: Released version 2.5.0 ([Notes](release-2.5.html)).
+Sept 12, 2014: Released version 2.5.0 ([Notes](release-2.5.md)).
 
-Feb 16, 2014: Released version 2.4.0 ([Notes](release-2.4.html)).
+Feb 16, 2014: Released version 2.4.0 ([Notes](release-2.4.md)).
 
 Mar 22, 2013: Released version 2.3.1 (a bug-fix release).
 
-Mar 14, 2013: Released version 2.3.0 ([Notes](release-2.3.html))
+Mar 14, 2013: Released version 2.3.0 ([Notes](release-2.3.md))
 
-Nov 4, 2012: Released version 2.2.1 ([Notes](release-2.2.1.html)).
+Nov 4, 2012: Released version 2.2.1 (a bug-fix release).
 
-Jul 5, 2012: Released version 2.2.0 ([Notes](release-2.2.0.html)).
+Jul 5, 2012: Released version 2.2.0 ([Notes](release-2.2.md)).
 
-Jan 22, 2012: Released version 2.1.1 ([Notes](release-2.1.1.html)).
+Jan 22, 2012: Released version 2.1.1 (a bug-fix release).
 
-Nov 24, 2011: Released version 2.1.0 ([Notes](release-2.1.0.html)).
+Nov 24, 2011: Released version 2.1.0 ([Notes](release-2.1.md)).
 
-Oct 7, 2009: Released version 2.0.3.
+Oct 7, 2009: Released version 2.0.3. (a bug-fix release).
 
-Sept 28, 2009: Released version 2.0.2 ([Notes](release-2.0.2.html)).
+Sept 28, 2009: Released version 2.0.2 (a bug-fix release).
 
-May 20, 2009: Released version 2.0.1 ([Notes](release-2.0.1.html)).
+May 20, 2009: Released version 2.0.1 (a bug-fix release).
 
-Mar 30, 2009: Released version 2.0 ([Notes](release-2.0.html)).
+Mar 30, 2009: Released version 2.0 ([Notes](release-2.0.md)).
 
 Mar 8, 2009: Release Candidate 2.0-rc-1.
 
diff --git a/docs/release-2.0.txt b/docs/change_log/release-2.0.md
similarity index 90%
rename from docs/release-2.0.txt
rename to docs/change_log/release-2.0.md
index f44b0c51e..78518b887 100644
--- a/docs/release-2.0.txt
+++ b/docs/change_log/release-2.0.md
@@ -1,8 +1,4 @@
 title:      Release Notes for v2.0
-prev_title: Release Notes for v2.0.1
-prev_url:   release-2.0.1.html
-next_title: Authors
-next_url:   authors.html
 
 Python-Markdown 2.0 Release Notes
 =================================
@@ -26,7 +22,7 @@ extensions, then you should not need to make any changes to your code.
 However, you may want to ensure that any third party extensions you are using
 are compatible with the new API.
 
-The new extension API is fully [documented](extensions/api.html) in the docs. 
+The new extension API is fully [documented](../extensions/api.md) in the docs. 
 Below is a summary of the significant changes:
 
 * The old home-grown NanoDOM has been replaced with ElementTree. Therefore all
@@ -67,7 +63,7 @@ accepts either the extension names as strings or instances of extensions. This
 makes it possible to include multiple extensions in a single module.
 
 Numerous extensions are included in the distribution by default. See
-[available_extensions](extensions/index.html) for a complete list.
+[available_extensions](../extensions/index.md) for a complete list.
 
-See the [Change Log](change_log.html) for a full list of changes.
+See the [Change Log](index.md) for a full list of changes.
 
diff --git a/docs/release-2.1.0.txt b/docs/change_log/release-2.1.md
similarity index 86%
rename from docs/release-2.1.0.txt
rename to docs/change_log/release-2.1.md
index efb3b4af7..5dcfe6161 100644
--- a/docs/release-2.1.0.txt
+++ b/docs/change_log/release-2.1.md
@@ -1,11 +1,7 @@
-title:      Release Notes for v2.1.0
-prev_title: Release Notes for v2.1.1
-prev_url:   release-2.1.1.html
-next_title: Release Notes for v2.0.2
-next_url:   release-2.0.2.html
+title:      Release Notes for v2.1
 
-Python-Markdown 2.1.0 Release Notes
-===================================
+Python-Markdown 2.1 Release Notes
+=================================
 
 We are pleased to release Python-Markdown 2.1 which makes many 
 improvements on 2.0. In fact, we consider 2.1 to be what 2.0 should have been. 
@@ -35,7 +31,7 @@ is suggested you manually use Python 3.1's 2to3 tool to do a conversion.
 wrapper methods. It now expects keyword arguments. Currently, the positional
 arguments should continue to work, but the solution feels hacky and may be 
 removed in a future version. All users are encouraged to use keyword arguments 
-as documented in the [Library Reference](reference.html).
+as documented in the [Library Reference](../reference.md).
 
 * Past versions of Python-Markdown provided module level Global variables which
 controlled the behavior of a few different aspects of the parser. Those global
@@ -44,13 +40,13 @@ Additionally, those attributes are settable as keyword arguments when
 initializing a class instance. Therefore, if you were editing the global 
 variables (either by editing the source or by overriding them in your code), 
 you should now set them on the class. See the 
-[Library Reference](reference.html) for the options available.
+[Library Reference](../reference.md) for the options available.
 
-* If you have been using the [HeaderId](extensions/header_id.html) extension 
+* If you have been using the HeaderId extension 
 to define custom ids on headers, you will want to switch to using the new 
-[Attribute List](extensions/attr_list.html) extension. The HeaderId extension 
+Attribute List extension. The HeaderId extension 
 now only auto-generates ids on headers which have not already had ids defined. 
-Note that the [Extra](extensions/extra.html) extension has been switched to use 
+Note that the Extra extension has been switched to use 
 Attribute Lists instead of HeaderId as it did previously.
 
 * Some code was moved into the `markdown.util` namespace which was previously
@@ -67,10 +63,10 @@ may want to remove them yourself as they are unlikely to work properly.
 What's New in Python-Markdown 2.1
 ---------------------------------
 
-Three new extensions were added. [Attribute Lists](extensions/attr_list.html), 
+Three new extensions were added. Attribute Lists, 
 which was inspired by Maruku's feature of the same name, 
-[Newline to Break](extensions/nl2br.html), which was inspired by GitHub 
-Flavored Markdown, and [Smart Strong](extensions/smart_strong.html), which 
+Newline to Break, which was inspired by GitHub 
+Flavored Markdown, and Smart Strong, which 
 fills a hole in the Extra extension.
 
 HTML5 is now supported. All this really means is that new block level elements 
@@ -115,7 +111,7 @@ script to accept input on `stdin`.
 The testing framework has been completely rebuilt using the Nose testing 
 framework. This provides a number of benefits including the ability to better
 test the built-in extensions and other options available to change the parsing 
-behavior. See the [Test Suite](test_suite.html) documentation for details.
+behavior. See the [Test Suite](../test_suite.md) documentation for details.
 
 Various bug fixes have been made, which are too numerous to list here. See the 
 [commit log](https://github.com/waylan/Python-Markdown/commits/master) for a 
diff --git a/docs/release-2.2.0.txt b/docs/change_log/release-2.2.md
similarity index 87%
rename from docs/release-2.2.0.txt
rename to docs/change_log/release-2.2.md
index c3750fac4..a5c678764 100644
--- a/docs/release-2.2.0.txt
+++ b/docs/change_log/release-2.2.md
@@ -1,11 +1,7 @@
-title:      Release Notes for v2.2.0
-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
+title:      Release Notes for v2.2
 
-Python-Markdown 2.2.0 Release Notes
-===================================
+Python-Markdown 2.2 Release Notes
+=================================
 
 We are pleased to release Python-Markdown 2.2 which makes improvements on 2.1. 
 While 2.2 is primarily a bug fix release, some internal improvements were made 
@@ -40,14 +36,14 @@ set to `True`. This change was made to prevent untrusted authors from injecting
 potentially harmful JavaScript in documents. This change had no effect when 
 not in `safe_mode`.
 
-What's New in Python-Markdown 2.1
+What's New in Python-Markdown 2.2
 ---------------------------------
 
 The docs were refactored and can now be found at 
 <http://packages.python.org/Markdown/>. The docs are now maintained in the
 Repository and are generated by the `setup.py build_docs` command.
 
-The [Sane_Lists](http://packages.python.org/Markdown/extensions/sane_lists.html)
+The Sane_Lists
 extension was added. The Sane Lists Extension alters the behavior of the 
 Markdown List syntax to be less surprising by not allowing the mixing of list
 types. In other words, an ordered list will not continue when an unordered list
diff --git a/docs/release-2.3.txt b/docs/change_log/release-2.3.md
similarity index 91%
rename from docs/release-2.3.txt
rename to docs/change_log/release-2.3.md
index a15e58411..52cc4aac7 100644
--- a/docs/release-2.3.txt
+++ b/docs/change_log/release-2.3.md
@@ -1,8 +1,4 @@
 title:      Release Notes for v2.3
-prev_title: Release Notes for v2.4
-prev_url:   release-2.4.html
-next_title: Release Notes for v2.2.1
-next_url:   release-2.2.1.html
 
 Python-Markdown 2.3 Release Notes
 =================================
@@ -40,12 +36,11 @@ you are outputting XHTML (the default) or HTML4.
 
 * The `force_linenos` configuration setting of the CodeHilite extension has been
 marked as **Pending Deprecation** and a new setting `linenums` has been added to
-replace it. See documentation for the [CodeHilite Extension] for an explanation
+replace it. See documentation for the CodeHilite Extension for an explanation
 of the new `linenums` setting. The new setting will honor the old 
 `force_linenos` if it is set, but it will raise a `PendingDeprecationWarning` 
 and will likely be removed in a future version of Python-Markdown.
 
-[CodeHilite Extension]: extensions/codehilite.html
 
 * The "RSS" extension has been removed and no longer ships with Python-Markdown.
 If you would like to continue using the extension (not recommended), it is 
@@ -75,12 +70,11 @@ sub-directory of another project. The various files within the library will
 still import each other properly even though 'markdown' may not be in Python's
 root namespace.
 
-* The [Admonition Extension] has been added, which implements [rST-style][rST] 
+* The Admonition Extension has been added, which implements [rST-style][rST] 
 admonitions in the Markdown syntax. However, be warned that this extension 
 is experimental and the syntax and behavior is still subject to change. Please 
 try it out and report bugs and/or improvements.
 
-[Admonition Extension]: extensions/admonition.html
 [rST]: http://docutils.sourceforge.net/docs/ref/rst/directives.html#specific-admonitions
 
 * Various bug fixes have been made.  See the
diff --git a/docs/release-2.4.txt b/docs/change_log/release-2.4.md
similarity index 73%
rename from docs/release-2.4.txt
rename to docs/change_log/release-2.4.md
index c32408cf1..50cfa5a9b 100644
--- a/docs/release-2.4.txt
+++ b/docs/change_log/release-2.4.md
@@ -1,8 +1,4 @@
 title:      Release Notes for v2.4
-prev_title: Release Notes for v2.5
-prev_url:   release-2.5.html
-next_title: Release Notes for v2.3
-next_url:   release-2.3.html
 
 Python-Markdown 2.4 Release Notes
 =================================
@@ -18,13 +14,11 @@ Backwards-incompatible Changes
 * The `force_linenos` configuration setting of the CodeHilite extension has been
 marked as **Deprecated**. It had previously been marked as "Pending Deprecation"
 in version 2.3 when a new setting `linenums` was added to replace it. See 
-documentation for the [CodeHilite Extension] for an explanation of the new 
+documentation for the CodeHilite Extension for an explanation of the new 
 `linenums` setting. The new setting will honor the old `force_linenos` if it 
 is set, but `force_linenos` will raise a `DeprecationWarning` and will likely 
 be removed in a future version of Python-Markdown.
 
-[CodeHilite Extension]: extensions/code_hilite.html
-
 * URLs are no longer percent-encoded. This improves compatibility with the
 original (written in Perl) Markdown implementation. Please percent-encode
 your URLs manually when needed.
@@ -32,7 +26,7 @@ your URLs manually when needed.
 What's New in Python-Markdown 2.4
 ---------------------------------
 
-* Thanks to the hard work of [Dmitry Shachnev] the [Smarty Extension] has been 
+* Thanks to the hard work of [Dmitry Shachnev] the Smarty Extension has been 
 added, which implements [SmartyPants] using Python-Markdown's Extension API. 
 This offers a few benefits over a third party script. The HTML does not need
 to be "tokenized" twice, no hacks are required to combine SmartyPants and
@@ -40,26 +34,23 @@ code highlighting, and we get markdown's escaping feature for free. Please try
 it out and report bugs and/or improvements.
 
 [Dmitry Shachnev]: https://github.com/mitya57
-[Smarty Extension]: extensions/smarty.html
 [SmartyPants]: http://daringfireball.net/projects/smartypants/
 
-* The [Table of Contents Extension] now supports new `permalink` option
+* The Table of Contents Extension now supports new `permalink` option
 for creating [Sphinx]-style anchor links.
 
-[Table of Contents Extension]: extensions/toc.html
 [Sphinx]: http://sphinx-doc.org/
 
 * It is now possible to enable Markdown formatting inside HTML blocks by
-appending `markdown=1` to opening tag attributes. See [Markdown Inside HTML
-Blocks] section for details. Thanks to [ryneeverett] for implementing this
+appending `markdown=1` to opening tag attributes. See Markdown Inside HTML
+Blocks section for details. Thanks to [ryneeverett] for implementing this
 feature.
 
-[Markdown Inside HTML Blocks]: extensions/extra.html#nested-markdown-inside-html-blocks
 [ryneeverett]: https://github.com/ryneeverett
 
 * The code blocks now support emphasizing some of the code lines. To use this
 feature, specify `hl_lines` option after language name, for example (using
-the [Fenced Code Extension]):
+the Fenced Code Extension):
 
         ```.python hl_lines="1 3"
         # This line will be emphasized.
@@ -69,7 +60,6 @@ the [Fenced Code Extension]):
 
     Thanks to [A. Jesse Jiryu Davis] for implementing this feature.
 
-[Fenced Code Extension]: extensions/fenced_code_blocks.html
 [A. Jesse Jiryu Davis]: https://github.com/ajdavis
 
 * Various bug fixes have been made.  See the
diff --git a/docs/release-2.5.txt b/docs/change_log/release-2.5.md
similarity index 87%
rename from docs/release-2.5.txt
rename to docs/change_log/release-2.5.md
index 0c399ed51..726abfad6 100644
--- a/docs/release-2.5.txt
+++ b/docs/change_log/release-2.5.md
@@ -1,8 +1,4 @@
 title:      Release Notes for v2.5
-prev_title: Release Notes for v2.6
-prev_url:   release-2.6.html
-next_title: Release Notes for v2.4
-next_url:   release-2.4.html
 
 Python-Markdown 2.5 Release Notes
 =================================
@@ -20,14 +16,11 @@ Backwards-incompatible Changes
 
 [importlib]: https://pypi.python.org/pypi/importlib
 
-* The `force_linenos` configuration key on the [CodeHilite Extension] has been **deprecated**
+* The `force_linenos` configuration key on the CodeHilite Extension has been **deprecated**
   and will raise a `KeyError` if provided. In the previous release (2.4), it was 
-  issuing a `DeprecationWarning`. The [`linenums`][linenums] keyword should be used 
+  issuing a `DeprecationWarning`. The `linenums` keyword should be used 
   instead, which provides more control of the output.
 
-[CodeHilite Extension]: extensions/code_hilite.html
-[linenums]: extensions/code_hilite.html#usage
-
 * Both `safe_mode` and the associated `html_replacement_text` keywords will be deprecated 
   in version 2.6 and will raise a **`PendingDeprecationWarning`** in 2.5. The so-called
   "safe mode" was never actually "safe" which has resulted in many people having a false 
@@ -96,25 +89,24 @@ Backwards-incompatible Changes
 
         $ python -m markdown -x markdown.extensions.extra input.txt
 
-    See the [documentation](reference.html#extensions) for a full explanation
+    See the [documentation](../reference.md#extensions) for a full explanation
     of the current behavior.
 
 * The previously documented method of appending the extension configuration as 
   a string to the extension name will be deprecated in Python-Markdown 
   version 2.6 and will raise a **`PendingDeprecationWarning`** in 2.5. The 
-  [`extension_configs`](reference.html#extension_configs) keyword should 
-  be used instead. See the [documentation](reference.html#extension-configs) 
+  [`extension_configs`](../reference.md#extension_configs) keyword should 
+  be used instead. See the [documentation](../reference.md#extension-configs) 
   for a full explanation of the current behavior.
 
 What's New in Python-Markdown 2.5
 ---------------------------------
 
-*   The [Smarty Extension] has had a number of additional configuration settings
+*   The Smarty Extension has had a number of additional configuration settings
     added, which allows one to define their own substitutions to better support
     languages other than English. Thanks to [Martin Altmayer] for implementing this 
 	feature.
 
-[Smarty Extension]: extensions/smarty.html
 [Martin Altmayer]:https://github.com/MartinAltmayer
 
 *   Named Extensions (strings passed to the [`extensions`][ex] keyword of 
@@ -146,7 +138,7 @@ What's New in Python-Markdown 2.5
 	the module. Extension authors will want to document carefully what is required to
 	load their extensions.
 
-[ex]: reference.html#extensions
+[ex]: ../reference.md#extensions
 
 *   The Extension Configuration code has been refactored to make it a little easier 
     for extension authors to work with configuration settings. As a result, the 
@@ -159,8 +151,8 @@ What's New in Python-Markdown 2.5
 	code going forward. The included extensions provide a model for best practices.
 	See the [API] documentation for a full explanation.
 
-[ec]: reference.html#extension_configs
-[API]: extensions/api.html#configsettings
+[ec]: ../reference.md#extension_configs
+[API]: ../extensions/api.md#configsettings
 
 *   The [Command Line Interface][cli] now accepts a `--extensions_config` (or `-c`) 
     option which accepts a file name and passes the parsed content of a [YAML] or 
@@ -169,14 +161,12 @@ What's New in Python-Markdown 2.5
 	matches the format required by the `extension_configs` keyword. Note that 
 	[PyYAML] is required to parse YAML files.
 
-[cli]: cli.html#using-extensions
+[cli]: ../cli.md#using-extensions
 [YAML]: http://yaml.org/
 [JSON]: http://json.org/
 [PyYAML]: http://pyyaml.org/
 
-*   The [admonition extension][ae] is no longer considered "experimental."
-
-[ae]: extensions/admonition.html
+*   The Admonition Extension is no longer considered "experimental."
 
 *   There have been various refactors of the testing framework. While those changes
     will not directly effect end users, the code is being better tested which will 
diff --git a/docs/release-2.6.txt b/docs/change_log/release-2.6.md
similarity index 91%
rename from docs/release-2.6.txt
rename to docs/change_log/release-2.6.md
index 747c81c51..4cd3501c3 100644
--- a/docs/release-2.6.txt
+++ b/docs/change_log/release-2.6.md
@@ -1,8 +1,4 @@
 title:      Release Notes for v2.6
-prev_title: Change Log
-prev_url:   change_log.html
-next_title: Release Notes for v2.5
-next_url:   release-2.5.html
 
 Python-Markdown 2.6 Release Notes
 =================================
@@ -104,7 +100,7 @@ as follows (assuming the file `mdx_math.py` is installed at the root of your PYT
 
 Extension authors will want to update their documentation to reflect the new behavior.
 
-See the [documentation](reference.html#extensions) for a full explanation
+See the [documentation](../reference.md#extensions) for a full explanation
 of the current behavior.
 
 ### Extension Configuration as Part of Extension Name Deprecated
@@ -112,16 +108,16 @@ of the current behavior.
 The previously documented method of appending the extension configuration options as 
 a string to the extension name is deprecated and will raise a
 **`DeprecationWarning`** in version 2.6 and an error in 2.7.
-The [`extension_configs`](reference.html#extension_configs) keyword should 
-be used instead. See the [documentation](reference.html#extension-configs) 
+The [`extension_configs`](../reference.md#extension_configs) keyword should 
+be used instead. See the [documentation](../reference.md#extension-configs) 
 for a full explanation of the current behavior.
 
 ### HeaderId Extension Pending Deprecation
 
-The [HeaderId][hid] Extension is pending deprecation and will raise a
+The HeaderId Extension is pending deprecation and will raise a
 **`PendingDeprecationWarning`** in version 2.6. The extension will be
 deprecated in version 2.7 and raise an error in version 2.8. Use the
-[Table of Contents][TOC] Extension instead, which offers most of the
+Table of Contents Extension instead, which offers most of the
 features of the HeaderId Extension and more (support for meta data is missing).
 
 Extension authors who have been using the `slugify` and `unique` functions
@@ -129,8 +125,6 @@ defined in the HeaderId Extension should note that those functions are now
 defined in the Table of Contents extension and should adjust their import
 statements accordingly (`from markdown.extensions.toc import slugify, unique`).
 
-[hid]: extensions/header_id.html
-
 ### The `configs` Keyword is Deprecated
 
 Positional arguments and the `configs` keyword on the `markdown.extension.Extension` class
@@ -178,8 +172,8 @@ options like so:
 Note the call to `super` to get the benefits of configuration handling from the parent class.
 See the [documentation][config] for more information.
 
-[config]: extensions/api.html#configsettings
-[mext]: extensions/api.html#makeextension
+[config]: ../extensions/api.md#configsettings
+[mext]: ../extensions/api.md#makeextension
 
 What's New in Python-Markdown 2.6
 ---------------------------------
@@ -193,7 +187,7 @@ worked on PyPy for some time, it is now officially supported and tested on PyPy.
 
 ### YAML Style Meta-Data
 
-The [Meta-Data] Extension now includes optional support for [YAML] style
+The Meta-Data Extension now includes optional support for [YAML] style
 meta-data. By default, the YAML deliminators are recognized, however, the
 actual data is parsed as previously.  This follows the syntax of
 [MultiMarkdown], which inspired this extension.
@@ -204,13 +198,12 @@ party extension be used if you want true YAML support. See [Issue #390][#390] fo
 explanation.</ins>
 
 [MultiMarkdown]: http://fletcherpenney.net/MultiMarkdown_Syntax_Guide#metadata
-[Meta-Data]: extensions/meta_data.html
 [YAML]: http://yaml.org/
 [#390]: https://github.com/waylan/Python-Markdown/issues/390
 
 ### Table of Contents Extension Refactored
 
-The [Table of Contents][TOC] Extension has been refactored and some new features
+The Table of Contents Extension has been refactored and some new features
 have been added.  See the documentation for a full explanation of each feature
 listed below:
 
@@ -234,11 +227,9 @@ listed below:
     of headers in their documents (h1-h6). This allows the header levels to be
     automatically adjusted to fit within the hierarchy of an HTML template.
 
-[TOC]: extensions/toc.html
-
 ### Pygments can now be disabled
 
-The [CodeHilite][ch] Extension has gained a new configuration option: `use_pygments`.
+The CodeHilite Extension has gained a new configuration option: `use_pygments`.
 The option is `True` by default, however, it allows one to turn off Pygments code
 highlighting (set to `False`) while preserving the language detection features of
 the extension. Note that Pygments language guessing is not used as that would 'use
@@ -247,7 +238,6 @@ Pygments'. If a language is defined for a code block, it will be assigned to the
 (alternate output will not be entertained) and could potentially be used by a JavaScript
 library in the browser to highlight the code block.
 
-[ch]: extensions/code_hilite.html
 [spec]: http://www.w3.org/TR/html5/text-level-semantics.html#the-code-element
 
 ### Miscellaneous
diff --git a/docs/cli.txt b/docs/cli.md
similarity index 89%
rename from docs/cli.txt
rename to docs/cli.md
index c2b37e4a9..9afc68089 100644
--- a/docs/cli.txt
+++ b/docs/cli.md
@@ -1,9 +1,4 @@
 title: Command Line
-prev_title: Library Reference
-prev_url:   reference.html
-next_title: Extensions
-next_url:   extensions/index.html
-
 
 Using Python-Markdown on the Command Line
 =========================================
@@ -15,7 +10,7 @@ Python-Markdown's various extensions.
 
 Generally, you will want to have the Markdown library fully installed on your
 system to run the command line script. See the 
-[Installation instructions](install.html) for details.
+[Installation instructions](install.md) for details.
 
 Python-Markdown's command line script takes advantage of Python's `-m` flag.
 Therefore, assuming the python executable is on your system path, use the 
@@ -60,7 +55,7 @@ path.
 
 * __*nix__ (Linux, OSX, BSD, Unix, etc.):
 
-    As each *nix distribution is different and we can't possibly document all 
+    As each \*nix distribution is different and we can't possibly document all 
     of them here, we'll provide a few helpful pointers:
 
     * Some systems will automatically install the script on your path. Try it 
@@ -107,7 +102,7 @@ Using Extensions
 
 To load a Python-Markdown extension from the command line use the `-x` 
 (or `--extension`) option. The extension module must be on your `PYTHONPATH` 
-(see the [Extension API](extensions/api.html) for details). The extension can 
+(see the [Extension API](extensions/api.md) for details). The extension can 
 then be invoked by the name of that module using Python's dot syntax:
 
 	$ python -m markdown -x path.to.module input.txt
@@ -140,14 +135,9 @@ The `--extension_configs` option will only support YAML configuration files if [
 installed on your system. JSON should work with no additional dependencies. The format
 of your configuration file is automatically detected.
 
-!!!warning
-	The previously documented method of appending the extension configuration options as a string to the 
-	extension name will be deprecated in Python-Markdown version 2.6. The `--extension_configs` 
-	option should be used instead. See the [2.5 release notes] for more information.
-
 [ec]: reference.html#extension_configs
 [YAML]: http://yaml.org/
 [JSON]: http://json.org/
 [PyYAML]: http://pyyaml.org/
-[2.5 release notes]: release-2.5.txt
+[2.5 release notes]: change_log/release-2.5.md
 
diff --git a/docs/extensions/abbreviations.txt b/docs/extensions/abbreviations.txt
deleted file mode 100644
index 9ac7c9534..000000000
--- a/docs/extensions/abbreviations.txt
+++ /dev/null
@@ -1,45 +0,0 @@
-title:      Abbreviations Extension
-prev_title: Extra Extension
-prev_url:   extra.html
-next_title: Attribute Lists Extension
-next_url:   attr_list.html
-
-Abbreviations
-=============
-
-Summary
--------
-
-The Abbreviations extension adds the ability to define abbreviations. 
-Specifically, any defined abbreviation is wrapped in  an `<abbr>` tag.
-
-The Abbreviations extension is included in the standard Markdown library.
-
-Syntax
-------
-
-Abbreviations are defined using the syntax established in 
-[PHP Markdown Extra][php].
-
-[php]: http://www.michelf.com/projects/php-markdown/extra/#abbr
-
-Thus, the following text (taken from the above referenced PHP documentation):
-
-    The HTML specification 
-    is maintained by the W3C.
-        
-    *[HTML]: Hyper Text Markup Language
-    *[W3C]:  World Wide Web Consortium
-
-will be rendered as:
-
-    <p>The <abbr title="Hyper Text Markup Language">HTML</abbr> specification 
-    is maintained by the <abbr title="World Wide Web Consortium">W3C</abbr>.</p>
-
-Usage
------
-
-See [Extensions](index.html) for general extension usage, specify `markdown.extensions.abbr`
-as the name of the extension.
-
-This extension does not accept any special configuration options.
diff --git a/docs/extensions/admonition.txt b/docs/extensions/admonition.txt
deleted file mode 100644
index d68bd5de1..000000000
--- a/docs/extensions/admonition.txt
+++ /dev/null
@@ -1,75 +0,0 @@
-title:      Admonition Extension
-prev_title: Smart Strong Extension
-prev_url:   smart_strong.html
-next_title: CodeHilite Extension
-next_url:   code_hilite.html
-
-Admonition
-==========
-
-Summary
--------
-
-The Admonition extension adds [rST-style][rST] admonitions to Markdown documents.
-
-This extension is included in the standard Markdown library.
-
-[rST]: http://docutils.sourceforge.net/docs/ref/rst/directives.html#specific-admonitions
-
-Syntax
-------
-
-Admonitions are created using the following syntax:
-
-    !!! type "optional explicit title within double quotes"
-        Any number of other indented markdown elements.
-
-        This is the second paragraph.
-
-`type` will be used as the CSS class name and as default title. It must be a
-single word. So, for instance:
-
-    !!! note
-        You should note that the title will be automatically capitalized.
-
-will render:
-
-    <div class="admonition note">
-    <p class="admonition-title">Note</p>
-    <p>You should note that the title will be automatically capitalized.</p>
-    </div>
-
-Optionally, you can use custom titles. For instance:
-
-    !!! danger "Don't try this at home"
-        ...
-
-will render:
-
-    <div class="admonition danger">
-    <p class="admonition-title">Don't try this at home</p>
-    <p>...</p>
-    </div>
-
-If you don't want a title, use a blank string `""`:
-
-    !!! important ""
-        This is a admonition box without a title.
-
-results in:
-
-    <div class="admonition important">
-    <p>This is a admonition box without a title.</p>
-    </div>
-
-
-rST suggests the following `types`, but you're free to use whatever you want:
-    attention, caution, danger, error, hint, important, note, tip, warning.
-
-Styling
--------
-
-There is no CSS included as part of this extension. Look up the default
-[Sphinx][sphinx] theme if you need inspiration.
-
-[sphinx]: http://sphinx.pocoo.org/
diff --git a/docs/extensions/api.txt b/docs/extensions/api.md
similarity index 99%
rename from docs/extensions/api.txt
rename to docs/extensions/api.md
index 66daa8685..4cb0135d9 100644
--- a/docs/extensions/api.txt
+++ b/docs/extensions/api.md
@@ -672,7 +672,7 @@ the module and call the `makeExtension` function to initiate your extension.
 [Config Settings]: #configsettings
 [makeExtension]: #makeextension
 [ElementTree]: http://effbot.org/zone/element-index.htm
-[Available Extensions]: index.html 
-[Footnotes]: footnotes.html
-[Definition Lists]: definition_lists.html
-[library reference]: ../reference.html
+[Available Extensions]: index.md 
+[Footnotes]: https://github.com/Python-Markdown/mdx_footnotes
+[Definition Lists]: https://github.com/Python-Markdown/mdx_definition_lists
+[library reference]: ../reference.md
diff --git a/docs/extensions/attr_list.txt b/docs/extensions/attr_list.txt
deleted file mode 100644
index 195134731..000000000
--- a/docs/extensions/attr_list.txt
+++ /dev/null
@@ -1,88 +0,0 @@
-title:      Attribute Lists Extension
-prev_title: Abbreviations Extension
-prev_url:   abbreviations.html
-next_title: Definition Lists Extension
-next_url:   definition_lists.html
-
-Attribute Lists
-===============
-
-Summary
--------
-
-The Attribute Lists extension adds a syntax to define attributes on the various
-HTML elements in markdown's output.
-
-This extension is included in the standard Markdown library.
-
-Syntax
-------
-
-The basic syntax was inspired by [Maruku][]'s Attribute Lists feature.
-
-[Maruku]: http://maruku.rubyforge.org/proposal.html#attribute_lists
-
-### The List ###
-
-An example attribute list might look like this:
-
-    {: #someid .someclass somekey='some value' }
-
-A word which starts with a hash (`#`) will set the id of an element.
-
-A word which starts with a dot (`.`) will be added to the list of classes 
-assigned to an element.
-
-A key/value pair (`somekey='some value'`) will assign that pair to the element.
-
-Be aware that while the dot syntax will add to a class, using key/value pairs
-will always override the previously defined attribute. Consider the following:
-
-    {: #id1 .class1 id=id2 class="class2 class3" .class4 }
-
-The above example would result in the following attributes being defined:
-
-    id="id2" class="class2 class3 class4"
-
-### Block Level ###
-
-To define attributes for a block level element, the attribute list should
-be defined on the last line of the block by itself.
-
-    This is a paragraph.
-    {: #an_id .a_class }
-
-The above results in the following output:
-
-    <p id="an_id" class="a_class">This is a paragraph.</p>
-
-The one exception is headers, as they are only ever allowed on one line.
-
-    A setext style header {: #setext}
-    =================================
-
-    ### A hash style header ### {: #hash }
-
-The above results in the following output:
-
-    <h1 id="setext">A setext style header</h1>
-    <h3 id="hash">A hash style header</h3>
-
-### Inline ###
-
-To define attributes on inline elements, the attribute list should be defined 
-immediately after the inline element with no white space.
-
-    [link](http://example.com){: class="foo bar" title="Some title!" }
-
-The above results in the following output:
-
-    <p><a href="http://example.com" class="foo bar" title="Some title!">link</a></p>
-
-Usage
------
-
-See [Extensions](index.html) for general extension usage, specify `markdown.extensions.attr_list`
-as the name of the extension.
-
-This extension does not accept any special configuration options.
diff --git a/docs/extensions/code_hilite.txt b/docs/extensions/code_hilite.txt
deleted file mode 100644
index 5e802b822..000000000
--- a/docs/extensions/code_hilite.txt
+++ /dev/null
@@ -1,176 +0,0 @@
-title:      CodeHilite Extension
-prev_title: Admonition Extension
-prev_url:   admonition.html
-next_title: HeaderId Extension
-next_url:   header_id.html
-
-CodeHilite
-==========
-
-Summary
--------
-
-The CodeHilite extension adds code/syntax highlighting to standard
-Python-Markdown code blocks using [Pygments][].
-
-[Pygments]: http://pygments.org/
-
-This extension is included in the standard Markdown library.
-
-Setup
------
-
-You will also need to [download][dl] and install the Pygments package on your
-`PYTHONPATH`. You will need to determine the appropriate CSS classes and create
-appropriate rules for them, which are either defined in or linked from the
-header of your HTML templates. See the excellent [documentation][] for more
-details. If no language is defined, Pygments will attempt to guess the
-language. When that fails, the code block will not be highlighted.
-
-[dl]: http://pygments.org/download/
-[documentation]: http://pygments.org/docs
-
-!!! Note
-    The CSS and/or JavaScript is not included as part of this extension
-    but must be provided by the end user. The Pygments project provides
-    default CSS styles which you may find to be a useful starting point.
-
-Syntax
-------
-
-The CodeHilite extension follows the same [syntax][] as regular Markdown code
-blocks, with one exception. The highlighter needs to know what language to use for
-the code block. There are three ways to tell the highlighter what language the
-code block contains and each one has a different result.
-
-!!! Note
-    The format of the language identifier only effects the display of line numbers
-    if `linenums` is set to `None` (the default). If set to `True` or `False`
-    (see [Usage](#usage) below) the format of the identifier has no effect on the
-    display of line numbers -- it only serves as a means to define the language
-    of the code block.
-
-[syntax]: http://daringfireball.net/projects/markdown/syntax#precode
-
-### Shebang (with path) ###
-
-If the first line of the code block contains a shebang, the language is derived
-from that and line numbers are used.
-
-        #!/usr/bin/python
-        # Code goes here ...
-
-Will result in:
-
-    #!/usr/bin/python
-    # Code goes here ...
-
-### Shebang (no path) ###
-
-If the first line contains a shebang, but the shebang line does not contain a
-path (a single `/` or even a space), then that line is removed from the code
-block before processing. Line numbers are used.
-
-        #!python
-        # Code goes here ...
-
-Will result in:
-
-    # Code goes here ...
-
-### Colons ###
-
-If the first line begins with three or more colons, the text following the
-colons identifies the language. The first line is removed from the code block
-before processing and line numbers are not used.
-
-        :::python
-        # Code goes here ...
-
-Will result in:
-
-    # Code goes here ...
-
-Certain lines can be selected for emphasis with the colon syntax. When 
-using Pygments' default CSS styles, emphasized lines have a yellow background. 
-This is useful to direct the reader's attention to specific lines.
-
-    :::python hl_lines="1 3"
-    # This line is emphasized
-    # This line isn't
-    # This line is emphasized
-
-!!! Note
-    `hl_lines` is named for Pygments' option meaning "highlighted lines".
-
-### When No Language is Defined ###
-
-CodeHilite is completely backwards compatible so that if a code block is
-encountered that does not define a language, the block is simply wrapped in
-`<pre>` tags and output. 
-
-        # Code goes here ...
-
-Will result in:
-
-    # Code goes here ...
-
-Lets see the source for that:
-
-    <div class="codehilite"><pre><code># Code goes here ...
-    </code></pre></div>
-
-!!! Note
-    When no language is defined, the Pygments highlighting engine will try to guess 
-    the language (unless `guess_lang` is set to `False`). Upon failure, the same 
-    behavior will happen as described above.
-
-Usage
------
-
-See [Extensions](index.html) for general extension usage, specify `markdown.extensions.codehilite`
-as the name of the extension.
-
-See the [Library Reference](../reference.html#extensions) for information about
-configuring extensions.
-
-The following options are provided to configure the output:
-
-* **`linenums`**:
-    Use line numbers. Possible values are `True` for yes, `False` for no and
-    `None` for auto. Defaults to `None`.
-
-    Using `True` will force every code block to have line numbers, even when
-    using colons (`:::`) for language identification.
-
-    Using `False` will turn off all line numbers, even when using shebangs
-    (`#!`) for language identification.
-
-* **`guess_lang`**:
-    Automatic language detection. Defaults to `True`.
-
-    Using `False` will prevent Pygments from guessing the language, and thus
-    highlighting blocks only when you explicitly set the language.
-
-* **`css_class`**:
-    Set CSS class name for the wrapper `<div>` tag. Defaults to
-    `codehilite`.
-
-* **`pygments_style`**:
-    Pygments HTML Formatter Style (`ColorScheme`). Defaults to `default`.
-
-    !!! Note
-        This is useful only when `noclasses` is set to `True`, otherwise the
-        CSS styles must be provided by the end user.
-
-* **`noclasses`**:
-    Use inline styles instead of CSS classes. Defaults to `False`.
-
-* **`use_pygments`**:
-    Defaults to `True`. Set to `False` to disable the use of Pygments.
-    If a language is defined for a code block, it will be assigned to the
-    `<code>` tag as a class in the manner suggested by the [HTML5 spec][spec]
-    (alternate output will not be entertained) and might be used by a JavaScript
-    library in the browser to highlight the code block.
-
-[spec]: http://www.w3.org/TR/html5/text-level-semantics.html#the-code-element
diff --git a/docs/extensions/definition_lists.txt b/docs/extensions/definition_lists.txt
deleted file mode 100644
index d7003e29a..000000000
--- a/docs/extensions/definition_lists.txt
+++ /dev/null
@@ -1,53 +0,0 @@
-title:      Definition Lists Extension
-prev_title: Attribute Lists Extension
-prev_url:   attr_list.html
-next_title: Fenced Code Blocks Extension
-next_url:   fenced_code_blocks.html
-
-Definition Lists
-================
-
-Summary
--------
-
-The Definition Lists extension adds the ability to create definition lists in
-Markdown documents.
-
-This extension is included in the standard Markdown library.
-
-Syntax
-------
-
-Definition lists are defined using the syntax established in 
-[PHP Markdown Extra][php].
-
-[php]: http://www.michelf.com/projects/php-markdown/extra/#def-list
-
-Thus, the following text (taken from the above referenced PHP documentation):
-
-    Apple
-    :   Pomaceous fruit of plants of the genus Malus in 
-        the family Rosaceae.
-
-    Orange
-    :   The fruit of an evergreen tree of the genus Citrus.
-
-will be rendered as:
-
-    <dl>
-    <dt>Apple</dt>
-    <dd>Pomaceous fruit of plants of the genus Malus in 
-    the family Rosaceae.</dd>
-
-    <dt>Orange</dt>
-    <dd>The fruit of an evergreen tree of the genus Citrus.</dd>
-    </dl>
-
-
-Usage
------
-
-See [Extensions](index.html) for general extension usage, specify `markdown.extensions.def_list`
-as the name of the extension.
-
-This extension does not accept any special configuration options.
diff --git a/docs/extensions/extra.txt b/docs/extensions/extra.txt
deleted file mode 100644
index 5347d81d1..000000000
--- a/docs/extensions/extra.txt
+++ /dev/null
@@ -1,147 +0,0 @@
-title:      Extra Extension
-prev_title: Extensions
-prev_url:   index.html
-next_title: Abbreviations Extension
-next_url:   abbreviations.html
-
-Python-Markdown Extra
-=====================
-
-Summary
--------
-
-A compilation of various Python-Markdown extensions that (mostly) imitates
-[PHP Markdown Extra](http://michelf.com/projects/php-markdown/extra/).
-
-The supported extensions include:
-
-* [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)
-
-See each individual extension for syntax documentation. Extra and all its 
-supported extensions are included in the standard Markdown library.
-
-Usage
------
-
-From the Python interpreter:
-
-    >>> import markdown
-    >>> html = markdown.markdown(text, ['markdown.extensions.extra'])
-
-There may be [additional extensions](index.html) that are distributed with
-Python-Markdown that are not included here in Extra. The features 
-of those extensions are not part of PHP Markdown Extra, and 
-therefore, not part of Python-Markdown Extra. If you really would 
-like Extra to include additional extensions, we suggest creating 
-your own clone of Extra under a different name 
-(see the [Extension API](api.html)).  
-
-Markdown Inside HTML Blocks
----------------------------
-
-Unlike the other Extra features, this feature is built into the markdown core and
-is turned on when `markdown.extensions.extra` is enabled.
-
-The content of any raw HTML block element can be Markdown-formatted simply by
-adding a `markdown` attribute to the opening tag. The markdown attribute will be
-stripped from the output, but all other attributes will be preserved.
-
-If the markdown value is set to `1` (recommended) or any value other than `span`
-or `block`, the default behavior will be executed: `p`,`h[1-6]`,`li`,`dd`,`dt`,
-`td`,`th`,`legend`, and `address` elements skip block parsing while others do not.
-If the default is overridden by a value of `span`, *block parsing will be skipped*
-regardless of tag. If the default is overridden by a value of `block`,
-*block parsing will occur* regardless of tag.
-
-#### Simple Example:
-```
-This is *true* markdown text.
-
-<div markdown="1">
-This is *true* markdown text.
-</div>
-```
-#### Result:
-```
-<p>This is <em>true</em> markdown text.</p>
-<div>
-<p>This is <em>true</em> markdown text.</p>
-</div>
-```
-
-### Nested Markdown Inside HTML Blocks
-Nested elements are more sensitive and must be used cautiously. To avoid
-unexpected results:
-
-* Only nest elements within block mode elements.
-* Follow the closing tag of inner elements with a blank line.
-* Only have one level of nesting.
-
-#### Complex Example:
-```
-<div markdown="1" name="Example">
-
-The text of the `Example` element.
-
-<div markdown="1" name="DefaultBlockMode">
-This text gets wrapped in `p` tags.
-</div>
-
-The tail of the `DefaultBlockMode` subelement.
-
-<p markdown="1" name="DefaultSpanMode">
-This text *is not* wrapped in additional `p` tags.
-</p>
-
-The tail of the `DefaultSpanMode` subelement.
-
-<div markdown="span" name="SpanModeOverride">
-This `div` block is not wrapped in paragraph tags.
-Note: Subelements are not required to have tail text.
-</div>
-
-<p markdown="block" name="BlockModeOverride">
-This `p` block *is* foolishly wrapped in further paragraph tags.
-</p>
-
-The tail of the `BlockModeOverride` subelement.
-
-<div name="RawHtml">
-Raw HTML blocks may also be nested.
-</div>
-
-</div>
-
-This text is after the markdown in HTML.
-```
-#### Result:
-```
-<div name="Example">
-<p>The text of the <code>Example</code> element.</p>
-<div name="DefaultBlockMode">
-<p>This text gets wrapped in <code>p</code> tags.</p>
-</div>
-<p>The tail of the <code>DefaultBlockMode</code> subelement.</p>
-<p name="DefaultSpanMode">
-This text <em>is not</em> wrapped in additional <code>p</code> tags.</p>
-<p>The tail of the <code>DefaultSpanMode</code> subelement.</p>
-<div name="SpanModeOverride">
-This <code>div</code> block is not wrapped in paragraph tags.
-Note: Subelements are not required to have tail text.</div>
-<p name="BlockModeOverride">
-<p>This <code>p</code> block <em>is</em> foolishly wrapped in further paragraph tags.</p>
-</p>
-<p>The tail of the <code>BlockModeOverride</code> subelement.</p>
-<div name="RawHtml">
-Raw HTML blocks may also be nested.
-</div>
-
-</div>
-<p>This text is after the markdown in HTML.</p>
-```
diff --git a/docs/extensions/fenced_code_blocks.txt b/docs/extensions/fenced_code_blocks.txt
deleted file mode 100644
index 982f5d4f3..000000000
--- a/docs/extensions/fenced_code_blocks.txt
+++ /dev/null
@@ -1,112 +0,0 @@
-title:      Fenced Code Blocks Extension
-prev_title: Definition Lists Extension
-prev_url:   definition_lists.html
-next_title: Footnotes Extension
-next_url:   footnotes.html
-
-Fenced Code Blocks
-==================
-
-Summary
--------
-
-The Fenced Code Blocks extension adds a secondary way to define code blocks,
-which overcomes a few limitations of the indented code blocks.
-
-This extension is included in the standard Markdown library.
-
-Syntax
-------
-
-Fenced Code Blocks are defined using the syntax established in 
-[PHP Markdown Extra][php].
-
-[php]: http://www.michelf.com/projects/php-markdown/extra/#fenced-code-blocks
-
-Thus, the following text (taken from the above referenced PHP documentation):
-
-    This is a paragraph introducing:
-
-    ~~~~~~~~~~~~~~~~~~~~
-    a one-line code block
-    ~~~~~~~~~~~~~~~~~~~~
-
-Fenced code blocks can have a blank line as the first  and/or last line of a 
-code block and they can also come immediately after a list item without becoming
-part of the list.
-
-!!! warning
-
-    Fenced Code Blocks are only supported at the document root level.
-    Therefore, they cannot be nested inside lists or blockquotes.
-
-### Language ###
-
-In addition to PHP Extra's syntax, you can define the language of the code 
-block for use by syntax highlighters etc. The language will be assigned as a 
-class attribute of the ``<code>`` element in the output. Therefore, you should 
-define the language as you would a CSS class - ``.language``. For consistency 
-with other markdown syntax, the language can *optionally* be wrapped in curly 
-brackets:
-
-    ~~~~{.python}
-    # python code
-    ~~~~
-
-    ~~~~.html
-    <p>HTML Document</p>
-    ~~~~
-
-The above will output:
-
-    <pre><code class="python"># python code
-    </code></pre>
-    
-    <pre><code class="html">&lt;p&gt;HTML Document&lt;/p&gt;
-    </code></pre>
-
-[GitHub][]'s backtick (`\``) syntax is also supported:
-
-    ```python
-    # more python code
-    ```
-
-[GitHub]: http://github.github.com/github-flavored-markdown/
-
-### Emphasized Lines ###
-
-If you would like to have your fenced code blocks highlighted with the 
-[CodeHilite][] extension, simply enable that extension (remember that
-[Pygments][] is its dependency) and the language of your fenced code blocks
-will be passed in and highlighted appropriately.
-
-Similar to the [colon][] syntax of the CodeHilite extension, fenced code blocks
-can also have emphasized certain lines of code.
-
-The lines can be specified with PHP Extra's syntax:
-
-    ~~~~{.python hl_lines="1 3"}
-    # This line is emphasized
-    # This line isn't
-    # This line is emphasized
-    ~~~~
-
-... or with GitHub's:
-
-    ```python hl_lines="1 3"
-    # This line is emphasized
-    # This line isn't
-    # This line is emphasized
-    ```
-
-[CodeHilite]: code_hilite.html
-[Pygments]: http://pygments.org/
-[colon]: code_hilite.html#colons
-
-Usage
------
-
-See [Extensions](index.html) for general extension usage, specify `markdown.extensions.fenced_code`
-as the name of the extension.
-
-This extension does not accept any special configuration options.
diff --git a/docs/extensions/footnotes.txt b/docs/extensions/footnotes.txt
deleted file mode 100644
index e9b0451f8..000000000
--- a/docs/extensions/footnotes.txt
+++ /dev/null
@@ -1,88 +0,0 @@
-title:      Footnotes Extension
-prev_title: Fenced Code Blocks Extension
-prev_url:   fenced_code_blocks.html
-next_title: Tables Extension
-next_url:   tables.html
-
-Footnotes
-=========
-
-Summary
--------
-
-The Footnotes extension adds syntax for defining footnotes in Markdown
-documents.
-
-This extension is included in the standard Markdown library.
-
-Syntax
-------
-
-Python-Markdown's Footnote syntax follows the generally accepted syntax of the 
-Markdown community at large and almost exactly matches [PHP Markdown Extra][]'s
-implementation of footnotes. The only differences involve a few subtleties in 
-the output.
-
-[PHP Markdown Extra]: http://michelf.com/projects/php-markdown/extra/#footnotes
-
-Example:
-
-    Footnotes[^1] have a label[^@#$%] and the footnote's content.
-
-    [^1]: This is a footnote content.
-    [^@#$%]: A footnote on the label: "@#$%".
-
-A footnote label must start with a caret `^` and may contain any inline text 
-(including spaces) between a set of square brackets `[]`. Only the first
-caret has any special meaning.
-
-A footnote content must start with the label followed by a colon and at least 
-one space. The label used to define the content must exactly match the label used
-in the body (including capitalization and white space). The content would then 
-follow the label either on the same line or on the next line. The content may 
-contain multiple lines, paragraphs, code blocks, blockquotes and most any other 
-markdown syntax. The additional lines must be indented one level (four spaces or 
-one tab). 
-
-When working with multiple blocks, it may be helpful to start the content on a 
-separate line from the label which defines the content. This way the entire block 
-is indented consistently and any errors are more easily discernible by the author.
-
-    [^1]: 
-        The first paragraph of the definition.
-
-        Paragraph two of the definition.
-
-        > A blockquote with
-        > multiple lines.
-
-            a code block
-
-        A final paragraph.
-
-
-Usage
------
-
-See [Extensions](index.html) for general extension usage, specify `markdown.extensions.footnotes`
-as the name of the extension.
-
-See the [Library Reference](../reference.html#extensions) for information about
-configuring extensions.
-
-The following options are provided to configure the output:
-
-* **`PLACE_MARKER`**:
-    A text string used to mark the position where the footnotes are rendered.
-    Defaults to `///Footnotes Go Here///`.
-
-    If the place marker text is not found in the document, the footnote
-    definitions are placed at the end of the resulting HTML document.
-
-* **`UNIQUE_IDS`**:
-    Whether to avoid collisions across multiple calls to `reset()`. Defaults to
-    `False`.
-    
-* **`BACKLINK_TEXT`**:
-    The text string that links from the footnote definition back to the position
-    in the document. Defaults to `&#8617;`.
diff --git a/docs/extensions/header_id.txt b/docs/extensions/header_id.txt
deleted file mode 100644
index 5557b1b0f..000000000
--- a/docs/extensions/header_id.txt
+++ /dev/null
@@ -1,126 +0,0 @@
-title:      HeaderId Extension
-prev_title: CodeHilite Extension
-prev_url:   code_hilite.html
-next_title: Meta-Data Extension
-next_url:   meta_data.html
-
-HeaderId
-========
-
-Summary
--------
-
-The HeaderId extension automatically generates `id` attributes for the header
-elements (`h1`-`h6`) in the resulting HTML document.
-
-This extension is included in the standard Markdown library.
-
-!!! warning
-    This extension is **Pending Deprecation**. The [Table of Contents][toc]
-    Extension should be used instead, which offers most the features of this
-    extension and more.
-    
-[toc]: toc.html
-
-Syntax
-------
-
-By default, all headers will automatically have unique `id` attributes 
-generated based upon the text of the header (see below to turn this off). 
-Note this example, in which all three headers would have the same `id`:
-
-    #Header
-    #Header
-    #Header
-
-Results in:
-
-    <h1 id="header">Header</h1>
-    <h1 id="header_1">Header</h1>
-    <h1 id="header_2">Header</h1>
-
-Usage
------
-
-See [Extensions](index.html) for general extension usage, specify `markdown.extensions.headerid`
-as the name of the extension.
-
-See the [Library Reference](../reference.html#extensions) for information about
-configuring extensions.
-
-The following options are provided to configure the output:
-
-* **`level`**: Base level for headers.
-
-    Default: `1`
-
-    The `level` setting allows you to automatically adjust the header levels to
-    fit within the hierarchy of your HTML templates. For example, suppose the 
-    markdown text for a page should not contain any headers higher than level 3
-    (`<h3>`). The following will accomplish that:
-
-        >>>  text = '''
-        ... #Some Header
-        ... ## Next Level'''
-        >>> from markdown.extensions.headerid import HeaderIdExtension
-        >>> html = markdown.markdown(text, extensions=[HeaderIdExtension(level=3)])
-        >>> print html
-        <h3 id="some_header">Some Header</h3>
-        <h4 id="next_level">Next Level</h4>'
-
-* **`forceid`**: Force all headers to have an id.
-
-    Default: `True`
-
-    The `forceid` setting turns on or off the automatically generated ids for 
-    headers that do not have one explicitly defined (using the
-    [Attribute List](attr_list.html) extension).
-
-        >>> text = '''
-        ... # Some Header
-        ... # Header with ID # { #foo }'''
-        >>> html = markdown.markdown(text,
-                                     extensions=['markdown.extensions.attr_list',
-                                                 HeaderIdExtension(forceid=False)])
-        >>> print html
-        <h1>Some Header</h1>
-        <h1 id="foo">Header with ID</h1>
-
-* **`separator`**: Word separator. Character which replaces white space in id.
-
-    Default: `-`
-
-* **`slugify`**: Callable to generate anchors.
-
-    Default: `markdown.extensions.headerid.slugify`
-
-    If you would like to use a different algorithm to define the ids, you can
-    pass in a callable which takes two arguments:
-
-    * `value`: The string to slugify.
-    * `separator`: The Word Separator.
-
-Using with Meta-Data
---------------------
-
-The HeaderId extension also supports the [Meta-Data](meta_data.html) extension.
-Please see the documentation for that extension for specifics. The supported 
-meta-data keywords are:
-
-* `header_level`
-* `header_forceid`
-
-When used, the meta-data will override the settings provided through the  
-`extension_configs` interface. 
-
-This document:
-
-    header_level: 2
-    header_forceid: Off
-
-    # A Header
-
-
-Will result in the following output:
-
-    <h2>A Header</h2>
diff --git a/docs/extensions/index.md b/docs/extensions/index.md
new file mode 100644
index 000000000..2e735728b
--- /dev/null
+++ b/docs/extensions/index.md
@@ -0,0 +1,85 @@
+title:      Extensions
+prev_title: Command Line
+prev_url:   ../cli.html
+next_title: Extra Extension
+next_url:   extra.html
+
+
+Available Extensions
+====================
+
+Python Markdown offers a flexible extension mechanism, which makes it possible
+to change and/or extend the behavior of the parser without having to edit the
+actual source files.
+
+To use an extension, pass it to markdown with the `extensions` keyword.
+
+    markdown.markdown(some_text, extensions=[MyExtension(), 'path.to.my.ext', 'markdown.extensions.footnotes'])
+
+See the [Library Reference](../reference.md#extensions) for more details.
+
+From the command line, specify an extension with the `-x` option. 
+
+    $ python -m markdown -x markdown.extensions.footnotes -x markdown.extensions.tables input.txt > output.html
+
+See the [Command Line docs](../cli.md) or use the `--help` option for more details.
+
+!!! seealso "See Also"
+    If you would like to write your own extensions, see the
+    [Extension API](api.md) for details.
+
+Officially Supported Extensions
+-------------------------------
+
+The extensions listed below are maintained and officially supported by
+the Python-Markdown team. However, no extensions ship with Python-Markdown
+by default. If you would like to use any of the extensions listed below,
+you will need to install each of them individually.  See the documentation
+for each extension for specifics in instalation and usage. 
+
+Extension                            | "Name"
+------------------------------------ | ---------------
+[Extra]                              | `extra`
+&nbsp; &nbsp; [Abbreviations][]      | `abbr`
+&nbsp; &nbsp; [Attribute Lists][]    | `attr_list`
+&nbsp; &nbsp; [Definition Lists][]   | `def_list`
+&nbsp; &nbsp; [Fenced Code Blocks][] | `fenced_code`
+&nbsp; &nbsp; [Footnotes][]          | `footnotes`
+&nbsp; &nbsp; [Tables][]             | `tables`
+&nbsp; &nbsp; [Smart Strong][]       | `smart_strong`
+[Admonition][]                       | `admonition`
+[CodeHilite][]                       | `codehilite`
+[Meta-Data]                          | `meta`
+[New Line to Break]                  | `nl2br`
+[Sane Lists]                         | `sane_lists`
+[SmartyPants]                        | `smarty`
+[Table of Contents]                  | `toc`
+[WikiLinks]                          | `wikilinks`
+
+[Extra]: https://github.com/Python-Markdown/mdx_extra
+[Abbreviations]: https://github.com/Python-Markdown/mdx_abbreviations
+[Attribute Lists]: https://github.com/Python-Markdown/mdx_attr_list
+[Definition Lists]: https://github.com/Python-Markdown/mdx_definition_lists
+[Fenced Code Blocks]: https://github.com/Python-Markdown/mdx_fenced_code_blocks
+[Footnotes]: https://github.com/Python-Markdown/mdx_footnotes
+[Tables]: https://github.com/Python-Markdown/mdx_tables
+[Smart Strong]: https://github.com/Python-Markdown/mdx_smart_strong
+[Admonition]: https://github.com/Python-Markdown/mdx_admonition
+[CodeHilite]: https://github.com/Python-Markdown/mdx_code_hilite
+[Meta-Data]: https://github.com/Python-Markdown/mdx_meta_data
+[New Line to Break]: https://github.com/Python-Markdown/mdx_nl2br
+[Sane Lists]: https://github.com/Python-Markdown/mdx_sane_lists
+[SmartyPants]: https://github.com/Python-Markdown/mdx_smarty
+[Table of Contents]: https://github.com/Python-Markdown/mdx_toc
+[WikiLinks]: https://github.com/Python-Markdown/mdx_wikilinks
+
+Third Party Extensions
+----------------------
+
+Various individuals and/or organizations have developed extensions which they
+have made available to the public. A [list of third party extensions][list]
+is maintained on the wiki for your convenience. The Python-Markdown team
+offers no official support for these extensions. Please see the developer of
+each extension for support.
+
+[list]: https://github.com/Python-Markdown/markdown/wiki/Third-Party-Extensions
diff --git a/docs/extensions/index.txt b/docs/extensions/index.txt
deleted file mode 100644
index 1052d1584..000000000
--- a/docs/extensions/index.txt
+++ /dev/null
@@ -1,87 +0,0 @@
-title:      Extensions
-prev_title: Command Line
-prev_url:   ../cli.html
-next_title: Extra Extension
-next_url:   extra.html
-
-
-Available Extensions
-====================
-
-Python Markdown offers a flexible extension mechanism, which makes it possible
-to change and/or extend the behavior of the parser without having to edit the
-actual source files.
-
-To use an extension, pass it to markdown with the `extensions` keyword.
-
-    markdown.markdown(some_text, extensions=[MyExtension(), 'path.to.my.ext', 'markdown.extensions.footnotes'])
-
-See the [Library Reference](../reference.html#extensions) for more details.
-
-From the command line, specify an extension with the `-x` option. 
-
-    $ python -m markdown -x markdown.extensions.footnotes -x markdown.extensions.tables input.txt > output.html
-
-See the [Command Line docs](../cli.html) or use the `--help` option for more details.
-
-!!! seealso "See Also"
-    If you would like to write your own extensions, see the
-    [Extension API](api.html) for details.
-
-Officially Supported Extensions
--------------------------------
-
-The extensions listed below are included with (at least) the most recent release
-and are officially supported by Python-Markdown. Any documentation is
-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 using the "name" listed in the second column below.
-
-Extension                            | "Name"
------------------------------------- | ---------------
-[Extra]                              | `markdown.extensions.extra`
-&nbsp; &nbsp; [Abbreviations][]      | `markdown.extensions.abbr`
-&nbsp; &nbsp; [Attribute Lists][]    | `markdown.extensions.attr_list`
-&nbsp; &nbsp; [Definition Lists][]   | `markdown.extensions.def_list`
-&nbsp; &nbsp; [Fenced Code Blocks][] | `markdown.extensions.fenced_code`
-&nbsp; &nbsp; [Footnotes][]          | `markdown.extensions.footnotes`
-&nbsp; &nbsp; [Tables][]             | `markdown.extensions.tables`
-&nbsp; &nbsp; [Smart Strong][]       | `markdown.extensions.smart_strong`
-[Admonition][]                       | `markdown.extensions.admonition`
-[CodeHilite][]                       | `markdown.extensions.codehilite`
-[HeaderId]                           | `markdown.extensions.headerid`
-[Meta-Data]                          | `markdown.extensions.meta`
-[New Line to Break]                  | `markdown.extensions.nl2br`
-[Sane Lists]                         | `markdown.extensions.sane_lists`
-[SmartyPants]                        | `markdown.extensions.smarty`
-[Table of Contents]                  | `markdown.extensions.toc`
-[WikiLinks]                          | `markdown.extensions.wikilinks`
-
-[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
-[HeaderId]: header_id.html
-[Meta-Data]: meta_data.html
-[New Line to Break]: nl2br.html
-[Sane Lists]: sane_lists.html
-[SmartyPants]: smarty.html
-[Table of Contents]: toc.html
-[WikiLinks]: wikilinks.html
-
-Third Party Extensions
-----------------------
-
-Various individuals and/or organizations have developed extensions which they
-have made available to the public. A [list of third party extensions][list]
-is maintained on the wiki for your convenience. The Python-Markdown team
-offers no official support for these extensions. Please see the developer of
-each extension for support.
-
-[list]: https://github.com/waylan/Python-Markdown/wiki/Third-Party-Extensions
diff --git a/docs/extensions/meta_data.txt b/docs/extensions/meta_data.txt
deleted file mode 100644
index 4a4fe62ac..000000000
--- a/docs/extensions/meta_data.txt
+++ /dev/null
@@ -1,111 +0,0 @@
-title:      Meta-Data Extension
-prev_title: HeaderId Extension
-prev_url:   header_id.html
-next_title: New Line to Break Extension
-next_url:   nl2br.html
-
-Meta-Data
-=========
-
-Summary
--------
-
-The Meta-Data extension adds a syntax for defining meta-data about a document.
-It is inspired by and follows the syntax of [MultiMarkdown][]. Currently,
-this extension does not use the meta-data in any way, but simply provides it as
-a `Meta` attribute of a Markdown instance for use by other extensions or
-directly by your python code.
-
-This extension is included in the standard Markdown library.
-
-[MultiMarkdown]: http://fletcherpenney.net/MultiMarkdown_Syntax_Guide#metadata
-
-Syntax
-------
-
-Meta-data consists of a series of keywords and values defined at the beginning 
-of a markdown document like this:
-
-    Title:   My Document
-    Summary: A brief description of my document.
-    Authors: Waylan Limberg
-             John Doe
-    Date:    October 2, 2007
-    blank-value: 
-    base_url: http://example.com
-
-    This is the first paragraph of the document.
-
-The keywords are case-insensitive and may consist of letters, numbers, 
-underscores and dashes and must end with a colon. The values consist of 
-anything following the colon on the line and may even be blank.
-
-If a line is indented by 4 or more spaces, that line is assumed to be an
-additional line of the value for the previous keyword. A keyword may have as
-many lines as desired. 
-
-The first blank line ends all meta-data for the document. Therefore, the first
-line of a document must not be blank.
-
-Alternatively, You may use YAML style deliminators to mark the start and/or end
-of your meta-data. When doing so, the first line of your document must be `---`.
-The meta-data ends at the first blank line or the first line containing an end
-deliminator (either `---` or `...`), whichever comes first. Even though YAML
-deliminators are supported, meta-data is not parsed as YAML.
-
-All meta-data is stripped from the document prior to any further processing
-by Markdown.
-
-Usage
------
-
-See [Extensions](index.html) for general extension usage, specify `markdown.extensions.meta`
-as the name of the extension.
-
-Accessing the Meta-Data
------------------------
-
-The meta-data is made available as a python Dict in the `Meta` attribute of an 
-instance of the Markdown class. For example, using the above document:
-
-    >>> md = markdown.Markdown(extensions = ['markdown.extensions.meta'])
-    >>> html = md.convert(text)
-    >>> # Meta-data has been stripped from output
-    >>> print html
-    <p>This is the first paragraph of the document.</p>
-
-    >>> # View meta-data
-    >>> print md.Meta
-    {
-    'title' : ['My Document'],
-    'summary' : ['A brief description of my document.'],
-    'authors' : ['Waylan Limberg', 'John Doe'],
-    'date' : ['October 2, 2007'],
-    'blank-value' : [''],
-    'base_url' : ['http://example.com']
-    }
-
-Note that the keys are all lowercase and the values consist of a list of 
-strings where each item is one line for that key. This way, one could preserve 
-line breaks if desired. Or the items could be joined where appropriate. No 
-assumptions are made regarding the data. It is simply passed as found to the 
-`Meta` attribute.
-
-Perhaps the meta-data could be passed into a template system, or used by
-various Markdown extensions. The possibilities are left to the imagination of
-the developer.
-
-Compatible Extensions
----------------------
-
-The following extensions are currently known to work with the Meta-Data 
-extension. The keywords they are known to support are also listed.
-
-* [HeaderId](header_id.html)
-    * `header_level`
-    * `header_forceid`
-* [WikiLinks](wikilinks.html)
-    * `wiki_base_url`
-    * `wiki_end_url`
-    * `wiki_html_class`
-
diff --git a/docs/extensions/nl2br.txt b/docs/extensions/nl2br.txt
deleted file mode 100644
index 8ba27c8f9..000000000
--- a/docs/extensions/nl2br.txt
+++ /dev/null
@@ -1,37 +0,0 @@
-title:      New Line to Break Extension
-prev_title: Meta-Data Extension
-prev_url:   meta_data.html
-next_title: Sane Lists Extension
-next_url:   sane_lists.html
-
-New-Line-to-Break Extension
-===========================
-
-Summary
--------
-
-The New-Line-to-Break (`nl2b`) Extension will cause newlines to be treated as hard breaks; like
-StackOverflow and [GitHub][] flavored Markdown do.
-
-[Github]: http://github.github.com/github-flavored-markdown/
-
-Example
--------
-
-    >>> import markdown
-    >>> text = """
-    ... Line 1
-    ... Line 2
-    ... """
-    >>> html = markdown.markdown(text, extensions=['markdown.extensions.nl2br'])
-    >>> print html
-    <p>Line 1<br />
-    Line 2</p>
-
-Usage
------
-
-See [Extensions](index.html) for general extension usage, specify `markdown.extensions.nl2br`
-as the name of the extension.
-
-This extension does not accept any special configuration options.
diff --git a/docs/extensions/sane_lists.txt b/docs/extensions/sane_lists.txt
deleted file mode 100644
index 8f713d0a7..000000000
--- a/docs/extensions/sane_lists.txt
+++ /dev/null
@@ -1,73 +0,0 @@
-title:      Sane Lists Extension
-prev_title: New Line to Break Extension
-prev_url:   nl2br.html
-next_title: SmartyPants Extension
-next_url:   smarty.html
-
-Sane Lists
-==========
-
-Summary
--------
-
-The Sane Lists extension alters the behavior of the Markdown List syntax
-to be less surprising.
-
-This extension is included in the standard Markdown library.
-
-Syntax
-------
-
-Sane Lists do not allow the mixing of list types. In other words, an ordered
-list will not continue when an unordered list item is encountered and
-vice versa. For example:
-
-    1. Ordered item 1
-    2. Ordered item 2
-
-    * Unordered item 1
-    * Unordered item 2
-
-will result in the following output:
-
-    <ol>
-      <li>Ordered item 1</li>
-      <li>Ordered item 2</li>
-    </ol>
-
-    <ul>
-      <li>Unordered item 1</li>
-      <li>Unordered item 2</li>
-    </ul>
-
-Whereas the default Markdown behavior would be to generate an unordered list.
-
-Note that, unlike the default Markdown behavior, if a blank line is not
-included between list items, the different list type is ignored completely. 
-This corresponds to the behavior of paragraphs. For example:
-
-    A Paragraph.
-    * Not a list item.
-
-    1. Ordered list item.
-    * Not a separate list item.
-
-With this extension the above will result in the following output:
-
-    <p>A Paragraph.
-    * Not a list item.</p>
-
-    <ol>
-      <li>Ordered list item.
-      * Not a separate list item.</li>
-    </ol>
-
-In all other ways, Sane Lists should behave as normal Markdown lists.
-
-Usage
------
-
-See [Extensions](index.html) for general extension usage, specify `markdown.extensions.sane_lists`
-as the name of the extension.
-
-This extension does not accept any special configuration options.
diff --git a/docs/extensions/smart_strong.txt b/docs/extensions/smart_strong.txt
deleted file mode 100644
index e2e545883..000000000
--- a/docs/extensions/smart_strong.txt
+++ /dev/null
@@ -1,41 +0,0 @@
-title:      Smart Strong Extension
-prev_title: Tables Extension
-prev_url:   tables.html
-next_title: Admonition Extension
-next_url:   admonition.html
-
-Smart_Strong
-============
-
-Summary
--------
-
-The Smart_Strong extension adds smarter handling of double underscores within
-words. This does for double underscores what [smart_emphasis][] does for single
-underscores.
-
-The Smart_Strong extension is included in the standard Markdown library.
-
-[smart_emphasis]: ../reference.html#smart_emphasis
-
-Example
--------
-
-    >>> import markdown
-    >>> markdown.markdown('Text with double__underscore__words.', \
-                          extensions=['markdown.extensions.smart_strong'])
-    u'<p>Text with double__underscore__words.</p>'
-    >>> markdown.markdown('__Strong__ still works.', \
-                          extensions=['markdown.extensions.smart_strong'])
-    u'<p><strong>Strong</strong> still works.</p>'
-    >>> markdown.markdown('__this__works__too__.', \
-                          extensions=['markdown.extensions.smart_strong'])
-    u'<p><strong>this__works__too</strong>.</p>'
-
-Usage
------
-
-See [Extensions](index.html) for general extension usage, specify `markdown.extensions.smart_strong`
-as the name of the extension.
-
-This extension does not accept any special configuration options.
diff --git a/docs/extensions/smarty.txt b/docs/extensions/smarty.txt
deleted file mode 100644
index 247658ab2..000000000
--- a/docs/extensions/smarty.txt
+++ /dev/null
@@ -1,80 +0,0 @@
-title: SmartyPants Extension
-prev_title: Sane Lists Extension
-prev_url:   sane_lists.html
-next_title: Table of Contents Extension
-next_url:   toc.html
-
-SmartyPants
-===========
-
-Summary
--------
-
-The SmartyPants extension converts ASCII dashes, quotes and ellipses to
-their HTML entity equivalents.
-
-ASCII symbol | Replacements    | HTML Entities       | Substitution Keys
------------- | --------------- | ------------------- | ----------------------------------------
-`'`          | &lsquo; &rsquo; | `&lsquo;` `&rsquo;` | `'left-single-quote'`, `'right-single-quote'`
-`"`          | &ldquo; &rdquo; | `&ldquo;` `&rdquo;` | `'left-double-quote'`, `'right-double-quote'`
-`<< >>`      | &laquo; &raquo; | `&laquo;` `&raquo;` | `'left-angle-quote'`, `'right-angle-quote'`
-`...`        | &hellip;        | `&hellip;`          | `'ellipsis'`
-`--`         | &ndash;         | `&ndash;`           | `'ndash'`
-`---`        | &mdash;         | `&mdash;`           | `'mdash'`
-
-Using the configuration option 'substitutions' you can overwrite the
-default substitutions. Just pass a dict mapping (a subset of) the 
-keys to the substitution strings.
-    
-For example, one might use the following configuration to get correct quotes for 
-the German language:
-
-    extension_configs = {
-        'markdown.extensions.smarty': {
-            'substitutions': {
-                'left-single-quote': '&sbquo;', # sb is not a typo!
-                'right-single-quote': '&lsquo;',
-                'left-double-quote': '&bdquo;',
-                'right-double-quote': '&ldquo;'
-            }
-        }
-    }
-
-!!! note
-    This extension re-implements the Python [SmartyPants] 
-    library by integrating it into the markdown parser.
-    While this does not provide any additional features,
-    it does offer a few advantages. Notably, it will not 
-    try to work on highlighted code blocks (using the 
-    [CodeHilite] Extension) like the third party library 
-    has been known to do.
-    
-[SmartyPants]: http://pythonhosted.org/smartypants/
-[CodeHilite]: code_hilite.html
-
-Usage
------
-
-See [Extensions](index.html) for general extension usage, specify 
-`markdown.extensions.smarty` as the name of the extension.
-
-See the [Library Reference](../reference.html#extensions) for information about
-configuring extensions.
-
-The following options are provided to configure the output:
-
-Option                | Default value | Description
-------                | ------------- | -----------
-`smart_dashes`        | `True`        | whether to convert dashes
-`smart_quotes`        | `True`        | whether to convert straight quotes
-`smart_angled_quotes` | `False`       | whether to convert angled quotes
-`smart_ellipses`      | `True`        | whether to convert ellipses
-`substitutions`       | `{}`          | overwrite default substitutions
-
-Further reading
----------------
-
-SmartyPants extension is based on the original SmartyPants implementation
-by John Gruber. Please read it's [documentation][1] for details.
-
-[1]: http://daringfireball.net/projects/smartypants/
diff --git a/docs/extensions/tables.txt b/docs/extensions/tables.txt
deleted file mode 100644
index 759772a02..000000000
--- a/docs/extensions/tables.txt
+++ /dev/null
@@ -1,58 +0,0 @@
-title:      Tables Extension
-prev_title: Footnotes Extension
-prev_url:   footnotes.html
-next_title: Smart Strong Extension
-next_url:   smart_strong.html
-
-Tables
-======
-
-Summary
--------
-
-The Tables extension adds the ability to create tables in Markdown documents.
-
-This extension is included in the standard Markdown library.
-
-Syntax
-------
-
-Tables are defined using the syntax established in [PHP Markdown Extra][php].
-
-[php]: http://www.michelf.com/projects/php-markdown/extra/#table
-
-Thus, the following text (taken from the above referenced PHP documentation):
-
-    First Header  | Second Header
-    ------------- | -------------
-    Content Cell  | Content Cell
-    Content Cell  | Content Cell
-
-will be rendered as:
-
-    <table>
-      <thead>
-        <tr>
-          <th>First Header</th>
-          <th>Second Header</th>
-        </tr>
-      </thead>
-      <tbody>
-        <tr>
-          <td>Content Cell</td>
-          <td>Content Cell</td>
-        </tr>
-        <tr>
-          <td>Content Cell</td>
-          <td>Content Cell</td>
-        </tr>
-      </tbody>
-    </table>
-
-Usage
------
-
-See [Extensions](index.html) for general extension usage, specify `markdown.extensions.tables`
-as the name of the extension.
-
-This extension does not accept any special configuration options.
diff --git a/docs/extensions/toc.txt b/docs/extensions/toc.txt
deleted file mode 100644
index 2cd1fa15f..000000000
--- a/docs/extensions/toc.txt
+++ /dev/null
@@ -1,127 +0,0 @@
-title:      Table of Contents Extension
-prev_title: SmartyPants Extension
-prev_url:   smarty.html
-next_title: WikiLinks Extension
-next_url:   wikilinks.html
-
-Table of Contents
-=================
-
-Summary
--------
-
-The Table of Contents extension generates a Table of Contents from a Markdown
-document and adds it into the resulting HTML document.
-
-This extension is included in the standard Markdown library.
-
-Syntax
-------
-
-By default, all headers will automatically have unique `id` attributes 
-generated based upon the text of the header. Note this example, in which all
-three headers would have the same `id`:
-
-    #Header
-    #Header
-    #Header
-
-Results in:
-
-    <h1 id="header">Header</h1>
-    <h1 id="header_1">Header</h1>
-    <h1 id="header_2">Header</h1>
-
-Place a marker in the document where you would like the Table of Contents to
-appear. Then, a nested list of all the headers in the document will replace the
-marker. The marker defaults to `[TOC]` so the following document:
-
-    [TOC]
-
-    # Header 1
-
-    ## Header 2
-
-would generate the following output:
-
-    <div class="toc">
-      <ul>
-        <li><a href="#header-1">Header 1</a></li>
-          <ul>
-            <li><a href="#header-2">Header 2</a></li>
-          </ul>
-      </ul>
-    </div>
-    <h1 id="header-1">Header 1</h1>
-    <h1 id="header-2">Header 2</h1>
-
-Regardless of whether a `marker` is found in the document (or disabled), the Table of
-Contents is available as an attribute (`toc`) on the Markdown class. This allows
-one to insert the Table of Contents elsewhere in their page template. For example:
-
-    >>> md = markdown.Markdown(extensions=['markdown.extensions.toc'])
-    >>> html = md.convert(text)
-    >>> page = render_some_template(context={'body': html, 'toc': md.toc})
-
-Usage
------
-
-See [Extensions](index.html) for general extension usage, specify `markdown.extensions.toc`
-as the name of the extension.
-
-See the [Library Reference](../reference.html#extensions) for information about
-configuring extensions.
-
-The following options are provided to configure the output:
-
-* **`marker`**:
-    Text to find and replace with the Table of Contents. Defaults to `[TOC]`.
-    
-    Set to an empty string to disable searching for a marker, which may save some time,
-    especially on long documents.
-
-* **`title`**:
-    Title to insert in the Table of Contents' `<div>`. Defaults to `None`.
-
-* **`anchorlink`**:
-    Set to `True` to cause all headers to link to themselves. Default is `False`.
-
-* **`permalink`**:
-    Set to `True` or a string to generate permanent links at the end of each header.
-    Useful with Sphinx style sheets.
-    
-    When set to `True` the paragraph symbol (&para; or "`&para;`") is used as the link
-    text. When set to a string, the provided string is used as the link text.
-
-* **`baselevel`**:
-    Base level for headers. Defaults to `1`.
-
-    The `baselevel` setting allows the header levels to be automatically adjusted to
-    fit within the hierarchy of your HTML templates. For example, suppose the 
-    Markdown text for a page should not contain any headers higher than level 3
-    (`<h3>`). The following will accomplish that:
-
-        >>>  text = '''
-        ... #Some Header
-        ... ## Next Level'''
-        >>> from markdown.extensions.toc import TocExtension
-        >>> html = markdown.markdown(text, extensions=[TocExtension(baselevel=3)])
-        >>> print html
-        <h3 id="some_header">Some Header</h3>
-        <h4 id="next_level">Next Level</h4>'
-
-* **`slugify`**:
-    Callable to generate anchors.
-
-    Default: `markdown.extensions.headerid.slugify`
-
-    In order to use a different algorithm to define the id attributes, define  and
-    pass in a callable which takes the following two arguments:
-
-    * `value`: The string to slugify.
-    * `separator`: The Word Separator.
-
-    The callable must return a string appropriate for use in HTML `id` attributes.
-
-* **`separator`**:
-    Word separator. Character which replaces white space in id. Defaults to "`-`".
\ No newline at end of file
diff --git a/docs/extensions/wikilinks.txt b/docs/extensions/wikilinks.txt
deleted file mode 100644
index 948b957f8..000000000
--- a/docs/extensions/wikilinks.txt
+++ /dev/null
@@ -1,135 +0,0 @@
-title:      WikiLinks Extension
-prev_title: Table of Contents Extension
-prev_url:   toc.html
-next_title: Extension API
-next_url:   api.html
-
-WikiLinks
-=========
-
-Summary
--------
-
-The WikiLinks extension adds support for [WikiLinks][]. Specifically, any
-``[[bracketed]]`` word is converted to a link.
-
-This extension is included in the standard Markdown library.
-
-[WikiLinks]: http://en.wikipedia.org/wiki/Wikilink
-
-Syntax
-------
-
-A ``[[bracketed]]`` word is any combination of  upper or lower case letters,
-number, dashes, underscores and spaces surrounded by double brackets. Therefore
-
-    [[Bracketed]]
-
-would produce the following HTML:
-
-    <a href="/Bracketed/" class="wikilink">Bracketed</a>
-
-Note that WikiLinks are automatically assigned `class="wikilink"` making it
-easy to style WikiLinks differently from other links on a page if one so
-desires. See below for ways to alter the class.
-
-Also note that when a space is used, the space is converted to an underscore in
-the link but left as-is in the label. Perhaps an example would illustrate this
-best:
-
-    [[Wiki Link]]
-
-becomes
-
-    <a href="/Wiki_Link/" class="wikilink">Wiki Link</a>
-
-Usage
------
-
-See [Extensions](index.html) for general extension usage, specify `markdown.extensions.wikilinks`
-as the name of the extension.
-
-See the [Library Reference](../reference.html#extensions) for information about
-configuring extensions.
-
-The default behavior is to point each link to the document root of the current
-domain and close with a trailing slash. Additionally, each link is assigned to
-the HTML class `wikilink`.
-
-The following options are provided to change the default behavior:
-
-* **`base_url`**: String to append to beginning of URL.
-
-    Default: `'/'`
-
-* **`end_url`**: String to append to end of URL.
-
-    Default: `'/'`
-
-* **`html_class`**: CSS class. Leave blank for none.
-
-    Default: `'wikilink'`
-
-* **`build_url`**: Callable which formats the URL from its parts.
-
-### Examples ###
-
-For an example, let us suppose links should always point to the sub-directory
-`/wiki/` and end with `.html`
-
-    >>> from markdown.extensions.wikilinks import WikiLinkExtension
-	>>> html = markdown.markdown(text,
-    ...     extensions=[WikiLinkExtension(base_url='/wiki/', end_url='.html')]
-    ... )
-
-The above would result in the following link for `[[WikiLink]]`.
-
-    <a href="/wiki/WikiLink.html" class="wikilink">WikiLink</a>
-
-If you want to do more that just alter the base and/or end of the URL, you
-could also pass in a callable which must accept three arguments (``label``,
-``base``, and ``end``). The callable must return the URL in it's entirety.
-
-    >>> def my_url_builder(label, base, end):
-    ...    # do stuff
-    ...    return url
-	...
-    >>> html = markdown.markdown(text,
-    ...     extensions=[WikiLinkExtension(build_url=my_url_builder)],
-    ... )
-
-The option is also provided to change or remove the class attribute.
-
-    >>> html = markdown.markdown(text,
-    ...     extensions=[WikiLinkExtension(html_class='myclass')]
-    ... )
-
-Would cause all WikiLinks to be assigned to the class `myclass`.
-
-    <a href="/WikiLink/" class="myclass">WikiLink</a>
-
-Using with Meta-Data extension
-------------------------------
-
-The WikiLink extension also supports the [Meta-Data](meta_data.html) extension.
-Please see the documentation for that extension for specifics. The supported
-meta-data keywords are:
-
-* `wiki_base_url`
-* `wiki_end_url`
-* `wiki_html_class`
-
-When used, the meta-data will override the settings provided through the
-`extension_configs` interface.
-
-This document:
-
-    wiki_base_url: http://example.com/
-    wiki_end_url:  .html
-    wiki_html_class:
-
-    A [[WikiLink]] in the first paragraph.
-
-would result in the following output (notice the blank `wiki_html_class`):
-
-    <p>A <a href="http://example.com/WikiLink.html">WikiLink</a> in the first paragraph.</p>
diff --git a/docs/index.txt b/docs/index.md
similarity index 77%
rename from docs/index.txt
rename to docs/index.md
index bfe18182b..cf6fa0e46 100644
--- a/docs/index.txt
+++ b/docs/index.md
@@ -1,7 +1,4 @@
-next_url:   install.html
-next_title: Installation
-prev_title: Table of Contents
-prev_url:   siteindex.html
+title: Python-Markdown
 
 Python-Markdown
 ===============
@@ -13,7 +10,7 @@ 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.
+See the [installation instructions](install.md) to get started.
 
 Goals
 -----
@@ -27,7 +24,7 @@ The Python-Markdown project is developed with the following goals in mind:
   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 makes it possible
+* Provide an [Extension API](extensions/api.md) which makes it possible
   to change and/or extend the behavior of the parser.
 
 Features
@@ -38,26 +35,25 @@ features:
 
 * __International Input__
 
-    Python-Markdown will accept [input](reference.html#text) in any language 
+    Python-Markdown will accept [input](reference.md#text) in any language 
     supported by Unicode including bi-directional text. In fact the test suite 
     includes documents written in Russian and Arabic.
 
 * __Extensions__
 
-    Various [extensions](extensions/index.html) are provided (including 
-    [extra](extensions/extra.html)) to change and/or extend the base syntax. 
-    Additionally, a public [Extension API](extensions/api.html) is available 
-    to write your own extensions.
+    Various [extensions](extensions/index.md) are avaiable to change and/or
+    extend the base syntax. Additionally, a public [Extension API](extensions/api.md)
+    is available to write your own extensions.
 
 * __Output Formats__
 
-    Python-Markdown can output documents in HTML4, XHTML and HTML5. See the
-    [Library Reference](reference.html#output_format) for details.
+    Python-Markdown can output documents with either HTML or XHTML styel tags. See the
+    [Library Reference](reference.md#output_format) for details.
 
 * __Command Line Interface__
 
     In addition to being a Python Library, a 
-    [command line script](cli.html) is available for your convenience.
+    [command line script](cli.md) is available for your convenience.
 
 Differences
 -----------
@@ -74,8 +70,7 @@ are summarized below:
 
     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.
+    This can be disabled with an extension.
 
 * __Indentation/Tab Length__
 
@@ -91,7 +86,7 @@ are summarized below:
     be indented by at least four spaces (or one tab) for each level of nesting.
 
     In the event that one would prefer different behavior,
-    [tab_length](reference.html#tab_length) can be set to whatever length is 
+    [tab_length](reference.md#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 root level code blocks).
 
@@ -101,7 +96,7 @@ are summarized below:
     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)
+    foreseeable future. That said, the Sane List Extension
     is available to provide a less surprising behavior.
     
 
diff --git a/docs/install.txt b/docs/install.md
similarity index 88%
rename from docs/install.txt
rename to docs/install.md
index 19893986c..81b722205 100644
--- a/docs/install.txt
+++ b/docs/install.md
@@ -1,8 +1,4 @@
 title:      Installation
-prev_title: Summary
-prev_url:   index.html
-next_title: Library Reference
-next_url:   reference.html
 
 Installing Python-Markdown
 ==========================
@@ -19,7 +15,7 @@ or
 
     easy_install markdown
 
-That's it! You're ready to [use](reference.html) Python-Markdown. Enjoy!
+That's it! You're ready to [use](reference.md) Python-Markdown. Enjoy!
 
 Installing on Windows {: #windows }
 -----------------------------------
@@ -41,8 +37,8 @@ the Scripts directory is `C:\PythonXX\Scripts\` (were "XX" is the Python version
 number, i.e., "27"). Adjust the path according to your system and add to your 
 system path.
 
-Installing on *nix Systems {: #linux }
---------------------------------------
+Installing on \*nix Systems {: #linux }
+---------------------------------------
 
 From the command line do the following (where 2.x is the version number):
 
diff --git a/docs/reference.txt b/docs/reference.md
similarity index 52%
rename from docs/reference.txt
rename to docs/reference.md
index ac5c72496..a9b02b610 100644
--- a/docs/reference.txt
+++ b/docs/reference.md
@@ -31,11 +31,13 @@ instance of the `markdown.Markdown` class and pass multiple documents through
 it. If you do use a single instance though, make sure to call the `reset`
 method appropriately ([see below](#convert)).
 
-### `markdown.markdown (text [, **kwargs])` {: #markdown }
+### markdown.markdown(text [, **kwargs]) {: #markdown }
 
 The following options are available on the `markdown.markdown` function:
 
-* __`text`__{: #text } (required): The source Unicode string.
+__text__{: #text }
+
+:   The source Unicode string. (required)
 
     !!! note "Important"
         Python-Markdown expects **Unicode** as input (although
@@ -55,12 +57,14 @@ The following options are available on the `markdown.markdown` function:
             )
             output_file.write(html)
 
-* __`extensions`__{: #extensions }: A list of extensions.
+__extensions__{: #extensions }
+
+:   A list of extensions.
 
-    Python-Markdown provides an [API](extensions/api.html) for third parties to
+    Python-Markdown provides an [API](extensions/api.md) 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 
+    library. See the [extension documentation](extensions/index.md) for a 
     list of available extensions.
 
     The list of extensions may contain instances of extensions and/or strings 
@@ -86,7 +90,7 @@ The following options are available on the `markdown.markdown` function:
 
     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 
+    required. Therefore, to import the 'extra' extension, one could do 
     `extensions=['markdown.extensions.extra']`  
 
 	Additionally, a Class may be specified in the name. The class must be at the end of 
@@ -102,7 +106,7 @@ The following options are available on the `markdown.markdown` function:
 
 	!!! note
 		You should only need to specify the class name if more than one extension 
-		is defined	within the same module. The extensions that come with 
+		is defined within the same module. The extensions that come with 
 		Python-Markdown do *not* need to have the class name specified. However,
 		doing so will not effect the behavior of the parser.
 
@@ -111,11 +115,12 @@ The following options are available on the `markdown.markdown` function:
     [`extension_configs`](#extension_configs) keyword.
 
     !!! seealso "See Also"
-        See the documentation of the [Extension API](extensions/api.html) for 
+        See the documentation of the [Extension API](extensions/api.md) for 
         assistance in creating extensions.
 
-* __`extension_configs`__{: #extension_configs }: A dictionary of
-  configuration settings for extensions.
+__extension_configs__{: #extension_configs }
+
+:   A dictionary of configuration settings for extensions.
 
     Any configuration settings will only be passed to extensions loaded by name 
     (as a string). When loading extensions as class instances, pass the 
@@ -145,114 +150,20 @@ The following options are available on the `markdown.markdown` function:
     See the documentation specific to the extension you are using for help in 
     specifying configuration settings for that extension.
 
-* __`output_format`__{: #output_format }: Format of output. 
+__output_format__{: #output_format }:
+
+:   Format of output. 
 
     Supported formats are:
 
-    * `"xhtml1"`: Outputs XHTML 1.x. **Default**.
-    * `"xhtml5"`: Outputs XHTML style tags of HTML 5
-    * `"xhtml"`: Outputs latest supported version of XHTML (currently XHTML 1.1).
-    * `"html4"`: Outputs HTML 4
-    * `"html5"`: Outputs HTML style tags of HTML 5
-    * `"html"`: Outputs latest supported version of HTML (currently HTML 4).
+    * `"xhtml"`: Outputs XHTML style tags. **Default**.
+    * `"html"`: Outputs HTML style tags.
 
     The values can be in either lowercase or uppercase.
 
-    !!! warning
-        It is suggested that the more specific formats (`"xhtml1"`, `"html5"`, & 
-        `"html4"`) be used as the more general formats (`"xhtml"` or `"html"`) may 
-        change in the future if it makes sense at that time. 
-
-* __`safe_mode`__{: #safe_mode }: Disallow raw HTML.
-
-    !!! warning
-        "`safe_mode`" is deprecated and should not be used.
-
-        HTML sanitizers (like [Bleach]) provide a better solution for 
-        dealing with markdown text submitted by untrusted users. 
-
-            import markdown
-            import bleach
-            html = bleach.clean(markdown.markdown(untrusted_text))
-
-        See the [release notes] for more info.
-    
-[Bleach]: https://github.com/jsocol/bleach
-[release notes]: release-2.6.html
-    
-    The following values are accepted:
-
-    * `False` (Default): Raw HTML is passed through unaltered.
-
-    * `replace`: Replace all HTML blocks with the text assigned to 
-      `html_replacement_text` To maintain backward compatibility, setting 
-      `safe_mode=True` will have the same effect as `safe_mode='replace'`.   
-
-        To replace raw HTML with something other than the default, do:
-
-            md = markdown.Markdown(safe_mode='replace', 
-                               html_replacement_text='--RAW HTML NOT ALLOWED--')
-
-    * `remove`: All raw HTML will be completely stripped from the text with
-      no warning to the author.
-
-    * `escape`: All raw HTML will be escaped and included in the document.
-
-        For example, the following source:
-
-            Foo <b>bar</b>.
-
-        Will result in the following HTML:
-
-            <p>Foo &lt;b&gt;bar&lt;/b&gt;.</p>
-
-    !!! Note 
-        "safe_mode" also alters the default value for the 
-        [`enable_attributes`](#enable_attributes) option.
-    
-
-* __`html_replacement_text`__{: #html_replacement_text }: Text used when 
-  safe_mode is set to `replace`. Defaults to `[HTML_REMOVED]`.
-
-    !!! warning
-        "`html_replacement_text`" is deprecated and should not be used.
-        See the [release notes] for more info.
-
-* __`tab_length`__{: #tab_length }: Length of tabs in the source. Default: 4
-
-* __`enable_attributes`__{: #enable_attributes}: Enable the conversion of 
-  attributes. Defaults to `True`, unless [`safe_mode`](#safe_mode) is enabled, 
-  in which case the default is `False`.
-
-    !!! Note 
-        `safe_mode` only overrides the default. If `enable_attributes` 
-        is explicitly set, the explicit value is used regardless of `safe_mode`.
-        However, this could potentially allow an untrusted user to inject
-        JavaScript into your documents.
-
-* __`smart_emphasis`__{: #smart_emphasis }: Treat `_connected_words_` 
-  intelligently Default: True
-
-* __`lazy_ol`__{: #lazy_ol }: Ignore number of first item of ordered lists. 
-  Default: True
-
-    Given the following list:
-
-        4. Apples
-        5. Oranges
-        6. Pears
-
-    By default markdown will ignore the fact the the first line started 
-    with item number "4" and the HTML list will start with a number "1".
-    If `lazy_ol` is set to `False`, then markdown will output the following
-    HTML:
-
-        <ol>
-          <li start="4">Apples</li>
-          <li>Oranges</li>
-          <li>Pears</li>
-        </ol>
+__tab_length__{: #tab_length }
 
+:   Length of tabs in the source. Default: 4
 
 ### `markdown.markdownFromFile (**kwargs)` {: #markdownFromFile }
 
@@ -260,7 +171,9 @@ With a few exceptions, `markdown.markdownFromFile` accepts the same options as
 `markdown.markdown`. It does **not** accept a `text` (or Unicode) string. 
 Instead, it accepts the following required options:
 
-* __`input`__{: #input } (required): The source text file.
+__input__{: #input } (required)
+
+:   The source text file.
 
     `input` may be set to one of three options:
 
@@ -268,7 +181,9 @@ Instead, it accepts the following required options:
     * a readable file-like object,
     * or `None` (default) which will read from `stdin`.
 
-* __`output`__{: #output }: The target which output is written to.
+__output__{: #output }
+
+:   The target which output is written to.
 
     `output` may be set to one of three options:
 
@@ -276,9 +191,12 @@ Instead, it accepts the following required options:
     * a writable file-like object,
     * or `None` (default) which will write to `stdout`.
 
-* __`encoding`__{: #encoding }: The encoding of the source text file. Defaults 
-  to `"utf-8"`. The same encoding will always be used for input and output. 
-  The `xmlcharrefreplace` error handler is used when encoding the output.
+__encoding__{: #encoding }
+
+:   The encoding of the source text file.
+
+    Defaults to `"utf-8"`. The same encoding will always be used for input and output. 
+    The `xmlcharrefreplace` error handler is used when encoding the output.
 
     !!! Note 
         This is the only place that decoding and encoding of Unicode
@@ -286,44 +204,44 @@ Instead, it accepts the following required options:
         meet your specific needs, it is suggested that you write your own code
         to handle your encoding/decoding needs.
 
-### `markdown.Markdown ([**kwargs])` {: #Markdown }
+### markdown.Markdown([**kwargs]) {: #Markdown }
 
 The same options are available when initializing the `markdown.Markdown` class
 as on the [`markdown.markdown`](#markdown) function, except that the class does
 **not** accept a source text string on initialization. Rather, the source text
 string must be passed to one of two instance methods:
 
-* `Markdown.convert(source)`{: #convert }
+#### Markdown.convert(source) {: #convert }
 
-    The `source` text must meet the same requirements as the [`text`](#text) 
-    argument of the [`markdown.markdown`](#markdown) function.
+The `source` text must meet the same requirements as the [`text`](#text) 
+argument of the [`markdown.markdown`](#markdown) function.
 
-    You should also use this method if you want to process multiple strings
-    without creating a new instance of the class for each string.
+You should also use this method if you want to process multiple strings
+without creating a new instance of the class for each string.
 
-        md = markdown.Markdown()
-        html1 = md.convert(text1)
-        html2 = md.convert(text2)
+    md = markdown.Markdown()
+    html1 = md.convert(text1)
+    html2 = md.convert(text2)
 
-    Depending on which options and/or extensions are being used, the parser may
-    need its state reset between each call to `convert`, otherwise performance
-    can degrade drastically:
+Depending on which options and/or extensions are being used, the parser may
+need its state reset between each call to `convert`, otherwise performance
+can degrade drastically:
 
-        html1 = md.convert(text1)
-        md.reset()
-        html2 = md.convert(text2)
-    
-    To make this easier, you can also chain calls to `reset` together:
-    
-        html3 = md.reset().convert(text3)
+    html1 = md.convert(text1)
+    md.reset()
+    html2 = md.convert(text2)
+
+To make this easier, you can also chain calls to `reset` together:
+
+    html3 = md.reset().convert(text3)
 
 
-* `Markdown.convertFile(**kwargs)`{: #convertFile }
+#### Markdown.convertFile(**kwargs) {: #convertFile }
 
-    The arguments of this method are identical to the arguments of the same
-    name on the `markdown.markdownFromFile` function ([`input`](#input), 
-    [`output`](#output), and [`encoding`](#encoding)). As with the 
-    [`convert`](#convert) method, this method should be used to 
-    process multiple files without creating a new instance of the class for 
-    each document. State may need to be `reset` between each call to 
-    `convertFile` as is the case with `convert`.
+The arguments of this method are identical to the arguments of the same
+name on the `markdown.markdownFromFile` function ([`input`](#input), 
+[`output`](#output), and [`encoding`](#encoding)). As with the 
+[`convert`](#convert) method, this method should be used to 
+process multiple files without creating a new instance of the class for 
+each document. State may need to be `reset` between each call to 
+`convertFile` as is the case with `convert`.
diff --git a/docs/release-2.0.1.txt b/docs/release-2.0.1.txt
deleted file mode 100644
index c0b890a91..000000000
--- a/docs/release-2.0.1.txt
+++ /dev/null
@@ -1,22 +0,0 @@
-title:      Release Notes for v2.0.1
-prev_title: Release Notes for v2.0.2
-prev_url:   release-2.0.2.html
-next_title: Release Notes for v2.0
-next_url:   release-2.0.html
-
-Python-Markdown 2.0.1 Release Notes
-===================================
-
-Python-Markdown 2.0.1 is a bug-fix release. No new features have been added.
-Most notably, various issues with the command line script have been fixed.
-There have also been a few fixes for minor parsing bugs in some edge cases.
-For a full list of changes, see the git log.
-
-Backwards-incompatible Changes
-------------------------------
-
-Due to various complications in how Python handles command line scripts in 
-difference systems and with different installation tools, we were forced to 
-rename the command line script to `markdown` (no `.py`). A matching batch
-script will get installed on Windows. Any shell scripts which call 
-`markdown.py` will need to be altered to call `markdown` instead.
diff --git a/docs/release-2.0.2.txt b/docs/release-2.0.2.txt
deleted file mode 100644
index 5315c8009..000000000
--- a/docs/release-2.0.2.txt
+++ /dev/null
@@ -1,15 +0,0 @@
-title:      Release Notes for v2.0.2
-prev_title: Release Notes for v2.1.0
-prev_url:   release-2.1.0.html
-next_title: Release Notes for v2.0.1
-next_url:   release-2.0.1.html
-
-Python-Markdown 2.0.2 Release Notes
-===================================
-
-Python-Markdown 2.0.2 is a bug-fix release. No new features have been added.
-Most notably, the setup script has been updated to include a dependency on 
-ElementTree on older versions of Python (< 2.5). There have also been a few 
-fixes for minor parsing bugs in some edge cases. For a full list of changes, 
-see the git log.
-
diff --git a/docs/release-2.1.1.txt b/docs/release-2.1.1.txt
deleted file mode 100644
index 59caf6842..000000000
--- a/docs/release-2.1.1.txt
+++ /dev/null
@@ -1,13 +0,0 @@
-title:      Release Notes for v2.1.1
-prev_title: Release Notes for v2.2.0
-prev_url:   release-2.2.0.html
-next_title: Release Notes for v2.1.0
-next_url:   release-2.1.0.html
-
-Python-Markdown 2.1.1 Release Notes
-===================================
-
-Python-Markdown 2.1.1 is a bug-fix release. No new features have been added.
-Most notably, a bug which caused raw HTML paring to run __very__ slowly has
-been fixed along with a few bugs which caused the parser to raise exceptions
-while parsing. For a full list of changes, see the git log.
diff --git a/docs/release-2.2.1.txt b/docs/release-2.2.1.txt
deleted file mode 100644
index fe27c71f9..000000000
--- a/docs/release-2.2.1.txt
+++ /dev/null
@@ -1,12 +0,0 @@
-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/siteindex.txt b/docs/siteindex.md
similarity index 100%
rename from docs/siteindex.txt
rename to docs/siteindex.md
diff --git a/docs/test_suite.txt b/docs/test_suite.md
similarity index 100%
rename from docs/test_suite.txt
rename to docs/test_suite.md
diff --git a/makefile b/makefile
index 0ed1dba7d..97aca1d7d 100644
--- a/makefile
+++ b/makefile
@@ -20,14 +20,16 @@ install:
 
 .PHONY : deploy
 deploy:
+	rm -rf dist
 	python setup.py register
 	python setup.py sdist --manifest-only
-	python setup.py sdist --formats zip,gztar upload
+	python setup.py sdist --formats zip,gztar bdist_wheel --universal
+	twine upload dist/*
 
 .PHONY : build
 build:
 	python setup.py sdist --manifest-only
-	python setup.py sdist --formats zip,gztar
+	python setup.py sdist --formats zip,gztar bdist_wheel --universal
 
 .PHONY : build-win
 build-win:
@@ -36,8 +38,8 @@ build-win:
 
 .PHONY : docs
 docs:
-	python setup.py build_docs --force
-	cd build/docs && zip -r ../docs.zip .
+	mkdocs build --clean
+	cd site && zip -r ../docs.zip .
 
 .PHONY : test
 test:
@@ -45,7 +47,7 @@ test:
 
 .PHONY : update-tests
 update-tests:
-	python run-tests.py update
+	python -m markdown.test update
 
 .PHONY : clean
 clean:
diff --git a/markdown/__init__.py b/markdown/__init__.py
index 1b8655313..ace1773e7 100644
--- a/markdown/__init__.py
+++ b/markdown/__init__.py
@@ -32,498 +32,34 @@
 
 from __future__ import absolute_import
 from __future__ import unicode_literals
-from .__version__ import version, version_info  # noqa
-import codecs
-import sys
-import logging
-import warnings
-import importlib
-from . import util
-from .preprocessors import build_preprocessors
-from .blockprocessors import build_block_parser
-from .treeprocessors import build_treeprocessors
-from .inlinepatterns import build_inlinepatterns
-from .postprocessors import build_postprocessors
-from .extensions import Extension
-from .serializers import to_html_string, to_xhtml_string
+from .core import Markdown, markdown, markdownFromFile
 
 __all__ = ['Markdown', 'markdown', 'markdownFromFile']
 
+# 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__ = (3, 0, 0, 'alpha', 0)
 
-logger = logging.getLogger('MARKDOWN')
 
+def _get_version():  # pragma: no cover
+    " Returns a PEP 386-compliant version number from version_info. "
+    assert len(__version_info__) == 5
+    assert __version_info__[3] in ('alpha', 'beta', 'rc', 'final')
 
-class Markdown(object):
-    """Convert Markdown to HTML."""
+    parts = 2 if __version_info__[2] == 0 else 3
+    main = '.'.join(map(str, __version_info__[:parts]))
 
-    doc_tag = "div"     # Element used to wrap document - later removed
+    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])
 
-    option_defaults = {
-        'html_replacement_text': '[HTML_REMOVED]',
-        'tab_length':            4,
-        'enable_attributes':     True,
-        'smart_emphasis':        True,
-        'lazy_ol':               True,
-    }
+    return str(main + sub)
 
-    output_formats = {
-        'html':   to_html_string,
-        'html4':  to_html_string,
-        'html5':  to_html_string,
-        'xhtml':  to_xhtml_string,
-        'xhtml1': to_xhtml_string,
-        'xhtml5': to_xhtml_string,
-    }
 
-    ESCAPED_CHARS = ['\\', '`', '*', '_', '{', '}', '[', ']',
-                     '(', ')', '>', '#', '+', '-', '.', '!']
-
-    def __init__(self, *args, **kwargs):
-        """
-        Creates a new Markdown instance.
-
-        Keyword arguments:
-
-        * extensions: A list of extensions.
-           If they are of type string, the module mdx_name.py will be loaded.
-           If they are a subclass of markdown.Extension, they will be used
-           as-is.
-        * extension_configs: Configuration settings for extensions.
-        * output_format: Format of output. Supported formats are:
-            * "xhtml1": Outputs XHTML 1.x. Default.
-            * "xhtml5": Outputs XHTML style tags of HTML 5
-            * "xhtml": Outputs latest supported version of XHTML
-              (currently XHTML 1.1).
-            * "html4": Outputs HTML 4
-            * "html5": Outputs HTML style tags of HTML 5
-            * "html": Outputs latest supported version of HTML
-              (currently HTML 4).
-            Note that it is suggested that the more specific formats ("xhtml1"
-            and "html4") be used as "xhtml" or "html" may change in the future
-            if it makes sense at that time.
-        * safe_mode: Deprecated! Disallow raw html. One of "remove", "replace"
-          or "escape".
-        * html_replacement_text: Deprecated! Text used when safe_mode is set
-          to "replace".
-        * tab_length: Length of tabs in the source. Default: 4
-        * enable_attributes: Enable the conversion of attributes. Default: True
-        * smart_emphasis: Treat `_connected_words_` intelligently Default: True
-        * lazy_ol: Ignore number of first item of ordered lists. Default: True
-
-        """
-
-        # For backward compatibility, loop through old positional args
-        pos = ['extensions', 'extension_configs', 'safe_mode', 'output_format']
-        for c, arg in enumerate(args):
-            if pos[c] not in kwargs:
-                kwargs[pos[c]] = arg
-            if c+1 == len(pos):  # pragma: no cover
-                # ignore any additional args
-                break
-        if len(args):
-            warnings.warn('Positional arguments are deprecated in Markdown. '
-                          'Use keyword arguments only.',
-                          DeprecationWarning)
-
-        # Loop through kwargs and assign defaults
-        for option, default in self.option_defaults.items():
-            setattr(self, option, kwargs.get(option, default))
-
-        self.safeMode = kwargs.get('safe_mode', False)
-        if self.safeMode and 'enable_attributes' not in kwargs:
-            # Disable attributes in safeMode when not explicitly set
-            self.enable_attributes = False
-
-        if 'safe_mode' in kwargs:
-            warnings.warn('"safe_mode" is deprecated in Python-Markdown. '
-                          'Use an HTML sanitizer (like '
-                          'Bleach http://bleach.readthedocs.org/) '
-                          'if you are parsing untrusted markdown text. '
-                          'See the 2.6 release notes for more info',
-                          DeprecationWarning)
-
-        if 'html_replacement_text' in kwargs:
-            warnings.warn('The "html_replacement_text" keyword is '
-                          'deprecated along with "safe_mode".',
-                          DeprecationWarning)
-
-        self.registeredExtensions = []
-        self.docType = ""
-        self.stripTopLevelTags = True
-
-        self.build_parser()
-
-        self.references = {}
-        self.htmlStash = util.HtmlStash()
-        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):
-        """ Build the parser from the various parts. """
-        self.preprocessors = build_preprocessors(self)
-        self.parser = build_block_parser(self)
-        self.inlinePatterns = build_inlinepatterns(self)
-        self.treeprocessors = build_treeprocessors(self)
-        self.postprocessors = build_postprocessors(self)
-        return self
-
-    def registerExtensions(self, extensions, configs):
-        """
-        Register extensions with this instance of Markdown.
-
-        Keyword arguments:
-
-        * extensions: A list of extensions, which can either
-           be strings or objects.  See the docstring on Markdown.
-        * configs: A dictionary mapping module names to config options.
-
-        """
-        for ext in extensions:
-            if isinstance(ext, util.string_type):
-                ext = self.build_extension(ext, configs.get(ext, {}))
-            if isinstance(ext, Extension):
-                ext.extendMarkdown(self, globals())
-                logger.debug(
-                    'Successfully loaded extension "%s.%s".'
-                    % (ext.__class__.__module__, ext.__class__.__name__)
-                )
-            elif ext is not None:
-                raise TypeError(
-                    'Extension "%s.%s" must be of type: "markdown.Extension"'
-                    % (ext.__class__.__module__, ext.__class__.__name__))
-
-        return self
-
-    def build_extension(self, ext_name, configs):
-        """Build extension by name, then return the module.
-
-        The extension name may contain arguments as part of the string in the
-        following format: "extname(key1=value1,key2=value2)"
-
-        """
-
-        configs = dict(configs)
-
-        # Parse extensions config params (ignore the order)
-        pos = ext_name.find("(")  # find the first "("
-        if pos > 0:
-            ext_args = ext_name[pos+1:-1]
-            ext_name = ext_name[:pos]
-            pairs = [x.split("=") for x in ext_args.split(",")]
-            configs.update([(x.strip(), y.strip()) for (x, y) in pairs])
-            warnings.warn('Setting configs in the Named Extension string is '
-                          'deprecated. It is recommended that you '
-                          'pass an instance of the extension class to '
-                          'Markdown or use the "extension_configs" keyword. '
-                          'The current behavior will raise an error in version 2.7. '
-                          'See the Release Notes for Python-Markdown version '
-                          '2.6 for more info.', DeprecationWarning)
-
-        # Get class name (if provided): `path.to.module:ClassName`
-        ext_name, class_name = ext_name.split(':', 1) \
-            if ':' in ext_name else (ext_name, '')
-
-        # Try loading the extension first from one place, then another
-        try:
-            # Assume string uses dot syntax (`path.to.some.module`)
-            module = importlib.import_module(ext_name)
-            logger.debug(
-                'Successfuly imported extension module "%s".' % ext_name
-            )
-            # For backward compat (until deprecation)
-            # check that this is an extension.
-            if ('.' not in ext_name and not (hasattr(module, 'makeExtension') or
-               (class_name and hasattr(module, class_name)))):
-                # We have a name conflict
-                # eg: extensions=['tables'] and PyTables is installed
-                raise ImportError
-        except ImportError:
-            # Preppend `markdown.extensions.` to name
-            module_name = '.'.join(['markdown.extensions', ext_name])
-            try:
-                module = importlib.import_module(module_name)
-                logger.debug(
-                    'Successfuly imported extension module "%s".' %
-                    module_name
-                )
-                warnings.warn('Using short names for Markdown\'s builtin '
-                              'extensions is deprecated. Use the '
-                              'full path to the extension with Python\'s dot '
-                              'notation (eg: "%s" instead of "%s"). The '
-                              'current behavior will raise an error in version '
-                              '2.7. See the Release Notes for '
-                              'Python-Markdown version 2.6 for more info.' %
-                              (module_name, ext_name),
-                              DeprecationWarning)
-            except ImportError:
-                # Preppend `mdx_` to name
-                module_name_old_style = '_'.join(['mdx', ext_name])
-                try:
-                    module = importlib.import_module(module_name_old_style)
-                    logger.debug(
-                        'Successfuly imported extension module "%s".' %
-                        module_name_old_style)
-                    warnings.warn('Markdown\'s behavior of prepending "mdx_" '
-                                  'to an extension name is deprecated. '
-                                  'Use the full path to the '
-                                  'extension with Python\'s dot notation '
-                                  '(eg: "%s" instead of "%s"). The current '
-                                  'behavior will raise an error in version 2.7. '
-                                  'See the Release Notes for Python-Markdown '
-                                  'version 2.6 for more info.' %
-                                  (module_name_old_style, ext_name),
-                                  DeprecationWarning)
-                except ImportError as e:
-                    message = "Failed loading extension '%s' from '%s', '%s' " \
-                        "or '%s'" % (ext_name, ext_name, module_name,
-                                     module_name_old_style)
-                    e.args = (message,) + e.args[1:]
-                    raise
-
-        if class_name:
-            # Load given class name from module.
-            return getattr(module, class_name)(**configs)
-        else:
-            # Expect  makeExtension() function to return a class.
-            try:
-                return module.makeExtension(**configs)
-            except AttributeError as e:
-                message = e.args[0]
-                message = "Failed to initiate extension " \
-                          "'%s': %s" % (ext_name, message)
-                e.args = (message,) + e.args[1:]
-                raise
-
-    def registerExtension(self, extension):
-        """ This gets called by the extension """
-        self.registeredExtensions.append(extension)
-        return self
-
-    def reset(self):
-        """
-        Resets all state variables so that we can start with a new text.
-        """
-        self.htmlStash.reset()
-        self.references.clear()
-
-        for extension in self.registeredExtensions:
-            if hasattr(extension, 'reset'):
-                extension.reset()
-
-        return self
-
-    def set_output_format(self, format):
-        """ Set the output format for the class instance. """
-        self.output_format = format.lower()
-        try:
-            self.serializer = self.output_formats[self.output_format]
-        except KeyError as e:
-            valid_formats = list(self.output_formats.keys())
-            valid_formats.sort()
-            message = 'Invalid Output Format: "%s". Use one of %s.' \
-                % (self.output_format,
-                   '"' + '", "'.join(valid_formats) + '"')
-            e.args = (message,) + e.args[1:]
-            raise
-        return self
-
-    def convert(self, source):
-        """
-        Convert markdown to serialized XHTML or HTML.
-
-        Keyword arguments:
-
-        * source: Source text as a Unicode string.
-
-        Markdown processing takes place in five steps:
-
-        1. A bunch of "preprocessors" munge the input text.
-        2. BlockParser() parses the high-level structural elements of the
-           pre-processed text into an ElementTree.
-        3. A bunch of "treeprocessors" are run against the ElementTree. One
-           such treeprocessor runs InlinePatterns against the ElementTree,
-           detecting inline markup.
-        4. Some post-processors are run against the text after the ElementTree
-           has been serialized into text.
-        5. The output is written to a string.
-
-        """
-
-        # Fixup the source text
-        if not source.strip():
-            return ''  # a blank unicode string
-
-        try:
-            source = util.text_type(source)
-        except UnicodeDecodeError as e:
-            # Customise error message while maintaining original trackback
-            e.reason += '. -- Note: Markdown only accepts unicode input!'
-            raise
-
-        # Split into lines and run the line preprocessors.
-        self.lines = source.split("\n")
-        for prep in self.preprocessors.values():
-            self.lines = prep.run(self.lines)
-
-        # Parse the high-level elements.
-        root = self.parser.parseDocument(self.lines).getroot()
-
-        # Run the tree-processors
-        for treeprocessor in self.treeprocessors.values():
-            newRoot = treeprocessor.run(root)
-            if newRoot is not None:
-                root = newRoot
-
-        # Serialize _properly_.  Strip top-level tags.
-        output = self.serializer(root)
-        if self.stripTopLevelTags:
-            try:
-                start = output.index(
-                    '<%s>' % self.doc_tag) + len(self.doc_tag) + 2
-                end = output.rindex('</%s>' % self.doc_tag)
-                output = output[start:end].strip()
-            except ValueError:  # pragma: no cover
-                if output.strip().endswith('<%s />' % self.doc_tag):
-                    # We have an empty document
-                    output = ''
-                else:
-                    # We have a serious problem
-                    raise ValueError('Markdown failed to strip top-level '
-                                     'tags. Document=%r' % output.strip())
-
-        # Run the text post-processors
-        for pp in self.postprocessors.values():
-            output = pp.run(output)
-
-        return output.strip()
-
-    def convertFile(self, input=None, output=None, encoding=None):
-        """Converts a Markdown file and returns the HTML as a Unicode string.
-
-        Decodes the file using the provided encoding (defaults to utf-8),
-        passes the file content to markdown, and outputs the html to either
-        the provided stream or the file with provided name, using the same
-        encoding as the source file. The 'xmlcharrefreplace' error handler is
-        used when encoding the output.
-
-        **Note:** This is the only place that decoding and encoding of Unicode
-        takes place in Python-Markdown.  (All other code is Unicode-in /
-        Unicode-out.)
-
-        Keyword arguments:
-
-        * input: File object or path. Reads from stdin if `None`.
-        * output: File object or path. Writes to stdout if `None`.
-        * encoding: Encoding of input and output files. Defaults to utf-8.
-
-        """
-
-        encoding = encoding or "utf-8"
-
-        # Read the source
-        if input:
-            if isinstance(input, util.string_type):
-                input_file = codecs.open(input, mode="r", encoding=encoding)
-            else:
-                input_file = codecs.getreader(encoding)(input)
-            text = input_file.read()
-            input_file.close()
-        else:
-            text = sys.stdin.read()
-            if not isinstance(text, util.text_type):
-                text = text.decode(encoding)
-
-        text = text.lstrip('\ufeff')  # remove the byte-order mark
-
-        # Convert
-        html = self.convert(text)
-
-        # Write to file or stdout
-        if output:
-            if isinstance(output, util.string_type):
-                output_file = codecs.open(output, "w",
-                                          encoding=encoding,
-                                          errors="xmlcharrefreplace")
-                output_file.write(html)
-                output_file.close()
-            else:
-                writer = codecs.getwriter(encoding)
-                output_file = writer(output, errors="xmlcharrefreplace")
-                output_file.write(html)
-                # Don't close here. User may want to write more.
-        else:
-            # 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)
-
-        return self
-
-
-"""
-EXPORTED FUNCTIONS
-=============================================================================
-
-Those are the two functions we really mean to export: markdown() and
-markdownFromFile().
-"""
-
-
-def markdown(text, *args, **kwargs):
-    """Convert a Markdown string to HTML and return HTML as a Unicode string.
-
-    This is a shortcut function for `Markdown` class to cover the most
-    basic use case.  It initializes an instance of Markdown, loads the
-    necessary extensions and runs the parser on the given text.
-
-    Keyword arguments:
-
-    * text: Markdown formatted text as Unicode or ASCII string.
-    * Any arguments accepted by the Markdown class.
-
-    Returns: An HTML document as a string.
-
-    """
-    md = Markdown(*args, **kwargs)
-    return md.convert(text)
-
-
-def markdownFromFile(*args, **kwargs):
-    """Read markdown code from a file and write it to a file or a stream.
-
-    This is a shortcut function which initializes an instance of Markdown,
-    and calls the convertFile method rather than convert.
-
-    Keyword arguments:
-
-    * input: a file name or readable object.
-    * output: a file name or writable object.
-    * encoding: Encoding of input and output.
-    * Any arguments accepted by the Markdown class.
-
-    """
-    # For backward compatibility loop through positional args
-    pos = ['input', 'output', 'extensions', 'encoding']
-    c = 0
-    for arg in args:
-        if pos[c] not in kwargs:
-            kwargs[pos[c]] = arg
-        c += 1
-        if c == len(pos):
-            break
-    if len(args):
-        warnings.warn('Positional arguments are depreacted in '
-                      'Markdown and will raise an error in version 2.7. '
-                      'Use keyword arguments only.',
-                      DeprecationWarning)
-
-    md = Markdown(**kwargs)
-    md.convertFile(kwargs.get('input', None),
-                   kwargs.get('output', None),
-                   kwargs.get('encoding', None))
+__version__ = _get_version()
diff --git a/markdown/__main__.py b/markdown/__main__.py
index 17bfa9f3c..8c3e8aea2 100644
--- a/markdown/__main__.py
+++ b/markdown/__main__.py
@@ -28,7 +28,7 @@ def parse_options(args=None, values=None):
        (STDIN is assumed if no INPUTFILE is given)"""
     desc = "A Python implementation of John Gruber's Markdown. " \
            "https://pythonhosted.org/Markdown/"
-    ver = "%%prog %s" % markdown.version
+    ver = "%%prog %s" % markdown.__version__
 
     parser = optparse.OptionParser(usage=usage, description=desc, version=ver)
     parser.add_option("-f", "--file", dest="filename", default=None,
@@ -36,10 +36,6 @@ def parse_options(args=None, values=None):
                       metavar="OUTPUT_FILE")
     parser.add_option("-e", "--encoding", dest="encoding",
                       help="Encoding for input and output files.",)
-    parser.add_option("-s", "--safe", dest="safe", default=False,
-                      metavar="SAFE_MODE",
-                      help="Deprecated! 'replace', 'remove' or 'escape' HTML "
-                      "tags in input")
     parser.add_option("-o", "--output_format", dest="output_format",
                       default='xhtml1', metavar="OUTPUT_FORMAT",
                       help="'xhtml1' (default), 'html4' or 'html5'.")
@@ -102,10 +98,6 @@ def parse_options(args=None, values=None):
         'lazy_ol': options.lazy_ol
     }
 
-    if options.safe:
-        # Avoid deprecation warning if user didn't set option
-        opts['safe_mode'] = options.safe
-
     return opts, options.verbose
 
 
diff --git a/markdown/__version__.py b/markdown/__version__.py
deleted file mode 100644
index 344250400..000000000
--- a/markdown/__version__.py
+++ /dev/null
@@ -1,29 +0,0 @@
-#
-# 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, 6, 2, 'final', 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/blockparser.py b/markdown/blockparser.py
index 32d3254cd..4980f2618 100644
--- a/markdown/blockparser.py
+++ b/markdown/blockparser.py
@@ -45,10 +45,10 @@ class BlockParser:
     looping through them and creating an ElementTree object.
     """
 
-    def __init__(self, markdown):
+    def __init__(self, md):
         self.blockprocessors = odict.OrderedDict()
         self.state = State()
-        self.markdown = markdown
+        self.md = md
 
     def parseDocument(self, lines):
         """ Parse a markdown document into an ElementTree.
@@ -61,7 +61,7 @@ def parseDocument(self, lines):
 
         """
         # Create a ElementTree from the lines
-        self.root = util.etree.Element(self.markdown.doc_tag)
+        self.root = util.etree.Element(self.md.doc_tag)
         self.parseChunk(self.root, '\n'.join(lines))
         return util.etree.ElementTree(self.root)
 
diff --git a/markdown/blockprocessors.py b/markdown/blockprocessors.py
index 29db022ce..332c3a76f 100644
--- a/markdown/blockprocessors.py
+++ b/markdown/blockprocessors.py
@@ -22,9 +22,9 @@
 logger = logging.getLogger('MARKDOWN')
 
 
-def build_block_parser(md_instance, **kwargs):
+def build_block_parser(md, **kwargs):
     """ Build the default block parser used by Markdown. """
-    parser = BlockParser(md_instance)
+    parser = BlockParser(md)
     parser.blockprocessors['empty'] = EmptyBlockProcessor(parser)
     parser.blockprocessors['indent'] = ListIndentProcessor(parser)
     parser.blockprocessors['code'] = CodeBlockProcessor(parser)
@@ -38,7 +38,7 @@ def build_block_parser(md_instance, **kwargs):
     return parser
 
 
-class BlockProcessor:
+class BlockProcessor(object):
     """ Base class for block processors.
 
     Each subclass will provide the methods below to work with the source and
@@ -51,7 +51,7 @@ class BlockProcessor:
 
     def __init__(self, parser):
         self.parser = parser
-        self.tab_length = parser.markdown.tab_length
+        self.tab_length = parser.md.tab_length
 
     def lastChild(self, parent):
         """ Return the last child of an etree element. """
@@ -141,7 +141,7 @@ class ListIndentProcessor(BlockProcessor):
     LIST_TYPES = ['ul', 'ol']
 
     def __init__(self, *args):
-        BlockProcessor.__init__(self, *args)
+        super(ListIndentProcessor, self).__init__(*args)
         self.INDENT_RE = re.compile(r'^(([ ]{%s})+)' % self.tab_length)
 
     def test(self, parent, block):
@@ -300,12 +300,8 @@ class OListProcessor(BlockProcessor):
     """ Process ordered list blocks. """
 
     TAG = 'ol'
-    # Detect an item (``1. item``). ``group(1)`` contains contents of item.
-    RE = re.compile(r'^[ ]{0,3}\d+\.[ ]+(.*)')
-    # Detect items on secondary lines. they can be of either list type.
-    CHILD_RE = re.compile(r'^[ ]{0,3}((\d+\.)|[*+-])[ ]+(.*)')
-    # Detect indented (nested) items of either type
-    INDENT_RE = re.compile(r'^[ ]{4,7}((\d+\.)|[*+-])[ ]+.*')
+    # Lazy ol - ignore startswith
+    LAZY_OL = True
     # The integer (python string) with which the lists starts (default=1)
     # Eg: If list is intialized as)
     #   3. Item
@@ -314,6 +310,17 @@ class OListProcessor(BlockProcessor):
     # List of allowed sibling tags.
     SIBLING_TAGS = ['ol', 'ul']
 
+    def __init__(self, parser):
+        super(OListProcessor, self).__init__(parser)
+        # Detect an item (``1. item``). ``group(1)`` contains contents of item.
+        self.RE = re.compile(r'^[ ]{0,%d}\d+\.[ ]+(.*)' % (self.tab_length - 1))
+        # Detect items on secondary lines. they can be of either list type.
+        self.CHILD_RE = re.compile(r'^[ ]{0,%d}((\d+\.)|[*+-])[ ]+(.*)' %
+                                   (self.tab_length - 1))
+        # Detect indented (nested) items of either type
+        self.INDENT_RE = re.compile(r'^[ ]{%d,%d}((\d+\.)|[*+-])[ ]+.*' %
+                                    (self.tab_length, self.tab_length * 2 - 1))
+
     def test(self, parent, block):
         return bool(self.RE.match(block))
 
@@ -360,7 +367,7 @@ def run(self, parent, blocks):
             # This is a new list so create parent with appropriate tag.
             lst = util.etree.SubElement(parent, self.TAG)
             # Check if a custom start integer is set
-            if not self.parser.markdown.lazy_ol and self.STARTSWITH != '1':
+            if not self.LAZY_OL and self.STARTSWITH != '1':
                 lst.attrib['start'] = self.STARTSWITH
 
         self.parser.state.set('list')
@@ -407,7 +414,11 @@ class UListProcessor(OListProcessor):
     """ Process unordered list blocks. """
 
     TAG = 'ul'
-    RE = re.compile(r'^[ ]{0,3}[*+-][ ]+(.*)')
+
+    def __init__(self, parser):
+        super(UListProcessor, self).__init__(parser)
+        # Detect an item (``1. item``). ``group(1)`` contains contents of item.
+        self.RE = re.compile(r'^[ ]{0,%d}[*+-][ ]+(.*)' % (self.tab_length - 1))
 
 
 class HashHeaderProcessor(BlockProcessor):
diff --git a/markdown/core.py b/markdown/core.py
new file mode 100644
index 000000000..0b21d06fe
--- /dev/null
+++ b/markdown/core.py
@@ -0,0 +1,338 @@
+from __future__ import absolute_import
+from __future__ import unicode_literals
+import codecs
+import sys
+import logging
+import pkg_resources
+from . import util
+from .preprocessors import build_preprocessors
+from .blockprocessors import build_block_parser
+from .treeprocessors import build_treeprocessors
+from .inlinepatterns import build_inlinepatterns
+from .postprocessors import build_postprocessors
+from .extensions import Extension
+from .serializers import to_html_string, to_xhtml_string
+
+__all__ = ['Markdown', 'markdown', 'markdownFromFile']
+
+
+logger = logging.getLogger('MARKDOWN')
+
+
+class Markdown(object):
+    """Convert Markdown to HTML."""
+
+    doc_tag = "div"     # Element used to wrap document - later removed
+
+    output_formats = {
+        'html':   to_html_string,
+        'xhtml':  to_xhtml_string,
+    }
+
+    ESCAPED_CHARS = ['\\', '`', '*', '_', '{', '}', '[', ']',
+                     '(', ')', '>', '#', '+', '-', '.', '!']
+
+    def __init__(self, **kwargs):
+        """
+        Creates a new Markdown instance.
+
+        Keyword arguments:
+
+        * extensions: A list of extensions.
+           If they are of type string, an entry point will be loaded.
+           If they are a subclass of `markdown.extensions.Extension`,
+           they will be used as-is.
+        * extension_configs: Configuration settings for extensions.
+        * output_format: Format of output. Supported formats are:
+            * "xhtml": Outputs XHTML style tags. Default.
+            * "html": Outputs HTML style tags.
+        * tab_length: Length of tabs in the source. Default: 4
+
+        """
+
+        self.tab_length = kwargs.get('tab_length', 4)
+
+        self.registeredExtensions = []
+        self.docType = ""
+        self.stripTopLevelTags = True
+
+        self.build_parser()
+
+        self.references = {}
+        self.htmlStash = util.HtmlStash()
+        self.registerExtensions(extensions=kwargs.get('extensions', []),
+                                configs=kwargs.get('extension_configs', {}))
+        self.set_output_format(kwargs.get('output_format', 'xhtml'))
+        self.reset()
+
+    def build_parser(self):
+        """ Build the parser from the various parts. """
+        self.preprocessors = build_preprocessors(self)
+        self.parser = build_block_parser(self)
+        self.inlinePatterns = build_inlinepatterns(self)
+        self.treeprocessors = build_treeprocessors(self)
+        self.postprocessors = build_postprocessors(self)
+        return self
+
+    def registerExtensions(self, extensions, configs):
+        """
+        Register extensions with this instance of Markdown.
+
+        Keyword arguments:
+
+        * extensions: A list of extensions, which can either
+           be strings or objects.
+        * configs: A dictionary mapping extension names to config options.
+
+        """
+        for ext in extensions:
+            if isinstance(ext, util.string_type):
+                ext = self.build_extension(ext, configs.get(ext, {}))
+            if isinstance(ext, Extension):
+                ext.extendMarkdown(self)
+                logger.debug(
+                    'Successfully loaded extension "%s.%s".'
+                    % (ext.__class__.__module__, ext.__class__.__name__)
+                )
+            elif ext is not None:
+                raise TypeError(
+                    'Extension "%s.%s" must be of type: "markdown.extensions.Extension"'
+                    % (ext.__class__.__module__, ext.__class__.__name__))
+
+        return self
+
+    def build_extension(self, name, configs):
+        """
+        Build an extension from a string name, and return an instance.
+
+        The string name must be registered as an entry point in the
+        `markdown.extensions` group which points to a subclass of
+        the `markdown.extensions.Extension` class.  If multiple
+        distributions have registered the same name, the first one
+        found by `pkg_resources.iter_entry_points` is returned.
+        """
+        ep = None
+        for x in pkg_resources.iter_entry_points('markdown.extensions', name):
+            ep = x
+            break
+        if ep is None:
+            raise ImportError('Can\'t find entry point "%s" in group "markdown.extensions".' % name)
+        ext = ep.load()
+        return ext(**configs)
+
+    def registerExtension(self, extension):
+        """ This gets called by the extension """
+        self.registeredExtensions.append(extension)
+        return self
+
+    def reset(self):
+        """
+        Resets all state variables so that we can start with a new text.
+        """
+        self.htmlStash.reset()
+        self.references.clear()
+
+        for extension in self.registeredExtensions:
+            if hasattr(extension, 'reset'):
+                extension.reset()
+
+        return self
+
+    def set_output_format(self, format):
+        """ Set the output format for the class instance. """
+        self.output_format = format.lower().rstrip('145')
+        try:
+            self.serializer = self.output_formats[self.output_format]
+        except KeyError as e:
+            valid_formats = list(self.output_formats.keys())
+            valid_formats.sort()
+            message = 'Invalid Output Format: "%s". Use one of %s.' \
+                % (self.output_format,
+                   '"' + '", "'.join(valid_formats) + '"')
+            e.args = (message,) + e.args[1:]
+            raise
+        return self
+
+    def convert(self, source):
+        """
+        Convert markdown to serialized XHTML or HTML.
+
+        Keyword arguments:
+
+        * source: Source text as a Unicode string.
+
+        Markdown processing takes place in five steps:
+
+        1. A bunch of "preprocessors" munge the input text.
+        2. BlockParser() parses the high-level structural elements of the
+           pre-processed text into an ElementTree.
+        3. A bunch of "treeprocessors" are run against the ElementTree. One
+           such treeprocessor runs InlinePatterns against the ElementTree,
+           detecting inline markup.
+        4. Some post-processors are run against the text after the ElementTree
+           has been serialized into text.
+        5. The output is written to a string.
+
+        """
+
+        # Fixup the source text
+        if not source.strip():
+            return ''  # a blank unicode string
+
+        try:
+            source = util.text_type(source)
+        except UnicodeDecodeError as e:  # pragma: no cover
+            # Customise error message while maintaining original trackback
+            e.reason += '. -- Note: Markdown only accepts unicode input!'
+            raise
+
+        # Split into lines and run the line preprocessors.
+        self.lines = source.split("\n")
+        for prep in self.preprocessors.values():
+            self.lines = prep.run(self.lines)
+
+        # Parse the high-level elements.
+        root = self.parser.parseDocument(self.lines).getroot()
+
+        # Run the tree-processors
+        for treeprocessor in self.treeprocessors.values():
+            newRoot = treeprocessor.run(root)
+            if newRoot is not None:
+                root = newRoot
+
+        # Serialize _properly_.  Strip top-level tags.
+        output = self.serializer(root)
+        if self.stripTopLevelTags:
+            try:
+                start = output.index(
+                    '<%s>' % self.doc_tag) + len(self.doc_tag) + 2
+                end = output.rindex('</%s>' % self.doc_tag)
+                output = output[start:end].strip()
+            except ValueError:  # pragma: no cover
+                if output.strip().endswith('<%s />' % self.doc_tag):
+                    # We have an empty document
+                    output = ''
+                else:
+                    # We have a serious problem
+                    raise ValueError('Markdown failed to strip top-level '
+                                     'tags. Document=%r' % output.strip())
+
+        # Run the text post-processors
+        for pp in self.postprocessors.values():
+            output = pp.run(output)
+
+        return output.strip()
+
+    def convertFile(self, input=None, output=None, encoding=None):
+        """Converts a markdown file and returns the HTML as a unicode string.
+
+        Decodes the file using the provided encoding (defaults to utf-8),
+        passes the file content to markdown, and outputs the html to either
+        the provided stream or the file with provided name, using the same
+        encoding as the source file. The 'xmlcharrefreplace' error handler is
+        used when encoding the output.
+
+        **Note:** This is the only place that decoding and encoding of unicode
+        takes place in Python-Markdown.  (All other code is unicode-in /
+        unicode-out.)
+
+        Keyword arguments:
+
+        * input: File object or path. Reads from stdin if `None`.
+        * output: File object or path. Writes to stdout if `None`.
+        * encoding: Encoding of input and output files. Defaults to utf-8.
+
+        """
+
+        encoding = encoding or "utf-8"
+
+        # Read the source
+        if input:
+            if isinstance(input, util.string_type):
+                input_file = codecs.open(input, mode="r", encoding=encoding)
+            else:
+                input_file = codecs.getreader(encoding)(input)
+            text = input_file.read()
+            input_file.close()
+        else:
+            text = sys.stdin.read()
+            if not isinstance(text, util.text_type):  # pragma: no cover
+                text = text.decode(encoding)
+
+        text = text.lstrip('\ufeff')  # remove the byte-order mark
+
+        # Convert
+        html = self.convert(text)
+
+        # Write to file or stdout
+        if output:
+            if isinstance(output, util.string_type):
+                output_file = codecs.open(output, "w",
+                                          encoding=encoding,
+                                          errors="xmlcharrefreplace")
+                output_file.write(html)
+                output_file.close()
+            else:
+                writer = codecs.getwriter(encoding)
+                output_file = writer(output, errors="xmlcharrefreplace")
+                output_file.write(html)
+                # Don't close here. User may want to write more.
+        else:
+            # 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:  # pragma: no cover
+                # Probably Python 2, which works with bytes by default.
+                sys.stdout.write(html)
+
+        return self
+
+
+"""
+EXPORTED FUNCTIONS
+=============================================================================
+
+Those are the two functions we really mean to export: markdown() and
+markdownFromFile().
+"""
+
+
+def markdown(text, **kwargs):
+    """Convert a markdown string to HTML and return HTML as a unicode string.
+
+    This is a shortcut function for `Markdown` class to cover the most
+    basic use case.  It initializes an instance of Markdown, loads the
+    necessary extensions and runs the parser on the given text.
+
+    Keyword arguments:
+
+    * text: Markdown formatted text as Unicode or ASCII string.
+    * Any arguments accepted by the Markdown class.
+
+    Returns: An HTML document as a string.
+
+    """
+    md = Markdown(**kwargs)
+    return md.convert(text)
+
+
+def markdownFromFile(**kwargs):
+    """Read markdown code from a file and write it to a file or a stream.
+
+    This is a shortcut function which initializes an instance of Markdown,
+    and calls the convertFile method rather than convert.
+
+    Keyword arguments:
+
+    * input: a file name or readable object.
+    * output: a file name or writable object.
+    * encoding: Encoding of input and output.
+    * Any arguments accepted by the Markdown class.
+
+    """
+    md = Markdown(**kwargs)
+    md.convertFile(kwargs.get('input', None),
+                   kwargs.get('output', None),
+                   kwargs.get('encoding', None))
diff --git a/markdown/extensions/__init__.py b/markdown/extensions/__init__.py
index 6e7a08a1e..342a0146b 100644
--- a/markdown/extensions/__init__.py
+++ b/markdown/extensions/__init__.py
@@ -5,7 +5,6 @@
 
 from __future__ import unicode_literals
 from ..util import parseBoolValue
-import warnings
 
 
 class Extension(object):
@@ -20,34 +19,8 @@ class Extension(object):
     # if a default is not set here.
     config = {}
 
-    def __init__(self, *args, **kwargs):
+    def __init__(self, **kwargs):
         """ Initiate Extension and set up configs. """
-
-        # check for configs arg for backward compat.
-        # (there only ever used to be one so we use arg[0])
-        if len(args):
-            if args[0] is not None:
-                self.setConfigs(args[0])
-            warnings.warn('Extension classes accepting positional args is '
-                          'pending Deprecation. Each setting should be '
-                          'passed into the Class as a keyword. Positional '
-                          'args are deprecated and will raise '
-                          'an error in version 2.7. See the Release Notes for '
-                          'Python-Markdown version 2.6 for more info.',
-                          DeprecationWarning)
-        # check for configs kwarg for backward compat.
-        if 'configs' in kwargs.keys():
-            if kwargs['configs'] is not None:
-                self.setConfigs(kwargs.pop('configs', {}))
-            warnings.warn('Extension classes accepting a dict on the single '
-                          'keyword "config" is pending Deprecation. Each '
-                          'setting should be passed into the Class as a '
-                          'keyword directly. The "config" keyword is '
-                          'deprecated and raise an error in '
-                          'version 2.7. See the Release Notes for '
-                          'Python-Markdown version 2.6 for more info.',
-                          DeprecationWarning)
-        # finally, use kwargs
         self.setConfigs(kwargs)
 
     def getConfig(self, key, default=''):
@@ -81,7 +54,7 @@ def setConfigs(self, items):
         for key, value in items:
             self.setConfig(key, value)
 
-    def extendMarkdown(self, md, md_globals):
+    def extendMarkdown(self, md):
         """
         Add the various proccesors and patterns to the Markdown Instance.
 
@@ -91,8 +64,6 @@ def extendMarkdown(self, md, md_globals):
 
         * md: The Markdown instance.
 
-        * md_globals: Global variables in the markdown module namespace.
-
         """
         raise NotImplementedError(
             'Extension "%s.%s" must define an "extendMarkdown"'
diff --git a/markdown/extensions/abbr.py b/markdown/extensions/abbr.py
index 353d126f6..dd98b6cc8 100644
--- a/markdown/extensions/abbr.py
+++ b/markdown/extensions/abbr.py
@@ -31,7 +31,7 @@
 class AbbrExtension(Extension):
     """ Abbreviation Extension for Python-Markdown. """
 
-    def extendMarkdown(self, md, md_globals):
+    def extendMarkdown(self, md):
         """ Insert AbbrPreprocessor before ReferencePreprocessor. """
         md.preprocessors.add('abbr', AbbrPreprocessor(md), '<reference')
 
@@ -51,7 +51,7 @@ def run(self, lines):
             if m:
                 abbr = m.group('abbr').strip()
                 title = m.group('title').strip()
-                self.markdown.inlinePatterns['abbr-%s' % abbr] = \
+                self.md.inlinePatterns['abbr-%s' % abbr] = \
                     AbbrPattern(self._generate_pattern(abbr), title)
             else:
                 new_text.append(line)
@@ -85,7 +85,3 @@ def handleMatch(self, m):
         abbr.text = AtomicString(m.group('abbr'))
         abbr.set('title', self.title)
         return abbr
-
-
-def makeExtension(*args, **kwargs):
-    return AbbrExtension(*args, **kwargs)
diff --git a/markdown/extensions/admonition.py b/markdown/extensions/admonition.py
index 76e0fb588..72f1e5f33 100644
--- a/markdown/extensions/admonition.py
+++ b/markdown/extensions/admonition.py
@@ -28,7 +28,7 @@
 class AdmonitionExtension(Extension):
     """ Admonition extension for Python-Markdown. """
 
-    def extendMarkdown(self, md, md_globals):
+    def extendMarkdown(self, md):
         """ Add Admonition to Markdown instance. """
         md.registerExtension(self)
 
@@ -90,7 +90,3 @@ def get_class_and_title(self, match):
             # e.g.: `!!! warning ""` will *not* render `p` with a title
             title = None
         return klass, title
-
-
-def makeExtension(*args, **kwargs):
-    return AdmonitionExtension(*args, **kwargs)
diff --git a/markdown/extensions/attr_list.py b/markdown/extensions/attr_list.py
index 683bdf831..639fac81d 100644
--- a/markdown/extensions/attr_list.py
+++ b/markdown/extensions/attr_list.py
@@ -24,12 +24,6 @@
 from ..util import isBlockLevel
 import re
 
-try:
-    Scanner = re.Scanner
-except AttributeError:  # pragma: no cover
-    # must be on Python 2.4
-    from sre import Scanner
-
 
 def _handle_double_quote(s, t):
     k, v = t.split('=')
@@ -52,7 +46,7 @@ def _handle_word(s, t):
         return 'id', t[1:]
     return t, t
 
-_scanner = Scanner([
+_scanner = re.Scanner([
     (r'[^ ]+=".*?"', _handle_double_quote),
     (r"[^ ]+='.*?'", _handle_single_quote),
     (r'[^ ]+=[^ =]+', _handle_key_value),
@@ -167,11 +161,7 @@ def sanitize_name(self, name):
 
 
 class AttrListExtension(Extension):
-    def extendMarkdown(self, md, md_globals):
+    def extendMarkdown(self, md):
         md.treeprocessors.add(
             'attr_list', AttrListTreeprocessor(md), '>prettify'
         )
-
-
-def makeExtension(*args, **kwargs):
-    return AttrListExtension(*args, **kwargs)
diff --git a/markdown/extensions/codehilite.py b/markdown/extensions/codehilite.py
index 0657c3768..622aef8d7 100644
--- a/markdown/extensions/codehilite.py
+++ b/markdown/extensions/codehilite.py
@@ -212,11 +212,10 @@ def run(self, root):
                     css_class=self.config['css_class'],
                     style=self.config['pygments_style'],
                     noclasses=self.config['noclasses'],
-                    tab_length=self.markdown.tab_length,
+                    tab_length=self.md.tab_length,
                     use_pygments=self.config['use_pygments']
                 )
-                placeholder = self.markdown.htmlStash.store(code.hilite(),
-                                                            safe=True)
+                placeholder = self.md.htmlStash.store(code.hilite())
                 # Clear codeblock in etree instance
                 block.clear()
                 # Change to p element which will later
@@ -228,7 +227,7 @@ def run(self, root):
 class CodeHiliteExtension(Extension):
     """ Add source code hilighting to markdown codeblocks. """
 
-    def __init__(self, *args, **kwargs):
+    def __init__(self, **kwargs):
         # define default configs
         self.config = {
             'linenums': [None,
@@ -250,16 +249,12 @@ def __init__(self, *args, **kwargs):
                              'Default: True']
             }
 
-        super(CodeHiliteExtension, self).__init__(*args, **kwargs)
+        super(CodeHiliteExtension, self).__init__(**kwargs)
 
-    def extendMarkdown(self, md, md_globals):
+    def extendMarkdown(self, md):
         """ Add HilitePostprocessor to Markdown instance. """
         hiliter = HiliteTreeprocessor(md)
         hiliter.config = self.getConfigs()
         md.treeprocessors.add("hilite", hiliter, "<inline")
 
         md.registerExtension(self)
-
-
-def makeExtension(*args, **kwargs):
-    return CodeHiliteExtension(*args, **kwargs)
diff --git a/markdown/extensions/def_list.py b/markdown/extensions/def_list.py
index 77cca6eb8..be674f8ce 100644
--- a/markdown/extensions/def_list.py
+++ b/markdown/extensions/def_list.py
@@ -101,7 +101,7 @@ def create_item(self, parent, block):
 class DefListExtension(Extension):
     """ Add definition lists to Markdown. """
 
-    def extendMarkdown(self, md, md_globals):
+    def extendMarkdown(self, md):
         """ Add an instance of DefListProcessor to BlockParser. """
         md.parser.blockprocessors.add('defindent',
                                       DefListIndentProcessor(md.parser),
@@ -109,7 +109,3 @@ def extendMarkdown(self, md, md_globals):
         md.parser.blockprocessors.add('deflist',
                                       DefListProcessor(md.parser),
                                       '>ulist')
-
-
-def makeExtension(*args, **kwargs):
-    return DefListExtension(*args, **kwargs)
diff --git a/markdown/extensions/extra.py b/markdown/extensions/extra.py
index de5db03cd..d5beaa907 100644
--- a/markdown/extensions/extra.py
+++ b/markdown/extensions/extra.py
@@ -37,40 +37,32 @@
 import re
 
 extensions = [
-    'markdown.extensions.smart_strong',
-    'markdown.extensions.fenced_code',
-    'markdown.extensions.footnotes',
-    'markdown.extensions.attr_list',
-    'markdown.extensions.def_list',
-    'markdown.extensions.tables',
-    'markdown.extensions.abbr'
+    'fenced_code',
+    'footnotes',
+    'attr_list',
+    'def_list',
+    'tables',
+    'abbr'
 ]
 
 
 class ExtraExtension(Extension):
     """ Add various extensions to Markdown class."""
 
-    def __init__(self, *args, **kwargs):
-        """ config is a dumb holder which gets passed to actual ext later. """
-        self.config = kwargs.pop('configs', {})
-        self.config.update(kwargs)
+    def __init__(self, **kwargs):
+        self.config = kwargs
 
-    def extendMarkdown(self, md, md_globals):
+    def extendMarkdown(self, md):
         """ Register extension instances. """
         md.registerExtensions(extensions, self.config)
-        if not md.safeMode:
-            # Turn on processing of markdown text within raw html
-            md.preprocessors['html_block'].markdown_in_raw = True
-            md.parser.blockprocessors.add('markdown_block',
-                                          MarkdownInHtmlProcessor(md.parser),
-                                          '_begin')
-            md.parser.blockprocessors.tag_counter = -1
-            md.parser.blockprocessors.contain_span_tags = re.compile(
-                r'^(p|h[1-6]|li|dd|dt|td|th|legend|address)$', re.IGNORECASE)
-
-
-def makeExtension(*args, **kwargs):
-    return ExtraExtension(*args, **kwargs)
+        # Turn on processing of markdown text within raw html
+        md.preprocessors['html_block'].markdown_in_raw = True
+        md.parser.blockprocessors.add('markdown_block',
+                                      MarkdownInHtmlProcessor(md.parser),
+                                      '_begin')
+        md.parser.blockprocessors.tag_counter = -1
+        md.parser.blockprocessors.contain_span_tags = re.compile(
+            r'^(p|h[1-6]|li|dd|dt|td|th|legend|address)$', re.IGNORECASE)
 
 
 class MarkdownInHtmlProcessor(BlockProcessor):
@@ -98,7 +90,7 @@ def _process_nests(self, element, block):
                  block[nest_index[-1][1]:], True)                      # nest
 
     def run(self, parent, blocks, tail=None, nest=False):
-        self._tag_data = self.parser.markdown.htmlStash.tag_data
+        self._tag_data = self.parser.md.htmlStash.tag_data
 
         self.parser.blockprocessors.tag_counter += 1
         tag = self._tag_data[self.parser.blockprocessors.tag_counter]
diff --git a/markdown/extensions/fenced_code.py b/markdown/extensions/fenced_code.py
index 4af8891a8..0ea15690e 100644
--- a/markdown/extensions/fenced_code.py
+++ b/markdown/extensions/fenced_code.py
@@ -25,7 +25,7 @@
 
 class FencedCodeExtension(Extension):
 
-    def extendMarkdown(self, md, md_globals):
+    def extendMarkdown(self, md):
         """ Add FencedBlockPreprocessor to the Markdown instance. """
         md.registerExtension(self)
 
@@ -57,7 +57,7 @@ def run(self, lines):
 
         # Check for code hilite extension
         if not self.checked_for_codehilite:
-            for ext in self.markdown.registeredExtensions:
+            for ext in self.md.registeredExtensions:
                 if isinstance(ext, CodeHiliteExtension):
                     self.codehilite_conf = ext.config
                     break
@@ -91,7 +91,7 @@ def run(self, lines):
                     code = self.CODE_WRAP % (lang,
                                              self._escape(m.group('code')))
 
-                placeholder = self.markdown.htmlStash.store(code, safe=True)
+                placeholder = self.md.htmlStash.store(code)
                 text = '%s\n%s\n%s' % (text[:m.start()],
                                        placeholder,
                                        text[m.end():])
@@ -106,7 +106,3 @@ def _escape(self, txt):
         txt = txt.replace('>', '&gt;')
         txt = txt.replace('"', '&quot;')
         return txt
-
-
-def makeExtension(*args, **kwargs):
-    return FencedCodeExtension(*args, **kwargs)
diff --git a/markdown/extensions/footnotes.py b/markdown/extensions/footnotes.py
index d8caae27c..454789375 100644
--- a/markdown/extensions/footnotes.py
+++ b/markdown/extensions/footnotes.py
@@ -33,7 +33,7 @@
 class FootnoteExtension(Extension):
     """ Footnote Extension. """
 
-    def __init__(self, *args, **kwargs):
+    def __init__(self, **kwargs):
         """ Setup configs. """
 
         self.config = {
@@ -49,14 +49,14 @@ def __init__(self, *args, **kwargs):
                  "The text string that links from the footnote "
                  "to the reader's place."]
         }
-        super(FootnoteExtension, self).__init__(*args, **kwargs)
+        super(FootnoteExtension, self).__init__(**kwargs)
 
         # In multiple invocations, emit links that don't get tangled.
         self.unique_prefix = 0
 
         self.reset()
 
-    def extendMarkdown(self, md, md_globals):
+    def extendMarkdown(self, md):
         """ Add pieces to Markdown. """
         md.registerExtension(self)
         self.parser = md.parser
@@ -96,7 +96,9 @@ def finder(element):
                 if child.tail:
                     if child.tail.find(self.getConfig("PLACE_MARKER")) > -1:
                         return child, element, False
-                finder(child)
+                child_res = finder(child)
+                if child_res is not None:
+                    return child_res
             return None
 
         res = finder(root)
@@ -312,8 +314,3 @@ def run(self, text):
             FN_BACKLINK_TEXT, self.footnotes.getConfig("BACKLINK_TEXT")
         )
         return text.replace(NBSP_PLACEHOLDER, "&#160;")
-
-
-def makeExtension(*args, **kwargs):
-    """ Return an instance of the FootnoteExtension """
-    return FootnoteExtension(*args, **kwargs)
diff --git a/markdown/extensions/headerid.py b/markdown/extensions/headerid.py
deleted file mode 100644
index 2cb20b97a..000000000
--- a/markdown/extensions/headerid.py
+++ /dev/null
@@ -1,97 +0,0 @@
-"""
-HeaderID Extension for Python-Markdown
-======================================
-
-Auto-generate id attributes for HTML headers.
-
-See <https://pythonhosted.org/Markdown/extensions/header_id.html>
-for documentation.
-
-Original code Copyright 2007-2011 [Waylan Limberg](http://achinghead.com/).
-
-All changes Copyright 2011-2014 The Python Markdown Project
-
-License: [BSD](http://www.opensource.org/licenses/bsd-license.php)
-
-"""
-
-from __future__ import absolute_import
-from __future__ import unicode_literals
-from . import Extension
-from ..treeprocessors import Treeprocessor
-from ..util import parseBoolValue
-from .toc import slugify, unique, stashedHTML2text
-import warnings
-
-
-class HeaderIdTreeprocessor(Treeprocessor):
-    """ Assign IDs to headers. """
-
-    IDs = set()
-
-    def run(self, doc):
-        start_level, force_id = self._get_meta()
-        slugify = self.config['slugify']
-        sep = self.config['separator']
-        for elem in doc:
-            if elem.tag in ['h1', 'h2', 'h3', 'h4', 'h5', 'h6']:
-                if force_id:
-                    if "id" in elem.attrib:
-                        id = elem.get('id')
-                    else:
-                        id = stashedHTML2text(''.join(elem.itertext()), self.md)
-                        id = slugify(id, sep)
-                    elem.set('id', unique(id, self.IDs))
-                if start_level:
-                    level = int(elem.tag[-1]) + start_level
-                    if level > 6:
-                        level = 6
-                    elem.tag = 'h%d' % level
-
-    def _get_meta(self):
-        """ Return meta data suported by this ext as a tuple """
-        level = int(self.config['level']) - 1
-        force = parseBoolValue(self.config['forceid'])
-        if hasattr(self.md, 'Meta'):
-            if 'header_level' in self.md.Meta:
-                level = int(self.md.Meta['header_level'][0]) - 1
-            if 'header_forceid' in self.md.Meta:
-                force = parseBoolValue(self.md.Meta['header_forceid'][0])
-        return level, force
-
-
-class HeaderIdExtension(Extension):
-    def __init__(self, *args, **kwargs):
-        # set defaults
-        self.config = {
-            'level': ['1', 'Base level for headers.'],
-            'forceid': ['True', 'Force all headers to have an id.'],
-            'separator': ['-', 'Word separator.'],
-            'slugify': [slugify, 'Callable to generate anchors']
-        }
-
-        super(HeaderIdExtension, self).__init__(*args, **kwargs)
-
-        warnings.warn(
-            'The HeaderId Extension is pending deprecation. Use the TOC Extension instead.',
-            PendingDeprecationWarning
-        )
-
-    def extendMarkdown(self, md, md_globals):
-        md.registerExtension(self)
-        self.processor = HeaderIdTreeprocessor()
-        self.processor.md = md
-        self.processor.config = self.getConfigs()
-        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 = set()
-
-
-def makeExtension(*args, **kwargs):
-    return HeaderIdExtension(*args, **kwargs)
diff --git a/markdown/extensions/legacy_attrs.py b/markdown/extensions/legacy_attrs.py
new file mode 100644
index 000000000..4c0c53559
--- /dev/null
+++ b/markdown/extensions/legacy_attrs.py
@@ -0,0 +1,45 @@
+"""
+Legacy Attributes Extension
+===========================
+
+An extension to Python Markdown which implements legacy attributes.
+
+Prior to Python-Markdown version 3.0, the Markdown class had an `enable_attributes`
+keyword which was on by default and provided for attributes to be defined for elements
+using the format `{@key=value}`. This extension is provided as a replacement for
+backward compatability. New documents should be authored using attr_lists. However,
+numerious documents exist which have been using the old attribute format for many
+years. This extension can be used to continue to render those documents correctly.
+"""
+
+import re
+from markdown.treeprocessors import Treeprocessor, isString
+from markdown.extensions import Extension
+
+
+ATTR_RE = re.compile("\{@([^\}]*)=([^\}]*)}")  # {@id=123}
+
+
+class LegacyAttrs(Treeprocessor):
+    def run(self, doc):
+        """Find and set values of attributes ({@key=value}). """
+        for el in doc.iter():
+            alt = el.get('alt', None)
+            if alt is not None:
+                el.set('alt', self.handleAttributes(el, alt))
+            if el.text and isString(el.text):
+                el.text = self.handleAttributes(el, el.text)
+            if el.tail and isString(el.tail):
+                el.tail = self.handleAttributes(el, el.tail)
+
+    def handleAttributes(self, el, txt):
+        """ Set attributes and return text without definitions. """
+        def attributeCallback(match):
+            el.set(match.group(1), match.group(2).replace('\n', ' '))
+        return ATTR_RE.sub(attributeCallback, txt)
+
+
+class LegacyAttrExtension(Extension):
+    def extendMarkdown(self, md):
+        la = LegacyAttrs(md)
+        md.treeprocessors.add('legacyattrs', la, '>inline')
diff --git a/markdown/extensions/legacy_em.py b/markdown/extensions/legacy_em.py
new file mode 100644
index 000000000..47963a8f1
--- /dev/null
+++ b/markdown/extensions/legacy_em.py
@@ -0,0 +1,30 @@
+'''
+Legacy Em Extension for Python-Markdown
+=======================================
+
+This extention provided legacy behavior for _connected_words_.
+
+Copyright 2015 The Python Markdown Project
+
+License: [BSD](http://www.opensource.org/licenses/bsd-license.php)
+
+'''
+
+from __future__ import absolute_import
+from __future__ import unicode_literals
+from . import Extension
+from ..inlinepatterns import SimpleTagPattern
+
+EMPHASIS_RE = r'(\*|_)(.+?)\2'
+STRONG_RE = r'(\*{2}|_{2})(.+?)\2'
+
+
+class LegacyEmExtension(Extension):
+    """ Add legacy_em extension to Markdown class."""
+
+    def extendMarkdown(self, md):
+        """ Modify inline patterns. """
+        md.inlinePatterns['strong'] = SimpleTagPattern(STRONG_RE, 'strong')
+        md.inlinePatterns['emphasis'] = SimpleTagPattern(EMPHASIS_RE, 'em')
+        del md.inlinePatterns['strong2']
+        del md.inlinePatterns['emphasis2']
diff --git a/markdown/extensions/meta.py b/markdown/extensions/meta.py
index 711235ef4..688c78b53 100644
--- a/markdown/extensions/meta.py
+++ b/markdown/extensions/meta.py
@@ -34,7 +34,7 @@
 class MetaExtension (Extension):
     """ Meta-Data extension for Python-Markdown. """
 
-    def extendMarkdown(self, md, md_globals):
+    def extendMarkdown(self, md):
         """ Add MetaPreprocessor to Markdown instance. """
         md.preprocessors.add("meta",
                              MetaPreprocessor(md),
@@ -70,9 +70,5 @@ def run(self, lines):
                 else:
                     lines.insert(0, line)
                     break  # no meta data - done
-        self.markdown.Meta = meta
+        self.md.Meta = meta
         return lines
-
-
-def makeExtension(*args, **kwargs):
-    return MetaExtension(*args, **kwargs)
diff --git a/markdown/extensions/nl2br.py b/markdown/extensions/nl2br.py
index 8acd60c2e..e70b87606 100644
--- a/markdown/extensions/nl2br.py
+++ b/markdown/extensions/nl2br.py
@@ -26,10 +26,6 @@
 
 class Nl2BrExtension(Extension):
 
-    def extendMarkdown(self, md, md_globals):
+    def extendMarkdown(self, md):
         br_tag = SubstituteTagPattern(BR_RE, 'br')
         md.inlinePatterns.add('nl', br_tag, '_end')
-
-
-def makeExtension(*args, **kwargs):
-    return Nl2BrExtension(*args, **kwargs)
diff --git a/markdown/extensions/sane_lists.py b/markdown/extensions/sane_lists.py
index 213c8a6fb..71448dd46 100644
--- a/markdown/extensions/sane_lists.py
+++ b/markdown/extensions/sane_lists.py
@@ -24,24 +24,29 @@
 
 class SaneOListProcessor(OListProcessor):
 
-    CHILD_RE = re.compile(r'^[ ]{0,3}((\d+\.))[ ]+(.*)')
     SIBLING_TAGS = ['ol']
+    LAZY_OL = False
+
+    def __init__(self, parser):
+        super(SaneOListProcessor, self).__init__(parser)
+        self.CHILD_RE = re.compile(r'^[ ]{0,%d}((\d+\.))[ ]+(.*)' %
+                                   (self.tab_length - 1))
 
 
 class SaneUListProcessor(UListProcessor):
 
-    CHILD_RE = re.compile(r'^[ ]{0,3}(([*+-]))[ ]+(.*)')
     SIBLING_TAGS = ['ul']
 
+    def __init__(self, parser):
+        super(SaneUListProcessor, self).__init__(parser)
+        self.CHILD_RE = re.compile(r'^[ ]{0,%d}(([*+-]))[ ]+(.*)' %
+                                   (self.tab_length - 1))
+
 
 class SaneListExtension(Extension):
     """ Add sane lists to Markdown. """
 
-    def extendMarkdown(self, md, md_globals):
+    def extendMarkdown(self, md):
         """ Override existing Processors. """
         md.parser.blockprocessors['olist'] = SaneOListProcessor(md.parser)
         md.parser.blockprocessors['ulist'] = SaneUListProcessor(md.parser)
-
-
-def makeExtension(*args, **kwargs):
-    return SaneListExtension(*args, **kwargs)
diff --git a/markdown/extensions/smart_strong.py b/markdown/extensions/smart_strong.py
deleted file mode 100644
index 58570bb55..000000000
--- a/markdown/extensions/smart_strong.py
+++ /dev/null
@@ -1,41 +0,0 @@
-'''
-Smart_Strong Extension for Python-Markdown
-==========================================
-
-This extention adds smarter handling of double underscores within words.
-
-See <https://pythonhosted.org/Markdown/extensions/smart_strong.html>
-for documentation.
-
-Original code Copyright 2011 [Waylan Limberg](http://achinghead.com)
-
-All changes Copyright 2011-2014 The Python Markdown Project
-
-License: [BSD](http://www.opensource.org/licenses/bsd-license.php)
-
-'''
-
-from __future__ import absolute_import
-from __future__ import unicode_literals
-from . import Extension
-from ..inlinepatterns import SimpleTagPattern
-
-SMART_STRONG_RE = r'(?<!\w)(_{2})(?!_)(.+?)(?<!_)\2(?!\w)'
-STRONG_RE = r'(\*{2})(.+?)\2'
-
-
-class SmartEmphasisExtension(Extension):
-    """ Add smart_emphasis extension to Markdown class."""
-
-    def extendMarkdown(self, md, md_globals):
-        """ Modify inline patterns. """
-        md.inlinePatterns['strong'] = SimpleTagPattern(STRONG_RE, 'strong')
-        md.inlinePatterns.add(
-            'strong2',
-            SimpleTagPattern(SMART_STRONG_RE, 'strong'),
-            '>emphasis2'
-        )
-
-
-def makeExtension(*args, **kwargs):
-    return SmartEmphasisExtension(*args, **kwargs)
diff --git a/markdown/extensions/smarty.py b/markdown/extensions/smarty.py
index 46e54c1ec..3956c911d 100644
--- a/markdown/extensions/smarty.py
+++ b/markdown/extensions/smarty.py
@@ -83,7 +83,7 @@
 
 from __future__ import unicode_literals
 from . import Extension
-from ..inlinepatterns import HtmlPattern
+from ..inlinepatterns import HtmlPattern, HTML_RE
 from ..odict import OrderedDict
 from ..treeprocessors import InlineProcessor
 
@@ -147,13 +147,15 @@
 remainingSingleQuotesRegex = "'"
 remainingDoubleQuotesRegex = '"'
 
+HTML_STRICT_RE = HTML_RE + r'(?!\>)'
+
 
 class SubstituteTextPattern(HtmlPattern):
-    def __init__(self, pattern, replace, markdown_instance):
+    def __init__(self, pattern, replace, md):
         """ Replaces matches with some text. """
         HtmlPattern.__init__(self, pattern)
         self.replace = replace
-        self.markdown = markdown_instance
+        self.md = md
 
     def handleMatch(self, m):
         result = ''
@@ -161,12 +163,12 @@ def handleMatch(self, m):
             if isinstance(part, int):
                 result += m.group(part)
             else:
-                result += self.markdown.htmlStash.store(part, safe=True)
+                result += self.md.htmlStash.store(part)
         return result
 
 
 class SmartyExtension(Extension):
-    def __init__(self, *args, **kwargs):
+    def __init__(self, **kwargs):
         self.config = {
             'smart_quotes': [True, 'Educate quotes'],
             'smart_angled_quotes': [False, 'Educate angled quotes'],
@@ -174,7 +176,7 @@ def __init__(self, *args, **kwargs):
             'smart_ellipses': [True, 'Educate ellipses'],
             'substitutions': [{}, 'Overwrite default substitutions'],
         }
-        super(SmartyExtension, self).__init__(*args, **kwargs)
+        super(SmartyExtension, self).__init__(**kwargs)
         self.substitutions = dict(substitutions)
         self.substitutions.update(self.getConfig('substitutions', default={}))
 
@@ -242,7 +244,7 @@ def educateQuotes(self, md):
         )
         self._addPatterns(md, patterns, 'quotes')
 
-    def extendMarkdown(self, md, md_globals):
+    def extendMarkdown(self, md):
         configs = self.getConfigs()
         self.inlinePatterns = OrderedDict()
         if configs['smart_ellipses']:
@@ -251,6 +253,9 @@ def extendMarkdown(self, md, md_globals):
             self.educateQuotes(md)
         if configs['smart_angled_quotes']:
             self.educateAngledQuotes(md)
+            # Override HTML_RE from inlinepatterns.py so that it does not
+            # process tags with duplicate closing quotes.
+            md.inlinePatterns["html"] = HtmlPattern(HTML_STRICT_RE, md)
         if configs['smart_dashes']:
             self.educateDashes(md)
         inlineProcessor = InlineProcessor(md)
diff --git a/markdown/extensions/tables.py b/markdown/extensions/tables.py
index 368321d46..7161db7e0 100644
--- a/markdown/extensions/tables.py
+++ b/markdown/extensions/tables.py
@@ -19,6 +19,7 @@
 from __future__ import unicode_literals
 from . import Extension
 from ..blockprocessors import BlockProcessor
+from ..inlinepatterns import BacktickPattern, BACKTICK_RE
 from ..util import etree
 
 
@@ -72,7 +73,11 @@ def _build_row(self, row, parent, align, border):
         for i, a in enumerate(align):
             c = etree.SubElement(tr, tag)
             try:
-                c.text = cells[i].strip()
+                if isinstance(cells[i], str) or isinstance(cells[i], unicode):
+                    c.text = cells[i].strip()
+                else:
+                    # we've already inserted a code element
+                    c.append(cells[i])
             except IndexError:  # pragma: no cover
                 c.text = ""
             if a:
@@ -85,18 +90,56 @@ def _split_row(self, row, border):
                 row = row[1:]
             if row.endswith('|'):
                 row = row[:-1]
-        return row.split('|')
+        return self._split(row, '|')
+
+    def _split(self, row, marker):
+        """ split a row of text with some code into a list of cells. """
+        if self._row_has_unpaired_backticks(row):
+            # fallback on old behaviour
+            return row.split(marker)
+        # modify the backtick pattern to only match at the beginning of the search string
+        backtick_pattern = BacktickPattern('^' + BACKTICK_RE)
+        elements = []
+        current = ''
+        i = 0
+        while i < len(row):
+            letter = row[i]
+            if letter == marker:
+                if current != '' or len(elements) == 0:
+                    # Don't append empty string unless it is the first element
+                    # The border is already removed when we get the row, then the line is strip()'d
+                    # If the first element is a marker, then we have an empty first cell
+                    elements.append(current)
+                current = ''
+            else:
+                match = backtick_pattern.getCompiledRegExp().match(row[i:])
+                if not match:
+                    current += letter
+                else:
+                    groups = match.groups()
+                    delim = groups[1]  # the code block delimeter (ie 1 or more backticks)
+                    row_contents = groups[2]  # the text contained inside the code block
+                    i += match.start(4) - 1  # jump pointer to the beginning of the rest of the text (group #4)
+                    element = delim + row_contents + delim  # reinstert backticks
+                    current += element
+            i += 1
+        elements.append(current)
+        return elements
+
+    def _row_has_unpaired_backticks(self, row):
+        count_total_backtick = row.count('`')
+        count_escaped_backtick = row.count('\`')
+        count_backtick = count_total_backtick - count_escaped_backtick
+        # odd number of backticks,
+        # we won't be able to build correct code blocks
+        return count_backtick & 1
 
 
 class TableExtension(Extension):
     """ Add tables to Markdown. """
 
-    def extendMarkdown(self, md, md_globals):
+    def extendMarkdown(self, md):
         """ Add an instance of TableProcessor to BlockParser. """
         md.parser.blockprocessors.add('table',
                                       TableProcessor(md.parser),
                                       '<hashheader')
-
-
-def makeExtension(*args, **kwargs):
-    return TableExtension(*args, **kwargs)
diff --git a/markdown/extensions/toc.py b/markdown/extensions/toc.py
index b3cf898f4..922463a61 100644
--- a/markdown/extensions/toc.py
+++ b/markdown/extensions/toc.py
@@ -49,11 +49,9 @@ def stashedHTML2text(text, md):
     def _html_sub(m):
         """ Substitute raw html with plain text. """
         try:
-            raw, safe = md.htmlStash.rawHtmlBlocks[int(m.group(1))]
+            raw = md.htmlStash.rawHtmlBlocks[int(m.group(1))]
         except (IndexError, TypeError):  # pragma: no cover
             return m.group(0)
-        if md.safeMode and not safe:  # pragma: no cover
-            return ''
         # Strip out tags and entities - leaveing text
         return re.sub(r'(<[^>]+>)|(&[\#a-zA-Z0-9]+;)', '', raw)
 
@@ -217,7 +215,7 @@ def build_etree_ul(toc_list, parent):
             return ul
 
         build_etree_ul(toc_list, div)
-        prettify = self.markdown.treeprocessors.get('prettify')
+        prettify = self.md.treeprocessors.get('prettify')
         if prettify:
             prettify.run(div)
         return div
@@ -237,7 +235,7 @@ def run(self, doc):
 
                 # Do not override pre-existing ids
                 if "id" not in el.attrib:
-                    innertext = stashedHTML2text(text, self.markdown)
+                    innertext = stashedHTML2text(text, self.md)
                     el.attrib["id"] = unique(self.slugify(innertext, self.sep), used_ids)
 
                 toc_tokens.append({
@@ -256,17 +254,17 @@ def run(self, doc):
             self.replace_marker(doc, div)
 
         # serialize and attach to markdown instance.
-        toc = self.markdown.serializer(div)
-        for pp in self.markdown.postprocessors.values():
+        toc = self.md.serializer(div)
+        for pp in self.md.postprocessors.values():
             toc = pp.run(toc)
-        self.markdown.toc = toc
+        self.md.toc = toc
 
 
 class TocExtension(Extension):
 
     TreeProcessorClass = TocTreeprocessor
 
-    def __init__(self, *args, **kwargs):
+    def __init__(self, **kwargs):
         self.config = {
             "marker": ['[TOC]',
                        'Text to find and replace with Table of Contents - '
@@ -287,9 +285,9 @@ def __init__(self, *args, **kwargs):
             'separator': ['-', 'Word separator. Defaults to "-".']
         }
 
-        super(TocExtension, self).__init__(*args, **kwargs)
+        super(TocExtension, self).__init__(**kwargs)
 
-    def extendMarkdown(self, md, md_globals):
+    def extendMarkdown(self, md):
         md.registerExtension(self)
         self.md = md
         self.reset()
@@ -303,7 +301,3 @@ def extendMarkdown(self, md, md_globals):
 
     def reset(self):
         self.md.toc = ''
-
-
-def makeExtension(*args, **kwargs):
-    return TocExtension(*args, **kwargs)
diff --git a/markdown/extensions/wikilinks.py b/markdown/extensions/wikilinks.py
index 94e1b6794..14f47a1c8 100644
--- a/markdown/extensions/wikilinks.py
+++ b/markdown/extensions/wikilinks.py
@@ -31,7 +31,7 @@ def build_url(label, base, end):
 
 class WikiLinkExtension(Extension):
 
-    def __init__(self, *args, **kwargs):
+    def __init__(self, **kwargs):
         self.config = {
             'base_url': ['/', 'String to append to beginning or URL.'],
             'end_url': ['/', 'String to append to end of URL.'],
@@ -39,9 +39,9 @@ def __init__(self, *args, **kwargs):
             'build_url': [build_url, 'Callable formats URL from label.'],
         }
 
-        super(WikiLinkExtension, self).__init__(*args, **kwargs)
+        super(WikiLinkExtension, self).__init__(**kwargs)
 
-    def extendMarkdown(self, md, md_globals):
+    def extendMarkdown(self, md):
         self.md = md
 
         # append to end of inline patterns
@@ -83,7 +83,3 @@ def _getMeta(self):
             if 'wiki_html_class' in self.md.Meta:
                 html_class = self.md.Meta['wiki_html_class'][0]
         return base_url, end_url, html_class
-
-
-def makeExtension(*args, **kwargs):
-    return WikiLinkExtension(*args, **kwargs)
diff --git a/markdown/inlinepatterns.py b/markdown/inlinepatterns.py
index 95d358d71..bfec05391 100644
--- a/markdown/inlinepatterns.py
+++ b/markdown/inlinepatterns.py
@@ -46,45 +46,38 @@
 from . import util
 from . import odict
 import re
-try:  # pragma: no cover
-    from urllib.parse import urlparse, urlunparse
-except ImportError:  # pragma: no cover
-    from urlparse import urlparse, urlunparse
 try:  # pragma: no cover
     from html import entities
 except ImportError:  # pragma: no cover
     import htmlentitydefs as entities
 
 
-def build_inlinepatterns(md_instance, **kwargs):
+def build_inlinepatterns(md, **kwargs):
     """ Build the default set of inline patterns for Markdown. """
     inlinePatterns = odict.OrderedDict()
     inlinePatterns["backtick"] = BacktickPattern(BACKTICK_RE)
-    inlinePatterns["escape"] = EscapePattern(ESCAPE_RE, md_instance)
-    inlinePatterns["reference"] = ReferencePattern(REFERENCE_RE, md_instance)
-    inlinePatterns["link"] = LinkPattern(LINK_RE, md_instance)
-    inlinePatterns["image_link"] = ImagePattern(IMAGE_LINK_RE, md_instance)
+    inlinePatterns["escape"] = EscapePattern(ESCAPE_RE, md)
+    inlinePatterns["reference"] = ReferencePattern(REFERENCE_RE, md)
+    inlinePatterns["link"] = LinkPattern(LINK_RE, md)
+    inlinePatterns["image_link"] = ImagePattern(IMAGE_LINK_RE, md)
     inlinePatterns["image_reference"] = ImageReferencePattern(
-        IMAGE_REFERENCE_RE, md_instance
+        IMAGE_REFERENCE_RE, md
     )
     inlinePatterns["short_reference"] = ReferencePattern(
-        SHORT_REF_RE, md_instance
+        SHORT_REF_RE, md
     )
-    inlinePatterns["autolink"] = AutolinkPattern(AUTOLINK_RE, md_instance)
-    inlinePatterns["automail"] = AutomailPattern(AUTOMAIL_RE, md_instance)
+    inlinePatterns["autolink"] = AutolinkPattern(AUTOLINK_RE, md)
+    inlinePatterns["automail"] = AutomailPattern(AUTOMAIL_RE, md)
     inlinePatterns["linebreak"] = SubstituteTagPattern(LINE_BREAK_RE, 'br')
-    if md_instance.safeMode != 'escape':
-        inlinePatterns["html"] = HtmlPattern(HTML_RE, md_instance)
-    inlinePatterns["entity"] = HtmlPattern(ENTITY_RE, md_instance)
+    inlinePatterns["html"] = HtmlPattern(HTML_RE, md)
+    inlinePatterns["entity"] = HtmlPattern(ENTITY_RE, md)
     inlinePatterns["not_strong"] = SimpleTextPattern(NOT_STRONG_RE)
     inlinePatterns["em_strong"] = DoubleTagPattern(EM_STRONG_RE, 'strong,em')
     inlinePatterns["strong_em"] = DoubleTagPattern(STRONG_EM_RE, 'em,strong')
     inlinePatterns["strong"] = SimpleTagPattern(STRONG_RE, 'strong')
     inlinePatterns["emphasis"] = SimpleTagPattern(EMPHASIS_RE, 'em')
-    if md_instance.smart_emphasis:
-        inlinePatterns["emphasis2"] = SimpleTagPattern(SMART_EMPHASIS_RE, 'em')
-    else:
-        inlinePatterns["emphasis2"] = SimpleTagPattern(EMPHASIS_2_RE, 'em')
+    inlinePatterns["strong2"] = SimpleTagPattern(SMART_STRONG_RE, 'strong')
+    inlinePatterns["emphasis2"] = SimpleTagPattern(SMART_EMPHASIS_RE, 'em')
     return inlinePatterns
 
 """
@@ -111,7 +104,13 @@ def build_inlinepatterns(md_instance, **kwargs):
 EMPHASIS_RE = r'(\*)([^\*]+)\2'
 
 # **strong**
-STRONG_RE = r'(\*{2}|_{2})(.+?)\2'
+STRONG_RE = r'(\*{2})(.+?)\2'
+
+# __smart__strong__
+SMART_STRONG_RE = r'(?<!\w)(_{2})(?!_)(.+?)(?<!_)\2(?!\w)'
+
+# _smart_emphasis_
+SMART_EMPHASIS_RE = r'(?<!\w)(_)(?!_)(.+?)(?<!_)\2(?!\w)'
 
 # ***strongem*** or ***em*strong**
 EM_STRONG_RE = r'(\*|_)\2{2}(.+?)\2(.*?)\2{2}'
@@ -119,12 +118,6 @@ def build_inlinepatterns(md_instance, **kwargs):
 # ***strong**em*
 STRONG_EM_RE = r'(\*|_)\2{2}(.+?)\2{2}(.*?)\2'
 
-# _smart_emphasis_
-SMART_EMPHASIS_RE = r'(?<!\w)(_)(?!_)(.+?)(?<!_)\2(?!\w)'
-
-# _emphasis_
-EMPHASIS_2_RE = r'(_)(.+?)\2'
-
 # [text](url) or [text](<url>) or [text](url "title")
 LINK_RE = NOIMG + BRK + \
     r'''\(\s*(<.*?>|((?:(?:\(.*?\))|[^\(\)]))*?)\s*((['"])(.*?)\12\s*)?\)'''
@@ -169,16 +162,6 @@ def dequote(string):
         return string
 
 
-ATTR_RE = re.compile("\{@([^\}]*)=([^\}]*)}")  # {@id=123}
-
-
-def handleAttributes(text, parent):
-    """Set values of an element based on attribute definitions ({@id=123})."""
-    def attributeCallback(match):
-        parent.set(match.group(1), match.group(2).replace('\n', ' '))
-    return ATTR_RE.sub(attributeCallback, text)
-
-
 """
 The pattern classes
 -----------------------------------------------------------------------------
@@ -188,7 +171,7 @@ def attributeCallback(match):
 class Pattern(object):
     """Base class that inline patterns subclass. """
 
-    def __init__(self, pattern, markdown_instance=None):
+    def __init__(self, pattern, md=None):
         """
         Create an instant of an inline pattern.
 
@@ -198,13 +181,11 @@ def __init__(self, pattern, markdown_instance=None):
 
         """
         self.pattern = pattern
-        self.compiled_re = re.compile("^(.*?)%s(.*?)$" % pattern,
+        self.compiled_re = re.compile("^(.*?)%s(.*)$" % pattern,
                                       re.DOTALL | re.UNICODE)
 
-        # Api for Markdown to pass safe_mode into instance
-        self.safe_mode = False
-        if markdown_instance:
-            self.markdown = markdown_instance
+        if md:
+            self.md = md
 
     def getCompiledRegExp(self):
         """ Return a compiled regular expression. """
@@ -229,23 +210,10 @@ def type(self):
     def unescape(self, text):
         """ Return unescaped text given text with an inline placeholder. """
         try:
-            stash = self.markdown.treeprocessors['inline'].stashed_nodes
+            stash = self.md.treeprocessors['inline'].stashed_nodes
         except KeyError:  # pragma: no cover
             return text
 
-        def itertext(el):  # pragma: no cover
-            ' Reimplement Element.itertext for older python versions '
-            tag = el.tag
-            if not isinstance(tag, util.string_type) 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:
@@ -254,7 +222,7 @@ def get_stash(m):
                     return value
                 else:
                     # An etree Element - return text content only
-                    return ''.join(itertext(value))
+                    return ''.join(value.itertext())
         return util.INLINE_PLACEHOLDER_RE.sub(get_stash, text)
 
 
@@ -269,7 +237,7 @@ class EscapePattern(Pattern):
 
     def handleMatch(self, m):
         char = m.group(2)
-        if char in self.markdown.ESCAPED_CHARS:
+        if char in self.md.ESCAPED_CHARS:
             return '%s%s%s' % (util.STX, ord(char), util.ETX)
         else:
             return None
@@ -329,13 +297,13 @@ class HtmlPattern(Pattern):
     """ Store raw inline html and return a placeholder. """
     def handleMatch(self, m):
         rawhtml = self.unescape(m.group(2))
-        place_holder = self.markdown.htmlStash.store(rawhtml)
+        place_holder = self.md.htmlStash.store(rawhtml)
         return place_holder
 
     def unescape(self, text):
         """ Return unescaped text given text with an inline placeholder. """
         try:
-            stash = self.markdown.treeprocessors['inline'].stashed_nodes
+            stash = self.md.treeprocessors['inline'].stashed_nodes
         except KeyError:  # pragma: no cover
             return text
 
@@ -344,7 +312,7 @@ def get_stash(m):
             value = stash.get(id)
             if value is not None:
                 try:
-                    return self.markdown.serializer(value)
+                    return self.md.serializer(value)
                 except:
                     return '\%s' % value
 
@@ -362,7 +330,7 @@ def handleMatch(self, m):
         if href:
             if href[0] == "<":
                 href = href[1:-1]
-            el.set("href", self.sanitize_url(self.unescape(href.strip())))
+            el.set("href", self.unescape(href.strip()))
         else:
             el.set("href", "")
 
@@ -371,52 +339,6 @@ def handleMatch(self, m):
             el.set("title", title)
         return el
 
-    def sanitize_url(self, url):
-        """
-        Sanitize a url against xss attacks in "safe_mode".
-
-        Rather than specifically blacklisting `javascript:alert("XSS")` and all
-        its aliases (see <http://ha.ckers.org/xss.html>), we whitelist known
-        safe url formats. Most urls contain a network location, however some
-        are known not to (i.e.: mailto links). Script urls do not contain a
-        location. Additionally, for `javascript:...`, the scheme would be
-        "javascript" but some aliases will appear to `urlparse()` to have no
-        scheme. On top of that relative links (i.e.: "foo/bar.html") have no
-        scheme. Therefore we must check "path", "parameters", "query" and
-        "fragment" for any literal colons. We don't check "scheme" for colons
-        because it *should* never have any and "netloc" must allow the form:
-        `username:password@host:port`.
-
-        """
-        if not self.markdown.safeMode:
-            # Return immediately bipassing parsing.
-            return url
-
-        try:
-            scheme, netloc, path, params, query, fragment = url = urlparse(url)
-        except ValueError:  # pragma: no cover
-            # Bad url - so bad it couldn't be parsed.
-            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:  # pragma: no cover
-            # This should not happen. Treat as suspect.
-            return ''
-
-        for part in url[2:]:
-            if ":" in part:
-                # A colon in "path", "parameters", "query"
-                # or "fragment" is suspect.
-                return ''
-
-        # Url passes all tests. Return url as-is.
-        return urlunparse(url)
-
 
 class ImagePattern(LinkPattern):
     """ Return a img element from the given match. """
@@ -427,18 +349,12 @@ def handleMatch(self, m):
             src = src_parts[0]
             if src[0] == "<" and src[-1] == ">":
                 src = src[1:-1]
-            el.set('src', self.sanitize_url(self.unescape(src)))
+            el.set('src', self.unescape(src))
         else:
             el.set('src', "")
         if len(src_parts) > 1:
             el.set('title', dequote(self.unescape(" ".join(src_parts[1:]))))
-
-        if self.markdown.enable_attributes:
-            truealt = handleAttributes(m.group(2), el)
-        else:
-            truealt = m.group(2)
-
-        el.set('alt', self.unescape(truealt))
+        el.set('alt', self.unescape(m.group(2)))
         return el
 
 
@@ -459,9 +375,9 @@ def handleMatch(self, m):
 
         # Clean up linebreaks in id
         id = self.NEWLINE_CLEANUP_RE.sub(' ', id)
-        if id not in self.markdown.references:  # ignore undefined refs
+        if id not in self.md.references:  # ignore undefined refs
             return None
-        href, title = self.markdown.references[id]
+        href, title = self.md.references[id]
 
         text = m.group(2)
         return self.makeTag(href, title, text)
@@ -469,7 +385,7 @@ def handleMatch(self, m):
     def makeTag(self, href, title, text):
         el = util.etree.Element('a')
 
-        el.set('href', self.sanitize_url(href))
+        el.set('href', href)
         if title:
             el.set('title', title)
 
@@ -481,13 +397,9 @@ class ImageReferencePattern(ReferencePattern):
     """ Match to a stored reference and return img element. """
     def makeTag(self, href, title, text):
         el = util.etree.Element("img")
-        el.set("src", self.sanitize_url(href))
+        el.set("src", href)
         if title:
             el.set("title", title)
-
-        if self.markdown.enable_attributes:
-            text = handleAttributes(text, el)
-
         el.set("alt", self.unescape(text))
         return el
 
diff --git a/markdown/postprocessors.py b/markdown/postprocessors.py
index 2d4dcb589..4c5bf7410 100644
--- a/markdown/postprocessors.py
+++ b/markdown/postprocessors.py
@@ -15,10 +15,10 @@
 import re
 
 
-def build_postprocessors(md_instance, **kwargs):
+def build_postprocessors(md, **kwargs):
     """ Build the default postprocessors for Markdown. """
     postprocessors = odict.OrderedDict()
-    postprocessors["raw_html"] = RawHtmlPostprocessor(md_instance)
+    postprocessors["raw_html"] = RawHtmlPostprocessor(md)
     postprocessors["amp_substitute"] = AndSubstitutePostprocessor()
     postprocessors["unescape"] = UnescapePostprocessor()
     return postprocessors
@@ -49,35 +49,19 @@ class RawHtmlPostprocessor(Postprocessor):
     """ Restore raw html to the document. """
 
     def run(self, text):
-        """ Iterate over html stash and restore "safe" html. """
-        for i in range(self.markdown.htmlStash.html_counter):
-            html, safe = self.markdown.htmlStash.rawHtmlBlocks[i]
-            if self.markdown.safeMode and not safe:
-                if str(self.markdown.safeMode).lower() == 'escape':
-                    html = self.escape(html)
-                elif str(self.markdown.safeMode).lower() == 'remove':
-                    html = ''
-                else:
-                    html = self.markdown.html_replacement_text
-            if (self.isblocklevel(html) and
-               (safe or not self.markdown.safeMode)):
+        """ Iterate over html stash and restore html. """
+        for i in range(self.md.htmlStash.html_counter):
+            html = self.md.htmlStash.rawHtmlBlocks[i]
+            if self.isblocklevel(html):
                 text = text.replace(
-                    "<p>%s</p>" %
-                    (self.markdown.htmlStash.get_placeholder(i)),
+                    "<p>%s</p>" % (self.md.htmlStash.get_placeholder(i)),
                     html + "\n"
                 )
             text = text.replace(
-                self.markdown.htmlStash.get_placeholder(i), html
+                self.md.htmlStash.get_placeholder(i), html
             )
         return text
 
-    def escape(self, html):
-        """ Basic html escaping """
-        html = html.replace('&', '&amp;')
-        html = html.replace('<', '&lt;')
-        html = html.replace('>', '&gt;')
-        return html.replace('"', '&quot;')
-
     def isblocklevel(self, html):
         m = re.match(r'^\<\/?([^ >]+)', html)
         if m:
diff --git a/markdown/preprocessors.py b/markdown/preprocessors.py
index 7fd38d331..74d1c9fef 100644
--- a/markdown/preprocessors.py
+++ b/markdown/preprocessors.py
@@ -13,13 +13,12 @@
 import re
 
 
-def build_preprocessors(md_instance, **kwargs):
+def build_preprocessors(md, **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)
+    preprocessors['normalize_whitespace'] = NormalizeWhitespace(md)
+    preprocessors["html_block"] = HtmlBlockPreprocessor(md)
+    preprocessors["reference"] = ReferencePreprocessor(md)
     return preprocessors
 
 
@@ -51,7 +50,7 @@ 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 = source.expandtabs(self.md.tab_length)
         source = re.sub(r'(?<=\n) +\n', '\n', source)
         return source.split('\n')
 
@@ -168,7 +167,7 @@ def _nested_markdown_in_html(self, items):
                     self._stringindex_to_listindex(data_index, items[i:]) + i
                 if 'markdown' in attrs.keys():
                     items[i] = items[i][left_index:]  # remove opening tag
-                    placeholder = self.markdown.htmlStash.store_tag(
+                    placeholder = self.md.htmlStash.store_tag(
                         left_tag, attrs, i + 1, right_listindex + 1)
                     items.insert(i, placeholder)
                     if len(items) - right_listindex <= 1:  # last nest, no tail
@@ -178,10 +177,11 @@ def _nested_markdown_in_html(self, items):
                 else:  # raw html
                     if len(items) - right_listindex <= 1:  # last element
                         right_listindex -= 1
-                    offset = 1 if i == right_listindex else 0
-                    placeholder = self.markdown.htmlStash.store('\n\n'.join(
-                        items[i:right_listindex + offset]))
-                    del items[i:right_listindex + offset]
+                    if right_listindex <= i:
+                        right_listindex = i + 1
+                    placeholder = self.md.htmlStash.store('\n\n'.join(
+                        items[i:right_listindex]))
+                    del items[i:right_listindex]
                     items.insert(i, placeholder)
         return items
 
@@ -232,12 +232,12 @@ def run(self, lines):
                             and self._equal_tags(left_tag, right_tag):
                         if self.markdown_in_raw and 'markdown' in attrs.keys():
                             block = block[left_index:-len(right_tag) - 2]
-                            new_blocks.append(self.markdown.htmlStash.
+                            new_blocks.append(self.md.htmlStash.
                                               store_tag(left_tag, attrs, 0, 2))
                             new_blocks.extend([block])
                         else:
                             new_blocks.append(
-                                self.markdown.htmlStash.store(block.strip()))
+                                self.md.htmlStash.store(block.strip()))
                         continue
                     else:
                         # if is block level tag and is not complete
@@ -247,7 +247,7 @@ def run(self, lines):
                             in_tag = True
                         else:
                             new_blocks.append(
-                                self.markdown.htmlStash.store(block.strip())
+                                self.md.htmlStash.store(block.strip())
                             )
                         continue
 
@@ -275,18 +275,18 @@ def run(self, lines):
                             right_index = len(items) + 3
                         else:
                             right_index = len(items) + 2
-                        new_blocks.append(self.markdown.htmlStash.store_tag(
+                        new_blocks.append(self.md.htmlStash.store_tag(
                             left_tag, attrs, 0, right_index))
-                        placeholderslen = len(self.markdown.htmlStash.tag_data)
+                        placeholderslen = len(self.md.htmlStash.tag_data)
                         new_blocks.extend(
                             self._nested_markdown_in_html(items))
-                        nests = len(self.markdown.htmlStash.tag_data) - \
+                        nests = len(self.md.htmlStash.tag_data) - \
                             placeholderslen
-                        self.markdown.htmlStash.tag_data[-1 - nests][
+                        self.md.htmlStash.tag_data[-1 - nests][
                             'right_index'] += nests - 2
                     else:
                         new_blocks.append(
-                            self.markdown.htmlStash.store('\n\n'.join(items)))
+                            self.md.htmlStash.store('\n\n'.join(items)))
                     items = []
 
         if items:
@@ -298,16 +298,16 @@ def run(self, lines):
                 else:
                     right_index = len(items) + 2
                 new_blocks.append(
-                    self.markdown.htmlStash.store_tag(
+                    self.md.htmlStash.store_tag(
                         left_tag, attrs, 0, right_index))
-                placeholderslen = len(self.markdown.htmlStash.tag_data)
+                placeholderslen = len(self.md.htmlStash.tag_data)
                 new_blocks.extend(self._nested_markdown_in_html(items))
-                nests = len(self.markdown.htmlStash.tag_data) - placeholderslen
-                self.markdown.htmlStash.tag_data[-1 - nests][
+                nests = len(self.md.htmlStash.tag_data) - placeholderslen
+                self.md.htmlStash.tag_data[-1 - nests][
                     'right_index'] += nests - 2
             else:
                 new_blocks.append(
-                    self.markdown.htmlStash.store('\n\n'.join(items)))
+                    self.md.htmlStash.store('\n\n'.join(items)))
             new_blocks.append('\n')
 
         new_text = "\n\n".join(new_blocks)
@@ -338,7 +338,7 @@ def run(self, lines):
                     if tm:
                         lines.pop(0)
                         t = tm.group(2) or tm.group(3) or tm.group(4)
-                self.markdown.references[id] = (link, t)
+                self.md.references[id] = (link, t)
             else:
                 new_text.append(line)
 
diff --git a/markdown/test.py b/markdown/test.py
new file mode 100644
index 000000000..28d9e712e
--- /dev/null
+++ b/markdown/test.py
@@ -0,0 +1,232 @@
+import os
+import sys
+import markdown
+import codecs
+import difflib
+try:
+    import nose
+    from nose.plugins.errorclass import ErrorClass, ErrorClassPlugin
+except ImportError as e:  # pragma: no cover
+    msg = e.args[0]
+    msg = msg + ". The nose testing framework is required to run the Python-" \
+        "Markdown tests. Run `pip install nose` to install the latest version."
+    e.args = (msg,) + e.args[1:]
+    raise
+try:
+    import tidylib
+except ImportError:  # pragma: no cover
+    tidylib = None
+try:
+    import yaml
+except ImportError as e:  # pragma: no cover
+    msg = e.args[0]
+    msg = msg + ". A YAML library is required to run the Python-Markdown " \
+        "tests. Run `pip install pyyaml` to install the latest version."
+    e.args = (msg,) + e.args[1:]
+    raise
+
+
+test_dir = os.path.abspath(os.path.join(os.getcwd(), 'tests'))
+
+
+class MarkdownSyntaxError(Exception):
+    pass
+
+
+class MarkdownPlugin(ErrorClassPlugin):
+    """ Add MarkdownSyntaxError and ensure proper formatting. """
+    mdsyntax = ErrorClass(
+        MarkdownSyntaxError,
+        label='MarkdownSyntaxError',
+        isfailure=True
+    )
+    enabled = True
+
+    def configure(self, options, conf):
+        self.conf = conf
+
+    def addError(self, test, err):  # pragma: no cover
+        """ Ensure other plugins see the error by returning nothing here. """
+        pass
+
+    def formatError(self, test, err):  # pragma: no cover
+        """ Remove unnessecary and unhelpful traceback from error report. """
+        et, ev, tb = err
+        if et.__name__ == 'MarkdownSyntaxError':
+            return et, ev, ''
+        return err
+
+
+class YamlConfig():
+    def __init__(self, defaults, filename):
+        """ Set defaults and load config file if it exists. """
+        self.DEFAULT_SECTION = 'DEFAULT'
+        self._defaults = defaults
+        self._config = {}
+        if os.path.exists(filename):
+            with codecs.open(filename, encoding="utf-8") as f:
+                self._config = yaml.load(f)
+
+    def get(self, section, option):
+        """ Get config value for given section and option key. """
+        if section in self._config and option in self._config[section]:
+            return self._config[section][option]
+        return self._defaults[option]
+
+    def get_section(self, file):
+        """ Get name of config section for given file. """
+        filename = os.path.basename(file)
+        if filename in self._config:
+            return filename
+        else:
+            return self.DEFAULT_SECTION
+
+    def get_args(self, file):
+        """ Get args to pass to markdown from config for a given file. """
+        args = {}
+        section = self.get_section(file)
+        if section in self._config:
+            for key in self._config[section].keys():
+                # Filter out args unique to testing framework
+                if key not in self._defaults.keys():
+                    args[key] = self.get(section, key)
+        return args
+
+
+def get_config(dir_name):
+    """ Get config for given directory name. """
+    defaults = {
+        'normalize': False,
+        'skip': False,
+        'input_ext': '.txt',
+        'output_ext': '.html'
+    }
+    config = YamlConfig(defaults, os.path.join(dir_name, 'test.cfg'))
+    return config
+
+
+def normalize(text):
+    """ Normalize whitespace for a string of html using tidylib. """
+    output, errors = tidylib.tidy_fragment(text, options={
+        'drop_empty_paras': 0,
+        'fix_backslash': 0,
+        'fix_bad_comments': 0,
+        'fix_uri': 0,
+        'join_styles': 0,
+        'lower_literals': 0,
+        'merge_divs': 0,
+        'output_xhtml': 1,
+        'quote_ampersand': 0,
+        'newline': 'LF'
+    })
+    return output
+
+
+class CheckSyntax(object):
+    def __init__(self, description=None):
+        if description:
+            self.description = 'TestSyntax: "%s"' % description
+
+    def __call__(self, file, config):
+        """ Compare expected output to actual output and report result. """
+        cfg_section = config.get_section(file)
+        if config.get(cfg_section, 'skip'):
+            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:
+            # Normalize line endings
+            # (on windows, git may have altered line endings).
+            expected_output = f.read().replace("\r\n", "\n")
+        output = markdown.markdown(input, **config.get_args(file))
+        if tidylib and config.get(cfg_section, 'normalize'):
+            # Normalize whitespace with tidylib before comparing.
+            expected_output = normalize(expected_output)
+            output = normalize(output)
+        elif config.get(cfg_section, 'normalize'):  # pragma: no cover
+            # Tidylib is not available. Skip this test.
+            raise nose.plugins.skip.SkipTest(
+                'Test skipped. Tidylib not available on system.'
+            )
+        diff = [l for l in difflib.unified_diff(
+            expected_output.splitlines(True),
+            output.splitlines(True),
+            output_file,
+            'actual_output.html',
+            n=3
+        )]
+        if diff:  # pragma: no cover
+            raise MarkdownSyntaxError(
+                'Output from "%s" failed to match expected '
+                'output.\n\n%s' % (input_file, ''.join(diff))
+            )
+
+
+def TestSyntax():
+    for dir_name, sub_dirs, files in os.walk(test_dir):
+        # Get dir specific config settings.
+        config = get_config(dir_name)
+        # Loop through files and generate tests.
+        for file in files:
+            root, ext = os.path.splitext(file)
+            if ext == config.get(config.get_section(file), 'input_ext'):
+                path = os.path.join(dir_name, root)
+                check_syntax = CheckSyntax(
+                    description=os.path.relpath(path, test_dir)
+                )
+                yield check_syntax, path, config
+
+
+def generate(file, config):  # pragma: no cover
+    """ Write expected output file for given input. """
+    cfg_section = config.get_section(file)
+    if config.get(cfg_section, 'skip') or config.get(cfg_section, 'normalize'):
+        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)
+        markdown.markdownFromFile(input=input_file, output=output_file,
+                                  encoding='utf-8', **config.get_args(file))
+    else:
+        print('Already up-to-date:', file)
+
+
+def generate_all():  # pragma: no cover
+    """ Generate expected output for all outdated tests. """
+    for dir_name, sub_dirs, files in os.walk(test_dir):
+        # Get dir specific config settings.
+        config = get_config(dir_name)
+        # Loop through files and generate tests.
+        for file in files:
+            root, ext = os.path.splitext(file)
+            if ext == config.get(config.get_section(file), 'input_ext'):
+                generate(os.path.join(dir_name, root), config)
+
+
+def main():
+    if len(sys.argv) > 1 and sys.argv[1] == "update":  # pragma: no cover
+        if len(sys.argv) > 2:
+            config = get_config(os.path.dirname(sys.argv[2]))
+            root, ext = os.path.splitext(sys.argv[2])
+            if ext == config.get(
+                config.get_section(os.path.basename(root)), 'input_ext'
+            ):
+                generate(root, config)
+            else:
+                print(
+                    sys.argv[2],
+                    'does not have a valid file extension. Check config.'
+                )
+        else:
+            generate_all()
+    else:
+        nose.main(addplugins=[MarkdownPlugin()])
+
+
+if __name__ == '__main__':  # pragma: no cover
+    main()
diff --git a/markdown/test_tools.py b/markdown/test_tools.py
new file mode 100644
index 000000000..6c7b02775
--- /dev/null
+++ b/markdown/test_tools.py
@@ -0,0 +1,44 @@
+import unittest
+import textwrap
+from .core import markdown
+
+
+class TestCase(unittest.TestCase):
+    """
+    A unittest.TestCase subclass with helpers for testing Markdown output.
+    
+    Define `default_kwargs` as a dict of keywords to pass to Markdown for each
+    test. The defaults can be overridden on individual tests.
+    
+    The `assertMarkdownRenders` method accepts the source text, the expected
+    output, and any keywords to pass to Markdown. The `default_kwargs` are used
+    except where overridden by `kwargs`. The ouput and expected ouput are passed
+    to `TestCase.assertMultiLineEqual`. An AssertionError is raised with a diff
+    if the actual output does not equal the expected output.
+    
+    The `dedent` method is available to dedent triple-quoted strings if
+    necessary.
+    
+    In all other respects, behaves as unittest.TestCase.
+    """
+
+    default_kwargs = {}
+
+    def assertMarkdownRenders(self, source, expected, **kwargs):
+        """
+        Test that source Markdown text renders to expected output with given keywords.
+        """
+        
+        kws = dict(**self.default_kwargs)
+        kws.update(kwargs)
+        output = markdown(source, **kws)
+        self.assertMultiLineEqual(output, expected)
+
+    def dedent(self, text):
+        """
+        Dedent text.
+        """
+        
+        # TODO: If/when actual output ends with a newline, then use:
+        # return textwrap.dedent(text.strip('/n'))
+        return textwrap.dedent(text).strip()
diff --git a/markdown/treeprocessors.py b/markdown/treeprocessors.py
index d06f19288..564c6836a 100644
--- a/markdown/treeprocessors.py
+++ b/markdown/treeprocessors.py
@@ -2,14 +2,13 @@
 from __future__ import absolute_import
 from . import util
 from . import odict
-from . import inlinepatterns
 
 
-def build_treeprocessors(md_instance, **kwargs):
+def build_treeprocessors(md, **kwargs):
     """ Build the default treeprocessors for Markdown. """
     treeprocessors = odict.OrderedDict()
-    treeprocessors["inline"] = InlineProcessor(md_instance)
-    treeprocessors["prettify"] = PrettifyTreeprocessor(md_instance)
+    treeprocessors["inline"] = InlineProcessor(md)
+    treeprocessors["prettify"] = PrettifyTreeprocessor(md)
     return treeprocessors
 
 
@@ -52,7 +51,7 @@ def __init__(self, md):
         self.__placeholder_length = 4 + len(self.__placeholder_prefix) \
                                       + len(self.__placeholder_suffix)
         self.__placeholder_re = util.INLINE_PLACEHOLDER_RE
-        self.markdown = md
+        self.md = md
         self.inlinePatterns = md.inlinePatterns
 
     def __makePlaceholder(self, type):
@@ -311,25 +310,8 @@ def run(self, tree):
                     stack.append(child)
 
             for element, lst in insertQueue:
-                if self.markdown.enable_attributes:
-                    if element.text and isString(element.text):
-                        element.text = inlinepatterns.handleAttributes(
-                            element.text, element
-                        )
-                i = 0
-                for newChild in lst:
-                    if self.markdown.enable_attributes:
-                        # Processing attributes
-                        if newChild.tail and isString(newChild.tail):
-                            newChild.tail = inlinepatterns.handleAttributes(
-                                newChild.tail, element
-                            )
-                        if newChild.text and isString(newChild.text):
-                            newChild.text = inlinepatterns.handleAttributes(
-                                newChild.text, newChild
-                            )
+                for i, newChild in enumerate(lst):
                     element.insert(i, newChild)
-                    i += 1
         return tree
 
 
diff --git a/markdown/util.py b/markdown/util.py
index d3d48f099..ed3b93db0 100644
--- a/markdown/util.py
+++ b/markdown/util.py
@@ -123,9 +123,9 @@ class AtomicString(text_type):
 
 
 class Processor(object):
-    def __init__(self, markdown_instance=None):
-        if markdown_instance:
-            self.markdown = markdown_instance
+    def __init__(self, md=None):
+        if md:
+            self.md = md
 
 
 class HtmlStash(object):
@@ -141,7 +141,7 @@ def __init__(self):
         self.tag_counter = 0
         self.tag_data = []  # list of dictionaries in the order tags appear
 
-    def store(self, html, safe=False):
+    def store(self, html):
         """
         Saves an HTML segment for later reinsertion.  Returns a
         placeholder string that needs to be inserted into the
@@ -150,12 +150,11 @@ def store(self, html, safe=False):
         Keyword arguments:
 
         * html: an html segment
-        * safe: label an html segment as safe for safemode
 
         Returns : a placeholder string
 
         """
-        self.rawHtmlBlocks.append((html, safe))
+        self.rawHtmlBlocks.append(html)
         placeholder = self.get_placeholder(self.html_counter)
         self.html_counter += 1
         return placeholder
diff --git a/mkdocs.yml b/mkdocs.yml
new file mode 100644
index 000000000..cf0df3e45
--- /dev/null
+++ b/mkdocs.yml
@@ -0,0 +1,30 @@
+site_name: Python-Markdown
+
+repo_url: https://github.com/Python-Markdown/markdown
+
+use_directory_urls: true
+
+theme: readthedocs
+
+pages:
+    - ['index.md', 'Python-Markdown']
+    - ['install.md', 'Installation']
+    - ['reference.md', 'Library Reference']
+    - ['cli.md', 'Command Line']
+    - ['extensions/index.md', 'Extensions']
+    - ['extensions/api.md', 'Extension API']
+    - ['test_suite.md', 'Test Suite']
+    - ['change_log/index.md', 'Change Log']
+    - ['change_log/release-2.6.md', 'Release Notes for v.2.6']
+    - ['change_log/release-2.5.md', 'Release Notes for v.2.5']
+    - ['change_log/release-2.4.md', 'Release Notes for v.2.4']
+    - ['change_log/release-2.3.md', 'Release Notes for v.2.3']
+    - ['change_log/release-2.2.md', 'Release Notes for v.2.2']
+    - ['change_log/release-2.1.md', 'Release Notes for v.2.1']
+    - ['change_log/release-2.0.md', 'Release Notes for v.2.0']
+    - ['authors.md', 'Authors']
+
+markdown_extensions:
+    - extra
+    - admonition
+    - smarty
\ No newline at end of file
diff --git a/run-tests.py b/run-tests.py
deleted file mode 100755
index 574895377..000000000
--- a/run-tests.py
+++ /dev/null
@@ -1,23 +0,0 @@
-#!/usr/bin/env python
-
-import tests
-import os
-import sys
-
-if len(sys.argv) > 1 and sys.argv[1] == "update":
-    if len(sys.argv) > 2:
-        config = tests.get_config(os.path.dirname(sys.argv[2]))
-        root, ext = os.path.splitext(sys.argv[2])
-        if ext == config.get(
-            config.get_section(os.path.basename(root)), 'input_ext'
-        ):
-            tests.generate(root, config)
-        else:
-            print(
-                sys.argv[2],
-                'does not have a valid file extension. Check config.'
-            )
-    else:
-        tests.generate_all()
-else:
-    tests.run()
diff --git a/setup.cfg b/setup.cfg
deleted file mode 100644
index b0695bbf5..000000000
--- a/setup.cfg
+++ /dev/null
@@ -1 +0,0 @@
-[nosetests]
diff --git a/setup.py b/setup.py
index fec2dcb2f..45cda5df5 100755
--- a/setup.py
+++ b/setup.py
@@ -1,29 +1,9 @@
 #!/usr/bin/env python
 
-from __future__ import with_statement
-import sys
-import os
-from distutils.core import setup
-from distutils.command.install_scripts import install_scripts
-from distutils.command.build import build
-from distutils.core import Command
-from distutils.util import change_root, newer
-import codecs
-import imp
+from setuptools import setup
+from markdown import __version__, __version_info__
 
 
-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',
@@ -31,184 +11,11 @@ def get_version():
     'rc':    '4 - Beta',
     'final': '5 - Production/Stable'
 }
-if version_info[3] == 'alpha' and version_info[4] == 0:
+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
-# "markdown.py" as the default config on some systems will cause the script to
-# try to import itself rather than the library which will raise an error.
-SCRIPT_NAME = 'markdown_py'
-
-
-class md_install_scripts(install_scripts):
-
-    """ Customized install_scripts. Create markdown_py.bat for win32. """
-
-    def run(self):
-        install_scripts.run(self)
-
-        if sys.platform == 'win32':
-            try:
-                script_dir = os.path.join(sys.prefix, 'Scripts')
-                script_path = os.path.join(script_dir, SCRIPT_NAME)
-                bat_str = '@"%s" "%s" %%*' % (sys.executable, script_path)
-                bat_path = os.path.join(
-                    self.install_dir, '%s.bat' % SCRIPT_NAME
-                )
-                f = open(bat_path, 'w')
-                f.write(bat_str)
-                f.close()
-                print('Created: %s' % bat_path)
-            except Exception:
-                _, err, _ = sys.exc_info()  # for both 2.x & 3.x compatability
-                print('ERROR: Unable to create %s: %s' % (bat_path, err))
-
-
-class build_docs(Command):
-
-    """ Build markdown documentation into html."""
-
-    description = '"build" documentation (convert markdown text to html)'
-
-    user_options = [
-        ('build-base=', 'd', 'directory to "build" to'),
-        ('force', 'f', 'forcibly build everything (ignore file timestamps)'),
-    ]
-
-    boolean_options = ['force']
-
-    def initialize_options(self):
-        self.build_base = None
-        self.force = None
-        self.docs = None
-        self.sitemap = ''
-
-    def finalize_options(self):
-        self.set_undefined_options(
-            'build',
-            ('build_base', 'build_base'),
-            ('force', 'force')
-        )
-        self.docs = self._get_docs()
-
-    def _get_docs(self):
-        for root, dirs, files in os.walk('docs'):
-            for file in files:
-                if not file.startswith('_'):
-                    path = os.path.join(root, file)
-                    yield path
-
-    def _get_context(self, src, path):
-        """ Build and return context to pass to template. """
-        # set defaults
-        c = {
-            'title':      '',
-            'prev_url':   '',
-            'prev_title': '',
-            'next_url':   '',
-            'next_title': '',
-            'crumb':      '',
-            'version':    version,
-        }
-        c['body'] = self.md.convert(src)
-        c['toc'] = self.md.toc
-        for k, v in self.md.Meta.items():
-            c[k] = ' '.join(v)
-        self.md.reset()
-        # Manipulate path
-        path = path[len(os.path.join(self.build_base, 'docs/')):]
-        dir, file = os.path.split(path)
-        name, ext = os.path.splitext(file)
-        parts = [x for x in dir.split(os.sep) if x]
-        c['source'] = '%s.txt' % name
-        c['base'] = '../' * len(parts)
-        # Build page title
-        if name.lower() != 'index' or parts:
-            c['page_title'] = '%s &#8212; Python Markdown' % c['title']
-        else:
-            c['page_title'] = 'Python Markdown'
-        # Build crumb trail
-        crumbs = []
-        ctemp = '<li><a href="%s">%s</a> &raquo;</li>'
-        for n, part in enumerate(parts):
-            href = ('../' * n) + 'index.html'
-            label = part.replace('_', ' ').capitalize()
-            crumbs.append(ctemp % (href, label))
-        if c['title'] and name.lower() != 'index':
-            crumbs.append(ctemp % (file, c['title']))
-        c['crumb'] = '\n'.join(crumbs)
-        return c
-
-    def run(self):
-        # Before importing markdown, tweak sys.path to import from the
-        # build directory (2to3 might have run on the library).
-        bld_cmd = self.get_finalized_command("build")
-        sys.path.insert(0, bld_cmd.build_lib)
-        try:
-            import markdown
-        except ImportError:
-            print('skipping build_docs: Markdown "import" failed!')
-        else:
-            with codecs.open('docs/_template.html', encoding='utf-8') as f:
-                template = f.read()
-            self.md = markdown.Markdown(
-                extensions=[
-                    'extra',
-                    'toc(permalink=true)',
-                    'meta',
-                    'admonition',
-                    'smarty'
-                ]
-            )
-            for infile in self.docs:
-                outfile, ext = os.path.splitext(infile)
-                if ext == '.txt':
-                    # Copy src to .txt file
-                    srcfile = outfile + '.txt'
-                    srcfile = change_root(self.build_base, srcfile)
-                    self.mkpath(os.path.split(srcfile)[0])
-                    self.copy_file(infile, srcfile)
-                    # Render html file
-                    outfile += '.html'
-                    outfile = change_root(self.build_base, outfile)
-                    self.mkpath(os.path.split(outfile)[0])
-                    if self.force or newer(infile, outfile):
-                        if self.verbose:
-                            print('Converting %s -> %s' % (infile, outfile))
-                        if not self.dry_run:
-                            with codecs.open(infile, encoding='utf-8') as f:
-                                src = f.read()
-                            out = template % self._get_context(src, outfile)
-                            doc = open(outfile, 'wb')
-                            doc.write(out.encode('utf-8'))
-                            doc.close()
-                else:
-                    outfile = change_root(self.build_base, infile)
-                    self.mkpath(os.path.split(outfile)[0])
-                    self.copy_file(infile, outfile)
-
-
-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 not self.no_build_docs
+    DEVSTATUS = dev_status_map[__version_info__[3]]
 
-    sub_commands = build.sub_commands + [('build_docs', has_docs)]
 
 long_description = '''
 This is a Python implementation of John Gruber's Markdown_.
@@ -218,8 +25,8 @@ def has_docs(self):
 supported by the `Available Extensions`_.
 
 .. _Markdown: http://daringfireball.net/projects/markdown/
-.. _Features: https://pythonhosted.org/Markdown/index.html#Features
-.. _`Available Extensions`: https://pythonhosted.org/Markdown/extensions/index.html
+.. _Features: https://pythonhosted.org/Markdown/#Features
+.. _`Available Extensions`: https://pythonhosted.org/Markdown/extensions/
 
 Support
 =======
@@ -228,14 +35,14 @@ def has_docs(self):
 `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
+.. _`bug tracker`: http://github.com/Python-Markdown/markdown/issues
 '''
 
 setup(
     name='Markdown',
-    version=version,
+    version=__version__,
     url='https://pythonhosted.org/Markdown/',
-    download_url='http://pypi.python.org/packages/source/M/Markdown/Markdown-%s.tar.gz' % version,
+    download_url='http://pypi.python.org/packages/source/M/Markdown/Markdown-%s-py2.py3-none-any.whl' % __version__,
     description='Python implementation of Markdown.',
     long_description=long_description,
     author='Manfred Stienstra, Yuri takhteyev and Waylan limberg',
@@ -244,11 +51,32 @@ def has_docs(self):
     maintainer_email='waylan.limberg [at] icloud.com',
     license='BSD License',
     packages=['markdown', 'markdown.extensions'],
-    scripts=['bin/%s' % SCRIPT_NAME],
-    cmdclass={
-        'install_scripts': md_install_scripts,
-        'build_docs': build_docs,
-        'build': md_build
+    entry_points={
+        'console_scripts': [
+            'markdown = markdown.__main__:run',
+            'mdtests = markdown.test:main'
+        ],
+        # Register the built in extensions
+        'markdown.extensions': [
+            'abbr = markdown.extensions.abbr:AbbrExtension',
+            'admonition = markdown.extensions.admonition:AdmonitionExtension',
+            'attr_list = markdown.extensions.attr_list:AttrListExtension',
+            'codehilite = markdown.extensions.codehilite:CodeHiliteExtension',
+            'def_list = markdown.extensions.def_list:DefListExtension',
+            'extra = markdown.extensions.extra:ExtraExtension',
+            'fenced_code = markdown.extensions.fenced_code:FencedCodeExtension',
+            'footnotes = markdown.extensions.footnotes:FootnoteExtension',
+            'headerid = markdown.extensions.headerid:HeaderIdExtension',
+            'meta = markdown.extensions.meta:MetaExtension',
+            'nl2br = markdown.extensions.nl2br:Nl2BrExtension',
+            'sane_lists = markdown.extensions.sane_lists:SaneListExtension',
+            'smarty = markdown.extensions.smarty:SmartyExtension',
+            'tables = markdown.extensions.tables:TableExtension',
+            'toc = markdown.extensions.toc:TocExtension',
+            'wikilinks = markdown.extensions.wikilinks:WikiLinkExtension',
+            'legacy_attrs = markdown.extensions.legacy_attrs:LegacyAttrExtension',
+            'legacy_em = markdown.extensions.legacy_em:LegacyEmExtension',
+        ]
     },
     classifiers=[
         'Development Status :: %s' % DEVSTATUS,
diff --git a/test-requirements.txt b/test-requirements.txt
index 4bfa76399..5913e4564 100644
--- a/test-requirements.txt
+++ b/test-requirements.txt
@@ -1,4 +1,5 @@
 nose
-coverage
+coverage<4.0
 pyyaml
-pytidylib
\ No newline at end of file
+pytidylib
+mkdocs
diff --git a/tests/__init__.py b/tests/__init__.py
index f759ceb74..e69de29bb 100644
--- a/tests/__init__.py
+++ b/tests/__init__.py
@@ -1,182 +0,0 @@
-import os
-import markdown
-import codecs
-import difflib
-try:
-    import nose
-except ImportError as e:
-    msg = e.args[0]
-    msg = msg + ". The nose testing framework is required to run the Python-" \
-        "Markdown tests. Run `pip install nose` to install the latest version."
-    e.args = (msg,) + e.args[1:]
-    raise
-from .plugins import HtmlOutput, Markdown, MarkdownSyntaxError
-try:
-    import tidylib
-except ImportError:
-    tidylib = None
-try:
-    import yaml
-except ImportError as e:
-    msg = e.args[0]
-    msg = msg + ". A YAML library is required to run the Python-Markdown " \
-        "tests. Run `pip install pyyaml` to install the latest version."
-    e.args = (msg,) + e.args[1:]
-    raise
-
-test_dir = os.path.abspath(os.path.dirname(__file__))
-
-
-class YamlConfig():
-    def __init__(self, defaults, filename):
-        """ Set defaults and load config file if it exists. """
-        self.DEFAULT_SECTION = 'DEFAULT'
-        self._defaults = defaults
-        self._config = {}
-        if os.path.exists(filename):
-            with codecs.open(filename, encoding="utf-8") as f:
-                self._config = yaml.load(f)
-
-    def get(self, section, option):
-        """ Get config value for given section and option key. """
-        if section in self._config and option in self._config[section]:
-            return self._config[section][option]
-        return self._defaults[option]
-
-    def get_section(self, file):
-        """ Get name of config section for given file. """
-        filename = os.path.basename(file)
-        if filename in self._config:
-            return filename
-        else:
-            return self.DEFAULT_SECTION
-
-    def get_args(self, file):
-        """ Get args to pass to markdown from config for a given file. """
-        args = {}
-        section = self.get_section(file)
-        if section in self._config:
-            for key in self._config[section].keys():
-                # Filter out args unique to testing framework
-                if key not in self._defaults.keys():
-                    args[key] = self.get(section, key)
-        return args
-
-
-def get_config(dir_name):
-    """ Get config for given directory name. """
-    defaults = {
-        'normalize': False,
-        'skip': False,
-        'input_ext': '.txt',
-        'output_ext': '.html'
-    }
-    config = YamlConfig(defaults, os.path.join(dir_name, 'test.cfg'))
-    return config
-
-
-def normalize(text):
-    """ Normalize whitespace for a string of html using tidylib. """
-    output, errors = tidylib.tidy_fragment(text, options={
-        'drop_empty_paras': 0,
-        'fix_backslash': 0,
-        'fix_bad_comments': 0,
-        'fix_uri': 0,
-        'join_styles': 0,
-        'lower_literals': 0,
-        'merge_divs': 0,
-        'output_xhtml': 1,
-        'quote_ampersand': 0,
-        'newline': 'LF'
-    })
-    return output
-
-
-class CheckSyntax(object):
-    def __init__(self, description=None):
-        if description:
-            self.description = 'TestSyntax: "%s"' % description
-
-    def __call__(self, file, config):
-        """ Compare expected output to actual output and report result. """
-        cfg_section = config.get_section(file)
-        if config.get(cfg_section, 'skip'):
-            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:
-            # Normalize line endings
-            # (on windows, git may have altered line endings).
-            expected_output = f.read().replace("\r\n", "\n")
-        output = markdown.markdown(input, **config.get_args(file))
-        if tidylib and config.get(cfg_section, 'normalize'):
-            # Normalize whitespace with tidylib before comparing.
-            expected_output = normalize(expected_output)
-            output = normalize(output)
-        elif config.get(cfg_section, 'normalize'):
-            # Tidylib is not available. Skip this test.
-            raise nose.plugins.skip.SkipTest(
-                'Test skipped. Tidylib not available on system.'
-            )
-        diff = [l for l in difflib.unified_diff(
-            expected_output.splitlines(True),
-            output.splitlines(True),
-            output_file,
-            'actual_output.html',
-            n=3
-        )]
-        if diff:
-            raise MarkdownSyntaxError(
-                'Output from "%s" failed to match expected '
-                'output.\n\n%s' % (input_file, ''.join(diff))
-            )
-
-
-def TestSyntax():
-    for dir_name, sub_dirs, files in os.walk(test_dir):
-        # Get dir specific config settings.
-        config = get_config(dir_name)
-        # Loop through files and generate tests.
-        for file in files:
-            root, ext = os.path.splitext(file)
-            if ext == config.get(config.get_section(file), 'input_ext'):
-                path = os.path.join(dir_name, root)
-                check_syntax = CheckSyntax(
-                    description=os.path.relpath(path, test_dir)
-                )
-                yield check_syntax, path, config
-
-
-def generate(file, config):
-    """ Write expected output file for given input. """
-    cfg_section = config.get_section(file)
-    if config.get(cfg_section, 'skip') or config.get(cfg_section, 'normalize'):
-        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)
-        markdown.markdownFromFile(input=input_file, output=output_file,
-                                  encoding='utf-8', **config.get_args(file))
-    else:
-        print('Already up-to-date:', file)
-
-
-def generate_all():
-    """ Generate expected output for all outdated tests. """
-    for dir_name, sub_dirs, files in os.walk(test_dir):
-        # Get dir specific config settings.
-        config = get_config(dir_name)
-        # Loop through files and generate tests.
-        for file in files:
-            root, ext = os.path.splitext(file)
-            if ext == config.get(config.get_section(file), 'input_ext'):
-                generate(os.path.join(dir_name, root), config)
-
-
-def run():
-    nose.main(addplugins=[HtmlOutput(), Markdown()])
diff --git a/tests/basic/inline-html-simple.html b/tests/basic/inline-html-simple.html
index 2159e1dcb..0f2633cfe 100644
--- a/tests/basic/inline-html-simple.html
+++ b/tests/basic/inline-html-simple.html
@@ -57,4 +57,5 @@
 
 <hr class="foo" id="bar" >
 
-<p><some <a href="http://example.com">weird</a> stuff></p>
\ No newline at end of file
+<p><some <a href="http://example.com">weird</a> stuff></p>
+<p><some>&gt; &lt;<unbalanced>&gt; &lt;<brackets></p>
\ No newline at end of file
diff --git a/tests/basic/inline-html-simple.txt b/tests/basic/inline-html-simple.txt
index 7210750d8..359aca4e5 100644
--- a/tests/basic/inline-html-simple.txt
+++ b/tests/basic/inline-html-simple.txt
@@ -68,3 +68,5 @@ Hr's:
 <hr class="foo" id="bar" >
 
 <some [weird](http://example.com) stuff>
+
+<some>> <<unbalanced>> <<brackets>
\ No newline at end of file
diff --git a/tests/extensions/extra/footnote_placeholder_depth.html b/tests/extensions/extra/footnote_placeholder_depth.html
new file mode 100644
index 000000000..9793a493e
--- /dev/null
+++ b/tests/extensions/extra/footnote_placeholder_depth.html
@@ -0,0 +1,13 @@
+<blockquote>
+<blockquote>
+<div class="footnote">
+<hr />
+<ol>
+<li id="fn:1">
+<p>A Footnote.&#160;<a class="footnote-backref" href="#fnref:1" rev="footnote" title="Jump back to footnote 1 in the text">&#8617;</a></p>
+</li>
+</ol>
+</div>
+<p>Some text with a footnote<sup id="fnref:1"><a class="footnote-ref" href="#fn:1" rel="footnote">1</a></sup>.</p>
+</blockquote>
+</blockquote>
\ No newline at end of file
diff --git a/tests/extensions/extra/footnote_placeholder_depth.txt b/tests/extensions/extra/footnote_placeholder_depth.txt
new file mode 100644
index 000000000..cfe87c00f
--- /dev/null
+++ b/tests/extensions/extra/footnote_placeholder_depth.txt
@@ -0,0 +1,5 @@
+>> ///Footnotes Go Here///
+>>
+>> Some text with a footnote[^1].
+
+[^1]: A Footnote.
diff --git a/tests/extensions/extra/raw-html.html b/tests/extensions/extra/raw-html.html
index 7acb2eeb7..9c0222f56 100644
--- a/tests/extensions/extra/raw-html.html
+++ b/tests/extensions/extra/raw-html.html
@@ -41,4 +41,9 @@
 
 <p>Markdown is <em>still</em> active here.</p>
 </div>
-<p>Markdown is <em>active again</em> here.</p>
\ No newline at end of file
+<p>Markdown is <em>active again</em> here.</p>
+<div>
+<p>foo bar</p>
+<p><em>bar</em>
+</p>
+</div>
\ No newline at end of file
diff --git a/tests/extensions/extra/raw-html.txt b/tests/extensions/extra/raw-html.txt
index 72f530b99..da24af03f 100644
--- a/tests/extensions/extra/raw-html.txt
+++ b/tests/extensions/extra/raw-html.txt
@@ -65,3 +65,9 @@ Markdown is *still* active here.
 </div>
 
 Markdown is *active again* here.
+
+<div markdown=1>
+foo bar
+
+<em>bar</em>
+</div>
diff --git a/tests/extensions/extra/tables.html b/tests/extensions/extra/tables.html
index 85a998d5e..783c20574 100644
--- a/tests/extensions/extra/tables.html
+++ b/tests/extensions/extra/tables.html
@@ -168,4 +168,86 @@ <h2>Table Tests</h2>
 </tr>
 </thead>
 <tbody></tbody>
+</table>
+<p>More inline code block tests</p>
+<table>
+<thead>
+<tr>
+<th>Column 1</th>
+<th>Column 2</th>
+<th>Column 3</th>
+</tr>
+</thead>
+<tbody>
+<tr>
+<td>word 1</td>
+<td>word 2</td>
+<td>word 3</td>
+</tr>
+<tr>
+<td>word 1</td>
+<td><code>word 2</code></td>
+<td>word 3</td>
+</tr>
+<tr>
+<td>word 1</td>
+<td>`word 2</td>
+<td>word 3</td>
+</tr>
+<tr>
+<td>word 1</td>
+<td>`word 2</td>
+<td>word 3</td>
+</tr>
+<tr>
+<td>word 1</td>
+<td><code>word |2</code></td>
+<td>word 3</td>
+</tr>
+<tr>
+<td>words</td>
+<td><code>some | code</code></td>
+<td>more words</td>
+</tr>
+<tr>
+<td>words</td>
+<td><code>some | code</code></td>
+<td>more words</td>
+</tr>
+<tr>
+<td>words</td>
+<td><code>some | code</code></td>
+<td>more words</td>
+</tr>
+<tr>
+<td>words</td>
+<td><code>some ` | ` code</code></td>
+<td>more words</td>
+</tr>
+<tr>
+<td>words</td>
+<td><code>some ` | ` code</code></td>
+<td>more words</td>
+</tr>
+<tr>
+<td>words</td>
+<td><code>some ` | ` code</code></td>
+<td>more words</td>
+</tr>
+</tbody>
+</table>
+<p>A test for issue #440:</p>
+<table>
+<thead>
+<tr>
+<th>foo</th>
+<th>bar</th>
+</tr>
+</thead>
+<tbody>
+<tr>
+<td>foo</td>
+<td>(<code>bar</code>) and <code>baz</code>.</td>
+</tr>
+</tbody>
 </table>
\ No newline at end of file
diff --git a/tests/extensions/extra/tables.txt b/tests/extensions/extra/tables.txt
index 8acc3c65b..c9ca70a1a 100644
--- a/tests/extensions/extra/tables.txt
+++ b/tests/extensions/extra/tables.txt
@@ -52,4 +52,26 @@ Four spaces is a code block:
     Content Cell | Content Cell
 
 | First Header | Second Header |
-| ------------ | ------------- |
\ No newline at end of file
+| ------------ | ------------- |
+
+More inline code block tests
+
+Column 1 | Column 2 | Column 3
+---------|----------|---------
+word 1   | word 2   | word 3
+word 1   | `word 2` | word 3
+word 1   | \`word 2 | word 3
+word 1   | `word 2 | word 3
+word 1   | `word |2` | word 3
+words    |`` some | code `` | more words
+words    |``` some | code ``` | more words
+words    |```` some | code ```` | more words
+words    |`` some ` | ` code `` | more words
+words    |``` some ` | ` code ``` | more words
+words    |```` some ` | ` code ```` | more words
+
+A test for issue #440:
+
+foo | bar
+--- | ---
+foo | (`bar`) and `baz`.
\ No newline at end of file
diff --git a/tests/extensions/extra/test.cfg b/tests/extensions/extra/test.cfg
index d956e2a1f..a2f03e9fb 100644
--- a/tests/extensions/extra/test.cfg
+++ b/tests/extensions/extra/test.cfg
@@ -1,36 +1,36 @@
 DEFAULT:
     extensions:
-        - markdown.extensions.extra
+        - extra
 
 loose_def_list:
     extensions:
-        - markdown.extensions.def_list
+        - def_list
 
 simple_def-lists:
     extensions:
-        - markdown.extensions.def_list
+        - def_list
 
 abbr:
     extensions:
-        - markdown.extensions.abbr
+        - abbr
 
 footnotes:
     extensions:
-        - markdown.extensions.footnotes
+        - footnotes
 
 tables:
     extensions:
-        - markdown.extensions.tables
+        - tables
 
 tables_and_attr_list:
     extensions:
-        - markdown.extensions.tables
-        - markdown.extensions.attr_list
+        - tables
+        - attr_list
 
 extra_config:
     extensions:
-        - markdown.extensions.extra
+        - extra
     extension_configs:
-        markdown.extensions.extra:
-            markdown.extensions.footnotes:
+        extra:
+            footnotes:
                 PLACE_MARKER: ~~~placemarker~~~
diff --git a/tests/extensions/sane_lists.html b/tests/extensions/sane_lists.html
index b9fe007db..ed51b4d9b 100644
--- a/tests/extensions/sane_lists.html
+++ b/tests/extensions/sane_lists.html
@@ -18,4 +18,15 @@
 <ul>
 <li>Unordered again
 1. not a list item</li>
-</ul>
\ No newline at end of file
+</ul>
+<ol start="3">
+<li>Bird</li>
+<li>McHale</li>
+<li>Parish</li>
+</ol>
+<p>Not a list</p>
+<ol start="3">
+<li>Bird</li>
+<li>McHale</li>
+<li>Parish</li>
+</ol>
\ No newline at end of file
diff --git a/tests/extensions/sane_lists.txt b/tests/extensions/sane_lists.txt
index 51981b3a3..464149ff5 100644
--- a/tests/extensions/sane_lists.txt
+++ b/tests/extensions/sane_lists.txt
@@ -14,3 +14,13 @@ Paragraph
 
 * Unordered again
 1. not a list item
+
+3. Bird
+1. McHale
+8. Parish
+
+Not a list
+
+3. Bird
+1. McHale
+8. Parish
\ No newline at end of file
diff --git a/tests/extensions/smarty.html b/tests/extensions/smarty.html
index d41e467b8..e37e377f9 100644
--- a/tests/extensions/smarty.html
+++ b/tests/extensions/smarty.html
@@ -16,6 +16,7 @@
 &ldquo;<a href="http://example.com">Link</a>&rdquo; &mdash; she said.</p>
 <p>&ldquo;Ellipsis within quotes&hellip;&rdquo;</p>
 <p>Кавычки-&laquo;ёлочки&raquo;<br />
+&laquo;hello&raquo;<br />
 Anführungszeichen-&raquo;Chevrons&laquo;</p>
 <hr />
 <p>Escaped -- ndash<br />
@@ -23,7 +24,9 @@
 Escaped ellipsis...</p>
 <p>&lsquo;Escaped "quotes" in real ones&rsquo;<br />
 '&ldquo;Real&rdquo; quotes in escaped ones'</p>
-<p>Skip <code>"code" -- --- 'spans' ...</code>.</p>
+<p>Skip <code>&lt;&lt;all&gt;&gt; "code" -- --- 'spans' ...</code>.</p>
 <pre><code>Also skip "code" 'blocks'
 foo -- bar --- baz ...
-</code></pre>
\ No newline at end of file
+</code></pre>
+<p>A line that &lsquo;wraps&rsquo; with
+<em>emphasis</em> at the beginning of the next line.</p>
\ No newline at end of file
diff --git a/tests/extensions/smarty.txt b/tests/extensions/smarty.txt
index 48430bb18..12e5c9537 100644
--- a/tests/extensions/smarty.txt
+++ b/tests/extensions/smarty.txt
@@ -19,6 +19,7 @@ em-dashes (---) and ellipes (...)
 "Ellipsis within quotes..."
 
 Кавычки-<<ёлочки>>  
+<<hello>>  
 Anführungszeichen->>Chevrons<<
 
 --- -- ---
@@ -30,7 +31,10 @@ Escaped ellipsis\...
 'Escaped \"quotes\" in real ones'  
 \'"Real" quotes in escaped ones\'
 
-Skip `"code" -- --- 'spans' ...`.
+Skip `<<all>> "code" -- --- 'spans' ...`.
 
     Also skip "code" 'blocks'
 	foo -- bar --- baz ...
+
+A line that 'wraps' with
+*emphasis* at the beginning of the next line.
diff --git a/tests/extensions/test.cfg b/tests/extensions/test.cfg
index 7e27b2966..5fa1d0096 100644
--- a/tests/extensions/test.cfg
+++ b/tests/extensions/test.cfg
@@ -1,73 +1,73 @@
 attr_list:
     extensions:
-        - markdown.extensions.attr_list
-        - markdown.extensions.def_list
-        - markdown.extensions.smarty
+        - attr_list
+        - def_list
+        - smarty
 
 codehilite:
     extensions:
-        - markdown.extensions.codehilite
+        - codehilite
     # This passes or not based on version of pygments.
     skip: True
 
 toc:
     extensions:
-        - markdown.extensions.toc
+        - toc
 
 toc_invalid:
     extensions:
-        - markdown.extensions.toc
+        - toc
 
 toc_out_of_order:
     extensions:
-        - markdown.extensions.toc
+        - toc
 
 toc_nested:
     extensions:
-        - markdown.extensions.toc
+        - toc
     extension_configs:
-        markdown.extensions.toc:
+        toc:
             permalink: True
 
 toc_nested2:
     extensions:
-        - markdown.extensions.toc
+        - toc
     extension_configs:
-        markdown.extensions.toc:
+        toc:
             permalink: "[link]"
 
 toc_nested_list:
     extensions:
-        - markdown.extensions.toc
+        - toc
 
 wikilinks:
     extensions:
-        - markdown.extensions.wikilinks
+        - wikilinks
 
 fenced_code:
     extensions:
-        - markdown.extensions.fenced_code
+        - fenced_code
 
 github_flavored:
      extensions:
-         - markdown.extensions.fenced_code
+         - fenced_code
 
 sane_lists:
     extensions:
-        - markdown.extensions.sane_lists
+        - sane_lists
 
 nl2br_w_attr_list:
      extensions:
-         - markdown.extensions.nl2br
-         - markdown.extensions.attr_list
+         - nl2br
+         - attr_list
 
 admonition:
     extensions:
-        - markdown.extensions.admonition
+        - admonition
 
 smarty:
     extensions:
-        - markdown.extensions.smarty
+        - smarty
     extension_configs:
-        markdown.extensions.smarty:
+        smarty:
             smart_angled_quotes: True
diff --git a/tests/misc/attributes-image-ref.html b/tests/misc/attributes-image-ref.html
deleted file mode 100644
index 6974420de..000000000
--- a/tests/misc/attributes-image-ref.html
+++ /dev/null
@@ -1 +0,0 @@
-<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
deleted file mode 100644
index a21697129..000000000
--- a/tests/misc/attributes-image-ref.txt
+++ /dev/null
@@ -1,4 +0,0 @@
-![img{@id=foo}][img]
-
-  [img]: http://example.com/i.jpg
-
diff --git a/tests/misc/attributes2.html b/tests/misc/attributes2.html
deleted file mode 100644
index b78fee0ec..000000000
--- a/tests/misc/attributes2.html
+++ /dev/null
@@ -1,6 +0,0 @@
-<p id="TABLE.OF.CONTENTS"></p>
-<ul>
-<li id="TABLEOFCONTENTS"></li>
-</ul>
-<p id="TABLEOFCONTENTS">Or in the middle of the text </p>
-<p id="tableofcontents"></p>
\ No newline at end of file
diff --git a/tests/misc/attributes2.txt b/tests/misc/attributes2.txt
deleted file mode 100644
index d635cb2f2..000000000
--- a/tests/misc/attributes2.txt
+++ /dev/null
@@ -1,10 +0,0 @@
-{@id=TABLE.OF.CONTENTS}
-
-
-* {@id=TABLEOFCONTENTS}
-
-
-Or in the middle of the text {@id=TABLEOFCONTENTS}
-
-{@id=tableofcontents}
-
diff --git a/tests/misc/bidi.html b/tests/misc/bidi.html
index ffe04dc73..3cc044483 100644
--- a/tests/misc/bidi.html
+++ b/tests/misc/bidi.html
@@ -1,7 +1,7 @@
 <p><strong>Python</strong>（パイソン）は、<a href="http://en.wikipedia.org/wiki/Guido_van_Rossum">Guido van Rossum</a> によって作られたオープンソースのオブジェクト指向スクリプト言語。<a href="http://ja.wikipedia.org/wiki/Perl">Perl</a>とともに欧米で広く普及している。イギリスのテレビ局 BBC が製作したコメディ番組『空飛ぶモンティ・パイソン』にちなんで名付けられた。 (Pythonには、爬虫類のニシキヘビの意味があり、Python言語のマスコットやアイコンとして使われることがある。)</p>
 <p>|||||||||||||||||||||||||||||THIS SHOULD BE LTR|||||||||||||||||||||||||</p>
-<p dir="rtl">|||||||||||||||||||||||||||||THIS SHOULD BE RTL|||||||||||||||||||||||||  </p>
-<p dir="ltr">(<strong>بايثون</strong> لغة برمجة حديثة بسيطة، واضحة، سريعة ، تستخدم أسلوب البرمجة الكائنية (THIS SHOULD BE LTR ) وقابلة للتطوير    بالإضافة إلى أنها مجانية و مفتوح </p>
+<p>|||||||||||||||||||||||||||||THIS SHOULD BE RTL|||||||||||||||||||||||||  </p>
+<p>(<strong>بايثون</strong> لغة برمجة حديثة بسيطة، واضحة، سريعة ، تستخدم أسلوب البرمجة الكائنية (THIS SHOULD BE LTR ) وقابلة للتطوير  بالإضافة إلى أنها مجانية و مفتوح </p>
 <p>پایتون زبان برنامه‌نویسی تفسیری و سطح بالا ، شی‌گرا و یک زبان برنامه‌نویسی تفسیری سمت سرور قدرتمند است که توسط گیدو ون روسوم در سال ۱۹۹۰ ساخته شد. این زبان در ویژگی‌ها شبیه پرل، روبی، اسکیم، اسمال‌تاک و تی‌سی‌ال است و از مدیریت خودکار حافظه استفاده می‌کند</p>
 <p>Python，是一种面向对象的、直譯式的计算机程序设计语言，也是一种功能强大而完善的通用型语言，已经具有十多年的发展历史，成熟且稳定。</p>
 <p>ބްލޫ ވޭލްގެ ދޫ މަތީގައި އެއްފަހަރާ 50 މީހުންނަށް ތިބެވިދާނެވެ. ބޮޑު މަހުގެ ދުލަކީ އެހާމެ ބޮޑު އެއްޗެކެވެ.</p>
diff --git a/tests/misc/bidi.txt b/tests/misc/bidi.txt
index f11ff1cc6..7e6dbeacd 100644
--- a/tests/misc/bidi.txt
+++ b/tests/misc/bidi.txt
@@ -2,10 +2,10 @@
 
 |||||||||||||||||||||||||||||THIS SHOULD BE LTR|||||||||||||||||||||||||
 
-|||||||||||||||||||||||||||||THIS SHOULD BE RTL||||||||||||||||||||||||| {@dir=rtl} 
+|||||||||||||||||||||||||||||THIS SHOULD BE RTL|||||||||||||||||||||||||  
 
 
-(**بايثون** لغة برمجة حديثة بسيطة، واضحة، سريعة ، تستخدم أسلوب البرمجة الكائنية (THIS SHOULD BE LTR ) وقابلة للتطوير  {@dir=ltr}  بالإضافة إلى أنها مجانية و مفتوح 
+(**بايثون** لغة برمجة حديثة بسيطة، واضحة، سريعة ، تستخدم أسلوب البرمجة الكائنية (THIS SHOULD BE LTR ) وقابلة للتطوير  بالإضافة إلى أنها مجانية و مفتوح 
 
 
 
diff --git a/tests/misc/em_strong_complex.html b/tests/misc/em_strong_complex.html
index 3befa70d9..65faddfad 100644
--- a/tests/misc/em_strong_complex.html
+++ b/tests/misc/em_strong_complex.html
@@ -1,7 +1,7 @@
 <p><em><strong>test test</strong> test test</em></p>
 <p><strong><em>test test</em> test test</strong></p>
 <p><strong><em>test</em></strong></p>
-<p><strong>test</strong>_</p>
+<p><strong>test</strong></p>
 <p><strong><em>test</em> test</strong>_</p>
 <p><strong><em>test</em> test</strong></p>
 <p><em>test_test test_test</em></p>
diff --git a/tests/misc/em_strong_complex.txt b/tests/misc/em_strong_complex.txt
index 66f4ff192..042597184 100644
--- a/tests/misc/em_strong_complex.txt
+++ b/tests/misc/em_strong_complex.txt
@@ -4,7 +4,7 @@ ___test test_ test test__
 
 ___test___
 
-__test___
+__test__
 
 ___test_ test___
 
diff --git a/tests/misc/multi-test.html b/tests/misc/multi-test.html
index 2c8fe9eeb..9fe96481c 100644
--- a/tests/misc/multi-test.html
+++ b/tests/misc/multi-test.html
@@ -1,5 +1,3 @@
-<h1 id="inthebeginning">Header </h1>
-<p>Now, let's try something <em class="special">inline</em>, to see if it works</p>
 <p>Blah blah blah <a href="http://www.slashdot.org">http://www.slashdot.org</a></p>
 <ul>
 <li>Basic list</li>
diff --git a/tests/misc/multi-test.txt b/tests/misc/multi-test.txt
index c4ab1c12b..feaac3124 100644
--- a/tests/misc/multi-test.txt
+++ b/tests/misc/multi-test.txt
@@ -1,9 +1,3 @@
-
-# Header {@id=inthebeginning}
-
-Now, let's try something *inline{@class=special}*, to see if it works
-
-
 Blah blah blah <http://www.slashdot.org>
 
 * Basic list
diff --git a/tests/misc/uche.html b/tests/misc/uche.html
index e62329d4c..9134e9591 100644
--- a/tests/misc/uche.html
+++ b/tests/misc/uche.html
@@ -1,3 +1,3 @@
 <p><img alt="asif" src="http://fourthought.com/images/ftlogo.png" title="Fourthought logo" /></p>
-<p><a href="http://fourthought.com/"><img alt="" src="http://fourthought.com/images/ftlogo.png" style="float: left; margin: 10px; border: none;" title="Fourthought logo" /></a></p>
+<p><a href="http://fourthought.com/"><img alt="Alt text" src="http://fourthought.com/images/ftlogo.png" title="Fourthought logo" /></a></p>
 <p><a href="http://link.com/"><img alt="text" src="x" /></a></p>
\ No newline at end of file
diff --git a/tests/misc/uche.txt b/tests/misc/uche.txt
index a3dda1a69..625d2ae18 100644
--- a/tests/misc/uche.txt
+++ b/tests/misc/uche.txt
@@ -1,7 +1,6 @@
 ![asif](http://fourthought.com/images/ftlogo.png "Fourthought logo")
 
-[![{@style=float: left; margin: 10px; border:
-none;}](http://fourthought.com/images/ftlogo.png "Fourthought
+[![Alt text](http://fourthought.com/images/ftlogo.png "Fourthought
 logo")](http://fourthought.com/)
 
 [![text](x)](http://link.com/)
diff --git a/tests/misc/underscores.html b/tests/misc/underscores.html
index 54bd9f952..72d51b8b5 100644
--- a/tests/misc/underscores.html
+++ b/tests/misc/underscores.html
@@ -1,6 +1,6 @@
 <p>THIS_SHOULD_STAY_AS_IS</p>
 <p>Here is some <em>emphasis</em>, ok?</p>
 <p>Ok, at least <em>this</em> should work.</p>
-<p>THIS<strong>SHOULD</strong>STAY</p>
+<p>THIS__SHOULD__STAY</p>
 <p>Here is some <strong>strong</strong> stuff.</p>
 <p>THIS<strong><em>SHOULD</em></strong>STAY?</p>
\ No newline at end of file
diff --git a/tests/options/html4.html b/tests/options/html4.html
deleted file mode 100644
index 7c88ad7d5..000000000
--- a/tests/options/html4.html
+++ /dev/null
@@ -1,2 +0,0 @@
-<p>A test of the most<br>
-basic of html/xhtml differences.</p>
\ No newline at end of file
diff --git a/tests/options/html4.txt b/tests/options/html4.txt
deleted file mode 100644
index fddaf8e45..000000000
--- a/tests/options/html4.txt
+++ /dev/null
@@ -1,2 +0,0 @@
-A test of the most  
-basic of html/xhtml differences.
\ No newline at end of file
diff --git a/tests/options/lazy_ol_off.html b/tests/options/lazy_ol_off.html
deleted file mode 100644
index bff1970da..000000000
--- a/tests/options/lazy_ol_off.html
+++ /dev/null
@@ -1,18 +0,0 @@
-<p>A numbered list from daringfireball:</p>
-<ol start="3">
-<li>Bird</li>
-<li>McHale</li>
-<li>Parish</li>
-</ol>
-<p>Again:</p>
-<ol start="3">
-<li>Bird</li>
-<li>McHale</li>
-<li>Parish</li>
-</ol>
-<p>Now starting with 1:</p>
-<ol>
-<li>Bird</li>
-<li>McHale</li>
-<li>Parish</li>
-</ol>
\ No newline at end of file
diff --git a/tests/options/lazy_ol_off.txt b/tests/options/lazy_ol_off.txt
deleted file mode 100644
index 611f3a653..000000000
--- a/tests/options/lazy_ol_off.txt
+++ /dev/null
@@ -1,17 +0,0 @@
-A numbered list from daringfireball:
-
-3. Bird
-1. McHale
-8. Parish
-
-Again:
-
-3. Bird
-1. McHale
-8. Parish
-
-Now starting with 1:
-
-1. Bird
-1. McHale
-8. Parish
diff --git a/tests/options/no-attributes.html b/tests/options/no-attributes.html
deleted file mode 100644
index a72e88888..000000000
--- a/tests/options/no-attributes.html
+++ /dev/null
@@ -1,2 +0,0 @@
-<p>Regression <em>test</em> for issue 87</p>
-<p>It's run with enable_attributes=False so this {@id=explanation} should not become an attribute</p>
\ No newline at end of file
diff --git a/tests/options/no-attributes.txt b/tests/options/no-attributes.txt
deleted file mode 100644
index b2cb653f3..000000000
--- a/tests/options/no-attributes.txt
+++ /dev/null
@@ -1,3 +0,0 @@
-Regression *test* for issue 87
-
-It's run with enable_attributes=False so this {@id=explanation} should not become an attribute
diff --git a/tests/options/no-smart-emphasis.html b/tests/options/no-smart-emphasis.html
deleted file mode 100644
index c4f1381f1..000000000
--- a/tests/options/no-smart-emphasis.html
+++ /dev/null
@@ -1 +0,0 @@
-<p><em>connected</em>words_</p>
\ No newline at end of file
diff --git a/tests/options/no-smart-emphasis.txt b/tests/options/no-smart-emphasis.txt
deleted file mode 100644
index cf500b5e2..000000000
--- a/tests/options/no-smart-emphasis.txt
+++ /dev/null
@@ -1 +0,0 @@
-_connected_words_
\ No newline at end of file
diff --git a/tests/options/test.cfg b/tests/options/test.cfg
deleted file mode 100644
index 2e14f1f45..000000000
--- a/tests/options/test.cfg
+++ /dev/null
@@ -1,11 +0,0 @@
-lazy_ol_off:
-    lazy_ol: False
-
-html4:
-    output_format: html4
-
-no-attributes:
-    enable_attributes: False
-
-no-smart-emphasis:
-    smart_emphasis: False
\ No newline at end of file
diff --git a/tests/pl/Tests_2004/test.cfg b/tests/pl/Tests_2004/test.cfg
index 80f48e525..f0339a93e 100644
--- a/tests/pl/Tests_2004/test.cfg
+++ b/tests/pl/Tests_2004/test.cfg
@@ -8,3 +8,5 @@ Yuri-Footnotes:
     extensions: footnotes
     skip: True
 
+Yuri-Attributes:
+    skip: True
\ No newline at end of file
diff --git a/tests/plugins.py b/tests/plugins.py
deleted file mode 100644
index 90c5c0d99..000000000
--- a/tests/plugins.py
+++ /dev/null
@@ -1,125 +0,0 @@
-import traceback
-from nose.plugins import Plugin
-from nose.plugins.errorclass import ErrorClass, ErrorClassPlugin
-
-
-class MarkdownSyntaxError(Exception):
-    pass
-
-
-class Markdown(ErrorClassPlugin):
-    """ Add MarkdownSyntaxError and ensure proper formatting. """
-    mdsyntax = ErrorClass(
-        MarkdownSyntaxError,
-        label='MarkdownSyntaxError',
-        isfailure=True
-    )
-    enabled = True
-
-    def configure(self, options, conf):
-        self.conf = conf
-
-    def addError(self, test, err):
-        """ Ensure other plugins see the error by returning nothing here. """
-        pass
-
-    def formatError(self, test, err):
-        """ Remove unnessecary and unhelpful traceback from error report. """
-        et, ev, tb = err
-        if et.__name__ == 'MarkdownSyntaxError':
-            return et, ev, ''
-        return err
-
-
-def escape(html):
-    """ Escape HTML for display as source within HTML. """
-    html = html.replace('&', '&amp;')
-    html = html.replace('<', '&lt;')
-    html = html.replace('>', '&gt;')
-    return html
-
-
-class HtmlOutput(Plugin):
-    """Output test results as ugly, unstyled html. """
-
-    name = 'html-output'
-    score = 2  # run late
-    enabled = True
-
-    def __init__(self):
-        super(HtmlOutput, self).__init__()
-        self.html = [
-            '<html><head>',
-            '<title>Test output</title>',
-            '</head><body>'
-        ]
-
-    def configure(self, options, conf):
-        self.conf = conf
-
-    def addSuccess(self, test):
-        self.html.append('<span>ok</span>')
-
-    def addError(self, test, err):
-        err = self.formatErr(err)
-        self.html.append('<span>ERROR</span>')
-        self.html.append('<pre>%s</pre>' % escape(err))
-
-    def addFailure(self, test, err):
-        err = self.formatErr(err)
-        self.html.append('<span>FAIL</span>')
-        self.html.append('<pre>%s</pre>' % escape(err))
-
-    def finalize(self, result):
-        self.html.append('<div>')
-        self.html.append(
-            "Ran %d test%s" %
-            (result.testsRun, result.testsRun != 1 and "s" or "")
-        )
-        self.html.append('</div>')
-        self.html.append('<div>')
-        if not result.wasSuccessful():
-            self.html.extend(['<span>FAILED (',
-                              'failures=%d ' % len(result.failures),
-                              'errors=%d' % len(result.errors)])
-            for cls in list(result.errorClasses.keys()):
-                storage, label, isfail = result.errorClasses[cls]
-                if len(storage):
-                    self.html.append(' %ss=%d' % (label, len(storage)))
-            self.html.append(')</span>')
-        else:
-            self.html.append('OK')
-        self.html.append('</div></body></html>')
-        f = open('test-output.html', 'w')
-        for l in self.html:
-            f.write(l)
-        f.close()
-
-    def formatErr(self, err):
-        exctype, value, tb = err
-        return ''.join(traceback.format_exception(exctype, value, tb))
-
-    def startContext(self, ctx):
-        try:
-            n = ctx.__name__
-        except AttributeError:
-            n = str(ctx).replace('<', '').replace('>', '')
-        self.html.extend(['<fieldset>', '<legend>', n, '</legend>'])
-        try:
-            path = ctx.__file__.replace('.pyc', '.py')
-            self.html.extend(['<div>', path, '</div>'])
-        except AttributeError:
-            pass
-
-    def stopContext(self, ctx):
-        self.html.append('</fieldset>')
-
-    def startTest(self, test):
-        self.html.extend([
-            '<div><span>',
-            test.shortDescription() or str(test),
-            '</span>'
-        ])
-
-    def stopTest(self, test):
-        self.html.append('</div>')
diff --git a/tests/safe_mode/html_then_blockquote.html b/tests/safe_mode/html_then_blockquote.html
deleted file mode 100644
index 62d24e47f..000000000
--- a/tests/safe_mode/html_then_blockquote.html
+++ /dev/null
@@ -1,6 +0,0 @@
-<p>to:</p>
-<p>&lt;td /&gt;&lt;td style="text-align: center; white-space: nowrap;"&gt;&lt;br /&gt;</p>
-<blockquote>
-<p>3) You don't need to alter all localization files.
-   Adding the new labels to the en_US files will do it.</p>
-</blockquote>
\ No newline at end of file
diff --git a/tests/safe_mode/html_then_blockquote.txt b/tests/safe_mode/html_then_blockquote.txt
deleted file mode 100644
index 544df677d..000000000
--- a/tests/safe_mode/html_then_blockquote.txt
+++ /dev/null
@@ -1,6 +0,0 @@
-to:
-
-<td /><td style="text-align: center; white-space: nowrap;"><br />
-
-> 3) You don't need to alter all localization files.
->    Adding the new labels to the en_US files will do it.
diff --git a/tests/safe_mode/inline-html-advanced.html b/tests/safe_mode/inline-html-advanced.html
deleted file mode 100644
index e9dd2ec01..000000000
--- a/tests/safe_mode/inline-html-advanced.html
+++ /dev/null
@@ -1,11 +0,0 @@
-<p>Simple block on one line:</p>
-<p>&lt;div&gt;foo&lt;/div&gt;</p>
-<p>And nested without indentation:</p>
-<p>&lt;div&gt;
-&lt;div&gt;
-&lt;div&gt;
-foo
-&lt;/div&gt;
-&lt;/div&gt;
-&lt;div&gt;bar&lt;/div&gt;
-&lt;/div&gt;</p>
\ No newline at end of file
diff --git a/tests/safe_mode/inline-html-advanced.txt b/tests/safe_mode/inline-html-advanced.txt
deleted file mode 100644
index 9d71ddcc1..000000000
--- a/tests/safe_mode/inline-html-advanced.txt
+++ /dev/null
@@ -1,14 +0,0 @@
-Simple block on one line:
-
-<div>foo</div>
-
-And nested without indentation:
-
-<div>
-<div>
-<div>
-foo
-</div>
-</div>
-<div>bar</div>
-</div>
diff --git a/tests/safe_mode/inline-html-comments.html b/tests/safe_mode/inline-html-comments.html
deleted file mode 100644
index 0f1e41780..000000000
--- a/tests/safe_mode/inline-html-comments.html
+++ /dev/null
@@ -1,8 +0,0 @@
-<p>Paragraph one.</p>
-<p>&lt;!-- This is a simple comment --&gt;</p>
-<p>&lt;!--
-    This is another comment.
---&gt;</p>
-<p>Paragraph two.</p>
-<p>&lt;!-- one comment block -- -- with two comments --&gt;</p>
-<p>The end.</p>
\ No newline at end of file
diff --git a/tests/safe_mode/inline-html-comments.txt b/tests/safe_mode/inline-html-comments.txt
deleted file mode 100644
index 41d830d03..000000000
--- a/tests/safe_mode/inline-html-comments.txt
+++ /dev/null
@@ -1,13 +0,0 @@
-Paragraph one.
-
-<!-- This is a simple comment -->
-
-<!--
-	This is another comment.
--->
-
-Paragraph two.
-
-<!-- one comment block -- -- with two comments -->
-
-The end.
diff --git a/tests/safe_mode/inline-html-simple.html b/tests/safe_mode/inline-html-simple.html
deleted file mode 100644
index 1e5df1716..000000000
--- a/tests/safe_mode/inline-html-simple.html
+++ /dev/null
@@ -1,46 +0,0 @@
-<p>Here's a simple block:</p>
-<p>&lt;div&gt;
-    foo
-&lt;/div&gt;</p>
-<p>This should be a code block, though:</p>
-<pre><code>&lt;div&gt;
-    foo
-&lt;/div&gt;
-</code></pre>
-<p>As should this:</p>
-<pre><code>&lt;div&gt;foo&lt;/div&gt;
-</code></pre>
-<p>Now, nested:</p>
-<p>&lt;div&gt;
-    &lt;div&gt;
-        &lt;div&gt;
-            foo
-        &lt;/div&gt;
-    &lt;/div&gt;
-&lt;/div&gt;</p>
-<p>This should just be an HTML comment:</p>
-<p>&lt;!-- Comment --&gt;</p>
-<p>Multiline:</p>
-<p>&lt;!--
-Blah
-Blah
---&gt;</p>
-<p>Code block:</p>
-<pre><code>&lt;!-- Comment --&gt;
-</code></pre>
-<p>Just plain comment, with trailing spaces on the line:</p>
-<p>&lt;!-- foo --&gt;   </p>
-<p>Code:</p>
-<pre><code>&lt;hr /&gt;
-</code></pre>
-<p>Hr's:</p>
-<p>&lt;hr&gt;</p>
-<p>&lt;hr/&gt;</p>
-<p>&lt;hr /&gt;</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>
-<p>&lt;hr class="foo" id="bar" &gt;</p>
-<p>&lt;some <a href="http://example.com">weird</a> stuff&gt;</p>
\ No newline at end of file
diff --git a/tests/safe_mode/inline-html-simple.txt b/tests/safe_mode/inline-html-simple.txt
deleted file mode 100644
index 7210750d8..000000000
--- a/tests/safe_mode/inline-html-simple.txt
+++ /dev/null
@@ -1,70 +0,0 @@
-Here's a simple block:
-
-<div>
-	foo
-</div>
-
-This should be a code block, though:
-
-	<div>
-		foo
-	</div>
-
-As should this:
-
-	<div>foo</div>
-
-Now, nested:
-
-<div>
-	<div>
-		<div>
-			foo
-		</div>
-	</div>
-</div>
-
-This should just be an HTML comment:
-
-<!-- Comment -->
-
-Multiline:
-
-<!--
-Blah
-Blah
--->
-
-Code block:
-
-	<!-- Comment -->
-
-Just plain comment, with trailing spaces on the line:
-
-<!-- foo -->   
-
-Code:
-
-	<hr />
-	
-Hr's:
-
-<hr>
-
-<hr/>
-
-<hr />
-
-<hr>   
-
-<hr/>  
-
-<hr /> 
-
-<hr class="foo" id="bar" />
-
-<hr class="foo" id="bar"/>
-
-<hr class="foo" id="bar" >
-
-<some [weird](http://example.com) stuff>
diff --git a/tests/safe_mode/link-targets.html b/tests/safe_mode/link-targets.html
deleted file mode 100644
index 768ae5bbf..000000000
--- a/tests/safe_mode/link-targets.html
+++ /dev/null
@@ -1,2 +0,0 @@
-<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
deleted file mode 100644
index 10eebda1d..000000000
--- a/tests/safe_mode/link-targets.txt
+++ /dev/null
@@ -1,3 +0,0 @@
-[XSS](javascript://%0Aalert%28'XSS'%29;)
-See http://security.stackexchange.com/q/30330/1261 for details.
-
diff --git a/tests/safe_mode/remove.html b/tests/safe_mode/remove.html
deleted file mode 100644
index d86b2b40f..000000000
--- a/tests/safe_mode/remove.html
+++ /dev/null
@@ -1,35 +0,0 @@
-<p>Here's a simple block:</p>
-<p></p>
-<p>This should be a code block, though:</p>
-<pre><code>&lt;div&gt;
-    foo
-&lt;/div&gt;
-</code></pre>
-<p>As should this:</p>
-<pre><code>&lt;div&gt;foo&lt;/div&gt;
-</code></pre>
-<p>Now, nested:</p>
-<p></p>
-<p>This should just be an HTML comment:</p>
-<p></p>
-<p>Multiline:</p>
-<p></p>
-<p>Code block:</p>
-<pre><code>&lt;!-- Comment --&gt;
-</code></pre>
-<p>Just plain comment, with trailing spaces on the line:</p>
-<p></p>
-<p>Code:</p>
-<pre><code>&lt;hr /&gt;
-</code></pre>
-<p>Hr's:</p>
-<p></p>
-<p></p>
-<p></p>
-<p></p>
-<p></p>
-<p></p>
-<p></p>
-<p></p>
-<p></p>
-<p></p>
\ No newline at end of file
diff --git a/tests/safe_mode/remove.txt b/tests/safe_mode/remove.txt
deleted file mode 100644
index 7210750d8..000000000
--- a/tests/safe_mode/remove.txt
+++ /dev/null
@@ -1,70 +0,0 @@
-Here's a simple block:
-
-<div>
-	foo
-</div>
-
-This should be a code block, though:
-
-	<div>
-		foo
-	</div>
-
-As should this:
-
-	<div>foo</div>
-
-Now, nested:
-
-<div>
-	<div>
-		<div>
-			foo
-		</div>
-	</div>
-</div>
-
-This should just be an HTML comment:
-
-<!-- Comment -->
-
-Multiline:
-
-<!--
-Blah
-Blah
--->
-
-Code block:
-
-	<!-- Comment -->
-
-Just plain comment, with trailing spaces on the line:
-
-<!-- foo -->   
-
-Code:
-
-	<hr />
-	
-Hr's:
-
-<hr>
-
-<hr/>
-
-<hr />
-
-<hr>   
-
-<hr/>  
-
-<hr /> 
-
-<hr class="foo" id="bar" />
-
-<hr class="foo" id="bar"/>
-
-<hr class="foo" id="bar" >
-
-<some [weird](http://example.com) stuff>
diff --git a/tests/safe_mode/replace.html b/tests/safe_mode/replace.html
deleted file mode 100644
index cb6bfb58d..000000000
--- a/tests/safe_mode/replace.html
+++ /dev/null
@@ -1,35 +0,0 @@
-<p>Here's a simple block:</p>
-<p>[HTML_REMOVED]</p>
-<p>This should be a code block, though:</p>
-<pre><code>&lt;div&gt;
-    foo
-&lt;/div&gt;
-</code></pre>
-<p>As should this:</p>
-<pre><code>&lt;div&gt;foo&lt;/div&gt;
-</code></pre>
-<p>Now, nested:</p>
-<p>[HTML_REMOVED]</p>
-<p>This should just be an HTML comment:</p>
-<p>[HTML_REMOVED]</p>
-<p>Multiline:</p>
-<p>[HTML_REMOVED]</p>
-<p>Code block:</p>
-<pre><code>&lt;!-- Comment --&gt;
-</code></pre>
-<p>Just plain comment, with trailing spaces on the line:</p>
-<p>[HTML_REMOVED]</p>
-<p>Code:</p>
-<pre><code>&lt;hr /&gt;
-</code></pre>
-<p>Hr's:</p>
-<p>[HTML_REMOVED]</p>
-<p>[HTML_REMOVED]</p>
-<p>[HTML_REMOVED]</p>
-<p>[HTML_REMOVED]</p>
-<p>[HTML_REMOVED]</p>
-<p>[HTML_REMOVED]</p>
-<p>[HTML_REMOVED]</p>
-<p>[HTML_REMOVED]</p>
-<p>[HTML_REMOVED]</p>
-<p>[HTML_REMOVED]</p>
\ No newline at end of file
diff --git a/tests/safe_mode/replace.txt b/tests/safe_mode/replace.txt
deleted file mode 100644
index 7210750d8..000000000
--- a/tests/safe_mode/replace.txt
+++ /dev/null
@@ -1,70 +0,0 @@
-Here's a simple block:
-
-<div>
-	foo
-</div>
-
-This should be a code block, though:
-
-	<div>
-		foo
-	</div>
-
-As should this:
-
-	<div>foo</div>
-
-Now, nested:
-
-<div>
-	<div>
-		<div>
-			foo
-		</div>
-	</div>
-</div>
-
-This should just be an HTML comment:
-
-<!-- Comment -->
-
-Multiline:
-
-<!--
-Blah
-Blah
--->
-
-Code block:
-
-	<!-- Comment -->
-
-Just plain comment, with trailing spaces on the line:
-
-<!-- foo -->   
-
-Code:
-
-	<hr />
-	
-Hr's:
-
-<hr>
-
-<hr/>
-
-<hr />
-
-<hr>   
-
-<hr/>  
-
-<hr /> 
-
-<hr class="foo" id="bar" />
-
-<hr class="foo" id="bar"/>
-
-<hr class="foo" id="bar" >
-
-<some [weird](http://example.com) stuff>
diff --git a/tests/safe_mode/script_tags.html b/tests/safe_mode/script_tags.html
deleted file mode 100644
index 6c1501b58..000000000
--- a/tests/safe_mode/script_tags.html
+++ /dev/null
@@ -1,20 +0,0 @@
-<p>This should be stripped/escaped in safe_mode.</p>
-<p>&lt;script&gt;
-alert("Hello world!")
-&lt;/script&gt;</p>
-<p>With blank lines.</p>
-<p>&lt;script&gt;</p>
-<p>alert("Hello world!")</p>
-<p>&lt;/script&gt;</p>
-<p>Now with some weirdness</p>
-<p><code>&lt;script &lt;!--
-alert("Hello world!")
-&lt;/script &lt;&gt;</code> `</p>
-<p>Try another way.</p>
-<p>&lt;script &lt;!--
-alert("Hello world!")
-&lt;/script &lt;&gt;</p>
-<p>This time with blank lines.</p>
-<p>&lt;script &lt;!--</p>
-<p>alert("Hello world!")</p>
-<p>&lt;/script &lt;&gt;</p>
\ No newline at end of file
diff --git a/tests/safe_mode/script_tags.txt b/tests/safe_mode/script_tags.txt
deleted file mode 100644
index 44041c2f0..000000000
--- a/tests/safe_mode/script_tags.txt
+++ /dev/null
@@ -1,33 +0,0 @@
-This should be stripped/escaped in safe_mode.
-
-<script>
-alert("Hello world!")
-</script>
-
-With blank lines.
-
-<script>
-
-alert("Hello world!")
-
-</script>
-
-Now with some weirdness
-
-``<script <!--
-alert("Hello world!")
-</script <>`` `
-
-Try another way.
-
-<script <!--
-alert("Hello world!")
-</script <>
-
-This time with blank lines.
-
-<script <!--
-
-alert("Hello world!")
-
-</script <>
diff --git a/tests/safe_mode/test.cfg b/tests/safe_mode/test.cfg
deleted file mode 100644
index 4e1720e87..000000000
--- a/tests/safe_mode/test.cfg
+++ /dev/null
@@ -1,8 +0,0 @@
-DEFAULT:
-    safe_mode: escape
-
-remove:
-    safe_mode: remove
-
-replace:
-    safe_mode: replace
diff --git a/tests/safe_mode/unsafe_urls.html b/tests/safe_mode/unsafe_urls.html
deleted file mode 100644
index e617f35cf..000000000
--- a/tests/safe_mode/unsafe_urls.html
+++ /dev/null
@@ -1,20 +0,0 @@
-<p>These links should be unsafe and not allowed in safe_mode</p>
-<p><a href="">link</a>
-<a href="">link</a>
-<a href="">link</a>
-<a href="">link</a>
-<a href="">link</a>
-<a href="">link</a>
-<a href="">link</a>
-<a href="">link</a>
-<a href="">link</a>
-<a href="">link</a>
-<a href="">link</a></p>
-<p><img alt="img" src="" />
-<a href="">ref</a>
-<img alt="imgref" src="" /></p>
-<p>These should work regardless:</p>
-<p><a href="relative/url.html">relative</a>
-<a href="mailto:foo@bar.com">email</a>
-<a href="news:some.news.group.com">news scheme</a>
-<a href="http://example.com">http link</a></p>
\ No newline at end of file
diff --git a/tests/safe_mode/unsafe_urls.txt b/tests/safe_mode/unsafe_urls.txt
deleted file mode 100644
index 7bfd81dc3..000000000
--- a/tests/safe_mode/unsafe_urls.txt
+++ /dev/null
@@ -1,27 +0,0 @@
-These links should be unsafe and not allowed in safe_mode
-
-[link](javascript:alert%28'Hello%20world!'%29)
-[link](vbscript:msgbox%28%22Hello%20world!%22%29)
-[link](livescript:alert%28'Hello%20world!'%29)
-[link](mocha:[code])
-[link](jAvAsCrIpT:alert%28'Hello%20world!'%29)
-[link](ja&#32;vas&#32;cr&#32;ipt:alert%28'Hello%20world!'%29)
-[link](ja&#00032;vas&#32;cr&#32;ipt:alert%28'Hello%20world!'%29)
-[link](ja&#x00020;vas&#32;cr&#32;ipt:alert%28'Hello%20world!'%29)
-[link](ja%09&#x20;%0Avas&#32;cr&#x0a;ipt:alert%28'Hello%20world!'%29)
-[link](ja%20vas%20cr%20ipt:alert%28'Hello%20world!'%29)
-[link](live%20script:alert%28'Hello%20world!'%29)
-
-![img](javascript:alert%29'XSS'%29)
-[ref][]
-![imgref][]
-
-[ref]: javascript:alert%29'XSS'%29
-[imgref]: javascript:alert%29'XSS'%29
-
-These should work regardless:
-
-[relative](relative/url.html)
-[email](mailto:foo@bar.com)
-[news scheme](news:some.news.group.com)
-[http link](http://example.com)
diff --git a/tests/syntax_unittests/__init__.py b/tests/syntax_unittests/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/tests/syntax_unittests/blocks/__init__.py b/tests/syntax_unittests/blocks/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/tests/syntax_unittests/blocks/test_code_blocks.py b/tests/syntax_unittests/blocks/test_code_blocks.py
new file mode 100644
index 000000000..61aa56b3c
--- /dev/null
+++ b/tests/syntax_unittests/blocks/test_code_blocks.py
@@ -0,0 +1,67 @@
+from markdown.test_tools import TestCase
+
+
+class TestCodeBlocks(TestCase):
+
+    def test_spaced_codeblock(self):
+        self.assertMarkdownRenders(
+            '    # A code block.',
+
+            self.dedent(
+                """
+                <pre><code># A code block.
+                </code></pre>
+                """
+            )
+        )
+
+    def test_tabbed_codeblock(self):
+        self.assertMarkdownRenders(
+            '\t# A code block.',
+
+            self.dedent(
+                """
+                <pre><code># A code block.
+                </code></pre>
+                """
+            )
+        )
+
+    def test_multiline_codeblock(self):
+        self.assertMarkdownRenders(
+            '    # Line 1\n    # Line 2\n',
+        
+            self.dedent(
+                """
+                <pre><code># Line 1
+                # Line 2
+                </code></pre>
+                """
+            )
+        )
+
+    def test_codeblock_with_blankline(self):
+        self.assertMarkdownRenders(
+            '    # Line 1\n\n    # Line 2\n',
+
+            self.dedent(
+                """
+                <pre><code># Line 1
+
+                # Line 2
+                </code></pre>
+                """
+            )
+        )
+
+    def test_codeblock_escape(self):
+        self.assertMarkdownRenders(
+            '    <foo & bar>',
+            
+            self.dedent(
+                """
+                <pre><code>&lt;foo &amp; bar&gt;
+                </code></pre>
+                """
+            )
+        )
diff --git a/tests/syntax_unittests/blocks/test_headers.py b/tests/syntax_unittests/blocks/test_headers.py
new file mode 100644
index 000000000..cddb8ef5d
--- /dev/null
+++ b/tests/syntax_unittests/blocks/test_headers.py
@@ -0,0 +1,689 @@
+import unittest
+from markdown.test_tools import TestCase
+
+
+class TestSetextHeaders(TestCase):
+
+    def test_setext_h1(self):
+        self.assertMarkdownRenders(
+            self.dedent(
+                """
+                This is an H1
+                =============
+                """
+            ),
+
+            '<h1>This is an H1</h1>'
+        )
+
+    def test_setext_h2(self):
+        self.assertMarkdownRenders(
+            self.dedent(
+                """
+                This is an H2
+                -------------
+                """
+            ),
+
+            '<h2>This is an H2</h2>'
+        )
+
+    def test_setext_h1_mismatched_length(self):
+        self.assertMarkdownRenders(
+            self.dedent(
+                """
+                This is an H1
+                ===
+                """
+            ),
+
+            '<h1>This is an H1</h1>'
+        )
+
+    def test_setext_h2_mismatched_length(self):
+        self.assertMarkdownRenders(
+            self.dedent(
+                """
+                This is an H2
+                ---
+                """
+            ),
+
+            '<h2>This is an H2</h2>'
+        )
+
+    def test_setext_h1_followed_by_p(self):
+        self.assertMarkdownRenders(
+            self.dedent(
+                """
+                This is an H1
+                =============
+                Followed by a Paragraph with no blank line.
+                """
+            ),
+            self.dedent(
+                """
+                <h1>This is an H1</h1>
+                <p>Followed by a Paragraph with no blank line.</p>
+                """
+            )
+        )
+
+    def test_setext_h2_followed_by_p(self):
+        self.assertMarkdownRenders(
+            self.dedent(
+                """
+                This is an H2
+                -------------
+                Followed by a Paragraph with no blank line.
+                """
+            ),
+            self.dedent(
+                """
+                <h2>This is an H2</h2>
+                <p>Followed by a Paragraph with no blank line.</p>
+                """
+            )
+        )
+
+    # TODO: fix this
+    # see http://johnmacfarlane.net/babelmark2/?normalize=1&text=Paragraph%0AAn+H1%0A%3D%3D%3D%3D%3D
+    @unittest.skip('This is broken in Python-Markdown')
+    def test_p_followed_by_setext_h1(self):
+        self.assertMarkdownRenders(
+            self.dedent(
+                """
+                This is a Paragraph.
+                Followed by an H1 with no blank line.
+                =====================================
+                """
+            ),
+            self.dedent(
+                """
+                <p>This is a Paragraph.</p>
+                <h1>Followed by an H1 with no blank line.</h1>
+                """
+            )
+        )
+
+    # TODO: fix this
+    # see http://johnmacfarlane.net/babelmark2/?normalize=1&text=Paragraph%0AAn+H2%0A-----
+    @unittest.skip('This is broken in Python-Markdown')
+    def test_p_followed_by_setext_h2(self):
+        self.assertMarkdownRenders(
+            self.dedent(
+                """
+                This is a Paragraph.
+                Followed by an H2 with no blank line.
+                -------------------------------------
+                """
+            ),
+            self.dedent(
+                """
+                <p>This is a Paragraph.</p>
+                <h2>Followed by an H2 with no blank line.</h2>
+                """
+            )
+        )
+
+
+class TestHashHeaders(TestCase):
+
+    def test_hash_h1_open(self):
+        self.assertMarkdownRenders(
+            '# This is an H1',
+
+            '<h1>This is an H1</h1>'
+        )
+
+    def test_hash_h2_open(self):
+        self.assertMarkdownRenders(
+            '## This is an H2',
+
+            '<h2>This is an H2</h2>'
+        )
+
+    def test_hash_h3_open(self):
+        self.assertMarkdownRenders(
+            '### This is an H3',
+
+            '<h3>This is an H3</h3>'
+        )
+
+    def test_hash_h4_open(self):
+        self.assertMarkdownRenders(
+            '#### This is an H4',
+
+            '<h4>This is an H4</h4>'
+        )
+
+    def test_hash_h5_open(self):
+        self.assertMarkdownRenders(
+            '##### This is an H5',
+
+            '<h5>This is an H5</h5>'
+        )
+
+    def test_hash_h6_open(self):
+        self.assertMarkdownRenders(
+            '###### This is an H6',
+
+            '<h6>This is an H6</h6>'
+        )
+
+    def test_hash_gt6_open(self):
+        self.assertMarkdownRenders(
+            '####### This is an H6',
+
+            '<h6># This is an H6</h6>'
+        )
+
+    def test_hash_h1_open_missing_space(self):
+        self.assertMarkdownRenders(
+            '#This is an H1',
+
+            '<h1>This is an H1</h1>'
+        )
+
+    def test_hash_h2_open_missing_space(self):
+        self.assertMarkdownRenders(
+            '##This is an H2',
+
+            '<h2>This is an H2</h2>'
+        )
+
+    def test_hash_h3_open_missing_space(self):
+        self.assertMarkdownRenders(
+            '###This is an H3',
+
+            '<h3>This is an H3</h3>'
+        )
+
+    def test_hash_h4_open_missing_space(self):
+        self.assertMarkdownRenders(
+            '####This is an H4',
+
+            '<h4>This is an H4</h4>'
+        )
+
+    def test_hash_h5_open_missing_space(self):
+        self.assertMarkdownRenders(
+            '#####This is an H5',
+
+            '<h5>This is an H5</h5>'
+        )
+
+    def test_hash_h6_open_missing_space(self):
+        self.assertMarkdownRenders(
+            '######This is an H6',
+
+            '<h6>This is an H6</h6>'
+        )
+
+    def test_hash_gt6_open_missing_space(self):
+        self.assertMarkdownRenders(
+            '#######This is an H6',
+
+            '<h6>#This is an H6</h6>'
+        )
+
+    def test_hash_h1_closed(self):
+        self.assertMarkdownRenders(
+            '# This is an H1 #',
+
+            '<h1>This is an H1</h1>'
+        )
+
+    def test_hash_h2_closed(self):
+        self.assertMarkdownRenders(
+            '## This is an H2 ##',
+
+            '<h2>This is an H2</h2>'
+        )
+
+    def test_hash_h3_closed(self):
+        self.assertMarkdownRenders(
+            '### This is an H3 ###',
+
+            '<h3>This is an H3</h3>'
+        )
+
+    def test_hash_h4_closed(self):
+        self.assertMarkdownRenders(
+            '#### This is an H4 ####',
+
+            '<h4>This is an H4</h4>'
+        )
+
+    def test_hash_h5_closed(self):
+        self.assertMarkdownRenders(
+            '##### This is an H5 #####',
+
+            '<h5>This is an H5</h5>'
+        )
+
+    def test_hash_h6_closed(self):
+        self.assertMarkdownRenders(
+            '###### This is an H6 ######',
+
+            '<h6>This is an H6</h6>'
+        )
+
+    def test_hash_gt6_closed(self):
+        self.assertMarkdownRenders(
+            '####### This is an H6 #######',
+
+            '<h6># This is an H6</h6>'
+        )
+
+    def test_hash_h1_closed_missing_space(self):
+        self.assertMarkdownRenders(
+            '#This is an H1#',
+
+            '<h1>This is an H1</h1>'
+        )
+
+    def test_hash_h2_closed_missing_space(self):
+        self.assertMarkdownRenders(
+            '##This is an H2##',
+
+            '<h2>This is an H2</h2>'
+        )
+
+    def test_hash_h3_closed_missing_space(self):
+        self.assertMarkdownRenders(
+            '###This is an H3###',
+
+            '<h3>This is an H3</h3>'
+        )
+
+    def test_hash_h4_closed_missing_space(self):
+        self.assertMarkdownRenders(
+            '####This is an H4####',
+
+            '<h4>This is an H4</h4>'
+        )
+
+    def test_hash_h5_closed_missing_space(self):
+        self.assertMarkdownRenders(
+            '#####This is an H5#####',
+
+            '<h5>This is an H5</h5>'
+        )
+
+    def test_hash_h6_closed_missing_space(self):
+        self.assertMarkdownRenders(
+            '######This is an H6######',
+
+            '<h6>This is an H6</h6>'
+        )
+
+    def test_hash_gt6_closed_missing_space(self):
+        self.assertMarkdownRenders(
+            '#######This is an H6#######',
+
+            '<h6>#This is an H6</h6>'
+        )
+
+    def test_hash_h1_closed_mismatch(self):
+        self.assertMarkdownRenders(
+            '# This is an H1 ##',
+
+            '<h1>This is an H1</h1>'
+        )
+
+    def test_hash_h2_closed_mismatch(self):
+        self.assertMarkdownRenders(
+            '## This is an H2 #',
+
+            '<h2>This is an H2</h2>'
+        )
+
+    def test_hash_h3_closed_mismatch(self):
+        self.assertMarkdownRenders(
+            '### This is an H3 #',
+
+            '<h3>This is an H3</h3>'
+        )
+
+    def test_hash_h4_closed_mismatch(self):
+        self.assertMarkdownRenders(
+            '#### This is an H4 #',
+
+            '<h4>This is an H4</h4>'
+        )
+
+    def test_hash_h5_closed_mismatch(self):
+        self.assertMarkdownRenders(
+            '##### This is an H5 #',
+
+            '<h5>This is an H5</h5>'
+        )
+
+    def test_hash_h6_closed_mismatch(self):
+        self.assertMarkdownRenders(
+            '###### This is an H6 #',
+
+            '<h6>This is an H6</h6>'
+        )
+
+    def test_hash_gt6_closed_mismatch(self):
+        self.assertMarkdownRenders(
+            '####### This is an H6 ##################',
+
+            '<h6># This is an H6</h6>'
+        )
+
+    def test_hash_h1_followed_by_p(self):
+        self.assertMarkdownRenders(
+            self.dedent(
+                """
+                # This is an H1
+                Followed by a Paragraph with no blank line.
+                """
+            ),
+            self.dedent(
+                """
+                <h1>This is an H1</h1>
+                <p>Followed by a Paragraph with no blank line.</p>
+                """
+            )
+        )
+
+    def test_hash_h2_followed_by_p(self):
+        self.assertMarkdownRenders(
+            self.dedent(
+                """
+                ## This is an H2
+                Followed by a Paragraph with no blank line.
+                """
+            ),
+            self.dedent(
+                """
+                <h2>This is an H2</h2>
+                <p>Followed by a Paragraph with no blank line.</p>
+                """
+            )
+        )
+
+    def test_hash_h3_followed_by_p(self):
+        self.assertMarkdownRenders(
+            self.dedent(
+                """
+                ### This is an H3
+                Followed by a Paragraph with no blank line.
+                """
+            ),
+            self.dedent(
+                """
+                <h3>This is an H3</h3>
+                <p>Followed by a Paragraph with no blank line.</p>
+                """
+            )
+        )
+
+    def test_hash_h4_followed_by_p(self):
+        self.assertMarkdownRenders(
+            self.dedent(
+                """
+                #### This is an H4
+                Followed by a Paragraph with no blank line.
+                """
+            ),
+            self.dedent(
+                """
+                <h4>This is an H4</h4>
+                <p>Followed by a Paragraph with no blank line.</p>
+                """
+            )
+        )
+
+    def test_hash_h5_followed_by_p(self):
+        self.assertMarkdownRenders(
+            self.dedent(
+                """
+                ##### This is an H5
+                Followed by a Paragraph with no blank line.
+                """
+            ),
+            self.dedent(
+                """
+                <h5>This is an H5</h5>
+                <p>Followed by a Paragraph with no blank line.</p>
+                """
+            )
+        )
+
+    def test_hash_h6_followed_by_p(self):
+        self.assertMarkdownRenders(
+            self.dedent(
+                """
+                ###### This is an H6
+                Followed by a Paragraph with no blank line.
+                """
+            ),
+            self.dedent(
+                """
+                <h6>This is an H6</h6>
+                <p>Followed by a Paragraph with no blank line.</p>
+                """
+            )
+        )
+
+
+    def test_hash_h1_leading_space(self):
+        self.assertMarkdownRenders(
+            ' # This is an H1',
+
+            '<p># This is an H1</p>'
+        )
+
+    def test_hash_h2_leading_space(self):
+        self.assertMarkdownRenders(
+            ' ## This is an H2',
+
+            '<p>## This is an H2</p>'
+        )
+
+    def test_hash_h3_leading_space(self):
+        self.assertMarkdownRenders(
+            ' ### This is an H3',
+
+            '<p>### This is an H3</p>'
+        )
+
+    def test_hash_h4_leading_space(self):
+        self.assertMarkdownRenders(
+            ' #### This is an H4',
+
+            '<p>#### This is an H4</p>'
+        )
+
+    def test_hash_h5_leading_space(self):
+        self.assertMarkdownRenders(
+            ' ##### This is an H5',
+
+            '<p>##### This is an H5</p>'
+        )
+
+    def test_hash_h6_leading_space(self):
+        self.assertMarkdownRenders(
+            ' ###### This is an H6',
+
+            '<p>###### This is an H6</p>'
+        )
+
+    def test_hash_h1_open_trailing_space(self):
+        self.assertMarkdownRenders(
+            '# This is an H1 ',
+
+            '<h1>This is an H1</h1>'
+        )
+
+    def test_hash_h2_open_trailing_space(self):
+        self.assertMarkdownRenders(
+            '## This is an H2 ',
+
+            '<h2>This is an H2</h2>'
+        )
+
+    def test_hash_h3_open_trailing_space(self):
+        self.assertMarkdownRenders(
+            '### This is an H3 ',
+
+            '<h3>This is an H3</h3>'
+        )
+
+    def test_hash_h4_open_trailing_space(self):
+        self.assertMarkdownRenders(
+            '#### This is an H4 ',
+
+            '<h4>This is an H4</h4>'
+        )
+
+    def test_hash_h5_open_trailing_space(self):
+        self.assertMarkdownRenders(
+            '##### This is an H5 ',
+
+            '<h5>This is an H5</h5>'
+        )
+
+    def test_hash_h6_open_trailing_space(self):
+        self.assertMarkdownRenders(
+            '###### This is an H6 ',
+
+            '<h6>This is an H6</h6>'
+        )
+
+    def test_hash_gt6_open_trailing_space(self):
+        self.assertMarkdownRenders(
+            '####### This is an H6 ',
+
+            '<h6># This is an H6</h6>'
+        )
+
+    # TODO: Possably change the following behavior. While this follows the behavior
+    # of markdown.pl, it is rather uncommon and not nessecarily intuitive.
+    # See: http://johnmacfarlane.net/babelmark2/?normalize=1&text=%23+This+is+an+H1+%23+
+    def test_hash_h1_closed_trailing_space(self):
+        self.assertMarkdownRenders(
+            '# This is an H1 # ',
+
+            '<h1>This is an H1 #</h1>'
+        )
+
+    def test_hash_h2_closed_trailing_space(self):
+        self.assertMarkdownRenders(
+            '## This is an H2 ## ',
+
+            '<h2>This is an H2 ##</h2>'
+        )
+
+    def test_hash_h3_closed_trailing_space(self):
+        self.assertMarkdownRenders(
+            '### This is an H3 ### ',
+
+            '<h3>This is an H3 ###</h3>'
+        )
+
+    def test_hash_h4_closed_trailing_space(self):
+        self.assertMarkdownRenders(
+            '#### This is an H4 #### ',
+
+            '<h4>This is an H4 ####</h4>'
+        )
+
+    def test_hash_h5_closed_trailing_space(self):
+        self.assertMarkdownRenders(
+            '##### This is an H5 ##### ',
+
+            '<h5>This is an H5 #####</h5>'
+        )
+
+    def test_hash_h6_closed_trailing_space(self):
+        self.assertMarkdownRenders(
+            '###### This is an H6 ###### ',
+
+            '<h6>This is an H6 ######</h6>'
+        )
+
+    def test_hash_gt6_closed_trailing_space(self):
+        self.assertMarkdownRenders(
+            '####### This is an H6 ####### ',
+
+            '<h6># This is an H6 #######</h6>'
+        )
+
+    def test_no_blank_lines_between_hashs(self):
+        self.assertMarkdownRenders(
+            self.dedent(
+                """
+                # This is an H1
+                ## This is an H2
+                """
+            ),
+            self.dedent(
+                """
+                <h1>This is an H1</h1>
+                <h2>This is an H2</h2>
+                """
+            )
+        )
+
+    def test_random_hash_levels(self):
+        self.assertMarkdownRenders(
+            self.dedent(
+                """
+                ### H3
+                ###### H6
+                # H1
+                ##### H5
+                #### H4
+                ## H2
+                ### H3
+                """
+            ),
+            self.dedent(
+                """
+                <h3>H3</h3>
+                <h6>H6</h6>
+                <h1>H1</h1>
+                <h5>H5</h5>
+                <h4>H4</h4>
+                <h2>H2</h2>
+                <h3>H3</h3>
+                """
+            )
+        )
+
+    def test_hash_followed_by_p(self):
+        self.assertMarkdownRenders(
+            self.dedent(
+                """
+                # This is an H1
+                Followed by a Paragraph with no blank line.
+                """
+            ),
+            self.dedent(
+                """
+                <h1>This is an H1</h1>
+                <p>Followed by a Paragraph with no blank line.</p>
+                """
+            )
+        )
+
+    def test_p_followed_by_hash(self):
+        self.assertMarkdownRenders(
+            self.dedent(
+                """
+                This is a Paragraph.
+                # Followed by an H1 with no blank line.
+                """
+            ),
+            self.dedent(
+                """
+                <p>This is a Paragraph.</p>
+                <h1>Followed by an H1 with no blank line.</h1>
+                """
+            )
+        )
\ No newline at end of file
diff --git a/tests/syntax_unittests/blocks/test_hr.py b/tests/syntax_unittests/blocks/test_hr.py
new file mode 100644
index 000000000..6e061c78c
--- /dev/null
+++ b/tests/syntax_unittests/blocks/test_hr.py
@@ -0,0 +1,342 @@
+from markdown.test_tools import TestCase
+
+
+class TestHorizontalRules(TestCase):
+
+    def test_hr_asterisks(self):
+        self.assertMarkdownRenders(
+            '***',
+
+            '<hr />'
+        )
+
+    def test_hr_asterisks_spaces(self):
+        self.assertMarkdownRenders(
+            '* * *',
+
+            '<hr />'
+        )
+
+    def test_hr_asterisks_long(self):
+        self.assertMarkdownRenders(
+            '*******',
+
+            '<hr />'
+        )
+
+    def test_hr_asterisks_spaces_long(self):
+        self.assertMarkdownRenders(
+            '* * * * * * *',
+
+            '<hr />'
+        )
+
+    def test_hr_asterisks_1_indent(self):
+        self.assertMarkdownRenders(
+            ' ***',
+
+            '<hr />'
+        )
+
+    def test_hr_asterisks_spaces_1_indent(self):
+        self.assertMarkdownRenders(
+            ' * * *',
+
+            '<hr />'
+        )
+
+    def test_hr_asterisks_2_indent(self):
+        self.assertMarkdownRenders(
+            '  ***',
+
+            '<hr />'
+        )
+
+    def test_hr_asterisks_spaces_2_indent(self):
+        self.assertMarkdownRenders(
+            '  * * *',
+
+            '<hr />'
+        )
+
+    def test_hr_asterisks_3_indent(self):
+        self.assertMarkdownRenders(
+            '   ***',
+
+            '<hr />'
+        )
+
+    def test_hr_asterisks_spaces_3_indent(self):
+        self.assertMarkdownRenders(
+            '   * * *',
+
+            '<hr />'
+        )
+
+    def test_hr_asterisks_trailing_space(self):
+        self.assertMarkdownRenders(
+            '*** ',
+
+            '<hr />'
+        )
+
+    def test_hr_asterisks_spaces_trailing_space(self):
+        self.assertMarkdownRenders(
+            '* * * ',
+
+            '<hr />'
+        )
+
+    def test_hr_hyphens(self):
+        self.assertMarkdownRenders(
+            '---',
+
+            '<hr />'
+        )
+
+    def test_hr_hyphens_spaces(self):
+        self.assertMarkdownRenders(
+            '- - -',
+
+            '<hr />'
+        )
+
+    def test_hr_hyphens_long(self):
+        self.assertMarkdownRenders(
+            '-------',
+
+            '<hr />'
+        )
+
+    def test_hr_hyphens_spaces_long(self):
+        self.assertMarkdownRenders(
+            '- - - - - - -',
+
+            '<hr />'
+        )
+
+    def test_hr_hyphens_1_indent(self):
+        self.assertMarkdownRenders(
+            ' ---',
+
+            '<hr />'
+        )
+
+    def test_hr_hyphens_spaces_1_indent(self):
+        self.assertMarkdownRenders(
+            ' - - -',
+
+            '<hr />'
+        )
+
+    def test_hr_hyphens_2_indent(self):
+        self.assertMarkdownRenders(
+            '  ---',
+
+            '<hr />'
+        )
+
+    def test_hr_hyphens_spaces_2_indent(self):
+        self.assertMarkdownRenders(
+            '  - - -',
+
+            '<hr />'
+        )
+
+    def test_hr_hyphens_3_indent(self):
+        self.assertMarkdownRenders(
+            '   ---',
+
+            '<hr />'
+        )
+
+    def test_hr_hyphens_spaces_3_indent(self):
+        self.assertMarkdownRenders(
+            '   - - -',
+
+            '<hr />'
+        )
+
+    def test_hr_hyphens_trailing_space(self):
+        self.assertMarkdownRenders(
+            '--- ',
+
+            '<hr />'
+        )
+
+    def test_hr_hyphens_spaces_trailing_space(self):
+        self.assertMarkdownRenders(
+            '- - - ',
+
+            '<hr />'
+        )
+
+    def test_hr_underscores(self):
+        self.assertMarkdownRenders(
+            '___',
+
+            '<hr />'
+        )
+
+    def test_hr_underscores_spaces(self):
+        self.assertMarkdownRenders(
+            '_ _ _',
+
+            '<hr />'
+        )
+
+    def test_hr_underscores_long(self):
+        self.assertMarkdownRenders(
+            '_______',
+
+            '<hr />'
+        )
+
+    def test_hr_underscores_spaces_long(self):
+        self.assertMarkdownRenders(
+            '_ _ _ _ _ _ _',
+
+            '<hr />'
+        )
+
+    def test_hr_underscores_1_indent(self):
+        self.assertMarkdownRenders(
+            ' ___',
+
+            '<hr />'
+        )
+
+    def test_hr_underscores_spaces_1_indent(self):
+        self.assertMarkdownRenders(
+            ' _ _ _',
+
+            '<hr />'
+        )
+
+    def test_hr_underscores_2_indent(self):
+        self.assertMarkdownRenders(
+            '  ___',
+
+            '<hr />'
+        )
+
+    def test_hr_underscores_spaces_2_indent(self):
+        self.assertMarkdownRenders(
+            '  _ _ _',
+
+            '<hr />'
+        )
+
+    def test_hr_underscores_3_indent(self):
+        self.assertMarkdownRenders(
+            '   ___',
+
+            '<hr />'
+        )
+
+    def test_hr_underscores_spaces_3_indent(self):
+        self.assertMarkdownRenders(
+            '   _ _ _',
+
+            '<hr />'
+        )
+
+    def test_hr_underscores_trailing_space(self):
+        self.assertMarkdownRenders(
+            '___ ',
+
+            '<hr />'
+        )
+
+    def test_hr_underscores_spaces_trailing_space(self):
+        self.assertMarkdownRenders(
+            '_ _ _ ',
+
+            '<hr />'
+        )
+
+    def test_hr_before_paragraph(self):
+        self.assertMarkdownRenders(
+            self.dedent(
+                """
+                ***
+                An HR followed by a paragraph with no blank line.
+                """
+            ),
+            self.dedent(
+                """
+                <hr />
+                <p>An HR followed by a paragraph with no blank line.</p>
+                """
+            )
+        )
+
+    def test_hr_after_paragraph(self):
+        self.assertMarkdownRenders(
+            self.dedent(
+                """
+                A paragraph followed by an HR with no blank line.
+                ***
+                """
+            ),
+            self.dedent(
+                """
+                <p>A paragraph followed by an HR with no blank line.</p>
+                <hr />
+                """
+            )
+        )
+
+    def test_not_hr_2_asterisks(self):
+        self.assertMarkdownRenders(
+            '**',
+
+            '<p>**</p>'
+        )
+
+    def test_not_hr_2_asterisks_spaces(self):
+        self.assertMarkdownRenders(
+            '* *',
+
+            self.dedent(
+                """
+                <ul>
+                <li>*</li>
+                </ul>
+                """
+            )
+        )
+
+    def test_not_hr_2_hyphens(self):
+        self.assertMarkdownRenders(
+            '--',
+
+            '<p>--</p>'
+        )
+
+    def test_not_hr_2_hyphens_spaces(self):
+        self.assertMarkdownRenders(
+            '- -',
+
+            self.dedent(
+                """
+                <ul>
+                <li>-</li>
+                </ul>
+                """
+            )
+        )
+
+    def test_not_hr_2_underscores(self):
+        self.assertMarkdownRenders(
+            '__',
+
+            '<p>__</p>'
+        )
+
+    def test_not_hr_2_underscores_spaces(self):
+        self.assertMarkdownRenders(
+            '_ _',
+
+            '<p>_ _</p>'
+        )
\ No newline at end of file
diff --git a/tests/syntax_unittests/blocks/test_paragraphs.py b/tests/syntax_unittests/blocks/test_paragraphs.py
new file mode 100644
index 000000000..7ddec7f10
--- /dev/null
+++ b/tests/syntax_unittests/blocks/test_paragraphs.py
@@ -0,0 +1,209 @@
+from markdown.test_tools import TestCase
+
+
+class TestParagraphBlocks(TestCase):
+
+    def test_simple_paragraph(self):
+        self.assertMarkdownRenders(
+            'A simple paragraph.',
+
+            '<p>A simple paragraph.</p>'
+        )
+
+    def test_blank_line_before_paragraph(self):
+        self.assertMarkdownRenders(
+            '\nA paragraph preceded by a blank line.',
+
+            '<p>A paragraph preceded by a blank line.</p>'
+        )
+
+    def test_multiline_paragraph(self):
+        self.assertMarkdownRenders(
+            self.dedent(
+                """
+                This is a paragraph
+                on multiple lines
+                with hard returns.
+                """
+            ),
+            self.dedent(
+                """
+                <p>This is a paragraph
+                on multiple lines
+                with hard returns.</p>
+                """
+            )
+        )
+
+    def test_paragraph_long_line(self):
+        self.assertMarkdownRenders(
+            'A very long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long paragraph on 1 line.',
+
+            '<p>A very long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long paragraph on 1 line.</p>'
+        )
+
+    def test_2_paragraphs_long_line(self):
+        self.assertMarkdownRenders(
+            self.dedent(
+                """
+                A very long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long paragraph on 1 line.
+
+                A new long long long long long long long long long long long long long long long long paragraph on 1 line.
+                """
+            ),
+            self.dedent(
+                """
+                <p>A very long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long paragraph on 1 line.</p>
+                <p>A new long long long long long long long long long long long long long long long long paragraph on 1 line.</p>
+                """
+            )
+        )
+
+    def test_consecutive_paragraphs(self):
+        self.assertMarkdownRenders(
+            self.dedent(
+                """
+                Paragraph 1.
+
+                Paragraph 2.
+                """
+            ),
+            self.dedent(
+                """
+                <p>Paragraph 1.</p>
+                <p>Paragraph 2.</p>
+                """
+            )
+        )
+
+    def test_consecutive_paragraphs_tab(self):
+        self.assertMarkdownRenders(
+            self.dedent(
+                """
+                Paragraph followed by a line with a tab only.
+                \t
+                Paragraph after a line with a tab only.
+                """
+            ),
+            self.dedent(
+                """
+                <p>Paragraph followed by a line with a tab only.</p>
+                <p>Paragraph after a line with a tab only.</p>
+                """
+            )
+        )
+
+    def test_consecutive_paragraphs_space(self):
+        self.assertMarkdownRenders(
+            self.dedent(
+                """
+                Paragraph followed by a line with a space only.
+
+                Paragraph after a line with a space only.
+                """
+            ),
+            self.dedent(
+                """
+                <p>Paragraph followed by a line with a space only.</p>
+                <p>Paragraph after a line with a space only.</p>
+                """
+            )
+        )
+
+    def test_consecutive_multiline_paragraphs(self):
+        self.assertMarkdownRenders(
+            self.dedent(
+                """
+                Paragraph 1, line 1.
+                Paragraph 1, line 2.
+
+                Paragraph 2, line 1.
+                Paragraph 2, line 2.
+                """
+            ),
+            self.dedent(
+                """
+                <p>Paragraph 1, line 1.
+                Paragraph 1, line 2.</p>
+                <p>Paragraph 2, line 1.
+                Paragraph 2, line 2.</p>
+                """
+            )
+        )
+
+    def test_paragraph_leading_space(self):
+        self.assertMarkdownRenders(
+            ' A paragraph with 1 leading space.',
+
+            '<p>A paragraph with 1 leading space.</p>'
+        )
+
+    def test_paragraph_2_leading_spaces(self):
+        self.assertMarkdownRenders(
+            '  A paragraph with 2 leading spaces.',
+
+            '<p>A paragraph with 2 leading spaces.</p>'
+        )
+
+    def test_paragraph_3_leading_spaces(self):
+        self.assertMarkdownRenders(
+            '   A paragraph with 3 leading spaces.',
+
+            '<p>A paragraph with 3 leading spaces.</p>'
+        )
+
+    def test_paragraph_trailing_leading_space(self):
+        self.assertMarkdownRenders(
+            ' A paragraph with 1 trailing and 1 leading space. ',
+
+            '<p>A paragraph with 1 trailing and 1 leading space. </p>'
+        )
+
+    def test_paragraph_trailing_tab(self):
+        self.assertMarkdownRenders(
+            'A paragraph with 1 trailing tab.\t',
+
+            '<p>A paragraph with 1 trailing tab.    </p>'
+        )
+
+    def test_paragraphs_CR(self):
+        self.assertMarkdownRenders(
+            'Paragraph 1, line 1.\rParagraph 1, line 2.\r\rParagraph 2, line 1.\rParagraph 2, line 2.\r',
+
+            self.dedent(
+                """
+                <p>Paragraph 1, line 1.
+                Paragraph 1, line 2.</p>
+                <p>Paragraph 2, line 1.
+                Paragraph 2, line 2.</p>
+                """
+            )
+        )
+
+    def test_paragraphs_LF(self):
+        self.assertMarkdownRenders(
+            'Paragraph 1, line 1.\nParagraph 1, line 2.\n\nParagraph 2, line 1.\nParagraph 2, line 2.\n',
+
+            self.dedent(
+                """
+                <p>Paragraph 1, line 1.
+                Paragraph 1, line 2.</p>
+                <p>Paragraph 2, line 1.
+                Paragraph 2, line 2.</p>
+                """
+            )
+        )
+
+    def test_paragraphs_CR_LF(self):
+        self.assertMarkdownRenders(
+            'Paragraph 1, line 1.\r\nParagraph 1, line 2.\r\n\r\nParagraph 2, line 1.\r\nParagraph 2, line 2.\r\n',
+
+            self.dedent(
+                """
+                <p>Paragraph 1, line 1.
+                Paragraph 1, line 2.</p>
+                <p>Paragraph 2, line 1.
+                Paragraph 2, line 2.</p>
+                """
+            )
+        )
diff --git a/tests/test_apis.py b/tests/test_apis.py
index e3de779dd..919d049ce 100644
--- a/tests/test_apis.py
+++ b/tests/test_apis.py
@@ -11,17 +11,23 @@
 import unittest
 import sys
 import os
-import types
 import markdown
 import warnings
 from markdown.__main__ import parse_options
 from logging import DEBUG, WARNING, CRITICAL
 import yaml
 import tempfile
+from io import BytesIO
+
 
 PY3 = sys.version_info[0] == 3
 
 
+if not PY3:
+    def bytes(string, encoding):
+        return string.encode(encoding)
+
+
 class TestMarkdownBasics(unittest.TestCase):
     """ Tests basics of the Markdown class. """
 
@@ -47,12 +53,47 @@ def testInstanceExtension(self):
         markdown.Markdown(extensions=[FootnoteExtension()])
 
     def testNamedExtension(self):
-        """ Test Extension loading with Name (`path.to.module`). """
-        markdown.Markdown(extensions=['markdown.extensions.footnotes'])
+        """ Test Extension loading with Name. """
+        markdown.Markdown(extensions=['footnotes'])
+
+
+class TestConvertFile(unittest.TestCase):
+    """ Tests of ConvertFile. """
 
-    def TestNamedExtensionWithClass(self):
-        """ Test Extension loading with class name (`path.to.module:Class`). """
-        markdown.Markdown(extensions=['markdown.extensions.footnotes:FootnoteExtension'])
+    def setUp(self):
+        self.saved = sys.stdin, sys.stdout
+        sys.stdin = BytesIO(bytes('foo', encoding='utf-8'))
+        sys.stdout = BytesIO()
+
+    def tearDown(self):
+        sys.stdin, sys.stdout = self.saved
+
+    def getTempFiles(self, src):
+        """ Return the file names for two temp files. """
+        infd, infile = tempfile.mkstemp(suffix='.txt')
+        with os.fdopen(infd, 'w') as fp:
+            fp.write(src)
+        outfd, outfile = tempfile.mkstemp(suffix='.html')
+        return infile, outfile, outfd
+
+    def testFileNames(self):
+        infile, outfile, outfd = self.getTempFiles('foo')
+        markdown.markdownFromFile(input=infile, output=outfile)
+        with os.fdopen(outfd, 'r') as fp:
+            output = fp.read()
+        self.assertEqual(output, '<p>foo</p>')
+
+    def testFileObjects(self):
+        infile = BytesIO(bytes('foo', encoding='utf-8'))
+        outfile = BytesIO()
+        markdown.markdownFromFile(input=infile, output=outfile)
+        outfile.seek(0)
+        self.assertEqual(outfile.read().decode('utf-8'), '<p>foo</p>')
+
+    def testStdinStdout(self):
+        markdown.markdownFromFile()
+        sys.stdout.seek(0)
+        self.assertEqual(sys.stdout.read().decode('utf-8'), '<p>foo</p>')
 
 
 class TestBlockParser(unittest.TestCase):
@@ -133,7 +174,7 @@ def testSimpleStore(self):
         """ Test HtmlStash.store. """
         self.assertEqual(self.placeholder, self.stash.get_placeholder(0))
         self.assertEqual(self.stash.html_counter, 1)
-        self.assertEqual(self.stash.rawHtmlBlocks, [('foo', False)])
+        self.assertEqual(self.stash.rawHtmlBlocks, ['foo'])
 
     def testStoreMore(self):
         """ Test HtmlStash.store with additional blocks. """
@@ -142,15 +183,7 @@ def testStoreMore(self):
         self.assertEqual(self.stash.html_counter, 2)
         self.assertEqual(
             self.stash.rawHtmlBlocks,
-            [('foo', False), ('bar', False)]
-        )
-
-    def testSafeStore(self):
-        """ Test HtmlStash.store with 'safe' html. """
-        self.stash.store('bar', True)
-        self.assertEqual(
-            self.stash.rawHtmlBlocks,
-            [('foo', False), ('bar', True)]
+            ['foo', 'bar']
         )
 
     def testReset(self):
@@ -159,25 +192,6 @@ def testReset(self):
         self.assertEqual(self.stash.html_counter, 0)
         self.assertEqual(self.stash.rawHtmlBlocks, [])
 
-    def testUnsafeHtmlInSafeMode(self):
-        """ Test that unsafe HTML gets escaped in safe_mode. """
-        output = markdown.markdown('foo', extensions=[self.build_extension()], safe_mode='escape')
-        self.assertEqual(output, '<p>&lt;script&gt;print(&quot;evil&quot;)&lt;/script&gt;</p>')
-
-    def build_extension(self):
-        """ Build an extention that addes unsafe html to Stash in same_mode. """
-        class Unsafe(markdown.treeprocessors.Treeprocessor):
-            def run(self, root):
-                el = root.find('p')
-                el.text = self.markdown.htmlStash.store('<script>print("evil")</script>', safe=False)
-                return root
-
-        class StoreUnsafeHtml(markdown.extensions.Extension):
-            def extendMarkdown(self, md, md_globals):
-                md.treeprocessors.add('unsafe', Unsafe(md), '_end')
-
-        return StoreUnsafeHtml()
-
 
 class TestOrderedDict(unittest.TestCase):
     """ Test OrderedDict storage class. """
@@ -340,7 +354,7 @@ def tearDown(self):
 
     def testNonUnicodeSource(self):
         """ Test falure on non-unicode source text. """
-        if sys.version_info < (3, 0):
+        if not PY3:
             source = "foo".encode('utf-16')
             self.assertRaises(UnicodeDecodeError, markdown.markdown, source)
 
@@ -355,10 +369,6 @@ def testLoadExtensionFailure(self):
             markdown.Markdown, extensions=['non_existant_ext']
         )
 
-    def testLoadBadExtension(self):
-        """ Test loading of an Extension with no makeExtension function. """
-        self.assertRaises(AttributeError, markdown.Markdown, extensions=['markdown.util'])
-
     def testNonExtension(self):
         """ Test loading a non Extension object as an extension. """
         self.assertRaises(TypeError, markdown.Markdown, extensions=[object])
@@ -370,53 +380,6 @@ def testBaseExtention(self):
             markdown.Markdown, extensions=[markdown.extensions.Extension()]
         )
 
-    def testMdxExtention(self):
-        """ Test that prepending mdx_ raises a DeprecationWarning. """
-        _create_fake_extension(name='fake', use_old_style=True)
-        self.assertRaises(
-            DeprecationWarning,
-            markdown.Markdown, extensions=['fake']
-        )
-
-    def testShortNameExtention(self):
-        """ Test that using a short name raises a DeprecationWarning. """
-        self.assertRaises(
-            DeprecationWarning,
-            markdown.Markdown, extensions=['footnotes']
-        )
-
-    def testStringConfigExtention(self):
-        """ Test that passing configs to an Extension in the name raises a DeprecationWarning. """
-        self.assertRaises(
-            DeprecationWarning,
-            markdown.Markdown, extensions=['markdown.extension.footnotes(PLACE_MARKER=FOO)']
-        )
-
-
-def _create_fake_extension(name, has_factory_func=True, is_wrong_type=False, use_old_style=False):
-    """ Create a fake extension module for testing. """
-    if use_old_style:
-        mod_name = '_'.join(['mdx', name])
-    else:
-        mod_name = 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(*args, **kwargs):
-        if is_wrong_type:
-            return object
-        else:
-            return markdown.extensions.Extension(*args, **kwargs)
-
-    if has_factory_func:
-        ext_mod.makeExtension = makeExtension
-    # Warning: this brute forces the extenson module onto the system. Either
-    # this needs to be specificly overriden or a new python session needs to
-    # be started to get rid of this. This should be ok in a testing context.
-    sys.modules[mod_name] = ext_mod
-
 
 class testETreeComments(unittest.TestCase):
     """
@@ -520,7 +483,7 @@ def fakeSerializer(elem):
             return '<div><p>foo</p></div>'
 
         class registerFakeSerializer(markdown.extensions.Extension):
-            def extendMarkdown(self, md, md_globals):
+            def extendMarkdown(self, md):
                 md.output_formats['fake'] = fakeSerializer
 
         return registerFakeSerializer()
@@ -533,6 +496,18 @@ def testRegisterSerializer(self):
             '<p>foo</p>'
         )
 
+    def testXHTMLOutput(self):
+        self.assertEqual(
+            markdown.markdown('foo  \nbar', output_format='xhtml'),
+            '<p>foo<br />\nbar</p>'
+        )
+
+    def testHTMLOutput(self):
+        self.assertEqual(
+            markdown.markdown('foo  \nbar', output_format='html'),
+            '<p>foo<br>\nbar</p>'
+        )
+
 
 class testAtomicString(unittest.TestCase):
     """ Test that AtomicStrings are honored (not parsed). """
@@ -662,11 +637,6 @@ def testEncodingOption(self):
         self.default_options['encoding'] = 'utf-8'
         self.assertEqual(options, self.default_options)
 
-    def testSafeModeOption(self):
-        options, logging_level = parse_options(['-s', 'escape'])
-        self.default_options['safe_mode'] = 'escape'
-        self.assertEqual(options, self.default_options)
-
     def testOutputFormatOption(self):
         options, logging_level = parse_options(['-o', 'html5'])
         self.default_options['output_format'] = 'html5'
diff --git a/tests/test_extensions.py b/tests/test_extensions.py
index 38f0be3c9..17f152eeb 100644
--- a/tests/test_extensions.py
+++ b/tests/test_extensions.py
@@ -61,7 +61,7 @@ class TestAbbr(unittest.TestCase):
     """ Test abbr extension. """
 
     def setUp(self):
-        self.md = markdown.Markdown(extensions=['markdown.extensions.abbr'])
+        self.md = markdown.Markdown(extensions=['abbr'])
 
     def testSimpleAbbr(self):
         """ Test Abbreviations. """
@@ -98,7 +98,7 @@ def setUp(self):
 
     def testBasicCodeHilite(self):
         text = '\t# A Code Comment'
-        md = markdown.Markdown(extensions=['markdown.extensions.codehilite'])
+        md = markdown.Markdown(extensions=['codehilite'])
         if self.has_pygments:
             # Pygments can use random lexer here as we did not specify the language
             self.assertTrue(md.convert(text).startswith('<div class="codehilite"><pre>'))
@@ -206,7 +206,7 @@ def testHighlightLinesWithColon(self):
         text1 = "\t:::Python hl_lines='2'\n\t#line 1\n\t#line 2\n\t#line 3"
 
         for text in (text0, text1):
-            md = markdown.Markdown(extensions=['markdown.extensions.codehilite'])
+            md = markdown.Markdown(extensions=['codehilite'])
             if self.has_pygments:
                 self.assertEqual(
                     md.convert(text),
@@ -241,7 +241,7 @@ class TestFencedCode(unittest.TestCase):
     """ Test fenced_code extension. """
 
     def setUp(self):
-        self.md = markdown.Markdown(extensions=['markdown.extensions.fenced_code'])
+        self.md = markdown.Markdown(extensions=['fenced_code'])
         self.has_pygments = True
         try:
             import pygments  # noqa
@@ -328,7 +328,7 @@ def testFencedCodeWithHighlightLines(self):
         md = markdown.Markdown(
             extensions=[
                 markdown.extensions.codehilite.CodeHiliteExtension(linenums=None, guess_lang=False),
-                'markdown.extensions.fenced_code'
+                'fenced_code'
             ]
         )
 
@@ -368,7 +368,7 @@ def testFencedLanguageAndHighlightLines(self):
             md = markdown.Markdown(
                 extensions=[
                     markdown.extensions.codehilite.CodeHiliteExtension(linenums=None, guess_lang=False),
-                    'markdown.extensions.fenced_code'
+                    'fenced_code'
                 ]
             )
             if self.has_pygments:
@@ -389,65 +389,11 @@ def testFencedLanguageAndHighlightLines(self):
                 )
 
 
-class TestHeaderId(unittest.TestCase):
-    """ Test HeaderId Extension. """
-
-    def setUp(self):
-        self.md = markdown.Markdown(extensions=['markdown.extensions.headerid'])
-
-    def testBasicHeaderId(self):
-        """ Test Basic HeaderID """
-
-        text = "# Some Header #"
-        self.assertEqual(
-            self.md.convert(text),
-            '<h1 id="some-header">Some Header</h1>'
-        )
-
-    def testNoAutoIds(self):
-        """ Test HeaderIDs with no auto generated IDs. """
-
-        text = '# Some Header\n# Another Header'
-        self.assertEqual(
-            markdown.markdown(text, [markdown.extensions.headerid.HeaderIdExtension(forceid=False)]),
-            '<h1>Some Header</h1>\n'
-            '<h1>Another Header</h1>'
-        )
-
-    def testHeaderIdWithMetaData(self):
-        """ Test Header IDs with MetaData extension. """
-
-        text = '''header_level: 2
-header_forceid: Off
-
-# A Header'''
-        self.assertEqual(
-            markdown.markdown(text, ['markdown.extensions.headerid', 'markdown.extensions.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, ['markdown.extensions.headerid', 'markdown.extensions.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, ['markdown.extensions.attr_list', 'markdown.extensions.headerid']),
-            '<h1 id="foo">Header1</h1>\n'
-            '<h1 class="bar" id="header2">Header2</h1>'
-        )
-
-
 class TestMetaData(unittest.TestCase):
     """ Test MetaData extension. """
 
     def setUp(self):
-        self.md = markdown.Markdown(extensions=['markdown.extensions.meta'])
+        self.md = markdown.Markdown(extensions=['meta'])
 
     def testBasicMetaData(self):
         """ Test basic metadata. """
@@ -514,7 +460,7 @@ class TestWikiLinks(unittest.TestCase):
     """ Test Wikilinks Extension. """
 
     def setUp(self):
-        self.md = markdown.Markdown(extensions=['markdown.extensions.wikilinks'])
+        self.md = markdown.Markdown(extensions=['wikilinks'])
         self.text = "Some text with a [[WikiLink]]."
 
     def testBasicWikilinks(self):
@@ -541,7 +487,7 @@ def testSimpleSettings(self):
         """ Test Simple Settings. """
 
         self.assertEqual(markdown.markdown(
-            self.text, [
+            self.text, extensions=[
                 markdown.extensions.wikilinks.WikiLinkExtension(
                     base_url='/wiki/',
                     end_url='.html',
@@ -555,13 +501,13 @@ def testComplexSettings(self):
         """ Test Complex Settings. """
 
         md = markdown.Markdown(
-            extensions=['markdown.extensions.wikilinks'],
+            extensions=['wikilinks'],
             extension_configs={
-                'markdown.extensions.wikilinks': [
-                    ('base_url', 'http://example.com/'),
-                    ('end_url', '.html'),
-                    ('html_class', '')
-                ]
+                'wikilinks': {
+                    'base_url': 'http://example.com/',
+                    'end_url': '.html',
+                    'html_class': ''
+                }
             },
             safe_mode=True
         )
@@ -579,7 +525,7 @@ def testWikilinksMetaData(self):
 wiki_html_class:
 
 Some text with a [[WikiLink]]."""
-        md = markdown.Markdown(extensions=['markdown.extensions.meta', 'markdown.extensions.wikilinks'])
+        md = markdown.Markdown(extensions=['meta', 'wikilinks'])
         self.assertEqual(
             md.convert(text),
             '<p>Some text with a '
@@ -612,7 +558,7 @@ class TestAdmonition(unittest.TestCase):
     """ Test Admonition Extension. """
 
     def setUp(self):
-        self.md = markdown.Markdown(extensions=['markdown.extensions.admonition'])
+        self.md = markdown.Markdown(extensions=['admonition'])
 
     def testRE(self):
         RE = self.md.parser.blockprocessors['admonition'].RE
@@ -629,7 +575,7 @@ class TestTOC(unittest.TestCase):
     """ Test TOC Extension. """
 
     def setUp(self):
-        self.md = markdown.Markdown(extensions=['markdown.extensions.toc'])
+        self.md = markdown.Markdown(extensions=['toc'])
 
     def testMarker(self):
         """ Test TOC with a Marker. """
@@ -798,7 +744,7 @@ def testTitle(self):
 
     def testWithAttrList(self):
         """ Test TOC with attr_list Extension. """
-        md = markdown.Markdown(extensions=['markdown.extensions.toc', 'markdown.extensions.attr_list'])
+        md = markdown.Markdown(extensions=['toc', 'attr_list'])
         text = '# Header 1\n\n## Header 2 { #foo }'
         self.assertEqual(
             md.convert(text),
@@ -829,9 +775,9 @@ def testUniqueFunc(self):
 class TestSmarty(unittest.TestCase):
     def setUp(self):
         config = {
-            'markdown.extensions.smarty': [
-                ('smart_angled_quotes', True),
-                ('substitutions', {
+            'smarty': {
+                'smart_angled_quotes': True,
+                'substitutions': {
                     'ndash': '\u2013',
                     'mdash': '\u2014',
                     'ellipsis': '\u2026',
@@ -841,11 +787,11 @@ def setUp(self):
                     'right-double-quote': '&ldquo;',
                     'left-angle-quote': '[',
                     'right-angle-quote': ']',
-                }),
-            ]
+                },
+            }
         }
         self.md = markdown.Markdown(
-            extensions=['markdown.extensions.smarty'],
+            extensions=['smarty'],
             extension_configs=config
         )
 
diff --git a/tox.ini b/tox.ini
index c4384a218..83ab2e9df 100644
--- a/tox.ini
+++ b/tox.ini
@@ -4,12 +4,14 @@ envlist = py27, py32, py33, py34, pypy, flake8, checkspelling
 [testenv]
 downloadcache = {toxworkdir}/cache
 deps = -rtest-requirements.txt
-commands = coverage run --source=markdown {toxinidir}/run-tests.py {posargs}
+commands = coverage erase
+           {envbindir}/python setup.py develop
+           coverage run --source=markdown {envbindir}/mdtests {posargs}
            coverage report --show-missing
 
 [testenv:flake8]
 deps = flake8
-commands = flake8 {toxinidir}/markdown {toxinidir}/tests {toxinidir}/setup.py {toxinidir}/run-tests.py
+commands = flake8 {toxinidir}/markdown {toxinidir}/tests {toxinidir}/setup.py
 
 [testenv:checkspelling]
 deps =
