Upgrade Markdown to 2.5.2

This commit is contained in:
Matthew Jones
2015-01-29 13:04:30 -05:00
parent 6c65ba826d
commit 692dcc1317
30 changed files with 663 additions and 749 deletions

View File

@@ -33,7 +33,7 @@ importlib==1.0.3 (importlib/*, needed for Python 2.6 support)
iso8601==0.1.10 (iso8601/*) iso8601==0.1.10 (iso8601/*)
keyring==4.0 (keyring/*, excluded bin/keyring) keyring==4.0 (keyring/*, excluded bin/keyring)
kombu==3.0.21 (kombu/*) kombu==3.0.21 (kombu/*)
Markdown==2.4.1 (markdown/*, excluded bin/markdown_py) Markdown==2.5.2 (markdown/*, excluded bin/markdown_py)
mock==1.0.1 (mock.py) mock==1.0.1 (mock.py)
ordereddict==1.1 (ordereddict.py, needed for Python 2.6 support) ordereddict==1.1 (ordereddict.py, needed for Python 2.6 support)
os-diskconfig-python-novaclient-ext==0.1.2 (os_diskconfig_python_novaclient_ext/*) os-diskconfig-python-novaclient-ext==0.1.2 (os_diskconfig_python_novaclient_ext/*)

View File

@@ -10,7 +10,7 @@ called from the command line.
import markdown import markdown
html = markdown.markdown(your_text_string) html = markdown.markdown(your_text_string)
See <http://packages.python.org/Markdown/> for more See <https://pythonhosted.org/Markdown/> for more
information and instructions on how to extend the functionality of information and instructions on how to extend the functionality of
Python Markdown. Read that before you try modifying this file. Python Markdown. Read that before you try modifying this file.
@@ -36,6 +36,8 @@ from .__version__ import version, version_info
import codecs import codecs
import sys import sys
import logging import logging
import warnings
import importlib
from . import util from . import util
from .preprocessors import build_preprocessors from .preprocessors import build_preprocessors
from .blockprocessors import build_block_parser from .blockprocessors import build_block_parser
@@ -48,6 +50,7 @@ from .serializers import to_html_string, to_xhtml_string
__all__ = ['Markdown', 'markdown', 'markdownFromFile'] __all__ = ['Markdown', 'markdown', 'markdownFromFile']
logger = logging.getLogger('MARKDOWN') logger = logging.getLogger('MARKDOWN')
logging.captureWarnings(True)
class Markdown(object): class Markdown(object):
@@ -96,8 +99,8 @@ class Markdown(object):
Note that it is suggested that the more specific formats ("xhtml1" Note that it is suggested that the more specific formats ("xhtml1"
and "html4") be used as "xhtml" or "html" may change in the future and "html4") be used as "xhtml" or "html" may change in the future
if it makes sense at that time. if it makes sense at that time.
* safe_mode: Disallow raw html. One of "remove", "replace" or "escape". * safe_mode: Deprecated! Disallow raw html. One of "remove", "replace" or "escape".
* html_replacement_text: Text used when safe_mode is set to "replace". * html_replacement_text: Deprecated! Text used when safe_mode is set to "replace".
* tab_length: Length of tabs in the source. Default: 4 * tab_length: Length of tabs in the source. Default: 4
* enable_attributes: Enable the conversion of attributes. Default: True * enable_attributes: Enable the conversion of attributes. Default: True
* smart_emphasis: Treat `_connected_words_` intelligently Default: True * smart_emphasis: Treat `_connected_words_` intelligently Default: True
@@ -107,14 +110,16 @@ class Markdown(object):
# For backward compatibility, loop through old positional args # For backward compatibility, loop through old positional args
pos = ['extensions', 'extension_configs', 'safe_mode', 'output_format'] pos = ['extensions', 'extension_configs', 'safe_mode', 'output_format']
c = 0 for c, arg in enumerate(args):
for arg in args:
if pos[c] not in kwargs: if pos[c] not in kwargs:
kwargs[pos[c]] = arg kwargs[pos[c]] = arg
c += 1 if c+1 == len(pos): #pragma: no cover
if c == len(pos):
# ignore any additional args # ignore any additional args
break break
if len(args):
warnings.warn('Positional arguments are pending depreacted in Markdown '
'and will be deprecated in version 2.6. Use keyword '
'arguments only.', PendingDeprecationWarning)
# Loop through kwargs and assign defaults # Loop through kwargs and assign defaults
for option, default in self.option_defaults.items(): for option, default in self.option_defaults.items():
@@ -125,6 +130,18 @@ class Markdown(object):
# Disable attributes in safeMode when not explicitly set # Disable attributes in safeMode when not explicitly set
self.enable_attributes = False self.enable_attributes = False
if 'safe_mode' in kwargs:
warnings.warn('"safe_mode" is pending deprecation in Python-Markdown '
'and will be deprecated in version 2.6. Use an HTML '
'sanitizer (like Bleach http://bleach.readthedocs.org/) '
'if you are parsing untrusted markdown text. See the '
'2.5 release notes for more info', PendingDeprecationWarning)
if 'html_replacement_text' in kwargs:
warnings.warn('The "html_replacement_text" keyword is pending deprecation '
'in Python-Markdown and will be deprecated in version 2.6 '
'along with "safe_mode".', PendingDeprecationWarning)
self.registeredExtensions = [] self.registeredExtensions = []
self.docType = "" self.docType = ""
self.stripTopLevelTags = True self.stripTopLevelTags = True
@@ -160,9 +177,11 @@ class Markdown(object):
""" """
for ext in extensions: for ext in extensions:
if isinstance(ext, util.string_type): if isinstance(ext, util.string_type):
ext = self.build_extension(ext, configs.get(ext, [])) ext = self.build_extension(ext, configs.get(ext, {}))
if isinstance(ext, Extension): if isinstance(ext, Extension):
ext.extendMarkdown(self, globals()) ext.extendMarkdown(self, globals())
logger.debug('Successfully loaded extension "%s.%s".'
% (ext.__class__.__module__, ext.__class__.__name__))
elif ext is not None: elif ext is not None:
raise TypeError( raise TypeError(
'Extension "%s.%s" must be of type: "markdown.Extension"' 'Extension "%s.%s" must be of type: "markdown.Extension"'
@@ -170,52 +189,87 @@ class Markdown(object):
return self return self
def build_extension(self, ext_name, configs = []): def build_extension(self, ext_name, configs):
"""Build extension by name, then return the module. """Build extension by name, then return the module.
The extension name may contain arguments as part of the string in the The extension name may contain arguments as part of the string in the
following format: "extname(key1=value1,key2=value2)" following format: "extname(key1=value1,key2=value2)"
""" """
# Parse extensions config params (ignore the order)
configs = dict(configs) configs = dict(configs)
# Parse extensions config params (ignore the order)
pos = ext_name.find("(") # find the first "(" pos = ext_name.find("(") # find the first "("
if pos > 0: if pos > 0:
ext_args = ext_name[pos+1:-1] ext_args = ext_name[pos+1:-1]
ext_name = ext_name[:pos] ext_name = ext_name[:pos]
pairs = [x.split("=") for x in ext_args.split(",")] pairs = [x.split("=") for x in ext_args.split(",")]
configs.update([(x.strip(), y.strip()) for (x, y) in pairs]) configs.update([(x.strip(), y.strip()) for (x, y) in pairs])
warnings.warn('Setting configs in the Named Extension string is pending deprecation. '
'It is recommended that you pass an instance of the extension class to '
'Markdown or use the "extension_configs" keyword. The current behavior '
'will be deprecated in version 2.6 and raise an error in version 2.7. '
'See the Release Notes for Python-Markdown version 2.5 for more info.',
PendingDeprecationWarning)
# Setup the module name # Get class name (if provided): `path.to.module:ClassName`
module_name = ext_name ext_name, class_name = ext_name.split(':', 1) if ':' in ext_name else (ext_name, '')
if '.' not in ext_name:
module_name = '.'.join(['markdown.extensions', ext_name])
# Try loading the extension first from one place, then another # Try loading the extension first from one place, then another
try: # New style (markdown.extensions.<extension>) try:
module = __import__(module_name, {}, {}, [module_name.rpartition('.')[0]]) # 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, 'extendMarkdown') or (class_name and hasattr(module, class_name))):
# We have a name conflict (eg: extensions=['tables'] and PyTables is installed)
raise ImportError
except ImportError: except ImportError:
module_name_old_style = '_'.join(['mdx', ext_name]) # Preppend `markdown.extensions.` to name
try: # Old style (mdx_<extension>) module_name = '.'.join(['markdown.extensions', ext_name])
module = __import__(module_name_old_style) try:
except ImportError as e: module = importlib.import_module(module_name)
message = "Failed loading extension '%s' from '%s' or '%s'" \ logger.debug('Successfuly imported extension module "%s".' % module_name)
% (ext_name, module_name, module_name_old_style) warnings.warn('Using short names for Markdown\'s builtin extensions is pending deprecation. '
'Use the full path to the extension with Python\'s dot notation '
'(eg: "%s" instead of "%s"). The current behavior will be deprecated in '
'version 2.6 and raise an error in version 2.7. See the Release Notes for '
'Python-Markdown version 2.5 for more info.' % (module_name, ext_name),
PendingDeprecationWarning)
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 behavuor of appending "mdx_" to an extension name '
'is pending deprecation. Use the full path to the extension with '
'Python\'s dot notation (eg: "%s" instead of "%s"). The '
'current behavior will be deprecated in version 2.6 and raise an '
'error in version 2.7. See the Release Notes for Python-Markdown '
'version 2.5 for more info.' % (module_name_old_style, ext_name),
PendingDeprecationWarning)
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:] e.args = (message,) + e.args[1:]
raise raise
# If the module is loaded successfully, we expect it to define a
# function called makeExtension()
try:
return module.makeExtension(configs.items())
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): def registerExtension(self, extension):
""" This gets called by the extension """ """ This gets called by the extension """
self.registeredExtensions.append(extension) self.registeredExtensions.append(extension)
@@ -303,7 +357,7 @@ class Markdown(object):
start = output.index('<%s>'%self.doc_tag)+len(self.doc_tag)+2 start = output.index('<%s>'%self.doc_tag)+len(self.doc_tag)+2
end = output.rindex('</%s>'%self.doc_tag) end = output.rindex('</%s>'%self.doc_tag)
output = output[start:end].strip() output = output[start:end].strip()
except ValueError: except ValueError: #pragma: no cover
if output.strip().endswith('<%s />'%self.doc_tag): if output.strip().endswith('<%s />'%self.doc_tag):
# We have an empty document # We have an empty document
output = '' output = ''
@@ -434,6 +488,10 @@ def markdownFromFile(*args, **kwargs):
c += 1 c += 1
if c == len(pos): if c == len(pos):
break break
if len(args):
warnings.warn('Positional arguments are pending depreacted in Markdown '
'and will be deprecated in version 2.6. Use keyword '
'arguments only.', PendingDeprecationWarning)
md = Markdown(**kwargs) md = Markdown(**kwargs)
md.convertFile(kwargs.get('input', None), md.convertFile(kwargs.get('input', None),

View File

@@ -7,20 +7,25 @@ COMMAND-LINE SPECIFIC STUFF
import markdown import markdown
import sys import sys
import optparse import optparse
import codecs
try:
import yaml
except ImportError: #pragma: no cover
import json as yaml
import logging import logging
from logging import DEBUG, INFO, CRITICAL from logging import DEBUG, INFO, CRITICAL
logger = logging.getLogger('MARKDOWN') logger = logging.getLogger('MARKDOWN')
def parse_options(): def parse_options(args=None, values=None):
""" """
Define and parse `optparse` options for command-line usage. Define and parse `optparse` options for command-line usage.
""" """
usage = """%prog [options] [INPUTFILE] usage = """%prog [options] [INPUTFILE]
(STDIN is assumed if no INPUTFILE is given)""" (STDIN is assumed if no INPUTFILE is given)"""
desc = "A Python implementation of John Gruber's Markdown. " \ desc = "A Python implementation of John Gruber's Markdown. " \
"http://packages.python.org/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 = optparse.OptionParser(usage=usage, description=desc, version=ver)
@@ -29,28 +34,36 @@ def parse_options():
metavar="OUTPUT_FILE") metavar="OUTPUT_FILE")
parser.add_option("-e", "--encoding", dest="encoding", parser.add_option("-e", "--encoding", dest="encoding",
help="Encoding for input and output files.",) 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'.")
parser.add_option("-n", "--no_lazy_ol", dest="lazy_ol",
action='store_false', default=True,
help="Observe number of first item of ordered lists.")
parser.add_option("-x", "--extension", action="append", dest="extensions",
help = "Load extension EXTENSION.", metavar="EXTENSION")
parser.add_option("-c", "--extension_configs", dest="configfile", default=None,
help="Read extension configurations from CONFIG_FILE. "
"CONFIG_FILE must be of JSON or YAML format. YAML format requires "
"that a python YAML library be installed. The parsed JSON or YAML "
"must result in a python dictionary which would be accepted by the "
"'extension_configs' keyword on the markdown.Markdown class. "
"The extensions must also be loaded with the `--extension` option.",
metavar="CONFIG_FILE")
parser.add_option("-q", "--quiet", default = CRITICAL, parser.add_option("-q", "--quiet", default = CRITICAL,
action="store_const", const=CRITICAL+10, dest="verbose", action="store_const", const=CRITICAL+10, dest="verbose",
help="Suppress all warnings.") help="Suppress all warnings.")
parser.add_option("-v", "--verbose", parser.add_option("-v", "--verbose",
action="store_const", const=INFO, dest="verbose", action="store_const", const=INFO, dest="verbose",
help="Print all warnings.") help="Print all warnings.")
parser.add_option("-s", "--safe", dest="safe", default=False,
metavar="SAFE_MODE",
help="'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'.")
parser.add_option("--noisy", parser.add_option("--noisy",
action="store_const", const=DEBUG, dest="verbose", action="store_const", const=DEBUG, dest="verbose",
help="Print debug messages.") help="Print debug messages.")
parser.add_option("-x", "--extension", action="append", dest="extensions",
help = "Load extension EXTENSION.", metavar="EXTENSION")
parser.add_option("-n", "--no_lazy_ol", dest="lazy_ol",
action='store_false', default=True,
help="Observe number of first item of ordered lists.")
(options, args) = parser.parse_args() (options, args) = parser.parse_args(args, values)
if len(args) == 0: if len(args) == 0:
input_file = None input_file = None
@@ -60,15 +73,26 @@ def parse_options():
if not options.extensions: if not options.extensions:
options.extensions = [] options.extensions = []
extension_configs = {}
if options.configfile:
with codecs.open(options.configfile, mode="r", encoding=options.encoding) as fp:
try:
extension_configs = yaml.load(fp)
except Exception as e:
message = "Failed parsing extension config file: %s" % options.configfile
e.args = (message,) + e.args[1:]
raise
return {'input': input_file, return {'input': input_file,
'output': options.filename, 'output': options.filename,
'safe_mode': options.safe, 'safe_mode': options.safe,
'extensions': options.extensions, 'extensions': options.extensions,
'extension_configs': extension_configs,
'encoding': options.encoding, 'encoding': options.encoding,
'output_format': options.output_format, 'output_format': options.output_format,
'lazy_ol': options.lazy_ol}, options.verbose 'lazy_ol': options.lazy_ol}, options.verbose
def run(): def run(): #pragma: no cover
"""Run Markdown from the command line.""" """Run Markdown from the command line."""
# Parse options and adjust logging level if necessary # Parse options and adjust logging level if necessary
@@ -80,7 +104,7 @@ def run():
# Run # Run
markdown.markdownFromFile(**options) markdown.markdownFromFile(**options)
if __name__ == '__main__': if __name__ == '__main__': #pragma: no cover
# Support running module as a commandline command. # Support running module as a commandline command.
# Python 2.5 & 2.6 do: `python -m markdown.__main__ [options] [args]`. # Python 2.5 & 2.6 do: `python -m markdown.__main__ [options] [args]`.
# Python 2.7 & 3.x do: `python -m markdown [options] [args]`. # Python 2.7 & 3.x do: `python -m markdown [options] [args]`.

View File

@@ -5,7 +5,7 @@
# (major, minor, micro, alpha/beta/rc/final, #) # (major, minor, micro, alpha/beta/rc/final, #)
# (1, 1, 2, 'alpha', 0) => "1.1.2.dev" # (1, 1, 2, 'alpha', 0) => "1.1.2.dev"
# (1, 2, 0, 'beta', 2) => "1.2b2" # (1, 2, 0, 'beta', 2) => "1.2b2"
version_info = (2, 4, 1, 'final', 0) version_info = (2, 5, 2, 'final', 0)
def _get_version(): def _get_version():
" Returns a PEP 386-compliant version number from version_info. " " Returns a PEP 386-compliant version number from version_info. "

View File

@@ -99,7 +99,7 @@ class BlockProcessor:
* ``block``: A block of text from the source which has been split at * ``block``: A block of text from the source which has been split at
blank lines. blank lines.
""" """
pass pass #pragma: no cover
def run(self, parent, blocks): def run(self, parent, blocks):
""" Run processor. Must be overridden by subclasses. """ Run processor. Must be overridden by subclasses.
@@ -123,7 +123,7 @@ class BlockProcessor:
* ``parent``: A etree element which is the parent of the current block. * ``parent``: A etree element which is the parent of the current block.
* ``blocks``: A list of all remaining blocks of the document. * ``blocks``: A list of all remaining blocks of the document.
""" """
pass pass #pragma: no cover
class ListIndentProcessor(BlockProcessor): class ListIndentProcessor(BlockProcessor):
@@ -433,7 +433,7 @@ class HashHeaderProcessor(BlockProcessor):
if after: if after:
# Insert remaining lines as first block for future parsing. # Insert remaining lines as first block for future parsing.
blocks.insert(0, after) blocks.insert(0, after)
else: else: #pragma: no cover
# This should never happen, but just in case... # This should never happen, but just in case...
logger.warn("We've got a problem header: %r" % block) logger.warn("We've got a problem header: %r" % block)

View File

@@ -4,17 +4,45 @@ Extensions
""" """
from __future__ import unicode_literals from __future__ import unicode_literals
from ..util import parseBoolValue
import warnings
class Extension(object): class Extension(object):
""" Base class for extensions to subclass. """ """ Base class for extensions to subclass. """
def __init__(self, configs = {}):
"""Create an instance of an Extention. # Default config -- to be overriden by a subclass
# Must be of the following format:
# {
# 'key': ['value', 'description']
# }
# Note that Extension.setConfig will raise a KeyError
# if a default is not set here.
config = {}
def __init__(self, *args, **kwargs):
""" Initiate Extension and set up configs. """
Keyword arguments: # check for configs arg for backward compat.
# (there only ever used to be one so we use arg[0])
* configs: A dict of configuration setting used by an Extension. if len(args):
""" self.setConfigs(args[0])
self.config = configs warnings.warn('Extension classes accepting positional args is pending Deprecation. '
'Each setting should be passed into the Class as a keyword. Positional '
'args will be deprecated in version 2.6 and raise an error in version '
'2.7. See the Release Notes for Python-Markdown version 2.5 for more info.',
PendingDeprecationWarning)
# check for configs kwarg for backward compat.
if 'configs' in kwargs.keys():
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 will be deprecated in version '
'2.6 and raise an error in version 2.7. See the Release Notes for '
'Python-Markdown version 2.5 for more info.',
PendingDeprecationWarning)
# finally, use kwargs
self.setConfigs(kwargs)
def getConfig(self, key, default=''): def getConfig(self, key, default=''):
""" Return a setting for the given key or an empty string. """ """ Return a setting for the given key or an empty string. """
@@ -33,8 +61,20 @@ class Extension(object):
def setConfig(self, key, value): def setConfig(self, key, value):
""" Set a config setting for `key` with the given `value`. """ """ Set a config setting for `key` with the given `value`. """
if isinstance(self.config[key][0], bool):
value = parseBoolValue(value)
if self.config[key][0] is None:
value = parseBoolValue(value, preserve_none=True)
self.config[key][0] = value self.config[key][0] = value
def setConfigs(self, items):
""" Set multiple config settings given a dict or list of tuples. """
if hasattr(items, 'items'):
# it's a dict
items = items.items()
for key, value in items:
self.setConfig(key, value)
def extendMarkdown(self, md, md_globals): def extendMarkdown(self, md, md_globals):
""" """
Add the various proccesors and patterns to the Markdown Instance. Add the various proccesors and patterns to the Markdown Instance.

View File

@@ -4,22 +4,15 @@ Abbreviation Extension for Python-Markdown
This extension adds abbreviation handling to Python-Markdown. This extension adds abbreviation handling to Python-Markdown.
Simple Usage: See <https://pythonhosted.org/Markdown/extensions/abbreviations.html>
for documentation.
>>> import markdown Oringinal code Copyright 2007-2008 [Waylan Limberg](http://achinghead.com/) and
>>> text = """ [Seemant Kulleen](http://www.kulleen.org/)
... Some text with an ABBR and a REF. Ignore REFERENCE and ref.
...
... *[ABBR]: Abbreviation
... *[REF]: Abbreviation Reference
... """
>>> print markdown.markdown(text, ['abbr'])
<p>Some text with an <abbr title="Abbreviation">ABBR</abbr> and a <abbr title="Abbreviation Reference">REF</abbr>. Ignore REFERENCE and ref.</p>
Copyright 2007-2008 All changes Copyright 2008-2014 The Python Markdown Project
* [Waylan Limberg](http://achinghead.com/)
* [Seemant Kulleen](http://www.kulleen.org/) License: [BSD](http://www.opensource.org/licenses/bsd-license.php)
''' '''
@@ -92,5 +85,5 @@ class AbbrPattern(Pattern):
abbr.set('title', self.title) abbr.set('title', self.title)
return abbr return abbr
def makeExtension(configs=None): def makeExtension(*args, **kwargs):
return AbbrExtension(configs=configs) return AbbrExtension(*args, **kwargs)

View File

@@ -4,39 +4,16 @@ Admonition extension for Python-Markdown
Adds rST-style admonitions. Inspired by [rST][] feature with the same name. Adds rST-style admonitions. Inspired by [rST][] feature with the same name.
The syntax is (followed by an indented block with the contents):
!!! [type] [optional explicit title]
Where `type` is used as a CSS class name of the div. If not present, `title`
defaults to the capitalized `type`, so "note" -> "Note".
rST suggests the following `types`, but you're free to use whatever you want:
attention, caution, danger, error, hint, important, note, tip, warning
A simple example:
!!! note
This is the first line inside the box.
Outputs:
<div class="admonition note">
<p class="admonition-title">Note</p>
<p>This is the first line inside the box</p>
</div>
You can also specify the title and CSS class of the admonition:
!!! custom "Did you know?"
Another line here.
Outputs:
<div class="admonition custom">
<p class="admonition-title">Did you know?</p>
<p>Another line here.</p>
</div>
[rST]: http://docutils.sourceforge.net/docs/ref/rst/directives.html#specific-admonitions [rST]: http://docutils.sourceforge.net/docs/ref/rst/directives.html#specific-admonitions
By [Tiago Serafim](http://www.tiagoserafim.com/). See <https://pythonhosted.org/Markdown/extensions/admonition.html>
for documentation.
Original code Copyright [Tiago Serafim](http://www.tiagoserafim.com/).
All changes Copyright The Python Markdown Project
License: [BSD](http://www.opensource.org/licenses/bsd-license.php)
""" """
@@ -114,5 +91,6 @@ class AdmonitionProcessor(BlockProcessor):
return klass, title return klass, title
def makeExtension(configs={}): def makeExtension(*args, **kwargs):
return AdmonitionExtension(configs=configs) return AdmonitionExtension(*args, **kwargs)

View File

@@ -6,15 +6,14 @@ Adds attribute list syntax. Inspired by
[maruku](http://maruku.rubyforge.org/proposal.html#attribute_lists)'s [maruku](http://maruku.rubyforge.org/proposal.html#attribute_lists)'s
feature of the same name. feature of the same name.
Copyright 2011 [Waylan Limberg](http://achinghead.com/). See <https://pythonhosted.org/Markdown/extensions/attr_list.html>
for documentation.
Contact: markdown@freewisdom.org Original code Copyright 2011 [Waylan Limberg](http://achinghead.com/).
License: BSD (see ../LICENSE.md for details) All changes Copyright 2011-2014 The Python Markdown Project
Dependencies: License: [BSD](http://www.opensource.org/licenses/bsd-license.php)
* [Python 2.4+](http://python.org)
* [Markdown 2.1+](http://packages.python.org/Markdown/)
""" """
@@ -27,7 +26,7 @@ import re
try: try:
Scanner = re.Scanner Scanner = re.Scanner
except AttributeError: except AttributeError: #pragma: no cover
# must be on Python 2.4 # must be on Python 2.4
from sre import Scanner from sre import Scanner
@@ -164,5 +163,5 @@ class AttrListExtension(Extension):
md.treeprocessors.add('attr_list', AttrListTreeprocessor(md), '>prettify') md.treeprocessors.add('attr_list', AttrListTreeprocessor(md), '>prettify')
def makeExtension(configs={}): def makeExtension(*args, **kwargs):
return AttrListExtension(configs=configs) return AttrListExtension(*args, **kwargs)

View File

@@ -4,17 +4,14 @@ CodeHilite Extension for Python-Markdown
Adds code/syntax highlighting to standard Python-Markdown code blocks. Adds code/syntax highlighting to standard Python-Markdown code blocks.
Copyright 2006-2008 [Waylan Limberg](http://achinghead.com/). See <https://pythonhosted.org/Markdown/extensions/code_hilite.html>
for documentation.
Project website: <http://packages.python.org/Markdown/extensions/code_hilite.html> Original code Copyright 2006-2008 [Waylan Limberg](http://achinghead.com/).
Contact: markdown@freewisdom.org
License: BSD (see ../LICENSE.md for details) All changes Copyright 2008-2014 The Python Markdown Project
Dependencies: License: [BSD](http://www.opensource.org/licenses/bsd-license.php)
* [Python 2.3+](http://python.org/)
* [Markdown 2.0+](http://packages.python.org/Markdown/)
* [Pygments](http://pygments.org/)
""" """
@@ -25,8 +22,8 @@ from ..treeprocessors import Treeprocessor
import warnings import warnings
try: try:
from pygments import highlight from pygments import highlight
from pygments.lexers import get_lexer_by_name, guess_lexer, TextLexer from pygments.lexers import get_lexer_by_name, guess_lexer
from pygments.formatters import HtmlFormatter from pygments.formatters import get_formatter_by_name
pygments = True pygments = True
except ImportError: except ImportError:
pygments = False pygments = False
@@ -112,14 +109,15 @@ class CodeHilite(object):
if self.guess_lang: if self.guess_lang:
lexer = guess_lexer(self.src) lexer = guess_lexer(self.src)
else: else:
lexer = TextLexer() lexer = get_lexer_by_name('text')
except ValueError: except ValueError:
lexer = TextLexer() lexer = get_lexer_by_name('text')
formatter = HtmlFormatter(linenos=self.linenums, formatter = get_formatter_by_name('html',
cssclass=self.css_class, linenos=self.linenums,
style=self.style, cssclass=self.css_class,
noclasses=self.noclasses, style=self.style,
hl_lines=self.hl_lines) noclasses=self.noclasses,
hl_lines=self.hl_lines)
return highlight(self.src, lexer, formatter) return highlight(self.src, lexer, formatter)
else: else:
# just escape and build markup usable by JS highlighting libs # just escape and build markup usable by JS highlighting libs
@@ -225,7 +223,7 @@ class HiliteTreeprocessor(Treeprocessor):
class CodeHiliteExtension(Extension): class CodeHiliteExtension(Extension):
""" Add source code hilighting to markdown codeblocks. """ """ Add source code hilighting to markdown codeblocks. """
def __init__(self, configs): def __init__(self, *args, **kwargs):
# define default configs # define default configs
self.config = { self.config = {
'linenums': [None, "Use lines numbers. True=yes, False=no, None=auto"], 'linenums': [None, "Use lines numbers. True=yes, False=no, None=auto"],
@@ -237,22 +235,7 @@ class CodeHiliteExtension(Extension):
'noclasses': [False, 'Use inline styles instead of CSS classes - Default false'] 'noclasses': [False, 'Use inline styles instead of CSS classes - Default false']
} }
# Override defaults with user settings super(CodeHiliteExtension, self).__init__(*args, **kwargs)
for key, value in configs:
# convert strings to booleans
if value == 'True': value = True
if value == 'False': value = False
if value == 'None': value = None
if key == 'force_linenos':
warnings.warn('The "force_linenos" config setting'
' to the CodeHilite extension is deprecrecated.'
' Use "linenums" instead.', DeprecationWarning)
if value:
# Carry 'force_linenos' over to new 'linenos'.
self.setConfig('linenums', True)
self.setConfig(key, value)
def extendMarkdown(self, md, md_globals): def extendMarkdown(self, md, md_globals):
""" Add HilitePostprocessor to Markdown instance. """ """ Add HilitePostprocessor to Markdown instance. """
@@ -263,6 +246,5 @@ class CodeHiliteExtension(Extension):
md.registerExtension(self) md.registerExtension(self)
def makeExtension(configs={}): def makeExtension(*args, **kwargs):
return CodeHiliteExtension(configs=configs) return CodeHiliteExtension(*args, **kwargs)

View File

@@ -2,19 +2,16 @@
Definition List Extension for Python-Markdown Definition List Extension for Python-Markdown
============================================= =============================================
Added parsing of Definition Lists to Python-Markdown. Adds parsing of Definition Lists to Python-Markdown.
A simple example: See <https://pythonhosted.org/Markdown/extensions/definition_lists.html>
for documentation.
Apple Original code Copyright 2008 [Waylan Limberg](http://achinghead.com)
: Pomaceous fruit of plants of the genus Malus in
the family Rosaceae.
: An american computer company.
Orange All changes Copyright 2008-2014 The Python Markdown Project
: The fruit of an evergreen tree of the genus Citrus.
Copyright 2008 - [Waylan Limberg](http://achinghead.com) License: [BSD](http://www.opensource.org/licenses/bsd-license.php)
""" """
@@ -113,6 +110,6 @@ class DefListExtension(Extension):
'>ulist') '>ulist')
def makeExtension(configs={}): def makeExtension(*args, **kwargs):
return DefListExtension(configs=configs) return DefListExtension(*args, **kwargs)

View File

@@ -11,10 +11,6 @@ convenience so that only one extension needs to be listed when
initiating Markdown. See the documentation for each individual initiating Markdown. See the documentation for each individual
extension for specifics about that extension. extension for specifics about that extension.
In the event that one or more of the supported extensions are not
available for import, Markdown will issue a warning and simply continue
without that extension.
There may be additional extensions that are distributed with There may be additional extensions that are distributed with
Python-Markdown that are not included here in Extra. Those extensions Python-Markdown that are not included here in Extra. Those extensions
are not part of PHP Markdown Extra, and therefore, not part of are not part of PHP Markdown Extra, and therefore, not part of
@@ -24,6 +20,13 @@ under a differant name. You could also edit the `extensions` global
variable defined below, but be aware that such changes may be lost variable defined below, but be aware that such changes may be lost
when you upgrade to any future version of Python-Markdown. when you upgrade to any future version of Python-Markdown.
See <https://pythonhosted.org/Markdown/extensions/extra.html>
for documentation.
Copyright The Python Markdown Project
License: [BSD](http://www.opensource.org/licenses/bsd-license.php)
""" """
from __future__ import absolute_import from __future__ import absolute_import
@@ -33,19 +36,25 @@ from ..blockprocessors import BlockProcessor
from .. import util from .. import util
import re import re
extensions = ['smart_strong', extensions = [
'fenced_code', 'markdown.extensions.smart_strong',
'footnotes', 'markdown.extensions.fenced_code',
'attr_list', 'markdown.extensions.footnotes',
'def_list', 'markdown.extensions.attr_list',
'tables', 'markdown.extensions.def_list',
'abbr', 'markdown.extensions.tables',
] 'markdown.extensions.abbr'
]
class ExtraExtension(Extension): class ExtraExtension(Extension):
""" Add various extensions to Markdown class.""" """ Add various extensions to Markdown class."""
def __init__(self, *args, **kwargs):
""" config is just a dumb holder which gets passed to actual ext later. """
self.config = kwargs.pop('configs', {})
self.config.update(kwargs)
def extendMarkdown(self, md, md_globals): def extendMarkdown(self, md, md_globals):
""" Register extension instances. """ """ Register extension instances. """
md.registerExtensions(extensions, self.config) md.registerExtensions(extensions, self.config)
@@ -60,8 +69,8 @@ class ExtraExtension(Extension):
r'^(p|h[1-6]|li|dd|dt|td|th|legend|address)$', re.IGNORECASE) r'^(p|h[1-6]|li|dd|dt|td|th|legend|address)$', re.IGNORECASE)
def makeExtension(configs={}): def makeExtension(*args, **kwargs):
return ExtraExtension(configs=dict(configs)) return ExtraExtension(*args, **kwargs)
class MarkdownInHtmlProcessor(BlockProcessor): class MarkdownInHtmlProcessor(BlockProcessor):

View File

@@ -4,87 +4,15 @@ Fenced Code Extension for Python Markdown
This extension adds Fenced Code Blocks to Python-Markdown. This extension adds Fenced Code Blocks to Python-Markdown.
>>> import markdown See <https://pythonhosted.org/Markdown/extensions/fenced_code_blocks.html>
>>> text = ''' for documentation.
... A paragraph before a fenced code block:
...
... ~~~
... Fenced code block
... ~~~
... '''
>>> html = markdown.markdown(text, extensions=['fenced_code'])
>>> print html
<p>A paragraph before a fenced code block:</p>
<pre><code>Fenced code block
</code></pre>
Works with safe_mode also (we check this because we are using the HtmlStash): Original code Copyright 2007-2008 [Waylan Limberg](http://achinghead.com/).
>>> print markdown.markdown(text, extensions=['fenced_code'], safe_mode='replace')
<p>A paragraph before a fenced code block:</p>
<pre><code>Fenced code block
</code></pre>
Include tilde's in a code block and wrap with blank lines: All changes Copyright 2008-2014 The Python Markdown Project
>>> text = '''
... ~~~~~~~~
...
... ~~~~
... ~~~~~~~~'''
>>> print markdown.markdown(text, extensions=['fenced_code'])
<pre><code>
~~~~
</code></pre>
Language tags:
>>> text = '''
... ~~~~{.python}
... # Some python code
... ~~~~'''
>>> print markdown.markdown(text, extensions=['fenced_code'])
<pre><code class="python"># Some python code
</code></pre>
Optionally backticks instead of tildes as per how github's code block markdown is identified:
>>> text = '''
... `````
... # Arbitrary code
... ~~~~~ # these tildes will not close the block
... `````'''
>>> print markdown.markdown(text, extensions=['fenced_code'])
<pre><code># Arbitrary code
~~~~~ # these tildes will not close the block
</code></pre>
If the codehighlite extension and Pygments are installed, lines can be highlighted:
>>> text = '''
... ```hl_lines="1 3"
... line 1
... line 2
... line 3
... ```'''
>>> print markdown.markdown(text, extensions=['codehilite', 'fenced_code'])
<pre><code><span class="hilight">line 1</span>
line 2
<span class="hilight">line 3</span>
</code></pre>
Copyright 2007-2008 [Waylan Limberg](http://achinghead.com/).
Project website: <http://packages.python.org/Markdown/extensions/fenced_code_blocks.html>
Contact: markdown@freewisdom.org
License: BSD (see ../docs/LICENSE for details)
Dependencies:
* [Python 2.4+](http://python.org)
* [Markdown 2.0+](http://packages.python.org/Markdown/)
* [Pygments (optional)](http://pygments.org)
License: [BSD](http://www.opensource.org/licenses/bsd-license.php)
""" """
from __future__ import absolute_import from __future__ import absolute_import
@@ -175,5 +103,6 @@ class FencedBlockPreprocessor(Preprocessor):
return txt return txt
def makeExtension(configs=None): def makeExtension(*args, **kwargs):
return FencedCodeExtension(configs=configs) return FencedCodeExtension(*args, **kwargs)

View File

@@ -1,25 +1,15 @@
""" """
========================= FOOTNOTES ================================= Footnotes Extension for Python-Markdown
=======================================
This section adds footnote handling to markdown. It can be used as Adds footnote handling to Python-Markdown.
an example for extending python-markdown with relatively complex
functionality. While in this case the extension is included inside
the module itself, it could just as easily be added from outside the
module. Not that all markdown classes above are ignorant about
footnotes. All footnote functionality is provided separately and
then added to the markdown instance at the run time.
Footnote functionality is attached by calling extendMarkdown() See <https://pythonhosted.org/Markdown/extensions/footnotes.html>
method of FootnoteExtension. The method also registers the for documentation.
extension to allow it's state to be reset by a call to reset()
method.
Example: Copyright The Python Markdown Project
Footnotes[^1] have a label[^label] and a definition[^!DEF].
[^1]: This is a footnote License: [BSD](http://www.opensource.org/licenses/bsd-license.php)
[^label]: A footnote on "label"
[^!DEF]: The footnote for definition
""" """
@@ -42,23 +32,23 @@ TABBED_RE = re.compile(r'((\t)|( ))(.*)')
class FootnoteExtension(Extension): class FootnoteExtension(Extension):
""" Footnote Extension. """ """ Footnote Extension. """
def __init__ (self, configs): def __init__ (self, *args, **kwargs):
""" Setup configs. """ """ Setup configs. """
self.config = {'PLACE_MARKER':
["///Footnotes Go Here///",
"The text string that marks where the footnotes go"],
'UNIQUE_IDS':
[False,
"Avoid name collisions across "
"multiple calls to reset()."],
"BACKLINK_TEXT":
["&#8617;",
"The text string that links from the footnote to the reader's place."]
}
for key, value in configs:
self.config[key][0] = value
self.config = {
'PLACE_MARKER':
["///Footnotes Go Here///",
"The text string that marks where the footnotes go"],
'UNIQUE_IDS':
[False,
"Avoid name collisions across "
"multiple calls to reset()."],
"BACKLINK_TEXT":
["&#8617;",
"The text string that links from the footnote to the reader's place."]
}
super(FootnoteExtension, self).__init__(*args, **kwargs)
# In multiple invocations, emit links that don't get tangled. # In multiple invocations, emit links that don't get tangled.
self.unique_prefix = 0 self.unique_prefix = 0
@@ -309,7 +299,7 @@ class FootnotePostprocessor(Postprocessor):
text = text.replace(FN_BACKLINK_TEXT, self.footnotes.getConfig("BACKLINK_TEXT")) text = text.replace(FN_BACKLINK_TEXT, self.footnotes.getConfig("BACKLINK_TEXT"))
return text.replace(NBSP_PLACEHOLDER, "&#160;") return text.replace(NBSP_PLACEHOLDER, "&#160;")
def makeExtension(configs=[]): def makeExtension(*args, **kwargs):
""" Return an instance of the FootnoteExtension """ """ Return an instance of the FootnoteExtension """
return FootnoteExtension(configs=configs) return FootnoteExtension(*args, **kwargs)

View File

@@ -4,73 +4,14 @@ HeaderID Extension for Python-Markdown
Auto-generate id attributes for HTML headers. Auto-generate id attributes for HTML headers.
Basic usage: See <https://pythonhosted.org/Markdown/extensions/header_id.html>
for documentation.
>>> import markdown Original code Copyright 2007-2011 [Waylan Limberg](http://achinghead.com/).
>>> text = "# Some Header #"
>>> md = markdown.markdown(text, ['headerid'])
>>> print md
<h1 id="some-header">Some Header</h1>
All header IDs are unique: All changes Copyright 2011-2014 The Python Markdown Project
>>> text = ''' License: [BSD](http://www.opensource.org/licenses/bsd-license.php)
... #Header
... #Header
... #Header'''
>>> md = markdown.markdown(text, ['headerid'])
>>> print md
<h1 id="header">Header</h1>
<h1 id="header_1">Header</h1>
<h1 id="header_2">Header</h1>
To fit within a html template's hierarchy, set the header base level:
>>> text = '''
... #Some Header
... ## Next Level'''
>>> md = markdown.markdown(text, ['headerid(level=3)'])
>>> print md
<h3 id="some-header">Some Header</h3>
<h4 id="next-level">Next Level</h4>
Works with inline markup.
>>> text = '#Some *Header* with [markup](http://example.com).'
>>> md = markdown.markdown(text, ['headerid'])
>>> print md
<h1 id="some-header-with-markup">Some <em>Header</em> with <a href="http://example.com">markup</a>.</h1>
Turn off auto generated IDs:
>>> text = '''
... # Some Header
... # Another Header'''
>>> md = markdown.markdown(text, ['headerid(forceid=False)'])
>>> print md
<h1>Some Header</h1>
<h1>Another Header</h1>
Use with MetaData extension:
>>> text = '''header_level: 2
... header_forceid: Off
...
... # A Header'''
>>> md = markdown.markdown(text, ['headerid', 'meta'])
>>> print md
<h2>A Header</h2>
Copyright 2007-2011 [Waylan Limberg](http://achinghead.com/).
Project website: <http://packages.python.org/Markdown/extensions/header_id.html>
Contact: markdown@freewisdom.org
License: BSD (see ../docs/LICENSE for details)
Dependencies:
* [Python 2.3+](http://python.org)
* [Markdown 2.0+](http://packages.python.org/Markdown/)
""" """
@@ -127,7 +68,7 @@ def stashedHTML2text(text, md):
def _html_sub(m): def _html_sub(m):
""" Substitute raw html with plain text. """ """ Substitute raw html with plain text. """
try: try:
raw, safe = md.htmlStash.rawHtmlBlocks[int(m.group(1))] raw, safe = md.htmlStash.rawHtmlBlocks[int(m.group(1))]
except (IndexError, TypeError): except (IndexError, TypeError):
return m.group(0) return m.group(0)
if md.safeMode and not safe: if md.safeMode and not safe:
@@ -176,7 +117,7 @@ class HeaderIdTreeprocessor(Treeprocessor):
class HeaderIdExtension(Extension): class HeaderIdExtension(Extension):
def __init__(self, configs): def __init__(self, *args, **kwargs):
# set defaults # set defaults
self.config = { self.config = {
'level' : ['1', 'Base level for headers.'], 'level' : ['1', 'Base level for headers.'],
@@ -185,8 +126,7 @@ class HeaderIdExtension(Extension):
'slugify' : [slugify, 'Callable to generate anchors'], 'slugify' : [slugify, 'Callable to generate anchors'],
} }
for key, value in configs: super(HeaderIdExtension, self).__init__(*args, **kwargs)
self.setConfig(key, value)
def extendMarkdown(self, md, md_globals): def extendMarkdown(self, md, md_globals):
md.registerExtension(self) md.registerExtension(self)
@@ -204,5 +144,6 @@ class HeaderIdExtension(Extension):
self.processor.IDs = set() self.processor.IDs = set()
def makeExtension(configs=None): def makeExtension(*args, **kwargs):
return HeaderIdExtension(configs=configs) return HeaderIdExtension(*args, **kwargs)

View File

@@ -4,38 +4,14 @@ Meta Data Extension for Python-Markdown
This extension adds Meta Data handling to markdown. This extension adds Meta Data handling to markdown.
Basic Usage: See <https://pythonhosted.org/Markdown/extensions/meta_data.html>
for documentation.
>>> import markdown Original code Copyright 2007-2008 [Waylan Limberg](http://achinghead.com).
>>> text = '''Title: A Test Doc.
... Author: Waylan Limberg
... John Doe
... Blank_Data:
...
... The body. This is paragraph one.
... '''
>>> md = markdown.Markdown(['meta'])
>>> print md.convert(text)
<p>The body. This is paragraph one.</p>
>>> print md.Meta
{u'blank_data': [u''], u'author': [u'Waylan Limberg', u'John Doe'], u'title': [u'A Test Doc.']}
Make sure text without Meta Data still works (markdown < 1.6b returns a <p>). All changes Copyright 2008-2014 The Python Markdown Project
>>> text = ' Some Code - not extra lines of meta data.' License: [BSD](http://www.opensource.org/licenses/bsd-license.php)
>>> md = markdown.Markdown(['meta'])
>>> print md.convert(text)
<pre><code>Some Code - not extra lines of meta data.
</code></pre>
>>> md.Meta
{}
Copyright 2007-2008 [Waylan Limberg](http://achinghead.com).
Project website: <http://packages.python.org/Markdown/meta_data.html>
Contact: markdown@freewisdom.org
License: BSD (see ../LICENSE.md for details)
""" """
@@ -55,7 +31,7 @@ class MetaExtension (Extension):
def extendMarkdown(self, md, md_globals): def extendMarkdown(self, md, md_globals):
""" Add MetaPreprocessor to Markdown instance. """ """ Add MetaPreprocessor to Markdown instance. """
md.preprocessors.add("meta", MetaPreprocessor(md), "_begin") md.preprocessors.add("meta", MetaPreprocessor(md), ">normalize_whitespace")
class MetaPreprocessor(Preprocessor): class MetaPreprocessor(Preprocessor):
@@ -89,5 +65,6 @@ class MetaPreprocessor(Preprocessor):
return lines return lines
def makeExtension(configs={}): def makeExtension(*args, **kwargs):
return MetaExtension(configs=configs) return MetaExtension(*args, **kwargs)

View File

@@ -5,18 +5,14 @@ NL2BR Extension
A Python-Markdown extension to treat newlines as hard breaks; like A Python-Markdown extension to treat newlines as hard breaks; like
GitHub-flavored Markdown does. GitHub-flavored Markdown does.
Usage: See <https://pythonhosted.org/Markdown/extensions/nl2br.html>
for documentation.
>>> import markdown Oringinal code Copyright 2011 [Brian Neal](http://deathofagremmie.com/)
>>> print markdown.markdown('line 1\\nline 2', extensions=['nl2br'])
<p>line 1<br />
line 2</p>
Copyright 2011 [Brian Neal](http://deathofagremmie.com/) All changes Copyright 2011-2014 The Python Markdown Project
Dependencies: License: [BSD](http://www.opensource.org/licenses/bsd-license.php)
* [Python 2.4+](http://python.org)
* [Markdown 2.1+](http://packages.python.org/Markdown/)
""" """
@@ -34,5 +30,6 @@ class Nl2BrExtension(Extension):
md.inlinePatterns.add('nl', br_tag, '_end') md.inlinePatterns.add('nl', br_tag, '_end')
def makeExtension(configs=None): def makeExtension(*args, **kwargs):
return Nl2BrExtension(configs) return Nl2BrExtension(*args, **kwargs)

View File

@@ -2,19 +2,16 @@
Sane List Extension for Python-Markdown Sane List Extension for Python-Markdown
======================================= =======================================
Modify the behavior of Lists in Python-Markdown t act in a sane manor. Modify the behavior of Lists in Python-Markdown to act in a sane manor.
In standard Markdown syntax, the following would constitute a single See <https://pythonhosted.org/Markdown/extensions/sane_lists.html>
ordered list. However, with this extension, the output would include for documentation.
two lists, the first an ordered list and the second and unordered list.
1. ordered Original code Copyright 2011 [Waylan Limberg](http://achinghead.com)
2. list
* unordered All changes Copyright 2011-2014 The Python Markdown Project
* list
Copyright 2011 - [Waylan Limberg](http://achinghead.com) License: [BSD](http://www.opensource.org/licenses/bsd-license.php)
""" """
@@ -46,6 +43,6 @@ class SaneListExtension(Extension):
md.parser.blockprocessors['ulist'] = SaneUListProcessor(md.parser) md.parser.blockprocessors['ulist'] = SaneUListProcessor(md.parser)
def makeExtension(configs={}): def makeExtension(*args, **kwargs):
return SaneListExtension(configs=configs) return SaneListExtension(*args, **kwargs)

View File

@@ -4,21 +4,14 @@ Smart_Strong Extension for Python-Markdown
This extention adds smarter handling of double underscores within words. This extention adds smarter handling of double underscores within words.
Simple Usage: See <https://pythonhosted.org/Markdown/extensions/smart_strong.html>
for documentation.
>>> import markdown Original code Copyright 2011 [Waylan Limberg](http://achinghead.com)
>>> print markdown.markdown('Text with double__underscore__words.',
... extensions=['smart_strong'])
<p>Text with double__underscore__words.</p>
>>> print markdown.markdown('__Strong__ still works.',
... extensions=['smart_strong'])
<p><strong>Strong</strong> still works.</p>
>>> print markdown.markdown('__this__works__too__.',
... extensions=['smart_strong'])
<p><strong>this__works__too</strong>.</p>
Copyright 2011 All changes Copyright 2011-2014 The Python Markdown Project
[Waylan Limberg](http://achinghead.com)
License: [BSD](http://www.opensource.org/licenses/bsd-license.php)
''' '''
@@ -38,5 +31,5 @@ class SmartEmphasisExtension(Extension):
md.inlinePatterns['strong'] = SimpleTagPattern(STRONG_RE, 'strong') md.inlinePatterns['strong'] = SimpleTagPattern(STRONG_RE, 'strong')
md.inlinePatterns.add('strong2', SimpleTagPattern(SMART_STRONG_RE, 'strong'), '>emphasis2') md.inlinePatterns.add('strong2', SimpleTagPattern(SMART_STRONG_RE, 'strong'), '>emphasis2')
def makeExtension(configs={}): def makeExtension(*args, **kwargs):
return SmartEmphasisExtension(configs=dict(configs)) return SmartEmphasisExtension(*args, **kwargs)

View File

@@ -1,73 +1,91 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# Smarty extension for Python-Markdown '''
# Author: 2013, Dmitry Shachnev <mitya57@gmail.com> Smarty extension for Python-Markdown
====================================
Adds conversion of ASCII dashes, quotes and ellipses to their HTML
entity equivalents.
See <https://pythonhosted.org/Markdown/extensions/smarty.html>
for documentation.
Author: 2013, Dmitry Shachnev <mitya57@gmail.com>
All changes Copyright 2013-2014 The Python Markdown Project
License: [BSD](http://www.opensource.org/licenses/bsd-license.php)
SmartyPants license:
Copyright (c) 2003 John Gruber <http://daringfireball.net/>
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in
the documentation and/or other materials provided with the
distribution.
* Neither the name "SmartyPants" nor the names of its contributors
may be used to endorse or promote products derived from this
software without specific prior written permission.
This software is provided by the copyright holders and contributors "as
is" and any express or implied warranties, including, but not limited
to, the implied warranties of merchantability and fitness for a
particular purpose are disclaimed. In no event shall the copyright
owner or contributors be liable for any direct, indirect, incidental,
special, exemplary, or consequential damages (including, but not
limited to, procurement of substitute goods or services; loss of use,
data, or profits; or business interruption) however caused and on any
theory of liability, whether in contract, strict liability, or tort
(including negligence or otherwise) arising in any way out of the use
of this software, even if advised of the possibility of such damage.
smartypants.py license:
smartypants.py is a derivative work of SmartyPants.
Copyright (c) 2004, 2007 Chad Miller <http://web.chad.org/>
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in
the documentation and/or other materials provided with the
distribution.
This software is provided by the copyright holders and contributors "as
is" and any express or implied warranties, including, but not limited
to, the implied warranties of merchantability and fitness for a
particular purpose are disclaimed. In no event shall the copyright
owner or contributors be liable for any direct, indirect, incidental,
special, exemplary, or consequential damages (including, but not
limited to, procurement of substitute goods or services; loss of use,
data, or profits; or business interruption) however caused and on any
theory of liability, whether in contract, strict liability, or tort
(including negligence or otherwise) arising in any way out of the use
of this software, even if advised of the possibility of such damage.
'''
# SmartyPants license:
#
# Copyright (c) 2003 John Gruber <http://daringfireball.net/>
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are
# met:
#
# * Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
#
# * Redistributions in binary form must reproduce the above copyright
# notice, this list of conditions and the following disclaimer in
# the documentation and/or other materials provided with the
# distribution.
#
# * Neither the name "SmartyPants" nor the names of its contributors
# may be used to endorse or promote products derived from this
# software without specific prior written permission.
#
# This software is provided by the copyright holders and contributors "as
# is" and any express or implied warranties, including, but not limited
# to, the implied warranties of merchantability and fitness for a
# particular purpose are disclaimed. In no event shall the copyright
# owner or contributors be liable for any direct, indirect, incidental,
# special, exemplary, or consequential damages (including, but not
# limited to, procurement of substitute goods or services; loss of use,
# data, or profits; or business interruption) however caused and on any
# theory of liability, whether in contract, strict liability, or tort
# (including negligence or otherwise) arising in any way out of the use
# of this software, even if advised of the possibility of such damage.
#
#
# smartypants.py license:
#
# smartypants.py is a derivative work of SmartyPants.
# Copyright (c) 2004, 2007 Chad Miller <http://web.chad.org/>
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are
# met:
#
# * Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
#
# * Redistributions in binary form must reproduce the above copyright
# notice, this list of conditions and the following disclaimer in
# the documentation and/or other materials provided with the
# distribution.
#
# This software is provided by the copyright holders and contributors "as
# is" and any express or implied warranties, including, but not limited
# to, the implied warranties of merchantability and fitness for a
# particular purpose are disclaimed. In no event shall the copyright
# owner or contributors be liable for any direct, indirect, incidental,
# special, exemplary, or consequential damages (including, but not
# limited to, procurement of substitute goods or services; loss of use,
# data, or profits; or business interruption) however caused and on any
# theory of liability, whether in contract, strict liability, or tort
# (including negligence or otherwise) arising in any way out of the use
# of this software, even if advised of the possibility of such damage.
from __future__ import unicode_literals from __future__ import unicode_literals
from . import Extension from . import Extension
from ..inlinepatterns import HtmlPattern from ..inlinepatterns import HtmlPattern
from ..odict import OrderedDict
from ..treeprocessors import InlineProcessor
from ..util import parseBoolValue from ..util import parseBoolValue
# Constants for quote education. # Constants for quote education.
@@ -83,12 +101,25 @@ openingQuotesBase = (
'|&[mn]dash;' # or named dash entities '|&[mn]dash;' # or named dash entities
'|&#8211;|&#8212;' # or decimal entities '|&#8211;|&#8212;' # or decimal entities
')' ')'
) )
substitutions = {
'mdash': '&mdash;',
'ndash': '&ndash;',
'ellipsis': '&hellip;',
'left-angle-quote': '&laquo;',
'right-angle-quote': '&raquo;',
'left-single-quote': '&lsquo;',
'right-single-quote': '&rsquo;',
'left-double-quote': '&ldquo;',
'right-double-quote': '&rdquo;',
}
# Special case if the very first character is a quote # Special case if the very first character is a quote
# followed by punctuation at a non-word-break. Close the quotes by brute force: # followed by punctuation at a non-word-break. Close the quotes by brute force:
singleQuoteStartRe = r"^'(?=%s\\B)" % punctClass singleQuoteStartRe = r"^'(?=%s\B)" % punctClass
doubleQuoteStartRe = r'^"(?=%s\\B)' % punctClass doubleQuoteStartRe = r'^"(?=%s\B)' % punctClass
# Special case for double sets of quotes, e.g.: # Special case for double sets of quotes, e.g.:
# <p>He said, "'Quoted' words in a larger quote."</p> # <p>He said, "'Quoted' words in a larger quote."</p>
@@ -113,8 +144,6 @@ closingSingleQuotesRegex2 = r"(?<=%s)'(\s|s\b)" % closeClass
remainingSingleQuotesRegex = "'" remainingSingleQuotesRegex = "'"
remainingDoubleQuotesRegex = '"' remainingDoubleQuotesRegex = '"'
lsquo, rsquo, ldquo, rdquo = '&lsquo;', '&rsquo;', '&ldquo;', '&rdquo;'
class SubstituteTextPattern(HtmlPattern): class SubstituteTextPattern(HtmlPattern):
def __init__(self, pattern, replace, markdown_instance): def __init__(self, pattern, replace, markdown_instance):
""" Replaces matches with some text. """ """ Replaces matches with some text. """
@@ -132,35 +161,56 @@ class SubstituteTextPattern(HtmlPattern):
return result return result
class SmartyExtension(Extension): class SmartyExtension(Extension):
def __init__(self, configs): def __init__(self, *args, **kwargs):
self.config = { self.config = {
'smart_quotes': [True, 'Educate quotes'], 'smart_quotes': [True, 'Educate quotes'],
'smart_angled_quotes': [False, 'Educate angled quotes'],
'smart_dashes': [True, 'Educate dashes'], 'smart_dashes': [True, 'Educate dashes'],
'smart_ellipses': [True, 'Educate ellipses'] 'smart_ellipses': [True, 'Educate ellipses'],
'substitutions' : [{}, 'Overwrite default substitutions'],
} }
for key, value in configs: super(SmartyExtension, self).__init__(*args, **kwargs)
self.setConfig(key, parseBoolValue(value)) self.substitutions = dict(substitutions)
self.substitutions.update(self.getConfig('substitutions', default={}))
def _addPatterns(self, md, patterns, serie): def _addPatterns(self, md, patterns, serie):
for ind, pattern in enumerate(patterns): for ind, pattern in enumerate(patterns):
pattern += (md,) pattern += (md,)
pattern = SubstituteTextPattern(*pattern) pattern = SubstituteTextPattern(*pattern)
after = ('>smarty-%s-%d' % (serie, ind - 1) if ind else '>entity') after = ('>smarty-%s-%d' % (serie, ind - 1) if ind else '_begin')
name = 'smarty-%s-%d' % (serie, ind) name = 'smarty-%s-%d' % (serie, ind)
md.inlinePatterns.add(name, pattern, after) self.inlinePatterns.add(name, pattern, after)
def educateDashes(self, md): def educateDashes(self, md):
emDashesPattern = SubstituteTextPattern(r'(?<!-)---(?!-)', ('&mdash;',), md) emDashesPattern = SubstituteTextPattern(r'(?<!-)---(?!-)',
enDashesPattern = SubstituteTextPattern(r'(?<!-)--(?!-)', ('&ndash;',), md) (self.substitutions['mdash'],), md)
md.inlinePatterns.add('smarty-em-dashes', emDashesPattern, '>entity') enDashesPattern = SubstituteTextPattern(r'(?<!-)--(?!-)',
md.inlinePatterns.add('smarty-en-dashes', enDashesPattern, (self.substitutions['ndash'],), md)
self.inlinePatterns.add('smarty-em-dashes', emDashesPattern, '_begin')
self.inlinePatterns.add('smarty-en-dashes', enDashesPattern,
'>smarty-em-dashes') '>smarty-em-dashes')
def educateEllipses(self, md): def educateEllipses(self, md):
ellipsesPattern = SubstituteTextPattern(r'(?<!\.)\.{3}(?!\.)', ('&hellip;',), md) ellipsesPattern = SubstituteTextPattern(r'(?<!\.)\.{3}(?!\.)',
md.inlinePatterns.add('smarty-ellipses', ellipsesPattern, '>entity') (self.substitutions['ellipsis'],), md)
self.inlinePatterns.add('smarty-ellipses', ellipsesPattern, '_begin')
def educateAngledQuotes(self, md):
leftAngledQuotePattern = SubstituteTextPattern(r'\<\<',
(self.substitutions['left-angle-quote'],), md)
rightAngledQuotePattern = SubstituteTextPattern(r'\>\>',
(self.substitutions['right-angle-quote'],), md)
self.inlinePatterns.add('smarty-left-angle-quotes',
leftAngledQuotePattern, '_begin')
self.inlinePatterns.add('smarty-right-angle-quotes',
rightAngledQuotePattern, '>smarty-left-angle-quotes')
def educateQuotes(self, md): def educateQuotes(self, md):
configs = self.getConfigs()
lsquo = self.substitutions['left-single-quote']
rsquo = self.substitutions['right-single-quote']
ldquo = self.substitutions['left-double-quote']
rdquo = self.substitutions['right-double-quote']
patterns = ( patterns = (
(singleQuoteStartRe, (rsquo,)), (singleQuoteStartRe, (rsquo,)),
(doubleQuoteStartRe, (rdquo,)), (doubleQuoteStartRe, (rdquo,)),
@@ -179,13 +229,19 @@ class SmartyExtension(Extension):
def extendMarkdown(self, md, md_globals): def extendMarkdown(self, md, md_globals):
configs = self.getConfigs() configs = self.getConfigs()
if configs['smart_quotes']: self.inlinePatterns = OrderedDict()
self.educateQuotes(md)
if configs['smart_dashes']:
self.educateDashes(md)
if configs['smart_ellipses']: if configs['smart_ellipses']:
self.educateEllipses(md) self.educateEllipses(md)
if configs['smart_quotes']:
self.educateQuotes(md)
if configs['smart_angled_quotes']:
self.educateAngledQuotes(md)
if configs['smart_dashes']:
self.educateDashes(md)
inlineProcessor = InlineProcessor(md)
inlineProcessor.inlinePatterns = self.inlinePatterns
md.treeprocessors.add('smarty', inlineProcessor, '_end')
md.ESCAPED_CHARS.extend(['"', "'"]) md.ESCAPED_CHARS.extend(['"', "'"])
def makeExtension(configs=None): def makeExtension(*args, **kwargs):
return SmartyExtension(configs) return SmartyExtension(*args, **kwargs)

View File

@@ -4,14 +4,15 @@ Tables Extension for Python-Markdown
Added parsing of tables to Python-Markdown. Added parsing of tables to Python-Markdown.
A simple example: See <https://pythonhosted.org/Markdown/extensions/tables.html>
for documentation.
First Header | Second Header Original code Copyright 2009 [Waylan Limberg](http://achinghead.com)
------------- | -------------
Content Cell | Content Cell All changes Copyright 2008-2014 The Python Markdown Project
Content Cell | Content Cell
License: [BSD](http://www.opensource.org/licenses/bsd-license.php)
Copyright 2009 - [Waylan Limberg](http://achinghead.com)
""" """
from __future__ import absolute_import from __future__ import absolute_import
@@ -71,7 +72,7 @@ class TableProcessor(BlockProcessor):
c = etree.SubElement(tr, tag) c = etree.SubElement(tr, tag)
try: try:
c.text = cells[i].strip() c.text = cells[i].strip()
except IndexError: except IndexError: #pragma: no cover
c.text = "" c.text = ""
if a: if a:
c.set('align', a) c.set('align', a)
@@ -96,5 +97,6 @@ class TableExtension(Extension):
'<hashheader') '<hashheader')
def makeExtension(configs={}): def makeExtension(*args, **kwargs):
return TableExtension(configs=configs) return TableExtension(*args, **kwargs)

View File

@@ -1,11 +1,15 @@
""" """
Table of Contents Extension for Python-Markdown Table of Contents Extension for Python-Markdown
* * * ===============================================
(c) 2008 [Jack Miller](http://codezen.org) See <https://pythonhosted.org/Markdown/extensions/toc.html>
for documentation.
Dependencies: Oringinal code Copyright 2008 [Jack Miller](http://codezen.org)
* [Markdown 2.1+](http://packages.python.org/Markdown/)
All changes Copyright 2008-2014 The Python Markdown Project
License: [BSD](http://www.opensource.org/licenses/bsd-license.php)
""" """
@@ -23,60 +27,59 @@ def order_toc_list(toc_list):
[{'level': 1}, {'level': 2}] [{'level': 1}, {'level': 2}]
=> =>
[{'level': 1, 'children': [{'level': 2, 'children': []}]}] [{'level': 1, 'children': [{'level': 2, 'children': []}]}]
A wrong list is also converted: A wrong list is also converted:
[{'level': 2}, {'level': 1}] [{'level': 2}, {'level': 1}]
=> =>
[{'level': 2, 'children': []}, {'level': 1, 'children': []}] [{'level': 2, 'children': []}, {'level': 1, 'children': []}]
""" """
def build_correct(remaining_list, prev_elements=[{'level': 1000}]): ordered_list = []
if len(toc_list):
if not remaining_list: # Initialize everything by processing the first entry
return [], [] last = toc_list.pop(0)
last['children'] = []
current = remaining_list.pop(0) levels = [last['level']]
if not 'children' in current.keys(): ordered_list.append(last)
current['children'] = [] parents = []
if not prev_elements: # Walk the rest nesting the entries properly
# This happens for instance with [8, 1, 1], ie. when some while toc_list:
# header level is outside a scope. We treat it as a t = toc_list.pop(0)
# top-level current_level = t['level']
next_elements, children = build_correct(remaining_list, [current]) t['children'] = []
current['children'].append(children)
return [current] + next_elements, [] # Reduce depth if current level < last item's level
if current_level < levels[-1]:
prev_element = prev_elements.pop() # Pop last level since we know we are less than it
children = [] levels.pop()
next_elements = []
# Is current part of the child list or next list? # Pop parents and levels we are less than or equal to
if current['level'] > prev_element['level']: to_pop = 0
#print "%d is a child of %d" % (current['level'], prev_element['level']) for p in reversed(parents):
prev_elements.append(prev_element) if current_level <= p['level']:
prev_elements.append(current) to_pop += 1
prev_element['children'].append(current) else:
next_elements2, children2 = build_correct(remaining_list, prev_elements) break
children += children2 if to_pop:
next_elements += next_elements2 levels = levels[:-to_pop]
else: parents = parents[:-to_pop]
#print "%d is ancestor of %d" % (current['level'], prev_element['level'])
if not prev_elements: # Note current level as last
#print "No previous elements, so appending to the next set" levels.append(current_level)
next_elements.append(current)
prev_elements = [current] # Level is the same, so append to the current parent (if available)
next_elements2, children2 = build_correct(remaining_list, prev_elements) if current_level == levels[-1]:
current['children'].extend(children2) (parents[-1]['children'] if parents else ordered_list).append(t)
# Current level is > last item's level,
# So make last item a parent and append current as child
else: else:
#print "Previous elements, comparing to those first" last['children'].append(t)
remaining_list.insert(0, current) parents.append(last)
next_elements2, children2 = build_correct(remaining_list, prev_elements) levels.append(current_level)
children.extend(children2) last = t
next_elements += next_elements2
return next_elements, children
ordered_list, __ = build_correct(toc_list)
return ordered_list return ordered_list
@@ -204,26 +207,26 @@ class TocExtension(Extension):
TreeProcessorClass = TocTreeprocessor TreeProcessorClass = TocTreeprocessor
def __init__(self, configs=[]): def __init__(self, *args, **kwargs):
self.config = { "marker" : ["[TOC]", self.config = {
"Text to find and replace with Table of Contents -" "marker" : ["[TOC]",
"Defaults to \"[TOC]\""], "Text to find and replace with Table of Contents - "
"slugify" : [slugify, "Defaults to \"[TOC]\""],
"Function to generate anchors based on header text-" "slugify" : [slugify,
"Defaults to the headerid ext's slugify function."], "Function to generate anchors based on header text - "
"title" : [None, "Defaults to the headerid ext's slugify function."],
"Title to insert into TOC <div> - " "title" : ["",
"Defaults to None"], "Title to insert into TOC <div> - "
"anchorlink" : [0, "Defaults to an empty string"],
"1 if header should be a self link" "anchorlink" : [0,
"Defaults to 0"], "1 if header should be a self link - "
"permalink" : [0, "Defaults to 0"],
"1 or link text if a Sphinx-style permalink should be added", "permalink" : [0,
"Defaults to 0"] "1 or link text if a Sphinx-style permalink should be added - "
} "Defaults to 0"]
}
for key, value in configs: super(TocExtension, self).__init__(*args, **kwargs)
self.setConfig(key, value)
def extendMarkdown(self, md, md_globals): def extendMarkdown(self, md, md_globals):
tocext = self.TreeProcessorClass(md) tocext = self.TreeProcessorClass(md)
@@ -236,5 +239,5 @@ class TocExtension(Extension):
md.treeprocessors.add("toc", tocext, "_end") md.treeprocessors.add("toc", tocext, "_end")
def makeExtension(configs={}): def makeExtension(*args, **kwargs):
return TocExtension(configs=configs) return TocExtension(*args, **kwargs)

View File

@@ -2,78 +2,17 @@
WikiLinks Extension for Python-Markdown WikiLinks Extension for Python-Markdown
====================================== ======================================
Converts [[WikiLinks]] to relative links. Requires Python-Markdown 2.0+ Converts [[WikiLinks]] to relative links.
Basic usage: See <https://pythonhosted.org/Markdown/extensions/wikilinks.html>
for documentation.
>>> import markdown Original code Copyright [Waylan Limberg](http://achinghead.com/).
>>> text = "Some text with a [[WikiLink]]."
>>> html = markdown.markdown(text, ['wikilinks'])
>>> print html
<p>Some text with a <a class="wikilink" href="/WikiLink/">WikiLink</a>.</p>
Whitespace behavior: All changes Copyright The Python Markdown Project
>>> print markdown.markdown('[[ foo bar_baz ]]', ['wikilinks'])
<p><a class="wikilink" href="/foo_bar_baz/">foo bar_baz</a></p>
>>> print markdown.markdown('foo [[ ]] bar', ['wikilinks'])
<p>foo bar</p>
To define custom settings the simple way:
>>> print markdown.markdown(text,
... ['wikilinks(base_url=/wiki/,end_url=.html,html_class=foo)']
... )
<p>Some text with a <a class="foo" href="/wiki/WikiLink.html">WikiLink</a>.</p>
Custom settings the complex way:
>>> md = markdown.Markdown(
... extensions = ['wikilinks'],
... extension_configs = {'wikilinks': [
... ('base_url', 'http://example.com/'),
... ('end_url', '.html'),
... ('html_class', '') ]},
... safe_mode = True)
>>> print md.convert(text)
<p>Some text with a <a href="http://example.com/WikiLink.html">WikiLink</a>.</p>
Use MetaData with mdx_meta.py (Note the blank html_class in MetaData):
>>> text = """wiki_base_url: http://example.com/
... wiki_end_url: .html
... wiki_html_class:
...
... Some text with a [[WikiLink]]."""
>>> md = markdown.Markdown(extensions=['meta', 'wikilinks'])
>>> print md.convert(text)
<p>Some text with a <a href="http://example.com/WikiLink.html">WikiLink</a>.</p>
MetaData should not carry over to next document:
>>> print md.convert("No [[MetaData]] here.")
<p>No <a class="wikilink" href="/MetaData/">MetaData</a> here.</p>
Define a custom URL builder:
>>> def my_url_builder(label, base, end):
... return '/bar/'
>>> md = markdown.Markdown(extensions=['wikilinks'],
... extension_configs={'wikilinks' : [('build_url', my_url_builder)]})
>>> print md.convert('[[foo]]')
<p><a class="wikilink" href="/bar/">foo</a></p>
From the command line:
python markdown.py -x wikilinks(base_url=http://example.com/,end_url=.html,html_class=foo) src.txt
By [Waylan Limberg](http://achinghead.com/).
License: [BSD](http://www.opensource.org/licenses/bsd-license.php) License: [BSD](http://www.opensource.org/licenses/bsd-license.php)
Dependencies:
* [Python 2.3+](http://python.org)
* [Markdown 2.0+](http://packages.python.org/Markdown/)
''' '''
from __future__ import absolute_import from __future__ import absolute_import
@@ -90,19 +29,17 @@ def build_url(label, base, end):
class WikiLinkExtension(Extension): class WikiLinkExtension(Extension):
def __init__(self, configs):
# set extension defaults def __init__ (self, *args, **kwargs):
self.config = { self.config = {
'base_url' : ['/', 'String to append to beginning or URL.'], 'base_url' : ['/', 'String to append to beginning or URL.'],
'end_url' : ['/', 'String to append to end of URL.'], 'end_url' : ['/', 'String to append to end of URL.'],
'html_class' : ['wikilink', 'CSS hook. Leave blank for none.'], 'html_class' : ['wikilink', 'CSS hook. Leave blank for none.'],
'build_url' : [build_url, 'Callable formats URL from label.'], 'build_url' : [build_url, 'Callable formats URL from label.'],
} }
configs = dict(configs) or {}
# Override defaults with user settings
for key, value in configs.items():
self.setConfig(key, value)
super(WikiLinkExtension, self).__init__(*args, **kwargs)
def extendMarkdown(self, md, md_globals): def extendMarkdown(self, md, md_globals):
self.md = md self.md = md
@@ -147,5 +84,5 @@ class WikiLinks(Pattern):
return base_url, end_url, html_class return base_url, end_url, html_class
def makeExtension(configs=None) : def makeExtension(*args, **kwargs) :
return WikiLinkExtension(configs=configs) return WikiLinkExtension(*args, **kwargs)

View File

@@ -46,13 +46,13 @@ from __future__ import unicode_literals
from . import util from . import util
from . import odict from . import odict
import re import re
try: try: #pragma: no cover
from urllib.parse import urlparse, urlunparse from urllib.parse import urlparse, urlunparse
except ImportError: except ImportError: #pragma: no cover
from urlparse import urlparse, urlunparse from urlparse import urlparse, urlunparse
try: try: #pragma: no cover
from html import entities from html import entities
except ImportError: except ImportError: #pragma: no cover
import htmlentitydefs as entities import htmlentitydefs as entities
@@ -75,7 +75,8 @@ def build_inlinepatterns(md_instance, **kwargs):
inlinePatterns["html"] = HtmlPattern(HTML_RE, md_instance) inlinePatterns["html"] = HtmlPattern(HTML_RE, md_instance)
inlinePatterns["entity"] = HtmlPattern(ENTITY_RE, md_instance) inlinePatterns["entity"] = HtmlPattern(ENTITY_RE, md_instance)
inlinePatterns["not_strong"] = SimpleTextPattern(NOT_STRONG_RE) inlinePatterns["not_strong"] = SimpleTextPattern(NOT_STRONG_RE)
inlinePatterns["strong_em"] = DoubleTagPattern(STRONG_EM_RE, 'strong,em') 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["strong"] = SimpleTagPattern(STRONG_RE, 'strong')
inlinePatterns["emphasis"] = SimpleTagPattern(EMPHASIS_RE, 'em') inlinePatterns["emphasis"] = SimpleTagPattern(EMPHASIS_RE, 'em')
if md_instance.smart_emphasis: if md_instance.smart_emphasis:
@@ -100,7 +101,8 @@ BACKTICK_RE = r'(?<!\\)(`+)(.+?)(?<!`)\2(?!`)' # `e=f()` or ``e=f("`")``
ESCAPE_RE = r'\\(.)' # \< ESCAPE_RE = r'\\(.)' # \<
EMPHASIS_RE = r'(\*)([^\*]+)\2' # *emphasis* EMPHASIS_RE = r'(\*)([^\*]+)\2' # *emphasis*
STRONG_RE = r'(\*{2}|_{2})(.+?)\2' # **strong** STRONG_RE = r'(\*{2}|_{2})(.+?)\2' # **strong**
STRONG_EM_RE = r'(\*{3}|_{3})(.+?)\2' # ***strong*** EM_STRONG_RE = r'(\*|_)\2{2}(.+?)\2(.*?)\2{2}' # ***strongem*** or ***em*strong**
STRONG_EM_RE = r'(\*|_)\2{2}(.+?)\2{2}(.*?)\2' # ***strong**em*
SMART_EMPHASIS_RE = r'(?<!\w)(_)(?!_)(.+?)(?<!_)\2(?!\w)' # _smart_emphasis_ SMART_EMPHASIS_RE = r'(?<!\w)(_)(?!_)(.+?)(?<!_)\2(?!\w)' # _smart_emphasis_
EMPHASIS_2_RE = r'(_)(.+?)\2' # _emphasis_ EMPHASIS_2_RE = r'(_)(.+?)\2' # _emphasis_
LINK_RE = NOIMG + BRK + \ LINK_RE = NOIMG + BRK + \
@@ -156,7 +158,7 @@ class Pattern(object):
""" """
self.pattern = pattern self.pattern = pattern
self.compiled_re = re.compile("^(.*?)%s(.*?)$" % pattern, self.compiled_re = re.compile("^(.*?)%s(.*?)$" % pattern,
re.DOTALL | re.UNICODE) re.DOTALL | re.UNICODE)
# Api for Markdown to pass safe_mode into instance # Api for Markdown to pass safe_mode into instance
@@ -178,7 +180,7 @@ class Pattern(object):
* m: A re match object containing a match of the pattern. * m: A re match object containing a match of the pattern.
""" """
pass pass #pragma: no cover
def type(self): def type(self):
""" Return class name, to define pattern type """ """ Return class name, to define pattern type """
@@ -188,9 +190,9 @@ class Pattern(object):
""" Return unescaped text given text with an inline placeholder. """ """ Return unescaped text given text with an inline placeholder. """
try: try:
stash = self.markdown.treeprocessors['inline'].stashed_nodes stash = self.markdown.treeprocessors['inline'].stashed_nodes
except KeyError: except KeyError: #pragma: no cover
return text return text
def itertext(el): def itertext(el): #pragma: no cover
' Reimplement Element.itertext for older python versions ' ' Reimplement Element.itertext for older python versions '
tag = el.tag tag = el.tag
if not isinstance(tag, util.string_type) and tag is not None: if not isinstance(tag, util.string_type) and tag is not None:
@@ -210,17 +212,14 @@ class Pattern(object):
return value return value
else: else:
# An etree Element - return text content only # An etree Element - return text content only
return ''.join(itertext(value)) return ''.join(itertext(value))
return util.INLINE_PLACEHOLDER_RE.sub(get_stash, text) return util.INLINE_PLACEHOLDER_RE.sub(get_stash, text)
class SimpleTextPattern(Pattern): class SimpleTextPattern(Pattern):
""" Return a simple text of group(2) of a Pattern. """ """ Return a simple text of group(2) of a Pattern. """
def handleMatch(self, m): def handleMatch(self, m):
text = m.group(2) return m.group(2)
if text == util.INLINE_PLACEHOLDER_PREFIX:
return None
return text
class EscapePattern(Pattern): class EscapePattern(Pattern):
@@ -231,7 +230,7 @@ class EscapePattern(Pattern):
if char in self.markdown.ESCAPED_CHARS: if char in self.markdown.ESCAPED_CHARS:
return '%s%s%s' % (util.STX, ord(char), util.ETX) return '%s%s%s' % (util.STX, ord(char), util.ETX)
else: else:
return None return None
class SimpleTagPattern(Pattern): class SimpleTagPattern(Pattern):
@@ -279,6 +278,8 @@ class DoubleTagPattern(SimpleTagPattern):
el1 = util.etree.Element(tag1) el1 = util.etree.Element(tag1)
el2 = util.etree.SubElement(el1, tag2) el2 = util.etree.SubElement(el1, tag2)
el2.text = m.group(3) el2.text = m.group(3)
if len(m.groups())==5:
el2.tail = m.group(4)
return el1 return el1
@@ -293,7 +294,7 @@ class HtmlPattern(Pattern):
""" Return unescaped text given text with an inline placeholder. """ """ Return unescaped text given text with an inline placeholder. """
try: try:
stash = self.markdown.treeprocessors['inline'].stashed_nodes stash = self.markdown.treeprocessors['inline'].stashed_nodes
except KeyError: except KeyError: #pragma: no cover
return text return text
def get_stash(m): def get_stash(m):
id = m.group(1) id = m.group(1)
@@ -303,7 +304,7 @@ class HtmlPattern(Pattern):
return self.markdown.serializer(value) return self.markdown.serializer(value)
except: except:
return '\%s' % value return '\%s' % value
return util.INLINE_PLACEHOLDER_RE.sub(get_stash, text) return util.INLINE_PLACEHOLDER_RE.sub(get_stash, text)
@@ -323,7 +324,7 @@ class LinkPattern(Pattern):
el.set("href", "") el.set("href", "")
if title: if title:
title = dequote(self.unescape(title)) title = dequote(self.unescape(title))
el.set("title", title) el.set("title", title)
return el return el
@@ -347,20 +348,20 @@ class LinkPattern(Pattern):
if not self.markdown.safeMode: if not self.markdown.safeMode:
# Return immediately bipassing parsing. # Return immediately bipassing parsing.
return url return url
try: try:
scheme, netloc, path, params, query, fragment = url = urlparse(url) scheme, netloc, path, params, query, fragment = url = urlparse(url)
except ValueError: except ValueError: #pragma: no cover
# Bad url - so bad it couldn't be parsed. # Bad url - so bad it couldn't be parsed.
return '' return ''
locless_schemes = ['', 'mailto', 'news'] locless_schemes = ['', 'mailto', 'news']
allowed_schemes = locless_schemes + ['http', 'https', 'ftp', 'ftps'] allowed_schemes = locless_schemes + ['http', 'https', 'ftp', 'ftps']
if scheme not in allowed_schemes: if scheme not in allowed_schemes:
# Not a known (allowed) scheme. Not safe. # Not a known (allowed) scheme. Not safe.
return '' return ''
if netloc == '' and scheme not in locless_schemes: if netloc == '' and scheme not in locless_schemes: #pragma: no cover
# This should not happen. Treat as suspect. # This should not happen. Treat as suspect.
return '' return ''

View File

@@ -82,11 +82,11 @@ class OrderedDict(dict):
for key in self.keyOrder: for key in self.keyOrder:
yield self[key] yield self[key]
if util.PY3: if util.PY3: #pragma: no cover
items = _iteritems items = _iteritems
keys = _iterkeys keys = _iterkeys
values = _itervalues values = _itervalues
else: else: #pragma: no cover
iteritems = _iteritems iteritems = _iteritems
iterkeys = _iterkeys iterkeys = _iterkeys
itervalues = _itervalues itervalues = _itervalues

View File

@@ -42,7 +42,7 @@ class Postprocessor(util.Processor):
(possibly modified) string. (possibly modified) string.
""" """
pass pass #pragma: no cover
class RawHtmlPostprocessor(Postprocessor): class RawHtmlPostprocessor(Postprocessor):

View File

@@ -41,7 +41,7 @@ class Preprocessor(util.Processor):
the (possibly modified) list of lines. the (possibly modified) list of lines.
""" """
pass pass #pragma: no cover
class NormalizeWhitespace(Preprocessor): class NormalizeWhitespace(Preprocessor):
@@ -174,9 +174,10 @@ class HtmlBlockPreprocessor(Preprocessor):
else: # raw html else: # raw html
if len(items) - right_listindex <= 1: # last element if len(items) - right_listindex <= 1: # last element
right_listindex -= 1 right_listindex -= 1
offset = 1 if i == right_listindex else 0
placeholder = self.markdown.htmlStash.store('\n\n'.join( placeholder = self.markdown.htmlStash.store('\n\n'.join(
items[i:right_listindex + 1])) items[i:right_listindex + offset]))
del items[i:right_listindex + 1] del items[i:right_listindex + offset]
items.insert(i, placeholder) items.insert(i, placeholder)
return items return items

View File

@@ -42,9 +42,9 @@ from __future__ import unicode_literals
from . import util from . import util
ElementTree = util.etree.ElementTree ElementTree = util.etree.ElementTree
QName = util.etree.QName QName = util.etree.QName
if hasattr(util.etree, 'test_comment'): if hasattr(util.etree, 'test_comment'): #pragma: no cover
Comment = util.etree.test_comment Comment = util.etree.test_comment
else: else: #pragma: no cover
Comment = util.etree.Comment Comment = util.etree.Comment
PI = util.etree.PI PI = util.etree.PI
ProcessingInstruction = util.etree.ProcessingInstruction ProcessingInstruction = util.etree.ProcessingInstruction
@@ -56,7 +56,7 @@ HTML_EMPTY = ("area", "base", "basefont", "br", "col", "frame", "hr",
try: try:
HTML_EMPTY = set(HTML_EMPTY) HTML_EMPTY = set(HTML_EMPTY)
except NameError: except NameError: #pragma: no cover
pass pass
_namespace_map = { _namespace_map = {
@@ -73,7 +73,7 @@ _namespace_map = {
} }
def _raise_serialization_error(text): def _raise_serialization_error(text): #pragma: no cover
raise TypeError( raise TypeError(
"cannot serialize %r (type %s)" % (text, type(text).__name__) "cannot serialize %r (type %s)" % (text, type(text).__name__)
) )
@@ -81,7 +81,7 @@ def _raise_serialization_error(text):
def _encode(text, encoding): def _encode(text, encoding):
try: try:
return text.encode(encoding, "xmlcharrefreplace") return text.encode(encoding, "xmlcharrefreplace")
except (TypeError, AttributeError): except (TypeError, AttributeError): #pragma: no cover
_raise_serialization_error(text) _raise_serialization_error(text)
def _escape_cdata(text): def _escape_cdata(text):
@@ -97,7 +97,7 @@ def _escape_cdata(text):
if ">" in text: if ">" in text:
text = text.replace(">", "&gt;") text = text.replace(">", "&gt;")
return text return text
except (TypeError, AttributeError): except (TypeError, AttributeError): #pragma: no cover
_raise_serialization_error(text) _raise_serialization_error(text)
@@ -115,7 +115,7 @@ def _escape_attrib(text):
if "\n" in text: if "\n" in text:
text = text.replace("\n", "&#10;") text = text.replace("\n", "&#10;")
return text return text
except (TypeError, AttributeError): except (TypeError, AttributeError): #pragma: no cover
_raise_serialization_error(text) _raise_serialization_error(text)
def _escape_attrib_html(text): def _escape_attrib_html(text):
@@ -130,7 +130,7 @@ def _escape_attrib_html(text):
if "\"" in text: if "\"" in text:
text = text.replace("\"", "&quot;") text = text.replace("\"", "&quot;")
return text return text
except (TypeError, AttributeError): except (TypeError, AttributeError): #pragma: no cover
_raise_serialization_error(text) _raise_serialization_error(text)
@@ -240,7 +240,7 @@ def _namespaces(elem, default_namespace=None):
"default_namespace option" "default_namespace option"
) )
qnames[qname] = qname qnames[qname] = qname
except TypeError: except TypeError: #pragma: no cover
_raise_serialization_error(qname) _raise_serialization_error(qname)
# populate qname and namespaces table # populate qname and namespaces table

View File

@@ -34,11 +34,11 @@ class Treeprocessor(util.Processor):
def run(self, root): def run(self, root):
""" """
Subclasses of Treeprocessor should implement a `run` method, which Subclasses of Treeprocessor should implement a `run` method, which
takes a root ElementTree. This method can return another ElementTree takes a root ElementTree. This method can return another ElementTree
object, and the existing root ElementTree will be replaced, or it can object, and the existing root ElementTree will be replaced, or it can
modify the current tree and return None. modify the current tree and return None.
""" """
pass pass #pragma: no cover
class InlineProcessor(Treeprocessor): class InlineProcessor(Treeprocessor):
@@ -53,6 +53,7 @@ class InlineProcessor(Treeprocessor):
+ len(self.__placeholder_suffix) + len(self.__placeholder_suffix)
self.__placeholder_re = util.INLINE_PLACEHOLDER_RE self.__placeholder_re = util.INLINE_PLACEHOLDER_RE
self.markdown = md self.markdown = md
self.inlinePatterns = md.inlinePatterns
def __makePlaceholder(self, type): def __makePlaceholder(self, type):
""" Generate a placeholder """ """ Generate a placeholder """
@@ -70,7 +71,7 @@ class InlineProcessor(Treeprocessor):
* index: index, from which we start search * index: index, from which we start search
Returns: placeholder id and string index, after the found placeholder. Returns: placeholder id and string index, after the found placeholder.
""" """
m = self.__placeholder_re.search(data, index) m = self.__placeholder_re.search(data, index)
if m: if m:
@@ -99,9 +100,9 @@ class InlineProcessor(Treeprocessor):
""" """
if not isinstance(data, util.AtomicString): if not isinstance(data, util.AtomicString):
startIndex = 0 startIndex = 0
while patternIndex < len(self.markdown.inlinePatterns): while patternIndex < len(self.inlinePatterns):
data, matched, startIndex = self.__applyPattern( data, matched, startIndex = self.__applyPattern(
self.markdown.inlinePatterns.value_for_index(patternIndex), self.inlinePatterns.value_for_index(patternIndex),
data, patternIndex, startIndex) data, patternIndex, startIndex)
if not matched: if not matched:
patternIndex += 1 patternIndex += 1
@@ -128,11 +129,10 @@ class InlineProcessor(Treeprocessor):
text = subnode.tail text = subnode.tail
subnode.tail = None subnode.tail = None
childResult = self.__processPlaceholders(text, subnode) childResult = self.__processPlaceholders(text, subnode, isText)
if not isText and node is not subnode: if not isText and node is not subnode:
pos = list(node).index(subnode) pos = list(node).index(subnode) + 1
node.remove(subnode)
else: else:
pos = 0 pos = 0
@@ -140,7 +140,7 @@ class InlineProcessor(Treeprocessor):
for newChild in childResult: for newChild in childResult:
node.insert(pos, newChild) node.insert(pos, newChild)
def __processPlaceholders(self, data, parent): def __processPlaceholders(self, data, parent, isText=True):
""" """
Process string with placeholders and generate ElementTree tree. Process string with placeholders and generate ElementTree tree.
@@ -150,7 +150,7 @@ class InlineProcessor(Treeprocessor):
* parent: Element, which contains processing inline data * parent: Element, which contains processing inline data
Returns: list with ElementTree elements with applied inline patterns. Returns: list with ElementTree elements with applied inline patterns.
""" """
def linkText(text): def linkText(text):
if text: if text:
@@ -159,6 +159,11 @@ class InlineProcessor(Treeprocessor):
result[-1].tail += text result[-1].tail += text
else: else:
result[-1].tail = text result[-1].tail = text
elif not isText:
if parent.tail:
parent.tail += text
else:
parent.tail = text
else: else:
if parent.text: if parent.text:
parent.text += text parent.text += text
@@ -182,7 +187,7 @@ class InlineProcessor(Treeprocessor):
for child in [node] + list(node): for child in [node] + list(node):
if child.tail: if child.tail:
if child.tail.strip(): if child.tail.strip():
self.__processElementText(node, child,False) self.__processElementText(node, child, False)
if child.text: if child.text:
if child.text.strip(): if child.text.strip():
self.__processElementText(child, child) self.__processElementText(child, child)
@@ -239,7 +244,7 @@ class InlineProcessor(Treeprocessor):
# We need to process current node too # We need to process current node too
for child in [node] + list(node): for child in [node] + list(node):
if not isString(node): if not isString(node):
if child.text: if child.text:
child.text = self.__handleInline(child.text, child.text = self.__handleInline(child.text,
patternIndex + 1) patternIndex + 1)
if child.tail: if child.tail:
@@ -287,11 +292,10 @@ class InlineProcessor(Treeprocessor):
if child.tail: if child.tail:
tail = self.__handleInline(child.tail) tail = self.__handleInline(child.tail)
dumby = util.etree.Element('d') dumby = util.etree.Element('d')
tailResult = self.__processPlaceholders(tail, dumby) child.tail = None
if dumby.text: tailResult = self.__processPlaceholders(tail, dumby, False)
child.tail = dumby.text if dumby.tail:
else: child.tail = dumby.tail
child.tail = None
pos = list(currElement).index(child) + 1 pos = list(currElement).index(child) + 1
tailResult.reverse() tailResult.reverse()
for newChild in tailResult: for newChild in tailResult:
@@ -303,7 +307,7 @@ class InlineProcessor(Treeprocessor):
if self.markdown.enable_attributes: if self.markdown.enable_attributes:
if element.text and isString(element.text): if element.text and isString(element.text):
element.text = \ element.text = \
inlinepatterns.handleAttributes(element.text, inlinepatterns.handleAttributes(element.text,
element) element)
i = 0 i = 0
for newChild in lst: for newChild in lst:
@@ -357,4 +361,4 @@ class PrettifyTreeprocessor(Treeprocessor):
pres = root.getiterator('pre') pres = root.getiterator('pre')
for pre in pres: for pre in pres:
if len(pre) and pre[0].tag == 'code': if len(pre) and pre[0].tag == 'code':
pre[0].text = pre[0].text.rstrip() + '\n' pre[0].text = util.AtomicString(pre[0].text.rstrip() + '\n')

View File

@@ -10,11 +10,11 @@ Python 3 Stuff
""" """
PY3 = sys.version_info[0] == 3 PY3 = sys.version_info[0] == 3
if PY3: if PY3: #pragma: no cover
string_type = str string_type = str
text_type = str text_type = str
int2str = chr int2str = chr
else: else: #pragma: no cover
string_type = basestring string_type = basestring
text_type = unicode text_type = unicode
int2str = unichr int2str = unichr
@@ -58,14 +58,15 @@ RTL_BIDI_RANGES = ( ('\u0590', '\u07FF'),
# Extensions should use "markdown.util.etree" instead of "etree" (or do `from # Extensions should use "markdown.util.etree" instead of "etree" (or do `from
# markdown.util import etree`). Do not import it by yourself. # markdown.util import etree`). Do not import it by yourself.
try: # Is the C implementation of ElementTree available? try: #pragma: no cover
# Is the C implementation of ElementTree available?
import xml.etree.cElementTree as etree import xml.etree.cElementTree as etree
from xml.etree.ElementTree import Comment from xml.etree.ElementTree import Comment
# Serializers (including ours) test with non-c Comment # Serializers (including ours) test with non-c Comment
etree.test_comment = Comment etree.test_comment = Comment
if etree.VERSION < "1.0.5": if etree.VERSION < "1.0.5":
raise RuntimeError("cElementTree version 1.0.5 or higher is required.") raise RuntimeError("cElementTree version 1.0.5 or higher is required.")
except (ImportError, RuntimeError): except (ImportError, RuntimeError): #pragma: no cover
# Use the Python implementation of ElementTree? # Use the Python implementation of ElementTree?
import xml.etree.ElementTree as etree import xml.etree.ElementTree as etree
if etree.VERSION < "1.1": if etree.VERSION < "1.1":
@@ -85,15 +86,20 @@ def isBlockLevel(tag):
# Some ElementTree tags are not strings, so return False. # Some ElementTree tags are not strings, so return False.
return False return False
def parseBoolValue(value, fail_on_errors=True): def parseBoolValue(value, fail_on_errors=True, preserve_none=False):
"""Parses a string representing bool value. If parsing was successful, """Parses a string representing bool value. If parsing was successful,
returns True or False. If parsing was not successful, raises returns True or False. If preserve_none=True, returns True, False,
ValueError, or, if fail_on_errors=False, returns None.""" or None. If parsing was not successful, raises ValueError, or, if
fail_on_errors=False, returns None."""
if not isinstance(value, string_type): if not isinstance(value, string_type):
if preserve_none and value is None:
return value
return bool(value) return bool(value)
elif preserve_none and value.lower() == 'none':
return None
elif value.lower() in ('true', 'yes', 'y', 'on', '1'): elif value.lower() in ('true', 'yes', 'y', 'on', '1'):
return True return True
elif value.lower() in ('false', 'no', 'n', 'off', '0'): elif value.lower() in ('false', 'no', 'n', 'off', '0', 'none'):
return False return False
elif fail_on_errors: elif fail_on_errors:
raise ValueError('Cannot parse bool value: %r' % value) raise ValueError('Cannot parse bool value: %r' % value)