diff --git a/CHANGES b/CHANGES index 9fd548371..e5c83ddb0 100644 --- a/CHANGES +++ b/CHANGES @@ -7,6 +7,8 @@ Dependencies Incompatible changes -------------------- +* apidoc: template files are renamed to ``.rst_t`` + Deprecated ---------- @@ -27,6 +29,7 @@ Features added * #5124: graphviz: ``:graphviz_dot:`` option is renamed to ``:layout:`` * #1464: html: emit a warning if :confval:`html_static_path` and :confval:`html_extra_path` directories are inside output directory +* #5602: apidoc: Add ``--templatedir`` option Bugs fixed ---------- diff --git a/doc/man/sphinx-apidoc.rst b/doc/man/sphinx-apidoc.rst index aef497e0d..78c0735cb 100644 --- a/doc/man/sphinx-apidoc.rst +++ b/doc/man/sphinx-apidoc.rst @@ -126,6 +126,30 @@ These options are used when :option:`--full` is specified: Sets the project release to put in generated files (see :confval:`release`). +.. rubric:: Project templating + +.. versionadded:: 2.2 + Project templating options for sphinx-apidoc + +.. option:: -t, --templatedir=TEMPLATEDIR + + Template directory for template files. You can modify the templates of + sphinx project files generated by apidoc. Following Jinja2 template + files are allowed: + + * ``module.rst_t`` + * ``package.rst_t`` + * ``toc.rst_t`` + * ``master_doc.rst_t`` + * ``conf.py_t`` + * ``Makefile_t`` + * ``Makefile.new_t`` + * ``make.bat_t`` + * ``make.bat.new_t`` + + In detail, please refer the system template files Sphinx provides. + (``sphinx/templates/apidoc`` and ``sphinx/templates/quickstart``) + Environment ----------- diff --git a/sphinx/ext/apidoc.py b/sphinx/ext/apidoc.py index d38b749f0..ed413de52 100644 --- a/sphinx/ext/apidoc.py +++ b/sphinx/ext/apidoc.py @@ -111,8 +111,8 @@ def format_directive(module, package=None): return directive -def create_module_file(package, basename, opts): - # type: (str, str, Any) -> None +def create_module_file(package, basename, opts, user_template_dir=None): + # type: (str, str, Any, str) -> None """Build the text of the file and write the file.""" qualname = module_join(package, basename) context = { @@ -121,12 +121,13 @@ def create_module_file(package, basename, opts): 'qualname': qualname, 'automodule_options': OPTIONS, } - text = ReSTRenderer(template_dir).render('module.rst', context) + text = ReSTRenderer([user_template_dir, template_dir]).render('module.rst_t', context) write_file(qualname, text, opts) -def create_package_file(root, master_package, subroot, py_files, opts, subs, is_namespace, excludes=[]): # NOQA - # type: (str, str, str, List[str], Any, List[str], bool, List[str]) -> None +def create_package_file(root, master_package, subroot, py_files, opts, subs, + is_namespace, excludes=[], user_template_dir=None): + # type: (str, str, str, List[str], Any, List[str], bool, List[str], str) -> None """Build the text of the file and write the file.""" # build a list of sub packages (directories containing an INITPY file) subpackages = [sub for sub in subs if not @@ -151,7 +152,7 @@ def create_package_file(root, master_package, subroot, py_files, opts, subs, is_ 'automodule_options': OPTIONS, 'show_headings': not opts.noheadings, } - text = ReSTRenderer(template_dir).render('package.rst', context) + text = ReSTRenderer([user_template_dir, template_dir]).render('package.rst_t', context) write_file(pkgname, text, opts) if submodules and opts.separatemodules: @@ -159,8 +160,8 @@ def create_package_file(root, master_package, subroot, py_files, opts, subs, is_ create_module_file(None, submodule, opts) -def create_modules_toc_file(modules, opts, name='modules'): - # type: (List[str], Any, str) -> None +def create_modules_toc_file(modules, opts, name='modules', user_template_dir=None): + # type: (List[str], Any, str, str) -> None """Create the module's index.""" modules.sort() prev_module = '' @@ -176,7 +177,7 @@ def create_modules_toc_file(modules, opts, name='modules'): 'maxdepth': opts.maxdepth, 'docnames': modules, } - text = ReSTRenderer(template_dir).render('toc.rst', context) + text = ReSTRenderer([user_template_dir, template_dir]).render('toc.rst_t', context) write_file(name, text, opts) @@ -220,8 +221,8 @@ def is_skipped_module(filename, opts, excludes): return False -def recurse_tree(rootpath, excludes, opts): - # type: (str, List[str], Any) -> List[str] +def recurse_tree(rootpath, excludes, opts, user_template_dir=None): + # type: (str, List[str], Any, str) -> List[str] """ Look for every file in the directory tree and create the corresponding ReST files. @@ -271,7 +272,8 @@ def recurse_tree(rootpath, excludes, opts): # a namespace and there is something there to document if not is_namespace or len(py_files) > 0: create_package_file(root, root_package, subpackage, - py_files, opts, subs, is_namespace, excludes) + py_files, opts, subs, is_namespace, excludes, + user_template_dir) toplevels.append(module_join(root_package, subpackage)) else: # if we are at the root level, we don't require it to be a package @@ -279,7 +281,7 @@ def recurse_tree(rootpath, excludes, opts): for py_file in py_files: if not is_skipped_module(path.join(rootpath, py_file), opts, excludes): module = path.splitext(py_file)[0] - create_module_file(root_package, module, opts) + create_module_file(root_package, module, opts, user_template_dir) toplevels.append(module) return toplevels @@ -386,6 +388,11 @@ Note: By default this script will not overwrite already created files.""")) const='sphinx.ext.%s' % ext, dest='extensions', help=__('enable %s extension') % ext) + group = parser.add_argument_group(__('Project templating')) + group.add_argument('-t', '--templatedir', metavar='TEMPLATEDIR', + dest='templatedir', + help=__('template directory for template files')) + return parser @@ -412,7 +419,7 @@ def main(argv=sys.argv[1:]): if not args.dryrun: ensuredir(args.destdir) excludes = [path.abspath(exclude) for exclude in args.exclude_pattern] - modules = recurse_tree(rootpath, excludes, args) + modules = recurse_tree(rootpath, excludes, args, args.templatedir) if args.full: from sphinx.cmd import quickstart as qs @@ -455,9 +462,10 @@ def main(argv=sys.argv[1:]): d['extensions'].extend(ext.split(',')) if not args.dryrun: - qs.generate(d, silent=True, overwrite=args.force) + qs.generate(d, silent=True, overwrite=args.force, + templatedir=args.templatedir) elif args.tocfile: - create_modules_toc_file(modules, args, args.tocfile) + create_modules_toc_file(modules, args, args.tocfile, args.templatedir) return 0 diff --git a/sphinx/templates/apidoc/module.rst b/sphinx/templates/apidoc/module.rst_t similarity index 100% rename from sphinx/templates/apidoc/module.rst rename to sphinx/templates/apidoc/module.rst_t diff --git a/sphinx/templates/apidoc/package.rst b/sphinx/templates/apidoc/package.rst_t similarity index 100% rename from sphinx/templates/apidoc/package.rst rename to sphinx/templates/apidoc/package.rst_t diff --git a/sphinx/templates/apidoc/toc.rst b/sphinx/templates/apidoc/toc.rst_t similarity index 100% rename from sphinx/templates/apidoc/toc.rst rename to sphinx/templates/apidoc/toc.rst_t diff --git a/sphinx/util/template.py b/sphinx/util/template.py index 636767d41..fd8886944 100644 --- a/sphinx/util/template.py +++ b/sphinx/util/template.py @@ -9,7 +9,7 @@ """ import os -from typing import Dict +from typing import Dict, List, Union from jinja2.loaders import BaseLoader from jinja2.sandbox import SandboxedEnvironment @@ -34,7 +34,13 @@ class BaseRenderer: class FileRenderer(BaseRenderer): - def __init__(self, search_path: str) -> None: + def __init__(self, search_path: Union[str, List[str]]) -> None: + if isinstance(search_path, str): + search_path = [search_path] + else: + # filter "None" paths + search_path = list(filter(None, search_path)) + loader = SphinxFileSystemLoader(search_path) super().__init__(loader) @@ -46,7 +52,7 @@ class FileRenderer(BaseRenderer): class SphinxRenderer(FileRenderer): - def __init__(self, template_path: str = None) -> None: + def __init__(self, template_path: Union[str, List[str]] = None) -> None: if template_path is None: template_path = os.path.join(package_dir, 'templates') super().__init__(template_path) @@ -76,7 +82,7 @@ class LaTeXRenderer(SphinxRenderer): class ReSTRenderer(SphinxRenderer): - def __init__(self, template_path: str = None, language: str = None) -> None: + def __init__(self, template_path: Union[str, List[str]] = None, language: str = None) -> None: # NOQA super().__init__(template_path) # add language to environment