Source code for sagenb.misc.sphinxify

# -*- coding: utf-8 -*
#!/usr/bin/env python2
r"""
Process docstrings with Sphinx

Processes docstrings with Sphinx. Can also be used as a commandline script:

``python sphinxify.py <text>``

AUTHORS:

- Tim Joseph Dumol (2009-09-29): initial version
"""
# **************************************************
# Copyright (C) 2009 Tim Dumol <tim@timdumol.com>
#
# Distributed under the terms of the BSD License
# **************************************************
import os
import re
import shutil
from tempfile import mkdtemp

from sage.env import SAGE_DOC_SRC

# We import Sphinx on demand, to reduce Sage startup time.
Sphinx = None


[docs]def is_sphinx_markup(docstring): """ Returns whether a string that contains Sphinx-style ReST markup. INPUT: - ``docstring`` - string to test for markup OUTPUT: - boolean """ # this could be made much more clever return ("`" in docstring or "::" in docstring)
[docs]def sphinxify(docstring, format='html'): r""" Runs Sphinx on a ``docstring``, and outputs the processed documentation. INPUT: - ``docstring`` -- string -- a ReST-formatted docstring - ``format`` -- string (optional, default 'html') -- either 'html' or 'text' OUTPUT: - string -- Sphinx-processed documentation, in either HTML or plain text format, depending on the value of ``format`` EXAMPLES:: sage: from sagenb.misc.sphinxify import sphinxify sage: sphinxify('A test') '...<div class="docstring">\n \n <p>A test</p>\n\n\n</div>' sage: sphinxify('**Testing**\n`monospace`') '<div class="docstring">\n \n <p><strong>Testing</strong>\n<span class="math notranslate nohighlight">monospace</span></p>\n\n\n</div>' sage: sphinxify('`x=y`') '<div class="docstring">\n \n <p><span class="math notranslate nohighlight">x=y</span></p>\n\n\n</div>' sage: sphinxify('`x=y`', format='text') 'x=y\n' sage: sphinxify(':math:`x=y`', format='text') 'x=y\n' TESTS:: sage: n = len(sys.path) sage: _ = sphinxify('A test') sage: assert n == len(sys.path) """ global Sphinx if not Sphinx: from sphinx.application import Sphinx srcdir = mkdtemp() base_name = os.path.join(srcdir, 'docstring') rst_name = base_name + '.rst' if format == 'html': suffix = '.html' else: suffix = '.txt' output_name = base_name + suffix with open(rst_name, 'w') as filed: filed.write(docstring) # Sphinx constructor: Sphinx(srcdir, confdir, outdir, doctreedir, # buildername, confoverrides, status, warning, freshenv). temp_confdir = False confdir = os.path.join(SAGE_DOC_SRC, 'en', 'introspect') if not SAGE_DOC_SRC and not os.path.exists(confdir): # If we don't have Sage, we need to do our own configuration # This may be inefficient or broken. TODO: Find a faster way to do this. temp_confdir = True confdir = mkdtemp() generate_configuration(confdir) doctreedir = os.path.join(srcdir, 'doctrees') confoverrides = {'html_context': {}, 'master_doc': 'docstring'} import sys old_sys_path = list(sys.path) # Sphinx modifies sys.path sphinx_app = Sphinx(srcdir, confdir, srcdir, doctreedir, format, confoverrides, None, None, True) sphinx_app.build(None, [rst_name]) sys.path = old_sys_path # We need to remove "_" from __builtin__ that the gettext module installs from six.moves import builtins builtins.__dict__.pop('_', None) if os.path.exists(output_name): output = open(output_name, 'r').read() output = output.replace('<pre>', '<pre class="literal-block">') # Translate URLs for media from something like # "../../media/...path.../blah.png" # or # "/media/...path.../blah.png" # to # "/doc/static/reference/media/...path.../blah.png" output = re.sub(r"""src=['"](/?\.\.)*/?media/([^"']*)['"]""", 'src="/doc/static/reference/media/\\2"', output) # Remove spurious \(, \), \[, \]. output = output.replace('\\(', '').replace('\\)', '').replace('\\[', '').replace('\\]', '') else: print("BUG -- Sphinx error") if format == 'html': output = '<pre class="introspection">%s</pre>' % docstring else: output = docstring if temp_confdir: shutil.rmtree(confdir, ignore_errors=True) shutil.rmtree(srcdir, ignore_errors=True) return output
[docs]def generate_configuration(directory): r""" Generates a Sphinx configuration in ``directory``. INPUT: - ``directory`` - string, base directory to use EXAMPLES:: sage: from sagenb.misc.sphinxify import generate_configuration sage: import tempfile, os sage: tmpdir = tempfile.mkdtemp() sage: generate_configuration(tmpdir) sage: open(os.path.join(tmpdir, 'conf.py')).read() '\n...extensions =...' """ conf = r''' ########################################################### # Taken from `$SAGE_ROOT$/devel/sage/doc/common/conf.py` # ########################################################### import sys, os, re # If your extensions are in another directory, add it here. If the directory # is relative to the documentation root, use os.path.abspath to make it # absolute, like shown here. #sys.path.append(os.path.abspath('.')) # General configuration # --------------------- # Add any Sphinx extension module names here, as strings. They can be extensions # coming with Sphinx (named 'sphinx.ext.*') or your custom ones. extensions = ['sage_autodoc', 'sphinx.ext.graphviz', 'sphinx.ext.inheritance_diagram', 'sphinx.ext.todo'] #, 'sphinx.ext.intersphinx', 'sphinx.ext.extlinks'] # The suffix of source filenames. source_suffix = '.rst' # The master toctree document. master_doc = 'index' # General information about the project. project = u"" copyright = u'2005--2011, The Sage Development Team' # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the # built documents. # # The short X.Y version. #version = '3.1.2' # The full version, including alpha/beta/rc tags. #release = '3.1.2' # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. #language = None # There are two options for replacing |today|: either, you set today to some # non-false value, then it is used: #today = '' # Else, today_fmt is used as the format for a strftime call. #today_fmt = '%B %d, %Y' # List of documents that shouldn't be included in the build. #unused_docs = [] # List of directories, relative to source directory, that shouldn't be searched # for source files. exclude_trees = ['.build'] # The reST default role (used for this markup: `text`) to use for all documents. default_role = 'math' # If true, '()' will be appended to :func: etc. cross-reference text. #add_function_parentheses = True # If true, the current module name will be prepended to all description # unit titles (such as .. function::). #add_module_names = True # If true, sectionauthor and moduleauthor directives will be shown in the # output. They are ignored by default. #show_authors = False # The name of the Pygments (syntax highlighting) style to use. NOTE: # This overrides a HTML theme's corresponding setting (see below). pygments_style = 'sphinx' # GraphViz includes dot, neato, twopi, circo, fdp. graphviz_dot = 'dot' inheritance_graph_attrs = { 'rankdir' : 'BT' } inheritance_node_attrs = { 'height' : 0.5, 'fontsize' : 12, 'shape' : 'oval' } inheritance_edge_attrs = {} # Extension configuration # ----------------------- # include the todos todo_include_todos = True # Options for HTML output # ----------------------- # HTML style sheet NOTE: This overrides a HTML theme's corresponding # setting. #html_style = 'classic.css' # The name for this set of Sphinx documents. If None, it defaults to # "<project> v<release> documentation". #html_title = None # A shorter title for the navigation bar. Default is the same as html_title. #html_short_title = None # The name of an image file (within the static path) to place at the top of # the sidebar. #html_logo = 'sagelogo-word.ico' # The name of an image file (within the static path) to use as favicon of the # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 # pixels large. html_favicon = 'favicon.ico' # If we're using MathJax, we prepend its location to the static path # array. We can override / overwrite selected files by putting them # in the remaining paths. We also write the local config file, # containing Sage-specific macros and allowing the use of dollar signs # to delimit math. We write the file to the directory # sagenb/data/mathjax/config/local/mathjax_sage.js, # in the sagenb-... subdirectory of # SAGE_ROOT/local/lib/python/site-libraries. # This is in the static path, so the file gets copied to the # appropriate place, allowing us to use the above setting for # mathjax_path. # The environment variable SAGE_DOC_MATHJAX may be set by the user or # by the file "builder.py". If it's set, or if SAGE_DOC_JSMATH is set # (for backwards compatibility), use MathJax. if (os.environ.get('SAGE_DOC_MATHJAX', False) or os.environ.get('SAGE_DOC_JSMATH', False)): extensions.append('sphinx.ext.mathjax') mathjax_path = 'MathJax.js?config=TeX-AMS_HTML-full,../mathjax_sage.js' from sage.misc.latex_macros import sage_mathjax_macros html_theme_options['mathjax_macros'] = sage_mathjax_macros() from pkg_resources import Requirement, working_set sagenb_path = working_set.find(Requirement.parse('sagenb')).location mathjax_relative = os.path.join('sagenb','data','mathjax') # It would be really nice if sphinx would copy the entire mathjax directory, # (so we could have a _static/mathjax directory), rather than the contents of the directory mathjax_static = os.path.join(sagenb_path, mathjax_relative) html_static_path.append(mathjax_static) exclude_patterns=['**/'+os.path.join(mathjax_relative, i) for i in ('docs', 'README*', 'test', 'unpacked', 'LICENSE')] else: extensions.append('sphinx.ext.pngmath') # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, # using the given strftime format. #html_last_updated_fmt = '%b %d, %Y' # If true, SmartyPants will be used to convert quotes and dashes to # typographically correct entities. #html_use_smartypants = True # Custom sidebar templates, maps document names to template names. #html_sidebars = {} # Additional templates that should be rendered to pages, maps page names to # template names. #html_additional_pages = {} # If false, no module index is generated. #html_use_modindex = True # If false, no index is generated. #html_use_index = True # If true, the reST sources are included in the HTML build as _sources/<name>. #html_copy_source = True # If true, an OpenSearch description file will be output, and all pages will # contain a <link> tag referring to it. The value of this option must be the # base URL from which the finished HTML is served. #html_use_opensearch = '' # If nonempty, this is the file name suffix for HTML files (e.g. ".xhtml"). #html_file_suffix = '' # Output file base name for HTML help builder. #htmlhelp_basename = '' # Options for LaTeX output # ------------------------ # See http://sphinx.pocoo.org/config.html#confval-latex_elements latex_elements = {} # Extended UTF-8 scheme latex_elements['inputenc'] = '\\usepackage[utf8x]{inputenc}' # Prevent Sphinx from by default inserting the following LaTeX # declaration into the preamble of a .tex file: # # \DeclareUnicodeCharacter{00A0}{\nobreakspace} # # This happens in the file src/sphinx/writers/latex.py in the sphinx # spkg. This declaration is known to result in a failure to build the # PDF version of a document in the Sage standard documentation. See # ticket #8183 for further information on this issue. latex_elements['utf8extra'] = '' # The paper size ('letterpaper' or 'a4paper'). #latex_elements['papersize'] = 'letterpaper' # The font size ('10pt', '11pt' or '12pt'). #latex_elements['pointsize'] = '10pt' # Grouping the document tree into LaTeX files. List of tuples # (source start file, target name, title, author, document class [howto/manual]). latex_documents = [] # The name of an image file (relative to this directory) to place at the top of # the title page. #latex_logo = 'sagelogo-word.png' # For "manual" documents, if this is true, then toplevel headings are parts, # not chapters. #latex_use_parts = False # Additional stuff for the LaTeX preamble. latex_elements['preamble'] = '\usepackage{amsmath}\n\usepackage{amssymb}\n' # Documents to append as an appendix to all manuals. #latex_appendices = [] # If false, no module index is generated. #latex_use_modindex = True ##################################################### # add LaTeX macros for Sage try: from sage.misc.latex_macros import sage_latex_macros except ImportError: def sage_latex_macros(): return [] try: pngmath_latex_preamble # check whether this is already defined except NameError: pngmath_latex_preamble = "" for macro in sage_latex_macros(): # used when building latex and pdf versions latex_elements['preamble'] += macro + '\n' # used when building html version pngmath_latex_preamble += macro + '\n' ##################################################### def process_docstring_aliases(app, what, name, obj, options, docstringlines): """ Change the docstrings for aliases to point to the original object. """ basename = name.rpartition('.')[2] if hasattr(obj, '__name__') and obj.__name__ != basename: docstringlines[:] = ['See :obj:`%s`.' % name] def process_directives(app, what, name, obj, options, docstringlines): """ Remove 'nodetex' and other directives from the first line of any docstring where they appear. """ if len(docstringlines) == 0: return first_line = docstringlines[0] directives = [ d.lower() for d in first_line.split(',') ] if 'nodetex' in directives: docstringlines.pop(0) def process_docstring_cython(app, what, name, obj, options, docstringlines): """ Remove Cython's filename and location embedding. """ if len(docstringlines) <= 1: return first_line = docstringlines[0] if first_line.startswith('File:') and '(starting at' in first_line: #Remove the first two lines docstringlines.pop(0) docstringlines.pop(0) def process_docstring_module_title(app, what, name, obj, options, docstringlines): """ Removes the first line from the beginning of the module's docstring. This corresponds to the title of the module's documentation page. """ if what != "module": return #Remove any additional blank lines at the beginning title_removed = False while len(docstringlines) > 1 and not title_removed: if docstringlines[0].strip() != "": title_removed = True docstringlines.pop(0) #Remove any additional blank lines at the beginning while len(docstringlines) > 1: if docstringlines[0].strip() == "": docstringlines.pop(0) else: break skip_picklability_check_modules = [ #'sage.misc.nested_class_test', # for test only 'sage.misc.latex', 'sage.misc.explain_pickle', '__builtin__', ] def check_nested_class_picklability(app, what, name, obj, skip, options): """ Print a warning if pickling is broken for nested classes. """ from six import iteritems import types if hasattr(obj, '__dict__') and hasattr(obj, '__module__'): # Check picklability of nested classes. Adapted from # sage.misc.nested_class.modify_for_nested_pickle. module = sys.modules[obj.__module__] for (nm, v) in iteritems(obj.__dict__): if (isinstance(v, (type, types.ClassType)) and v.__name__ == nm and v.__module__ == module.__name__ and getattr(module, nm, None) is not v and v.__module__ not in skip_picklability_check_modules): # OK, probably this is an *unpicklable* nested class. app.warn('Pickling of nested class %r is probably broken. ' 'Please set __metaclass__ of the parent class to ' 'sage.misc.nested_class.NestedClassMetaclass.' % ( v.__module__ + '.' + name + '.' + nm)) def skip_member(app, what, name, obj, skip, options): """ To suppress Sphinx warnings / errors, we - Don't include [aliases of] builtins. - Don't include the docstring for any nested class which has been inserted into its module by :class:`sage.misc.NestedClassMetaclass` only for pickling. The class will be properly documented inside its surrounding class. - Don't include sagenb.notebook.twist.userchild_download_worksheets.zip. - Optionally, check whether pickling is broken for nested classes. - Optionally, include objects whose name begins with an underscore ('_'), i.e., "private" or "hidden" attributes, methods, etc. Otherwise, we abide by Sphinx's decision. Note: The object ``obj`` is excluded (included) if this handler returns True (False). """ if 'SAGE_CHECK_NESTED' in os.environ: check_nested_class_picklability(app, what, name, obj, skip, options) if getattr(obj, '__module__', None) == '__builtin__': return True if (hasattr(obj, '__name__') and obj.__name__.find('.') != -1 and obj.__name__.split('.')[-1] != name): return True if name.find("userchild_download_worksheets.zip") != -1: return True if 'SAGE_DOC_UNDERSCORE' in os.environ: if name.split('.')[-1].startswith('_'): return False return skip def process_dollars(app, what, name, obj, options, docstringlines): r""" Replace dollar signs with backticks. See sage.misc.sagedoc.process_dollars for more information """ if len(docstringlines) > 0 and name.find("process_dollars") == -1: from sage.misc.sagedoc import process_dollars as sagedoc_dollars s = sagedoc_dollars("\n".join(docstringlines)) lines = s.split("\n") for i in range(len(lines)): docstringlines[i] = lines[i] def process_inherited(app, what, name, obj, options, docstringlines): """ If we're including inherited members, omit their docstrings. """ if not options.get('inherited-members'): return if what in ['class', 'data', 'exception', 'function', 'module']: return name = name.split('.')[-1] if what == 'method' and hasattr(obj, 'im_class'): if name in obj.im_class.__dict__.keys(): return if what == 'attribute' and hasattr(obj, '__objclass__'): if name in obj.__objclass__.__dict__.keys(): return for i in xrange(len(docstringlines)): docstringlines.pop() from sage.misc.sageinspect import sage_getargspec autodoc_builtin_argspec = sage_getargspec def setup(app): app.connect('autodoc-process-docstring', process_docstring_cython) app.connect('autodoc-process-docstring', process_directives) app.connect('autodoc-process-docstring', process_docstring_module_title) app.connect('autodoc-process-docstring', process_dollars) app.connect('autodoc-process-docstring', process_inherited) app.connect('autodoc-skip-member', skip_member) ''' # From SAGE_DOC_SRC/en/introspect/templates/layout.html: layout = r""" <div class="docstring"> {% block body %}{% endblock %} </div> """ os.makedirs(os.path.join(directory, 'templates')) os.makedirs(os.path.join(directory, 'static')) open(os.path.join(directory, 'conf.py'), 'w').write(conf) open(os.path.join(directory, '__init__.py'), 'w').write('') open(os.path.join(directory, 'templates', 'layout.html'), 'w').write(layout) open(os.path.join(directory, 'static', 'empty'), 'w').write('')
if __name__ == '__main__': import sys if len(sys.argv) == 2: print(sphinxify(sys.argv[1])) else: print("""Usage: %s 'docstring' docstring -- docstring to be processed """)