1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132
|
#! /usr/bin/env python3
# Requires Python 3.6+
"""Sphinx extension for making titles with dates from Git tags."""
import subprocess
from typing import List
from sphinx.application import Sphinx
from sphinx.util.docutils import SphinxDirective
from sphinx.util.nodes import nodes
import dateutil.parser
from cheroot import __version__
_SCM_COMMANDS = {
'hg': (
'hg', 'log',
'-l', '1',
'--template', '{date|isodate}',
'-r',
),
'git': (
'git', 'log',
'-1', '--format=%aI',
),
}
def _get_scm_timestamp_for(commitish, *, scm=None):
"""Retrieve the tag date from SCM."""
if scm is None:
scm = 'git'
try:
ts = subprocess.check_output(
_SCM_COMMANDS[scm] + (commitish,),
stderr=subprocess.DEVNULL,
text=True,
).strip()
except subprocess.SubprocessError:
raise ValueError(
f'There is no `{commitish}` in {scm.title()}',
) from None
return dateutil.parser.parse(ts)
class version_subtitle(nodes.subtitle, nodes.Element):
"""Version subtitle node."""
def _visit_version_subtitle(self, node):
"""Inject an opening tag."""
self.visit_title(node)
def _depart_version_subtitle(self, node):
"""Inject a closing tag."""
self.depart_title(node)
class SCMVersionTitle(SphinxDirective):
"""Definition of the scm-version-title directive."""
has_content = True # False
def run(self) -> List[nodes.Node]:
"""Generate the node tree in place of the directive."""
env = self.state.document.settings.env
ext_conf = env.config.scm_version_title_settings
# conf_py_path = pathlib.Path(conf._raw_config['__file__'])
self.assert_has_content()
first_line = self.content[:1]
inner_content = self.content[1:]
version_tag = ''.join(first_line)
try:
version_date = _get_scm_timestamp_for(
version_tag,
scm=ext_conf['scm'],
)
except (ValueError, RuntimeError):
release_date = '(no Git tag matched)'
else:
release_date = f'{{:{ext_conf["date_format"]}}}'.format(
version_date,
)
release_section = nodes.section()
release_section.tagname = 'div'
release_section['ids'] = [version_tag.replace('.', '-')]
release_section['class'] = ['section']
release_section += version_subtitle(version_tag, version_tag)
release_section += nodes.paragraph(release_date, release_date)
self.state.nested_parse(
inner_content, self.content_offset,
release_section,
)
return [release_section]
def setup(app: Sphinx) -> None:
"""Initialize the extension."""
app.add_config_value(
'scm_version_title_settings',
{
'scm': 'git',
'date_format': '%d %b %Y',
},
'html',
)
app.add_node(
version_subtitle,
html=(
_visit_version_subtitle,
_depart_version_subtitle,
),
)
app.add_directive(
'scm-version-title', SCMVersionTitle,
)
return {
'parallel_read_safe': True,
'version': __version__,
}
|