diff options
author | Waylan Limberg <waylan@gmail.com> | 2013-02-10 11:47:43 -0800 |
---|---|---|
committer | Waylan Limberg <waylan@gmail.com> | 2013-02-10 11:47:43 -0800 |
commit | 5eebfc371fbb92f0b7364c582ee21cfe35e61a74 (patch) | |
tree | cac5cc1548af1d8025de4d21f24e8e472d167feb | |
parent | 41cc055580d63ffb7eb2bbb6c88e121727d91d06 (diff) | |
parent | f78dcbedf94baa17392dafd5bb08c47d2a57ba74 (diff) | |
download | markdown-5eebfc371fbb92f0b7364c582ee21cfe35e61a74.tar.gz markdown-5eebfc371fbb92f0b7364c582ee21cfe35e61a74.tar.bz2 markdown-5eebfc371fbb92f0b7364c582ee21cfe35e61a74.zip |
Merge pull request #140 from slig/admonition
Admonitions: Initial version. This is still considered beta and subject to change. Thanks for all the work @slig.
-rw-r--r-- | docs/extensions/admonition.txt | 75 | ||||
-rw-r--r-- | docs/extensions/code_hilite.txt | 46 | ||||
-rw-r--r-- | docs/extensions/index.txt | 26 | ||||
-rw-r--r-- | docs/extensions/smart_strong.txt | 6 | ||||
-rw-r--r-- | markdown/extensions/admonition.py | 117 | ||||
-rw-r--r-- | tests/extensions/admonition.html | 30 | ||||
-rw-r--r-- | tests/extensions/admonition.txt | 28 | ||||
-rw-r--r-- | tests/extensions/test.cfg | 3 | ||||
-rw-r--r-- | tests/test_extensions.py | 35 |
9 files changed, 318 insertions, 48 deletions
diff --git a/docs/extensions/admonition.txt b/docs/extensions/admonition.txt new file mode 100644 index 0000000..21a16f4 --- /dev/null +++ b/docs/extensions/admonition.txt @@ -0,0 +1,75 @@ +title: Admonition +prev_title: Smart Strong Extension +prev_url: smart_strong.html +next_title: CodeHilite Extension +next_url: code_hilite.html + +Admonition +========== + +Summary +------- + +This extension adds [rST-style][rST] admonitions to Markdown documents. + +This extension is included in the standard Markdown library. + +[rST]: http://docutils.sourceforge.net/docs/ref/rst/directives.html#specific-admonitions + +Syntax +------ + +Admonitions are created using the following syntax: + + !!! type "optional explicit title within double quotes" + Any number of other indented markdown elements. + + This is the second paragraph. + +`type` will be used as the CSS classname and as default title. It must be a +single word. So, for instance: + + !!! note + You should note that the title will be automatically capitalized. + +will render: + + <div class="admonition note"> + <p class="admonition-title">Note</p> + <p>You should note that the title will be automatically capitalized.</p> + </div> + +Optionally, you can use custom titles. For instance: + + !!! danger "Don't try this at home" + ... + +will render: + + <div class="admonition danger"> + <p class="admonition-title">Don't try this at home</p> + <p>...</p> + </div> + +If you don't want a title, use a blank string `""`: + + !!! important "" + This is a admonition box without a title. + +results in: + + <div class="admonition important"> + <p>This is a admonition box without a title.</p> + </div> + + +rST suggests the following `types`, but you're free to use whatever you want: + attention, caution, danger, error, hint, important, note, tip, warning. + +Styling +------- + +There is no CSS included as part of this extension. Look up the default +[Sphinx][sphinx] theme if you need inspiration. + +[sphinx]: http://sphinx.pocoo.org/
\ No newline at end of file diff --git a/docs/extensions/code_hilite.txt b/docs/extensions/code_hilite.txt index 55a1a00..fbf05b3 100644 --- a/docs/extensions/code_hilite.txt +++ b/docs/extensions/code_hilite.txt @@ -1,6 +1,6 @@ title: CodeHilite Extension -prev_title: Smart Strong Extension -prev_url: smart_strong.html +prev_title: Admonition Extension +prev_url: admonition.html next_title: HTML Tidy Extension next_url: html_tidy.html @@ -10,7 +10,7 @@ CodeHilite Summary ------- -The CodeHilite Extension adds code/syntax highlighting to standard +The CodeHilite Extension adds code/syntax highlighting to standard Python-Markdown code blocks using [Pygments][]. [Pygments]: http://pygments.org/ @@ -20,32 +20,32 @@ This extension is included in the Markdown library. Setup ----- -You will also need to [download][dl] and install the Pygments package on your +You will also need to [download][dl] and install the Pygments package on your `PYTHONPATH`. You will need to determine the appropriate CSS classes and create -appropriate rules for them, which are either defined in or linked from the -header of your HTML templates. See the excellent [documentation][] for more -details. If no language is defined, Pygments will attempt to guess the +appropriate rules for them, which are either defined in or linked from the +header of your HTML templates. See the excellent [documentation][] for more +details. If no language is defined, Pygments will attempt to guess the language. When that fails, the code block will display as un-highlighted code. [dl]: http://pygments.org/download/ [documentation]: http://pygments.org/docs -**Note:** The css and/or javascript is not included as part of this extension +**Note:** The css and/or javascript is not included as part of this extension but shall always be provided by the end user. Syntax ------ -The CodeHilite Extension follows the same [syntax][] as regular Markdown code -blocks, with one exception. The hiliter needs to know what language to use for -the code block. There are three ways to tell the hiliter what language the code +The CodeHilite Extension follows the same [syntax][] as regular Markdown code +blocks, with one exception. The hiliter needs to know what language to use for +the code block. There are three ways to tell the hiliter what language the code block contains and each one has a different result. [syntax]: http://daringfireball.net/projects/markdown/syntax#precode ###SheBang (with path) -If the first line of the codeblock contains a shebang, the language is derived +If the first line of the codeblock contains a shebang, the language is derived from that and line numbers are used. #!/usr/bin/python @@ -59,8 +59,8 @@ Will result in: ###SheBang (no path) -If the first line contains a shebang, but the shebang line does not contain a -path (a single `/` or even a space), then that line is removed from the code +If the first line contains a shebang, but the shebang line does not contain a +path (a single `/` or even a space), then that line is removed from the code block before processing. Line numbers are used. #!python @@ -72,8 +72,8 @@ Will result in: ####Colons -If the first line begins with three or more colons, the text following the -colons identifies the language. The first line is removed from the code block +If the first line begins with three or more colons, the text following the +colons identifies the language. The first line is removed from the code block before processing and line numbers are not used. :::python @@ -85,10 +85,10 @@ Will result in: ###When No Language is Defined -CodeHilite is completely backward compatible so that if a code block is -encountered that does not define a language, the block is simple wrapped in -`<pre>` tags and output. Note: one exception would be that the Pygments -highlighting engine will try to guess the language. Upon failure, the same +CodeHilite is completely backward compatible so that if a code block is +encountered that does not define a language, the block is simple wrapped in +`<pre>` tags and output. Note: one exception would be that the Pygments +highlighting engine will try to guess the language. Upon failure, the same behavior will happen as described here. # Code goes here ... @@ -109,11 +109,11 @@ From the Python interpreter: >>> html = markdown.markdown(text, ['codehilite']) -If you want every code block to have line numbers, even when using colons -(`:::`) for language identification, the setting `force_linenos` is available +If you want every code block to have line numbers, even when using colons +(`:::`) for language identification, the setting `force_linenos` is available to do so. - >>> html = markdown.markdown(text, + >>> html = markdown.markdown(text, ... ['codehilite(force_linenos=True)'] ... ) diff --git a/docs/extensions/index.txt b/docs/extensions/index.txt index a51f797..c9ee005 100644 --- a/docs/extensions/index.txt +++ b/docs/extensions/index.txt @@ -8,16 +8,16 @@ next_url: extra.html Available 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 -actual source files. +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 +actual source files. To use an extension, pass it's name to markdown with the `extensions` keyword. -See the [Library Reference](../reference.html#extensions) for more details. +See the [Library Reference](../reference.html#extensions) for more details. markdown.markdown(some_text, extensions=['footnotes', 'nl2br']) -From the command line, specify an extension with the `-x` option. See the +From the command line, specify an extension with the `-x` option. See the [Command Line docs](../cli.html) or use the `--help` option for more details. python -m markdown -x footnotes -x tables input.txt > output.html @@ -26,9 +26,9 @@ Officially Supported Extensions ------------------------------- 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 +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. ### Markdown Extra @@ -63,6 +63,7 @@ but come in the standard Python-Markdown library. Extension | Name in Python-Markdown --------- | ----------------------- +[Admonition][] | `admonition` [CodeHilite][] | `codehilite` [HTML Tidy][] | `html_tidy` [HeaderId] | `headerid` @@ -73,6 +74,7 @@ Extension | Name in Python-Markdown [Table of Contents] | `toc` [WikiLinks] | `wikilinks` +[Admonition]: admonition.html [CodeHilite]: code_hilite.html [HTML Tidy]: html_tidy.html [HeaderId]: header_id.html @@ -87,11 +89,11 @@ Third Party Extensions ---------------------- Various individuals and/or organizations have developed extensions which they -have made available to the public. A [list of third party +have made available to the public. A [list of third party extensions](https://github.com/waylan/Python-Markdown/wiki/Third-Party-Extensions) -is maintained on the wiki for your convenience. The Python-Markdown team -offers no official support for these extensions. Please see the developer of +is maintained on the wiki for your convenience. The Python-Markdown team +offers no official support for these extensions. Please see the developer of each extension for support. -If you would like to write your own extensions, see the +If you would like to write your own extensions, see the [Extensions API](api.html) for details. diff --git a/docs/extensions/smart_strong.txt b/docs/extensions/smart_strong.txt index 8de574f..fd3bae6 100644 --- a/docs/extensions/smart_strong.txt +++ b/docs/extensions/smart_strong.txt @@ -1,8 +1,8 @@ title: Smart Strong Extension prev_title: Tables Extension prev_url: tables.html -next_title: CodeHilite Extension -next_url: code_hilite.html +next_title: Admonition Extension +next_url: admonition.html Smart_Strong ============ @@ -11,7 +11,7 @@ Summary ------- The Markdown Smart_Strong Extension adds smarter handling of double underscores -within words. This does for double underscores what +within words. This does for double underscores what [smart_emphasis](../reference.html#smart_emphasis) does for single underscores. The Smart_Strong Extension is included in the standard Markdown library. diff --git a/markdown/extensions/admonition.py b/markdown/extensions/admonition.py new file mode 100644 index 0000000..798befb --- /dev/null +++ b/markdown/extensions/admonition.py @@ -0,0 +1,117 @@ +#!/usr/bin/env python + +""" +Admonition extension for Python-Markdown +======================================== + +Adds rST-style admonitions. Inspired by [rST][] feature with the same name. + +The syntax is (followed by an indented block with the contents): + !!! [type] [optional explicit title] + +Where `type` is used as a CSS class name of the div. If not present, `title` +defaults to the capitalized `type`, so "note" -> "Note". + +rST suggests the following `types`, but you're free to use whatever you want: + attention, caution, danger, error, hint, important, note, tip, warning + + +A simple example: + !!! note + This is the first line inside the box. + +Outputs: + <div class="admonition note"> + <p class="admonition-title">Note</p> + <p>This is the first line inside the box</p> + </div> + +You can also specify the title and CSS class of the admonition: + !!! custom "Did you know?" + Another line here. + +Outputs: + <div class="admonition custom"> + <p class="admonition-title">Did you know?</p> + <p>Another line here.</p> + </div> + +[rST]: http://docutils.sourceforge.net/docs/ref/rst/directives.html#specific-admonitions + +By [Tiago Serafim](http://www.tiagoserafim.com/). + +""" + +import re +import markdown +from markdown.util import etree + + +class AdmonitionExtension(markdown.Extension): + """ Admonition extension for Python-Markdown. """ + + def extendMarkdown(self, md, md_globals): + """ Add Admonition to Markdown instance. """ + md.registerExtension(self) + + md.parser.blockprocessors.add('admonition', + AdmonitionProcessor(md.parser), + '_begin') + + +class AdmonitionProcessor(markdown.blockprocessors.BlockProcessor): + + CLASSNAME = 'admonition' + CLASSNAME_TITLE = 'admonition-title' + RE = re.compile(r'(?:^|\n)!!!\ ?([\w\-]+)(?:\ "(.*?)")?') + + def test(self, parent, block): + sibling = self.lastChild(parent) + return self.RE.search(block) or \ + (block.startswith(' ' * self.tab_length) and sibling and \ + sibling.get('class', '').find(self.CLASSNAME) != -1) + + def run(self, parent, blocks): + sibling = self.lastChild(parent) + block = blocks.pop(0) + m = self.RE.search(block) + + if m: + block = block[m.end() + 1:] # removes the first line + + block, theRest = self.detab(block) + + if m: + klass, title = self.get_class_and_title(m) + div = etree.SubElement(parent, 'div') + div.set('class', u'%s %s' % (self.CLASSNAME, klass)) + if title: + p = etree.SubElement(div, 'p') + p.text = title + p.set('class', self.CLASSNAME_TITLE) + else: + div = sibling + + self.parser.parseChunk(div, block) + + if theRest: + # This block contained unindented line(s) after the first indented + # line. Insert these lines as the first block of the master blocks + # list for future processing. + blocks.insert(0, theRest) + + def get_class_and_title(self, match): + klass, title = match.group(1), match.group(2) + if title is None: + # no title was provided, use the capitalized classname as title + # e.g.: `!!! note` will render `<p class="admonition-title">Note</p>` + title = klass.capitalize() + elif title == '': + # an explicit blank title should not be rendered + # e.g.: `!!! warning ""` will *not* render `p` with a title + title = None + return klass, title + + +def makeExtension(configs={}): + return AdmonitionExtension(configs=configs) diff --git a/tests/extensions/admonition.html b/tests/extensions/admonition.html new file mode 100644 index 0000000..437cac6 --- /dev/null +++ b/tests/extensions/admonition.html @@ -0,0 +1,30 @@ +<p>Some text</p> +<div class="admonition note"> +<p class="admonition-title">Note</p> +<p>A normal paragraph here</p> +<ol> +<li>first</li> +<li>second</li> +</ol> +<blockquote> +<p>Some important quote</p> +<p>another paragraph in the quote</p> +</blockquote> +<pre><code>int main() { + // insert some code +} +</code></pre> +</div> +<p>More text and stuff.</p> +<div class="admonition note"> +<p class="admonition-title">Did you know?</p> +<p>You can customize the title of the admonition</p> +</div> +<div class="admonition mycustomcssclass"> +<p class="admonition-title">And now...</p> +<p>For something completely different.</p> +<p>You can also use a custom CSS class name.</p> +</div> +<div class="admonition tip"> +<p>An explicitly empty string prevents the title from being rendered.</p> +</div>
\ No newline at end of file diff --git a/tests/extensions/admonition.txt b/tests/extensions/admonition.txt new file mode 100644 index 0000000..fd18bd6 --- /dev/null +++ b/tests/extensions/admonition.txt @@ -0,0 +1,28 @@ +Some text + +!!! note + A normal paragraph here + + 1. first + 2. second + + > Some important quote + + > another paragraph in the quote + + int main() { + // insert some code + } + +More text and stuff. + +!!! note "Did you know?" + You can customize the title of the admonition + +!!! mycustomcssclass "And now..." + For something completely different. + + You can also use a custom CSS class name. + +!!! tip "" + An explicitly empty string prevents the title from being rendered. diff --git a/tests/extensions/test.cfg b/tests/extensions/test.cfg index d642bd8..4efd837 100644 --- a/tests/extensions/test.cfg +++ b/tests/extensions/test.cfg @@ -32,3 +32,6 @@ extensions=sane_lists [nl2br_w_attr_list] extensions=nl2br,attr_list + +[admonition] +extensions=admonition diff --git a/tests/test_extensions.py b/tests/test_extensions.py index 7dab60a..fd77e5e 100644 --- a/tests/test_extensions.py +++ b/tests/test_extensions.py @@ -2,7 +2,7 @@ Python-Markdown Extension Regression Tests ========================================== -A collection of regression tests to confirm that the included extensions +A collection of regression tests to confirm that the included extensions continue to work as advertised. This used to be accomplished by doctests. """ @@ -60,7 +60,7 @@ class TestFencedCode(unittest.TestCase): def testBasicFence(self): """ Test Fenced Code Blocks. """ - text = ''' + text = ''' A paragraph before a fenced code block: ~~~ @@ -124,7 +124,7 @@ class TestHeaderId(unittest.TestCase): def testBasicHeaderId(self): """ Test Basic HeaderID """ - + text = "# Some Header #" self.assertEqual(self.md.convert(text), '<h1 id="some-header">Some Header</h1>') @@ -202,8 +202,8 @@ The body. This is paragraph one.''' self.assertEqual(self.md.convert(text), '<p>The body. This is paragraph one.</p>') self.assertEqual(self.md.Meta, - {'author': ['Waylan Limberg', 'John Doe'], - 'blank_data': [''], + {'author': ['Waylan Limberg', 'John Doe'], + 'blank_data': [''], 'title': ['A Test Doc.']}) def testMissingMetaData(self): @@ -239,18 +239,18 @@ class TestWikiLinks(unittest.TestCase): def testSimpleSettings(self): """ Test Simple Settings. """ - self.assertEqual(markdown.markdown(self.text, + self.assertEqual(markdown.markdown(self.text, ['wikilinks(base_url=/wiki/,end_url=.html,html_class=foo)']), '<p>Some text with a ' '<a class="foo" href="/wiki/WikiLink.html">WikiLink</a>.</p>') - + def testComplexSettings(self): """ Test Complex Settings. """ md = markdown.Markdown( - extensions = ['wikilinks'], + extensions = ['wikilinks'], extension_configs = {'wikilinks': [ - ('base_url', 'http://example.com/'), + ('base_url', 'http://example.com/'), ('end_url', '.html'), ('html_class', '') ]}, safe_mode = True) @@ -281,8 +281,23 @@ Some text with a [[WikiLink]].""" def my_url_builder(label, base, end): return '/bar/' - md = markdown.Markdown(extensions=['wikilinks'], + md = markdown.Markdown(extensions=['wikilinks'], extension_configs={'wikilinks' : [('build_url', my_url_builder)]}) self.assertEqual(md.convert('[[foo]]'), '<p><a class="wikilink" href="/bar/">foo</a></p>') +class TestAdmonition(unittest.TestCase): + """ Test Admonition Extension. """ + + def setUp(self): + self.md = markdown.Markdown(extensions=['admonition']) + + def testRE(self): + RE = self.md.parser.blockprocessors['admonition'].RE + tests = [ + ('!!! note', ('note', None)), + ('!!! note "Please Note"', ('note', 'Please Note')), + ('!!! note ""', ('note', '')), + ] + for test, expected in tests: + self.assertEqual(RE.match(test).groups(), expected) |