diff options
-rw-r--r-- | .spell-dict | 1 | ||||
-rw-r--r-- | docs/cli.md | 34 | ||||
-rw-r--r-- | docs/extensions/abbreviations.md | 4 | ||||
-rw-r--r-- | docs/extensions/admonition.md | 11 | ||||
-rw-r--r-- | docs/extensions/api.md | 77 | ||||
-rw-r--r-- | docs/extensions/attr_list.md | 4 | ||||
-rw-r--r-- | docs/extensions/code_hilite.md | 4 | ||||
-rw-r--r-- | docs/extensions/definition_lists.md | 4 | ||||
-rw-r--r-- | docs/extensions/extra.md | 2 | ||||
-rw-r--r-- | docs/extensions/fenced_code_blocks.md | 4 | ||||
-rw-r--r-- | docs/extensions/footnotes.md | 5 | ||||
-rw-r--r-- | docs/extensions/index.md | 48 | ||||
-rw-r--r-- | docs/extensions/meta_data.md | 6 | ||||
-rw-r--r-- | docs/extensions/nl2br.md | 6 | ||||
-rw-r--r-- | docs/extensions/sane_lists.md | 4 | ||||
-rw-r--r-- | docs/extensions/smart_strong.md | 13 | ||||
-rw-r--r-- | docs/extensions/smarty.md | 6 | ||||
-rw-r--r-- | docs/extensions/tables.md | 4 | ||||
-rw-r--r-- | docs/extensions/toc.md | 6 | ||||
-rw-r--r-- | docs/extensions/wikilinks.md | 4 | ||||
-rw-r--r-- | docs/reference.md | 57 | ||||
-rw-r--r-- | markdown/core.py | 86 | ||||
-rw-r--r-- | markdown/extensions/extra.py | 14 | ||||
-rwxr-xr-x | setup.py | 19 | ||||
-rw-r--r-- | tests/test_apis.py | 49 | ||||
-rw-r--r-- | tests/test_extensions.py | 38 | ||||
-rw-r--r-- | tests/test_legacy.py | 69 |
27 files changed, 288 insertions, 291 deletions
diff --git a/.spell-dict b/.spell-dict index ab6b7a5..139a195 100644 --- a/.spell-dict +++ b/.spell-dict @@ -86,6 +86,7 @@ sanitizer sanitizers Sauder schemeless +setuptools Sergej serializer serializers diff --git a/docs/cli.md b/docs/cli.md index dbac2c4..35c77b4 100644 --- a/docs/cli.md +++ b/docs/cli.md @@ -118,16 +118,26 @@ Using Extensions To load a Python-Markdown extension from the command line use the `-x` (or `--extension`) option. The extension module must be on your `PYTHONPATH` (see the [Extension API](extensions/api.md) for details). The extension can -then be invoked by the name of that module using Python's dot syntax: +then be invoked by the name assigned to an entry point or using Python's dot +notation to point to an extension + +For example, to load an extension with the assigned entry point name `myext`, +run the following command: + +```bash +python -m markdown -x myext input.txt +``` + +And to load an extension with Python's dot notation: ```bash -python -m markdown -x path.to.module input.txt +python -m markdown -x path.to.module:MyExtClass input.txt ``` To load multiple extensions, specify an `-x` option for each extension: ```bash -python -m markdown -x markdown.extensions.footnotes -x markdown.extensions.codehilite input.txt +python -m markdown -x myext -x path.to.module:MyExtClass input.txt ``` If the extension supports configuration options (see the documentation for the @@ -135,7 +145,7 @@ extension you are using to determine what settings it supports, if any), you can pass them in as well: ```bash -python -m markdown -x markdown.extensions.footnotes -c config.yml input.txt +python -m markdown -x myext -c config.yml input.txt ``` The `-c` (or `--extension_configs`) option accepts a file name. The file must be @@ -145,25 +155,19 @@ map to a Python Dictionary in the format required by the the file `config.yaml` referenced in the above example might look like this: ```yaml -markdown.extensions.footnotes: - PLACE_MARKER: ~~~~~~~~ - UNIQUE_IDS: True +myext: + option1: 'value1' + option2: True ``` Note that while the `--extension_configs` option does specify the -"markdown.extensions.footnotes" extension, you still need to load the extension -with the `-x` option, or the configuration for that extension will be ignored. +`myext` extension, you still need to load the extension with the `-x` option, +or the configuration for that extension will be ignored. The `--extension_configs` option will only support YAML configuration files if [PyYAML] is installed on your system. JSON should work with no additional dependencies. The format of your configuration file is automatically detected. -!!!warning - The previously documented method of appending the extension configuration - options as a string to the extension name will be deprecated in - Python-Markdown version 2.6. The `--extension_configs` option should be used - instead. See the [2.5 release notes] for more information. - [ec]: reference.html#extension_configs [YAML]: http://yaml.org/ [JSON]: http://json.org/ diff --git a/docs/extensions/abbreviations.md b/docs/extensions/abbreviations.md index d580d56..eed8788 100644 --- a/docs/extensions/abbreviations.md +++ b/docs/extensions/abbreviations.md @@ -39,7 +39,7 @@ is maintained by the <abbr title="World Wide Web Consortium">W3C</abbr>.</p> Usage ----- -See [Extensions](index.md) for general extension usage, specify `markdown.extensions.abbr` -as the name of the extension. +See [Extensions](index.md) for general extension usage. Use `abbr` as the name +of the extension. This extension does not accept any special configuration options. diff --git a/docs/extensions/admonition.md b/docs/extensions/admonition.md index 26e6299..a6a8398 100644 --- a/docs/extensions/admonition.md +++ b/docs/extensions/admonition.md @@ -78,7 +78,14 @@ rST suggests the following `types`, but you're free to use whatever you want: Styling ------- -There is no CSS included as part of this extension. Look up the default -[Sphinx][sphinx] theme if you need inspiration. +There is no CSS included as part of this extension. Check out the default +[Sphinx][sphinx] theme for inspiration. [sphinx]: http://sphinx.pocoo.org/ + +## Usage + +See [Extensions](index.md) for general extension usage. Use `admonition` as the +name of the extension. + +This extension does not accept any special configuration options. diff --git a/docs/extensions/api.md b/docs/extensions/api.md index 3d8cfff..cba4ea7 100644 --- a/docs/extensions/api.md +++ b/docs/extensions/api.md @@ -639,7 +639,7 @@ following methods available to assist in working with configuration settings: Sets multiple configuration settings given a dict of key/value pairs. -### `makeExtension` {: #makeextension } +### 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 @@ -649,36 +649,70 @@ For example: ```python import markdown -import myextension -myext = myextension.MyExtension(option='value') -md = markdown.Markdown(extensions=[myext]) +from path.to.module import MyExtention +md = markdown.Markdown(extensions=[MyExtension(option='value')]) ``` -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). +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). -The "name" of your extension must be a string consisting of the importable path to -your module using Python's dot notation. Therefore, if you are providing a library -to your users and would like to include a custom markdown extension within your -library, that extension would be named `"mylib.mdext.myext"` where `mylib/mdext/myext.py` -contains the extension and the `mylib` directory is on the PYTHONPATH. +#### 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: + +```python +from setuptools import setup + +setup( + # ... + entry_points={ + 'markdown.extensions': ['myextension = path.to.module:MyExtension'] + } +) +``` + +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=['myextention']) +``` + +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]. + +#### 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 +be at the end of the name and be separated by a colon from the module. -The string can also include the name of the class separated by a colon. Therefore, if you were to import the class like this: ```python -from path.to.module import SomeExtensionClass +from path.to.module import MyExtention ``` -Then the named extension would comprise this string: +Then the extension can be loaded as follows: ```python -"path.to.module:SomeExtensionClass" +markdown.markdown(text, extensions=['path.to.module:MyExtention']) ``` 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. +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 @@ -697,9 +731,9 @@ def makeExtension(**kwargs): return MyExtension(**kwargs) ``` -When Markdown is passed the "name" of your extension as a dot notation string, -it will import the module and call the `makeExtension` function to initiate your -extension. +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. [Preprocessors]: #preprocessors [Inline Patterns]: #inlinepatterns @@ -718,3 +752,6 @@ extension. [Footnotes]: https://github.com/Python-Markdown/mdx_footnotes [Definition Lists]: https://github.com/Python-Markdown/mdx_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 +[Packaging and Distributing Projects]: https://packaging.python.org/tutorials/distributing-packages/ diff --git a/docs/extensions/attr_list.md b/docs/extensions/attr_list.md index 7b2e19f..9fc8b6e 100644 --- a/docs/extensions/attr_list.md +++ b/docs/extensions/attr_list.md @@ -92,7 +92,7 @@ The above results in the following output: ## Usage -See [Extensions](index.md) for general extension usage, specify -`markdown.extensions.attr_list` as the name of the extension. +See [Extensions](index.md) for general extension usage. Use `attr_list` as the +name of the extension. This extension does not accept any special configuration options. diff --git a/docs/extensions/code_hilite.md b/docs/extensions/code_hilite.md index 6490dcc..552a82d 100644 --- a/docs/extensions/code_hilite.md +++ b/docs/extensions/code_hilite.md @@ -168,8 +168,8 @@ Lets see the source for that: ## Usage -See [Extensions](index.md) for general extension usage, specify -`markdown.extensions.codehilite` as the name of the extension. +See [Extensions](index.md) for general extension usage. Use `codehilite` as the +name of the extension. See the [Library Reference](../reference.md#extensions) for information about configuring extensions. diff --git a/docs/extensions/definition_lists.md b/docs/extensions/definition_lists.md index e9f8984..0d42fd0 100644 --- a/docs/extensions/definition_lists.md +++ b/docs/extensions/definition_lists.md @@ -46,7 +46,7 @@ the family Rosaceae.</dd> Usage ----- -See [Extensions](index.md) for general extension usage, specify -`markdown.extensions.def_list` as the name of the extension. +See [Extensions](index.md) for general extension usage. Use `def_list` as the +name of the extension. This extension does not accept any special configuration options. diff --git a/docs/extensions/extra.md b/docs/extensions/extra.md index 0639d3d..3d9f374 100644 --- a/docs/extensions/extra.md +++ b/docs/extensions/extra.md @@ -26,7 +26,7 @@ From the Python interpreter: ```pycon >>> import markdown ->>> html = markdown.markdown(text, ['markdown.extensions.extra']) +>>> html = markdown.markdown(text, ['extra']) ``` There may be [additional extensions](index.md) that are distributed with diff --git a/docs/extensions/fenced_code_blocks.md b/docs/extensions/fenced_code_blocks.md index b7a657e..96fe786 100644 --- a/docs/extensions/fenced_code_blocks.md +++ b/docs/extensions/fenced_code_blocks.md @@ -110,7 +110,7 @@ The lines can be specified with PHP Extra's syntax: ## Usage -See [Extensions](index.md) for general extension usage, specify -`markdown.extensions.fenced_code` as the name of the extension. +See [Extensions](index.md) for general extension usage. Use `fenced_code` as +the name of the extension. This extension does not accept any special configuration options. diff --git a/docs/extensions/footnotes.md b/docs/extensions/footnotes.md index aaff184..4df12a1 100644 --- a/docs/extensions/footnotes.md +++ b/docs/extensions/footnotes.md @@ -63,8 +63,8 @@ is indented consistently and any errors are more easily discernible by the autho Usage ----- -See [Extensions](index.md) for general extension usage, specify -`markdown.extensions.footnotes` as the name of the extension. +See [Extensions](index.md) for general extension usage. Use `footnotes` as the +name of the extension. See the [Library Reference](../reference.md#extensions) for information about configuring extensions. @@ -90,4 +90,3 @@ The following options are provided to configure the output: The text string for the `title` HTML attribute of the footnote definition link. `%d` will be replaced by the footnote number. Defaults to `Jump back to footnote %d in the text` - diff --git a/docs/extensions/index.md b/docs/extensions/index.md index 34f8084..3d98760 100644 --- a/docs/extensions/index.md +++ b/docs/extensions/index.md @@ -1,7 +1,6 @@ title: Extensions -Available Extensions -==================== +# Extensions Python Markdown offers a flexible extension mechanism, which makes it possible to change and/or extend the behavior of the parser without having to edit the @@ -10,7 +9,7 @@ actual source files. To use an extension, pass it to markdown with the `extensions` keyword. ```python -markdown.markdown(some_text, extensions=[MyExtension(), 'path.to.my.ext', 'markdown.extensions.footnotes']) +markdown.markdown(some_text, extensions=[MyExtClass(), 'myext', 'path.to.my.ext:MyExtClass']) ``` See the [Library Reference](../reference.md#extensions) for more details. @@ -18,7 +17,7 @@ See the [Library Reference](../reference.md#extensions) for more details. From the command line, specify an extension with the `-x` option. ```bash -python -m markdown -x markdown.extensions.footnotes -x markdown.extensions.tables input.txt > output.html +python -m markdown -x myext -x path.to.module:MyExtClass input.txt > output.html ``` See the [Command Line docs](../cli.md) or use the `--help` option for more details. @@ -34,27 +33,26 @@ The extensions listed below are included with (at least) the most recent release and are officially supported by Python-Markdown. Any documentation is maintained here and all bug reports should be made to the project. If you have a typical install of Python-Markdown, these extensions are already -available to you using the "name" listed in the second column below. - -Extension | "Name" ------------------------------------- | --------------- -[Extra] | `markdown.extensions.extra` - [Abbreviations][] | `markdown.extensions.abbr` - [Attribute Lists][] | `markdown.extensions.attr_list` - [Definition Lists][] | `markdown.extensions.def_list` - [Fenced Code Blocks][] | `markdown.extensions.fenced_code` - [Footnotes][] | `markdown.extensions.footnotes` - [Tables][] | `markdown.extensions.tables` - [Smart Strong][] | `markdown.extensions.smart_strong` -[Admonition][] | `markdown.extensions.admonition` -[CodeHilite][] | `markdown.extensions.codehilite` -[HeaderId] | `markdown.extensions.headerid` -[Meta-Data] | `markdown.extensions.meta` -[New Line to Break] | `markdown.extensions.nl2br` -[Sane Lists] | `markdown.extensions.sane_lists` -[SmartyPants] | `markdown.extensions.smarty` -[Table of Contents] | `markdown.extensions.toc` -[WikiLinks] | `markdown.extensions.wikilinks` +available to you using the "Entry Point" name listed in the second column below. + +Extension | Entry Point | Dot Notation +------------------------------------ | -------------- | ------------ +[Extra] | `extra` | `markdown.extensions.extra` + [Abbreviations][] | `abbr` | `markdown.extensions.abbr` + [Attribute Lists][] | `attr_list` | `markdown.extensions.attr_list` + [Definition Lists][] | `def_list` | `markdown.extensions.def_list` + [Fenced Code Blocks][] | `fenced_code` | `markdown.extensions.fenced_code` + [Footnotes][] | `footnotes` | `markdown.extensions.footnotes` + [Tables][] | `tables` | `markdown.extensions.tables` + [Smart Strong][] | `smart_strong` | `markdown.extensions.smart_strong` +[Admonition][] | `admonition` | `markdown.extensions.admonition` +[CodeHilite][] | `codehilite` | `markdown.extensions.codehilite` +[Meta-Data] | `meta` | `markdown.extensions.meta` +[New Line to Break] | `nl2br` | `markdown.extensions.nl2br` +[Sane Lists] | `sane_lists` | `markdown.extensions.sane_lists` +[SmartyPants] | `smarty` | `markdown.extensions.smarty` +[Table of Contents] | `toc` | `markdown.extensions.toc` +[WikiLinks] | `wikilinks` | `markdown.extensions.wikilinks` [Extra]: extra.md [Abbreviations]: abbreviations.md diff --git a/docs/extensions/meta_data.md b/docs/extensions/meta_data.md index 36d5e7a..29f3e0b 100644 --- a/docs/extensions/meta_data.md +++ b/docs/extensions/meta_data.md @@ -57,8 +57,8 @@ by Markdown. Usage ----- -See [Extensions](index.md) for general extension usage, specify -`markdown.extensions.meta` as the name of the extension. +See [Extensions](index.md) for general extension usage. Use `meta` as the name +of the extension. Accessing the Meta-Data ----------------------- @@ -67,7 +67,7 @@ The meta-data is made available as a python Dict in the `Meta` attribute of an instance of the Markdown class. For example, using the above document: ```pycon ->>> md = markdown.Markdown(extensions = ['markdown.extensions.meta']) +>>> md = markdown.Markdown(extensions = ['meta']) >>> html = md.convert(text) >>> # Meta-data has been stripped from output >>> print html diff --git a/docs/extensions/nl2br.md b/docs/extensions/nl2br.md index 4f5e611..8c53d33 100644 --- a/docs/extensions/nl2br.md +++ b/docs/extensions/nl2br.md @@ -20,7 +20,7 @@ Example ... Line 1 ... Line 2 ... """ ->>> html = markdown.markdown(text, extensions=['markdown.extensions.nl2br']) +>>> html = markdown.markdown(text, extensions=['nl2br']) >>> print html <p>Line 1<br /> Line 2</p> @@ -29,7 +29,7 @@ Line 2</p> Usage ----- -See [Extensions](index.md) for general extension usage, specify -`markdown.extensions.nl2br` as the name of the extension. +See [Extensions](index.md) for general extension usage. Use `nl2br` as the name +of the extension. This extension does not accept any special configuration options. diff --git a/docs/extensions/sane_lists.md b/docs/extensions/sane_lists.md index 49a7a85..81f8d84 100644 --- a/docs/extensions/sane_lists.md +++ b/docs/extensions/sane_lists.md @@ -71,7 +71,7 @@ In all other ways, Sane Lists should behave as normal Markdown lists. Usage ----- -See [Extensions](index.md) for general extension usage, specify -`markdown.extensions.sane_lists` as the name of the extension. +See [Extensions](index.md) for general extension usage. Use `sane_lists` as the +name of the extension. This extension does not accept any special configuration options. diff --git a/docs/extensions/smart_strong.md b/docs/extensions/smart_strong.md index 1fb3c68..65c88d6 100644 --- a/docs/extensions/smart_strong.md +++ b/docs/extensions/smart_strong.md @@ -19,21 +19,18 @@ Example ```pycon >>> import markdown ->>> markdown.markdown('Text with double__underscore__words.', \ - extensions=['markdown.extensions.smart_strong']) +>>> markdown.markdown('Text with double__underscore__words.', extensions=['smart_strong']) u'<p>Text with double__underscore__words.</p>' ->>> markdown.markdown('__Strong__ still works.', \ - extensions=['markdown.extensions.smart_strong']) +>>> markdown.markdown('__Strong__ still works.', extensions=['smart_strong']) u'<p><strong>Strong</strong> still works.</p>' ->>> markdown.markdown('__this__works__too__.', \ - extensions=['markdown.extensions.smart_strong']) +>>> markdown.markdown('__this__works__too__.', extensions=['smart_strong']) u'<p><strong>this__works__too</strong>.</p>' ``` Usage ----- -See [Extensions](index.md) for general extension usage, specify -`markdown.extensions.smart_strong` as the name of the extension. +See [Extensions](index.md) for general extension usage. Use `smart_strong` as +the name of the extension. This extension does not accept any special configuration options. diff --git a/docs/extensions/smarty.md b/docs/extensions/smarty.md index 9353d9b..109d78c 100644 --- a/docs/extensions/smarty.md +++ b/docs/extensions/smarty.md @@ -27,7 +27,7 @@ the German language: ```python extension_configs = { - 'markdown.extensions.smarty': { + 'smarty': { 'substitutions': { 'left-single-quote': '‚', # sb is not a typo! 'right-single-quote': '‘', @@ -53,8 +53,8 @@ extension_configs = { Usage ----- -See [Extensions](index.md) for general extension usage, specify -`markdown.extensions.smarty` as the name of the extension. +See [Extensions](index.md) for general extension usage. Use `smarty` as the +name of the extension. See the [Library Reference](../reference.md#extensions) for information about configuring extensions. diff --git a/docs/extensions/tables.md b/docs/extensions/tables.md index 2bea470..59693f0 100644 --- a/docs/extensions/tables.md +++ b/docs/extensions/tables.md @@ -52,7 +52,7 @@ will be rendered as: Usage ----- -See [Extensions](index.md) for general extension usage, specify -`markdown.extensions.tables` as the name of the extension. +See [Extensions](index.md) for general extension usage. Use `tables` as the +name of the extension. This extension does not accept any special configuration options. diff --git a/docs/extensions/toc.md b/docs/extensions/toc.md index a1c583f..e358132 100644 --- a/docs/extensions/toc.md +++ b/docs/extensions/toc.md @@ -65,7 +65,7 @@ This allows one to insert the Table of Contents elsewhere in their page template. For example: ```pycon ->>> md = markdown.Markdown(extensions=['markdown.extensions.toc']) +>>> md = markdown.Markdown(extensions=['toc']) >>> html = md.convert(text) >>> page = render_some_template(context={'body': html, 'toc': md.toc}) ``` @@ -73,8 +73,8 @@ template. For example: Usage ----- -See [Extensions](index.md) for general extension usage, specify `markdown.extensions.toc` -as the name of the extension. +See [Extensions](index.md) for general extension usage. Use `toc` as the name +of the extension. See the [Library Reference](../reference.md#extensions) for information about configuring extensions. diff --git a/docs/extensions/wikilinks.md b/docs/extensions/wikilinks.md index a46265a..dd82a96 100644 --- a/docs/extensions/wikilinks.md +++ b/docs/extensions/wikilinks.md @@ -46,8 +46,8 @@ becomes ## Usage -See [Extensions](index.md) for general extension usage, specify -`markdown.extensions.wikilinks` as the name of the extension. +See [Extensions](index.md) for general extension usage. Use `wikilinks` as the +name of the extension. See the [Library Reference](../reference.md#extensions) for information about configuring extensions. diff --git a/docs/reference.md b/docs/reference.md index 55fb501..1d73439 100644 --- a/docs/reference.md +++ b/docs/reference.md @@ -67,7 +67,7 @@ __extensions__{: #extensions } of extension names. :::python - extensions=[MyExtension(), 'path.to.my.ext'] + extensions=[MyExtClass(), 'myext', 'path.to.my.ext:MyExtClass'] !!! note The preferred method is to pass in an instance of an extension. Strings @@ -81,37 +81,48 @@ __extensions__{: #extensions } :::python from markdown.extensions import Extension - class MyExtension(Extension): + class MyExtClass(Extension): # define your extension here... - markdown.markdown(text, extensions=[MyExtension(option='value')]) + markdown.markdown(text, extensions=[MyExtClass(option='value')]) - If an extension name is provided as a string, the extension must be - importable as a python module on your PYTHONPATH. Python's dot notation is - required. Therefore, to import the 'extra' extension, one would do - `extensions=['markdown.extensions.extra']` + If an extension name is provided as a string, the string must either be the + registered entry point of any installed extension or the importable path + using Python's dot notation. - Additionally, a Class may be specified in the name. The class must be at the - end of the name and be separated by a colon from the module. + See the documentation specific to an extension for the string name assigned + to an extension as an entry point. Simply include the defined name as + a string in the list of extensions. For example, if an extension has the + name `myext` assigned to it and the extension is properly installed, then + do the following: + + :::python + markdown.markdown(text, extensions=['myext']) + + 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: :::python - from path.to.module import SomeExtensionClass + from path.to.module import MyExtClass - Then the named extension would comprise this string: + Then load the extension as follows: :::python - "path.to.module:SomeExtensionClass" + markdown.markdown(text, extensions=['path.to.module:MyExtClass']) - !!! note - You should only need to specify the class name if more than one extension - is defined within the same module. The extensions that come with - Python-Markdown do *not* need to have the class name specified. However, - doing so will not effect the behavior of the parser. + If only one extension is defined within a module and the module includes a + `makeExtension` function which returns an instance of the extension, then + the class name is not necessary. For example, in that case one could do + `extensions=['path.to.module']`. Check the documentation for a specific + extension to determine if it supports this feature. - When loading an extension by name (as a string), you may pass in - configuration settings to the extension using the + When loading an extension by name (as a string), you can only pass in + configuration settings to the extension by using the [`extension_configs`](#extension_configs) keyword. !!! seealso "See Also" @@ -144,6 +155,14 @@ __extension_configs__{: #extension_configs } } } + When specifying the extension name, be sure to use the exact same + string as is used in the [extensions](#extensions) keyword to load the + extension. Otherwise, the configuration settings will not be applied to + the extension. In other words, you cannot use the entry point in on + place and Python dot notation in the other. While both may be valid for + a given extension, they will not be recognized as being the same + extension by Markdown. + See the documentation specific to the extension you are using for help in specifying configuration settings for that extension. diff --git a/markdown/core.py b/markdown/core.py index 7d9d839..ce5c333 100644 --- a/markdown/core.py +++ b/markdown/core.py @@ -2,9 +2,9 @@ from __future__ import absolute_import from __future__ import unicode_literals import codecs import sys -import warnings import logging import importlib +import pkg_resources from . import util from .preprocessors import build_preprocessors from .blockprocessors import build_block_parser @@ -49,10 +49,11 @@ class Markdown(object): Keyword arguments: * extensions: A list of extensions. - If they are of type string, the module mdx_name.py will be loaded. - If they are a subclass of markdown.Extension, they will be used - as-is. - * extension_configs: Configuration settingis for extensions. + If an item is an instance of a subclass of `markdown.extension.Extension`, the instance will be used + as-is. If an item is of type string, first an entry point will be loaded. If that fails, the string is + assumed to use Python dot notation (`path.to.module:ClassName`) to load a markdown.Extension subclass. If + no class is specified, then a `makeExtension` function is called within the specified module. + * extension_configs: Configuration settings for extensions. * output_format: Format of output. Supported formats are: * "xhtml1": Outputs XHTML 1.x. Default. * "xhtml5": Outputs XHTML style tags of HTML 5 @@ -108,8 +109,8 @@ class Markdown(object): Keyword arguments: * extensions: A list of extensions, which can either - be strings or objects. See the docstring on Markdown. - * configs: A dictionary mapping module names to config options. + be strings or objects. + * configs: A dictionary mapping extension names to config options. """ for ext in extensions: @@ -130,72 +131,37 @@ class Markdown(object): def build_extension(self, ext_name, configs): """ - Build extension by name, then return the module. + 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. + 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 + the Extension instance returned by that function. + """ configs = dict(configs) + entry_points = [ep for ep in pkg_resources.iter_entry_points('markdown.extensions', ext_name)] + if entry_points: + ext = entry_points[0].load() + return ext(**configs) + # Get class name (if provided): `path.to.module:ClassName` ext_name, class_name = ext_name.split(':', 1) \ if ':' in ext_name else (ext_name, '') - # Try loading the extension first from one place, then another try: - # Assume string uses dot syntax (`path.to.some.module`) module = importlib.import_module(ext_name) logger.debug( 'Successfuly imported extension module "%s".' % ext_name ) - # For backward compat (until deprecation) - # check that this is an extension. - if ('.' not in ext_name and not (hasattr(module, 'makeExtension') or - (class_name and hasattr(module, class_name)))): - # We have a name conflict - # eg: extensions=['tables'] and PyTables is installed - raise ImportError - except ImportError: - # Preppend `markdown.extensions.` to name - module_name = '.'.join(['markdown.extensions', ext_name]) - try: - module = importlib.import_module(module_name) - logger.debug( - 'Successfuly imported extension module "%s".' % - module_name - ) - warnings.warn('Using short names for Markdown\'s builtin ' - 'extensions is deprecated. Use the ' - 'full path to the extension with Python\'s dot ' - 'notation (eg: "%s" instead of "%s"). The ' - 'current behavior will raise an error in version ' - '2.7. See the Release Notes for ' - 'Python-Markdown version 2.6 for more info.' % - (module_name, ext_name), - DeprecationWarning) - except ImportError: - # Preppend `mdx_` to name - module_name_old_style = '_'.join(['mdx', ext_name]) - try: - module = importlib.import_module(module_name_old_style) - logger.debug( - 'Successfuly imported extension module "%s".' % - module_name_old_style) - warnings.warn('Markdown\'s behavior of prepending "mdx_" ' - 'to an extension name is deprecated. ' - 'Use the full path to the ' - 'extension with Python\'s dot notation ' - '(eg: "%s" instead of "%s"). The current ' - 'behavior will raise an error in version 2.7. ' - 'See the Release Notes for Python-Markdown ' - 'version 2.6 for more info.' % - (module_name_old_style, ext_name), - DeprecationWarning) - except ImportError as e: - message = "Failed loading extension '%s' from '%s', '%s' " \ - "or '%s'" % (ext_name, ext_name, module_name, - module_name_old_style) - e.args = (message,) + e.args[1:] - raise + except ImportError as e: + message = 'Failed loading extension "%s".' % ext_name + e.args = (message,) + e.args[1:] + raise if class_name: # Load given class name from module. diff --git a/markdown/extensions/extra.py b/markdown/extensions/extra.py index f59e09e..cea18ed 100644 --- a/markdown/extensions/extra.py +++ b/markdown/extensions/extra.py @@ -37,13 +37,13 @@ from .. import util import re extensions = [ - 'markdown.extensions.smart_strong', - 'markdown.extensions.fenced_code', - 'markdown.extensions.footnotes', - 'markdown.extensions.attr_list', - 'markdown.extensions.def_list', - 'markdown.extensions.tables', - 'markdown.extensions.abbr' + 'smart_strong', + 'fenced_code', + 'footnotes', + 'attr_list', + 'def_list', + 'tables', + 'abbr' ] @@ -59,6 +59,25 @@ setup( entry_points={ 'console_scripts': [ '%s = markdown.__main__:run' % SCRIPT_NAME, + ], + # Register the built in extensions + 'markdown.extensions': [ + 'abbr = markdown.extensions.abbr:AbbrExtension', + 'admonition = markdown.extensions.admonition:AdmonitionExtension', + 'attr_list = markdown.extensions.attr_list:AttrListExtension', + 'codehilite = markdown.extensions.codehilite:CodeHiliteExtension', + 'def_list = markdown.extensions.def_list:DefListExtension', + 'extra = markdown.extensions.extra:ExtraExtension', + 'fenced_code = markdown.extensions.fenced_code:FencedCodeExtension', + 'footnotes = markdown.extensions.footnotes:FootnoteExtension', + 'meta = markdown.extensions.meta:MetaExtension', + 'nl2br = markdown.extensions.nl2br:Nl2BrExtension', + 'sane_lists = markdown.extensions.sane_lists:SaneListExtension', + 'smart_strong = markdown.extensions.smart_strong:SmartEmphasisExtension', + 'smarty = markdown.extensions.smarty:SmartyExtension', + 'tables = markdown.extensions.tables:TableExtension', + 'toc = markdown.extensions.toc:TocExtension', + 'wikilinks = markdown.extensions.wikilinks:WikiLinkExtension', ] }, classifiers=[ diff --git a/tests/test_apis.py b/tests/test_apis.py index 6a1829b..42e7496 100644 --- a/tests/test_apis.py +++ b/tests/test_apis.py @@ -11,7 +11,6 @@ from __future__ import unicode_literals import unittest import sys import os -import types import markdown import warnings from markdown.__main__ import parse_options @@ -46,11 +45,15 @@ class TestMarkdownBasics(unittest.TestCase): from markdown.extensions.footnotes import FootnoteExtension markdown.Markdown(extensions=[FootnoteExtension()]) - def testNamedExtension(self): + def testEntryPointExtension(self): + """ Test Extension loading with an entry point. """ + markdown.Markdown(extensions=['footnotes']) + + def testDotNotationExtension(self): """ Test Extension loading with Name (`path.to.module`). """ markdown.Markdown(extensions=['markdown.extensions.footnotes']) - def TestNamedExtensionWithClass(self): + def TestDotNotationExtensionWithClass(self): """ Test Extension loading with class name (`path.to.module:Class`). """ markdown.Markdown(extensions=['markdown.extensions.footnotes:FootnoteExtension']) @@ -343,46 +346,6 @@ class TestErrors(unittest.TestCase): markdown.Markdown, extensions=[markdown.extensions.Extension()] ) - def testMdxExtention(self): - """ Test that prepending mdx_ raises a DeprecationWarning. """ - _create_fake_extension(name='fake', use_old_style=True) - self.assertRaises( - DeprecationWarning, - markdown.Markdown, extensions=['fake'] - ) - - def testShortNameExtention(self): - """ Test that using a short name raises a DeprecationWarning. """ - self.assertRaises( - DeprecationWarning, - markdown.Markdown, extensions=['footnotes'] - ) - - -def _create_fake_extension(name, has_factory_func=True, is_wrong_type=False, use_old_style=False): - """ Create a fake extension module for testing. """ - if use_old_style: - mod_name = '_'.join(['mdx', name]) - else: - mod_name = name - if not PY3: - # mod_name must be bytes in Python 2.x - mod_name = bytes(mod_name) - ext_mod = types.ModuleType(mod_name) - - def makeExtension(*args, **kwargs): - if is_wrong_type: - return object - else: - return markdown.extensions.Extension(*args, **kwargs) - - if has_factory_func: - ext_mod.makeExtension = makeExtension - # Warning: this brute forces the extenson module onto the system. Either - # this needs to be specificly overriden or a new python session needs to - # be started to get rid of this. This should be ok in a testing context. - sys.modules[mod_name] = ext_mod - class testETreeComments(unittest.TestCase): """ diff --git a/tests/test_extensions.py b/tests/test_extensions.py index 5dee0fd..5a04e64 100644 --- a/tests/test_extensions.py +++ b/tests/test_extensions.py @@ -72,7 +72,7 @@ class TestAbbr(unittest.TestCase): """ Test abbr extension. """ def setUp(self): - self.md = markdown.Markdown(extensions=['markdown.extensions.abbr']) + self.md = markdown.Markdown(extensions=['abbr']) def testSimpleAbbr(self): """ Test Abbreviations. """ @@ -109,7 +109,7 @@ class TestCodeHilite(TestCaseWithAssertStartsWith): def testBasicCodeHilite(self): text = '\t# A Code Comment' - md = markdown.Markdown(extensions=['markdown.extensions.codehilite']) + md = markdown.Markdown(extensions=['codehilite']) if self.has_pygments: # Pygments can use random lexer here as we did not specify the language self.assertStartsWith('<div class="codehilite"><pre>', md.convert(text)) @@ -205,7 +205,7 @@ class TestCodeHilite(TestCaseWithAssertStartsWith): text1 = "\t:::Python hl_lines='1'\n\t#line 1\n\t#line 2\n\t#line 3" for text in (text0, text1): - md = markdown.Markdown(extensions=['markdown.extensions.codehilite']) + md = markdown.Markdown(extensions=['codehilite']) if self.has_pygments: self.assertStartsWith( '<div class="codehilite"><pre><span class="hll"', @@ -236,7 +236,7 @@ class TestFencedCode(TestCaseWithAssertStartsWith): """ Test fenced_code extension. """ def setUp(self): - self.md = markdown.Markdown(extensions=['markdown.extensions.fenced_code']) + self.md = markdown.Markdown(extensions=['fenced_code']) self.has_pygments = True try: import pygments # noqa @@ -323,7 +323,7 @@ line 3 md = markdown.Markdown( extensions=[ markdown.extensions.codehilite.CodeHiliteExtension(linenums=None, guess_lang=False), - 'markdown.extensions.fenced_code' + 'fenced_code' ] ) @@ -359,7 +359,7 @@ line 3 md = markdown.Markdown( extensions=[ markdown.extensions.codehilite.CodeHiliteExtension(linenums=None, guess_lang=False), - 'markdown.extensions.fenced_code' + 'fenced_code' ] ) if self.has_pygments: @@ -382,7 +382,7 @@ line 3 md = markdown.Markdown( extensions=[ markdown.extensions.codehilite.CodeHiliteExtension(use_pygments=False), - 'markdown.extensions.fenced_code' + 'fenced_code' ] ) self.assertTrue('<code class="language-python">' in md.convert(text)) @@ -392,7 +392,7 @@ class TestMetaData(unittest.TestCase): """ Test MetaData extension. """ def setUp(self): - self.md = markdown.Markdown(extensions=['markdown.extensions.meta']) + self.md = markdown.Markdown(extensions=['meta']) def testBasicMetaData(self): """ Test basic metadata. """ @@ -459,7 +459,7 @@ class TestWikiLinks(unittest.TestCase): """ Test Wikilinks Extension. """ def setUp(self): - self.md = markdown.Markdown(extensions=['markdown.extensions.wikilinks']) + self.md = markdown.Markdown(extensions=['wikilinks']) self.text = "Some text with a [[WikiLink]]." def testBasicWikilinks(self): @@ -500,9 +500,9 @@ class TestWikiLinks(unittest.TestCase): """ Test Complex Settings. """ md = markdown.Markdown( - extensions=['markdown.extensions.wikilinks'], + extensions=['wikilinks'], extension_configs={ - 'markdown.extensions.wikilinks': [ + 'wikilinks': [ ('base_url', 'http://example.com/'), ('end_url', '.html'), ('html_class', '') @@ -524,7 +524,7 @@ wiki_end_url: .html wiki_html_class: Some text with a [[WikiLink]].""" - md = markdown.Markdown(extensions=['markdown.extensions.meta', 'markdown.extensions.wikilinks']) + md = markdown.Markdown(extensions=['meta', 'wikilinks']) self.assertEqual( md.convert(text), '<p>Some text with a ' @@ -557,7 +557,7 @@ class TestAdmonition(unittest.TestCase): """ Test Admonition Extension. """ def setUp(self): - self.md = markdown.Markdown(extensions=['markdown.extensions.admonition']) + self.md = markdown.Markdown(extensions=['admonition']) def testRE(self): RE = self.md.parser.blockprocessors['admonition'].RE @@ -574,7 +574,7 @@ class TestTOC(TestCaseWithAssertStartsWith): """ Test TOC Extension. """ def setUp(self): - self.md = markdown.Markdown(extensions=['markdown.extensions.toc']) + self.md = markdown.Markdown(extensions=['toc']) def testMarker(self): """ Test TOC with a Marker. """ @@ -818,7 +818,7 @@ class TestTOC(TestCaseWithAssertStartsWith): def testWithAttrList(self): """ Test TOC with attr_list Extension. """ - md = markdown.Markdown(extensions=['markdown.extensions.toc', 'markdown.extensions.attr_list']) + md = markdown.Markdown(extensions=['toc', 'attr_list']) text = '# Header 1\n\n## Header 2 { #foo }' self.assertEqual( md.convert(text), @@ -849,7 +849,7 @@ class TestTOC(TestCaseWithAssertStartsWith): class TestSmarty(unittest.TestCase): def setUp(self): config = { - 'markdown.extensions.smarty': [ + 'smarty': [ ('smart_angled_quotes', True), ('substitutions', { 'ndash': '\u2013', @@ -865,7 +865,7 @@ class TestSmarty(unittest.TestCase): ] } self.md = markdown.Markdown( - extensions=['markdown.extensions.smarty'], + extensions=['smarty'], extension_configs=config ) @@ -885,8 +885,8 @@ class TestFootnotes(unittest.TestCase): def testBacklinkText(self): md = markdown.Markdown( - extensions=['markdown.extensions.footnotes'], - extension_configs={'markdown.extensions.footnotes': {'BACKLINK_TEXT': 'back'}} + extensions=['footnotes'], + extension_configs={'footnotes': {'BACKLINK_TEXT': 'back'}} ) text = 'paragraph[^1]\n\n[^1]: A Footnote' self.assertEqual( diff --git a/tests/test_legacy.py b/tests/test_legacy.py index ddc54bb..7fca02a 100644 --- a/tests/test_legacy.py +++ b/tests/test_legacy.py @@ -123,80 +123,67 @@ class TestExtensions(LegacyTestCase): location = os.path.join(parent_test_dir, 'extensions') exclude = ['codehilite'] - attr_list = Kwargs( - extensions=[ - 'markdown.extensions.attr_list', - 'markdown.extensions.def_list', - 'markdown.extensions.smarty' - ] - ) + attr_list = Kwargs(extensions=['attr_list', 'def_list', 'smarty']) - codehilite = Kwargs(extensions=['markdown.extensions.codehilite']) + codehilite = Kwargs(extensions=['codehilite']) - toc = Kwargs(extensions=['markdown.extensions.toc']) + toc = Kwargs(extensions=['toc']) - toc_invalid = Kwargs(extensions=['markdown.extensions.toc']) + toc_invalid = Kwargs(extensions=['toc']) - toc_out_of_order = Kwargs(extensions=['markdown.extensions.toc']) + toc_out_of_order = Kwargs(extensions=['toc']) toc_nested = Kwargs( - extensions=['markdown.extensions.toc'], - extension_configs={'markdown.extensions.toc': {'permalink': True}} + extensions=['toc'], + extension_configs={'toc': {'permalink': True}} ) toc_nested2 = Kwargs( - extensions=['markdown.extensions.toc'], - extension_configs={'markdown.extensions.toc': {'permalink': "[link]"}} + extensions=['toc'], + extension_configs={'toc': {'permalink': "[link]"}} ) - toc_nested_list = Kwargs(extensions=['markdown.extensions.toc']) + toc_nested_list = Kwargs(extensions=['toc']) - wikilinks = Kwargs(extensions=['markdown.extensions.wikilinks']) + wikilinks = Kwargs(extensions=['wikilinks']) - fenced_code = Kwargs(extensions=['markdown.extensions.fenced_code']) + fenced_code = Kwargs(extensions=['fenced_code']) - github_flavored = Kwargs(extensions=['markdown.extensions.fenced_code']) + github_flavored = Kwargs(extensions=['fenced_code']) - sane_lists = Kwargs(extensions=['markdown.extensions.sane_lists']) + sane_lists = Kwargs(extensions=['sane_lists']) - nl2br_w_attr_list = Kwargs( - extensions=[ - 'markdown.extensions.nl2br', - 'markdown.extensions.attr_list' - ] - ) + nl2br_w_attr_list = Kwargs(extensions=['nl2br', 'attr_list']) - admonition = Kwargs(extensions=['markdown.extensions.admonition']) + admonition = Kwargs(extensions=['admonition']) smarty = Kwargs( - extensions=['markdown.extensions.smarty'], - extension_configs={'markdown.extensions.smarty': {'smart_angled_quotes': True}} + extensions=['smarty'], + extension_configs={'smarty': {'smart_angled_quotes': True}} ) class TestExtensionsExtra(LegacyTestCase): location = os.path.join(parent_test_dir, 'extensions/extra') - default_kwargs = Kwargs(extensions=['markdown.extensions.extra']) + default_kwargs = Kwargs(extensions=['extra']) - loose_def_list = Kwargs(extensions=['markdown.extensions.def_list']) + loose_def_list = Kwargs(extensions=['def_list']) - simple_def_lists = Kwargs(extensions=['markdown.extensions.def_list']) + simple_def_lists = Kwargs(extensions=['def_list']) - abbr = Kwargs(extensions=['markdown.extensions.abbr']) + abbr = Kwargs(extensions=['abbr']) - footnotes = Kwargs(extensions=['markdown.extensions.footnotes']) + footnotes = Kwargs(extensions=['footnotes']) - tables = Kwargs(extensions=['markdown.extensions.tables']) + tables = Kwargs(extensions=['tables']) - tables_and_attr_list = Kwargs( - extensions=['markdown.extensions.tables', 'markdown.extensions.attr_list'] - ) + tables_and_attr_list = Kwargs(extensions=['tables', 'attr_list']) extra_config = Kwargs( - extensions=['markdown.extensions.extra'], + extensions=['extra'], extension_configs={ - 'markdown.extensions.extra': { - 'markdown.extensions.footnotes': { + 'extra': { + 'footnotes': { 'PLACE_MARKER': '~~~placemarker~~~' } } |