aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorWaylan Limberg <waylan.limberg@icloud.com>2014-08-27 14:59:40 -0400
committerWaylan Limberg <waylan.limberg@icloud.com>2014-08-27 14:59:40 -0400
commite98ac74737c1f28ec730fa5c645062e0e0d5702b (patch)
treeec4400643c52c1441c61d9fe994b574b48170006
parent44e718ed82ed4c8e8e0f0fe1dbdb73d441747b19 (diff)
downloadmarkdown-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.txt30
-rw-r--r--markdown/__init__.py26
-rw-r--r--tests/test_apis.py17
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~~~'
}
}