Código fuente para sphinx.builders.changes

"""Changelog builder."""

from __future__ import annotations

import html
from pathlib import Path
from typing import TYPE_CHECKING

from sphinx import package_dir
from sphinx._cli.util.colour import bold
from sphinx.builders import Builder
from sphinx.locale import _, __
from sphinx.theming import HTMLThemeFactory
from sphinx.util import logging
from sphinx.util.fileutil import copy_asset_file

if TYPE_CHECKING:
    from collections.abc import Set

    from sphinx.application import Sphinx
    from sphinx.util.typing import ExtensionMetadata

logger = logging.getLogger(__name__)


[documentos] class ChangesBuilder(Builder): """Write a summary with all versionadded/changed/deprecated/removed directives.""" name = 'changes' epilog = __('The overview file is in %(outdir)s.') def init(self) -> None: self.create_template_bridge() theme_factory = HTMLThemeFactory(self.app) self.theme = theme_factory.create('default') self.templates.init(self, self.theme) def get_outdated_docs(self) -> str: return str(self.outdir) typemap = { 'versionadded': 'added', 'versionchanged': 'changed', 'deprecated': 'deprecated', 'versionremoved': 'removed', } def write_documents(self, _docnames: Set[str]) -> None: version = self.config.version domain = self.env.domains.changeset_domain libchanges: dict[str, list[tuple[str, str, int]]] = {} apichanges: list[tuple[str, str, int]] = [] otherchanges: dict[tuple[str, str], list[tuple[str, str, int]]] = {} changesets = domain.get_changesets_for(version) if not changesets: logger.info(bold(__('no changes in version %s.')), version) return logger.info(bold(__('writing summary file...'))) for changeset in changesets: descname = changeset.descname ttext = self.typemap[changeset.type] context = changeset.content.replace('\n', ' ') if descname and changeset.docname.startswith('c-api'): if context: entry = f'<b>{descname}</b>: <i>{ttext}:</i> {context}' else: entry = f'<b>{descname}</b>: <i>{ttext}</i>.' apichanges.append((entry, changeset.docname, changeset.lineno)) elif descname or changeset.module: module = changeset.module or _('Builtins') if not descname: descname = _('Module level') if context: entry = f'<b>{descname}</b>: <i>{ttext}:</i> {context}' else: entry = f'<b>{descname}</b>: <i>{ttext}</i>.' libchanges.setdefault(module, []).append(( entry, changeset.docname, changeset.lineno, )) else: if not context: continue entry = f'<i>{ttext.capitalize()}:</i> {context}' title = self.env.titles[changeset.docname].astext() otherchanges.setdefault((changeset.docname, title), []).append(( entry, changeset.docname, changeset.lineno, )) ctx = { 'project': self.config.project, 'version': version, 'docstitle': self.config.html_title, 'shorttitle': self.config.html_short_title, 'libchanges': sorted(libchanges.items()), 'apichanges': sorted(apichanges), 'otherchanges': sorted(otherchanges.items()), 'show_copyright': self.config.html_show_copyright, 'show_sphinx': self.config.html_show_sphinx, } with open(self.outdir / 'index.html', 'w', encoding='utf8') as f: f.write(self.templates.render('changes/frameset.html', ctx)) with open(self.outdir / 'changes.html', 'w', encoding='utf8') as f: f.write(self.templates.render('changes/versionchanges.html', ctx)) hltext = [ f'.. versionadded:: {version}', f'.. versionchanged:: {version}', f'.. deprecated:: {version}', f'.. versionremoved:: {version}', ] def hl(no: int, line: str) -> str: line = '<a name="L%s"> </a>' % no + html.escape(line) for x in hltext: if x in line: line = '<span class="hl">%s</span>' % line break return line logger.info(bold(__('copying source files...'))) for docname in self.env.all_docs: with open( self.env.doc2path(docname), encoding=self.config.source_encoding ) as f: try: lines = f.readlines() except UnicodeDecodeError: logger.warning( __('could not read %r for changelog creation'), docname ) continue text = ''.join(hl(i + 1, line) for (i, line) in enumerate(lines)) ctx = { 'filename': str(self.env.doc2path(docname, False)), 'text': text, } rendered = self.templates.render('changes/rstsource.html', ctx) targetfn = self.outdir / 'rst' / f'{docname}.html' targetfn.parent.mkdir(parents=True, exist_ok=True) with open(targetfn, 'w', encoding='utf-8') as f: f.write(rendered) themectx = { 'theme_' + key: val for (key, val) in self.theme.get_options({}).items() } copy_asset_file( Path(package_dir, 'themes', 'default', 'static', 'default.css.jinja'), self.outdir, context=themectx, renderer=self.templates, force=True, ) copy_asset_file( Path(package_dir, 'themes', 'basic', 'static', 'basic.css'), self.outdir / 'basic.css', force=True, ) def hl(self, text: str, version: str) -> str: text = html.escape(text) for directive in ( 'versionchanged', 'versionadded', 'deprecated', 'versionremoved', ): text = text.replace( f'.. {directive}:: {version}', f'<b>.. {directive}:: {version}</b>' ) return text def finish(self) -> None: pass
def setup(app: Sphinx) -> ExtensionMetadata: app.add_builder(ChangesBuilder) return { 'version': 'builtin', 'parallel_read_safe': True, 'parallel_write_safe': True, }