Files
Phoenix/etgtools/sphinx_generator.py
2016-05-19 23:57:17 -07:00

3514 lines
116 KiB
Python

# -*- coding: utf-8 -*-
#!/usr/bin/env python
#---------------------------------------------------------------------------
# Name: etgtools/sphinx_generator.py
# Author: Andrea Gavana
#
# Created: 30-Nov-2010
# Copyright: (c) 2010-2016 by Total Control Software
# License: wxWindows License
#---------------------------------------------------------------------------
"""
This generator will create the docstrings for Sphinx to process, by refactoring
the various XML elements passed by the Phoenix extractors into ReST format.
"""
# Standard library stuff
import os
import operator
import sys
import shutil
import textwrap
import glob
if sys.version_info < (3, ):
from StringIO import StringIO
string_base = basestring
else:
from io import StringIO
string_base = str
import xml.etree.ElementTree as et
# Phoenix-specific stuff
import etgtools.extractors as extractors
import etgtools.generators as generators
# Sphinx-Phoenix specific stuff
from sphinxtools.inheritance import InheritanceDiagram
from sphinxtools import templates
from sphinxtools.utilities import ODict
from sphinxtools.utilities import convertToPython
from sphinxtools.utilities import removeWxPrefix, writeSphinxOutput
from sphinxtools.utilities import findControlImages, makeSummary, pickleItem
from sphinxtools.utilities import chopDescription, pythonizeType, wx2Sphinx
from sphinxtools.utilities import pickleClassInfo, isNumeric
from sphinxtools.utilities import underscore2Capitals, countSpaces
from sphinxtools.utilities import formatContributedSnippets
from sphinxtools.utilities import PickleFile
from sphinxtools.constants import VERSION, REMOVED_LINKS, SECTIONS
from sphinxtools.constants import MAGIC_METHODS, MODULENAME_REPLACE
from sphinxtools.constants import IGNORE, NO_MODULE
from sphinxtools.constants import SPHINXROOT, DOXYROOT
from sphinxtools.constants import SNIPPETROOT, TABLEROOT, OVERVIEW_IMAGES_ROOT
from sphinxtools.constants import DOCSTRING_KEY
# ----------------------------------------------------------------------- #
class Node(object):
"""
This is the base class of all the subsequent classes in this script, and it
holds information about a XML element coming from doxygen and this `Node`
parent element (another `Node`).
"""
# -----------------------------------------------------------------------
def __init__(self, element, parent):
"""
Class constructor.
:param xml.etree.ElementTree.Element `element`: a XML element containing the
information coming from Doxygen.
:param Node `parent`: the parent node, or ``None``.
"""
self.element = element
self.parent = parent
self.children = []
if parent is not None:
parent.Add(self)
# -----------------------------------------------------------------------
def Add(self, node):
"""
Adds a node to its children list.
:param Node `node`: another `Node` class.
"""
self.children.append(node)
# -----------------------------------------------------------------------
def GetParent(self):
"""
Returns this node parent or ``None`` if it has no parent.
:rtype: :class:`Node`
"""
return self.parent
# -----------------------------------------------------------------------
def GetTopLevelParent(self):
"""
Returns the top level ancestor for this node or ``None``. If the ancestor
is not ``None``, then it should be an instance of :class:`Root`.
:rtype: :class:`Node`
"""
parent = self.parent
while 1:
if parent is None:
return
if isinstance(parent, Root):
return parent
parent = parent.parent
# -----------------------------------------------------------------------
def GetTag(self, tag_name):
"""
Given a `tag_name` for the element stored in this node, return the content
of that tag (or ``None`` if this element has no such tag).
:param string `tag_name`: the element tag name.
:returns: The element text for the input `tag_name` or ``None``.
"""
if isinstance(self.element, string_base):
return None
return self.element.get(tag_name)
# -----------------------------------------------------------------------
def GetHierarchy(self):
"""
Returns a list of strings representing this node hierarchy up to the :class:`Root`
node.
:rtype: `list`
"""
hierarchy = [self.__class__.__name__]
parent = self.parent
while parent:
hierarchy.append(parent.__class__.__name__)
parent = parent.parent
return hierarchy
# -----------------------------------------------------------------------
def IsClassDescription(self):
"""
Returns a non-empty string if this node holds information about a class general description
(i.e., its `element` attribute does not contain information on a method, a property,
and so on).
This is needed to resolve ReST link conflicts in the :class:`XRef` below.
:rtype: `string`.
"""
top_level = self.GetTopLevelParent()
if top_level is None:
return ''
xml_docs = top_level.xml_docs
if xml_docs.kind != 'class':
return ''
dummy, class_name = wx2Sphinx(xml_docs.class_name)
return class_name
# -----------------------------------------------------------------------
def Find(self, klass, node=None):
"""
This method returns ``True`` if this node contains a specific class into its
descendants.
:param `klass`: can be any of the classes definied in this script except :class:`XMLDocString`.
:param `node`: another `Node` instance or ``None`` if this is the first invocation of
this function.
:rtype: `bool`
.. note:: This is a recursive method.
"""
if node is None:
node = self
for child in node.children:
if isinstance(child, klass):
return True
return self.Find(klass, child)
return False
# -----------------------------------------------------------------------
def GetSpacing(self):
hierarchy = self.GetHierarchy()
if 'ParameterList' in hierarchy:
return ' '
elif not isinstance(self, ListItem) and 'List' in hierarchy:
return ' '*2
return ''
# -----------------------------------------------------------------------
def Join(self, with_tail=True):
"""
Join this node `element` attribute text and tail, adding all its children's
node text and tail in the meanwhile.
:param `with_tail`: ``True`` if the element tail should be included in the
text, ``False`` otherwise.
:rtype: `string`
:returns: A string containing the ReSTified version of this node `element` text and
tail plus all its children's element text and tail.
.. note:: For some of the classes in this script (for example the :class:`Emphasis`,
:class:`ComputerOutput`) the `with_tail` parameter should be set to ``False`` in order
to avoid wrong ReST output.
"""
text = tail = ''
if self.element is None:
return text
if isinstance(self.element, string_base):
text = self.element
else:
text, tail = self.element.text, self.element.tail
text = (text is not None and [text] or [''])[0]
tail = (tail is not None and [tail] or [''])[0]
for link in REMOVED_LINKS:
if link in text.strip():
return ''
text = convertToPython(text)
for child in self.children:
text += child.Join(with_tail)
if with_tail and tail:
text += convertToPython(tail)
if text.strip() and not text.endswith('\n'):
text += ' '
return text
# ----------------------------------------------------------------------- #
class Root(Node):
"""
This is the root class which has as its children all the other classes in
this script (excluding :class:`XMLDocString`). It is used to hold information
about an XML Doxygen item describing a class, a method or a function.
"""
# -----------------------------------------------------------------------
def __init__(self, xml_docs, is_overload, share_docstrings):
"""
Class constructor.
:param XMLDocString `xml_docs`: an instance of :class:`XMLDocString`.
:param bool `is_overload`: ``True`` if the root node describes an overloaded
method/function, ``False`` otherwise.
:param bool `share_docstrings`: ``True`` if all the overloaded methods/functions
share the same docstrings.
"""
Node.__init__(self, '', None)
self.xml_docs = xml_docs
self.is_overload = is_overload
self.share_docstrings = share_docstrings
self.sections = ODict()
# -----------------------------------------------------------------------
def Insert(self, node, before=None, after=None, dontcare=True):
"""
Inserts a node into the root children, depending of the `before` and `after`
input arguments.
"""
insert_at = -1
for index, child in enumerate(self.children):
if (before and child.Find(before)) or (after and child.Find(after)):
insert_at = index
break
node.parent = self
if insert_at >= 0:
if before:
self.children.insert(insert_at, node)
else:
self.children.insert(insert_at+1, node)
else:
if dontcare:
self.children.append(node)
else:
return False
return True
# -----------------------------------------------------------------------
def Join(self, with_tail=True):
"""
Join this node `element` attribute text and tail, adding all its children's
node text and tail in the meanwhile.
:param `with_tail`: ``True`` if the element tail should be included in the
text, ``False`` otherwise.
:rtype: `string`
:returns: A string containing the ReSTified version of this node `element` text and
tail plus all its children's element text and tail.
.. note:: For some of the classes in this script (for example the :class:`Emphasis`,
:class:`ComputerOutput`) the `with_tail` parameter should be set to ``False`` in order
to avoid wrong ReST output.
"""
if self.is_overload and self.share_docstrings:
# If it is an overloaded method and the docstrings are shared, we only return
# information about the parameter list and admonition sections
return self.CommonJoin(self, '')
text = Node.Join(self, with_tail)
# Health check
existing_sections = list(self.sections.keys())
for section_name, dummy in SECTIONS:
if section_name not in self.sections:
continue
existing_sections.remove(section_name)
for section in self.sections[section_name]:
text += '\n\n%s\n\n' % section.Join()
# Health check
if any(existing_sections):
raise Exception('Unconverted sections remain: %s'%(', '.join(existing_sections)))
return text
# -----------------------------------------------------------------------
def CommonJoin(self, node, docstrings):
"""
Selectively join this node `element` attribute text and tail, adding all its children's
node text and tail in the meanwhile but only if they are instances of :class:`ParameterList`
or :class:`Section`.
:param `node`: either `self` or a child node.
:param string `docstrings`: the resulting docstrings obtained (they will go as output as well).
:rtype: `string`
.. note:: This is a recursive method.
"""
for child in node.children:
if isinstance(child, (ParameterList, Section)):
docstrings += child.Join()
docstrings = self.CommonJoin(child, docstrings)
return docstrings
# -----------------------------------------------------------------------
def AddSection(self, section):
"""
Adds an admonition section to the root node (i.e., `.. seealso::`, `.. note::`,
`:returns:` and so on).
Admonitions are somewhat different from other nodes as they need to be refactored and
handled differently when, for example, the return value of a method is different in Phoenix
than in wxWidgets or when the XML docs are a mess and an admonition ends up into
a tail of an xml element...
:param Section `section`: an instance of :class:`Section`.
"""
kind = section.section_type
if kind == 'return':
self.sections[kind] = [section]
elif kind == 'available':
if kind not in self.sections:
text = section.element.text
text = text.split(',')
newtext = []
for t in text:
newtext.append(t[2:].upper())
newtext = ', '.join(newtext)
newtext = 'Only available for %s'%newtext
if section.element.tail and section.element.tail.strip():
newtext += ' ' + section.element.tail.strip() + ' '
else:
newtext += '. '
section.element.text = newtext
self.sections[kind] = [section]
else:
prevsection = self.sections[kind][0]
prevtext = prevsection.element.text
currtext = section.element.text
pos = 1000
if '.' in currtext:
pos = currtext.index('.')
if currtext and currtext.strip():
prevtext = prevtext + currtext[pos+1:].strip()
prevsection.element.text = prevtext
self.sections[kind] = [prevsection]
else:
if kind not in self.sections:
self.sections[kind] = []
self.sections[kind].append(section)
# ----------------------------------------------------------------------- #
class ParameterList(Node):
"""
This class holds information about XML elements with a ``<parameterlist>`` tag.
"""
# -----------------------------------------------------------------------
def __init__(self, element, parent, xml_item, kind):
"""
Class constructor.
:param xml.etree.ElementTree.Element `element`: a XML element containing the
information coming from Doxygen about the ``<parameterlist>`` tag
:param Node `parent`: the parent node, must not be ``None``
:param `xml_item`: one of the classes available in `etgtools/extractors.py`, such as
`PyMethodDef`, `PyFunctionDef` and so on
:param string `kind`: one of `class`, `method`, `function`.
"""
Node.__init__(self, element, parent)
self.xml_item = xml_item
self.kind = kind
self.checked = False
self.py_parameters = ODict()
for pdef in xml_item.items:
name = pdef.name
parameter = Parameter(self, pdef)
self.py_parameters[name] = parameter
# -----------------------------------------------------------------------
def Get(self, element_name):
"""
Returns an instance of :class:`Parameter` if this parameter name (retrieved using
the input `element_name`) is actually in the list of parameters held by this class.
:param string `element_name`: the parameter name.
:rtype: :class:`Parameter` or ``None``
.. note:: Very often the list of parameters in wxWidgets does not match the Phoenix Python
signature, as some of the parameters in Python get merged into one or removed altogether.
"""
name = element_name.strip()
if name in self.py_parameters:
return self.py_parameters[name]
if '_' in name:
name = name[0:name.index('_')]
if name in self.py_parameters:
return self.py_parameters[name]
# -----------------------------------------------------------------------
def CheckSignature(self):
"""
Checks if the function/method signature for items coming from pure C++ implementation
matches the parameter list itself.
This is mostly done as health check, as there are some instances (like `wx.Locale.Init`)
for which the function signature does not match the parameter list (see, for example,
the `shortName` parameter in the signature against the `name` in the parameter list).
These kind of mismatches can sometimes break the ReST docstrings.
"""
if self.checked:
return
self.checked = True
xml_item = self.xml_item
if isinstance(xml_item, (extractors.PyFunctionDef, extractors.CppMethodDef)):
return
name = xml_item.name or xml_item.pyName
name = removeWxPrefix(name)
parent = self.GetTopLevelParent()
is_overload = parent.is_overload if parent else False
if xml_item.overloads and not is_overload:
return
arguments = xml_item.pyArgsString
if not arguments:
return
if hasattr(xml_item, 'isStatic') and not xml_item.isStatic:
if arguments[:2] == '()':
return
arguments = arguments[1:]
if '->' in arguments:
arguments, dummy = arguments.split("->")
arguments = arguments.strip()
if arguments.endswith(','):
arguments = arguments[0:-1]
if arguments.startswith('('):
arguments = arguments[1:]
if arguments.endswith(')'):
arguments = arguments[0:-1]
signature = name + '(%s)'%arguments
arguments = arguments.split(',')
py_parameters = []
for key, parameter in self.py_parameters.items():
pdef = parameter.pdef
if pdef.out or pdef.ignored:
continue
py_parameters.append(key)
message = '\nSEVERE: Incompatibility between function/method signature and list of parameters in `%s`:\n\n' \
'The parameter `%s` appears in the method signature but could not be found in the parameter list.\n\n' \
' ==> Function/Method signature from `extractors`: %s\n' \
' ==> Parameter list from wxWidgets XML items: %s\n\n' \
'This may be a documentation bug in wxWidgets or a side-effect of removing the `wx` prefix from signatures.\n\n'
theargs = []
for arg in arguments:
myarg = arg.split('=')[0].strip()
if myarg:
theargs.append(myarg)
if '*' in arg or ')' in arg:
continue
arg = arg.split('=')[0].strip()
if arg and arg not in py_parameters:
class_name = ''
if hasattr(xml_item, 'className') and xml_item.className is not None:
class_name = wx2Sphinx(xml_item.className)[1] + '.'
print((message % (class_name + name, arg, signature, py_parameters)))
## for param in py_parameters:
## if param not in theargs:
## class_name = ''
## if hasattr(xml_item, 'className') and xml_item.className is not None:
## class_name = wx2Sphinx(xml_item.className)[1] + '.'
##
## print '\n ||| %s;%s;%s |||\n'%(class_name[0:-1], signature, param)
## fid = open('mismatched.txt', 'a')
## fid.write('%s;%s;%s\n'%(class_name[0:-1], signature, param))
## fid.close()
# -----------------------------------------------------------------------
def Join(self, with_tail=True):
"""
Join this node `element` attribute text and tail, adding all its children's
node text and tail in the meanwhile.
:param `with_tail`: ``True`` if the element tail should be included in the
text, ``False`` otherwise.
:rtype: `string`
:returns: A string containing the ReSTified version of this node `element` text and
tail plus all its children's element text and tail.
.. note:: For some of the classes in this script (for example the :class:`Emphasis`,
:class:`ComputerOutput`) the `with_tail` parameter should be set to ``False`` in order
to avoid wrong ReST output.
"""
docstrings = ''
for name, parameter in list(self.py_parameters.items()):
pdef = parameter.pdef
if pdef.out or pdef.ignored:
continue
## print name
## print parameter.Join()
## print
if parameter.type.strip():
docstrings += ':param `%s`: %s\n'%(name, parameter.Join().lstrip('\n'))
docstrings += ':type `%s`: %s\n'%(name, parameter.type)
else:
docstrings += ':param `%s`: %s\n'%(name, parameter.Join().lstrip('\n'))
if docstrings:
docstrings = '\n\n\n%s\n\n'%docstrings
for child in self.children:
if not isinstance(child, Parameter):
docstrings += child.Join() + '\n\n'
return docstrings
# ----------------------------------------------------------------------- #
class Parameter(Node):
"""
This class holds information about XML elements with ``<parametername>``
``<parameterdescription>`` tags.
"""
# -----------------------------------------------------------------------
def __init__(self, parent, pdef):
"""
Class constructor.
:param Node `parent`: the parent node, must not be ``None``
:param `pdef`: a `ParamDef` class, as described in `etgtools/extractors.py`.
"""
Node.__init__(self, '', parent)
self.pdef = pdef
self.name = pdef.name
self.type = pythonizeType(pdef.type, is_param=True)
# ----------------------------------------------------------------------- #
class Paragraph(Node):
"""
This class holds information about XML elements with a ``<para>`` tag.
"""
# -----------------------------------------------------------------------
def __init__(self, element, parent, kind):
"""
Class constructor.
:param xml.etree.ElementTree.Element `element`: a XML element containing the
information coming from Doxygen about the ``<para>`` tag
:param Node `parent`: the parent node, must not be ``None``
:param string `kind`: one of `class`, `method`, `function`.
"""
Node.__init__(self, element, parent)
self.kind = kind
# -----------------------------------------------------------------------
def Join(self, with_tail=True):
"""
Join this node `element` attribute text and tail, adding all its children's
node text and tail in the meanwhile.
:param `with_tail`: ``True`` if the element tail should be included in the
text, ``False`` otherwise.
:rtype: `string`
:returns: A string containing the ReSTified version of this node `element` text and
tail plus all its children's element text and tail.
.. note:: For some of the classes in this script (for example the :class:`Emphasis`,
:class:`ComputerOutput`) the `with_tail` parameter should be set to ``False`` in order
to avoid wrong ReST output.
"""
text = Node.Join(self, with_tail)
if 'Availability:' not in text:
return text
newtext = ''
for line in text.splitlines():
if 'Availability:' in line:
first = line.index('Availability:')
element = et.Element('available', kind='available')
element.text = line[first+13:]
section = Section(element, None, self.kind)
root = self.GetTopLevelParent()
root.AddSection(section)
else:
newtext += line + '\n'
return newtext
# ----------------------------------------------------------------------- #
class ReturnType(Node):
"""
A special admonition section to customize the `:rtype:` ReST role from
the XML / Python description.
"""
# -----------------------------------------------------------------------
def __init__(self, element, parent):
"""
Class constructor.
:param xml.etree.ElementTree.Element `element`: a XML element
:param Node `parent`: the parent node, must not be ``None``
"""
Node.__init__(self, element, parent)
# -----------------------------------------------------------------------
def Join(self, with_tail=True):
"""
Join this node `element` attribute text and tail, adding all its children's
node text and tail in the meanwhile.
:param `with_tail`: ``True`` if the element tail should be included in the
text, ``False`` otherwise.
:rtype: `string`
:returns: A string containing the ReSTified version of this node `element` text and
tail plus all its children's element text and tail.
.. note:: For some of the classes in this script (for example the :class:`Emphasis`,
:class:`ComputerOutput`) the `with_tail` parameter should be set to ``False`` in order
to avoid wrong ReST output.
"""
docstrings = '\n\n:rtype: %s\n\n' % self.element
return docstrings
# ----------------------------------------------------------------------- #
class List(Node):
"""
This class holds information about XML elements with the ``<itemizedlist>``
and ``<orderedlist>`` tags.
"""
# -----------------------------------------------------------------------
def __init__(self, element, parent):
"""
Class constructor.
:param xml.etree.ElementTree.Element `element`: a XML element containing the
information coming from Doxygen about the ``<itemizedlist>`` and ``<orderedlist>`` tags
:param Node `parent`: the parent node, must not be ``None``
"""
Node.__init__(self, element, parent)
# -----------------------------------------------------------------------
def Join(self, with_tail=True):
"""
Join this node `element` attribute text and tail, adding all its children's
node text and tail in the meanwhile.
:param `with_tail`: ``True`` if the element tail should be included in the
text, ``False`` otherwise.
:rtype: `string`
:returns: A string containing the ReSTified version of this node `element` text and
tail plus all its children's element text and tail.
.. note:: For some of the classes in this script (for example the :class:`Emphasis`,
:class:`ComputerOutput`) the `with_tail` parameter should be set to ``False`` in order
to avoid wrong ReST output.
"""
docstrings = Node.Join(self, with_tail=False)
docstrings = '\n\n%s\n'%docstrings
if self.element.tail:
spacer = ('ParameterList' in self.GetHierarchy() and [' '] or [''])[0]
text = '%s%s\n'%(spacer, convertToPython(self.element.tail.strip()))
docstrings += text
return docstrings
# ----------------------------------------------------------------------- #
class ListItem(Node):
"""
This class holds information about XML elements with the ``<listitem>`` tag.
"""
# -----------------------------------------------------------------------
def __init__(self, element, parent):
"""
Class constructor.
:param xml.etree.ElementTree.Element `element`: a XML element containing the
information coming from Doxygen about the ``<listitem>`` tag
:param Node `parent`: the parent node, must not be ``None``
"""
Node.__init__(self, element, parent)
self.level = parent.GetHierarchy().count('List') - 1
# -----------------------------------------------------------------------
def GetSpacing(self):
hierarchy = self.GetHierarchy()
if 'ParameterList' in hierarchy:
return ' '
elif 'Section' in hierarchy:
return ' '*3
return ' ' * self.level
# -----------------------------------------------------------------------
def Join(self, with_tail=True):
"""
Join this node `element` attribute text and tail, adding all its children's
node text and tail in the meanwhile.
:param `with_tail`: ``True`` if the element tail should be included in the
text, ``False`` otherwise.
:rtype: `string`
:returns: A string containing the ReSTified version of this node `element` text and
tail plus all its children's element text and tail.
.. note:: For some of the classes in this script (for example the :class:`Emphasis`,
:class:`ComputerOutput`) the `with_tail` parameter should be set to ``False`` in order
to avoid wrong ReST output.
"""
spacer = self.GetSpacing()
to_remove = ['(id, event, func)', '(id1, id2, event, func)', '(id1, id2, func)',
'(id, func)', '(func)',
'(id, event, func)', '(id1, id2, event, func)', '(id1, id2, func)',
'(id, func)']
docstrings = ''
for child in self.children:
child_text = child.Join(with_tail)
for rem in to_remove:
child_text = child_text.replace(rem, '')
if '_:' in child_text:
child_text = child_text.replace('_:', '_*:')
docstrings += child_text
docstrings = '%s- %s\n'%(spacer, docstrings)
return docstrings
# ----------------------------------------------------------------------- #
class Section(Node):
"""
This class holds information about XML elements with the ``<xrefsect>`` and
``<simplesect>`` tags.
"""
# -----------------------------------------------------------------------
def __init__(self, element, parent, kind, is_overload=False, share_docstrings=False):
"""
Class constructor.
:param xml.etree.ElementTree.Element `element`: a XML element containing the
information coming from Doxygen about the ``<xrefsect>`` and ``<simplesect>`` tags
:param Node `parent`: the parent node, can be ``None``
:param string `kind`: one of `class`, `method`, `function`
:param bool `is_overload`: ``True`` if the root node describes an overloaded
method/function, ``False`` otherwise.
:param bool `share_docstrings`: ``True`` if all the overloaded methods/functions
share the same docstrings.
"""
Node.__init__(self, element, parent)
self.kind = kind
self.is_overload = is_overload
self.share_docstrings = share_docstrings
dummy, section_type = list(self.element.items())[0]
self.section_type = section_type.split("_")[0]
# -----------------------------------------------------------------------
def Join(self, with_tail=True):
"""
Join this node `element` attribute text and tail, adding all its children's
node text and tail in the meanwhile.
:param `with_tail`: ``True`` if the element tail should be included in the
text, ``False`` otherwise.
:rtype: `string`
:returns: A string containing the ReSTified version of this node `element` text and
tail plus all its children's element text and tail.
.. note:: For some of the classes in this script (for example the :class:`Emphasis`,
:class:`ComputerOutput`) the `with_tail` parameter should be set to ``False`` in order
to avoid wrong ReST output.
"""
section_type = self.section_type
text = Node.Join(self, with_tail=False)
if not text.strip() or len(text.strip()) < 3:
# Empy text or just trailing commas
return ''
if self.is_overload and self.share_docstrings:
return ''
sub_spacer = ' '*3
if section_type == 'since':
# Special treatment for the versionadded
if len(text) > 6:
version, remainder = text[0:6], text[6:]
if '.' in version:
text = '%s\n%s%s'%(version, sub_spacer, remainder)
else:
target = (' 2.' in text and [' 2.'] or [' 3.'])[0]
vindex1 = text.index(target)
vindex2 = text[vindex1+2:].index(' ') + vindex1 + 2
version = text[vindex1:vindex2].strip()
if version.endswith('.'):
version = version[0:-1]
text = '%s\n%s%s'%(version, sub_spacer, text)
elif section_type == 'deprecated':
# Special treatment for deprecated, wxWidgets devs do not put the version number
text = '%s\n%s%s'%(VERSION, sub_spacer, text.lstrip('Deprecated'))
elif section_type == 'par':
# Horrible hack... Why is there a </para> end tag inside the @par tag???
text = Node.Join(self, with_tail=True)
split = text.split('\n')
current = 0
for index, line in enumerate(split):
if '---' in line:
current = index-1
break
return '\n\n' + '\n'.join(split[current:]) + '\n\n'
if section_type in ['note', 'remark', 'remarks', 'return']:
text = '\n\n' + sub_spacer + text
for section, replacement in SECTIONS:
if section == section_type:
break
docstrings = ''
section_spacer = ''
if section_type != 'return':
section_spacer = self.GetSpacing()
docstrings = '\n%s%s %s\n\n'%(section_spacer, replacement, text)
return '\n' + docstrings
# ----------------------------------------------------------------------- #
class Image(Node):
"""
This class holds information about XML elements with the ``<image>`` tag.
"""
# -----------------------------------------------------------------------
def __init__(self, element, parent):
"""
Class constructor.
:param xml.etree.ElementTree.Element `element`: a XML element containing the
information coming from Doxygen about the ``<image>`` tag
:param Node `parent`: the parent node, must not be ``None``
"""
Node.__init__(self, element, parent)
# -----------------------------------------------------------------------
def Join(self, with_tail=True):
"""
Join this node `element` attribute text and tail, adding all its children's
node text and tail in the meanwhile.
:param `with_tail`: ``True`` if the element tail should be included in the
text, ``False`` otherwise.
:rtype: `string`
:returns: A string containing the ReSTified version of this node `element` text and
tail plus all its children's element text and tail.
.. note:: For some of the classes in this script (for example the :class:`Emphasis`,
:class:`ComputerOutput`) the `with_tail` parameter should be set to ``False`` in order
to avoid wrong ReST output.
"""
for key, value in list(self.element.items()):
if key == 'name':
break
if 'appear-' in value:
return ''
image_path = os.path.normpath(os.path.join(DOXYROOT, 'images', value))
static_path = os.path.join(OVERVIEW_IMAGES_ROOT, os.path.split(image_path)[1])
if not os.path.isfile(static_path):
shutil.copyfile(image_path, static_path)
rel_path_index = static_path.rfind('_static')
rel_path = os.path.normpath(static_path[rel_path_index:])
docstrings = '\n\n'
docstrings += '.. figure:: %s\n' % rel_path
docstrings += ' :align: center\n\n\n'
docstrings += '|\n\n'
if self.element.tail and self.element.tail.strip():
docstrings += convertToPython(self.element.tail.rstrip())
return docstrings
# ----------------------------------------------------------------------- #
class Table(Node):
"""
This class holds information about XML elements with the ``<table>`` tag.
"""
# -----------------------------------------------------------------------
def __init__(self, element, parent, xml_item_name):
"""
Class constructor.
:param xml.etree.ElementTree.Element `element`: a XML element containing the
information coming from Doxygen about the ``<table>`` tag
:param Node `parent`: the parent node, must not be ``None``
:param string `xml_item_name`: if a custom version of a table has been created
inside the ``TABLEROOT`` folder, the `xml_item_name` string will match the
``*.rst`` file name for the custom table.
.. note::
There are 4 customized versions of 4 XML tables up to now, for various reasons:
1. The `wx.Sizer` flags table is a flexible grid table, very difficult to ReSTify automatically
2. The `wx.ColourDatabase` table of colour comes up all messy when ReSTified from XML
3. The "wxWidgets 2.8 Compatibility Functions" table for `wx.VScrolledWindow`
4. The `wx.ArtProvider` IDs table
"""
Node.__init__(self, element, parent)
self.xml_item_name = xml_item_name
# -----------------------------------------------------------------------
def Join(self, with_tail=True):
"""
Join this node `element` attribute text and tail, adding all its children's
node text and tail in the meanwhile.
:param `with_tail`: ``True`` if the element tail should be included in the
text, ``False`` otherwise.
:rtype: `string`
:returns: A string containing the ReSTified version of this node `element` text and
tail plus all its children's element text and tail.
.. note:: For some of the classes in this script (for example the :class:`Emphasis`,
:class:`ComputerOutput`) the `with_tail` parameter should be set to ``False`` in order
to avoid wrong ReST output.
"""
needs_space = 'ParameterList' in self.GetHierarchy()
spacer = (needs_space and [' '] or [''])[0]
rows, cols = int(self.element.get('rows')), int(self.element.get('cols'))
longest = [0]*cols
has_title = False
count = 0
for row in range(rows):
for col in range(cols):
child = self.children[count]
text = child.Join(with_tail)
longest[col] = max(len(text), longest[col])
if (row == 0 and col == 0 and '**' in text) or child.GetTag('thead') == 'yes':
has_title = True
count += 1
table = '\n\n'
table += spacer
formats = []
for lng in longest:
table += '='* lng + ' '
formats.append('%-' + '%ds'%(lng+1))
table += '\n'
count = 0
for row in range(rows):
table += spacer
for col in range(cols):
table += formats[col] % (self.children[count].Join(with_tail).strip())
count += 1
table += '\n'
if row == 0 and has_title:
table += spacer
for lng in longest:
table += '='* lng + ' '
table += '\n'
table += spacer
for lng in longest:
table += '='* lng + ' '
table += '\n\n%s|\n\n'%spacer
possible_rest_input = os.path.join(TABLEROOT, self.xml_item_name)
if os.path.isfile(possible_rest_input):
# Work around for the buildbot sphinx generator which seems unable
# to find the tables...
rst_file = os.path.split(possible_rest_input)[1]
rst_folder = os.path.normpath(os.path.relpath(TABLEROOT, SPHINXROOT))
table = '\n\n' + spacer + '.. include:: %s\n\n'%os.path.join(rst_folder, rst_file)
if self.element.tail and self.element.tail.strip():
rest = convertToPython(self.element.tail.rstrip())
split = rest.splitlines()
for index, r in enumerate(split):
table += spacer + r
if index < len(split)-1:
table += '\n'
return table
# ----------------------------------------------------------------------- #
class TableEntry(Node):
"""
This class holds information about XML elements with the ``<entry>`` tag.
"""
pass
# ----------------------------------------------------------------------- #
class Snippet(Node):
"""
This class holds information about XML elements with the ``<programlisting>``,
``<sp>``, ``<codeline>``, ``<highlight>`` and ``<ref>`` (but only when the
``<ref>`` tags appears as a child of ``<programlisting>``) tags.
"""
# -----------------------------------------------------------------------
def __init__(self, element, parent, cpp_file, python_file, converted_py):
"""
Class constructor.
:param xml.etree.ElementTree.Element `element`: a XML element containing the
information coming from Doxygen about the aforementioned tags
:param Node `parent`: the parent node, must not be ``None``
:param string `cpp_file`: the path to the C++ snippet of code found in the XML
wxWidgets docstring, saved into the ``SNIPPETROOT/cpp`` folder
:param string `python_file`: the path to the roughly-converted to Python
snippet of code found in the XML wxWidgets docstring, saved into the
``SNIPPETROOT/python`` folder
:param string `converted_py`: the path to the fully-converted to Python
snippet of code found in the XML wxWidgets docstring, saved into the
``SNIPPETROOT/python/converted`` folder.
"""
Node.__init__(self, element, parent)
self.cpp_file = cpp_file
self.python_file = python_file
self.converted_py = converted_py
self.snippet = ''
# -----------------------------------------------------------------------
def AddCode(self, element):
"""
Adds a C++ part of the code snippet.
:param xml.etree.ElementTree.Element `element`: a XML element containing the
information coming from Doxygen about the aforementioned tags.
"""
tag = element.tag
if tag == 'codeline':
self.snippet += '\n'
elif tag in ['highlight', 'ref', 'sp']:
if tag == 'sp':
self.snippet += ' '
if isinstance(element, string_base):
self.snippet += element
else:
if element.text:
self.snippet += element.text.strip(' ')
if element.tail:
self.snippet += element.tail.strip(' ')
else:
raise Exception('Unhandled tag in class Snippet: %s'%tag)
# -----------------------------------------------------------------------
def Join(self, with_tail=True):
"""
Join this node `element` attribute text and tail, adding all its children's
node text and tail in the meanwhile.
:param `with_tail`: ``True`` if the element tail should be included in the
text, ``False`` otherwise.
:rtype: `string`
:returns: A string containing the ReSTified version of this node `element` text and
tail plus all its children's element text and tail.
.. note:: For some of the classes in this script (for example the :class:`Emphasis`,
:class:`ComputerOutput`) the `with_tail` parameter should be set to ``False`` in order
to avoid wrong ReST output.
"""
docstrings = ''
if not os.path.exists(os.path.dirname(self.cpp_file)):
os.makedirs(os.path.dirname(self.cpp_file))
fid = open(self.cpp_file, 'wt')
fid.write(self.snippet)
fid.close()
if not os.path.isfile(self.converted_py):
message = '\nWARNING: Missing C++ => Python conversion of the snippet of code for %s'%(os.path.split(self.cpp_file)[1])
message += '\n\nA slightly Pythonized version of this snippet has been saved into:\n\n ==> %s\n\n'%self.python_file
print(message)
py_code = self.snippet.replace(';', '')
py_code = py_code.replace('{', '').replace('}', '')
py_code = py_code.replace('->', '.').replace('//', '#')
py_code = py_code.replace('m_', 'self.')
py_code = py_code.replace('::', '.')
py_code = py_code.replace('wx', 'wx.')
py_code = py_code.replace('new ', '').replace('this', 'self')
py_code = py_code.replace('( ', '(').replace(' )', ')')
py_code = py_code.replace('||', 'or').replace('&&', 'and')
spacer = ' '*4
new_py_code = ''
for code in py_code.splitlines():
new_py_code += spacer + code + '\n'
fid = open(self.python_file, 'wt')
fid.write(new_py_code)
fid.close()
else:
fid = open(self.converted_py, 'rt')
highlight = None
while 1:
tline = fid.readline()
if not tline: # end of file
code = ""
fid.close()
break
if 'code-block::' in tline:
highlight = tline.replace('#', '').strip()
continue
if not tline.strip():
continue
code = tline + fid.read()
fid.close()
break
if highlight:
docstrings += '\n\n%s\n\n'%highlight
else:
docstrings += '::\n\n'
docstrings += code.rstrip() + '\n\n'
if self.element.tail and len(self.element.tail.strip()) > 1:
hierarchy = self.GetHierarchy()
spacer = ''
if 'Section' in hierarchy:
spacer = ' '*3
elif 'Parameter' in hierarchy:
spacer = ' '
elif 'List' in hierarchy:
spacer = ' '
tail = convertToPython(self.element.tail.lstrip())
tail = tail.replace('\n', ' ')
docstrings += spacer + tail.replace(' ', ' ')
return docstrings
# ----------------------------------------------------------------------- #
class XRef(Node):
"""
This class holds information about XML elements with the ``<ref>`` tag, excluding
when these elements are children of a ``<programlisting>`` element.
"""
# -----------------------------------------------------------------------
def __init__(self, element, parent):
"""
Class constructor.
:param xml.etree.ElementTree.Element `element`: a XML element containing the
information coming from Doxygen about the aforementioned tags
:param Node `parent`: the parent node, must not be ``None``.
"""
Node.__init__(self, element, parent)
# -----------------------------------------------------------------------
def Join(self, with_tail=True):
"""
Join this node `element` attribute text and tail, adding all its children's
node text and tail in the meanwhile.
:param `with_tail`: ``True`` if the element tail should be included in the
text, ``False`` otherwise.
:rtype: `string`
:returns: A string containing the ReSTified version of this node `element` text and
tail plus all its children's element text and tail.
.. note:: For some of the classes in this script (for example the :class:`Emphasis`,
:class:`ComputerOutput`) the `with_tail` parameter should be set to ``False`` in order
to avoid wrong ReST output.
"""
element = self.element
text = element.text
tail = element.tail
tail = (tail is not None and [tail] or [''])[0]
hascomma = '::' in text
original = text
text = removeWxPrefix(text)
text = text.replace("::", ".")
if "(" in text:
text = text[0:text.index("(")]
refid, link = list(element.items())[0]
remainder = link.split('_')[-1]
space_before, space_after = countSpaces(text)
stripped = text.strip()
if stripped in IGNORE:
return space_before + text + space_after + tail
if ' ' in stripped or 'overview' in link:
if 'classwx_' in link:
ref = 1000
if '_1' in link:
ref = link.index('_1')
ref = underscore2Capitals(link[6:ref])
text = ':ref:`%s <%s>`'%(stripped, ref)
elif 'funcmacro' in link or 'samples' in link or 'debugging' in text.lower() or \
'unix' in text.lower() or 'page_libs' in link:
text = space_before + space_after
elif 'library list' in stripped.lower():
text = space_before + text + space_after
else:
backlink = stripped.lower()
if 'device context' in backlink:
backlink = 'device contexts'
elif 'reference count' in backlink or 'refcount' in link:
backlink = 'reference counting'
elif 'this list' in backlink:
backlink = 'stock items'
elif 'window deletion' in backlink:
backlink = 'window deletion'
elif 'programming with wxboxsizer' in backlink:
stripped = 'Programming with BoxSizer'
backlink = 'programming with boxsizer'
text = ':ref:`%s <%s>`'%(stripped, backlink)
elif (text.upper() == text and len(stripped) > 4):
if not original.strip().startswith('wx') or ' ' in stripped:
text = ''
elif not isNumeric(text):
text = '``%s``'%text
elif 'funcmacro' in link:
if '(' in stripped:
stripped = stripped[0:stripped.index('(')].strip()
text = ':func:`%s`'%stripped
elif hascomma or len(remainder) > 30:
if '.m_' in text:
text = '``%s``'%stripped
else:
# it was :meth:
if '.wx' in text:
prev = text.split('.')
text = '.'.join(prev[:-1]) + '.__init__'
text = ':meth:`%s` '%text.strip()
else:
stripped = text.strip()
if '(' in stripped:
stripped = stripped[0:stripped.index('(')].strip()
if stripped in NO_MODULE:
text = ':ref:`%s`'%(NO_MODULE[stripped] + stripped)
else:
if '.' not in stripped:
klass = self.IsClassDescription()
if klass:
text = ':meth:`~%s.%s`'%(klass, stripped)
else:
text = ':meth:`%s` '%stripped
else:
text = ':meth:`%s` '%stripped
else:
text = ':ref:`%s`' % wx2Sphinx(stripped)[1]
return space_before + text + space_after + convertToPython(tail)
# ----------------------------------------------------------------------- #
class ComputerOutput(Node):
"""
This class holds information about XML elements with the ``<computeroutput>`` tag.
"""
def __init__(self, element, parent):
"""
Class constructor.
:param xml.etree.ElementTree.Element `element`: a XML element containing the
information coming from Doxygen about the aforementioned tags
:param Node `parent`: the parent node, must not be ``None``.
"""
Node.__init__(self, element, parent)
# -----------------------------------------------------------------------
def Join(self, with_tail=True):
"""
Join this node `element` attribute text and tail, adding all its children's
node text and tail in the meanwhile.
:param `with_tail`: ``True`` if the element tail should be included in the
text, ``False`` otherwise.
:rtype: `string`
:returns: A string containing the ReSTified version of this node `element` text and
tail plus all its children's element text and tail.
.. note:: For some of the classes in this script (for example the :class:`Emphasis`,
:class:`ComputerOutput`) the `with_tail` parameter should be set to ``False`` in order
to avoid wrong ReST output.
"""
text = self.element.text
if not text and not self.children:
return ''
if text is not None:
stripped = text.strip()
space_before, space_after = countSpaces(text)
text = removeWxPrefix(text.strip())
else:
text = ''
for child in self.children:
text += child.Join(with_tail=False)
if '`' not in text:
text = "``%s`` "%text
if self.element.tail:
text += convertToPython(self.element.tail)
space_before, space_after = countSpaces(text)
if space_before == '':
space_before = ' '
return space_before + text + space_after
# ----------------------------------------------------------------------- #
class Emphasis(Node):
"""
This class holds information about XML elements with the ``<emphasis>`` and
``<bold>`` tags.
"""
# -----------------------------------------------------------------------
def __init__(self, element, parent):
"""
Class constructor.
:param xml.etree.ElementTree.Element `element`: a XML element containing the
information coming from Doxygen about the aforementioned tags
:param Node `parent`: the parent node, must not be ``None``.
"""
Node.__init__(self, element, parent)
# -----------------------------------------------------------------------
def Join(self, with_tail=True):
"""
Join this node `element` attribute text and tail, adding all its children's
node text and tail in the meanwhile.
:param `with_tail`: ``True`` if the element tail should be included in the
text, ``False`` otherwise.
:rtype: `string`
:returns: A string containing the ReSTified version of this node `element` text and
tail plus all its children's element text and tail.
.. note:: For some of the classes in this script (for example the :class:`Emphasis`,
:class:`ComputerOutput`) the `with_tail` parameter should be set to ``False`` in order
to avoid wrong ReST output.
"""
text = Node.Join(self, with_tail=False)
if '``' in text:
format = '%s'
emphasis = ''
elif self.element.tag == 'emphasis':
format = '`%s`'
emphasys = '`'
elif self.element.tag == 'bold':
format = '**%s**'
emphasys = '**'
spacing = ('ParameterList' in self.GetHierarchy() and [' '] or [''])[0]
if self.children:
startPos = 0
newText = spacing
for child in self.children:
childText = child.Join()
tail = child.element.tail
tail = (tail is not None and [tail] or [''])[0]
if tail.strip() != ':':
childText = childText.replace(convertToPython(tail), '')
fullChildText = child.Join()
endPos = text.index(childText)
newText += ' ' + emphasys + text[startPos:endPos].strip() + emphasys + ' '
newText += childText + ' '
remaining = fullChildText.replace(childText, '')
newText += emphasys + remaining.strip() + emphasys + ' '
startPos = endPos
text = newText
else:
if text.strip():
text = spacing + format % text.strip()
if self.element.tail:
text += convertToPython(self.element.tail)
return text
# ----------------------------------------------------------------------- #
class Title(Node):
"""
This class holds information about XML elements with the ``<title>`` tag.
"""
# -----------------------------------------------------------------------
def __init__(self, element, parent):
"""
Class constructor.
:param xml.etree.ElementTree.Element `element`: a XML element containing the
information coming from Doxygen about the aforementioned tags
:param Node `parent`: the parent node, must not be ``None``.
"""
Node.__init__(self, element, parent)
# -----------------------------------------------------------------------
def Join(self, with_tail=True):
"""
Join this node `element` attribute text and tail, adding all its children's
node text and tail in the meanwhile.
:param `with_tail`: ``True`` if the element tail should be included in the
text, ``False`` otherwise.
:rtype: `string`
:returns: A string containing the ReSTified version of this node `element` text and
tail plus all its children's element text and tail.
.. note:: For some of the classes in this script (for example the :class:`Emphasis`,
:class:`ComputerOutput`) the `with_tail` parameter should be set to ``False`` in order
to avoid wrong ReST output.
"""
if isinstance(self.parent, Section) and self.parent.section_type == 'par':
# Sub-title in a @par doxygen tag
text = convertToPython(self.element.text)
underline = '-'
else:
# Normal big title
text = '|phoenix_title| ' + convertToPython(self.element.text)
underline = '='
lentext = len(text)
text = '\n\n%s\n%s\n\n'%(text.rstrip('.'), underline*lentext)
return text
# ----------------------------------------------------------------------- #
class ULink(Node):
"""
This class holds information about XML elements with the ``<ulink>`` tag.
"""
# -----------------------------------------------------------------------
def __init__(self, element, parent):
"""
Class constructor.
:param xml.etree.ElementTree.Element `element`: a XML element containing the
information coming from Doxygen about the aforementioned tags
:param Node `parent`: the parent node, must not be ``None``.
"""
Node.__init__(self, element, parent)
# -----------------------------------------------------------------------
def Join(self, with_tail=True):
"""
Join this node `element` attribute text and tail, adding all its children's
node text and tail in the meanwhile.
:param `with_tail`: ``True`` if the element tail should be included in the
text, ``False`` otherwise.
:rtype: `string`
:returns: A string containing the ReSTified version of this node `element` text and
tail plus all its children's element text and tail.
.. note:: For some of the classes in this script (for example the :class:`Emphasis`,
:class:`ComputerOutput`) the `with_tail` parameter should be set to ``False`` in order
to avoid wrong ReST output.
"""
dummy, link = list(self.element.items())[0]
text = self.element.text
text = '`%s <%s>`_'%(text, link)
if self.element.tail:
text += convertToPython(self.element.tail)
return text
# ----------------------------------------------------------------------- #
class XMLDocString(object):
"""
This is the main class of this script, and it uses heavily the :class:`Node`
subclasses documented above.
The :class:`XMLDocString` is responsible for building function/method signatures,
class descriptions, window styles and events and so on.
"""
# -----------------------------------------------------------------------
def __init__(self, xml_item, is_overload=False, share_docstrings=False):
"""
Class constructor.
:param `xml_item`: one of the classes available in `etgtools/extractors.py`, such as
`PyMethodDef`, `PyFunctionDef` and so on
:param bool `is_overload`: ``True`` if this class describes an overloaded
method/function, ``False`` otherwise.
:param bool `share_docstrings`: ``True`` if all the overloaded methods/functions
share the same docstrings.
"""
self.xml_item = xml_item
self.is_overload = is_overload
self.share_docstrings = share_docstrings
self.docstrings = ''
self.class_name = ''
self.snippet_count = 0
self.contrib_snippets = []
self.table_count = 0
self.list_level = 1
self.list_order = {}
self.parameter_list = None
self.root = Root(self, is_overload, share_docstrings)
self.appearance = []
self.overloads = []
if isinstance(xml_item, extractors.MethodDef):
self.kind = 'method'
elif isinstance(xml_item, (extractors.FunctionDef, extractors.PyFunctionDef)):
self.kind = 'function'
elif isinstance(xml_item, (extractors.ClassDef, extractors.PyClassDef, extractors.TypedefDef)):
self.kind = 'class'
self.appearance = findControlImages(xml_item)
self.class_name = removeWxPrefix(xml_item.name) or xml_item.pyName
elif isinstance(xml_item, extractors.EnumDef):
self.kind = 'enum'
else:
raise Exception('Unhandled docstring kind for %s'%xml_item.__class__.__name__)
if hasattr(xml_item, 'deprecated') and xml_item.deprecated and isinstance(xml_item.deprecated, string_base):
element = et.Element('deprecated', kind='deprecated')
element.text = VERSION
deprecated_section = Section(element, None, self.kind, self.is_overload, self.share_docstrings)
self.root.AddSection(deprecated_section)
# -----------------------------------------------------------------------
def ToReST(self):
""" Auxiliary method. """
brief, detailed = self.xml_item.briefDoc, self.xml_item.detailedDoc
self.RecurseXML(brief, self.root)
for detail in detailed:
blank_element = Node('\n\n\n', self.root)
self.RecurseXML(detail, self.root)
self.InsertParameterList()
self.BuildSignature()
self.docstrings = self.root.Join()
self.LoadOverLoads()
# -----------------------------------------------------------------------
def GetBrief(self):
"""
Returns a ReSTified version of the `briefDoc` attribute for the XML docstrings.
:rtype: `string`
"""
brief = self.xml_item.briefDoc
dummy_root = Root(self, False, False)
rest_class = self.RecurseXML(brief, dummy_root)
return rest_class.Join()
# -----------------------------------------------------------------------
def LoadOverLoads(self):
"""
Extracts the overloaded implementations of a method/function, unless this
class is itself an overload or the current method/function has no overloads.
"""
if self.is_overload:
return
if self.kind not in ['method', 'function'] or not self.xml_item.overloads:
return
share_docstrings = True
all_docs = []
for sub_item in [self.xml_item] + self.xml_item.overloads:
if sub_item.ignored:
continue
dummy_root = Root(self, False, False)
self.RecurseXML(sub_item.briefDoc, dummy_root)
for det in sub_item.detailedDoc:
self.RecurseXML(det, dummy_root)
all_docs.append(dummy_root.Join())
if len(all_docs) == 1:
# Only one overload, don't act like there were more
self.xml_item.overloads = []
return
zero = all_docs[0]
for docs in all_docs[1:]:
if docs != zero:
share_docstrings = False
break
self.share_docstrings = share_docstrings
for sub_item in [self.xml_item] + self.xml_item.overloads:
if sub_item.ignored:
continue
sub_item.name = self.xml_item.pyName or removeWxPrefix(self.xml_item.name)
docstring = XMLDocString(sub_item, is_overload=True, share_docstrings=share_docstrings)
docstring.class_name = self.class_name
docstring.current_module = self.current_module
docs = docstring.Dump()
self.overloads.append(docs)
# -----------------------------------------------------------------------
def RecurseXML(self, element, parent):
"""
Scan recursively all the XML elements which make up the whole documentation for
a particular class/method/function.
:param xml.etree.ElementTree.Element `element`: a XML element containing the
information coming from Doxygen about the aforementioned tags
:param Node `parent`: the parent node, a subclass of the :class:`Node` class.
:rtype: a subclass of :class:`Node`
.. note: This is a recursive method.
"""
if element is None:
return Node('', parent)
if isinstance(element, string_base):
rest_class = Paragraph(element, parent, self.kind)
return rest_class
tag, text, tail = element.tag, element.text, element.tail
text = (text is not None and [text] or [''])[0]
tail = (tail is not None and [tail] or [''])[0]
if tag == 'parameterlist':
rest_class = ParameterList(element, parent, self.xml_item, self.kind)
self.parameter_list = rest_class
elif tag == 'parametername':
self.parameter_name = text
rest_class = self.parameter_list
elif tag == 'parameterdescription':
parameter_class = self.parameter_list.Get(self.parameter_name)
if parameter_class:
rest_class = parameter_class
parameter_class.element = element
else:
rest_class = self.parameter_list
elif tag in ['itemizedlist', 'orderedlist']:
rest_class = List(element, parent)
elif tag == 'listitem':
rest_class = ListItem(element, parent)
elif tag in ['simplesect', 'xrefsect']:
if 'ListItem' in parent.GetHierarchy():
rest_class = Section(element, parent, self.kind, self.is_overload, self.share_docstrings)
else:
dummy, section_type = list(element.items())[0]
section_type = section_type.split("_")[0]
if element.tail and section_type != 'par':
Node(element.tail, parent)
if section_type == 'par':
# doxygen @par stuff
rest_class = Section(element, parent, self.kind, self.is_overload, self.share_docstrings)
if element.tail:
Node(element.tail, rest_class)
else:
rest_class = Section(element, None, self.kind, self.is_overload, self.share_docstrings)
self.root.AddSection(rest_class)
elif tag == 'image':
rest_class = Image(element, parent)
elif tag == 'table':
fullname = self.GetFullName()
self.table_count += 1
fullname = '%s.%d.rst'%(fullname, self.table_count)
rest_class = Table(element, parent, fullname)
self.table = rest_class
elif tag == 'entry':
rest_class = TableEntry(element, self.table)
elif tag == 'row':
rest_class = self.table
elif tag == 'programlisting':
cpp_file, python_file, converted_py = self.SnippetName()
rest_class = Snippet(element, parent, cpp_file, python_file, converted_py)
self.code = rest_class
elif tag in ['codeline', 'highlight', 'sp']:
self.code.AddCode(element)
rest_class = self.code
elif tag == 'ref':
if 'Snippet' in parent.GetHierarchy():
self.code.AddCode(element)
rest_class = self.code
else:
rest_class = XRef(element, parent)
elif tag == 'computeroutput':
rest_class = ComputerOutput(element, parent)
elif tag in ['emphasis', 'bold']:
rest_class = Emphasis(element, parent)
elif tag == 'title':
text = convertToPython(element.text)
rest_class = Title(element, parent)
elif tag == 'para':
rest_class = Paragraph(element, parent, self.kind)
elif tag == 'linebreak':
spacer = ('ParameterList' in parent.GetHierarchy() and [' '] or [''])[0]
dummy = Node('\n\n%s%s'%(spacer, tail.strip()), parent)
rest_class = parent
elif tag == 'ulink':
rest_class = ULink(element, parent)
elif tag == 'onlyfor':
onlyfor = et.Element('available', kind='available')
onlyfor.text = text
onlyfor.tail = tail
section = Section(onlyfor, None, self.kind)
self.root.AddSection(section)
rest_class = parent
else:
rest_class = Node('', parent)
for child_element in element:
self.RecurseXML(child_element, rest_class)
return rest_class
# -----------------------------------------------------------------------
def GetFullName(self):
"""
Returns the complete name for a class/method/function, including
its module/package.
:rtype: `string`
"""
if self.kind == 'class':
klass = self.xml_item
name = removeWxPrefix(klass.name) or klass.pyName
dummy, fullname = wx2Sphinx(name)
elif self.kind == 'method':
method = self.xml_item
if hasattr(method, 'isCtor') and method.isCtor:
method_name = '__init__'
if hasattr(method, 'className') and method.className is not None:
klass = removeWxPrefix(method.className)
else:
klass = removeWxPrefix(method.klass.name)
method_name = '%s.%s'%(klass, method_name)
else:
method_name = method.name or method.pyName
if hasattr(method, 'className') and method.className is not None:
klass = removeWxPrefix(method.className)
method_name = '%s.%s'%(klass, method_name)
elif hasattr(method, 'klass'):
klass = removeWxPrefix(method.klass.name)
method_name = '%s.%s'%(klass, method_name)
else:
method_name = removeWxPrefix(method_name)
method_name = '%s'%method_name
klass = None
dummy, fullname = wx2Sphinx(method_name)
elif self.kind == 'function':
function = self.xml_item
name = function.pyName or function.name
fullname = self.current_module + 'functions.%s'%name
if not fullname.strip():
dummy = xml_item.name or xml_item.pyName
raise Exception('Invalid item name for %s (kind=%s)'%(dummy, self.kind))
return fullname
# -----------------------------------------------------------------------
def SnippetName(self):
"""
Returns a tuple of 3 elements (3 file paths), representing the following:
1. `cpp_file`: the path to the C++ snippet of code found in the XML
wxWidgets docstring, saved into the ``SNIPPETROOT/cpp`` folder
2. `python_file`: the path to the roughly-converted to Python
snippet of code found in the XML wxWidgets docstring, saved into the
``SNIPPETROOT/python`` folder
3. `converted_py`: the path to the fully-converted to Python
snippet of code found in the XML wxWidgets docstring, saved into the
``SNIPPETROOT/python/converted`` folder.
"""
fullname = self.GetFullName()
self.snippet_count += 1
cpp_file = os.path.join(SNIPPETROOT, 'cpp', fullname + '.%d.cpp'%self.snippet_count)
python_file = os.path.join(SNIPPETROOT, 'python', fullname + '.%d.py'%self.snippet_count)
converted_py = os.path.join(SNIPPETROOT, 'python', 'converted', fullname + '.%d.py'%self.snippet_count)
return cpp_file, python_file, converted_py
def HuntContributedSnippets(self):
fullname = self.GetFullName()
contrib_folder = os.path.join(SNIPPETROOT, 'python', 'contrib')
possible_py = []
for suffix in range(1, 101):
sample = os.path.join(contrib_folder, '%s.%d.py'%(fullname, suffix))
if not os.path.isfile(sample):
break
possible_py.append(sample)
return possible_py
# -----------------------------------------------------------------------
def Dump(self, write=True):
"""
Dumps the whole ReSTified docstrings and returns its correct ReST representation.
:param bool `write`: ``True`` to write the resulting docstrings to a file, ``False``
otherwise.
:rtype: `string`
"""
self.ToReST()
methodMap = {
'class' : self.DumpClass,
'method' : self.DumpMethod,
'function' : self.DumpFunction,
'enum' : self.DumpEnum,
}
function = methodMap[self.kind]
return function(write)
# -----------------------------------------------------------------------
def DumpClass(self, write):
"""
Dumps a ReSTified class description and returns its correct ReST representation.
:param bool `write`: ``True`` to write the resulting docstrings to a file, ``False``
otherwise.
:rtype: `string`
"""
stream = StringIO()
# class declaration
klass = self.xml_item
name = self.class_name
dummy, fullname = wx2Sphinx(name)
if '.' in fullname:
parts = fullname.split('.')
module = '.'.join(parts[0:-1])
stream.write('\n\n.. currentmodule:: %s\n\n'%module)
stream.write(templates.TEMPLATE_DESCRIPTION % (fullname, name))
self.Reformat(stream)
if not klass.nodeBases:
klass.nodeBases = ({name: (name, name, [])}, [name])
inheritance_diagram = InheritanceDiagram(klass.nodeBases)
png, map = inheritance_diagram.makeInheritanceDiagram()
image_desc = templates.TEMPLATE_INHERITANCE % ('class', name, png, name, map)
stream.write(image_desc)
if self.appearance:
appearance_desc = templates.TEMPLATE_APPEARANCE % tuple(self.appearance)
stream.write(appearance_desc)
if klass.subClasses:
subs = [':ref:`%s`' % wx2Sphinx(cls)[1] for cls in klass.subClasses]
subs = ', '.join(subs)
subs_desc = templates.TEMPLATE_SUBCLASSES % subs
stream.write(subs_desc)
possible_py = self.HuntContributedSnippets()
if possible_py:
possible_py.sort()
snippets = formatContributedSnippets(self.kind, possible_py)
stream.write(snippets)
if klass.method_list:
summary = makeSummary(name, klass.method_list, templates.TEMPLATE_METHOD_SUMMARY, 'meth')
stream.write(summary)
if klass.property_list:
summary = makeSummary(name, klass.property_list, templates.TEMPLATE_PROPERTY_SUMMARY, 'attr')
stream.write(summary)
stream.write(templates.TEMPLATE_API)
stream.write("\n.. class:: %s"%name)
bases = klass.bases or ['object']
if bases:
stream.write('(')
bases = [removeWxPrefix(b) for b in bases]
stream.write(', '.join(bases))
stream.write(')')
stream.write('\n\n')
py_docs = klass.pyDocstring
if isinstance(self.xml_item, (extractors.PyClassDef, extractors.TypedefDef)):
newlines = self.xml_item.briefDoc.splitlines()
else:
newlines = []
found = False
for line in py_docs.splitlines():
if line.startswith(name):
if not found:
newlines.append("**Possible constructors**::\n")
found = True
else:
found = False
newlines.append(convertToPython(line))
if found:
line = line.replace('wx.EmptyString', '""')
line = line.replace('wx.', '')
newlines = self.CodeIndent(line, newlines)
newdocs = ''
for line in newlines:
newdocs += ' '*3 + line + "\n"
stream.write(newdocs + "\n\n")
if write:
writeSphinxOutput(stream, self.output_file)
else:
return stream.getvalue()
# -----------------------------------------------------------------------
def BuildSignature(self):
""" Builds a function/method signature. """
if self.kind not in ['method', 'function']:
return
if self.kind == 'method':
method = self.xml_item
name = method.name or method.pyName
name = removeWxPrefix(name)
if method.overloads and not self.is_overload:
if not method.isStatic:
arguments = '(self, *args, **kw)'
else:
arguments = '(*args, **kw)'
else:
arguments = method.pyArgsString
if not arguments:
arguments = '()'
if not method.isStatic:
if arguments[:2] == '()':
arguments = '(self)' + arguments[2:]
else:
arguments = '(self, ' + arguments[1:]
if '->' in arguments:
arguments, after = arguments.split("->")
self.AddReturnType(after, name)
arguments = arguments.rstrip()
if arguments.endswith(','):
arguments = arguments[0:-1]
if not arguments.endswith(')'):
arguments += ')'
if self.is_overload:
arguments = '`%s`'%arguments.strip()
elif self.kind == 'function':
function = self.xml_item
name = function.name or function.pyName
name = removeWxPrefix(name)
if function.overloads and not self.is_overload:
arguments = '(*args, **kw)'
else:
if "->" in function.pyArgsString:
arguments, after = function.pyArgsString.split("->")
self.AddReturnType(after, name)
else:
arguments = function.pyArgsString
if self.is_overload:
arguments = '`%s`'%arguments.strip()
arguments = arguments.replace('wx.', '')
self.arguments = arguments
# -----------------------------------------------------------------------
def InsertParameterList(self):
"""
Inserts a :class:`ParameterList` item in the correct position into the
:class:`Root` hierarchy, and checks the signature validity against the
parameter list itself.
"""
if self.kind not in ['method', 'function']:
return
if self.parameter_list is not None:
self.parameter_list.CheckSignature()
return
if not self.xml_item.overloads or self.is_overload:
self.parameter_list = ParameterList('', None, self.xml_item, self.kind)
self.root.Insert(self.parameter_list, before=Section)
self.parameter_list.CheckSignature()
# -----------------------------------------------------------------------
def AddReturnType(self, after, name):
after = after.strip()
if not after:
return
if '(' in after:
rtype = ReturnType('`tuple`', None)
return_section = after.lstrip('(').rstrip(')')
return_section = return_section.split(',')
new_section = []
for ret in return_section:
stripped = ret.strip()
if stripped in NO_MODULE:
ret = NO_MODULE[stripped] + stripped
new_section.append(':ref:`%s`'%ret)
else:
if ret[0].isupper():
new_section.append(':ref:`%s`'%stripped)
else:
new_section.append('`%s`'%stripped)
element = et.Element('return', kind='return')
element.text = '( %s )'%(', '.join(new_section))
return_section = Section(element, None, self.kind, self.is_overload, self.share_docstrings)
self.root.AddSection(return_section)
else:
rtype = pythonizeType(after, is_param=False)
if not rtype:
return
if rtype[0].isupper() or '.' in rtype:
rtype = ':ref:`%s`'%rtype
else:
rtype = '`%s`'%rtype
rtype = ReturnType(rtype, None)
if self.parameter_list:
self.parameter_list.Add(rtype)
else:
self.root.Insert(rtype, before=Section)
# -----------------------------------------------------------------------
def DumpMethod(self, write):
"""
Dumps a ReSTified method description and returns its correct ReST representation.
:param bool `write`: ``True`` to write the resulting docstrings to a file, ``False``
otherwise.
:rtype: `string`
"""
stream = StringIO()
method = self.xml_item
name = method.name or method.pyName
name = removeWxPrefix(name)
if self.is_overload:
definition = '**%s** '%name
else:
if method.isStatic:
definition = ' .. staticmethod:: ' + name
else:
definition = ' .. method:: ' + name
# write the method declaration
stream.write('\n%s'%definition)
stream.write(self.arguments)
stream.write('\n\n')
self.Reformat(stream)
if hasattr(method, 'deprecated') and method.deprecated:
text = method.deprecated
if isinstance(text, string_base):
text = '%s %s\n%s%s\n\n'%(' .. deprecated::', VERSION, ' '*9, text.replace('\n', ' '))
stream.write(text)
possible_py = self.HuntContributedSnippets()
if possible_py:
possible_py.sort()
snippets = formatContributedSnippets(self.kind, possible_py)
stream.write(snippets)
stream.write("\n\n")
if not self.is_overload and write:
writeSphinxOutput(stream, self.output_file, append=True)
return stream.getvalue()
# -----------------------------------------------------------------------
def DumpFunction(self, write):
"""
Dumps a ReSTified function description and returns its correct ReST representation.
:param bool `write`: ``True`` to write the resulting docstrings to a file, ``False``
otherwise.
:rtype: `string`
"""
stream = StringIO()
function = self.xml_item
name = function.pyName or function.name
if self.is_overload:
definition = '**%s** '%name
else:
definition = '.. function:: ' + name
stream.write('\n%s'%definition)
stream.write(self.arguments.strip())
stream.write('\n\n')
self.Reformat(stream)
if hasattr(function, 'deprecated') and function.deprecated:
if isinstance(function.deprecated, int):
msg = ""
else:
msg = function.deprecated.replace('\n', ' ')
text = '%s %s\n%s%s\n\n'%(' .. deprecated::', VERSION, ' '*6, msg)
stream.write(text)
possible_py = self.HuntContributedSnippets()
if possible_py:
possible_py.sort()
snippets = formatContributedSnippets(self.kind, possible_py)
stream.write(snippets)
if not self.is_overload and write:
pickleItem(stream.getvalue(), self.current_module, name, 'function')
return stream.getvalue()
# -----------------------------------------------------------------------
def DumpEnum(self, write):
"""
Dumps a ReSTified enumeration description and returns its correct ReST representation.
:param bool `write`: ``True`` to write the resulting docstrings to a file, ``False``
otherwise.
:rtype: `string`
"""
enum_name, fullname = wx2Sphinx(self.xml_item.name)
if '@' in enum_name:
return
stream = StringIO()
self.output_file = self.current_module + "%s.enumeration.txt"%enum_name
if self.current_module.strip():
stream.write('\n\n.. currentmodule:: %s\n\n'%self.current_module[0:-1])
stream.write(templates.TEMPLATE_DESCRIPTION % (fullname, enum_name))
stream.write('\n\nThe `%s` enumeration provides the following values:\n\n'%enum_name)
stream.write('\n\n' + '='*80 + ' ' + '='*80 + '\n')
stream.write('%-80s **Value**\n'%'**Description**')
stream.write('='*80 + ' ' + '='*80 + '\n')
count = 0
for v in self.xml_item.items:
if v.ignored:
continue
docstrings = v.briefDoc
name = convertToPython(removeWxPrefix(v.name))
stream.write('%-80s'%name)
if not isinstance(docstrings, string_base):
rest_class = self.RecurseXML(docstrings, self.root)
docstrings = rest_class.Join()
stream.write(' %s\n'%docstrings)
count += 1
stream.write('='*80 + ' ' + '='*80 + '\n\n|\n\n')
text_file = os.path.join(SPHINXROOT, self.output_file)
#if os.path.isfile(text_file):
# message = '\nWARNING: Duplicated description for `%s` enumeration.\n\n' \
# 'The duplicated instance will still be written to its output ReST file but\n' \
# 'Sphinx/Docutils will issue a warning when building the HTML docs.\n\n'
#
# duplicated = self.output_file.replace('.enumeration.txt', '')
# print message % duplicated
if count > 0 and write:
writeSphinxOutput(stream, self.output_file)
return stream.getvalue()
# -----------------------------------------------------------------------
def EventsInStyle(self, line, class_name, added):
docstrings = ''
newline = line
if 'supports the following styles:' in line:
if class_name is not None:
# Crappy wxWidgets docs!!! They put the Window Styles inside the
# constructor!!!
docstrings += templates.TEMPLATE_WINDOW_STYLES % class_name
elif 'The following event handler macros' in line and not added:
index = line.index('The following event handler macros')
newline1 = line[0:index] + '\n\n'
macro_line = line[index:]
last = macro_line.index(':')
line = macro_line[last+1:].strip()
if line.count(':') > 2:
newline = 'Handlers bound for the following event types will receive one of the %s parameters.'%line
else:
newline = 'Handlers bound for the following event types will receive a %s parameter.'%line
docstrings += newline1 + templates.TEMPLATE_EVENTS % class_name
docstrings = docstrings.replace('Event macros for events emitted by this class: ', '')
newline = newline.replace('Event macros for events emitted by this class: ', '')
added = True
elif 'Event macros for events' in line:
if added:
newline = ''
else:
docstrings += templates.TEMPLATE_EVENTS % class_name
added = True
elif 'following extra styles:' in line:
docstrings += templates.TEMPLATE_WINDOW_EXTRASTYLES % class_name
return docstrings, newline, added
# -----------------------------------------------------------------------
def CodeIndent(self, code, newlines):
if len(code) < 72:
newlines.append(' %s'%code)
newlines.append(' ')
return newlines
start = code.index('(')
wrapped = textwrap.wrap(code, width=72)
newcode = ''
for indx, line in enumerate(wrapped):
if indx == 0:
newlines.append(' %s'%line)
else:
newlines.append(' '*(start+5) + line)
newlines.append(' ')
return newlines
# -----------------------------------------------------------------------
def Indent(self, class_name, item, spacer, docstrings):
added = False
for line in item.splitlines():
if line.strip():
newdocs, newline, added = self.EventsInStyle(line, class_name, added)
if newline.strip():
docstrings += newdocs
docstrings += spacer + newline + '\n'
else:
docstrings += line + '\n'
return docstrings
# -----------------------------------------------------------------------
def Reformat(self, stream):
spacer = ''
if not self.is_overload:
if self.kind == 'function':
spacer = 3*' '
elif self.kind == 'method':
spacer = 6*' '
if self.overloads and not self.share_docstrings:
docstrings = ''
elif self.is_overload and self.share_docstrings:
docstrings = self.Indent(None, self.docstrings, spacer, '')
else:
class_name = None
if self.kind == 'class':
class_name = self.class_name
if isinstance(self.xml_item, (extractors.PyFunctionDef, extractors.PyClassDef)):
docstrings = self.xml_item.briefDoc
if docstrings:
docstrings = self.Indent(class_name, docstrings, spacer, '')
else:
docstrings = ''
else:
docstrings = self.Indent(class_name, self.docstrings, spacer, '')
if self.kind == 'class':
desc = chopDescription(docstrings)
self.short_description = desc
class_name = self.class_name.lower()
pickleItem(desc, self.current_module, self.class_name, 'class')
if self.overloads:
docstrings += '\n\n%s|overload| **Overloaded Implementations**:\n\n'%spacer
docstrings += '%s**~~~**\n\n'%spacer
for index, over in enumerate(self.overloads):
for line in over.splitlines():
docstrings += spacer + line + '\n'
docstrings += '%s**~~~**\n\n'%spacer
if '**Perl Note:**' in docstrings:
index = docstrings.index('**Perl Note:**')
docstrings = docstrings[0:index]
stream.write(docstrings + "\n\n")
# ---------------------------------------------------------------------------
class SphinxGenerator(generators.DocsGeneratorBase):
def generate(self, module):
self.current_module = MODULENAME_REPLACE[module.module]
self.module_name = module.name
self.current_class = None
self.generateModule(module)
# -----------------------------------------------------------------------
def RemoveDuplicated(self, class_name, class_items):
duplicated_indexes = []
done = []
properties = (extractors.PropertyDef, extractors.PyPropertyDef)
methods = (extractors.MethodDef, extractors.CppMethodDef, extractors.CppMethodDef_sip,
extractors.PyMethodDef, extractors.PyFunctionDef)
message = '\nWARNING: Duplicated instance of %s `%s` encountered in class `%s`.\n' \
'The last occurrence of `%s` (an instance of `%s`) will be discarded.\n\n'
for index, item in enumerate(class_items):
if isinstance(item, methods):
name, dummy = self.getName(item)
kind = 'method'
elif isinstance(item, properties):
name = item.name
kind = 'property'
else:
continue
if name in done:
print((message % (kind, name, class_name, name, item.__class__.__name__)))
duplicated_indexes.append(index)
continue
done.append(name)
duplicated_indexes.reverse()
for index in duplicated_indexes:
class_items.pop(index)
return class_items
# -----------------------------------------------------------------------
def generateModule(self, module):
"""
Generate code for each of the top-level items in the module.
"""
assert isinstance(module, extractors.ModuleDef)
methodMap = {
extractors.ClassDef : self.generateClass,
extractors.DefineDef : self.generateDefine,
extractors.FunctionDef : self.generateFunction,
extractors.EnumDef : self.generateEnum,
extractors.GlobalVarDef : self.generateGlobalVar,
extractors.TypedefDef : self.generateTypedef,
extractors.WigCode : self.generateWigCode,
extractors.PyCodeDef : self.generatePyCode,
extractors.CppMethodDef : self.generateFunction,
extractors.CppMethodDef_sip : self.generateFunction,
extractors.PyFunctionDef : self.generatePyFunction,
extractors.PyClassDef : self.generatePyClass,
}
if module.isARealModule:
filename = os.path.join(SPHINXROOT, self.current_module+'1classindex.pkl')
with PickleFile(filename) as pf:
pf.items[DOCSTRING_KEY] = module.docstring
for item in module:
if item.ignored:
continue
function = methodMap[item.__class__]
function(item)
# -----------------------------------------------------------------------
def generatePyFunction(self, function):
function.overloads = []
function.pyArgsString = function.argsString
self.UnIndent(function)
# docstring
docstring = XMLDocString(function)
docstring.kind = 'function'
docstring.current_module = self.current_module
docstring.Dump()
# -----------------------------------------------------------------------
def generateFunction(self, function):
# docstring
docstring = XMLDocString(function)
docstring.kind = 'function'
docstring.current_module = self.current_module
docstring.Dump()
def UnIndent(self, item):
if not item.briefDoc:
return
newdocs = ''
for line in item.briefDoc.splitlines():
if line.strip():
stripped = len(line) - len(line.lstrip())
break
newdocs = ''
for line in item.briefDoc.splitlines():
if line.strip():
line = line[stripped:]
newdocs += line + '\n'
item.briefDoc = newdocs
# -----------------------------------------------------------------------
def generatePyClass(self, klass):
klass.module = self.current_module
self.current_class = klass
class_name = klass.name
self.current_class.method_list = []
self.current_class.property_list = []
class_items = [i for i in klass if not i.ignored]
class_items = sorted(class_items, key=operator.attrgetter('name'))
class_items = self.RemoveDuplicated(class_name, class_items)
init_position = -1
for index, item in enumerate(class_items):
if isinstance(item, extractors.PyFunctionDef):
method_name, simple_docs = self.getName(item)
if method_name == '__init__':
init_position = index
self.current_class.method_list.insert(0, ('%s.%s'%(class_name, method_name), simple_docs))
else:
self.current_class.method_list.append(('%s.%s'%(class_name, method_name), simple_docs))
elif isinstance(item, extractors.PyPropertyDef):
simple_docs = self.createPropertyLinks(class_name, item)
self.current_class.property_list.append(('%s.%s'%(class_name, item.name), simple_docs))
if init_position >= 0:
init_method = class_items.pop(init_position)
class_items.insert(0, init_method)
self.UnIndent(klass)
docstring = XMLDocString(klass)
docstring.kind = 'class'
filename = self.current_module + "%s.txt"%class_name
docstring.output_file = filename
docstring.current_module = self.current_module
docstring.Dump()
pickleClassInfo(self.current_module + class_name, self.current_class, docstring.short_description)
# these are the only kinds of items allowed to be items in a PyClass
dispatch = [(extractors.PyFunctionDef, self.generateMethod),
(extractors.PyPropertyDef, self.generatePyProperty),
(extractors.PyCodeDef, self.generatePyCode),
(extractors.PyClassDef, self.generatePyClass)]
for kind, function in dispatch:
for item in class_items:
if kind == item.__class__:
item.klass = klass
function(item)
# -----------------------------------------------------------------------
def generatePyProperty(self, prop):
name = removeWxPrefix(self.current_class.name) or self.current_class.pyName
getter_setter = self.createPropertyLinks(name, prop)
stream = StringIO()
stream.write('\n .. attribute:: %s\n\n' % prop.name)
stream.write(' %s\n\n'%getter_setter)
filename = self.current_module + "%s.txt"%name
writeSphinxOutput(stream, filename, append=True)
# -----------------------------------------------------------------------
def generateClass(self, klass):
assert isinstance(klass, extractors.ClassDef)
if klass.ignored:
return
# generate nested classes
for item in klass.innerclasses:
self.generateClass(item)
name = removeWxPrefix(klass.name) or klass.pyName
## # Hack for App/PyApp...
## if name == 'PyApp':
## klass.name = name = 'App'
klass.module = self.current_module
self.current_class = klass
self.current_class.method_list = []
self.current_class.property_list = []
# Inspected class Method to call Sort order
dispatch = {
extractors.MethodDef : (self.generateMethod, 1),
extractors.CppMethodDef : (self.generateMethod, 1),
extractors.CppMethodDef_sip : (self.generateMethod, 1),
extractors.PyMethodDef : (self.generatePyMethod, 1),
extractors.MemberVarDef : (self.generateMemberVar, 2),
extractors.PropertyDef : (self.generateProperty, 2),
extractors.PyPropertyDef : (self.generateProperty, 2),
extractors.EnumDef : (self.generateEnum, 0),
extractors.PyCodeDef : (self.generatePyCode, 3),
extractors.WigCode : (self.generateWigCode, 4),
extractors.TypedefDef : (lambda a: None, 5),
}
# Build a list to check if there are any properties
properties = (extractors.PropertyDef, extractors.PyPropertyDef)
methods = (extractors.MethodDef, extractors.CppMethodDef, extractors.CppMethodDef_sip, extractors.PyMethodDef)
# Split the items documenting the __init__ methods first
ctors = [i for i in klass if
isinstance(i, extractors.MethodDef) and
i.protection == 'public' and (i.isCtor or i.isDtor)]
class_items = [i for i in klass if i not in ctors and not i.ignored]
for item in class_items:
item.sort_order = dispatch[item.__class__][1]
class_items = sorted(class_items, key=operator.attrgetter('sort_order', 'name'))
class_items = self.RemoveDuplicated(name, class_items)
for item in class_items:
if isinstance(item, methods) and not self.IsFullyDeprecated(item):
method_name, simple_docs = self.getName(item)
self.current_class.method_list.append(('%s.%s'%(name, method_name), simple_docs))
elif isinstance(item, properties):
simple_docs = self.createPropertyLinks(name, item)
self.current_class.property_list.append(('%s.%s'%(name, item.name), simple_docs))
for item in ctors:
if item.isCtor:
method_name, simple_docs = self.getName(item)
self.current_class.method_list.insert(0, ('%s.__init__'%name, simple_docs))
docstring = XMLDocString(klass)
filename = self.current_module + "%s.txt"%name
docstring.output_file = filename
docstring.current_module = self.current_module
docstring.Dump()
pickleClassInfo(self.current_module + name, self.current_class, docstring.short_description)
for item in ctors:
if item.isCtor:
self.generateMethod(item, name='__init__', docstring=klass.pyDocstring)
for item in class_items:
f = dispatch[item.__class__][0]
f(item)
# -----------------------------------------------------------------------
def generateMethod(self, method, name=None, docstring=None):
if method.ignored:
return
name = name or self.getName(method)[0]
## if name.startswith("__") and "__init__" not in name:
## return
if isinstance(method, extractors.PyFunctionDef):
self.UnIndent(method)
class_name = removeWxPrefix(self.current_class.name) or self.current_class.pyName
# docstring
method.name = name
method.pyArgsString = method.pyArgsString.replace('(self)', ' ').replace('(self, ', ' ')
docstring = XMLDocString(method)
docstring.kind = 'method'
name = removeWxPrefix(self.current_class.name) or self.current_class.pyName
filename = self.current_module + "%s.txt"%class_name
docstring.output_file = filename
docstring.class_name = class_name
docstring.current_module = self.current_module
docstring.Dump()
# -----------------------------------------------------------------------
def IsFullyDeprecated(self, pyMethod):
if not isinstance(pyMethod, extractors.PyMethodDef):
return False
if pyMethod.deprecated:
brief, detailed = pyMethod.briefDoc, pyMethod.detailedDoc
if not brief and not detailed:
# Skip simple wrappers unless they have a brief or a detailed doc
return True
return False
def generatePyMethod(self, pm):
assert isinstance(pm, extractors.PyMethodDef)
if pm.ignored:
return
if self.IsFullyDeprecated(pm):
return
stream = StringIO()
stream.write('\n .. method:: %s%s\n\n' % (pm.name, pm.argsString))
docstrings = return_type = ''
for line in pm.pyDocstring.splitlines():
if '->' in line:
arguments, after = line.strip().split("->")
return_type = self.ReturnSection(after)
else:
docstrings += line + '\n'
docstrings = convertToPython(docstrings)
newdocs = ''
spacer = ' '*6
for line in docstrings.splitlines():
if not line.startswith(spacer):
newdocs += spacer + line + "\n"
else:
newdocs += line + "\n"
stream.write(newdocs + '\n\n')
if hasattr(pm, 'deprecated') and pm.deprecated:
text = '%s %s\n%s%s\n\n'%(' .. deprecated::', VERSION, ' '*9, pm.deprecated.replace('\n', ' '))
stream.write(text)
name = removeWxPrefix(self.current_class.name) or self.current_class.pyName
filename = self.current_module + "%s.txt"%name
writeSphinxOutput(stream, filename, append=True)
# -----------------------------------------------------------------------
def generateMemberVar(self, memberVar):
assert isinstance(memberVar, extractors.MemberVarDef)
if memberVar.ignored:
return
# -----------------------------------------------------------------------
def generateProperty(self, prop):
if prop.ignored:
return
name = removeWxPrefix(self.current_class.name) or self.current_class.pyName
getter_setter = self.createPropertyLinks(name, prop)
stream = StringIO()
stream.write('\n .. attribute:: %s\n\n' % prop.name)
stream.write(' %s\n\n'%getter_setter)
filename = self.current_module + "%s.txt"%name
writeSphinxOutput(stream, filename, append=True)
def createPropertyLinks(self, name, prop):
if prop.getter and prop.setter:
return 'See :meth:`~%s.%s` and :meth:`~%s.%s`'%(name, prop.getter, name, prop.setter)
else:
method = (prop.getter and [prop.getter] or [prop.setter])[0]
return 'See :meth:`~%s.%s`'%(name, method)
# -----------------------------------------------------------------------
def generateEnum(self, enum):
assert isinstance(enum, extractors.EnumDef)
if enum.ignored:
return
docstring = XMLDocString(enum)
docstring.current_module = self.current_module
docstring.Dump()
# -----------------------------------------------------------------------
def generateGlobalVar(self, globalVar):
assert isinstance(globalVar, extractors.GlobalVarDef)
if globalVar.ignored:
return
name = globalVar.pyName or globalVar.name
if guessTypeInt(globalVar):
valTyp = '0'
elif guessTypeFloat(globalVar):
valTyp = '0.0'
elif guessTypeStr(globalVar):
valTyp = '""'
else:
valTyp = removeWxPrefix(globalVar.type) + '()'
# -----------------------------------------------------------------------
def generateDefine(self, define):
assert isinstance(define, extractors.DefineDef)
# write nothing for this one
# -----------------------------------------------------------------------
def generateTypedef(self, typedef):
assert isinstance(typedef, extractors.TypedefDef)
if typedef.ignored or not typedef.docAsClass:
return
name = removeWxPrefix(typedef.name) or typedef.pyName
typedef.module = self.current_module
all_classes = {}
nodename = fullname = name
specials = [nodename]
baselist = [base for base in typedef.bases if base != 'object']
all_classes[nodename] = (nodename, fullname, baselist)
for base in baselist:
all_classes[base] = (base, base, [])
self.UnIndent(typedef)
typedef.nodeBases = all_classes, specials
typedef.subClasses = []
typedef.method_list = typedef.property_list = []
typedef.pyDocstring = typedef.briefDoc
self.current_class = typedef
docstring = XMLDocString(typedef)
docstring.kind = 'class'
filename = self.current_module + "%s.txt"%name
docstring.output_file = filename
docstring.current_module = self.current_module
docstring.Dump()
pickleClassInfo(self.current_module + name, self.current_class, docstring.short_description)
# -----------------------------------------------------------------------
def generateWigCode(self, wig):
assert isinstance(wig, extractors.WigCode)
# write nothing for this one
# -----------------------------------------------------------------------
def generatePyCode(self, pc):
assert isinstance(pc, extractors.PyCodeDef)
# -----------------------------------------------------------------------
def getName(self, method):
if hasattr(method, 'isCtor') and method.isCtor:
method_name = '__init__'
else:
method_name = method.pyName or method.name
if method_name in MAGIC_METHODS:
method_name = MAGIC_METHODS[method_name]
simple_docs = ''
if isinstance(method, extractors.PyMethodDef):
simple_docs = convertToPython(method.pyDocstring)
else:
brief = method.briefDoc
if not isinstance(brief, string_base):
docstring = XMLDocString(method)
docstring.kind = 'method'
docstring.current_module = self.current_module
simple_docs = docstring.GetBrief()
elif brief is not None:
simple_docs = convertToPython(brief)
simple_docs = chopDescription(simple_docs)
return method_name, simple_docs
# ---------------------------------------------------------------------------
def ReturnSection(self, after):
if '(' in after:
rtype1 = ReturnType('`tuple`', None)
return_section = after.strip().lstrip('(').rstrip(')')
return_section = return_section.split(',')
new_section = []
for ret in return_section:
stripped = ret.strip()
if stripped in NO_MODULE:
ret = NO_MODULE[stripped] + stripped
new_section.append(':ref:`%s`'%ret)
else:
if ret[0].isupper():
new_section.append(':ref:`%s`'%stripped)
else:
new_section.append('`%s`'%stripped)
element = et.Element('return', kind='return')
element.text = '( %s )'%(', '.join(new_section))
rtype2 = Section(element, None, 'method')
rtype = rtype1.Join() + rtype2.Join()
else:
rtype = pythonizeType(after, is_param=False)
if not rtype:
return ''
if rtype[0].isupper() or '.' in rtype:
rtype = ':ref:`%s`'%rtype
else:
rtype = '`%s`'%rtype
rtype = ReturnType(rtype, None)
rtype = rtype.Join()
out = ''
for r in rtype.splitlines():
out += 6*' ' + r + '\n'
return out
# ---------------------------------------------------------------------------
# helpers
def guessTypeInt(v):
if isinstance(v, extractors.EnumValueDef):
return True
if isinstance(v, extractors.DefineDef) and '"' not in v.value:
return True
type = v.type.replace('const', '')
type = type.replace(' ', '')
if type in ['int', 'long', 'byte', 'size_t']:
return True
if 'unsigned' in type:
return True
return False
def guessTypeFloat(v):
type = v.type.replace('const', '')
type = type.replace(' ', '')
if type in ['float', 'double', 'wxDouble']:
return True
return False
def guessTypeStr(v):
if hasattr(v, 'value') and '"' in v.value:
return True
if 'wxString' in v.type:
return True
return False
# ---------------------------------------------------------------------------