Commit 8f0b827d authored by Tiago Peixoto's avatar Tiago Peixoto
Browse files

Replace jsmath with MathJax and update sphinxext

parent 56152176
......@@ -25,10 +25,12 @@ sys.path.append(os.path.abspath('.'))
# Add any Sphinx extension module names here, as strings. They can be extensions
# coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
extensions = ['sphinx.ext.autodoc', 'sphinx.ext.doctest',
'sphinx.ext.intersphinx', 'sphinx.ext.jsmath',
'sphinxext.numpydoc', 'sphinxext.autosummary']
'sphinx.ext.intersphinx', 'mathjax',
'sphinx.ext.autosummary', 'sphinxext.numpydoc']
jsmath_path = "jsMath/easy/load.js"
numpydoc_show_class_members = False
mathjax_path = "MathJax/MathJax.js"
# Add any paths that contain templates here, relative to this directory.
templates_path = ['.templates']
......
# -*- coding: utf-8 -*-
"""
sphinx.ext.mathjax
~~~~~~~~~~~~~~~~~~
Set up everything for use of MathJax (http://www.mathjax.org/)
to display math in HTML via JavaScript.
(c) Kevin Dunn, http://connectmv.com
:license: BSD, see LICENSE for details.
"""
from docutils import nodes
from sphinx.application import ExtensionError
from sphinx.ext.mathbase import setup_math as mathbase_setup
def html_visit_math(self, node):
self.body.append(self.starttag(node, 'span', '', CLASS='math'))
self.body.append(self.builder.config.mathjax_inline[0] + \
self.encode(node['latex']) +\
self.builder.config.mathjax_inline[1] + '</span>')
raise nodes.SkipNode
def html_visit_displaymath(self, node):
self.body.append(self.starttag(node, 'div', CLASS='math'))
if node['nowrap']:
self.body.append(self.builder.config.mathjax_display[0] + \
node['latex'] +\
self.builder.config.mathjax_display[1])
self.body.append('</div>')
raise nodes.SkipNode
parts = [prt for prt in node['latex'].split('\n\n') if prt.strip() != '']
for i, part in enumerate(parts):
part = self.encode(part)
if i == 0:
# necessary to e.g. set the id property correctly
if node['number']:
self.body.append('<span class="eqno">(%s)</span>' %
node['number'])
if '&' in part or '\\\\' in part:
self.body.append(self.builder.config.mathjax_display[0] + \
'\\begin{split}' + part + '\\end{split}' + \
self.builder.config.mathjax_display[1])
else:
self.body.append(self.builder.config.mathjax_display[0] + part + \
self.builder.config.mathjax_display[1])
self.body.append('</div>\n')
raise nodes.SkipNode
def builder_inited(app):
if not app.config.mathjax_path:
raise ExtensionError('mathjax_path config value must be set for the '
'mathjax extension to work')
app.add_javascript(app.config.mathjax_path)
def setup(app):
mathbase_setup(app, (html_visit_math, None), (html_visit_displaymath, None))
app.add_config_value('mathjax_path', '', False)
app.add_config_value('mathjax_inline', [r'\(', r'\)'], 'html')
app.add_config_value('mathjax_display', [r'\[', r'\]'], 'html')
app.connect('builder-inited', builder_inited)
"""
===========
autosummary
===========
Sphinx extension that adds an autosummary:: directive, which can be
used to generate function/method/attribute/etc. summary lists, similar
to those output eg. by Epydoc and other API doc generation tools.
An :autolink: role is also provided.
autosummary directive
---------------------
The autosummary directive has the form::
.. autosummary::
:nosignatures:
:toctree: generated/
module.function_1
module.function_2
...
and it generates an output table (containing signatures, optionally)
======================== =============================================
module.function_1(args) Summary line from the docstring of function_1
module.function_2(args) Summary line from the docstring
...
======================== =============================================
If the :toctree: option is specified, files matching the function names
are inserted to the toctree with the given prefix:
generated/module.function_1
generated/module.function_2
...
Note: The file names contain the module:: or currentmodule:: prefixes.
.. seealso:: autosummary_generate.py
autolink role
-------------
The autolink role functions as ``:obj:`` when the name referred can be
resolved to a Python object, and otherwise it becomes simple emphasis.
This can be used as the default role to make links 'smart'.
"""
import sys, os, posixpath, re
from docutils.parsers.rst import directives
from docutils.statemachine import ViewList
from docutils import nodes
import sphinx.addnodes, sphinx.roles, sphinx.builder
from sphinx.util import patfilter
from docscrape_sphinx import get_doc_object
def setup(app):
app.add_directive('autosummary', autosummary_directive, True, (0, 0, False),
toctree=directives.unchanged,
nosignatures=directives.flag)
app.add_role('autolink', autolink_role)
app.add_node(autosummary_toc,
html=(autosummary_toc_visit_html, autosummary_toc_depart_noop),
latex=(autosummary_toc_visit_latex, autosummary_toc_depart_noop))
app.connect('doctree-read', process_autosummary_toc)
#------------------------------------------------------------------------------
# autosummary_toc node
#------------------------------------------------------------------------------
class autosummary_toc(nodes.comment):
pass
def process_autosummary_toc(app, doctree):
"""
Insert items described in autosummary:: to the TOC tree, but do
not generate the toctree:: list.
"""
env = app.builder.env
crawled = {}
def crawl_toc(node, depth=1):
crawled[node] = True
for j, subnode in enumerate(node):
try:
if (isinstance(subnode, autosummary_toc)
and isinstance(subnode[0], sphinx.addnodes.toctree)):
env.note_toctree(env.docname, subnode[0])
continue
except IndexError:
continue
if not isinstance(subnode, nodes.section):
continue
if subnode not in crawled:
crawl_toc(subnode, depth+1)
crawl_toc(doctree)
def autosummary_toc_visit_html(self, node):
"""Hide autosummary toctree list in HTML output"""
raise nodes.SkipNode
def autosummary_toc_visit_latex(self, node):
"""Show autosummary toctree (= put the referenced pages here) in Latex"""
pass
def autosummary_toc_depart_noop(self, node):
pass
#------------------------------------------------------------------------------
# .. autosummary::
#------------------------------------------------------------------------------
def autosummary_directive(dirname, arguments, options, content, lineno,
content_offset, block_text, state, state_machine):
"""
Pretty table containing short signatures and summaries of functions etc.
autosummary also generates a (hidden) toctree:: node.
"""
names = []
names += [x.strip() for x in content if x.strip()]
table, warnings, real_names = get_autosummary(names, state,
'nosignatures' in options)
node = table
env = state.document.settings.env
suffix = env.config.source_suffix
all_docnames = env.found_docs.copy()
dirname = posixpath.dirname(env.docname)
if 'toctree' in options:
tree_prefix = options['toctree'].strip()
docnames = []
for name in names:
name = real_names.get(name, name)
docname = tree_prefix + name
if docname.endswith(suffix):
docname = docname[:-len(suffix)]
docname = posixpath.normpath(posixpath.join(dirname, docname))
if docname not in env.found_docs:
warnings.append(state.document.reporter.warning(
'toctree references unknown document %r' % docname,
line=lineno))
docnames.append(docname)
tocnode = sphinx.addnodes.toctree()
tocnode['includefiles'] = docnames
tocnode['maxdepth'] = -1
tocnode['glob'] = None
tocnode = autosummary_toc('', '', tocnode)
return warnings + [node] + [tocnode]
else:
return warnings + [node]
def get_autosummary(names, state, no_signatures=False):
"""
Generate a proper table node for autosummary:: directive.
Parameters
----------
names : list of str
Names of Python objects to be imported and added to the table.
document : document
Docutils document object
"""
document = state.document
real_names = {}
warnings = []
prefixes = ['']
prefixes.insert(0, document.settings.env.currmodule)
table = nodes.table('')
group = nodes.tgroup('', cols=2)
table.append(group)
group.append(nodes.colspec('', colwidth=45))
group.append(nodes.colspec('', colwidth=55))
body = nodes.tbody('')
group.append(body)
def append_row(*column_texts):
row = nodes.row('')
for text in column_texts:
node = nodes.paragraph('')
vl = ViewList()
vl.append(text, '<autosummary>')
state.nested_parse(vl, 0, node)
row.append(nodes.entry('', node))
body.append(row)
for name in names:
try:
obj, real_name = import_by_name(name, prefixes=prefixes)
except ImportError:
warnings.append(document.reporter.warning(
'failed to import %s' % name))
append_row(":obj:`%s`" % name, "")
continue
real_names[name] = real_name
doc = get_doc_object(obj)
if doc['Summary']:
title = " ".join(doc['Summary'])
else:
title = ""
col1 = ":obj:`%s <%s>`" % (name, real_name)
if doc['Signature']:
sig = re.sub('^[a-zA-Z_0-9.-]*', '', doc['Signature'])
if '=' in sig:
# abbreviate optional arguments
sig = re.sub(r', ([a-zA-Z0-9_]+)=', r'[, \1=', sig, count=1)
sig = re.sub(r'\(([a-zA-Z0-9_]+)=', r'([\1=', sig, count=1)
sig = re.sub(r'=[^,)]+,', ',', sig)
sig = re.sub(r'=[^,)]+\)$', '])', sig)
# shorten long strings
sig = re.sub(r'(\[.{16,16}[^,)]*?),.*?\]\)', r'\1, ...])', sig)
else:
sig = re.sub(r'(\(.{16,16}[^,)]*?),.*?\)', r'\1, ...)', sig)
col1 += " " + sig
col2 = title
append_row(col1, col2)
return table, warnings, real_names
def import_by_name(name, prefixes=[None]):
"""
Import a Python object that has the given name, under one of the prefixes.
Parameters
----------
name : str
Name of a Python object, eg. 'numpy.ndarray.view'
prefixes : list of (str or None), optional
Prefixes to prepend to the name (None implies no prefix).
The first prefixed name that results to successful import is used.
Returns
-------
obj
The imported object
name
Name of the imported object (useful if `prefixes` was used)
"""
for prefix in prefixes:
try:
if prefix:
prefixed_name = '.'.join([prefix, name])
else:
prefixed_name = name
return _import_by_name(prefixed_name), prefixed_name
except ImportError:
pass
raise ImportError
def _import_by_name(name):
"""Import a Python object given its full name"""
try:
# try first interpret `name` as MODNAME.OBJ
name_parts = name.split('.')
try:
modname = '.'.join(name_parts[:-1])
__import__(modname)
return getattr(sys.modules[modname], name_parts[-1])
except (ImportError, IndexError, AttributeError):
pass
# ... then as MODNAME, MODNAME.OBJ1, MODNAME.OBJ1.OBJ2, ...
last_j = 0
modname = None
for j in reversed(range(1, len(name_parts)+1)):
last_j = j
modname = '.'.join(name_parts[:j])
try:
__import__(modname)
except ImportError:
continue
if modname in sys.modules:
break
if last_j < len(name_parts):
obj = sys.modules[modname]
for obj_name in name_parts[last_j:]:
obj = getattr(obj, obj_name)
return obj
else:
return sys.modules[modname]
except (ValueError, ImportError, AttributeError, KeyError), e:
raise ImportError(e)
#------------------------------------------------------------------------------
# :autolink: (smart default role)
#------------------------------------------------------------------------------
def autolink_role(typ, rawtext, etext, lineno, inliner,
options={}, content=[]):
"""
Smart linking role.
Expands to ":obj:`text`" if `text` is an object that can be imported;
otherwise expands to "*text*".
"""
r = sphinx.roles.xfileref_role('obj', rawtext, etext, lineno, inliner,
options, content)
pnode = r[0][0]
prefixes = [None]
#prefixes.insert(0, inliner.document.settings.env.currmodule)
try:
obj, name = import_by_name(pnode['reftarget'], prefixes)
except ImportError:
content = pnode[0]
r[0][0] = nodes.emphasis(rawtext, content[0].astext(),
classes=content['classes'])
return r
#!/usr/bin/env python
r"""
autosummary_generate.py OPTIONS FILES
Generate automatic RST source files for items referred to in
autosummary:: directives.
Each generated RST file contains a single auto*:: directive which
extracts the docstring of the referred item.
Example Makefile rule::
generate:
./ext/autosummary_generate.py -o source/generated source/*.rst
"""
import glob, re, inspect, os, optparse, pydoc
from autosummary import import_by_name
try:
from phantom_import import import_phantom_module
except ImportError:
import_phantom_module = lambda x: x
def main():
p = optparse.OptionParser(__doc__.strip())
p.add_option("-p", "--phantom", action="store", type="string",
dest="phantom", default=None,
help="Phantom import modules from a file")
p.add_option("-o", "--output-dir", action="store", type="string",
dest="output_dir", default=None,
help=("Write all output files to the given directory (instead "
"of writing them as specified in the autosummary:: "
"directives)"))
options, args = p.parse_args()
if len(args) == 0:
p.error("wrong number of arguments")
if options.phantom and os.path.isfile(options.phantom):
import_phantom_module(options.phantom)
# read
names = {}
for name, loc in get_documented(args).items():
for (filename, sec_title, keyword, toctree) in loc:
if toctree is not None:
path = os.path.join(os.path.dirname(filename), toctree)
names[name] = os.path.abspath(path)
# write
for name, path in sorted(names.items()):
if options.output_dir is not None:
path = options.output_dir
if not os.path.isdir(path):
os.makedirs(path)
try:
obj, name = import_by_name(name)
except ImportError, e:
print "Failed to import '%s': %s" % (name, e)
continue
fn = os.path.join(path, '%s.rst' % name)
if os.path.exists(fn):
# skip
continue
f = open(fn, 'w')
try:
f.write('%s\n%s\n\n' % (name, '='*len(name)))
if inspect.isclass(obj):
if issubclass(obj, Exception):
f.write(format_modulemember(name, 'autoexception'))
else:
f.write(format_modulemember(name, 'autoclass'))
elif inspect.ismodule(obj):
f.write(format_modulemember(name, 'automodule'))
elif inspect.ismethod(obj) or inspect.ismethoddescriptor(obj):
f.write(format_classmember(name, 'automethod'))
elif callable(obj):
f.write(format_modulemember(name, 'autofunction'))
elif hasattr(obj, '__get__'):
f.write(format_classmember(name, 'autoattribute'))
else:
f.write(format_modulemember(name, 'autofunction'))
finally:
f.close()
def format_modulemember(name, directive):
parts = name.split('.')
mod, name = '.'.join(parts[:-1]), parts[-1]
return ".. currentmodule:: %s\n\n.. %s:: %s\n" % (mod, directive, name)
def format_classmember(name, directive):
parts = name.split('.')
mod, name = '.'.join(parts[:-2]), '.'.join(parts[-2:])
return ".. currentmodule:: %s\n\n.. %s:: %s\n" % (mod, directive, name)
def get_documented(filenames):
"""
Find out what items are documented in source/*.rst
See `get_documented_in_lines`.
"""
documented = {}
for filename in filenames:
f = open(filename, 'r')
lines = f.read().splitlines()
documented.update(get_documented_in_lines(lines, filename=filename))
f.close()
return documented
def get_documented_in_docstring(name, module=None, filename=None):
"""
Find out what items are documented in the given object's docstring.
See `get_documented_in_lines`.
"""
try:
obj, real_name = import_by_name(name)
lines = pydoc.getdoc(obj).splitlines()
return get_documented_in_lines(lines, module=name, filename=filename)
except AttributeError:
pass
except ImportError, e:
print "Failed to import '%s': %s" % (name, e)
return {}
def get_documented_in_lines(lines, module=None, filename=None):
"""
Find out what items are documented in the given lines
Returns
-------
documented : dict of list of (filename, title, keyword, toctree)
Dictionary whose keys are documented names of objects.
The value is a list of locations where the object was documented.
Each location is a tuple of filename, the current section title,
the name of the directive, and the value of the :toctree: argument
(if present) of the directive.
"""
title_underline_re = re.compile("^[-=*_^#]{3,}\s*$")
autodoc_re = re.compile(".. auto(function|method|attribute|class|exception|module)::\s*([A-Za-z0-9_.]+)\s*$")
autosummary_re = re.compile(r'^\.\.\s+autosummary::\s*')
module_re = re.compile(r'^\.\.\s+(current)?module::\s*([a-zA-Z0-9_.]+)\s*$')
autosummary_item_re = re.compile(r'^\s+([_a-zA-Z][a-zA-Z0-9_.]*)\s*')
toctree_arg_re = re.compile(r'^\s+:toctree:\s*(.*?)\s*$')
documented = {}
current_title = []
last_line = None
toctree = None
current_module = module
in_autosummary = False
for line in lines:
try:
if in_autosummary:
m = toctree_arg_re.match(line)
if m:
toctree = m.group(1)
continue
if line.strip().startswith(':'):
continue # skip options
m = autosummary_item_re.match(line)
if m:
name = m.group(1).strip()
if current_module and not name.startswith(current_module + '.'):
name = "%s.%s" % (current_module, name)
documented.setdefault(name, []).append(
(filename, current_title, 'autosummary', toctree))
continue
if line.strip() == '':
continue
in_autosummary = False
m = autosummary_re.match(line)
if m:
in_autosummary = True
continue