Source code for granite.sphinx

"""
Provides the main DocBuilder class for easily interfacing with sphinx to build
the project's documentation.
"""

from __future__ import absolute_import

import argparse
import os
import sys
import shutil


from sphinx.cmd import build
from sphinx.ext import apidoc

from granite.exceptions import GraniteException


[docs]class RequiredAttributeException(GraniteException): """Raised when an attribute on the DocBuilder class has not been defined""" def __init__(self, attribute, class_, description): super(RequiredAttributeException, self).__init__( 'The required attribute, "{}", has not been defined on the class ' '"{}". The attribute should contain {}' .format(attribute, class_.__name__, description) )
[docs]class DocBuilder(object): """ Provides a simple interface for consistently building documentation. By default, this class will generate the API docs using sphinx's apidoc script, overwriting any existing API .rst files (provided the ``API_OUTPUT_DIR`` attribute has been set. Afterward, sphinx itself is run on the project. Projects are expected to create a file named ``build_docs.py`` (or something similar) within the ``docs`` directory of your project that contains a subclass of this class, instantiate it, then run its build method. An example:: import os import sys THIS_DIR = os.path.dirname(__file__) sys.path.insert(0, os.path.join(THIS_DIR, '..', '..', 'src')) from granite.sphinx import DocBuilder def mkpath(*parts): \"\"\"Makes an abspath from THIS_DIR\"\"\" return os.path.normpath(os.path.join(THIS_DIR, *parts)) class Builder(DocBuilder): # input path SOURCE_DIR = mkpath('source') # output path BUILD_DIR = mkpath('build') # remove this line if auto-api generation is not desired API_OUTPUT_DIR = mkpath('source', 'api') # the path to the python package PROJECT_DIR = mkpath('..', 'src', 'granite') FILES_TO_CLEAN = [ API_OUTPUT_DIR, BUILD_DIR, ] if __name__ == '__main__': Builder().build() Each of the following attributes should be defined in order to configure DcoBuilder. Each attribute that is a path should be an absolute path. Attributes: SOURCE_DIR (str): the path to the project's source directory BUILD_DIR (str): the path to the documentation output API_OUTPUT_DIR (str): defining this will automatically call sphinx-apidoc on the project directory. See the generate_api_docs() method for more information. *Optional* PROJECT_DIR (str): path to the directory containing Python project. FILES_TO_CLEAN (List[str]): a list of files to clean when ``--clean`` is passed on the command line. *Optional* API_EXCLUDE_DIRS (List[str]): a list of paths relative to PROJECT_DIR to exclude from the api-doc generation. """ SOURCE_DIR = '' BUILD_DIR = '' API_OUTPUT_DIR = '' PROJECT_DIR = '' API_EXCLUDE_DIRS = [] FILES_TO_CLEAN = [] def __init__(self): for attribute, description in ( ('SOURCE_DIR', 'a path to the documentation source directory.'), ('BUILD_DIR', 'a directory place the built documentation.'), ('PROJECT_DIR', 'the path to the project package directory (the directory ' 'containing the topmost __init__.py).'), ): if not getattr(self, attribute, None): raise RequiredAttributeException( attribute, self.__class__, description) self.parser = argparse.ArgumentParser() self.args = None self.argv = []
[docs] def safe_delete(self, filename): """ Tries to delete ``filename`` and ignores any error that is raised. """ try: os.remove(filename) except OSError: pass
[docs] def generate_api_docs(self): """ Generates the API documentation for all of the packages/modules/classes/functions. Sphinx doesn't automatically generate the documentation for the api. This calls sphinx-apidoc which will create the API .rst files and dump them in the source directory. It is expected that one of the TOC directives calls out to the created API directory. *Note:* if the attribute ``API_OUTPUT_DIR`` is not set on this class, then this method does nothing. """ if self.API_OUTPUT_DIR: args = [ # Put documentation for each module on its own page '-e', # don't create the "modules.rst" file (the table of contents # file) as this is already provided by the package's main rst # file. '-T', # Overwrite existing files '--force', '-o', self.API_OUTPUT_DIR, # the package to generate docs from self.PROJECT_DIR ] excludes = [ os.path.join(self.PROJECT_DIR, p) if not os.path.isabs(p) else p for p in self.API_EXCLUDE_DIRS ] apidoc.main(args + excludes)
[docs] def generate_documentation(self): """ Runs sphinx on the project using the default conf.py file in the source directory. """ self.generate_api_docs() build.main([ self.SOURCE_DIR, self.BUILD_DIR, ])
[docs] def try_clean(self): """ Attempts to clean all of the files found in ``self.FILES_TO_CLEAN``. Ignores all errors. """ for f in self.FILES_TO_CLEAN: if not os.path.exists(f): continue if os.path.isdir(f): # don't care on error shutil.rmtree(f, onerror=lambda *x, **y: None) else: self.safe_delete(f)
[docs] def add_argument(self, *args, **kwargs): """ Add an argument to the ArgumentParser in ``self.parser``. Takes in the same args and kwargs as the :meth:`ArgumentParser.add_argument` method. Use this method in order to add custom flags to the argument parsing. The argv flags are parsed in the ``build()`` method. This sets the parsed args into ``self.args`` and the leftover unknown flags into ``self.argv``. """ self.parser.add_argument(*args, **kwargs)
[docs] def setup_default_arguments(self): """ This method adds some default command line parameters. Current, the default flags are: - ``--clean``: Cleans all files found in the ``FILES_TO_CLEAN`` list. """ self.add_argument('--clean', action='store_true', help='Cleans all generated files.')
[docs] def pre_build_hook(self): """ This is called after all arguments have been collected, but before sphinx is called. Override this method for any custom functionality. """
[docs] def post_build_hook(self): """ This is called immediately after all documentation has been generated. Override this method for any custom functionality. """
[docs] def build(self, argv=None): """ Gathers all command line arguments and then builds the docs. This performs command line parsing and stores the known flags (those added with ``self.add_argument()``) into ``self.args`` and all leftover unknown args into ``self.argv`` (see :meth:`argparse.ArgumentParser.parse_known_args` for more information on the types of each). After argparsing the following three methods are called in this order: * :meth:`pre_build_hook` * :meth:`generate_documentation` * :meth:`post_build_hook` Override the pre and post build hooks in order to add custom checks or other functionality. Args: argv (List[str]): command line flags to parse; defaults to sys.argv """ if argv is None: argv = sys.argv self.setup_default_arguments() self.args, self.argv = self.parser.parse_known_args(argv) if self.args.clean: self.try_clean() self.pre_build_hook() self.generate_documentation() self.post_build_hook()