Source code for capsul.sphinxext.pipelinedocgen

# -*- coding: utf-8 -*-
from __future__ import print_function

# System import
from __future__ import absolute_import
import os
import logging
import six
from six.moves import range

# Define logger
logger = logging.getLogger(__file__)

# Capsul import
from capsul.api import get_process_instance


[docs] class PipelineHelpWriter(object): """ Class for automatic generation of pipeline API documentations in Sphinx-parsable reST format. """ # Only separating first two levels rst_section_levels = ['*', '=', '-', '~', '^'] def __init__(self, pipelines, rst_extension=".rst", short_names={}): """ Initialize package for parsing Parameters ---------- pipelines : list (mandatory) list of pipeline class rst_extension : string (optional) extension for reST files, default '.rst' short_names : dict (optional) translation dict for module/pipeline file names """ self.pipelines = sorted(pipelines) self.rst_extension = rst_extension self.short_names = short_names
[docs] def generate_api_doc(self, pipeline, schema): """ Make autodoc documentation for a pipeline python module Parameters ---------- pipeline : string python location of pipeline - e.g 'caps.fmri.PIPELINE' schema : string path to the pipeline representation image Returns ------- ad : string contents of API doc title : string the fist line of the docstring """ # Fiest get the pipeline instance from its string description pipeline_instance = get_process_instance(pipeline) # Get the header, ie. the first line of the docstring # Default title is '' header = pipeline_instance.__doc__ title = "" if header: title = pipeline_instance.__doc__.splitlines()[0] # Add header to tell us that this documentation must not be edited ad = ".. AUTO-GENERATED FILE -- DO NOT EDIT!\n\n" # Generate the page title: name of the pipeline ad += ":orphan:\n\n" # Set the current module currentmodule = ".".join(pipeline_instance.id.split(".")[:-1]) ad += ".. currentmodule:: {0}\n\n".format(currentmodule) # Generate a bookmark (for cross references) pipeline_name = pipeline_instance.__class__.__name__ label = pipeline + ":" ad += "\n.. _{0}\n\n".format(label) chap_title = pipeline ad += (chap_title + "\n" + self.rst_section_levels[1] * len(chap_title) + "\n\n") # Add a subtitle ad += (pipeline_name + "\n" + self.rst_section_levels[2] * len(pipeline_name) + "\n\n") # Then add the trait description # It will generate two sections: input and output ad += pipeline_instance.get_help(returnhelp=True, use_labels=True) # Add schema if generated if schema: schama_title = "Pipeline schema" ad += ("\n" + schama_title + "\n" + "~" * len(schama_title) + "\n\n") ad += ".. image:: {0}\n".format(schema) ad += " :height: 400px\n" ad += " :align: center\n\n" return ad, title
[docs] def write_api_docs(self, outdir=None, returnrst=False): """ Generate API reST files. Parameters ---------- outdir : string (optional, default None) directory name in which to store files. Automatic filenames are created for each module. returnrst: bool (optional, default False) if True return the rst string documentation, otherwise write it to disk. Notes ----- Sets self.written_modules to list of written modules """ # Check output directory if returnrst is False: if not isinstance(outdir, six.string_types): raise Exception("If 'returnrst' is False, need a valid output " "directory.") if not os.path.exists(outdir): os.makedirs(outdir) else: rstdoc = {} # Generate reST API written_modules = [] for pipeline in self.pipelines: # Information message logger.info("Processing pipeline '{0}'...".format(pipeline)) pipeline_short = self.get_short_name(pipeline) # Check if an image representation of the pipeline exists if returnrst is False: schema = os.path.join(os.pardir, "schema", pipeline_short + ".png") if not os.path.isfile(os.path.join(outdir, schema)): schema = None else: schema = None # Generate the rst string description api_str, title_str = self.generate_api_doc(pipeline, schema) if not api_str: continue # Write to file if returnrst is False: outfile = os.path.join(outdir, pipeline_short + self.rst_extension) fileobj = open(outfile, "wt") fileobj.write(api_str) fileobj.close() else: rstdoc[pipeline] = api_str # Update the list of written modules written_modules.append((title_str, pipeline)) # Update the class attribute containing the list of written modules self.written_modules = written_modules if returnrst is True: return rstdoc
[docs] def get_short_name(self, name): """ Get a short file name prefix for module/process in the short_names dict. Used to build "reasonably short" path/file names. """ short_name = self.short_names.get(name) if short_name: return short_name # look for a shorter name for the longest module prefix modules = name.split(".") for i in range(len(modules)-1, 0, -1): path = '.'.join(modules[:i]) short_path = self.short_names.get(path) if short_path: return '.'.join([short_path] + modules[i+1:]) # not found return name
[docs] def write_index(self, outdir, froot="index", relative_to=None, rst_extension=".rst"): """ Make a reST API index file from the list of written files Parameters ---------- outdir : string (mandatory) directory to which to write generated index file froot : string (optional) root (filename without extension) of filename to write to Defaults to 'index'. We add ``rst_extension``. relative_to : string path to which written filenames are relative. This component of the written file path will be removed from outdir, in the generated index. Default is None, meaning, leave path as it is. rst_extension : string (optional) extension for reST files, default '.rst' """ # Check if some modules have been written if self.written_modules is None: raise ValueError('No modules written') # Get full index filename path path = os.path.join(outdir, froot + rst_extension) # Path written into index is relative to rootpath if relative_to is not None: relpath = outdir.replace(relative_to + os.path.sep, "") else: relpath = outdir print('relpath:', relpath) # Information message logger.info("Writing index at location '{0}'...".format( os.path.abspath(path))) # Edit the index file idx = open(path, "wt") w = idx.write # Add header to tell us that this documentation must not be edited w(".. AUTO-GENERATED FILE -- DO NOT EDIT!\n\n") # Generate a table with all the generated modules # module_name (link) + first docstring line w(".. raw:: html\n\n") # Table definition table = ["<!-- Block section -->"] table.append("<table border='1' class='docutils' style='width:100%'>") table.append("<colgroup><col width='25%'/><col width='75%'/>" "</colgroup>") table.append("<tbody valign='top'>") # Add all modules for title_str, f in self.written_modules: pipeline_short = self.get_short_name(f) print('title_str:', title_str, ', f:', f) relative_pipeline = ".".join(f.split(".")[2:]) print('relative_pipeline:', relative_pipeline) ref = os.path.join(relpath, pipeline_short + ".html") print('ref:', ref) table.append("<tr class='row-odd'>") table.append( "<td><a class='reference internal' href='{0}'>" "<em>{1}</em></a></td>\n".format(ref, relative_pipeline)) table.append("<td>{0}</td>".format(title_str)) table.append("</tr>") # Close divs table.append("</tbody>\n\n") table.append("</table>") # Format the table table_with_indent = [" " * 4 + line for line in table] w("\n".join(table_with_indent)) # Close the file idx.close()
[docs] def write_main_index(self, outdir, module_name, root_module_name, froot="index", rst_extension=".rst", have_usecases=True): """ Make a reST API index file for the module Parameters ---------- outdir : string (mandatory) Directory to which to write generated index file module_name: str (mandatory) The name of module from which we want to generate an index. root_module_name: str (mandatory) The python package name froot : string (optional) root (filename without extension) of filename to write to Defaults to 'index'. We add ``rst_extension``. rst_extension : string (optional) Extension for reST files, default '.rst' """ # Get full index filename path path = os.path.join(outdir, froot + rst_extension) # Information message logger.info("Writing module '{0}' index at location '{1}'...".format( module_name, os.path.abspath(path))) # Open the result index file idx = open(path, "wt") # Stat writing w = idx.write # Add header to tell us that this documentation must not be edited w(".. AUTO-GENERATED FILE -- DO NOT EDIT!\n\n") w(":orphan:\n\n") # Generate a title chap_title = " ".join([x.capitalize() for x in module_name.split("_")]) w(chap_title + "\n" + self.rst_section_levels[0] * len(chap_title) + "\n\n") # Generate a markup label = module_name w(".. _{0}:\n\n".format(label)) # Page use cases # # Generate a title chap_title = ":mod:`{0}.{1}`: User Guide".format( root_module_name, module_name) w(chap_title + "\n" + self.rst_section_levels[1] * len(chap_title) + "\n\n") if have_usecases: # # Generate a markup label = module_name + "_ug" w(".. _{0}:\n\n".format(label)) # # Some text description w("Some live examples containing snippets of codes.\n\n") # # Include user guide index w(".. include:: use_cases/index%s\n\n" % rst_extension) # API page # # Generate a title chap_title = ":mod:`{0}.{1}`: API".format( root_module_name, module_name) w(chap_title + "\n" + self.rst_section_levels[1] * len(chap_title) + "\n\n") # # Generate a markup label = module_name + "_api" w(".. _{0}:\n\n".format(label)) # # Some text description w("The API of functions and classes, as given by the " "docstrings.") if have_usecases: w(" For the *user guide* see the {0}_ug_ " "section for further details.\n\n".format(module_name)) else: w("\n\n") # # Include pipeline and buildingblock indexes # ## Pipeline chap_title = "Pipelines" w(chap_title + "\n" + self.rst_section_levels[2] * len(chap_title) + "\n\n") w(".. include:: pipeline/index%s\n\n" % rst_extension) # ## Buildingblocks chap_title = "Buildingblocks" w(chap_title + "\n" + self.rst_section_levels[2] * len(chap_title) + "\n\n") w(".. include:: process/index%s\n\n" % rst_extension) # Close file idx.close()