Source code for capsul.sphinxext.usecasesdocgen
# -*- coding: utf-8 -*-
##########################################################################
# CAPSUL - CAPS - Copyright (C) CEA, 2013
# Distributed under the terms of the CeCILL-B license, as published by
# the CEA-CNRS-INRIA. Refer to the LICENSE file or to
# http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.html
# for details.
##########################################################################
# System import
from __future__ import absolute_import
import inspect
import ast
import os
import logging
import six
from six.moves import range
# Define logger
logger = logging.getLogger(__file__)
[docs]
class UseCasesHelperWriter(object):
""" A basic class to convert the pilot codes to rst use cases
"""
def __init__(self, pilots, rst_extension=".rst"):
""" Initialize the UseCasesHelper class
Parameters
----------
pilots: list of @function (mandatory)
list of pilot functions.
rst_extension : string (optional)
Extension for reST files, default '.rst'.
"""
self.pilots = pilots
self.rst_extension = rst_extension
[docs]
def getsource(self, function):
""" Method that returns the source code of a function
Parameters
----------
function: @function (mandatory)
a python function.
Returns
-------
srccode: str
the function source code.
"""
return inspect.getsource(function)
[docs]
def generate_usecases_doc(self, src_code, module_name):
""" Make autodoc documentation of pilots
Parameters
----------
src_code : string
pilot source code.
Returns
-------
ad : string
the use case reST formatted documentation.
"""
# First parse the pilot function code
code_tree = ast.parse(src_code).body
pilot_tree = code_tree[0].body
# Then reST formatting
lines = src_code.splitlines()
nb_lines = len(lines)
ad = ".. AUTO-GENERATED FILE -- DO NOT EDIT!\n\n"
ad += ":orphan:\n\n"
ad += ".. _example_{0} :\n\n".format(module_name)
line_start_code = 0
line_end_code = 0
is_header = True
full_code = "# The full use case code: {0}\n".format(module_name)
for code_item in pilot_tree:
if (isinstance(code_item, ast.Expr) and
isinstance(code_item.value, ast.Str)):
# Find End code line
code_value = lines[line_start_code:line_end_code]
for line_index in range(line_end_code, nb_lines):
clean_line = lines[line_index].lstrip()
if clean_line[:3] in ["'''", '"""']:
break
else:
code_value.append(lines[line_index])
# Add code value to reST documentation
if line_start_code != 0:
full_code += "\n".join(code_value)
ad += "::\n\n"
ad += "\n".join(code_value)
ad += "\n\n"
# Add expand box with full code in header
if is_header:
code = ".. hidden-code-block:: python\n"
code += " :starthidden: True\n\n"
code += " $FULL_CODE"
# Add comments to reST
comment = inspect.cleandoc(code_item.value.s)
# Insert full code after main title
if is_header:
comment = comment.splitlines()
comment.insert(3, code)
comment.insert(3, "")
ad += "\n".join(comment)
is_header = False
else:
ad += comment
ad += "\n\n"
line_start_code = code_item.lineno
else:
line_end_code = code_item.lineno + 1
# Add the end of the file
if line_start_code != nb_lines:
full_code += "\n".join(lines[line_start_code:])
ad += "::\n\n"
ad += "\n".join(lines[line_start_code:])
ad += "\n\n"
# Insert full use case code
ad = ad.replace("$FULL_CODE", full_code)
return ad
[docs]
def write_usecases_docs(self, outdir=None, returnrst=False):
""" Generate Use Cases reST files.
Parameters
----------
outdir : string
Directory name in which to store files.
We create automatic filenames for each Use case.
returnrst: bool (optional, default False)
if True return the rst string documentation,
otherwise write it to disk.
"""
# 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 = {}
# Write reST use cases documentation
written_usecases = []
for pilot in self.pilots:
# Information message
logger.info("Processing pilot '{0}' in module '{1}'...".format(
pilot.__name__, pilot.__module__))
# Generate reST
uid = pilot.__module__ + "." + pilot.__name__
code_str = self.getsource(pilot)
use_case_str = self.generate_usecases_doc(code_str, uid)
title_str = code_str.splitlines()[3].strip()
if not use_case_str:
continue
# Write to file
if returnrst is False:
outfile = os.path.join(outdir, uid + self.rst_extension)
fileobj = open(outfile, "wt")
fileobj.write(use_case_str)
fileobj.close()
else:
rstdoc[uid] = use_case_str
# Update the list of written use cases
written_usecases.append((title_str, uid))
# Update the class attribute containing the list of written use cases
self.written_usecases = written_usecases
if returnrst is True:
return rstdoc
[docs]
def write_index(self, outdir, froot="index", relative_to=None,
rst_extension=".rst"):
""" Make a reST API index file from 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'
"""
if self.written_usecases is None:
raise ValueError('No modules written')
# Get full 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
idx = open(path, "wt")
w = idx.write
w(".. AUTO-GENERATED FILE -- DO NOT EDIT!\n\n")
w(".. raw:: html\n\n")
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'>")
for title_str, f in self.written_usecases:
# generate the relative pipeline name
relative_uid = ".".join(f.split(".")[2:])
ref = os.path.join(relpath, f + ".html")
table.append("<tr class='row-odd'>")
table.append(
"<td><a class='reference internal' href='{0}'>"
"<em>{1}</em></a></td>\n".format(ref, relative_uid))
table.append("<td>{0}</td>".format(title_str))
table.append("</tr>")
table.append("</tbody>\n\n")
table.append("</table>")
table_with_indent = [" " * 4 + line for line in table]
w("\n".join(table_with_indent))
idx.close()