Skip to content

Commit 6662053

Browse files
authored
TOC extension: Add new boolean option permalink_prepend (#1339)
Boolean flag which, when set to True, will cause permalink anchors to be inserted at the start of heading elements, rather than at the end (the default).
1 parent 47c978d commit 6662053

File tree

6 files changed

+159
-1
lines changed

6 files changed

+159
-1
lines changed

docs/change_log/index.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,16 @@ title: Change Log
33
Python-Markdown Change Log
44
=========================
55

6+
7+
*under development*: version 3.5 ([Notes](release-3.5.md))
8+
69
July 25, 2023: version 3.4.4 (a bug-fix release).
710

811
* Add a special case for initial `'s` to smarty extension (#1305).
912
* Unescape any backslash escaped inline raw HTML (#1358).
1013
* Unescape backslash escaped TOC token names (#1360).
1114

15+
1216
March 23, 2023: version 3.4.3 (a bug-fix release).
1317

1418
* Restore console script (#1327).

docs/change_log/release-3.5.md

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
title: Release Notes for v3.5
2+
3+
# Python-Markdown 3.5 Release Notes
4+
5+
Python-Markdown version 3.5 supports Python versions 3.7, 3.8, 3.9, 3.10,
6+
3.11 and PyPy3.
7+
8+
## New features
9+
10+
The following new features have been included in the 3.5 release:
11+
12+
* A new configuration option has been added to the
13+
[toc](../extensions/toc.md) extension (#1339):
14+
15+
* A new boolean option `permalink_leading` controls the position of
16+
the permanent link anchors generated with `permalink`. Setting
17+
`permalink_leading` to `True` will cause the links to be inserted
18+
at the start of the header, before any other header content. The
19+
default behavior for `permalink` is to append permanent links to
20+
the header, placing them after all other header content.

docs/extensions/toc.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -174,6 +174,15 @@ The following options are provided to configure the output:
174174
* **`permalink_title`**:
175175
Title attribute of the permanent link. Defaults to `Permanent link`.
176176

177+
* **`permalink_leading`**:
178+
Set to `True` if the extension should generate leading permanent links.
179+
Default is `False`.
180+
181+
Leading permanent links are placed at the start of the header tag,
182+
before any header content. The default `permalink` behavior (when
183+
`permalink_leading` is unset or set to `False`) creates trailing
184+
permanent links, which are placed at the end of the header content.
185+
177186
* **`baselevel`**:
178187
Base level for headers. Defaults to `1`.
179188

markdown/extensions/toc.py

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -168,6 +168,7 @@ def __init__(self, md, config):
168168
self.use_permalinks = config["permalink"]
169169
self.permalink_class = config["permalink_class"]
170170
self.permalink_title = config["permalink_title"]
171+
self.permalink_leading = parseBoolValue(config["permalink_leading"], False)
171172
self.header_rgx = re.compile("[Hh][123456]")
172173
if isinstance(config["toc_depth"], str) and '-' in config["toc_depth"]:
173174
self.toc_top, self.toc_bottom = [int(x) for x in config["toc_depth"].split('-')]
@@ -235,7 +236,12 @@ def add_permalink(self, c, elem_id):
235236
permalink.attrib["class"] = self.permalink_class
236237
if self.permalink_title:
237238
permalink.attrib["title"] = self.permalink_title
238-
c.append(permalink)
239+
if self.permalink_leading:
240+
permalink.tail = c.text
241+
c.text = ""
242+
c.insert(0, permalink)
243+
else:
244+
c.append(permalink)
239245

240246
def build_toc_div(self, toc_list):
241247
""" Return a string div given a toc list. """
@@ -347,6 +353,10 @@ def __init__(self, **kwargs):
347353
"permalink_title": ["Permanent link",
348354
"Title attribute of the permalink - "
349355
"Defaults to 'Permanent link'"],
356+
"permalink_leading": [False,
357+
"True if permalinks should be placed at "
358+
"the start of the header, rather than the "
359+
"end - Defaults to False."],
350360
"baselevel": ['1', 'Base level for headers.'],
351361
"slugify": [slugify,
352362
"Function to generate anchors based on header text - "

mkdocs.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ nav:
4141
- Test Tools: test_tools.md
4242
- Contributing to Python-Markdown: contributing.md
4343
- Change Log: change_log/index.md
44+
- Release Notes for v.3.5: change_log/release-3.5.md
4445
- Release Notes for v.3.4: change_log/release-3.4.md
4546
- Release Notes for v.3.3: change_log/release-3.3.md
4647
- Release Notes for v.3.2: change_log/release-3.2.md

tests/test_extensions.py

Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -629,3 +629,117 @@ def testTocInHeaders(self):
629629
'</div>\n' # noqa
630630
'<h1 id="toc"><em>[TOC]</em></h1>' # noqa
631631
)
632+
633+
def testPermalink(self):
634+
""" Test TOC `permalink` feature. """
635+
text = '# Hd 1\n\n## Hd 2'
636+
md = markdown.Markdown(
637+
extensions=[markdown.extensions.toc.TocExtension(
638+
permalink=True, permalink_title="PL")]
639+
)
640+
self.assertEqual(
641+
md.convert(text),
642+
'<h1 id="hd-1">'
643+
'Hd 1' # noqa
644+
'<a class="headerlink" href="#hd-1" title="PL">' # noqa
645+
'&para;' # noqa
646+
'</a>' # noqa
647+
'</h1>\n'
648+
'<h2 id="hd-2">'
649+
'Hd 2' # noqa
650+
'<a class="headerlink" href="#hd-2" title="PL">' # noqa
651+
'&para;' # noqa
652+
'</a>' # noqa
653+
'</h2>'
654+
)
655+
656+
def testPermalinkLeading(self):
657+
""" Test TOC `permalink` with `permalink_leading` option. """
658+
text = '# Hd 1\n\n## Hd 2'
659+
md = markdown.Markdown(extensions=[
660+
markdown.extensions.toc.TocExtension(
661+
permalink=True, permalink_title="PL", permalink_leading=True)]
662+
)
663+
self.assertEqual(
664+
md.convert(text),
665+
'<h1 id="hd-1">'
666+
'<a class="headerlink" href="#hd-1" title="PL">' # noqa
667+
'&para;' # noqa
668+
'</a>' # noqa
669+
'Hd 1' # noqa
670+
'</h1>\n'
671+
'<h2 id="hd-2">'
672+
'<a class="headerlink" href="#hd-2" title="PL">' # noqa
673+
'&para;' # noqa
674+
'</a>' # noqa
675+
'Hd 2' # noqa
676+
'</h2>'
677+
)
678+
679+
def testInlineMarkupPermalink(self):
680+
""" Test TOC `permalink` with headers containing markup. """
681+
text = '# Code `in` hd'
682+
md = markdown.Markdown(
683+
extensions=[markdown.extensions.toc.TocExtension(
684+
permalink=True, permalink_title="PL")]
685+
)
686+
self.assertEqual(
687+
md.convert(text),
688+
'<h1 id="code-in-hd">'
689+
'Code <code>in</code> hd' # noqa
690+
'<a class="headerlink" href="#code-in-hd" title="PL">' # noqa
691+
'&para;' # noqa
692+
'</a>' # noqa
693+
'</h1>'
694+
)
695+
696+
def testInlineMarkupPermalinkLeading(self):
697+
""" Test TOC `permalink_leading` with headers containing markup. """
698+
text = '# Code `in` hd'
699+
md = markdown.Markdown(extensions=[
700+
markdown.extensions.toc.TocExtension(
701+
permalink=True, permalink_title="PL", permalink_leading=True)]
702+
)
703+
self.assertEqual(
704+
md.convert(text),
705+
'<h1 id="code-in-hd">'
706+
'<a class="headerlink" href="#code-in-hd" title="PL">' # noqa
707+
'&para;' # noqa
708+
'</a>' # noqa
709+
'Code <code>in</code> hd' # noqa
710+
'</h1>'
711+
)
712+
713+
714+
class TestSmarty(unittest.TestCase):
715+
def setUp(self):
716+
config = {
717+
'smarty': [
718+
('smart_angled_quotes', True),
719+
('substitutions', {
720+
'ndash': '\u2013',
721+
'mdash': '\u2014',
722+
'ellipsis': '\u2026',
723+
'left-single-quote': '&sbquo;', # `sb` is not a typo!
724+
'right-single-quote': '&lsquo;',
725+
'left-double-quote': '&bdquo;',
726+
'right-double-quote': '&ldquo;',
727+
'left-angle-quote': '[',
728+
'right-angle-quote': ']',
729+
}),
730+
]
731+
}
732+
self.md = markdown.Markdown(
733+
extensions=['smarty'],
734+
extension_configs=config
735+
)
736+
737+
def testCustomSubstitutions(self):
738+
text = """<< The "Unicode char of the year 2014"
739+
is the 'mdash': ---
740+
Must not be confused with 'ndash' (--) ... >>
741+
"""
742+
correct = """<p>[ The &bdquo;Unicode char of the year 2014&ldquo;
743+
is the &sbquo;mdash&lsquo;: \u2014
744+
Must not be confused with &sbquo;ndash&lsquo; (\u2013) \u2026 ]</p>"""
745+
self.assertEqual(self.md.convert(text), correct)

0 commit comments

Comments
 (0)