Source code for sphinx.builders.changes

# -*- coding: utf-8 -*-
"""
    sphinx.builders.changes
    ~~~~~~~~~~~~~~~~~~~~~~~

    Changelog builder.

    :copyright: Copyright 2007-2017 by the Sphinx team, see AUTHORS.
    :license: BSD, see LICENSE for details.
"""

import codecs
from os import path

from six import iteritems

from sphinx import package_dir
from sphinx.locale import _
from sphinx.theming import HTMLThemeFactory
from sphinx.builders import Builder
from sphinx.util import logging
from sphinx.util.osutil import ensuredir, os_path
from sphinx.util.console import bold  # type: ignore
from sphinx.util.fileutil import copy_asset_file
from sphinx.util.pycompat import htmlescape

if False:
    # For type annotation
    from typing import Any, Dict, List, Tuple  # NOQA
    from sphinx.application import Sphinx  # NOQA


logger = logging.getLogger(__name__)


[docs]class ChangesBuilder(Builder): """ Write a summary with all versionadded/changed directives. """ name = 'changes' def init(self): # type: () -> 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): # type: () -> unicode return self.outdir typemap = { 'versionadded': 'added', 'versionchanged': 'changed', 'deprecated': 'deprecated', } # type: Dict[unicode, unicode] def write(self, *ignored): # type: (Any) -> None version = self.config.version libchanges = {} # type: Dict[unicode, List[Tuple[unicode, unicode, int]]] apichanges = [] # type: List[Tuple[unicode, unicode, int]] otherchanges = {} # type: Dict[Tuple[unicode, unicode], List[Tuple[unicode, unicode, int]]] # NOQA if version not in self.env.versionchanges: logger.info(bold('no changes in version %s.' % version)) return logger.info(bold('writing summary file...')) for type, docname, lineno, module, descname, content in \ self.env.versionchanges[version]: if isinstance(descname, tuple): descname = descname[0] ttext = self.typemap[type] context = content.replace('\n', ' ') if descname and docname.startswith('c-api'): if not descname: continue if context: entry = '<b>%s</b>: <i>%s:</i> %s' % (descname, ttext, context) else: entry = '<b>%s</b>: <i>%s</i>.' % (descname, ttext) apichanges.append((entry, docname, lineno)) elif descname or module: if not module: module = _('Builtins') if not descname: descname = _('Module level') if context: entry = '<b>%s</b>: <i>%s:</i> %s' % (descname, ttext, context) else: entry = '<b>%s</b>: <i>%s</i>.' % (descname, ttext) libchanges.setdefault(module, []).append((entry, docname, lineno)) else: if not context: continue entry = '<i>%s:</i> %s' % (ttext.capitalize(), context) title = self.env.titles[docname].astext() otherchanges.setdefault((docname, title), []).append( (entry, docname, lineno)) ctx = { 'project': self.config.project, 'version': version, 'docstitle': self.config.html_title, 'shorttitle': self.config.html_short_title, 'libchanges': sorted(iteritems(libchanges)), 'apichanges': sorted(apichanges), 'otherchanges': sorted(iteritems(otherchanges)), 'show_copyright': self.config.html_show_copyright, 'show_sphinx': self.config.html_show_sphinx, } with codecs.open(path.join(self.outdir, 'index.html'), 'w', 'utf8') as f: # type: ignore # NOQA f.write(self.templates.render('changes/frameset.html', ctx)) with codecs.open(path.join(self.outdir, 'changes.html'), 'w', 'utf8') as f: # type: ignore # NOQA f.write(self.templates.render('changes/versionchanges.html', ctx)) hltext = ['.. versionadded:: %s' % version, '.. versionchanged:: %s' % version, '.. deprecated:: %s' % version] def hl(no, line): # type: (int, unicode) -> unicode line = '<a name="L%s"> </a>' % no + htmlescape(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 codecs.open(self.env.doc2path(docname), 'r', # type: ignore self.env.config.source_encoding) as f: try: lines = f.readlines() except UnicodeDecodeError: logger.warning('could not read %r for changelog creation', docname) continue targetfn = path.join(self.outdir, 'rst', os_path(docname)) + '.html' ensuredir(path.dirname(targetfn)) with codecs.open(targetfn, 'w', 'utf-8') as f: # type: ignore text = ''.join(hl(i + 1, line) for (i, line) in enumerate(lines)) ctx = { 'filename': self.env.doc2path(docname, None), 'text': text } f.write(self.templates.render('changes/rstsource.html', ctx)) themectx = dict(('theme_' + key, val) for (key, val) in iteritems(self.theme.get_options({}))) copy_asset_file(path.join(package_dir, 'themes', 'default', 'static', 'default.css_t'), self.outdir, context=themectx, renderer=self.templates) copy_asset_file(path.join(package_dir, 'themes', 'basic', 'static', 'basic.css'), self.outdir) def hl(self, text, version): # type: (unicode, unicode) -> unicode text = htmlescape(text) for directive in ['versionchanged', 'versionadded', 'deprecated']: text = text.replace('.. %s:: %s' % (directive, version), '<b>.. %s:: %s</b>' % (directive, version)) return text def finish(self): # type: () -> None pass
def setup(app): # type: (Sphinx) -> Dict[unicode, Any] app.add_builder(ChangesBuilder) return { 'version': 'builtin', 'parallel_read_safe': True, 'parallel_write_safe': True, }