diff options
author | Waylan Limberg <waylan.limberg@icloud.com> | 2014-08-27 14:59:40 -0400 |
---|---|---|
committer | Waylan Limberg <waylan.limberg@icloud.com> | 2014-08-27 14:59:40 -0400 |
commit | e98ac74737c1f28ec730fa5c645062e0e0d5702b (patch) | |
tree | ec4400643c52c1441c61d9fe994b574b48170006 | |
parent | 44e718ed82ed4c8e8e0f0fe1dbdb73d441747b19 (diff) | |
download | markdown-e98ac74737c1f28ec730fa5c645062e0e0d5702b.tar.gz markdown-e98ac74737c1f28ec730fa5c645062e0e0d5702b.tar.bz2 markdown-e98ac74737c1f28ec730fa5c645062e0e0d5702b.zip |
Allow named extensions to specify the Class Name
If you were to import the class like this:
from path.to.module import SomeExtensionClass
Then the named extension would be the string:
"path.to.module:SomeExtensionClass"
This should simplify loading extensions from the command line or
template filters -- expecially when multiple extensions are defined
in a single python module.
The docs still need updating. I'm waiting to update the docs after
implementing #335 and #336 as that will require a major refactor of
that section of the docs anyway.
-rw-r--r-- | docs/release-2.5.txt | 30 | ||||
-rw-r--r-- | markdown/__init__.py | 26 | ||||
-rw-r--r-- | tests/test_apis.py | 17 |
3 files changed, 61 insertions, 12 deletions
diff --git a/docs/release-2.5.txt b/docs/release-2.5.txt index eb9c44c..ea351b0 100644 --- a/docs/release-2.5.txt +++ b/docs/release-2.5.txt @@ -36,6 +36,36 @@ languages other than English. Thanks to [Martin Altmayer] for implementing this [Smarty Extension]: extensions/smarty.html [Martin Altmayer]:https://github.com/MartinAltmayer +* Named Extensions (strings passed to the [`extensions`][ex] keyword of `markdown.Markdown`) + can now point to any module and/or Class on your PYTHONPATH. While dot notation was + previously supported, a module could not be at the root of your PYTHONPATH. The name had + to contain at least one dot (requiring it to be a submodule). This restriction no longer + exists. + + Additionaly, a Class may be specified in the name. The class must be at the end of the + name (usig dot notation from PYTHONPATH) and seperated by a colon from the module. + + Therefore, if you were to import the class like this: + + from path.to.module import SomeExtensionClass + + Then the named extension would be the string: + + "path.to.module:SomeExtensionClass" + + This allows multiple extensions to be implemented within the same module and still + accessable when the user isn't able to import the extension directly (perhaps from + a template filter or the command line). + + This also means that extension modules are no longer required to include the + `makeExtension` funtion which returns an instance of the extension class. However, + if the user does not specify the class name (she only provides `"path.to.module"`) + the extension will fail to load without the `makeExtension` function included in + the module. Extension authors will want to document carfully what is required to + load their extensions. + +[ex]: reference.html#extensions + * The Extension Configuration code has been refactord to make it a little easier for extension authors to work with config settings. As a result, the [`extension_configs`][ec] keyword now accepts a dictionary rather than requiring diff --git a/markdown/__init__.py b/markdown/__init__.py index 6190dd2..28f30c8 100644 --- a/markdown/__init__.py +++ b/markdown/__init__.py @@ -190,6 +190,9 @@ class Markdown(object): pairs = [x.split("=") for x in ext_args.split(",")] configs.update([(x.strip(), y.strip()) for (x, y) in pairs]) + # 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`) @@ -213,16 +216,19 @@ class Markdown(object): e.args = (message,) + e.args[1:] raise - # If the module is loaded successfully, we expect it to define a - # function called makeExtension() - try: - return module.makeExtension(configs.items()) - except AttributeError as e: - message = e.args[0] - message = "Failed to initiate extension " \ - "'%s': %s" % (ext_name, message) - e.args = (message,) + e.args[1:] - raise + if class_name: + # Load given class name from module. + return getattr(module, class_name)(configs.items()) + else: + # Expect makeExtension() function to return a class. + try: + return module.makeExtension(configs.items()) + except AttributeError as e: + message = e.args[0] + message = "Failed to initiate extension " \ + "'%s': %s" % (ext_name, message) + e.args = (message,) + e.args[1:] + raise def registerExtension(self, extension): """ This gets called by the extension """ diff --git a/tests/test_apis.py b/tests/test_apis.py index f0c7f3d..e347cf9 100644 --- a/tests/test_apis.py +++ b/tests/test_apis.py @@ -40,6 +40,19 @@ class TestMarkdownBasics(unittest.TestCase): """ Test simple input. """ self.assertEqual(self.md.convert('foo'), '<p>foo</p>') + def testInstanceExtension(self): + """ Test Extension loading with a class instance. """ + from markdown.extensions.footnotes import FootnoteExtension + markdown.Markdown(extensions=[FootnoteExtension()]) + + def testNamedExtension(self): + """ Test Extension loading with Name (`path.to.module`). """ + markdown.Markdown(extensions=['markdown.extensions.footnotes']) + + def TestNamedExtensionWithClass(self): + """ Test Extension loading with class name (`path.to.module:Class`). """ + markdown.Markdown(extensions=['markdown.extensions.footnotes:FootnoteExtension']) + class TestBlockParser(unittest.TestCase): """ Tests of the BlockParser class. """ @@ -572,7 +585,7 @@ class TestCliOptionParsing(unittest.TestCase): 'end_url': '.html', 'html_class': 'test', }, - 'footnotes': { + 'footnotes:FootnotesExtension': { 'PLACE_MARKER': '~~~footnotes~~~' } } @@ -588,7 +601,7 @@ class TestCliOptionParsing(unittest.TestCase): 'end_url': '.html', 'html_class': 'test', }, - 'footnotes': { + 'footnotes:FootnotesExtension': { 'PLACE_MARKER': '~~~footnotes~~~' } } |