Make section titles have stable anchor links
Currently some sections produce anchors like "#id1", which will change over time as new releases are made. Instead, put in explicit references so that each version gets an anchor based on the version number, and so that each heading inside of the version has one based on the section title and the release version. Each anchor needs to be prefixed either with the title, if given, or with another string as some people include reno inside of other sphinx documentation and these will produce global refererences for the sphinx build. Also, since it's one global set of anchors, each version needs to be prefixed by title (or 'relnotes') and the section title because otherwise in cases like reno's own docs where some versions are included in the output twice, sphinx will produce conflicts. Change-Id: Ia6bdaffa6d0ae286542fbb7ae12613be56bdb326
This commit is contained in:
@ -0,0 +1,5 @@
|
||||
---
|
||||
features:
|
||||
- |
|
||||
Added explicitly calculated anchors to ensure section links are both
|
||||
unique and stable over time.
|
@ -25,6 +25,21 @@ def _indent_for_list(text, prefix=' '):
|
||||
]) + '\n'
|
||||
|
||||
|
||||
def _anchor(version_title, title):
|
||||
title = title or 'relnotes'
|
||||
return '.. _{title}_{version_title}:'.format(
|
||||
title=title,
|
||||
version_title=version_title)
|
||||
|
||||
|
||||
def _section_anchor(section_title, version_title, title):
|
||||
# Get the title and remove the trailing :
|
||||
title = _anchor(version_title, title)[:-1]
|
||||
return "{title}_{section_title}:".format(
|
||||
title=title,
|
||||
section_title=section_title)
|
||||
|
||||
|
||||
def format_report(loader, config, versions_to_include, title=None,
|
||||
show_source=True):
|
||||
report = []
|
||||
@ -44,11 +59,13 @@ def format_report(loader, config, versions_to_include, title=None,
|
||||
for version in versions_to_include:
|
||||
if '-' in version:
|
||||
# This looks like an "unreleased version".
|
||||
title = config.unreleased_version_title or version
|
||||
version_title = config.unreleased_version_title or version
|
||||
else:
|
||||
title = version
|
||||
report.append(title)
|
||||
report.append('=' * len(title))
|
||||
version_title = version
|
||||
report.append(_anchor(version_title, title))
|
||||
report.append('')
|
||||
report.append(version_title)
|
||||
report.append('=' * len(version_title))
|
||||
report.append('')
|
||||
|
||||
# Add the preludes.
|
||||
@ -57,7 +74,11 @@ def format_report(loader, config, versions_to_include, title=None,
|
||||
notefiles_with_prelude = [(n, sha) for n, sha in notefiles
|
||||
if prelude_name in file_contents[n]]
|
||||
if notefiles_with_prelude:
|
||||
report.append(prelude_name.replace('_', ' ').title())
|
||||
prelude_title = prelude_name.replace('_', ' ').title()
|
||||
report.append(_section_anchor(
|
||||
prelude_title, version_title, title))
|
||||
report.append('')
|
||||
report.append(prelude_title)
|
||||
report.append('-' * len(prelude_name))
|
||||
report.append('')
|
||||
|
||||
@ -76,6 +97,9 @@ def format_report(loader, config, versions_to_include, title=None,
|
||||
for n in file_contents[fn].get(section_name, [])
|
||||
]
|
||||
if notes:
|
||||
report.append(_section_anchor(
|
||||
section_title, version_title, title))
|
||||
report.append('')
|
||||
report.append(section_title)
|
||||
report.append('-' * len(section_title))
|
||||
report.append('')
|
||||
|
@ -156,6 +156,7 @@ class TestFormatterCustomSections(TestFormatterBase):
|
||||
expected = [prelude_pos, api_pos, features_pos]
|
||||
actual = list(sorted([prelude_pos, features_pos, api_pos]))
|
||||
self.assertEqual(expected, actual)
|
||||
self.assertIn('.. _relnotes_1.0.0_API Changes:', result)
|
||||
|
||||
|
||||
class TestFormatterCustomUnreleaseTitle(TestFormatterBase):
|
||||
@ -182,6 +183,7 @@ class TestFormatterCustomUnreleaseTitle(TestFormatterBase):
|
||||
)
|
||||
self.assertIn('Not Released', result)
|
||||
self.assertNotIn('0.1.0-1', result)
|
||||
self.assertIn('.. _This is the title_Not Released:', result)
|
||||
|
||||
def test_without_title(self):
|
||||
result = formatter.format_report(
|
||||
@ -191,3 +193,49 @@ class TestFormatterCustomUnreleaseTitle(TestFormatterBase):
|
||||
title='This is the title',
|
||||
)
|
||||
self.assertIn('0.1.0-1', result)
|
||||
self.assertIn('.. _This is the title_0.1.0-1:', result)
|
||||
|
||||
|
||||
class TestFormatterAnchors(TestFormatterBase):
|
||||
|
||||
note_bodies = {
|
||||
'note1': {
|
||||
'prelude': 'This is the prelude.',
|
||||
},
|
||||
'note2': {
|
||||
'issues': [
|
||||
'This is the first issue.',
|
||||
'This is the second issue.',
|
||||
],
|
||||
},
|
||||
'note3': {
|
||||
'features': [
|
||||
'We added a feature!',
|
||||
],
|
||||
'upgrade': None,
|
||||
},
|
||||
}
|
||||
|
||||
def test_with_title(self):
|
||||
self.c.override(unreleased_version_title='Not Released')
|
||||
result = formatter.format_report(
|
||||
loader=self.ldr,
|
||||
config=self.c,
|
||||
versions_to_include=self.versions,
|
||||
title='This is the title',
|
||||
)
|
||||
self.assertIn('.. _This is the title_0.0.0:', result)
|
||||
self.assertIn('.. _This is the title_0.0.0_Prelude:', result)
|
||||
self.assertIn('.. _This is the title_1.0.0:', result)
|
||||
self.assertIn('.. _This is the title_1.0.0_Known Issues:', result)
|
||||
|
||||
def test_without_title(self):
|
||||
result = formatter.format_report(
|
||||
loader=self.ldr,
|
||||
config=self.c,
|
||||
versions_to_include=self.versions,
|
||||
)
|
||||
self.assertIn('.. _relnotes_0.0.0:', result)
|
||||
self.assertIn('.. _relnotes_0.0.0_Prelude:', result)
|
||||
self.assertIn('.. _relnotes_1.0.0:', result)
|
||||
self.assertIn('.. _relnotes_1.0.0_Known Issues:', result)
|
||||
|
Reference in New Issue
Block a user