summaryrefslogtreecommitdiffstats
path: root/sphinx/builders/texinfo.py
diff options
context:
space:
mode:
Diffstat (limited to 'sphinx/builders/texinfo.py')
-rw-r--r--sphinx/builders/texinfo.py229
1 files changed, 229 insertions, 0 deletions
diff --git a/sphinx/builders/texinfo.py b/sphinx/builders/texinfo.py
new file mode 100644
index 0000000..441b598
--- /dev/null
+++ b/sphinx/builders/texinfo.py
@@ -0,0 +1,229 @@
+"""Texinfo builder."""
+
+from __future__ import annotations
+
+import os
+import warnings
+from os import path
+from typing import TYPE_CHECKING, Any
+
+from docutils import nodes
+from docutils.frontend import OptionParser
+from docutils.io import FileOutput
+
+from sphinx import addnodes, package_dir
+from sphinx.builders import Builder
+from sphinx.environment.adapters.asset import ImageAdapter
+from sphinx.errors import NoUri
+from sphinx.locale import _, __
+from sphinx.util import logging
+from sphinx.util.console import darkgreen # type: ignore[attr-defined]
+from sphinx.util.display import progress_message, status_iterator
+from sphinx.util.docutils import new_document
+from sphinx.util.fileutil import copy_asset_file
+from sphinx.util.nodes import inline_all_toctrees
+from sphinx.util.osutil import SEP, ensuredir, make_filename_from_project
+from sphinx.writers.texinfo import TexinfoTranslator, TexinfoWriter
+
+if TYPE_CHECKING:
+ from collections.abc import Iterable
+
+ from docutils.nodes import Node
+
+ from sphinx.application import Sphinx
+ from sphinx.config import Config
+
+logger = logging.getLogger(__name__)
+template_dir = os.path.join(package_dir, 'templates', 'texinfo')
+
+
+class TexinfoBuilder(Builder):
+ """
+ Builds Texinfo output to create Info documentation.
+ """
+ name = 'texinfo'
+ format = 'texinfo'
+ epilog = __('The Texinfo files are in %(outdir)s.')
+ if os.name == 'posix':
+ epilog += __("\nRun 'make' in that directory to run these through "
+ "makeinfo\n"
+ "(use 'make info' here to do that automatically).")
+
+ supported_image_types = ['image/png', 'image/jpeg',
+ 'image/gif']
+ default_translator_class = TexinfoTranslator
+
+ def init(self) -> None:
+ self.docnames: Iterable[str] = []
+ self.document_data: list[tuple[str, str, str, str, str, str, str, bool]] = []
+
+ def get_outdated_docs(self) -> str | list[str]:
+ return 'all documents' # for now
+
+ def get_target_uri(self, docname: str, typ: str | None = None) -> str:
+ if docname not in self.docnames:
+ raise NoUri(docname, typ)
+ return '%' + docname
+
+ def get_relative_uri(self, from_: str, to: str, typ: str | None = None) -> str:
+ # ignore source path
+ return self.get_target_uri(to, typ)
+
+ def init_document_data(self) -> None:
+ preliminary_document_data = [list(x) for x in self.config.texinfo_documents]
+ if not preliminary_document_data:
+ logger.warning(__('no "texinfo_documents" config value found; no documents '
+ 'will be written'))
+ return
+ # assign subdirs to titles
+ self.titles: list[tuple[str, str]] = []
+ for entry in preliminary_document_data:
+ docname = entry[0]
+ if docname not in self.env.all_docs:
+ logger.warning(__('"texinfo_documents" config value references unknown '
+ 'document %s'), docname)
+ continue
+ self.document_data.append(entry) # type: ignore[arg-type]
+ if docname.endswith(SEP + 'index'):
+ docname = docname[:-5]
+ self.titles.append((docname, entry[2]))
+
+ def write(self, *ignored: Any) -> None:
+ self.init_document_data()
+ self.copy_assets()
+ for entry in self.document_data:
+ docname, targetname, title, author = entry[:4]
+ targetname += '.texi'
+ direntry = description = category = ''
+ if len(entry) > 6:
+ direntry, description, category = entry[4:7]
+ toctree_only = False
+ if len(entry) > 7:
+ toctree_only = entry[7]
+ destination = FileOutput(
+ destination_path=path.join(self.outdir, targetname),
+ encoding='utf-8')
+ with progress_message(__("processing %s") % targetname):
+ appendices = self.config.texinfo_appendices or []
+ doctree = self.assemble_doctree(docname, toctree_only, appendices=appendices)
+
+ with progress_message(__("writing")):
+ self.post_process_images(doctree)
+ docwriter = TexinfoWriter(self)
+ with warnings.catch_warnings():
+ warnings.filterwarnings('ignore', category=DeprecationWarning)
+ # DeprecationWarning: The frontend.OptionParser class will be replaced
+ # by a subclass of argparse.ArgumentParser in Docutils 0.21 or later.
+ settings: Any = OptionParser(
+ defaults=self.env.settings,
+ components=(docwriter,),
+ read_config_files=True).get_default_values()
+ settings.author = author
+ settings.title = title
+ settings.texinfo_filename = targetname[:-5] + '.info'
+ settings.texinfo_elements = self.config.texinfo_elements
+ settings.texinfo_dir_entry = direntry or ''
+ settings.texinfo_dir_category = category or ''
+ settings.texinfo_dir_description = description or ''
+ settings.docname = docname
+ doctree.settings = settings
+ docwriter.write(doctree, destination)
+ self.copy_image_files(targetname[:-5])
+
+ def assemble_doctree(
+ self, indexfile: str, toctree_only: bool, appendices: list[str],
+ ) -> nodes.document:
+ self.docnames = set([indexfile] + appendices)
+ logger.info(darkgreen(indexfile) + " ", nonl=True)
+ tree = self.env.get_doctree(indexfile)
+ tree['docname'] = indexfile
+ if toctree_only:
+ # extract toctree nodes from the tree and put them in a
+ # fresh document
+ new_tree = new_document('<texinfo output>')
+ new_sect = nodes.section()
+ new_sect += nodes.title('<Set title in conf.py>',
+ '<Set title in conf.py>')
+ new_tree += new_sect
+ for node in tree.findall(addnodes.toctree):
+ new_sect += node
+ tree = new_tree
+ largetree = inline_all_toctrees(self, self.docnames, indexfile, tree,
+ darkgreen, [indexfile])
+ largetree['docname'] = indexfile
+ for docname in appendices:
+ appendix = self.env.get_doctree(docname)
+ appendix['docname'] = docname
+ largetree.append(appendix)
+ logger.info('')
+ logger.info(__("resolving references..."))
+ self.env.resolve_references(largetree, indexfile, self)
+ # TODO: add support for external :ref:s
+ for pendingnode in largetree.findall(addnodes.pending_xref):
+ docname = pendingnode['refdocname']
+ sectname = pendingnode['refsectname']
+ newnodes: list[Node] = [nodes.emphasis(sectname, sectname)]
+ for subdir, title in self.titles:
+ if docname.startswith(subdir):
+ newnodes.append(nodes.Text(_(' (in ')))
+ newnodes.append(nodes.emphasis(title, title))
+ newnodes.append(nodes.Text(')'))
+ break
+ else:
+ pass
+ pendingnode.replace_self(newnodes)
+ return largetree
+
+ def copy_assets(self) -> None:
+ self.copy_support_files()
+
+ def copy_image_files(self, targetname: str) -> None:
+ if self.images:
+ stringify_func = ImageAdapter(self.app.env).get_original_image_uri
+ for src in status_iterator(self.images, __('copying images... '), "brown",
+ len(self.images), self.app.verbosity,
+ stringify_func=stringify_func):
+ dest = self.images[src]
+ try:
+ imagedir = path.join(self.outdir, targetname + '-figures')
+ ensuredir(imagedir)
+ copy_asset_file(path.join(self.srcdir, src),
+ path.join(imagedir, dest))
+ except Exception as err:
+ logger.warning(__('cannot copy image file %r: %s'),
+ path.join(self.srcdir, src), err)
+
+ def copy_support_files(self) -> None:
+ try:
+ with progress_message(__('copying Texinfo support files')):
+ logger.info('Makefile ', nonl=True)
+ copy_asset_file(os.path.join(template_dir, 'Makefile'), self.outdir)
+ except OSError as err:
+ logger.warning(__("error writing file Makefile: %s"), err)
+
+
+def default_texinfo_documents(
+ config: Config,
+) -> list[tuple[str, str, str, str, str, str, str]]:
+ """ Better default texinfo_documents settings. """
+ filename = make_filename_from_project(config.project)
+ return [(config.root_doc, filename, config.project, config.author, filename,
+ 'One line description of project', 'Miscellaneous')]
+
+
+def setup(app: Sphinx) -> dict[str, Any]:
+ app.add_builder(TexinfoBuilder)
+
+ app.add_config_value('texinfo_documents', default_texinfo_documents, False)
+ app.add_config_value('texinfo_appendices', [], False)
+ app.add_config_value('texinfo_elements', {}, False)
+ app.add_config_value('texinfo_domain_indices', True, False, [list])
+ app.add_config_value('texinfo_show_urls', 'footnote', False)
+ app.add_config_value('texinfo_no_detailmenu', False, False)
+ app.add_config_value('texinfo_cross_references', True, False)
+
+ return {
+ 'version': 'builtin',
+ 'parallel_read_safe': True,
+ 'parallel_write_safe': True,
+ }