aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--docs/extensions/toc.txt2
-rw-r--r--markdown/extensions/headerid.py15
-rw-r--r--markdown/extensions/smarty.py11
-rw-r--r--markdown/extensions/toc.py48
-rw-r--r--markdown/util.py13
-rwxr-xr-xsetup.py3
-rw-r--r--tests/extensions/test.cfg4
-rw-r--r--tests/extensions/toc_nested.html8
-rw-r--r--tests/extensions/toc_nested2.html8
-rw-r--r--tests/test_apis.py13
10 files changed, 78 insertions, 47 deletions
diff --git a/docs/extensions/toc.txt b/docs/extensions/toc.txt
index 2a91bb6..260129c 100644
--- a/docs/extensions/toc.txt
+++ b/docs/extensions/toc.txt
@@ -58,6 +58,8 @@ The following options are provided to configure the output:
* **title**: Title to insert in TOC ``<div>``. Defaults to ``None``.
* **anchorlink**: Set to ``True`` to have the headers link to themselves.
Default is ``False``.
+* **permalink**: Set to ``True`` to have this extension generate Sphinx-style
+ permanent links near the headers (for use with Sphinx stylesheets).
If a 'marker' is not found in the document, then the toc is available as an
attribute of the Markdown class. This allows one to insert the toc elsewhere
diff --git a/markdown/extensions/headerid.py b/markdown/extensions/headerid.py
index 0476f9e..8221fe1 100644
--- a/markdown/extensions/headerid.py
+++ b/markdown/extensions/headerid.py
@@ -78,7 +78,7 @@ from __future__ import absolute_import
from __future__ import unicode_literals
from . import Extension
from ..treeprocessors import Treeprocessor
-from ..util import HTML_PLACEHOLDER_RE
+from ..util import HTML_PLACEHOLDER_RE, parseBoolValue
import re
import logging
import unicodedata
@@ -166,23 +166,14 @@ class HeaderIdTreeprocessor(Treeprocessor):
def _get_meta(self):
""" Return meta data suported by this ext as a tuple """
level = int(self.config['level']) - 1
- force = self._str2bool(self.config['forceid'])
+ force = parseBoolValue(self.config['forceid'])
if hasattr(self.md, 'Meta'):
if 'header_level' in self.md.Meta:
level = int(self.md.Meta['header_level'][0]) - 1
if 'header_forceid' in self.md.Meta:
- force = self._str2bool(self.md.Meta['header_forceid'][0])
+ force = parseBoolValue(self.md.Meta['header_forceid'][0])
return level, force
- def _str2bool(self, s, default=False):
- """ Convert a string to a booleen value. """
- s = str(s)
- if s.lower() in ['0', 'f', 'false', 'off', 'no', 'n']:
- return False
- elif s.lower() in ['1', 't', 'true', 'on', 'yes', 'y']:
- return True
- return default
-
class HeaderIdExtension(Extension):
def __init__(self, configs):
diff --git a/markdown/extensions/smarty.py b/markdown/extensions/smarty.py
index 18f9217..a0737b7 100644
--- a/markdown/extensions/smarty.py
+++ b/markdown/extensions/smarty.py
@@ -68,6 +68,7 @@
from __future__ import unicode_literals
from . import Extension
from ..inlinepatterns import HtmlPattern
+from ..util import parseBoolValue
def canonicalize(regex):
"""
@@ -173,15 +174,7 @@ class SmartyExtension(Extension):
'smart_ellipses': [True, 'Educate ellipses']
}
for key, value in configs:
- if not isinstance(value, str):
- value = bool(value)
- elif value.lower() in ('true', 't', 'yes', 'y', '1'):
- value = True
- elif value.lower() in ('false', 'f', 'no', 'n', '0'):
- value = False
- else:
- raise ValueError('Cannot parse bool value: %s' % value)
- self.setConfig(key, value)
+ self.setConfig(key, parseBoolValue(value))
def _addPatterns(self, md, patterns, serie):
for ind, pattern in enumerate(patterns):
diff --git a/markdown/extensions/toc.py b/markdown/extensions/toc.py
index 99afba0..89468d6 100644
--- a/markdown/extensions/toc.py
+++ b/markdown/extensions/toc.py
@@ -13,7 +13,7 @@ from __future__ import absolute_import
from __future__ import unicode_literals
from . import Extension
from ..treeprocessors import Treeprocessor
-from ..util import etree
+from ..util import etree, parseBoolValue, AMP_SUBSTITUTE
from .headerid import slugify, unique, itertext, stashedHTML2text
import re
@@ -89,16 +89,24 @@ class TocTreeprocessor(Treeprocessor):
yield parent, child
def add_anchor(self, c, elem_id): #@ReservedAssignment
- if self.use_anchors:
- anchor = etree.Element("a")
- anchor.text = c.text
- anchor.attrib["href"] = "#" + elem_id
- anchor.attrib["class"] = "toclink"
- c.text = ""
- for elem in c.getchildren():
- anchor.append(elem)
- c.remove(elem)
- c.append(anchor)
+ anchor = etree.Element("a")
+ anchor.text = c.text
+ anchor.attrib["href"] = "#" + elem_id
+ anchor.attrib["class"] = "toclink"
+ c.text = ""
+ for elem in c.getchildren():
+ anchor.append(elem)
+ c.remove(elem)
+ c.append(anchor)
+
+ def add_permalink(self, c, elem_id):
+ permalink = etree.Element("a")
+ permalink.text = ("%spara;" % AMP_SUBSTITUTE
+ if self.use_permalinks is True else self.use_permalinks)
+ permalink.attrib["href"] = "#" + elem_id
+ permalink.attrib["class"] = "headerlink"
+ permalink.attrib["title"] = "Permanent link"
+ c.append(permalink)
def build_toc_etree(self, div, toc_list):
# Add title to the div
@@ -127,7 +135,10 @@ class TocTreeprocessor(Treeprocessor):
div.attrib["class"] = "toc"
header_rgx = re.compile("[Hh][123456]")
- self.use_anchors = self.config["anchorlink"] in [1, '1', True, 'True', 'true']
+ self.use_anchors = parseBoolValue(self.config["anchorlink"])
+ self.use_permalinks = parseBoolValue(self.config["permalink"], False)
+ if self.use_permalinks is None:
+ self.use_permalinks = self.config["permalink"]
# Get a list of id attributes
used_ids = set()
@@ -171,8 +182,11 @@ class TocTreeprocessor(Treeprocessor):
toc_list.append({'level': tag_level,
'id': elem_id,
'name': text})
-
- self.add_anchor(c, elem_id)
+
+ if self.use_anchors:
+ self.add_anchor(c, elem_id)
+ if self.use_permalinks:
+ self.add_permalink(c, elem_id)
toc_list_nested = order_toc_list(toc_list)
self.build_toc_etree(div, toc_list_nested)
@@ -202,7 +216,11 @@ class TocExtension(Extension):
"Defaults to None"],
"anchorlink" : [0,
"1 if header should be a self link"
- "Defaults to 0"]}
+ "Defaults to 0"],
+ "permalink" : [0,
+ "1 or link text if a Sphinx-style permalink should be added",
+ "Defaults to 0"]
+ }
for key, value in configs:
self.setConfig(key, value)
diff --git a/markdown/util.py b/markdown/util.py
index d292aad..d0ef8a3 100644
--- a/markdown/util.py
+++ b/markdown/util.py
@@ -84,6 +84,19 @@ def isBlockLevel(tag):
# Some ElementTree tags are not strings, so return False.
return False
+def parseBoolValue(value, fail_on_errors=True):
+ """Parses a string representing bool value. If parsing was successful,
+ returns True or False. If parsing was not successful, raises
+ ValueError, or, if fail_on_errors=False, returns None."""
+ if not isinstance(value, string_type):
+ return bool(value)
+ elif value.lower() in ('true', 'yes', 'y', 'on', '1'):
+ return True
+ elif value.lower() in ('false', 'no', 'n', 'off', '0'):
+ return False
+ elif fail_on_errors:
+ raise ValueError('Cannot parse bool value: %r' % value)
+
"""
MISC AUXILIARY CLASSES
=============================================================================
diff --git a/setup.py b/setup.py
index 826de9d..0702f6b 100755
--- a/setup.py
+++ b/setup.py
@@ -144,7 +144,8 @@ class build_docs(Command):
else:
with codecs.open('docs/_template.html', encoding='utf-8') as f:
template = f.read()
- self.md = markdown.Markdown(extensions=['extra', 'toc', 'meta', 'admonition', 'smarty'])
+ self.md = markdown.Markdown(
+ extensions=['extra', 'toc(permalink=true)', 'meta', 'admonition', 'smarty'])
for infile in self.docs:
outfile, ext = os.path.splitext(infile)
if ext == '.txt':
diff --git a/tests/extensions/test.cfg b/tests/extensions/test.cfg
index 1a13b1c..494d79b 100644
--- a/tests/extensions/test.cfg
+++ b/tests/extensions/test.cfg
@@ -16,10 +16,10 @@ extensions=toc
extensions=toc
[toc_nested]
-extensions=toc
+extensions=toc(permalink=1)
[toc_nested2]
-extensions=toc
+extensions=toc(permalink=[link])
[wikilinks]
extensions=wikilinks
diff --git a/tests/extensions/toc_nested.html b/tests/extensions/toc_nested.html
index 3703e51..27af9df 100644
--- a/tests/extensions/toc_nested.html
+++ b/tests/extensions/toc_nested.html
@@ -1,7 +1,7 @@
-<h1 id="header-a">Header A</h1>
-<h2 id="header-1">Header 1</h2>
-<h3 id="header-i">Header i</h3>
-<h1 id="header-b">Header <em>B</em></h1>
+<h1 id="header-a">Header A<a class="headerlink" href="#header-a" title="Permanent link">&para;</a></h1>
+<h2 id="header-1">Header 1<a class="headerlink" href="#header-1" title="Permanent link">&para;</a></h2>
+<h3 id="header-i">Header i<a class="headerlink" href="#header-i" title="Permanent link">&para;</a></h3>
+<h1 id="header-b">Header <em>B</em><a class="headerlink" href="#header-b" title="Permanent link">&para;</a></h1>
<div class="toc">
<ul>
<li><a href="#header-a">Header A</a><ul>
diff --git a/tests/extensions/toc_nested2.html b/tests/extensions/toc_nested2.html
index bf87716..2d8fa2d 100644
--- a/tests/extensions/toc_nested2.html
+++ b/tests/extensions/toc_nested2.html
@@ -8,7 +8,7 @@
<li><a href="#header-3_1">Header 3</a></li>
</ul>
</div>
-<h3 id="start-with-header-other-than-one">Start with header other than one.</h3>
-<h3 id="header-3">Header 3</h3>
-<h4 id="header-4">Header 4</h4>
-<h3 id="header-3_1">Header 3</h3> \ No newline at end of file
+<h3 id="start-with-header-other-than-one">Start with header other than one.<a class="headerlink" href="#start-with-header-other-than-one" title="Permanent link">[link]</a></h3>
+<h3 id="header-3">Header 3<a class="headerlink" href="#header-3" title="Permanent link">[link]</a></h3>
+<h4 id="header-4">Header 4<a class="headerlink" href="#header-4" title="Permanent link">[link]</a></h4>
+<h3 id="header-3_1">Header 3<a class="headerlink" href="#header-3_1" title="Permanent link">[link]</a></h3> \ No newline at end of file
diff --git a/tests/test_apis.py b/tests/test_apis.py
index bbe165d..a7d6685 100644
--- a/tests/test_apis.py
+++ b/tests/test_apis.py
@@ -420,3 +420,16 @@ class testAtomicString(unittest.TestCase):
'<div><p>*some* <span>*more* <span>*text* <span>*here*</span> '
'*to*</span> *test*</span> *with*</p></div>')
+class TestConfigParsing(unittest.TestCase):
+ def assertParses(self, value, result):
+ self.assertIs(markdown.util.parseBoolValue(value, False), result)
+
+ def testBooleansParsing(self):
+ self.assertParses(True, True)
+ self.assertParses('novalue', None)
+ self.assertParses('yES', True)
+ self.assertParses('FALSE', False)
+ self.assertParses(0., False)
+
+ def testInvalidBooleansParsing(self):
+ self.assertRaises(ValueError, markdown.util.parseBoolValue, 'novalue')