aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--markdown/blockparser.py5
-rw-r--r--markdown/blockprocessors.py20
-rw-r--r--markdown/core.py6
-rw-r--r--markdown/extensions/abbr.py7
-rw-r--r--markdown/extensions/admonition.py4
-rw-r--r--markdown/extensions/attr_list.py4
-rw-r--r--markdown/extensions/codehilite.py2
-rw-r--r--markdown/extensions/def_list.py8
-rw-r--r--markdown/extensions/extra.py6
-rw-r--r--markdown/extensions/fenced_code.py4
-rw-r--r--markdown/extensions/footnotes.py32
-rw-r--r--markdown/extensions/legacy_attrs.py3
-rw-r--r--markdown/extensions/meta.py4
-rw-r--r--markdown/extensions/nl2br.py2
-rw-r--r--markdown/extensions/sane_lists.py4
-rw-r--r--markdown/extensions/smart_strong.py8
-rw-r--r--markdown/extensions/smarty.py33
-rw-r--r--markdown/extensions/tables.py4
-rw-r--r--markdown/extensions/toc.py11
-rw-r--r--markdown/extensions/wikilinks.py2
-rw-r--r--markdown/inlinepatterns.py45
-rw-r--r--markdown/odict.py191
-rw-r--r--markdown/postprocessors.py9
-rw-r--r--markdown/preprocessors.py9
-rw-r--r--markdown/treeprocessors.py11
-rw-r--r--markdown/util.py215
-rw-r--r--tests/test_apis.py359
27 files changed, 528 insertions, 480 deletions
diff --git a/markdown/blockparser.py b/markdown/blockparser.py
index 32d3254..5e9d567 100644
--- a/markdown/blockparser.py
+++ b/markdown/blockparser.py
@@ -1,7 +1,6 @@
from __future__ import unicode_literals
from __future__ import absolute_import
from . import util
-from . import odict
class State(list):
@@ -46,7 +45,7 @@ class BlockParser:
"""
def __init__(self, markdown):
- self.blockprocessors = odict.OrderedDict()
+ self.blockprocessors = util.Registry()
self.state = State()
self.markdown = markdown
@@ -93,7 +92,7 @@ class BlockParser:
"""
while blocks:
- for processor in self.blockprocessors.values():
+ for processor in self.blockprocessors:
if processor.test(parent, blocks[0]):
if processor.run(parent, blocks) is not False:
# run returns True or None
diff --git a/markdown/blockprocessors.py b/markdown/blockprocessors.py
index db9add5..50c4591 100644
--- a/markdown/blockprocessors.py
+++ b/markdown/blockprocessors.py
@@ -25,16 +25,16 @@ logger = logging.getLogger('MARKDOWN')
def build_block_parser(md_instance, **kwargs):
""" Build the default block parser used by Markdown. """
parser = BlockParser(md_instance)
- parser.blockprocessors['empty'] = EmptyBlockProcessor(parser)
- parser.blockprocessors['indent'] = ListIndentProcessor(parser)
- parser.blockprocessors['code'] = CodeBlockProcessor(parser)
- parser.blockprocessors['hashheader'] = HashHeaderProcessor(parser)
- parser.blockprocessors['setextheader'] = SetextHeaderProcessor(parser)
- parser.blockprocessors['hr'] = HRProcessor(parser)
- parser.blockprocessors['olist'] = OListProcessor(parser)
- parser.blockprocessors['ulist'] = UListProcessor(parser)
- parser.blockprocessors['quote'] = BlockQuoteProcessor(parser)
- parser.blockprocessors['paragraph'] = ParagraphProcessor(parser)
+ parser.blockprocessors.register(EmptyBlockProcessor(parser), 'empty', 100)
+ parser.blockprocessors.register(ListIndentProcessor(parser), 'indent', 90)
+ parser.blockprocessors.register(CodeBlockProcessor(parser), 'code', 80)
+ parser.blockprocessors.register(HashHeaderProcessor(parser), 'hashheader', 70)
+ parser.blockprocessors.register(SetextHeaderProcessor(parser), 'setextheader', 60)
+ parser.blockprocessors.register(HRProcessor(parser), 'hr', 50)
+ parser.blockprocessors.register(OListProcessor(parser), 'olist', 40)
+ parser.blockprocessors.register(UListProcessor(parser), 'ulist', 30)
+ parser.blockprocessors.register(BlockQuoteProcessor(parser), 'quote', 20)
+ parser.blockprocessors.register(ParagraphProcessor(parser), 'paragraph', 10)
return parser
diff --git a/markdown/core.py b/markdown/core.py
index 4b8d1a6..06bf262 100644
--- a/markdown/core.py
+++ b/markdown/core.py
@@ -230,14 +230,14 @@ class Markdown(object):
# Split into lines and run the line preprocessors.
self.lines = source.split("\n")
- for prep in self.preprocessors.values():
+ for prep in self.preprocessors:
self.lines = prep.run(self.lines)
# Parse the high-level elements.
root = self.parser.parseDocument(self.lines).getroot()
# Run the tree-processors
- for treeprocessor in self.treeprocessors.values():
+ for treeprocessor in self.treeprocessors:
newRoot = treeprocessor.run(root)
if newRoot is not None:
root = newRoot
@@ -260,7 +260,7 @@ class Markdown(object):
'tags. Document=%r' % output.strip())
# Run the text post-processors
- for pp in self.postprocessors.values():
+ for pp in self.postprocessors:
output = pp.run(output)
return output.strip()
diff --git a/markdown/extensions/abbr.py b/markdown/extensions/abbr.py
index 5e8845b..a3d456f 100644
--- a/markdown/extensions/abbr.py
+++ b/markdown/extensions/abbr.py
@@ -33,7 +33,7 @@ class AbbrExtension(Extension):
def extendMarkdown(self, md, md_globals):
""" Insert AbbrPreprocessor before ReferencePreprocessor. """
- md.preprocessors.add('abbr', AbbrPreprocessor(md), '<reference')
+ md.preprocessors.register(AbbrPreprocessor(md), 'abbr', 12)
class AbbrPreprocessor(Preprocessor):
@@ -51,8 +51,9 @@ class AbbrPreprocessor(Preprocessor):
if m:
abbr = m.group('abbr').strip()
title = m.group('title').strip()
- self.markdown.inlinePatterns['abbr-%s' % abbr] = \
- AbbrInlineProcessor(self._generate_pattern(abbr), title)
+ self.markdown.inlinePatterns.register(
+ AbbrInlineProcessor(self._generate_pattern(abbr), title), 'abbr-%s' % abbr, 2
+ )
# Preserve the line to prevent raw HTML indexing issue.
# https://github.com/Python-Markdown/markdown/issues/584
new_text.append('')
diff --git a/markdown/extensions/admonition.py b/markdown/extensions/admonition.py
index b001957..cf1b506 100644
--- a/markdown/extensions/admonition.py
+++ b/markdown/extensions/admonition.py
@@ -32,9 +32,7 @@ class AdmonitionExtension(Extension):
""" Add Admonition to Markdown instance. """
md.registerExtension(self)
- md.parser.blockprocessors.add('admonition',
- AdmonitionProcessor(md.parser),
- '_begin')
+ md.parser.blockprocessors.register(AdmonitionProcessor(md.parser), 'admonition', 105)
class AdmonitionProcessor(BlockProcessor):
diff --git a/markdown/extensions/attr_list.py b/markdown/extensions/attr_list.py
index 76b7d99..a63a5ea 100644
--- a/markdown/extensions/attr_list.py
+++ b/markdown/extensions/attr_list.py
@@ -163,9 +163,7 @@ class AttrListTreeprocessor(Treeprocessor):
class AttrListExtension(Extension):
def extendMarkdown(self, md, md_globals):
- md.treeprocessors.add(
- 'attr_list', AttrListTreeprocessor(md), '>prettify'
- )
+ md.treeprocessors.register(AttrListTreeprocessor(md), 'attr_list', 8)
def makeExtension(**kwargs): # pragma: no cover
diff --git a/markdown/extensions/codehilite.py b/markdown/extensions/codehilite.py
index fed4d64..8b9cd8f 100644
--- a/markdown/extensions/codehilite.py
+++ b/markdown/extensions/codehilite.py
@@ -255,7 +255,7 @@ class CodeHiliteExtension(Extension):
""" Add HilitePostprocessor to Markdown instance. """
hiliter = HiliteTreeprocessor(md)
hiliter.config = self.getConfigs()
- md.treeprocessors.add("hilite", hiliter, "<inline")
+ md.treeprocessors.register(hiliter, 'hilite', 30)
md.registerExtension(self)
diff --git a/markdown/extensions/def_list.py b/markdown/extensions/def_list.py
index a79c1c1..9463d3a 100644
--- a/markdown/extensions/def_list.py
+++ b/markdown/extensions/def_list.py
@@ -103,12 +103,8 @@ class DefListExtension(Extension):
def extendMarkdown(self, md, md_globals):
""" Add an instance of DefListProcessor to BlockParser. """
- md.parser.blockprocessors.add('defindent',
- DefListIndentProcessor(md.parser),
- '>indent')
- md.parser.blockprocessors.add('deflist',
- DefListProcessor(md.parser),
- '>ulist')
+ md.parser.blockprocessors.register(DefListIndentProcessor(md.parser), 'defindent', 85)
+ md.parser.blockprocessors.register(DefListProcessor(md.parser), 'deflist', 25)
def makeExtension(**kwargs): # pragma: no cover
diff --git a/markdown/extensions/extra.py b/markdown/extensions/extra.py
index d1294e0..da4cb38 100644
--- a/markdown/extensions/extra.py
+++ b/markdown/extensions/extra.py
@@ -59,9 +59,9 @@ class ExtraExtension(Extension):
md.registerExtensions(extensions, self.config)
# Turn on processing of markdown text within raw html
md.preprocessors['html_block'].markdown_in_raw = True
- md.parser.blockprocessors.add('markdown_block',
- MarkdownInHtmlProcessor(md.parser),
- '_begin')
+ md.parser.blockprocessors.register(
+ MarkdownInHtmlProcessor(md.parser), 'markdown_block', 105
+ )
md.parser.blockprocessors.tag_counter = -1
md.parser.blockprocessors.contain_span_tags = re.compile(
r'^(p|h[1-6]|li|dd|dt|td|th|legend|address)$', re.IGNORECASE)
diff --git a/markdown/extensions/fenced_code.py b/markdown/extensions/fenced_code.py
index e1a616e..c38dabf 100644
--- a/markdown/extensions/fenced_code.py
+++ b/markdown/extensions/fenced_code.py
@@ -29,9 +29,7 @@ class FencedCodeExtension(Extension):
""" Add FencedBlockPreprocessor to the Markdown instance. """
md.registerExtension(self)
- md.preprocessors.add('fenced_code_block',
- FencedBlockPreprocessor(md),
- ">normalize_whitespace")
+ md.preprocessors.register(FencedBlockPreprocessor(md), 'fenced_code_block', 25)
class FencedBlockPreprocessor(Preprocessor):
diff --git a/markdown/extensions/footnotes.py b/markdown/extensions/footnotes.py
index a957278..2b9cc40 100644
--- a/markdown/extensions/footnotes.py
+++ b/markdown/extensions/footnotes.py
@@ -21,7 +21,7 @@ from ..inlinepatterns import InlineProcessor
from ..treeprocessors import Treeprocessor
from ..postprocessors import Postprocessor
from .. import util
-from ..odict import OrderedDict
+from collections import OrderedDict
import re
import copy
@@ -71,33 +71,24 @@ class FootnoteExtension(Extension):
self.parser = md.parser
self.md = md
# Insert a preprocessor before ReferencePreprocessor
- md.preprocessors.add(
- "footnote", FootnotePreprocessor(self), "<reference"
- )
+ md.preprocessors.register(FootnotePreprocessor(self), 'footnote', 15)
+
# Insert an inline pattern before ImageReferencePattern
FOOTNOTE_RE = r'\[\^([^\]]*)\]' # blah blah [^1] blah
- md.inlinePatterns.add(
- "footnote", FootnoteInlineProcessor(FOOTNOTE_RE, self), "<reference"
- )
+ md.inlinePatterns.register(FootnoteInlineProcessor(FOOTNOTE_RE, self), 'footnote', 175)
# Insert a tree-processor that would actually add the footnote div
# This must be before all other treeprocessors (i.e., inline and
# codehilite) so they can run on the the contents of the div.
- md.treeprocessors.add(
- "footnote", FootnoteTreeprocessor(self), "_begin"
- )
+ md.treeprocessors.register(FootnoteTreeprocessor(self), 'footnote', 50)
# Insert a tree-processor that will run after inline is done.
# In this tree-processor we want to check our duplicate footnote tracker
# And add additional backrefs to the footnote pointing back to the
# duplicated references.
- md.treeprocessors.add(
- "footnote-duplicate", FootnotePostTreeprocessor(self), '>inline'
- )
+ md.treeprocessors.register(FootnotePostTreeprocessor(self), 'footnote-duplicate', 15)
- # Insert a postprocessor after amp_substitute oricessor
- md.postprocessors.add(
- "footnote", FootnotePostprocessor(self), ">amp_substitute"
- )
+ # Insert a postprocessor after amp_substitute processor
+ md.postprocessors.register(FootnotePostprocessor(self), 'footnote', 25)
def reset(self):
""" Clear footnotes on reset, and prepare for distinct document. """
@@ -180,7 +171,7 @@ class FootnoteExtension(Extension):
ol = util.etree.SubElement(div, "ol")
surrogate_parent = util.etree.Element("div")
- for id in self.footnotes.keys():
+ for index, id in enumerate(self.footnotes.keys(), start=1):
li = util.etree.SubElement(ol, "li")
li.set("id", self.makeFootnoteId(id))
# Parse footnote with surrogate parent as li cannot be used.
@@ -197,8 +188,7 @@ class FootnoteExtension(Extension):
backlink.set("class", "footnote-backref")
backlink.set(
"title",
- self.getConfig("BACKLINK_TITLE") %
- (self.footnotes.index(id)+1)
+ self.getConfig("BACKLINK_TITLE") % (index)
)
backlink.text = FN_BACKLINK_TEXT
@@ -332,7 +322,7 @@ class FootnoteInlineProcessor(InlineProcessor):
if self.footnotes.md.output_format not in ['html5', 'xhtml5']:
a.set('rel', 'footnote') # invalid in HTML5
a.set('class', 'footnote-ref')
- a.text = util.text_type(self.footnotes.footnotes.index(id) + 1)
+ a.text = util.text_type(list(self.footnotes.footnotes.keys()).index(id) + 1)
return sup, m.start(0), m.end(0)
else:
return None, None, None
diff --git a/markdown/extensions/legacy_attrs.py b/markdown/extensions/legacy_attrs.py
index b28223f..740f9d6 100644
--- a/markdown/extensions/legacy_attrs.py
+++ b/markdown/extensions/legacy_attrs.py
@@ -41,8 +41,7 @@ class LegacyAttrs(Treeprocessor):
class LegacyAttrExtension(Extension):
def extendMarkdown(self, md, md_globals):
- la = LegacyAttrs(md)
- md.treeprocessors.add('legacyattrs', la, '>inline')
+ md.treeprocessors.register(LegacyAttrs(md), 'legacyattrs', 15)
def makeExtension(**kwargs): # pragma: no cover
diff --git a/markdown/extensions/meta.py b/markdown/extensions/meta.py
index 2c2c8e3..27adcb2 100644
--- a/markdown/extensions/meta.py
+++ b/markdown/extensions/meta.py
@@ -38,9 +38,7 @@ class MetaExtension (Extension):
""" Add MetaPreprocessor to Markdown instance. """
md.registerExtension(self)
self.md = md
- md.preprocessors.add("meta",
- MetaPreprocessor(md),
- ">normalize_whitespace")
+ md.preprocessors.register(MetaPreprocessor(md), 'meta', 27)
def reset(self):
self.md.Meta = {}
diff --git a/markdown/extensions/nl2br.py b/markdown/extensions/nl2br.py
index 5b9373f..d334b02 100644
--- a/markdown/extensions/nl2br.py
+++ b/markdown/extensions/nl2br.py
@@ -28,7 +28,7 @@ class Nl2BrExtension(Extension):
def extendMarkdown(self, md, md_globals):
br_tag = SubstituteTagInlineProcessor(BR_RE, 'br')
- md.inlinePatterns.add('nl', br_tag, '_end')
+ md.inlinePatterns.register(br_tag, 'nl', 5)
def makeExtension(**kwargs): # pragma: no cover
diff --git a/markdown/extensions/sane_lists.py b/markdown/extensions/sane_lists.py
index 89f929f..7fb4fd6 100644
--- a/markdown/extensions/sane_lists.py
+++ b/markdown/extensions/sane_lists.py
@@ -47,8 +47,8 @@ class SaneListExtension(Extension):
def extendMarkdown(self, md, md_globals):
""" Override existing Processors. """
- md.parser.blockprocessors['olist'] = SaneOListProcessor(md.parser)
- md.parser.blockprocessors['ulist'] = SaneUListProcessor(md.parser)
+ md.parser.blockprocessors.register(SaneOListProcessor(md.parser), 'olist', 40)
+ md.parser.blockprocessors.register(SaneUListProcessor(md.parser), 'ulist', 30)
def makeExtension(**kwargs): # pragma: no cover
diff --git a/markdown/extensions/smart_strong.py b/markdown/extensions/smart_strong.py
index f34531d..e7a15d9 100644
--- a/markdown/extensions/smart_strong.py
+++ b/markdown/extensions/smart_strong.py
@@ -29,12 +29,8 @@ class SmartEmphasisExtension(Extension):
def extendMarkdown(self, md, md_globals):
""" Modify inline patterns. """
- md.inlinePatterns['strong'] = SimpleTagInlineProcessor(STRONG_RE, 'strong')
- md.inlinePatterns.add(
- 'strong2',
- SimpleTagInlineProcessor(SMART_STRONG_RE, 'strong'),
- '>emphasis2'
- )
+ md.inlinePatterns.register(SimpleTagInlineProcessor(STRONG_RE, 'strong'), 'strong', 40)
+ md.inlinePatterns.register(SimpleTagInlineProcessor(SMART_STRONG_RE, 'strong'), 'strong2', 10)
def makeExtension(**kwargs): # pragma: no cover
diff --git a/markdown/extensions/smarty.py b/markdown/extensions/smarty.py
index 189651f..d25620b 100644
--- a/markdown/extensions/smarty.py
+++ b/markdown/extensions/smarty.py
@@ -84,8 +84,8 @@ smartypants.py license:
from __future__ import unicode_literals
from . import Extension
from ..inlinepatterns import HtmlInlineProcessor, HTML_RE
-from ..odict import OrderedDict
from ..treeprocessors import InlineProcessor
+from ..util import Registry
# Constants for quote education.
@@ -180,13 +180,12 @@ class SmartyExtension(Extension):
self.substitutions = dict(substitutions)
self.substitutions.update(self.getConfig('substitutions', default={}))
- def _addPatterns(self, md, patterns, serie):
+ def _addPatterns(self, md, patterns, serie, priority):
for ind, pattern in enumerate(patterns):
pattern += (md,)
pattern = SubstituteTextPattern(*pattern)
- after = ('>smarty-%s-%d' % (serie, ind - 1) if ind else '_begin')
name = 'smarty-%s-%d' % (serie, ind)
- self.inlinePatterns.add(name, pattern, after)
+ self.inlinePatterns.register(pattern, name, priority-ind)
def educateDashes(self, md):
emDashesPattern = SubstituteTextPattern(
@@ -195,16 +194,14 @@ class SmartyExtension(Extension):
enDashesPattern = SubstituteTextPattern(
r'(?<!-)--(?!-)', (self.substitutions['ndash'],), md
)
- self.inlinePatterns.add('smarty-em-dashes', emDashesPattern, '_begin')
- self.inlinePatterns.add(
- 'smarty-en-dashes', enDashesPattern, '>smarty-em-dashes'
- )
+ self.inlinePatterns.register(emDashesPattern, 'smarty-em-dashes', 50)
+ self.inlinePatterns.register(enDashesPattern, 'smarty-en-dashes', 45)
def educateEllipses(self, md):
ellipsesPattern = SubstituteTextPattern(
r'(?<!\.)\.{3}(?!\.)', (self.substitutions['ellipsis'],), md
)
- self.inlinePatterns.add('smarty-ellipses', ellipsesPattern, '_begin')
+ self.inlinePatterns.register(ellipsesPattern, 'smarty-ellipses', 10)
def educateAngledQuotes(self, md):
leftAngledQuotePattern = SubstituteTextPattern(
@@ -213,14 +210,8 @@ class SmartyExtension(Extension):
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'
- )
+ self.inlinePatterns.register(leftAngledQuotePattern, 'smarty-left-angle-quotes', 40)
+ self.inlinePatterns.register(rightAngledQuotePattern, 'smarty-right-angle-quotes', 35)
def educateQuotes(self, md):
lsquo = self.substitutions['left-single-quote']
@@ -242,11 +233,11 @@ class SmartyExtension(Extension):
(closingDoubleQuotesRegex2, (rdquo,)),
(remainingDoubleQuotesRegex, (ldquo,))
)
- self._addPatterns(md, patterns, 'quotes')
+ self._addPatterns(md, patterns, 'quotes', 30)
def extendMarkdown(self, md, md_globals):
configs = self.getConfigs()
- self.inlinePatterns = OrderedDict()
+ self.inlinePatterns = Registry()
if configs['smart_ellipses']:
self.educateEllipses(md)
if configs['smart_quotes']:
@@ -255,12 +246,12 @@ class SmartyExtension(Extension):
self.educateAngledQuotes(md)
# Override HTML_RE from inlinepatterns.py so that it does not
# process tags with duplicate closing quotes.
- md.inlinePatterns["html"] = HtmlInlineProcessor(HTML_STRICT_RE, md)
+ md.inlinePatterns.register(HtmlInlineProcessor(HTML_STRICT_RE, md), 'html', 90)
if configs['smart_dashes']:
self.educateDashes(md)
inlineProcessor = InlineProcessor(md)
inlineProcessor.inlinePatterns = self.inlinePatterns
- md.treeprocessors.add('smarty', inlineProcessor, '_end')
+ md.treeprocessors.register(inlineProcessor, 'smarty', 2)
md.ESCAPED_CHARS.extend(['"', "'"])
diff --git a/markdown/extensions/tables.py b/markdown/extensions/tables.py
index b8218b0..0f221a6 100644
--- a/markdown/extensions/tables.py
+++ b/markdown/extensions/tables.py
@@ -218,9 +218,7 @@ class TableExtension(Extension):
""" Add an instance of TableProcessor to BlockParser. """
if '|' not in md.ESCAPED_CHARS:
md.ESCAPED_CHARS.append('|')
- md.parser.blockprocessors.add('table',
- TableProcessor(md.parser),
- '<hashheader')
+ md.parser.blockprocessors.register(TableProcessor(md.parser), 'table', 75)
def makeExtension(**kwargs): # pragma: no cover
diff --git a/markdown/extensions/toc.py b/markdown/extensions/toc.py
index 5783f3d..f5f2b2e 100644
--- a/markdown/extensions/toc.py
+++ b/markdown/extensions/toc.py
@@ -218,9 +218,10 @@ class TocTreeprocessor(Treeprocessor):
return ul
build_etree_ul(toc_list, div)
- prettify = self.markdown.treeprocessors.get('prettify')
- if prettify:
- prettify.run(div)
+
+ if 'prettify' in self.markdown.treeprocessors:
+ self.markdown.treeprocessors['prettify'].run(div)
+
return div
def run(self, doc):
@@ -260,7 +261,7 @@ class TocTreeprocessor(Treeprocessor):
# serialize and attach to markdown instance.
toc = self.markdown.serializer(div)
- for pp in self.markdown.postprocessors.values():
+ for pp in self.markdown.postprocessors:
toc = pp.run(toc)
self.markdown.toc = toc
@@ -305,7 +306,7 @@ class TocExtension(Extension):
# by the header id extension) if both are used. Same goes for
# attr_list extension. This must come last because we don't want
# to redefine ids after toc is created. But we do want toc prettified.
- md.treeprocessors.add("toc", tocext, "_end")
+ md.treeprocessors.register(tocext, 'toc', 5)
def reset(self):
self.md.toc = ''
diff --git a/markdown/extensions/wikilinks.py b/markdown/extensions/wikilinks.py
index b535d9c..603682f 100644
--- a/markdown/extensions/wikilinks.py
+++ b/markdown/extensions/wikilinks.py
@@ -48,7 +48,7 @@ class WikiLinkExtension(Extension):
WIKILINK_RE = r'\[\[([\w0-9_ -]+)\]\]'
wikilinkPattern = WikiLinksInlineProcessor(WIKILINK_RE, self.getConfigs())
wikilinkPattern.md = md
- md.inlinePatterns.add('wikilink', wikilinkPattern, "<not_strong")
+ md.inlinePatterns.register(wikilinkPattern, 'wikilink', 75)
class WikiLinksInlineProcessor(InlineProcessor):
diff --git a/markdown/inlinepatterns.py b/markdown/inlinepatterns.py
index d67ef1f..343fdd6 100644
--- a/markdown/inlinepatterns.py
+++ b/markdown/inlinepatterns.py
@@ -44,7 +44,6 @@ So, we apply the expressions in the following order:
from __future__ import absolute_import
from __future__ import unicode_literals
from . import util
-from . import odict
import re
try: # pragma: no cover
from html import entities
@@ -54,32 +53,32 @@ except ImportError: # pragma: no cover
def build_inlinepatterns(md_instance, **kwargs):
""" Build the default set of inline patterns for Markdown. """
- inlinePatterns = odict.OrderedDict()
- inlinePatterns["backtick"] = BacktickInlineProcessor(BACKTICK_RE)
- inlinePatterns["escape"] = EscapeInlineProcessor(ESCAPE_RE, md_instance)
- inlinePatterns["reference"] = ReferenceInlineProcessor(REFERENCE_RE, md_instance)
- inlinePatterns["link"] = LinkInlineProcessor(LINK_RE, md_instance)
- inlinePatterns["image_link"] = ImageInlineProcessor(IMAGE_LINK_RE, md_instance)
- inlinePatterns["image_reference"] = ImageReferenceInlineProcessor(
- IMAGE_REFERENCE_RE, md_instance
+ inlinePatterns = util.Registry()
+ inlinePatterns.register(BacktickInlineProcessor(BACKTICK_RE), 'backtick', 190)
+ inlinePatterns.register(EscapeInlineProcessor(ESCAPE_RE, md_instance), 'escape', 180)
+ inlinePatterns.register(ReferenceInlineProcessor(REFERENCE_RE, md_instance), 'reference', 170)
+ inlinePatterns.register(LinkInlineProcessor(LINK_RE, md_instance), 'link', 160)
+ inlinePatterns.register(ImageInlineProcessor(IMAGE_LINK_RE, md_instance), 'image_link', 150)
+ inlinePatterns.register(
+ ImageReferenceInlineProcessor(IMAGE_REFERENCE_RE, md_instance), 'image_reference', 140
)
- inlinePatterns["short_reference"] = ShortReferenceInlineProcessor(
- REFERENCE_RE, md_instance
+ inlinePatterns.register(
+ ShortReferenceInlineProcessor(REFERENCE_RE, md_instance), 'short_reference', 130
)
- inlinePatterns["autolink"] = AutolinkInlineProcessor(AUTOLINK_RE, md_instance)
- inlinePatterns["automail"] = AutomailInlineProcessor(AUTOMAIL_RE, md_instance)
- inlinePatterns["linebreak"] = SubstituteTagInlineProcessor(LINE_BREAK_RE, 'br')
- inlinePatterns["html"] = HtmlInlineProcessor(HTML_RE, md_instance)
- inlinePatterns["entity"] = HtmlInlineProcessor(ENTITY_RE, md_instance)
- inlinePatterns["not_strong"] = SimpleTextInlineProcessor(NOT_STRONG_RE)
- inlinePatterns["em_strong"] = DoubleTagInlineProcessor(EM_STRONG_RE, 'strong,em')
- inlinePatterns["strong_em"] = DoubleTagInlineProcessor(STRONG_EM_RE, 'em,strong')
- inlinePatterns["strong"] = SimpleTagInlineProcessor(STRONG_RE, 'strong')
- inlinePatterns["emphasis"] = SimpleTagInlineProcessor(EMPHASIS_RE, 'em')
+ inlinePatterns.register(AutolinkInlineProcessor(AUTOLINK_RE, md_instance), 'autolink', 120)
+ inlinePatterns.register(AutomailInlineProcessor(AUTOMAIL_RE, md_instance), 'automail', 110)
+ inlinePatterns.register(SubstituteTagInlineProcessor(LINE_BREAK_RE, 'br'), 'linebreak', 100)
+ inlinePatterns.register(HtmlInlineProcessor(HTML_RE, md_instance), 'html', 90)
+ inlinePatterns.register(HtmlInlineProcessor(ENTITY_RE, md_instance), 'entity', 80)
+ inlinePatterns.register(SimpleTextInlineProcessor(NOT_STRONG_RE), 'not_strong', 70)
+ inlinePatterns.register(DoubleTagInlineProcessor(EM_STRONG_RE, 'strong,em'), 'em_strong', 60)
+ inlinePatterns.register(DoubleTagInlineProcessor(STRONG_EM_RE, 'em,strong'), 'strong_em', 50)
+ inlinePatterns.register(SimpleTagInlineProcessor(STRONG_RE, 'strong'), 'strong', 40)
+ inlinePatterns.register(SimpleTagInlineProcessor(EMPHASIS_RE, 'em'), 'emphasis', 30)
if md_instance.smart_emphasis:
- inlinePatterns["emphasis2"] = SimpleTagInlineProcessor(SMART_EMPHASIS_RE, 'em')
+ inlinePatterns.register(SimpleTagInlineProcessor(SMART_EMPHASIS_RE, 'em'), 'emphasis2', 20)
else:
- inlinePatterns["emphasis2"] = SimpleTagInlineProcessor(EMPHASIS_2_RE, 'em')
+ inlinePatterns.register(SimpleTagInlineProcessor(EMPHASIS_2_RE, 'em'), 'emphasis2', 20)
return inlinePatterns
diff --git a/markdown/odict.py b/markdown/odict.py
deleted file mode 100644
index 584ad7c..0000000
--- a/markdown/odict.py
+++ /dev/null
@@ -1,191 +0,0 @@
-from __future__ import unicode_literals
-from __future__ import absolute_import
-from . import util
-from copy import deepcopy
-
-
-class OrderedDict(dict):
- """
- A dictionary that keeps its keys in the order in which they're inserted.
-
- Copied from Django's SortedDict with some modifications.
-
- """
- def __new__(cls, *args, **kwargs):
- instance = super(OrderedDict, cls).__new__(cls, *args, **kwargs)
- instance.keyOrder = []
- return instance
-
- def __init__(self, data=None):
- if data is None or isinstance(data, dict):
- data = data or []
- super(OrderedDict, self).__init__(data)
- self.keyOrder = list(data) if data else []
- else:
- super(OrderedDict, self).__init__()
- super_set = super(OrderedDict, self).__setitem__
- for key, value in data:
- # Take the ordering from first key
- if key not in self:
- self.keyOrder.append(key)
- # But override with last value in data (dict() does this)
- super_set(key, value)
-
- def __deepcopy__(self, memo):
- return self.__class__([(key, deepcopy(value, memo))
- for key, value in self.items()])
-
- def __copy__(self):
- # The Python's default copy implementation will alter the state
- # of self. The reason for this seems complex but is likely related to
- # subclassing dict.
- return self.copy()
-
- def __setitem__(self, key, value):
- if key not in self:
- self.keyOrder.append(key)
- super(OrderedDict, self).__setitem__(key, value)
-
- def __delitem__(self, key):
- super(OrderedDict, self).__delitem__(key)
- self.keyOrder.remove(key)
-
- def __iter__(self):
- return iter(self.keyOrder)
-
- def __reversed__(self):
- return reversed(self.keyOrder)
-
- def pop(self, k, *args):
- result = super(OrderedDict, self).pop(k, *args)
- try:
- self.keyOrder.remove(k)
- except ValueError:
- # Key wasn't in the dictionary in the first place. No problem.
- pass
- return result
-
- def popitem(self):
- result = super(OrderedDict, self).popitem()
- self.keyOrder.remove(result[0])
- return result
-
- def _iteritems(self):
- for key in self.keyOrder:
- yield key, self[key]
-
- def _iterkeys(self):
- for key in self.keyOrder:
- yield key
-
- def _itervalues(self):
- for key in self.keyOrder:
- yield self[key]
-
- if util.PY3: # pragma: no cover
- items = _iteritems
- keys = _iterkeys
- values = _itervalues
- else: # pragma: no cover
- iteritems = _iteritems
- iterkeys = _iterkeys
- itervalues = _itervalues
-
- def items(self):
- return [(k, self[k]) for k in self.keyOrder]
-
- def keys(self):
- return self.keyOrder[:]
-
- def values(self):
- return [self[k] for k in self.keyOrder]
-
- def update(self, dict_):
- for k in dict_:
- self[k] = dict_[k]
-
- def setdefault(self, key, default):
- if key not in self:
- self.keyOrder.append(key)
- return super(OrderedDict, self).setdefault(key, default)
-
- def value_for_index(self, index):
- """Returns the value of the item at the given zero-based index."""
- return self[self.keyOrder[index]]
-
- def insert(self, index, key, value):
- """Inserts the key, value pair before the item with the given index."""
- if key in self.keyOrder:
- n = self.keyOrder.index(key)
- del self.keyOrder[n]
- if n < index:
- index -= 1
- self.keyOrder.insert(index, key)
- super(OrderedDict, self).__setitem__(key, value)
-
- def copy(self):
- """Returns a copy of this object."""
- # This way of initializing the copy means it works for subclasses, too.
- return self.__class__(self)
-
- def __repr__(self):
- """
- Replaces the normal dict.__repr__ with a version that returns the keys
- in their Ordered order.
- """
- return '{%s}' % ', '.join(
- ['%r: %r' % (k, v) for k, v in self._iteritems()]
- )
-
- def clear(self):
- super(OrderedDict, self).clear()
- self.keyOrder = []
-
- def index(self, key):
- """ Return the index of a given key. """
- try:
- return self.keyOrder.index(key)
- except ValueError:
- raise ValueError("Element '%s' was not found in OrderedDict" % key)
-
- def index_for_location(self, location):
- """ Return index or None for a given location. """
- if location == '_begin':
- i = 0
- elif location == '_end':
- i = None
- elif location.startswith('<') or location.startswith('>'):
- i = self.index(location[1:])
- if location.startswith('>'):
- if i >= len(self):
- # last item
- i = None
- else:
- i += 1
- else:
- raise ValueError('Not a valid location: "%s". Location key '
- 'must start with a ">" or "<".' % location)
- return i
-
- def add(self, key, value, location):
- """ Insert by key location. """
- i = self.index_for_location(location)
- if i is not None:
- self.insert(i, key, value)
- else:
- self.__setitem__(key, value)
-
- def link(self, key, location):
- """ Change location of an existing item. """
- n = self.keyOrder.index(key)
- del self.keyOrder[n]
- try:
- i = self.index_for_location(location)
- if i is not None:
- self.keyOrder.insert(i, key)
- else:
- self.keyOrder.append(key)
- except Exception as e:
- # restore to prevent data loss and reraise
- self.keyOrder.insert(n, key)
- raise e
diff --git a/markdown/postprocessors.py b/markdown/postprocessors.py
index f59e070..0fb4406 100644
--- a/markdown/postprocessors.py
+++ b/markdown/postprocessors.py
@@ -12,16 +12,15 @@ from __future__ import absolute_import
from __future__ import unicode_literals
from collections import OrderedDict
from . import util
-from . import odict
import re
def build_postprocessors(md_instance, **kwargs):
""" Build the default postprocessors for Markdown. """
- postprocessors = odict.OrderedDict()
- postprocessors["raw_html"] = RawHtmlPostprocessor(md_instance)
- postprocessors["amp_substitute"] = AndSubstitutePostprocessor()
- postprocessors["unescape"] = UnescapePostprocessor()
+ postprocessors = util.Registry()
+ postprocessors.register(RawHtmlPostprocessor(md_instance), 'raw_html', 30)
+ postprocessors.register(AndSubstitutePostprocessor(), 'amp_substitute', 20)
+ postprocessors.register(UnescapePostprocessor(), 'unescape', 10)
return postprocessors
diff --git a/markdown/preprocessors.py b/markdown/preprocessors.py
index 8c87ecf..cac0037 100644
--- a/markdown/preprocessors.py
+++ b/markdown/preprocessors.py
@@ -9,16 +9,15 @@ complicated.
from __future__ import absolute_import
from __future__ import unicode_literals
from . import util
-from . import odict
import re
def build_preprocessors(md_instance, **kwargs):
""" Build the default set of preprocessors used by Markdown. """
- preprocessors = odict.OrderedDict()
- preprocessors['normalize_whitespace'] = NormalizeWhitespace(md_instance)
- preprocessors["html_block"] = HtmlBlockPreprocessor(md_instance)
- preprocessors["reference"] = ReferencePreprocessor(md_instance)
+ preprocessors = util.Registry()
+ preprocessors.register(NormalizeWhitespace(md_instance), 'normalize_whitespace', 30)
+ preprocessors.register(HtmlBlockPreprocessor(md_instance), 'html_block', 20)
+ preprocessors.register(ReferencePreprocessor(md_instance), 'reference', 10)
return preprocessors
diff --git a/markdown/treeprocessors.py b/markdown/treeprocessors.py
index df5e748..0177e43 100644
--- a/markdown/treeprocessors.py
+++ b/markdown/treeprocessors.py
@@ -1,15 +1,14 @@
from __future__ import unicode_literals
from __future__ import absolute_import
from . import util
-from . import odict
from . import inlinepatterns
def build_treeprocessors(md_instance, **kwargs):
""" Build the default treeprocessors for Markdown. """
- treeprocessors = odict.OrderedDict()
- treeprocessors["inline"] = InlineProcessor(md_instance)
- treeprocessors["prettify"] = PrettifyTreeprocessor(md_instance)
+ treeprocessors = util.Registry()
+ treeprocessors.register(InlineProcessor(md_instance), 'inline', 20)
+ treeprocessors.register(PrettifyTreeprocessor(md_instance), 'prettify', 10)
return treeprocessors
@@ -103,8 +102,8 @@ class InlineProcessor(Treeprocessor):
startIndex = 0
while patternIndex < len(self.inlinePatterns):
data, matched, startIndex = self.__applyPattern(
- self.inlinePatterns.value_for_index(patternIndex),
- data, patternIndex, startIndex)
+ self.inlinePatterns[patternIndex], data, patternIndex, startIndex
+ )
if not matched:
patternIndex += 1
return data
diff --git a/markdown/util.py b/markdown/util.py
index 6591cf5..697cf45 100644
--- a/markdown/util.py
+++ b/markdown/util.py
@@ -2,6 +2,8 @@
from __future__ import unicode_literals
import re
import sys
+from collections import namedtuple
+import warnings
"""
@@ -179,3 +181,216 @@ class HtmlStash(object):
placeholder = TAG_PLACEHOLDER % str(self.tag_counter)
self.tag_counter += 1 # equal to the tag's index in self.tag_data
return placeholder
+
+
+# Used internally by `Registry` for each item in its sorted list.
+# Provides an easier to read API when editing the code later.
+# For example, `item.name` is more clear than `item[0]`.
+_PriorityItem = namedtuple('PriorityItem', ['name', 'priority'])
+
+
+class Registry(object):
+ """
+ A priority sorted registry.
+
+ A `Registry` instance provides two public methods to alter the data of the
+ registry: `register` and `deregister`. Use `register` to add items and
+ `deregister` to remove items. See each method for specifics.
+
+ When registering an item, a "name" and a "priority" must be provided. All
+ items are automatically sorted by "priority" from highest to lowest. The
+ "name" is used to remove ("deregister") and get items.
+
+ A `Registry` instance it like a list (which maintains order) when reading
+ data. You may iterate over the items, get an item and get a count (length)
+ of all items. You may also check that the registry contains an item.
+
+ When getting an item you may use either the index of the item or the
+ string-based "name". For example:
+
+ registry = Registry()
+ registry.register(SomeItem(), 'itemname', 20)
+ # Get the item by index
+ item = registry[0]
+ # Get the item by name
+ item = registry['itemname']
+
+ When checking that the registry contains an item, you may use either the
+ string-based "name", or a reference to the actual item. For example:
+
+ someitem = SomeItem()
+ registry.register(someitem, 'itemname', 20)
+ # Contains the name
+ assert 'itemname' in registry
+ # Contains the item instance
+ assert someitem in registry
+
+ The method `get_index_for_name` is also available to obtain the index of
+ an item using that item's assigned "name".
+ """
+
+ def __init__(self):
+ self._data = {}
+ self._priority = []
+ self._is_sorted = False
+
+ def __contains__(self, item):
+ if isinstance(item, string_type):
+ # Check if an item exists by this name.
+ return item in self._data.keys()
+ # Check if this instance exists.
+ return item in self._data.values()
+
+ def __iter__(self):
+ self._sort()
+ return iter([self._data[k] for k, p in self._priority])
+
+ def __getitem__(self, key):
+ self._sort()
+ if isinstance(key, slice):
+ data = Registry()
+ for k, p in self._priority[key]:
+ data.register(self._data[k], k, p)
+ return data
+ if isinstance(key, int):
+ return self._data[self._priority[key].name]
+ return self._data[key]
+
+ def __len__(self):
+ return len(self._priority)
+
+ def __repr__(self):
+ return '<{0}({1})>'.format(self.__class__.__name__, list(self))
+
+ def get_index_for_name(self, name):
+ """
+ Return the index of the given name.
+ """
+ if name in self:
+ self._sort()
+ return self._priority.index(
+ [x for x in self._priority if x.name == name][0]
+ )
+ raise ValueError('No item named "{0}" exists.'.format(name))
+
+ def register(self, item, name, priority):
+ """
+ Add an item to the registry with the given name and priority.
+
+ Parameters:
+
+ * `item`: The item being registered.
+ * `name`: A string used to reference the item.
+ * `priority`: An integer or float used to sort against all items.
+
+ If an item is registered with a "name" which already exists, the
+ existing item is replaced with the new item. Tread carefully as the
+ old item is lost with no way to recover it. The new item will be
+ sorted according to its priority and will **not** retain the position
+ of the old item.
+ """
+ if name in self:
+ # Remove existing item of same name first
+ self.deregister(name)
+ self._is_sorted = False
+ self._data[name] = item
+ self._priority.append(_PriorityItem(name, priority))
+
+ def deregister(self, name, strict=True):
+ """
+ Remove an item from the registry.
+
+ Set `strict=False` to fail silently.
+ """
+ try:
+ index = self.get_index_for_name(name)
+ del self._priority[index]
+ del self._data[name]
+ except ValueError:
+ if strict:
+ raise
+
+ def _sort(self):
+ """
+ Sort the registry by priority from highest to lowest.
+
+ This method is called internally and should never be explicitly called.
+ """
+ if not self._is_sorted:
+ self._priority.sort(key=lambda item: item.priority, reverse=True)
+ self._is_sorted = True
+
+ # Deprecated Methods which provide a smooth transition from OrderedDict
+
+ def __setitem__(self, key, value):
+ """ Register item with priorty 5 less than lowest existing priority. """
+ if isinstance(key, string_type):
+ warnings.warn(
+ 'Using setitem to register a processor or pattern is deprecated. '
+ 'Use the `register` method instead.', DeprecationWarning
+ )
+ if key in self:
+ # Key already exists, replace without altering priority
+ self._data[key] = value
+ return
+ if len(self) == 0:
+ # This is the first item. Set priority to 50.
+ priority = 50
+ else:
+ self._sort()
+ priority = self._priority[-1].priority - 5
+ self.register(value, key, priority)
+ else:
+ raise TypeError
+
+ def __delitem__(self, key):
+ """ Deregister an item by name. """
+ if key in self:
+ self.deregister(key)
+ warnings.warn(
+ 'Using del to remove a processor or pattern is deprecated. '
+ 'Use the `deregister` method instead.', DeprecationWarning
+ )
+ else:
+ raise TypeError
+
+ def add(self, key, value, location):
+ """ Register a key by location. """
+ if len(self) == 0:
+ # This is the first item. Set priority to 50.
+ priority = 50
+ elif location == '_begin':
+ self._sort()
+ # Set priority 5 greater than highest existing priority
+ priority = self._priority[0].priority + 5
+ elif location == '_end':
+ self._sort()
+ # Set priority 5 less than lowest existing priority
+ priority = self._priority[-1].priority - 5
+ elif location.startswith('<') or location.startswith('>'):
+ # Set priority halfway between existing priorities.
+ i = self.get_index_for_name(location[1:])
+ if location.startswith('<'):
+ after = self._priority[i].priority
+ if i > 0:
+ before = self._priority[i-1].priority
+ else:
+ # Location is first item`
+ before = after + 10
+ else:
+ # location.startswith('>')
+ before = self._priority[i].priority
+ if i < len(self) - 1:
+ after = self._priority[i+1].priority
+ else:
+ # location is last item
+ after = before - 10
+ priority = before - ((before - after) / 2)
+ else:
+ raise ValueError('Not a valid location: "%s". Location key '
+ 'must start with a ">" or "<".' % location)
+ self.register(value, key, priority)
+ warnings.warn(
+ 'Using the add method to register a processor or pattern is deprecated. '
+ 'Use the `register` method instead.', DeprecationWarning
+ )
diff --git a/tests/test_apis.py b/tests/test_apis.py
index 2875c85..3941bd4 100644
--- a/tests/test_apis.py
+++ b/tests/test_apis.py
@@ -202,152 +202,217 @@ class TestHtmlStash(unittest.TestCase):
self.assertEqual(self.stash.rawHtmlBlocks, [])
-class TestOrderedDict(unittest.TestCase):
- """ Test OrderedDict storage class. """
-
- def setUp(self):
- self.odict = markdown.odict.OrderedDict()
- self.odict['first'] = 'This'
- self.odict['third'] = 'a'
- self.odict['fourth'] = 'self'
- self.odict['fifth'] = 'test'
-
- def testValues(self):
- """ Test output of OrderedDict.values(). """
- self.assertEqual(list(self.odict.values()), ['This', 'a', 'self', 'test'])
-
- def testKeys(self):
- """ Test output of OrderedDict.keys(). """
- self.assertEqual(
- list(self.odict.keys()),
- ['first', 'third', 'fourth', 'fifth']
- )
-
- def testItems(self):
- """ Test output of OrderedDict.items(). """
- self.assertEqual(
- list(self.odict.items()), [
- ('first', 'This'),
- ('third', 'a'),
- ('fourth', 'self'),
- ('fifth', 'test')
- ]
- )
-
- def testAddBefore(self):
- """ Test adding an OrderedDict item before a given key. """
- self.odict.add('second', 'is', '<third')
- self.assertEqual(
- list(self.odict.items()), [
- ('first', 'This'),
- ('second', 'is'),
- ('third', 'a'),
- ('fourth', 'self'),
- ('fifth', 'test')
- ]
- )
-
- def testAddAfter(self):
- """ Test adding an OrderDict item after a given key. """
- self.odict.add('second', 'is', '>first')
- self.assertEqual(
- list(self.odict.items()), [
- ('first', 'This'),
- ('second', 'is'),
- ('third', 'a'),
- ('fourth', 'self'),
- ('fifth', 'test')
- ]
- )
-
- def testAddAfterEnd(self):
- """ Test adding an OrderedDict item after the last key. """
- self.odict.add('sixth', '.', '>fifth')
- self.assertEqual(
- list(self.odict.items()), [
- ('first', 'This'),
- ('third', 'a'),
- ('fourth', 'self'),
- ('fifth', 'test'),
- ('sixth', '.')
- ]
- )
-
- def testAdd_begin(self):
- """ Test adding an OrderedDict item using "_begin". """
- self.odict.add('zero', 'CRAZY', '_begin')
- self.assertEqual(
- list(self.odict.items()), [
- ('zero', 'CRAZY'),
- ('first', 'This'),
- ('third', 'a'),
- ('fourth', 'self'),
- ('fifth', 'test')
- ]
- )
-
- def testAdd_end(self):
- """ Test adding an OrderedDict item using "_end". """
- self.odict.add('sixth', '.', '_end')
- self.assertEqual(
- list(self.odict.items()), [
- ('first', 'This'),
- ('third', 'a'),
- ('fourth', 'self'),
- ('fifth', 'test'),
- ('sixth', '.')
- ]
- )
-
- def testAddBadLocation(self):
- """ Test Error on bad location in OrderedDict.add(). """
- self.assertRaises(ValueError, self.odict.add, 'sixth', '.', '<seventh')
- self.assertRaises(ValueError, self.odict.add, 'second', 'is', 'third')
-
- def testDeleteItem(self):
- """ Test deletion of an OrderedDict item. """
- del self.odict['fourth']
- self.assertEqual(
- list(self.odict.items()),
- [('first', 'This'), ('third', 'a'), ('fifth', 'test')]
- )
-
- def testChangeValue(self):
- """ Test OrderedDict change value. """
- self.odict['fourth'] = 'CRAZY'
- self.assertEqual(
- list(self.odict.items()), [
- ('first', 'This'),
- ('third', 'a'),
- ('fourth', 'CRAZY'),
- ('fifth', 'test')
- ]
- )
-
- def testChangeOrder(self):
- """ Test OrderedDict change order. """
- self.odict.link('fourth', '<third')
- self.assertEqual(
- list(self.odict.items()), [
- ('first', 'This'),
- ('fourth', 'self'),
- ('third', 'a'),
- ('fifth', 'test')
- ]
- )
-
- def textBadLink(self):
- """ Test OrderedDict change order with bad location. """
- self.assertRaises(ValueError, self.odict.link('fourth', '<bad'))
- # Check for data integrity ("fourth" wasn't deleted).'
- self.assertEqual(
- list(self.odict.items()), [
- ('first', 'This'),
- ('third', 'a'),
- ('fourth', 'self'),
- ('fifth', 'test')
- ]
- )
+class Item(object):
+ """ A dummy Registry item object for testing. """
+ def __init__(self, data):
+ self.data = data
+
+ def __repr__(self):
+ return repr(self.data)
+
+ def __eq__(self, other):
+ return self.data == other
+
+
+class RegistryTests(unittest.TestCase):
+ """ Test the processor registry. """
+
+ def testCreateRegistry(self):
+ r = markdown.util.Registry()
+ r.register(Item('a'), 'a', 20)
+ self.assertEqual(len(r), 1)
+ self.assertTrue(isinstance(r, markdown.util.Registry))
+
+ def testRegisterWithoutPriority(self):
+ r = markdown.util.Registry()
+ with self.assertRaises(TypeError):
+ r.register(Item('a'))
+
+ def testSortRegistry(self):
+ r = markdown.util.Registry()
+ r.register(Item('a'), 'a', 20)
+ r.register(Item('b'), 'b', 21)
+ r.register(Item('c'), 'c', 20.5)
+ self.assertEqual(len(r), 3)
+ self.assertEqual(list(r), ['b', 'c', 'a'])
+
+ def testIsSorted(self):
+ r = markdown.util.Registry()
+ self.assertFalse(r._is_sorted)
+ r.register(Item('a'), 'a', 20)
+ list(r)
+ self.assertTrue(r._is_sorted)
+ r.register(Item('b'), 'b', 21)
+ self.assertFalse(r._is_sorted)
+ r['a']
+ self.assertTrue(r._is_sorted)
+ r._is_sorted = False
+ r.get_index_for_name('a')
+ self.assertTrue(r._is_sorted)
+ r._is_sorted = False
+ repr(r)
+ self.assertTrue(r._is_sorted)
+
+ def testDeregister(self):
+ r = markdown.util.Registry()
+ r.register(Item('a'), 'a', 20)
+ r.register(Item('b'), 'b', 30)
+ r.register(Item('c'), 'c', 40)
+ self.assertEqual(len(r), 3)
+ r.deregister('b')
+ self.assertEqual(len(r), 2)
+ r.deregister('c', strict=False)
+ self.assertEqual(len(r), 1)
+ # deregister non-existant item with strict=False
+ r.deregister('d', strict=False)
+ self.assertEqual(len(r), 1)
+ with self.assertRaises(ValueError):
+ # deregister non-existant item with strict=True
+ r.deregister('e')
+ self.assertEqual(list(r), ['a'])
+
+ def testRegistryContains(self):
+ r = markdown.util.Registry()
+ item = Item('a')
+ r.register(item, 'a', 20)
+ self.assertTrue('a' in r)
+ self.assertTrue(item in r)
+ self.assertFalse('b' in r)
+
+ def testRegistryIter(self):
+ r = markdown.util.Registry()
+ r.register(Item('a'), 'a', 20)
+ r.register(Item('b'), 'b', 30)
+ self.assertEqual(list(r), ['b', 'a'])
+
+ def testRegistryGetItemByIndex(self):
+ r = markdown.util.Registry()
+ r.register(Item('a'), 'a', 20)
+ r.register(Item('b'), 'b', 30)
+ self.assertEqual(r[0], 'b')
+ self.assertEqual(r[1], 'a')
+ with self.assertRaises(IndexError):
+ r[3]
+
+ def testRegistryGetItemByItem(self):
+ r = markdown.util.Registry()
+ r.register(Item('a'), 'a', 20)
+ r.register(Item('b'), 'b', 30)
+ self.assertEqual(r['a'], 'a')
+ self.assertEqual(r['b'], 'b')
+ with self.assertRaises(KeyError):
+ r['c']
+
+ def testRegistrySetItem(self):
+ r = markdown.util.Registry()
+ with self.assertRaises(TypeError):
+ r[0] = 'a'
+ # TODO: restore this when deprecated __setitem__ is removed.
+ # with self.assertRaises(TypeError):
+ # r['a'] = 'a'
+ # TODO: remove this when deprecated __setitem__ is removed.
+ with warnings.catch_warnings(record=True) as w:
+ warnings.simplefilter("always")
+
+ r['a'] = Item('a')
+ self.assertEqual(list(r), ['a'])
+ r['b'] = Item('b')
+ self.assertEqual(list(r), ['a', 'b'])
+ r['a'] = Item('a1')
+ self.assertEqual(list(r), ['a1', 'b'])
+
+ # Check the warnings
+ self.assertEqual(len(w), 3)
+ self.assertTrue(all(issubclass(x.category, DeprecationWarning) for x in w))
+
+ def testRegistryDelItem(self):
+ r = markdown.util.Registry()
+ r.register(Item('a'), 'a', 20)
+ with self.assertRaises(TypeError):
+ del r[0]
+ # TODO: restore this when deprecated __del__ is removed.
+ # with self.assertRaises(TypeError):
+ # del r['a']
+ # TODO: remove this when deprecated __del__ is removed.
+ with warnings.catch_warnings(record=True) as w:
+ warnings.simplefilter("always")
+
+ r.register(Item('b'), 'b', 15)
+ r.register(Item('c'), 'c', 10)
+ del r['b']
+ self.assertEqual(list(r), ['a', 'c'])
+ del r['a']
+ self.assertEqual(list(r), ['c'])
+ with self.assertRaises(TypeError):
+ del r['badname']
+ del r['c']
+ self.assertEqual(list(r), [])
+
+ # Check the warnings
+ self.assertEqual(len(w), 3)
+ self.assertTrue(all(issubclass(x.category, DeprecationWarning) for x in w))
+
+ def testRegistrySlice(self):
+ r = markdown.util.Registry()
+ r.register(Item('a'), 'a', 20)
+ r.register(Item('b'), 'b', 30)
+ r.register(Item('c'), 'c', 40)
+ slc = r[1:]
+ self.assertEqual(len(slc), 2)
+ self.assertTrue(isinstance(slc, markdown.util.Registry))
+ self.assertEqual(list(slc), ['b', 'a'])
+
+ def testGetIndexForName(self):
+ r = markdown.util.Registry()
+ r.register(Item('a'), 'a', 20)
+ r.register(Item('b'), 'b', 30)
+ self.assertEqual(r.get_index_for_name('a'), 1)
+ self.assertEqual(r.get_index_for_name('b'), 0)
+ with self.assertRaises(ValueError):
+ r.get_index_for_name('c')
+
+ def testRegisterDupplicate(self):
+ r = markdown.util.Registry()
+ r.register(Item('a'), 'a', 20)
+ r.register(Item('b1'), 'b', 10)
+ self.assertEqual(list(r), ['a', 'b1'])
+ self.assertEqual(len(r), 2)
+ r.register(Item('b2'), 'b', 30)
+ self.assertEqual(len(r), 2)
+ self.assertEqual(list(r), ['b2', 'a'])
+
+ def testRegistryDeprecatedAdd(self):
+ with warnings.catch_warnings(record=True) as w:
+ warnings.simplefilter("always")
+
+ r = markdown.util.Registry()
+ # Add first item
+ r.add('c', Item('c'), '_begin')
+ self.assertEqual(list(r), ['c'])
+ # Added to beginning
+ r.add('b', Item('b'), '_begin')
+ self.assertEqual(list(r), ['b', 'c'])
+ # Add before first item
+ r.add('a', Item('a'), '<b')
+ self.assertEqual(list(r), ['a', 'b', 'c'])
+ # Add before non-first item
+ r.add('a1', Item('a1'), '<b')
+ self.assertEqual(list(r), ['a', 'a1', 'b', 'c'])
+ # Add after non-last item
+ r.add('b1', Item('b1'), '>b')
+ self.assertEqual(list(r), ['a', 'a1', 'b', 'b1', 'c'])
+ # Add after last item
+ r.add('d', Item('d'), '>c')
+ self.assertEqual(list(r), ['a', 'a1', 'b', 'b1', 'c', 'd'])
+ # Add to end
+ r.add('e', Item('e'), '_end')
+ self.assertEqual(list(r), ['a', 'a1', 'b', 'b1', 'c', 'd', 'e'])
+ with self.assertRaises(ValueError):
+ r.add('f', Item('f'), 'badlocation')
+
+ # Check the warnings
+ self.assertEqual(len(w), 7)
+ self.assertTrue(all(issubclass(x.category, DeprecationWarning) for x in w))
class TestErrors(unittest.TestCase):
@@ -862,7 +927,7 @@ class TestAncestorExclusion(unittest.TestCase):
"""Modify inline patterns."""
pattern = r'(\+)([^\+]+)\1'
- md.inlinePatterns["ancestor-test"] = TestAncestorExclusion.AncestorExample(pattern, 'strong')
+ md.inlinePatterns.register(TestAncestorExclusion.AncestorExample(pattern, 'strong'), 'ancestor-test', 0)
def setUp(self):
"""Setup markdown object."""