From 55a4c08a47fc1860bcc5217290b9179e528bdea0 Mon Sep 17 00:00:00 2001
From: Hyunjee Kim A regular paragraph of text. First paragraph of wrapped text. Second Paragraph of **wrapped** text. Another regular paragraph of text. First line of the block.
+ This is A paragraph is simply one or more consecutive lines of text, separated
by one or more blank lines. (A blank line is any line that looks like a
blank line -- a line containing nothing spaces or tabs is considered
-blank.) Normal paragraphs should not be intended with spaces or tabs. tags (#862).
+* Always wrap CodeHilite code in `code` tags (#862).
diff --git a/docs/cli.md b/docs/cli.md
index 1c4e40a..50e9ec2 100644
--- a/docs/cli.md
+++ b/docs/cli.md
@@ -55,7 +55,7 @@ path.
* **Windows**:
Assuming a default install of Python on Windows, your "Scripts" directory
- is most likely something like `C:\\Python26\Scripts`. Verify the location
+ is most likely something like `C:\\Python37\Scripts`. Verify the location
of your "Scripts" directory and add it to you system path.
Calling `markdown_py` from the command line will call the wrapper batch
diff --git a/docs/contributing.md b/docs/contributing.md
index ca95042..974d380 100644
--- a/docs/contributing.md
+++ b/docs/contributing.md
@@ -9,7 +9,7 @@ propose changes to this document in a pull request.
This project and everyone participating in it is governed by the
[Python-Markdown Code of Conduct]. By participating, you are expected to uphold
-this code. Please report unacceptable behavior to
strike`.
```python
-from markdown.treeprocessors import Treeprocessor
+from markdown.inlinepatterns import InlineProcessor
+from markdown.extensions import Extension
+import xml.etree.ElementTree as etree
-class MyTreeprocessor(Treeprocessor):
- def run(self, root):
- root.text = 'modified content'
+
+class DelInlineProcessor(InlineProcessor):
+ def handleMatch(self, m, data):
+ el = etree.Element('del')
+ el.text = m.group(1)
+ return el, m.start(0), m.end(0)
+
+class DelExtension(Extension):
+ def extendMarkdown(self, md):
+ DEL_PATTERN = r'--(.*?)--' # like --del--
+ md.inlinePatterns.register(DelInlineProcessor(DEL_PATTERN, md), 'del', 175)
```
-Note that Python class methods return `None` by default when no `return`
-statement is defined. Additionally all Python variables refer to objects by
-reference. Therefore, the above `run` method modifies the `root` element
-in place and returns `None`. The changes made to the `root` element and its
-children are retained.
+Use this input example:
+
+``` text
+First line of the block.
+This is --strike one--.
+This is --strike two--.
+End of the block.
+```
-Some may be inclined to return the modified `root` element. While that would
-work, it would cause a copy of the entire ElementTree to be generated each
-time the Treeprocessor is run. Therefore, it is generally expected that
-the `run` method would only return `None` or a new ElementTree object.
+The example output might display as follows:
-For specifics on manipulating the ElementTree, see
-[Working with the ElementTree][workingwithetree] below.
+!!! note ""
+ strike one.
+ This is strike two.
+ End of the block.
`), then it
-appends that block to the previous code block rather than creating a new
-code block.
+2. The `handleMatch` method now takes an additional input called `data`, which is the entire block under analysis,
+ not just what is matched with the specified pattern. The method now returns the element *and* the indexes relative
+ to `data` that the return element is replacing (usually `m.start(0)` and `m.end(0)`). If the boundaries are
+ returned as `None`, it is assumed that the match did not take place, and nothing will be altered in `data`.
-Each Blockprocessor has the following utility methods available:
+ This allows handling of more complex constructs than regular expressions can handle, e.g., matching nested
+ brackets, and explicit control of the span "consumed" by the processor.
+
+#### Inline Patterns
-* **`lastChild(parent)`**:
-
- Returns the last child of the given ElementTree Element or `None` if it
- had no children.
+Inline Patterns can implement inline HTML element syntax for Markdown such as `*emphasis*` or
+`[links](http://example.com)`. Pattern objects should be instances of classes that inherit from
+`markdown.inlinepatterns.Pattern` or one of its children. Each pattern object uses a single regular expression and
+must have the following methods:
-* **`detab(text)`**:
+* **`getCompiledRegExp()`**:
- Removes one level of indent (four spaces by default) from the front of each
- line of the given text string.
+ Returns a compiled regular expression.
-* **`looseDetab(text, level)`**:
+* **`handleMatch(m)`**:
- Removes "level" levels of indent (defaults to 1) from the front of each line
- of the given text string. However, this methods allows secondary lines to
- not be indented as does some parts of the Markdown syntax.
+ Accepts a match object and returns an ElementTree element of a plain Unicode string.
-Each Blockprocessor also has a pointer to the containing BlockParser instance at
-`self.parser`, which can be used to check or alter the state of the parser.
-The BlockParser tracks it's state in a stack at `parser.state`. The state
-stack is an instance of the `State` class.
+Inline Patterns can define the property `ANCESTOR_EXCLUDES` with is either a list or tuple of undesirable ancestors.
+The pattern will be skipped if it would cause the content to be a descendant of one of the listed tag names.
-**`State`** is a subclass of `list` and has the additional methods:
+Note that any regular expression returned by `getCompiledRegExp` must capture the whole block. Therefore, they should
+all start with `r'^(.*?)'` and end with `r'(.*?)!'`. When using the default `getCompiledRegExp()` method provided in
+the `Pattern` you can pass in a regular expression without that and `getCompiledRegExp` will wrap your expression for
+you and set the `re.DOTALL` and `re.UNICODE` flags. This means that the first group of your match will be `m.group(2)`
+as `m.group(1)` will match everything before the pattern.
-* **`set(state)`**:
+For an example, consider this simplified emphasis pattern:
- Set a new state to string `state`. The new state is appended to the end
- of the stack.
+```python
+from markdown.inlinepatterns import Pattern
+import xml.etree.ElementTree as etree
-* **`reset()`**:
+class EmphasisPattern(Pattern):
+ def handleMatch(self, m):
+ el = etree.Element('em')
+ el.text = m.group(2)
+ return el
+```
- Step back one step in the stack. The last state at the end is removed from
- the stack.
+As discussed in [Integrating Your Code Into Markdown][], an instance of this class will need to be provided to
+Markdown. That instance would be created like so:
-* **`isstate(state)`**:
+```python
+# an oversimplified regex
+MYPATTERN = r'\*([^*]+)\*'
+# pass in pattern and create instance
+emphasis = EmphasisPattern(MYPATTERN)
+```
- Test that the top (current) level of the stack is of the given string
- `state`.
+### Postprocessors {: #postprocessors }
-Note that to ensure that the state stack does not become corrupted, each time a
-state is set for a block, that state *must* be reset when the parser finishes
-parsing that block.
+Postprocessors munge the document after the ElementTree has been serialized into a string. Postprocessors should be
+used to work with the text just before output. Usually, they are used add back sections that were extracted in a
+preprocessor, fix up outgoing encodings, or wrap the whole document.
-An instance of the **`BlockParser`** is found at `Markdown.parser`.
-`BlockParser` has the following methods:
+Postprocessors inherit from `markdown.postprocessors.Postprocessor` and implement a `run` method which takes a single
+parameter `text`, the entire HTML document as a single Unicode string. `run` should return a single Unicode string
+ready for output. Note that preprocessors use a list of lines while postprocessors use a single multi-line string.
-* **`parseDocument(lines)`**:
+#### Example
- Given a list of lines, an ElementTree object is returned. This should be
- passed an entire document and is the only method the `Markdown` class
- calls directly.
+Here is a simple example that changes the output to one big page showing the raw html.
-* **`parseChunk(parent, text)`**:
+```python
+from markdown.postprocessors import Postprocessor
+import re
- Parses a chunk of markdown text composed of multiple blocks and attaches
- those blocks to the `parent` Element. The `parent` is altered in place
- and nothing is returned. Extensions would most likely use this method for
- block parsing.
+class ShowActualHtmlPostprocesor(Postprocessor):
+ """ Wrap entire output in ...
tags as a diagnostic. """
+ def run(self, text):
+ return '
\n' + re.sub('<', '<', text) + '
\n'
+```
-* **`parseBlocks(parent, blocks)`**:
+#### Usages
- Parses a list of blocks of text and attaches those blocks to the `parent`
- Element. The `parent` is altered in place and nothing is returned. This
- method will generally only be used internally to recursively parse nested
- blocks of text.
+Some postprocessors in the Markdown source tree include:
-While it is not recommended, an extension could subclass or completely replace
-the `BlockParser`. The new class would have to provide the same public API.
-However, be aware that other extensions may expect the core parser provided
-and will not work with such a drastically different parser.
+| Class | Kind | Description |
+| ------------------------------|-----------|----------------------------------------------------|
+| [`raw_html`][p1] | built-in | Restore raw html from `htmlStash`, stored by `HTMLBlockPreprocessor`, and code highlighters |
+| [`amp_substitute`][p2] | built-in | Convert ampersand substitutes to `&`; used in links |
+| [`unescape`][p3] | built-in | Convert some escaped characters back from integers; used in links |
+| [`FootnotePostProcessor`][p4] | extension | Replace footnote placeholders with html entities; as set by other stages |
+
+ [p1]: https://github.com/Python-Markdown/markdown/blob/master/markdown/postprocessors.py
+ [p2]: https://github.com/Python-Markdown/markdown/blob/master/markdown/postprocessors.py
+ [p3]: https://github.com/Python-Markdown/markdown/blob/master/markdown/postprocessors.py
+ [p4]: https://github.com/Python-Markdown/markdown/blob/master/markdown/extensions/footnotes.py
+
## Working with the ElementTree {: #working_with_et }
-As mentioned, the Markdown parser converts a source document to an
-[ElementTree][ElementTree] object before serializing that back to Unicode text.
-Markdown has provided some helpers to ease that manipulation within the context
+As mentioned, the Markdown parser converts a source document to an [ElementTree][ElementTree] object before
+serializing that back to Unicode text. Markdown has provided some helpers to ease that manipulation within the context
of the Markdown module.
First, import the ElementTree module:
@@ -434,19 +517,17 @@ First, import the ElementTree module:
```python
import xml.etree.ElementTree as etree
```
-Sometimes you may want text inserted into an element to be parsed by
-[Inline Patterns][]. In such a situation, simply insert the text as you normally
-would and the text will be automatically run through the Inline Patterns.
-However, if you do *not* want some text to be parsed by Inline Patterns,
-then insert the text as an `AtomicString`.
+Sometimes you may want text inserted into an element to be parsed by [Inline Patterns][]. In such a situation, simply
+insert the text as you normally would and the text will be automatically run through the Inline Patterns. However, if
+you do *not* want some text to be parsed by Inline Patterns, then insert the text as an `AtomicString`.
```python
from markdown.util import AtomicString
some_element.text = AtomicString(some_text)
```
-Here's a basic example which creates an HTML table (note that the contents of
-the second cell (`td2`) will be run through Inline Patterns latter):
+Here's a basic example which creates an HTML table (note that the contents of the second cell (`td2`) will be run
+through Inline Patterns latter):
```python
table = etree.Element("table")
@@ -459,50 +540,44 @@ td2.text = "*text* with **inline** formatting." # Add markup text
table.tail = "Text after table" # Add text after table
```
-You can also manipulate an existing tree. Consider the following example which
-adds a `class` attribute to `` elements:
+You can also manipulate an existing tree. Consider the following example which adds a `class` attribute to ``
+elements:
```python
def set_link_class(self, element):
for child in element:
if child.tag == "a":
child.set("class", "myclass") #set the class attribute
- set_link_class(child) # run recursively on children
+ set_link_class(child) # run recursively on children
```
For more information about working with ElementTree see the ElementTree
-[Documentation](https://effbot.org/zone/element-index.htm)
-([Python Docs](https://docs.python.org/3/library/xml.etree.elementtree.html)).
+[Documentation](https://effbot.org/zone/element-index.htm) ([Python
+Docs](https://docs.python.org/3/library/xml.etree.elementtree.html)).
## Integrating Your Code Into Markdown {: #integrating_into_markdown }
-Once you have the various pieces of your extension built, you need to tell
-Markdown about them and ensure that they are run in the proper sequence.
-Markdown accepts an `Extension` instance for each extension. Therefore, you
-will need to define a class that extends `markdown.extensions.Extension` and
-over-rides the `extendMarkdown` method. Within this class you will manage
-configuration options for your extension and attach the various processors and
-patterns to the Markdown instance.
-
-It is important to note that the order of the various processors and patterns
-matters. For example, if we replace `http://...` links with `` elements, and
-*then* try to deal with inline HTML, we will end up with a mess. Therefore, the
-various types of processors and patterns are stored within an instance of the
-Markdown class in a [Registry][]. Your `Extension` class will need to manipulate
-those registries appropriately. You may `register` instances of your processors
-and patterns with an appropriate priority, `deregister` built-in instances, or
-replace a built-in instance with your own.
+Once you have the various pieces of your extension built, you need to tell Markdown about them and ensure that they
+are run in the proper sequence. Markdown accepts an `Extension` instance for each extension. Therefore, you will need
+to define a class that extends `markdown.extensions.Extension` and over-rides the `extendMarkdown` method. Within this
+class you will manage configuration options for your extension and attach the various processors and patterns to the
+Markdown instance.
+
+It is important to note that the order of the various processors and patterns matters. For example, if we replace
+`http://...` links with `` elements, and *then* try to deal with inline HTML, we will end up with a mess.
+Therefore, the various types of processors and patterns are stored within an instance of the `markdown.Markdown` class
+in a [Registry][]. Your `Extension` class will need to manipulate those registries appropriately. You may `register`
+instances of your processors and patterns with an appropriate priority, `deregister` built-in instances, or replace a
+built-in instance with your own.
### `extendMarkdown` {: #extendmarkdown }
-The `extendMarkdown` method of a `markdown.extensions.Extension` class
-accepts one argument:
+The `extendMarkdown` method of a `markdown.extensions.Extension` class accepts one argument:
* **`md`**:
- A pointer to the instance of the Markdown class. You should use this to
- access the [Registries][Registry] of processors and patterns. They are
- found under the following attributes:
+ A pointer to the instance of the `markdown.Markdown` class. You should use this to access the
+ [Registries][Registry] of processors and patterns. They are found under the following attributes:
* `md.preprocessors`
* `md.inlinePatterns`
@@ -510,7 +585,7 @@ accepts one argument:
* `md.treeprocessors`
* `md.postprocessors`
- Some other things you may want to access in the markdown instance are:
+ Some other things you may want to access on the `markdown.Markdown` instance are:
* `md.htmlStash`
* `md.output_formats`
@@ -523,12 +598,10 @@ accepts one argument:
* `md.isBlockLevel()`
!!! Warning
- With access to the above items, theoretically you have the option to
- change anything through various [monkey_patching][] techniques. However,
- you should be aware that the various undocumented parts of markdown may
- change without notice and your monkey_patches may break with a new release.
- Therefore, what you really should be doing is inserting processors and
- patterns into the markdown pipeline. Consider yourself warned!
+ With access to the above items, theoretically you have the option to change anything through various
+ [monkey_patching][] techniques. However, you should be aware that the various undocumented parts of Markdown may
+ change without notice and your monkey_patches may break with a new release. Therefore, what you really should be
+ doing is inserting processors and patterns into the Markdown pipeline. Consider yourself warned!
[monkey_patching]: https://en.wikipedia.org/wiki/Monkey_patch
@@ -543,77 +616,10 @@ class MyExtension(Extension):
md.inlinePatterns.register(MyPattern(md), 'mypattern', 175)
```
-### Registry
-
-The `markdown.util.Registry` class is a priority sorted registry which Markdown
-uses internally to determine the processing order of its various processors and
-patterns.
-
-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 the value of the "priority" parameter such
-that the item with the highest value will be processed first. The "name" is
-used to remove (`deregister`) and get items.
-
-A `Registry` instance is 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
-
-`markdown.util.Registry` has the following methods:
-
-#### `Registry.register(self, item, name, priority)` {: #registry.register }
-
-: 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.
-
-#### `Registry.deregister(self, name, strict=True)` {: #registry.deregister }
-
-: Remove an item from the registry.
-
- Set `strict=False` to fail silently.
-
-#### `Registry.get_index_for_name(self, name)` {: #registry.get_index_for_name }
-
-: Return the index of the given `name`.
-
### registerExtension {: #registerextension }
-Some extensions may need to have their state reset between multiple runs of the
-Markdown class. For example, consider the following use of the [Footnotes][]
-extension:
+Some extensions may need to have their state reset between multiple runs of the `markdown.Markdown` class. For
+example, consider the following use of the [Footnotes][] extension:
```python
md = markdown.Markdown(extensions=['footnotes'])
@@ -622,15 +628,12 @@ md.reset()
html2 = md.convert(text_without_footnote)
```
-Without calling `reset`, the footnote definitions from the first document will
-be inserted into the second document as they are still stored within the class
-instance. Therefore the `Extension` class needs to define a `reset` method
-that will reset the state of the extension (i.e.: `self.footnotes = {}`).
-However, as many extensions do not have a need for `reset`, `reset` is only
-called on extensions that are registered.
+Without calling `reset`, the footnote definitions from the first document will be inserted into the second document as
+they are still stored within the class instance. Therefore the `Extension` class needs to define a `reset` method that
+will reset the state of the extension (i.e.: `self.footnotes = {}`). However, as many extensions do not have a need
+for `reset`, `reset` is only called on extensions that are registered.
-To register an extension, call `md.registerExtension` from within your
-`extendMarkdown` method:
+To register an extension, call `md.registerExtension` from within your `extendMarkdown` method:
```python
def extendMarkdown(self, md):
@@ -638,43 +641,41 @@ def extendMarkdown(self, md):
# insert processors and patterns here
```
-Then, each time `reset` is called on the Markdown instance, the `reset`
-method of each registered extension will be called as well. You should also
-note that `reset` will be called on each registered extension after it is
-initialized the first time. Keep that in mind when over-riding the extension's
-`reset` method.
+Then, each time `reset` is called on the `markdown.Markdown` instance, the `reset` method of each registered extension
+will be called as well. You should also note that `reset` will be called on each registered extension after it is
+initialized the first time. Keep that in mind when over-riding the extension's `reset` method.
### Configuration Settings {: #configsettings }
-If an extension uses any parameters that the user may want to change,
-those parameters should be stored in `self.config` of your
-`markdown.extensions.Extension` class in the following format:
+If an extension uses any parameters that the user may want to change, those parameters should be stored in
+`self.config` of your `markdown.extensions.Extension` class in the following format:
```python
class MyExtension(markdown.extensions.Extension):
def __init__(self, **kwargs):
- self.config = {'option1' : ['value1', 'description1'],
- 'option2' : ['value2', 'description2'] }
+ self.config = {
+ 'option1' : ['value1', 'description1'],
+ 'option2' : ['value2', 'description2']
+ }
super(MyExtension, self).__init__(**kwargs)
```
-When implemented this way the configuration parameters can be over-ridden at
-run time (thus the call to `super`). For example:
+When implemented this way the configuration parameters can be over-ridden at run time (thus the call to `super`). For
+example:
```python
markdown.Markdown(extensions=[MyExtension(option1='other value')])
```
-Note that if a keyword is passed in that is not already defined in
-`self.config`, then a `KeyError` is raised.
+Note that if a keyword is passed in that is not already defined in `self.config`, then a `KeyError` is raised.
-The `markdown.extensions.Extension` class and its subclasses have the
-following methods available to assist in working with configuration settings:
+The `markdown.extensions.Extension` class and its subclasses have the following methods available to assist in working
+with configuration settings:
* **`getConfig(key [, default])`**:
- Returns the stored value for the given `key` or `default` if the `key`
- does not exist. If not set, `default` returns an empty string.
+ Returns the stored value for the given `key` or `default` if the `key` does not exist. If not set, `default`
+ returns an empty string.
* **`getConfigs()`**:
@@ -686,12 +687,10 @@ following methods available to assist in working with configuration settings:
* **`setConfig(key, value)`**:
- Sets a configuration setting for `key` with the given `value`. If `key` is
- unknown, a `KeyError` is raised. If the previous value of `key` was
- a Boolean value, then `value` is converted to a Boolean value. If
- the previous value of `key` is `None`, then `value` is converted to
- a Boolean value except when it is `None`. No conversion takes place
- when the previous value of `key` is a string.
+ Sets a configuration setting for `key` with the given `value`. If `key` is unknown, a `KeyError` is raised. If the
+ previous value of `key` was a Boolean value, then `value` is converted to a Boolean value. If the previous value
+ of `key` is `None`, then `value` is converted to a Boolean value except when it is `None`. No conversion takes
+ place when the previous value of `key` is a string.
* **`setConfigs(items)`**:
@@ -699,9 +698,8 @@ following methods available to assist in working with configuration settings:
### Naming an Extension { #naming_an_extension }
-As noted in the [library reference] an instance of an extension can be passed
-directly to Markdown. In fact, this is the preferred way to use third-party
-extensions.
+As noted in the [library reference] an instance of an extension can be passed directly to `markdown.Markdown`. In
+fact, this is the preferred way to use third-party extensions.
For example:
@@ -711,18 +709,15 @@ from path.to.module import MyExtension
md = markdown.Markdown(extensions=[MyExtension(option='value')])
```
-However, Markdown also accepts "named" third party extensions for those
-occasions when it is impractical to import an extension directly (from the
-command line or from within templates). A "name" can either be a registered
-[entry point](#entry_point) or a string using Python's [dot
-notation](#dot_notation).
+However, Markdown also accepts "named" third party extensions for those occasions when it is impractical to import an
+extension directly (from the command line or from within templates). A "name" can either be a registered [entry
+point](#entry_point) or a string using Python's [dot notation](#dot_notation).
#### Entry Point { #entry_point }
-[Entry points] are defined in a Python package's `setup.py` script. The script
-must use [setuptools] to support entry points. Python-Markdown extensions must
-be assigned to the `markdown.extensions` group. An entry point definition might
-look like this:
+[Entry points] are defined in a Python package's `setup.py` script. The script must use [setuptools] to support entry
+points. Python-Markdown extensions must be assigned to the `markdown.extensions` group. An entry point definition
+might look like this:
```python
from setuptools import setup
@@ -735,25 +730,23 @@ setup(
)
```
-After a user installs your extension using the above script, they could then
-call the extension using the `myextension` string name like this:
+After a user installs your extension using the above script, they could then call the extension using the
+`myextension` string name like this:
```python
markdown.markdown(text, extensions=['myextension'])
```
-Note that if two or more entry points within the same group are assigned the
-same name, Python-Markdown will only ever use the first one found and ignore all
-others. Therefore, be sure to give your extension a unique name.
+Note that if two or more entry points within the same group are assigned the same name, Python-Markdown will only ever
+use the first one found and ignore all others. Therefore, be sure to give your extension a unique name.
-For more information on writing `setup.py` scripts, see the Python documentation
-on [Packaging and Distributing Projects].
+For more information on writing `setup.py` scripts, see the Python documentation on [Packaging and Distributing
+Projects].
#### Dot Notation { #dot_notation }
-If an extension does not have a registered entry point, Python's dot notation
-may be used instead. The extension must be installed as a Python module on your
-PYTHONPATH. Generally, a class should be specified in the name. The class must
+If an extension does not have a registered entry point, Python's dot notation may be used instead. The extension must
+be installed as a Python module on your PYTHONPATH. Generally, a class should be specified in the name. The class must
be at the end of the name and be separated by a colon from the module.
Therefore, if you were to import the class like this:
@@ -768,16 +761,13 @@ Then the extension can be loaded as follows:
markdown.markdown(text, extensions=['path.to.module:MyExtension'])
```
-You do not need to do anything special to support this feature. As long as your
-extension class is able to be imported, a user can include it with the above
-syntax.
+You do not need to do anything special to support this feature. As long as your extension class is able to be
+imported, a user can include it with the above syntax.
-The above two methods are especially useful if you need to implement a large
-number of extensions with more than one residing in a module. However, if you do
-not want to require that your users include the class name in their string, you
-must define only one extension per module and that module must contain a
-module-level function called `makeExtension` that accepts `**kwargs` and returns
-an extension instance.
+The above two methods are especially useful if you need to implement a large number of extensions with more than one
+residing in a module. However, if you do not want to require that your users include the class name in their string,
+you must define only one extension per module and that module must contain a module-level function called
+`makeExtension` that accepts `**kwargs` and returns an extension instance.
For example:
@@ -789,15 +779,78 @@ def makeExtension(**kwargs):
return MyExtension(**kwargs)
```
-When Markdown is passed the "name" of your extension as a dot notation string
-that does not include a class (for example `path.to.module`), it will import the
-module and call the `makeExtension` function to initiate your extension.
+When `markdown.Markdown` is passed the "name" of your extension as a dot notation string that does not include a class
+(for example `path.to.module`), it will import the module and call the `makeExtension` function to initiate your
+extension.
+
+## Registries
+
+The `markdown.util.Registry` class is a priority sorted registry which Markdown uses internally to determine the
+processing order of its various processors and patterns.
+
+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 the value
+of the "priority" parameter such that the item with the highest value will be processed first. The "name" is used to
+remove (`deregister`) and get items.
+
+A `Registry` instance is 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:
+
+```python
+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:
+
+```python
+someitem = SomeItem()
+registry.register(someitem, 'itemname', 20)
+# Contains the name
+assert 'itemname' in registry
+# Contains the item instance
+assert someitem in registry
+```
+
+`markdown.util.Registry` has the following methods:
+
+### `Registry.register(self, item, name, priority)` {: #registry.register data-toc-label='Registry.register'}
+
+: 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.
+
+### `Registry.deregister(self, name, strict=True)` {: #registry.deregister data-toc-label='Registry.deregister'}
+
+: Remove an item from the registry.
+
+ Set `strict=False` to fail silently.
+
+### `Registry.get_index_for_name(self, name)` {: #registry.get_index_for_name data-toc-label='Registry.get_index_for_name'}
+
+: Return the index of the given `name`.
-[Preprocessors]: #preprocessors
-[Inline Patterns]: #inlinepatterns
-[Treeprocessors]: #treeprocessors
-[Postprocessors]: #postprocessors
-[BlockParser]: #blockparser
+[match object]: https://docs.python.org/3/library/re.html#match-objects
+[bug tracker]: https://github.com/Python-Markdown/markdown/issues
+[extension source]: https://github.com/Python-Markdown/markdown/tree/master/markdown/extensions
+[tutorial]: https://github.com/Python-Markdown/markdown/wiki/Tutorial:-Writing-Extensions-for-Python-Markdown
[workingwithetree]: #working_with_et
[Integrating your code into Markdown]: #integrating_into_markdown
[extendMarkdown]: #extendmarkdown
@@ -807,8 +860,8 @@ module and call the `makeExtension` function to initiate your extension.
[makeExtension]: #makeextension
[ElementTree]: https://effbot.org/zone/element-index.htm
[Available Extensions]: index.md
-[Footnotes]: https://github.com/Python-Markdown/mdx_footnotes
-[Definition Lists]: https://github.com/Python-Markdown/mdx_definition_lists
+[Footnotes]: https://github.com/Python-Markdown/markdown/blob/master/markdown/extensions/footnotes.py
+[Definition Lists]: https://github.com/Python-Markdown/markdown/blob/master/markdown/extensions/definition_lists
[library reference]: ../reference.md
[setuptools]: https://packaging.python.org/key_projects/#setuptools
[Entry points]: https://setuptools.readthedocs.io/en/latest/setuptools.html#dynamic-discovery-of-services-and-plugins
diff --git a/docs/extensions/attr_list.md b/docs/extensions/attr_list.md
index d89eac2..4dcbc59 100644
--- a/docs/extensions/attr_list.md
+++ b/docs/extensions/attr_list.md
@@ -11,9 +11,9 @@ This extension is included in the standard Markdown library.
## Syntax
-The basic syntax was inspired by [Maruku][]'s Attribute Lists feature.
+The basic syntax was inspired by Maruku's Attribute Lists feature (see [web archive][Maruku]).
-[Maruku]: http://maruku.rubyforge.org/proposal.html#attribute_lists
+[Maruku]: https://web.archive.org/web/20170324172643/http://maruku.rubyforge.org/proposal.html
### The List
diff --git a/docs/extensions/fenced_code_blocks.md b/docs/extensions/fenced_code_blocks.md
index 9095057..0a584f7 100644
--- a/docs/extensions/fenced_code_blocks.md
+++ b/docs/extensions/fenced_code_blocks.md
@@ -34,6 +34,10 @@ part of the list.
Fenced Code Blocks are only supported at the document root level.
Therefore, they cannot be nested inside lists or blockquotes.
+ If you need to nest fenced code blocks, you may want to try the
+ the third party extension [SuperFences] instead.
+
+[SuperFences]: https://facelessuser.github.io/pymdown-extensions/extensions/superfences/
### Language
diff --git a/docs/reference.md b/docs/reference.md
index 44fd174..8153ebe 100644
--- a/docs/reference.md
+++ b/docs/reference.md
@@ -25,7 +25,7 @@ instance of the `markdown.Markdown` class and pass multiple documents through
it. If you do use a single instance though, make sure to call the `reset`
method appropriately ([see below](#convert)).
-### markdown.markdown(text [, **kwargs]) {: #markdown }
+### markdown.markdown(text [, **kwargs]) {: #markdown data-toc-label='markdown.markdown' }
The following options are available on the `markdown.markdown` function:
@@ -34,24 +34,20 @@ __text__{: #text }
: The source Unicode string. (required)
!!! note "Important"
- Python-Markdown expects **Unicode** as input (although
- some simple ASCII strings *may* work) and returns output as Unicode.
- Do not pass encoded strings to it! If your input is encoded, (e.g. as
- UTF-8), it is your responsibility to decode it. For example:
+ Python-Markdown expects a **Unicode** string as input (some simple ASCII binary strings *may* work only by
+ coincidence) and returns output as a Unicode string. Do not pass binary strings to it! If your input is
+ encoded, (e.g. as UTF-8), it is your responsibility to decode it. For example:
:::python
- input_file = codecs.open("some_file.txt", mode="r", encoding="utf-8")
- text = input_file.read()
+ with open("some_file.txt", "r", encoding="utf-8") as input_file:
+ text = input_file.read()
html = markdown.markdown(text)
If you want to write the output to disk, you *must* encode it yourself:
:::python
- output_file = codecs.open("some_file.html", "w",
- encoding="utf-8",
- errors="xmlcharrefreplace"
- )
- output_file.write(html)
+ with open("some_file.html", "w", encoding="utf-8", errors="xmlcharrefreplace") as output_file:
+ output_file.write(html)
__extensions__{: #extensions }
@@ -181,7 +177,7 @@ __tab_length__{: #tab_length }:
: Length of tabs in the source. Default: 4
-### `markdown.markdownFromFile (**kwargs)` {: #markdownFromFile }
+### `markdown.markdownFromFile (**kwargs)` {: #markdownFromFile data-toc-label='markdown.markdownFromFile' }
With a few exceptions, `markdown.markdownFromFile` accepts the same options as
`markdown.markdown`. It does **not** accept a `text` (or Unicode) string.
@@ -220,7 +216,7 @@ __encoding__{: #encoding }
meet your specific needs, it is suggested that you write your own code
to handle your encoding/decoding needs.
-### markdown.Markdown([**kwargs]) {: #Markdown }
+### markdown.Markdown([**kwargs]) {: #Markdown data-toc-label='markdown.Markdown' }
The same options are available when initializing the `markdown.Markdown` class
as on the [`markdown.markdown`](#markdown) function, except that the class does
@@ -233,7 +229,7 @@ string must be passed to one of two instance methods.
the thread they were created in. A single instance should not be accessed
from multiple threads.
-#### Markdown.convert(source) {: #convert }
+#### Markdown.convert(source) {: #convert data-toc-label='Markdown.convert' }
The `source` text must meet the same requirements as the [`text`](#text)
argument of the [`markdown.markdown`](#markdown) function.
@@ -248,8 +244,7 @@ html2 = md.convert(text2)
```
Depending on which options and/or extensions are being used, the parser may
-need its state reset between each call to `convert`, otherwise performance
-can degrade drastically:
+need its state reset between each call to `convert`.
```python
html1 = md.convert(text1)
@@ -263,7 +258,7 @@ To make this easier, you can also chain calls to `reset` together:
html3 = md.reset().convert(text3)
```
-#### Markdown.convertFile(**kwargs) {: #convertFile }
+#### Markdown.convertFile(**kwargs) {: #convertFile data-toc-label='Markdown.convertFile' }
The arguments of this method are identical to the arguments of the same
name on the `markdown.markdownFromFile` function ([`input`](#input),
diff --git a/docs/test_tools.md b/docs/test_tools.md
index c252086..3a83d8e 100644
--- a/docs/test_tools.md
+++ b/docs/test_tools.md
@@ -169,6 +169,6 @@ rules apply.
[unittest]: https://docs.python.org/3/library/unittest.html
[Perl]: https://daringfireball.net/projects/markdown/
[PHP]: http://michelf.com/projects/php-markdown/
-[PyTidyLib]: http://countergram.com/open-source/pytidylib/
+[PyTidyLib]: http://countergram.github.io/pytidylib/
[Contributing Guide]: contributing.md
[development environment]: contributing.md#development-environment
diff --git a/markdown/__meta__.py b/markdown/__meta__.py
index ead7c31..436ed0d 100644
--- a/markdown/__meta__.py
+++ b/markdown/__meta__.py
@@ -19,11 +19,6 @@ Copyright 2004 Manfred Stienstra (the original version)
License: BSD (see LICENSE.md for details).
"""
-try:
- import packaging.version
-except ImportError:
- from pkg_resources.extern import packaging
-
# __version_info__ format:
# (major, minor, patch, dev/alpha/beta/rc/final, #)
# (1, 1, 2, 'dev', 0) => "1.1.2.dev0"
@@ -31,25 +26,24 @@ except ImportError:
# (1, 2, 0, 'beta', 2) => "1.2b2"
# (1, 2, 0, 'rc', 4) => "1.2rc4"
# (1, 2, 0, 'final', 0) => "1.2"
-__version_info__ = (3, 2, 1, 'final', 0)
+__version_info__ = (3, 2, 2, 'final', 0)
-def _get_version(): # pragma: no cover
+def _get_version(version_info):
" Returns a PEP 440-compliant version number from version_info. "
- assert len(__version_info__) == 5
- assert __version_info__[3] in ('dev', 'alpha', 'beta', 'rc', 'final')
+ assert len(version_info) == 5
+ assert version_info[3] in ('dev', 'alpha', 'beta', 'rc', 'final')
- parts = 2 if __version_info__[2] == 0 else 3
- v = '.'.join(map(str, __version_info__[:parts]))
+ parts = 2 if version_info[2] == 0 else 3
+ v = '.'.join(map(str, version_info[:parts]))
- if __version_info__[3] == 'dev':
- v += '.dev' + str(__version_info__[4])
- elif __version_info__[3] != 'final':
+ if version_info[3] == 'dev':
+ v += '.dev' + str(version_info[4])
+ elif version_info[3] != 'final':
mapping = {'alpha': 'a', 'beta': 'b', 'rc': 'rc'}
- v += mapping[__version_info__[3]] + str(__version_info__[4])
+ v += mapping[version_info[3]] + str(version_info[4])
- # Ensure version is valid and normalized
- return str(packaging.version.Version(v))
+ return v
-__version__ = _get_version()
+__version__ = _get_version(__version_info__)
diff --git a/markdown/core.py b/markdown/core.py
index 6c7822c..e2c0d88 100644
--- a/markdown/core.py
+++ b/markdown/core.py
@@ -23,7 +23,6 @@ import codecs
import sys
import logging
import importlib
-import pkg_resources
from . import util
from .preprocessors import build_preprocessors
from .blockprocessors import build_block_parser
@@ -141,9 +140,8 @@ class Markdown:
Build extension from a string name, then return an instance.
First attempt to load an entry point. The string name must be registered as an entry point in the
- `markdown.extensions` group which points to a subclass of the `markdown.extensions.Extension` class. If
- multiple distributions have registered the same name, the first one found by `pkg_resources.iter_entry_points`
- is returned.
+ `markdown.extensions` group which points to a subclass of the `markdown.extensions.Extension` class.
+ If multiple distributions have registered the same name, the first one found is returned.
If no entry point is found, assume dot notation (`path.to.module:ClassName`). Load the specified class and
return an instance. If no class is specified, import the module and call a `makeExtension` function and return
@@ -151,7 +149,7 @@ class Markdown:
"""
configs = dict(configs)
- entry_points = [ep for ep in pkg_resources.iter_entry_points('markdown.extensions', ext_name)]
+ entry_points = [ep for ep in util.INSTALLED_EXTENSIONS if ep.name == ext_name]
if entry_points:
ext = entry_points[0].load()
return ext(**configs)
diff --git a/markdown/extensions/__init__.py b/markdown/extensions/__init__.py
index 010e310..4bc8e5f 100644
--- a/markdown/extensions/__init__.py
+++ b/markdown/extensions/__init__.py
@@ -75,15 +75,18 @@ class Extension:
md = args[0]
try:
self.extendMarkdown(md)
- except TypeError:
- # Must be a 2.x extension. Pass in a dumby md_globals.
- self.extendMarkdown(md, {})
- warnings.warn(
- "The 'md_globals' parameter of '{}.{}.extendMarkdown' is "
- "deprecated.".format(self.__class__.__module__, self.__class__.__name__),
- category=DeprecationWarning,
- stacklevel=2
- )
+ except TypeError as e:
+ if "missing 1 required positional argument" in str(e):
+ # Must be a 2.x extension. Pass in a dumby md_globals.
+ self.extendMarkdown(md, {})
+ warnings.warn(
+ "The 'md_globals' parameter of '{}.{}.extendMarkdown' is "
+ "deprecated.".format(self.__class__.__module__, self.__class__.__name__),
+ category=DeprecationWarning,
+ stacklevel=2
+ )
+ else:
+ raise
def extendMarkdown(self, md):
"""
diff --git a/markdown/extensions/codehilite.py b/markdown/extensions/codehilite.py
index c3f3257..ac45ede 100644
--- a/markdown/extensions/codehilite.py
+++ b/markdown/extensions/codehilite.py
@@ -139,16 +139,16 @@ class CodeHilite:
def _parseHeader(self):
"""
- Determines language of a code block from shebang line and whether said
- line should be removed or left in place. If the sheband line contains a
- path (even a single /) then it is assumed to be a real shebang line and
- left alone. However, if no path is given (e.i.: #!python or :::python)
- then it is assumed to be a mock shebang for language identifitation of
- a code fragment and removed from the code block prior to processing for
- code highlighting. When a mock shebang (e.i: #!python) is found, line
- numbering is turned on. When colons are found in place of a shebang
- (e.i.: :::python), line numbering is left in the current state - off
- by default.
+ Determines language of a code block from shebang line and whether the
+ said line should be removed or left in place. If the sheband line
+ contains a path (even a single /) then it is assumed to be a real
+ shebang line and left alone. However, if no path is given
+ (e.i.: #!python or :::python) then it is assumed to be a mock shebang
+ for language identification of a code fragment and removed from the
+ code block prior to processing for code highlighting. When a mock
+ shebang (e.i: #!python) is found, line numbering is turned on. When
+ colons are found in place of a shebang (e.i.: :::python), line
+ numbering is left in the current state - off by default.
Also parses optional list of highlight lines, like:
diff --git a/markdown/extensions/toc.py b/markdown/extensions/toc.py
index 8f2b13f..b6cdc73 100644
--- a/markdown/extensions/toc.py
+++ b/markdown/extensions/toc.py
@@ -15,9 +15,10 @@ License: [BSD](https://opensource.org/licenses/bsd-license.php)
from . import Extension
from ..treeprocessors import Treeprocessor
-from ..util import code_escape, parseBoolValue, AMP_SUBSTITUTE, HTML_PLACEHOLDER_RE
+from ..util import code_escape, parseBoolValue, AMP_SUBSTITUTE, HTML_PLACEHOLDER_RE, AtomicString
from ..postprocessors import UnescapePostprocessor
import re
+import html
import unicodedata
import xml.etree.ElementTree as etree
@@ -44,6 +45,18 @@ def unique(id, ids):
return id
+def get_name(el):
+ """Get title name."""
+
+ text = []
+ for c in el.itertext():
+ if isinstance(c, AtomicString):
+ text.append(html.unescape(c))
+ else:
+ text.append(c)
+ return ''.join(text).strip()
+
+
def stashedHTML2text(text, md, strip_entities=True):
""" Extract raw HTML from stash, reduce to plain text and swap with placeholder. """
def _html_sub(m):
@@ -253,7 +266,7 @@ class TocTreeprocessor(Treeprocessor):
self.set_level(el)
if int(el.tag[-1]) < self.toc_top or int(el.tag[-1]) > self.toc_bottom:
continue
- text = ''.join(el.itertext()).strip()
+ text = get_name(el)
# Do not override pre-existing ids
if "id" not in el.attrib:
diff --git a/markdown/test_tools.py b/markdown/test_tools.py
index a42b14b..be7bbf1 100644
--- a/markdown/test_tools.py
+++ b/markdown/test_tools.py
@@ -167,7 +167,7 @@ class LegacyTestCase(unittest.TestCase, metaclass=LegacyTestMeta):
arguments for all test files in the directory.
In addition, properties can be defined for each individual set of test files within
- the directory. The property should be given the name of the file wihtout the file
+ the directory. The property should be given the name of the file without the file
extension. Any spaces and dashes in the filename should be replaced with
underscores. The value of the property should be a `Kwargs` instance which
contains the keyword arguments that should be passed to `Markdown` for that
diff --git a/markdown/util.py b/markdown/util.py
index e7bc295..a8db7bd 100644
--- a/markdown/util.py
+++ b/markdown/util.py
@@ -27,6 +27,11 @@ import warnings
import xml.etree.ElementTree
from .pep562 import Pep562
+try:
+ from importlib import metadata
+except ImportError:
+ #
Markdown offers two styles of headers: Setext and atx.
Setext-style headers for <h1>
and <h2>
are created by
"underlining" with equal signs (=
) and hyphens (-
), respectively.
diff --git a/tests/basic/markdown-documentation-basics.txt b/tests/basic/markdown-documentation-basics.txt
index 6c5a6fd..6de671a 100644
--- a/tests/basic/markdown-documentation-basics.txt
+++ b/tests/basic/markdown-documentation-basics.txt
@@ -37,7 +37,7 @@ can [see the source for it by adding '.text' to the URL] [src].
A paragraph is simply one or more consecutive lines of text, separated
by one or more blank lines. (A blank line is any line that looks like a
blank line -- a line containing nothing spaces or tabs is considered
-blank.) Normal paragraphs should not be intended with spaces or tabs.
+blank.) Normal paragraphs should not be indented with spaces or tabs.
Markdown offers two styles of headers: *Setext* and *atx*.
Setext-style headers for `
&
in your example code needs to be escaped.)
A paragraph is simply one or more consecutive lines of text, separated by one or more blank lines. (A blank line is any line that looks like a blank line -- a line containing nothing but spaces or tabs is considered -blank.) Normal paragraphs should not be intended with spaces or tabs.
+blank.) Normal paragraphs should not be indented with spaces or tabs.The implication of the "one or more consecutive lines of text" rule is
that Markdown supports "hard-wrapped" text paragraphs. This differs
significantly from most other text-to-HTML formatters (including Movable
@@ -328,7 +328,7 @@ items in <p>
tags in the HTML output. For example, this input
</ul>
List items may consist of multiple paragraphs. Each subsequent -paragraph in a list item must be intended by either 4 spaces +paragraph in a list item must be indented by either 4 spaces or one tab:
1. This is a list item with two paragraphs. Lorem ipsum dolor
sit amet, consectetuer adipiscing elit. Aliquam hendrerit
diff --git a/tests/basic/markdown-syntax.txt b/tests/basic/markdown-syntax.txt
index fabec2e..38f6e78 100644
--- a/tests/basic/markdown-syntax.txt
+++ b/tests/basic/markdown-syntax.txt
@@ -186,7 +186,7 @@ and `&` in your example code needs to be escaped.)
A paragraph is simply one or more consecutive lines of text, separated
by one or more blank lines. (A blank line is any line that looks like a
blank line -- a line containing nothing but spaces or tabs is considered
-blank.) Normal paragraphs should not be intended with spaces or tabs.
+blank.) Normal paragraphs should not be indented with spaces or tabs.
The implication of the "one or more consecutive lines of text" rule is
that Markdown supports "hard-wrapped" text paragraphs. This differs
@@ -401,7 +401,7 @@ will turn into:
List items may consist of multiple paragraphs. Each subsequent
-paragraph in a list item must be intended by either 4 spaces
+paragraph in a list item must be indented by either 4 spaces
or one tab:
1. This is a list item with two paragraphs. Lorem ipsum dolor
diff --git a/tests/extensions/extra/markdown-syntax.html b/tests/extensions/extra/markdown-syntax.html
index 2b79d2d..cd7ba17 100644
--- a/tests/extensions/extra/markdown-syntax.html
+++ b/tests/extensions/extra/markdown-syntax.html
@@ -151,7 +151,7 @@ and &
in your example code needs to be escaped.)
A paragraph is simply one or more consecutive lines of text, separated
by one or more blank lines. (A blank line is any line that looks like a
blank line -- a line containing nothing but spaces or tabs is considered
-blank.) Normal paragraphs should not be intended with spaces or tabs.
+blank.) Normal paragraphs should not be indented with spaces or tabs.
The implication of the "one or more consecutive lines of text" rule is
that Markdown supports "hard-wrapped" text paragraphs. This differs
significantly from most other text-to-HTML formatters (including Movable
@@ -328,7 +328,7 @@ items in <p>
tags in the HTML output. For example, this input
</ul>
List items may consist of multiple paragraphs. Each subsequent -paragraph in a list item must be intended by either 4 spaces +paragraph in a list item must be indented by either 4 spaces or one tab:
1. This is a list item with two paragraphs. Lorem ipsum dolor
sit amet, consectetuer adipiscing elit. Aliquam hendrerit
diff --git a/tests/extensions/extra/markdown-syntax.txt b/tests/extensions/extra/markdown-syntax.txt
index fabec2e..38f6e78 100644
--- a/tests/extensions/extra/markdown-syntax.txt
+++ b/tests/extensions/extra/markdown-syntax.txt
@@ -186,7 +186,7 @@ and `&` in your example code needs to be escaped.)
A paragraph is simply one or more consecutive lines of text, separated
by one or more blank lines. (A blank line is any line that looks like a
blank line -- a line containing nothing but spaces or tabs is considered
-blank.) Normal paragraphs should not be intended with spaces or tabs.
+blank.) Normal paragraphs should not be indented with spaces or tabs.
The implication of the "one or more consecutive lines of text" rule is
that Markdown supports "hard-wrapped" text paragraphs. This differs
@@ -401,7 +401,7 @@ will turn into:
List items may consist of multiple paragraphs. Each subsequent
-paragraph in a list item must be intended by either 4 spaces
+paragraph in a list item must be indented by either 4 spaces
or one tab:
1. This is a list item with two paragraphs. Lorem ipsum dolor
diff --git a/tests/extensions/toc.html b/tests/extensions/toc.html
index 4936f0d..1f06b68 100644
--- a/tests/extensions/toc.html
+++ b/tests/extensions/toc.html
@@ -135,7 +135,7 @@ and &
in your example code needs to be escaped.)
A paragraph is simply one or more consecutive lines of text, separated
by one or more blank lines. (A blank line is any line that looks like a
blank line -- a line containing nothing but spaces or tabs is considered
-blank.) Normal paragraphs should not be intended with spaces or tabs.
+blank.) Normal paragraphs should not be indented with spaces or tabs.
The implication of the "one or more consecutive lines of text" rule is
that Markdown supports "hard-wrapped" text paragraphs. This differs
significantly from most other text-to-HTML formatters (including Movable
@@ -309,7 +309,7 @@ items in <p>
tags in the HTML output. For example, this input
</ul>
List items may consist of multiple paragraphs. Each subsequent -paragraph in a list item must be intended by either 4 spaces +paragraph in a list item must be indented by either 4 spaces or one tab:
1. This is a list item with two paragraphs. Lorem ipsum dolor
sit amet, consectetuer adipiscing elit. Aliquam hendrerit
diff --git a/tests/extensions/toc.txt b/tests/extensions/toc.txt
index f71afd2..1a1de34 100644
--- a/tests/extensions/toc.txt
+++ b/tests/extensions/toc.txt
@@ -149,7 +149,7 @@ and `&` in your example code needs to be escaped.)
A paragraph is simply one or more consecutive lines of text, separated
by one or more blank lines. (A blank line is any line that looks like a
blank line -- a line containing nothing but spaces or tabs is considered
-blank.) Normal paragraphs should not be intended with spaces or tabs.
+blank.) Normal paragraphs should not be indented with spaces or tabs.
The implication of the "one or more consecutive lines of text" rule is
that Markdown supports "hard-wrapped" text paragraphs. This differs
@@ -364,7 +364,7 @@ will turn into:
List items may consist of multiple paragraphs. Each subsequent
-paragraph in a list item must be intended by either 4 spaces
+paragraph in a list item must be indented by either 4 spaces
or one tab:
1. This is a list item with two paragraphs. Lorem ipsum dolor
diff --git a/tests/pl/Tests_2004/Markdown Documentation - Basics.html b/tests/pl/Tests_2004/Markdown Documentation - Basics.html
index d5bdbb2..342f0c1 100644
--- a/tests/pl/Tests_2004/Markdown Documentation - Basics.html
+++ b/tests/pl/Tests_2004/Markdown Documentation - Basics.html
@@ -29,7 +29,7 @@ can see the source for it by adding '.t
A paragraph is simply one or more consecutive lines of text, separated
by one or more blank lines. (A blank line is any line that looks like a
blank line -- a line containing nothing spaces or tabs is considered
-blank.) Normal paragraphs should not be intended with spaces or tabs.
+blank.) Normal paragraphs should not be indented with spaces or tabs.
Markdown offers two styles of headers: Setext and atx.
Setext-style headers for <h1>
and <h2>
are created by
diff --git a/tests/pl/Tests_2004/Markdown Documentation - Syntax.html b/tests/pl/Tests_2004/Markdown Documentation - Syntax.html
index 5c01306..7847793 100644
--- a/tests/pl/Tests_2004/Markdown Documentation - Syntax.html
+++ b/tests/pl/Tests_2004/Markdown Documentation - Syntax.html
@@ -186,7 +186,7 @@ and &
in your example code needs to be escaped.)
A paragraph is simply one or more consecutive lines of text, separated
by one or more blank lines. (A blank line is any line that looks like a
blank line -- a line containing nothing but spaces or tabs is considered
-blank.) Normal paragraphs should not be intended with spaces or tabs.
+blank.) Normal paragraphs should not be indented with spaces or tabs.
The implication of the "one or more consecutive lines of text" rule is
that Markdown supports "hard-wrapped" text paragraphs. This differs
@@ -414,7 +414,7 @@ items in <p>
tags in the HTML output. For example, this input
List items may consist of multiple paragraphs. Each subsequent -paragraph in a list item must be intended by either 4 spaces +paragraph in a list item must be indented by either 4 spaces or one tab:
1. This is a list item with two paragraphs. Lorem ipsum dolor
diff --git a/tests/pl/Tests_2007/Markdown Documentation - Basics.html b/tests/pl/Tests_2007/Markdown Documentation - Basics.html
index d5bdbb2..342f0c1 100644
--- a/tests/pl/Tests_2007/Markdown Documentation - Basics.html
+++ b/tests/pl/Tests_2007/Markdown Documentation - Basics.html
@@ -29,7 +29,7 @@ can see the source for it by adding '.t
A paragraph is simply one or more consecutive lines of text, separated
by one or more blank lines. (A blank line is any line that looks like a
blank line -- a line containing nothing spaces or tabs is considered
-blank.) Normal paragraphs should not be intended with spaces or tabs.
+blank.) Normal paragraphs should not be indented with spaces or tabs.
Markdown offers two styles of headers: Setext and atx.
Setext-style headers for <h1>
and <h2>
are created by
diff --git a/tests/pl/Tests_2007/Markdown Documentation - Syntax.html b/tests/pl/Tests_2007/Markdown Documentation - Syntax.html
index 5c01306..7847793 100644
--- a/tests/pl/Tests_2007/Markdown Documentation - Syntax.html
+++ b/tests/pl/Tests_2007/Markdown Documentation - Syntax.html
@@ -186,7 +186,7 @@ and &
in your example code needs to be escaped.)
A paragraph is simply one or more consecutive lines of text, separated
by one or more blank lines. (A blank line is any line that looks like a
blank line -- a line containing nothing but spaces or tabs is considered
-blank.) Normal paragraphs should not be intended with spaces or tabs.
+blank.) Normal paragraphs should not be indented with spaces or tabs.
The implication of the "one or more consecutive lines of text" rule is
that Markdown supports "hard-wrapped" text paragraphs. This differs
@@ -414,7 +414,7 @@ items in <p>
tags in the HTML output. For example, this input
List items may consist of multiple paragraphs. Each subsequent -paragraph in a list item must be intended by either 4 spaces +paragraph in a list item must be indented by either 4 spaces or one tab:
1. This is a list item with two paragraphs. Lorem ipsum dolor
diff --git a/tests/test_apis.py b/tests/test_apis.py
index 39236f2..6564c66 100644
--- a/tests/test_apis.py
+++ b/tests/test_apis.py
@@ -337,7 +337,7 @@ class RegistryTests(unittest.TestCase):
def testRegistryDelItem(self):
r = markdown.util.Registry()
r.register(Item('a'), 'a', 20)
- with self.assertRaises(TypeError):
+ with self.assertRaises(KeyError):
del r[0]
# TODO: restore this when deprecated __del__ is removed.
# with self.assertRaises(TypeError):
@@ -352,7 +352,7 @@ class RegistryTests(unittest.TestCase):
self.assertEqual(list(r), ['a', 'c'])
del r['a']
self.assertEqual(list(r), ['c'])
- with self.assertRaises(TypeError):
+ with self.assertRaises(KeyError):
del r['badname']
del r['c']
self.assertEqual(list(r), [])
diff --git a/tests/test_meta.py b/tests/test_meta.py
new file mode 100644
index 0000000..10a2d33
--- /dev/null
+++ b/tests/test_meta.py
@@ -0,0 +1,24 @@
+import unittest
+from markdown.__meta__ import _get_version, __version__
+
+
+class TestVersion(unittest.TestCase):
+
+ def test_get_version(self):
+ """Test that _get_version formats __version_info__ as required by PEP 440."""
+
+ self.assertEqual(_get_version((1, 1, 2, 'dev', 0)), "1.1.2.dev0")
+ self.assertEqual(_get_version((1, 1, 2, 'alpha', 1)), "1.1.2a1")
+ self.assertEqual(_get_version((1, 2, 0, 'beta', 2)), "1.2b2")
+ self.assertEqual(_get_version((1, 2, 0, 'rc', 4)), "1.2rc4")
+ self.assertEqual(_get_version((1, 2, 0, 'final', 0)), "1.2")
+
+ def test__version__IsValid(self):
+ """Test that __version__ is valid and normalized."""
+
+ try:
+ import packaging.version
+ except ImportError:
+ from pkg_resources.extern import packaging
+
+ self.assertEqual(__version__, str(packaging.version.Version(__version__)))
diff --git a/tests/test_syntax/extensions/test_toc.py b/tests/test_syntax/extensions/test_toc.py
index 5b9ad92..3fc9780 100644
--- a/tests/test_syntax/extensions/test_toc.py
+++ b/tests/test_syntax/extensions/test_toc.py
@@ -27,6 +27,28 @@ class TestTOC(TestCase):
# TODO: Move the rest of the TOC tests here.
+ def test_escaped_code(self):
+ self.assertMarkdownRenders(
+ self.dedent(
+ '''
+ [TOC]
+
+ # ``
+ '''
+ ),
+ self.dedent(
+ '''
+
+
+ - <test>
+
+
+ <test>
+ '''
+ ),
+ extensions=['toc']
+ )
+
def test_escaped_char_in_id(self):
self.assertMarkdownRenders(
r'# escaped\_character',
diff --git a/tox.ini b/tox.ini
index 14dcc21..ea153e4 100644
--- a/tox.ini
+++ b/tox.ini
@@ -1,5 +1,5 @@
[tox]
-envlist = py35, py36, py37, py38, pypy3, flake8, checkspelling, pep517check
+envlist = py35, py36, py37, py38, pypy3, flake8, checkspelling, pep517check, checklinks
isolated_build = True
min_verison = 1.9
@@ -20,6 +20,11 @@ deps =
mkdocs_nature
commands = {toxinidir}/checkspelling.sh
+[testenv:checklinks]
+whitelist_externals = markdown-link-check
+deps =
+commands = {toxinidir}/checklinks.sh
+
[testenv:pep517check]
deps = pep517
commands = python -m pep517.check {toxinidir}
--
2.34.1