Skip to content
Browse files

add sphinx command runpython to produce RST documentation

  • Loading branch information...
sdpython committed Aug 5, 2015
1 parent 07ab347 commit 7b2fc77329a383432ec7e9cdf3b8e71de90d2fe2
@@ -55,17 +55,15 @@ This project contains the following folders:

* a source folder: ``src``
* a unit test folder: ``_unittests``, go to this folder and run ````
* a _doc folder: ``_doc``, it will contain the documentation
* a folder: ``_doc``, it will contain the documentation, a subfolder ``_doc/sphinxdox/source/blog`` contains blog post
to communicate on the module
* a file ```` to build and to install the module, if the source were retrieve from GitHub,
the script can also be called with the following extra options (``python <option>``):
* clean_space: remove extra spaces in the code
* clean_pyd: remove files *.pyd
* build_sphinx: builds the documentation
* unittests: run the unit tests, compute the code coverage

* a script ``build_setup_help_on_windows.bat`` which run the unit tests, builds the setups and generate the documentaton on Windows
* a script ```` which does almost the same on Linux
* a script ``publish_on_pipy.bat``
* unittests: run the unit tests, compute the code coverage
* a script ``build_script.bat`` which produces many script on Windows to easily run the setup,
generate the documentation, run the unit tests.

Versions / Changes
@@ -75,6 +73,7 @@ Versions / Changes
*this might break classes taking dependency on it*
* **add:** function write_pyproj to generate a file ``.pyproj``
to be used by `PTVS <>`_.
* **add:** directive RunPythonDirective to generate documentation from a script
* **1.1 - 2015/05/24**
* **fix:** shorten, move functionalities to the module, move to subfolder pycode
* **change:** improve the generation of automated documentation
@@ -89,6 +89,8 @@ Functionalities
* function :func:`rst2html <pyquickhelper.helpgen.convert_doc_helper.rst2html>` to convert RST into HTML
* Sphinx directive :class:`BlogPostDirective <pyquickhelper.helpgen.sphinx_blog_extension.BlogPostDirective>`
to add a directive ``blogpost`` into the docutmention
* Sphinx directive :class:`RunPythonDirective <pyquickhelper.helpgen.sphinx_run_script_extension.RunPythonDirective>`
to generate documentation from a script


@@ -0,0 +1,42 @@
@brief test log(time=4s)
@author Xavier Dupre

import sys
import os
import unittest
from docutils.parsers.rst import directives

import src
except ImportError:
path = os.path.normpath(
if path not in sys.path:
import src

from src.pyquickhelper.loghelper.flog import fLOG
from src.pyquickhelper import get_temp_folder
from src.pyquickhelper.helpgen.utils_sphinx_doc import private_migrating_doxygen_doc
from src.pyquickhelper.helpgen import RunPythonDirective

class TestRunPythonHelper(unittest.TestCase):

def test_post_parse(self):
OutputPrint=__name__ == "__main__")

directives.register_directive("runpython", RunPythonDirective)

if __name__ == "__main__":
@@ -29,3 +29,4 @@ def get_help_usage():
from .sphinx_helper import sphinx_add_scripts
from .process_notebook_api import nb2slides, nb2html
from .helpgen_exceptions import HelpGenException, ImportErrorHelpGen, HelpGenConvertError
from .sphinx_run_script_extension import RunPythonDirective
@@ -338,6 +338,8 @@ def custom_setup(app, author):
from .sphinx_blog_extension import visit_blogpostagg_node, depart_blogpostagg_node
from .sphinx_blog_extension import blogpost_node, blogpostagg_node
from .sphinx_blog_extension import BlogPostDirective, BlogPostDirectiveAgg
from .sphinx_run_script_extension import RunPythonDirective
from .sphinx_run_script_extension import runpython_node, visit_runpython_node, depart_runpython_node

app.connect("autodoc-skip-member", skip)
app.add_config_value('author', author, True)
@@ -357,8 +359,14 @@ def custom_setup(app, author):
latex=(visit_blogpostagg_node, depart_blogpostagg_node),
text=(visit_blogpostagg_node, depart_blogpostagg_node))

html=(visit_runpython_node, depart_runpython_node),
latex=(visit_runpython_node, depart_runpython_node),
text=(visit_runpython_node, depart_runpython_node))

app.add_directive('blogpost', BlogPostDirective)
app.add_directive('blogpostagg', BlogPostDirectiveAgg)
app.add_directive('runpython', RunPythonDirective)
#app.add_directive('blogpostlist', BlogPostListDirective)
#app.connect('doctree-resolved', process_blogpost_nodes)
#app.connect('env-purge-doc', purge_blogpost)
@@ -19,6 +19,7 @@
from .utils_sphinx_config import ie_layout_html
from .blog_post_list import BlogPostList
from .sphinx_blog_extension import BlogPostDirective, BlogPostDirectiveAgg
from .sphinx_run_script_extension import RunPythonDirective
from .post_process import post_process_latex_output
from .process_notebooks import process_notebooks, add_notebook_page
from .sphinx_helper import post_process_html_nb_output_static_file
@@ -189,6 +190,11 @@ def generate_help_sphinx(project_var_name,
"C:\\Program Files (x86)\\HTML Help Workshop\\hhc.exe" build\\htmlhelp\\<module>.hhp
.. versionadded:: 1.2
The documentation includes blog (with sphinx command ``.. blogpost::``
and python scripts ``.. runpython::``. The second command runs a python
script which outputs RST documntation adds it to the current documentation.

@@ -219,6 +225,7 @@ def lay_build_override_newconf(t3):

directives.register_directive("blogpost", BlogPostDirective)
directives.register_directive("blogpostagg", BlogPostDirectiveAgg)
directives.register_directive("runpython", RunPythonDirective)

if "conf" in sys.modules:
raise ImportError("module conf was imported, this function expects not to:\n{0}".format(
@@ -0,0 +1,176 @@
# -*- coding: utf-8 -*-
@brief Defines runpython directives.
See `Tutorial: Writing a simple extension <>`_
.. versionadded:: 1.2
import os
import sys
from docutils import nodes
from docutils.parsers.rst import Directive
from sphinx.locale import _ as _locale
from docutils.parsers.rst import directives
from docutils.statemachine import StringList
from sphinx import addnodes
from sphinx.util.nodes import set_source_info, process_index_entry
from .texts_language import TITLES
from io import StringIO

def run_python_script(script, params={}):
execute a script python as a string
@param script python script
@param params params to add before the execution
@return stdout, stderr
obj = compile(script, "", "exec")

loc = locals()
for k, v in params.items():
loc[k] = v
loc["__dict__"] = params

kout = sys.stdout
kerr = sys.stderr
sout = StringIO()
serr = StringIO()
sys.stdout = sout
sys.stderr = serr

exec(obj, globals(), loc)

sys.stdout = kout
sys.stderr = kerr

return sout.getvalue(), serr.getvalue()

class runpython_node(nodes.Structural, nodes.Element):

defines *runpython* node

class RunPythonDirective(Directive):

extracts script to run described by ``.. runpython::``
and modifies the documentation
@example(A python script which generates documentation)
The following code prints the version of Python
on the standard output. It it added to the documentation::
import sys
print(" " + str(sys.version_info))
If give the following results:
.. runpython::
import sys
print(" " + str(sys.version_info))
required_arguments = 0
optional_arguments = 0
final_argument_whitespace = True
option_spec = {'indent': directives.unchanged,
has_content = True
runpython_class = runpython_node

def run(self):
extracts the information in a dictionary,
run the script
@return a list of nodes
# settings
sett = self.state.document.settings
language_code = sett.language_code

# env
if hasattr(self.state.document.settings, "env"):
env = self.state.document.settings.env
env = None

if env is None:
# we need an access to the environment to run the script
return []
# otherwise, it means sphinx is running

# settings and configuration
config = env.config

# post
p = {
'indent': int(self.options.get("indent", 1)),

# build node
node = self.__class__.runpython_class(ids=[], indent=p["indent"])

# we add the date
content = self.content

# run the script
content = ["if True:"]
for line in self.content:
content.append(" " + line)
script = "\n".join(content)
out, err = run_python_script(script)
content = out
if len(err) > 0:
content += "\n\nERROR:\n\n" + err

# add indent
lines = content.split("\n")
if p['indent'] > 0:
lines = [(" " * p['indent'] + _) for _ in lines]

content = StringList(lines)

# parse the content into sphinx directive, we add it to section
paragraph = nodes.paragraph()
self.state.nested_parse(content, self.content_offset, paragraph)
node += paragraph
p['runpython'] = node

# classes
node['classes'] += "-runpython"
ns = [node]
return ns

def visit_runpython_node(self, node):
what to do when visiting a node runpython
the function should have different behaviour,
depending on the format, or the setup should
specify a different function for each.

def depart_runpython_node(self, node):
what to do when leaving a node runpython
the function should have different behaviour,
depending on the format, or the setup should
specify a different function for each.

0 comments on commit 7b2fc77

Please sign in to comment.
You can’t perform that action at this time.