- First stub at integrating `wx.lib` (116 warnings);
- Added `internationalization.rst` file from Werner (with a cool sample app as well);
- Added new samples for AffineMatrix2D.

git-svn-id: https://svn.wxwidgets.org/svn/wx/wxPython/Phoenix/trunk@71099 c3d73ce0-8a6f-49c7-b76d-6d57e0e08775
This commit is contained in:
Andrea Gavana
2012-04-05 18:28:47 +00:00
parent ba91908eec
commit 77338050a0
27 changed files with 2477 additions and 222 deletions

View File

@@ -144,8 +144,68 @@ HTML_REPLACE = ['module', 'function', 'method', 'class', 'classmethod', 'staticm
# The SVN revision of wxWidgets/Phoenix used to build the Sphinx docs.
# There must be a more intelligent way to get this information automatically.
SVN_REVISION = '71086'
SVN_REVISION = '71066'
# Today's date representation for the Sphinx HTML docs
TODAY = datetime.date.today().strftime('%d %B %Y')
# wx.lib and other pure-Python stuff
class Enumeration(object):
def __init__(self, name, enumList):
self.__doc__ = name
lookup = { }
reverseLookup = { }
i = 0
uniqueNames = [ ]
uniqueValues = [ ]
for item in enumList:
x = item.upper()
uniqueNames.append(x)
uniqueValues.append(i)
lookup[x] = i
reverseLookup[i] = x
i = i + 1
self.lookup = lookup
self.reverseLookup = reverseLookup
def __getattr__(self, attr):
if not self.lookup.has_key(attr):
raise AttributeError
return self.lookup[attr]
def whatis(self, value):
return self.reverseLookup[value]
CONSTANT_RE = re.compile('^([\w\s,]+)=', re.M)
EXCLUDED_ATTRS = ['__builtins__', '__doc__', '__name__', '__file__', '__path__',
'__module__', '__all__']
TYPE_DESCRIPTION = ['library',
'package',
'py_module', 'pyd_module', 'pyc_module', 'pyw_module',
'klass',
'function',
'method', 'static_method', 'class_method', 'instance_method',
'method_descriptor', 'builtin_method', 'builtin_function',
'property',
'booltype', 'classtype', 'complextype', 'dictproxytype', 'dicttype', 'filetype',
'floattype', 'instancetype', 'inttype', 'lambdatype', 'listtype', 'longtype',
'nonetype', 'objecttype', 'slicetype', 'strtype', 'tracebacktype', 'tupletype',
'typetype', 'unicodetype', 'unknowntype', 'xrangetype']
object_types = Enumeration('Object_Types', TYPE_DESCRIPTION)
MODULE_TO_ICON = [(".py", object_types.PY_MODULE, "Py_Module"), (".pyd", object_types.PYD_MODULE, "Pyd_Module"),
(".pyc", object_types.PYC_MODULE, "Pyc_Module"), (".pyw", object_types.PYW_MODULE, "Pyw_Module"),
(".so", object_types.PYD_MODULE, "Pyd_Module")]

View File

@@ -25,192 +25,246 @@ ENOENT = getattr(errno, 'ENOENT', 0)
EPIPE = getattr(errno, 'EPIPE', 0)
class InheritanceDiagram(object):
"""
Given a list of classes, determines the set of classes that they inherit
from all the way to the root "object", and then is able to generate a
graphviz dot graph from them.
"""
# ----------------------------------------------------------------------- #
def __init__(self, class_info):
#print class_info
#print
#print
self.class_info, self.specials = class_info
# These are the default attrs for graphviz
default_graph_attrs = {
'rankdir': 'LR',
'size': '"8.0, 12.0"',
}
default_node_attrs = {
'shape': 'box',
'fontsize': 10,
'height': 0.3,
'fontname': 'Vera Sans, DejaVu Sans, Liberation Sans, '
'Arial, Helvetica, sans',
'style': '"setlinewidth(0.5)"',
}
default_edge_attrs = {
'arrowsize': 0.5,
'style': '"setlinewidth(0.5)"',
}
# ----------------------------------------------------------------------- #
def FormatNodeAttrs(self, attrs):
return ','.join(['%s=%s' % x for x in attrs.items()])
# ----------------------------------------------------------------------- #
def FormatGraphAttrs(self, attrs):
return ''.join(['%s=%s;\n' % x for x in attrs.items()])
# ----------------------------------------------------------------------- #
def GenerateDot(self, name="dummy"):
"""
Generate a graphviz dot graph from the classes that were passed in to `__init__`.
:param string `name`: the name of the graph.
:rtype: `string`
:returns: A string representing the Graphviz dot diagram.
"""
inheritance_graph_attrs = dict(fontsize=9, ratio='auto', size='""', rankdir="LR")
inheritance_node_attrs = {"align": "center", 'shape': 'box',
'fontsize': 10, 'height': 0.3,
'fontname': 'Vera Sans, DejaVu Sans, Liberation Sans, '
'Arial, Helvetica, sans', 'style': '"setlinewidth(0.5)"',
'labelloc': 'c', 'fontcolor': 'grey45'}
inheritance_edge_attrs = {'arrowsize': 0.5, 'style': '"setlinewidth(0.5)"', "color": "black"}
g_attrs = self.default_graph_attrs.copy()
n_attrs = self.default_node_attrs.copy()
e_attrs = self.default_edge_attrs.copy()
g_attrs.update(inheritance_graph_attrs)
n_attrs.update(inheritance_node_attrs)
e_attrs.update(inheritance_edge_attrs)
res = []
res.append('digraph %s {\n' % name)
res.append(self.FormatGraphAttrs(g_attrs))
for name, fullname, bases in self.class_info.values():
# Write the node
this_node_attrs = n_attrs.copy()
if name in self.specials:
this_node_attrs['fontcolor'] = 'black'
this_node_attrs['color'] = 'blue'
this_node_attrs['style'] = 'bold'
newname, fullname = Wx2Sphinx(name)
this_node_attrs['URL'] = '"%s.html"'%fullname
res.append(' "%s" [%s];\n' %
(newname, self.FormatNodeAttrs(this_node_attrs)))
# Write the edges
for base_name in bases:
this_edge_attrs = e_attrs.copy()
if name in self.specials:
this_edge_attrs['color'] = 'red'
base_name, dummy = Wx2Sphinx(base_name)
res.append(' "%s" -> "%s" [%s];\n' %
(base_name, newname,
self.FormatNodeAttrs(this_edge_attrs)))
res.append('}\n')
return ''.join(res)
# ----------------------------------------------------------------------- #
def MakeInheritanceDiagram(self):
"""
Actually generates the inheritance diagram as a PNG file plus the corresponding
MAP file for mouse navigation over the inheritance boxes.
These two files are saved into the ``INHERITANCEROOT`` folder (see `sphinxtools/constants.py`
for more information).
:rtype: `tuple`
:returns: a tuple containing the PNG file name and a string representing the content
of the MAP file (with newlines stripped away).
.. note:: The MAP file is deleted as soon as its content has been read.
"""
code = self.GenerateDot()
# graphviz expects UTF-8 by default
if isinstance(code, unicode):
code = code.encode('utf-8')
static_root = INHERITANCEROOT
dot_args = ['dot']
dummy, filename = Wx2Sphinx(self.specials[0])
outfn = os.path.join(static_root, filename + '_inheritance.png')
if os.path.isfile(outfn):
os.remove(outfn)
if os.path.isfile(outfn + '.map'):
os.remove(outfn + '.map')
dot_args.extend(['-Tpng', '-o' + outfn])
dot_args.extend(['-Tcmapx', '-o%s.map' % outfn])
try:
p = Popen(dot_args, stdout=PIPE, stdin=PIPE, stderr=PIPE)
except OSError, err:
if err.errno != ENOENT: # No such file or directory
raise
print '\nERROR: Graphviz command `dot` cannot be run (needed for Graphviz output), check your ``PATH`` setting'
try:
# Graphviz may close standard input when an error occurs,
# resulting in a broken pipe on communicate()
stdout, stderr = p.communicate(code)
except OSError, err:
# in this case, read the standard output and standard error streams
# directly, to get the error message(s)
stdout, stderr = p.stdout.read(), p.stderr.read()
p.wait()
if p.returncode != 0:
print '\nERROR: Graphviz `dot` command exited with error:\n[stderr]\n%s\n[stdout]\n%s\n\n' % (stderr, stdout)
mapfile = outfn + '.map'
fid = open(mapfile, 'rt')
map = fid.read()
fid.close()
os.remove(mapfile)
return os.path.split(outfn)[1], map.replace('\n', ' ')
class InheritanceDiagram(object):
"""
Given a list of classes, determines the set of classes that they inherit
from all the way to the root "object", and then is able to generate a
graphviz dot graph from them.
"""
def __init__(self, classes, main_class=None):
if main_class is None:
self.class_info, self.specials = classes
self.class_info = self.class_info.values()
else:
self.class_info, self.specials = self._class_info(classes)
self.main_class = main_class
def _class_info(self, classes):
"""Return name and bases for all classes that are ancestors of
*classes*.
*parts* gives the number of dotted name parts that is removed from the
displayed node names.
"""
all_classes = {}
specials = []
def recurse(cls):
nodename, fullname = self.class_name(cls)
baselist = []
all_classes[cls] = (nodename, fullname, baselist)
for base in cls.__bases__:
baselist.append(self.class_name(base)[0])
if base not in all_classes:
recurse(base)
for cls in classes:
recurse(cls)
specials.append(self.class_name(cls)[1])
return all_classes.values(), specials
def class_name(self, cls):
"""Given a class object, return a fully-qualified name.
This works for things I've tested in matplotlib so far, but may not be
completely general.
"""
module = cls.__module__
if module == '__builtin__':
fullname = cls.__name__
else:
fullname = '%s.%s' % (module, cls.__name__)
name_parts = fullname.split('.')
if 'wx._' in fullname:
nodename = fullname = name_parts[-1]
else:
# Just the last 2 parts
nodename = '.'.join(name_parts[-2:])
return nodename, fullname
# These are the default attrs for graphviz
default_graph_attrs = {
'rankdir': 'LR',
'size': '"8.0, 12.0"',
}
default_node_attrs = {
'shape': 'box',
'fontsize': 10,
'height': 0.3,
'fontname': 'Vera Sans, DejaVu Sans, Liberation Sans, '
'Arial, Helvetica, sans',
'style': '"setlinewidth(0.5)"',
}
default_edge_attrs = {
'arrowsize': 0.5,
'style': '"setlinewidth(0.5)"',
}
def _format_node_attrs(self, attrs):
return ','.join(['%s=%s' % x for x in attrs.items()])
def _format_graph_attrs(self, attrs):
return ''.join(['%s=%s;\n' % x for x in attrs.items()])
def generate_dot(self, name="dummy", urls={}, graph_attrs={}, node_attrs={}, edge_attrs={}):
"""Generate a graphviz dot graph from the classes that were passed in
to __init__.
*name* is the name of the graph.
*urls* is a dictionary mapping class names to HTTP URLs.
*graph_attrs*, *node_attrs*, *edge_attrs* are dictionaries containing
key/value pairs to pass on as graphviz properties.
"""
inheritance_graph_attrs = dict(fontsize=9, ratio='auto', size='""', rankdir="LR")
inheritance_node_attrs = {"align": "center", 'shape': 'box',
'fontsize': 10, 'height': 0.3,
'fontname': 'Vera Sans, DejaVu Sans, Liberation Sans, '
'Arial, Helvetica, sans', 'style': '"setlinewidth(0.5)"',
'labelloc': 'c', 'fontcolor': 'grey45'}
inheritance_edge_attrs = {'arrowsize': 0.5, 'style': '"setlinewidth(0.5)"', "color": "black"}
g_attrs = self.default_graph_attrs.copy()
n_attrs = self.default_node_attrs.copy()
e_attrs = self.default_edge_attrs.copy()
g_attrs.update(inheritance_graph_attrs)
n_attrs.update(inheritance_node_attrs)
e_attrs.update(inheritance_edge_attrs)
res = []
res.append('digraph %s {\n' % name)
res.append(self._format_graph_attrs(g_attrs))
for name, fullname, bases in self.class_info:
# Write the node
this_node_attrs = n_attrs.copy()
if fullname in self.specials:
this_node_attrs['fontcolor'] = 'black'
this_node_attrs['color'] = 'blue'
this_node_attrs['style'] = 'bold'
if self.main_class is None:
newname, fullname = Wx2Sphinx(name)
else:
newname = name
this_node_attrs['URL'] = '"%s.html"'%fullname
res.append(' "%s" [%s];\n' %
(newname, self._format_node_attrs(this_node_attrs)))
# Write the edges
for base_name in bases:
this_edge_attrs = e_attrs.copy()
if fullname in self.specials:
this_edge_attrs['color'] = 'red'
if self.main_class is None:
base_name, dummy = Wx2Sphinx(base_name)
res.append(' "%s" -> "%s" [%s];\n' %
(base_name, newname,
self._format_node_attrs(this_edge_attrs)))
res.append('}\n')
return ''.join(res)
# ----------------------------------------------------------------------- #
def MakeInheritanceDiagram(self):
"""
Actually generates the inheritance diagram as a PNG file plus the corresponding
MAP file for mouse navigation over the inheritance boxes.
These two files are saved into the ``INHERITANCEROOT`` folder (see `sphinxtools/constants.py`
for more information).
:rtype: `tuple`
:returns: a tuple containing the PNG file name and a string representing the content
of the MAP file (with newlines stripped away).
.. note:: The MAP file is deleted as soon as its content has been read.
"""
static_root = INHERITANCEROOT
if self.main_class is not None:
filename = self.main_class.name
else:
dummy, filename = Wx2Sphinx(self.specials[0])
outfn = os.path.join(static_root, filename + '_inheritance.png')
mapfile = outfn + '.map'
if os.path.isfile(outfn) and os.path.isfile(mapfile):
fid = open(mapfile, 'rt')
map = fid.read()
fid.close()
return os.path.split(outfn)[1], map.replace('\n', ' ')
code = self.generate_dot()
# graphviz expects UTF-8 by default
if isinstance(code, unicode):
code = code.encode('utf-8')
dot_args = ['dot']
if os.path.isfile(outfn):
os.remove(outfn)
if os.path.isfile(mapfile):
os.remove(mapfile)
dot_args.extend(['-Tpng', '-o' + outfn])
dot_args.extend(['-Tcmapx', '-o' + mapfile])
try:
p = Popen(dot_args, stdout=PIPE, stdin=PIPE, stderr=PIPE)
except OSError, err:
if err.errno != ENOENT: # No such file or directory
raise
print '\nERROR: Graphviz command `dot` cannot be run (needed for Graphviz output), check your ``PATH`` setting'
try:
# Graphviz may close standard input when an error occurs,
# resulting in a broken pipe on communicate()
stdout, stderr = p.communicate(code)
except OSError, err:
# in this case, read the standard output and standard error streams
# directly, to get the error message(s)
stdout, stderr = p.stdout.read(), p.stderr.read()
p.wait()
if p.returncode != 0:
print '\nERROR: Graphviz `dot` command exited with error:\n[stderr]\n%s\n[stdout]\n%s\n\n' % (stderr, stdout)
fid = open(mapfile, 'rt')
map = fid.read()
fid.close()
return os.path.split(outfn)[1], map.replace('\n', ' ')

View File

@@ -0,0 +1,735 @@
import sys
import os
import operator
import errno
import re
from StringIO import StringIO
from subprocess import Popen, PIPE
from inspect import getmro, getclasstree, getdoc, getcomments
from utilities import MakeSummary, ChopDescription, WriteSphinxOutput, PickleClassInfo
from constants import object_types, MODULE_TO_ICON
import templates
ENOENT = getattr(errno, 'ENOENT', 0)
EPIPE = getattr(errno, 'EPIPE', 0)
reload(sys)
sys.setdefaultencoding("utf-8")
def make_class_tree(tree):
class_tree = []
if isinstance(tree, list):
for node in tree:
class_tree.append(make_class_tree(node))
else:
name = tree[0].__name__
class_tree.append(name)
return class_tree
def generic_summary(libraryItem, stream):
write_toc = True
add_tilde = [True, True]
if libraryItem.kind in [object_types.LIBRARY, object_types.PACKAGE]:
list1 = libraryItem.GetItemByKind(object_types.PACKAGE)
list2 = libraryItem.GetItemByKind(object_types.PY_MODULE, object_types.PYW_MODULE)
templ = [templates.TEMPLATE_PACKAGE_SUMMARY, templates.TEMPLATE_MODULE_SUMMARY]
refs = ['mod', 'mod']
elif libraryItem.kind in range(object_types.PY_MODULE, object_types.PYW_MODULE+1):
list1 = libraryItem.GetItemByKind(object_types.FUNCTION)
list2 = libraryItem.GetItemByKind(object_types.KLASS, recurse=True)
templ = [templates.TEMPLATE_STD_FUNCTION_SUMMARY, templates.TEMPLATE_STD_CLASS_SUMMARY]
refs = ['func', 'ref']
add_tilde = [True, False]
elif libraryItem.kind == object_types.KLASS:
write_toc = False
list1 = libraryItem.GetItemByKind(object_types.METHOD, object_types.INSTANCE_METHOD)
list2 = libraryItem.GetItemByKind(object_types.PROPERTY)
templ = [templates.TEMPLATE_METHOD_SUMMARY, templates.TEMPLATE_PROPERTY_SUMMARY]
refs = ['meth', 'attr']
add_tilde = [True, True]
else:
raise Exception('Invalid library item: %s'%libraryItem.GetShortName())
toctree = ''
for index, sub_list in enumerate([list1, list2]):
table = []
for item in sub_list:
if item.is_redundant:
continue
docs = ChopDescription(ReplaceWxDot(item.docs))
table.append((item.name, docs))
if item.kind != object_types.FUNCTION:
toctree += ' %s\n'%item.name
if table:
summary = MakeSummary(table, templ[index], refs[index], add_tilde[index])
stream.write(summary)
if toctree and write_toc:
stream.write(templates.TEMPLATE_TOCTREE%toctree)
stream.write('\n\n')
def MakeSphinxFile(name):
return os.path.join(os.getcwd(), 'docs', 'sphinx', '%s.txt'%name)
def ReplaceWxDot(text):
# Double ticks with 'wx.' in them
text = re.sub(r'``wx\.(.*?)``', r'``\1`` ', text)
# Signle ticks with 'wx.' in them... try and referencing them
text = re.sub(r'`wx\.(.*?)`', r'`\1` ', text)
return text
class ParentBase(object):
def __init__(self, name, kind):
self.name = name
self.kind = kind
self.docs = u''
self.comments = u''
self.is_redundant = False
self.children = []
def Add(self, klass):
if u'lambda' in klass.name:
return
for child in self.children:
if child.name == klass.name:
return
klass.parent = self
self.children.append(klass)
def Save(self):
self.children = sorted(self.children, key=lambda k: (getattr(k, "order"), getattr(k, "name").lower()))
if self.docs is None:
self.docs = u''
if self.comments is None or not self.comments.strip():
self.comments = u''
for child in self.children:
child.Save()
def GetImage(self):
return self.kind
def GetName(self):
return self.name
def GetShortName(self):
return self.name.split(".")[-1]
def GetObject(self):
return self.obj_type
def GetChildren(self):
return self.children
def GetChildrenCount(self, recursively=True):
"""
Gets the number of children of this item.
:param bool `recursively`: if ``True``, returns the total number of descendants,
otherwise only one level of children is counted.
"""
count = len(self.children)
if not recursively:
return count
total = count
for n in xrange(count):
total += self.children[n].GetChildrenCount()
return total
def GetKindCount(self, minObj, maxObj=None):
if maxObj is None:
maxObj = minObj
count = 0
for child in self.children:
if minObj <= child.kind <= maxObj:
count += 1
return count
def GetItemByKind(self, minObj, maxObj=None, recurse=False):
if maxObj is None:
maxObj = minObj
items = []
for child in self.children:
if minObj <= child.kind <= maxObj:
items.append(child)
if recurse:
items = items + child.GetItemByKind(minObj, maxObj, recurse)
return items
def ToRest(self):
pass
class Library(ParentBase):
def __init__(self, name):
ParentBase.__init__(self, name, object_types.LIBRARY)
self.parent = None
self.filename = u''
self.order = 0
self.obj_type = u"Library"
self.python_version = u''
self.sphinx_file = MakeSphinxFile(name)
self.base_name = name
def GetShortName(self):
return self.name
def Walk(self, obj):
if obj == self:
obj.ToRest()
# must have at least root folder
children = obj.GetChildren()
if not children:
return
# check each name
for child in children:
if child.is_redundant:
continue
child.ToRest()
# recursively scan other folders, appending results
self.Walk(child)
def GetPythonVersion(self):
return self.python_version
def ToRest(self):
print '\n\nReST-ifying %s...\n\n'%self.base_name
stream = StringIO()
header = templates.TEMPLATE_DESCRIPTION%(self.base_name, self.base_name + ' Library')
stream.write(header)
stream.write(ReplaceWxDot(self.docs) + '\n\n')
generic_summary(self, stream)
WriteSphinxOutput(stream, self.sphinx_file)
class Module(ParentBase):
def __init__(self, name, kind):
ParentBase.__init__(self, name, kind)
self.filename = u''
self.sphinx_file = MakeSphinxFile(name)
if kind == object_types.PACKAGE:
self.obj_type = u"Package"
self.order = kind
return
self.order = object_types.PY_MODULE
for dummy, icon, description in MODULE_TO_ICON:
if icon == kind:
self.obj_type = description
break
self.inheritance_diagram = None
def ToRest(self):
stream = StringIO()
label = 'Module'
if self.kind == object_types.PACKAGE:
label = 'Package'
stream.write('.. module:: %s\n\n'%self.name)
stream.write('.. currentmodule:: %s\n\n'%self.name)
header = templates.TEMPLATE_DESCRIPTION%(self.name, '%s %s'%(self.name, label))
stream.write(header)
stream.write(ReplaceWxDot(self.docs) + '\n\n')
spacer = ' '*self.name.count('.')
if self.kind != object_types.PACKAGE:
print '%s - %s (module)'%(spacer, self.name)
if self.inheritance_diagram:
png, map = self.inheritance_diagram.MakeInheritanceDiagram()
short_name = self.GetShortName()
image_desc = templates.TEMPLATE_INHERITANCE % (short_name, png, short_name, map)
stream.write(image_desc)
else:
print '%s - %s (package)'%(spacer, self.name)
generic_summary(self, stream)
if self.kind != object_types.PACKAGE:
functions = self.GetItemByKind(object_types.FUNCTION)
count = 0
for fun in functions:
if not fun.is_redundant:
count = 1
break
if count > 0:
stream.write('\n\nFunctions\n===========\n\n')
for fun in functions:
if fun.is_redundant:
continue
fun.Write(stream)
WriteSphinxOutput(stream, self.sphinx_file)
class Class(ParentBase):
def __init__(self, name, obj):
ParentBase.__init__(self, name, object_types.KLASS)
try:
subs = obj.__subclasses__()
except (AttributeError, TypeError):
subs = []
sups = list(obj.__bases__)
sortedSubClasses = []
sortedSupClasses = []
for item in sups:
item = repr(item)
sup = item.replace("<class ", "").replace(">", "").replace("<type ", "")
sup = sup.strip().replace('"', "").replace("'", "")
if " at " in sup:
sup = sup[0:sup.index(" at ")].strip()
if 'wx._' in sup:
name_parts = sup.split('.')
sup = name_parts[-1]
sortedSupClasses.append(sup)
sortedSupClasses.sort()
for s in subs:
s = repr(s)
if "'" in s:
start = s.index("'")
end = s.rindex("'")
cls = s[start+1:end]
else:
cls = s
if 'wx._' in cls:
name_parts = cls.split('.')
cls = name_parts[-1]
sortedSubClasses.append(cls)
sortedSubClasses.sort()
self.class_tree = make_class_tree(getclasstree(getmro(obj)))
self.subClasses = sortedSubClasses
self.superClasses = sortedSupClasses
self.signature = ""
self.inheritance_diagram = None
self.order = 3
self.obj_type = u"Class"
self.sphinx_file = MakeSphinxFile(name)
def ToRest(self):
if self.is_redundant:
return
stream = StringIO()
parts = self.name.split('.')
current_module = '.'.join(parts[0:-1])
stream.write('.. currentmodule:: %s\n\n'%current_module)
header = templates.TEMPLATE_DESCRIPTION%(self.name, self.GetShortName())
stream.write(header)
stream.write(ReplaceWxDot(self.docs) + '\n\n')
if self.inheritance_diagram:
png, map = self.inheritance_diagram.MakeInheritanceDiagram()
short_name = self.GetShortName()
image_desc = templates.TEMPLATE_INHERITANCE % (short_name, png, short_name, map)
stream.write(image_desc)
if self.subClasses:
subs = [':ref:`%s`'%cls for cls in self.superClasses]
subs = ', '.join(subs)
subs_desc = templates.TEMPLATE_SUBCLASSES % subs
stream.write(subs_desc)
if self.superClasses:
subs = [':ref:`%s`'%cls for cls in self.superClasses]
subs = ', '.join(subs)
subs_desc = templates.TEMPLATE_SUPERCLASSES % subs
stream.write(subs_desc)
generic_summary(self, stream)
stream.write(templates.TEMPLATE_API)
stream.write("\n.. class:: %s\n\n\n"%self.signature)
methods = self.GetItemByKind(object_types.METHOD, object_types.INSTANCE_METHOD)
properties = self.GetItemByKind(object_types.PROPERTY)
method_list = []
for meth in methods:
meth.Write(stream)
if not meth.is_redundant:
method_list.append((meth.GetShortName(), ''))
for prop in properties:
prop.Write(stream, short_name)
WriteSphinxOutput(stream, self.sphinx_file)
self.bases = self.superClasses
self.method_list = method_list
PickleClassInfo(self.name, self)
def Save(self):
ParentBase.Save(self)
pop = -1
for index, child in enumerate(self.children):
name = child.GetShortName()
if name == '__init__':
pop = index
break
if pop >= 0:
init = self.children.pop(pop)
self.children.insert(0, init)
self.signature = self.signature.replace('wx.', '')
self.signature = self.signature.rstrip(':').lstrip('class ')
if ' def __init__' in self.signature:
index = self.signature.index(' def __init__')
self.signature = self.signature[0:index]
self.signature.strip()
class ChildrenBase(object):
def __init__(self, name, kind):
self.name = name
self.kind = kind
self.order = 4
self.docs = u''
self.comments = u''
self.is_redundant = False
## self.id = NewId()
def GetImage(self):
return self.kind
def GetName(self):
return self.name
def GetShortName(self):
return self.name.split(".")[-1]
def GetChildren(self):
return []
def GetChildrenCount(self, recursively=True):
return 0
def GetObject(self):
return self.obj_type
def Save(self):
if self.docs is None:
self.docs = u''
if self.comments is None or not self.comments.strip():
self.comments = u''
def ToRest(self):
pass
class Method(ChildrenBase):
def __init__(self, name, kind):
ChildrenBase.__init__(self, name, kind)
self.order = 5
self.arguments = []
self.signature = u''
self.obj_type = u"Method/Function"
def Save(self):
ChildrenBase.Save(self)
newargs = []
if self.arguments and any(self.arguments[0]):
for name, repr_val, eval_val in self.arguments:
repr_val = (repr_val is not None and [repr_val] or [""])[0]
eval_val = (eval_val is not None and [eval_val] or [""])[0]
newargs.append((name, repr_val, eval_val))
self.arguments = newargs
self.signature = self.signature.replace('wx.', '')
self.signature = self.signature.rstrip(':').lstrip()
if self.signature.startswith('def '):
self.signature = self.signature[4:]
if not self.signature.strip():
self.is_redundant = True
def Write(self, stream):
if self.is_redundant:
return
if self.kind == object_types.FUNCTION:
stream.write('.. function:: %s\n\n'%self.signature)
indent = 3*' '
else:
stream.write(' .. method:: %s\n\n'%self.signature)
indent = 6*' '
if not self.docs.strip():
stream.write('\n')
return
text = ''
for line in self.docs.splitlines():
text += indent + ReplaceWxDot(line) + '\n'
text += '\n\n'
stream.write(text)
class Property(ChildrenBase):
def __init__(self, name, item):
ChildrenBase.__init__(self, name, object_types.PROPERTY)
self.getter = self.setter = self.deleter = ""
try:
if item.fget:
self.getter = item.fget.__name__
if item.fset:
self.setter = item.fset.__name__
if item.fdel:
self.deleter = item.fdel.__name__
except AttributeError:
# Thank you for screwing it up, Cython...
if item.fget:
self.getter = item.fget.__class__.__name__
if item.fset:
self.setter = item.fset.__class__.__name__
if item.fdel:
self.deleter = item.fdel.__class__.__name__
self.docs = getdoc(item)
self.comments = getcomments(item)
self.obj_type = u"Property"
self.order = 6
def Write(self, stream, class_name):
if self.is_redundant:
return
docs = ''
for item in [self.setter, self.getter, self.deleter]:
if item and 'lambda' not in item and not item.startswith('_'):
if docs:
docs += ', :meth:`~%s.%s` '%(class_name, item)
else:
docs += ':meth:`~%s.%s` '%(class_name, item)
if docs:
docs = 'See %s'%docs
stream.write(' .. attribute:: %s\n\n'%self.GetShortName())
stream.write(' %s\n\n\n'%docs)
class Attribute(ChildrenBase):
def __init__(self, name, specs, value):
specs = unicode(specs)
start, end = specs.find("'"), specs.rfind("'")
specs = specs[start+1:end]
strValue = repr(value)
uspecs = specs.upper()
try:
kind = getattr(object_types, uspecs)
except AttributeError:
try:
uspecs = uspecs + u"TYPE"
kind = getattr(object_types, uspecs)
except AttributeError:
kind = object_types.UNKNOWNTYPE
try:
reprValue = repr(value.__class__)
except (NameError, AttributeError):
reprValue = ""
if u"class" in strValue or u"class" in reprValue:
kind = object_types.INSTANCETYPE
ChildrenBase.__init__(self, name, kind)
self.value = strValue
self.specs = specs
try:
self.docs = getdoc(value)
except (NameError, AttributeError):
self.docs = u''
self.obj_type = u"Attribute"
self.order = 7
def ToRest(self):
pass

632
sphinxtools/modulehunter.py Normal file
View File

@@ -0,0 +1,632 @@
# -*- coding: utf-8 -*-
# Describe classes, methods and functions in a module.
# Works with user-defined modules, all Python library
# modules, including built-in modules.
import os
import sys
import glob
import types
import cPickle
import imp
import traceback
import pkgutil
import __builtin__
from inspect import getargspec, ismodule, getdoc, getmodule, getcomments, isfunction
from inspect import ismethoddescriptor, getsource, ismemberdescriptor, isgetsetdescriptor
from inspect import isbuiltin, isclass, getfile, ismethod
from librarydescription import Library, Module, Class
from librarydescription import Method, Property, Attribute
import inheritance
from constants import object_types, EXCLUDED_ATTRS, MODULE_TO_ICON
from constants import CONSTANT_RE
reload(sys)
sys.setdefaultencoding("utf-8")
try:
import wx
except ImportError:
wxPath = None
for path in sys.path:
if 'wx-' in path:
wxPath = path
break
if wxPath is None:
raise Exception('Unable to find the wx package')
sys.path.insert(0, wxPath)
import wx
if hasattr(os.path, "relpath"):
relpath = os.path.relpath # since Python 2.6
else:
def relpath(path, start=os.path.curdir):
"""Return a relative version of a path"""
if not path:
raise ValueError("no path specified")
start_list = os.path.abspath(start).split(os.path.sep)
path_list = os.path.abspath(path).split(os.path.sep)
# Work out how much of the filepath is shared by start and path.
i = len(os.path.commonprefix([start_list, path_list]))
rel_list = [os.path.pardir] * (len(start_list)-i) + path_list[i:]
if not rel_list:
return os.path.curdir
return os.path.join(*rel_list)
def format_traceback():
t, v = sys.exc_info()[:2]
message = "".join(traceback.format_exception_only(t, v)).replace("\n", " ")
return message.strip()
def format_method(method):
method = method.strip()
if "def " not in method:
return None, None
indx1, indx2, indx3 = method.index("def "), method.index("("), method.rindex(")")
name = method[indx1+4:indx2]
signature = method[indx2+1:indx3]
if "\n" in signature:
sig = signature.split("\n")
params = ""
for s in sig:
params += s.strip() + " "
params = params.rstrip()
else:
params = signature
return name, params
def analyze_params(obj, signature):
params = signature.split(",")
if "self" in params:
params.remove("self")
signature = ",".join(params)
param_tuple = []
if not params:
return signature, param_tuple
try:
arginfo = getargspec(obj)
except TypeError:
arginfo = None
pevals = {}
if arginfo:
args = arginfo[0]
argsvar = arginfo[1]
if arginfo[3]:
dl = len(arginfo[3])
al = len(args)
defargs = args[al-dl:al]
info = arginfo[3]
for d, i in zip(defargs, info):
pevals[d] = i
for par in params:
p = par.strip()
pvalue = peval = None
if "=" in p:
all_values = p.split("=")
pname, pvalue = all_values[0].strip(), "=".join(all_values[1:]).strip()
pvalue = pvalue.strip()
if pname in pevals:
try:
peval = unicode(pevals[pname])
except UnicodeDecodeError:
peval = repr(pevals[pname])
except TypeError:
peval = u''
else:
pname = p
param_tuple.append((pname, pvalue, peval))
return signature, param_tuple
def get_constructor(source):
description = ""
hasComma = False
for line in source.split("\n"):
if '#' in line:
line = line[0:line.index('#')].strip()
if ":" in line:
hasComma = True
commaPos = line.index(":")
if ('"""' in line or "'''" in line) and hasComma:
break
if hasComma and ' def ' in line:
defPos = line.index(' def ')
if defPos > commaPos:
break
description += " " + line.strip()
if "):" in line or ") :" in line or ") :" in line:
break
return description
def inspect_source(method_class, obj, source):
description = get_constructor(source)
name, params = format_method(description)
if name is None:
return
signature, param_tuple = analyze_params(obj, params)
method_class.arguments = param_tuple
method_class.signature = description.strip()
if "classmethod " in description or is_classmethod(obj):
method_class.kind = object_types.CLASS_METHOD
elif "staticmethod " in description:
method_class.kind = object_types.STATIC_METHOD
def is_classmethod(instancemethod):
" Determine if an instancemethod is a classmethod. "
if hasattr(instancemethod, 'im_self'):
return instancemethod.im_self is not None
return False
def describe_func(obj, parent_class, module_name):
"""
Describe the function object passed as argument.
If this is a method object, the second argument will
be passed as True.
"""
try:
name = obj.__name__
except AttributeError:
# Funny comtypes...
return
if name.startswith("_") and "__init__" not in name:
return
name = parent_class.name + "." + name
docs = getdoc(obj)
comments = getcomments(obj)
if isfunction(obj):
method = object_types.FUNCTION
elif ismethod(obj):
method = object_types.METHOD
elif ismethoddescriptor(obj):
method = object_types.METHOD_DESCRIPTOR
if isinstance(obj, types.MethodType):
method = object_types.INSTANCE_METHOD
try:
source_code = getsource(obj)
except (IOError, TypeError):
source_code = ""
klass = Method(name, method)
klass.docs = docs
klass_module = getmodule(obj)
if klass_module and klass_module.__name__ != module_name:
klass.is_redundant = True
if source_code:
inspect_source(klass, obj, source_code)
klass.number_lines = "%d"%len(source_code.split("\n"))
if isinstance(obj, staticmethod):
klass.method = method = object_types.STATIC_METHOD
try:
if method in [object_types.METHOD, object_types.METHOD_DESCRIPTOR, object_types.INSTANCE_METHOD]:
code = obj.im_func.func_code
elif method == object_types.STATIC_METHOD:
code = obj.im_func.func_code
else:
code = obj.func_code
except AttributeError:
code = None
if code is not None:
klass.firstlineno = "%d"%code.co_firstlineno
parent_class.Add(klass)
def describe_class(obj, module_class, module_name, constants):
"""
Describe the class object passed as argument,
including its methods.
"""
class_name = obj.__name__
if class_name == "object":
return
class_name = module_class.name + "." + class_name
docs = getdoc(obj)
comments = getcomments(obj)
obj_dict = obj.__dict__
klass = Class(class_name, obj)
count = 0
for name in obj_dict:
if name.startswith("_") and "__init__" not in name:
continue
if name in EXCLUDED_ATTRS:
continue
try:
item = getattr(obj, name)
except AttributeError:
# Thanks to ReportLab for this funny exception...
continue
if ismodule(item):
continue
if ismemberdescriptor(item) or isgetsetdescriptor(item):
continue
if isbuiltin(item):
count += 1
elif ismethod(item) or isfunction(item) or ismethoddescriptor(item) or \
isinstance(item, types.MethodType):
count += 1
describe_func(item, klass, module_name)
elif isclass(item):
count += 1
describe_class(item, klass, module_name, constants)
else:
name = class_name + "." + name
if isinstance(item, property):
item_class = Property(name, item)
klass.Add(item_class)
item_module = getmodule(obj)
if item_module and item_module.__name__ != module_name:
item_class.is_redundant = True
else:
item_class = Attribute(name, type(item), item)
klass.Add(item_class)
if constants:
item_class.is_redundant = name not in constants
count += 1
klass.docs = docs
klass.comments = comments
klass_module = getmodule(obj)
if klass_module and klass_module.__name__ != module_name:
klass.is_redundant = True
else:
klass.inheritance_diagram = inheritance.InheritanceDiagram([obj], klass)
module_class.Add(klass)
try:
source_code = getsource(obj)
except (IOError, TypeError):
source_code = ""
if source_code:
description = get_constructor(source_code)
klass.signature = description.strip()
klass.number_lines = '%d'%len(source_code.split("\n"))
def describe_module(module, kind, constants=[]):
"""
Describe the module object passed as argument
including its classes and functions.
"""
module_name = module.__name__
if kind == object_types.LIBRARY:
klass = Library(module_name)
else:
klass = Module(module_name, kind)
klass.docs = getdoc(module)
klass.comments = getcomments(module)
klass.filename = module.__file__
inheritance_diagram = []
count = 0
for name in dir(module):
if name in EXCLUDED_ATTRS:
continue
obj = getattr(module, name)
if ismodule(obj):
continue
if ismemberdescriptor(obj) or isgetsetdescriptor(obj):
continue
if isclass(obj):
count += 1
describe_class(obj, klass, module_name, constants)
if obj.__module__ == module.__name__:
inheritance_diagram.append(obj)
elif isbuiltin(obj):
count += 1
elif ismethod(obj) or isfunction(obj) or ismethoddescriptor(obj) or \
isinstance(obj, types.MethodType):
count +=1
describe_func(obj, klass, module_name)
else:
attribute = Attribute(module_name + "." + name, type(obj), obj)
klass.Add(attribute)
if constants:
attribute.is_redundant = name not in constants
if kind not in [object_types.PACKAGE, object_types.LIBRARY]:
if inheritance_diagram and len(inheritance_diagram) < 20:
klass.inheritance_diagram = inheritance.InheritanceDiagram(inheritance_diagram, klass)
return klass, count
def Import(init_name, import_name, full_process=True):
directory, module_name = os.path.split(init_name)
dirname = os.path.dirname(directory)
if not full_process:
path = list(sys.path)
sys.path.insert(0, dirname)
f = None
try:
f, filename, description = imp.find_module(import_name, [dirname])
mainmod = imp.load_module(import_name, f, filename, description)
except (ImportError, NameError):
message = format_traceback()
message += ' Please check that the "Import command" text box is correctly filled'
print "Error: %s"%message
if not full_process:
sys.path = path[:]
return
if f:
f.close()
if not full_process:
sys.path = path[:]
try:
version = mainmod.__version__
except AttributeError:
try:
version = mainmod.__VERSION__
except AttributeError:
print "Warning: Library '%s' has no __version__ or __VERSION__ attribute. Please specify it in the 'Import command' textbox"%import_name
return
print version
return mainmod
def PrintProgress(name, looped_names):
looped_names.append(name)
if len(looped_names) == 5:
message = ", ".join(looped_names)
looped_names = []
print message
return looped_names
def FindModuleType(filename):
splitext = os.path.splitext(filename)[0]
for extension, icon, description in MODULE_TO_ICON:
if os.path.isfile(splitext + extension):
return icon
def SubImport(import_string, module, parent_class, ispkg):
try:
submod = __import__(import_string, fromlist=[module])
except:
# pubsub and Editra can be funny sometimes...
message = "Unable to import module/package '%s'.\n Exception was: %s"%(module, format_traceback())
print "\nWARNING: %s\n"%message
return None, 0
if not ismodule(submod):
return None, 0
filename = getfile(submod)
if ispkg:
kind = object_types.PACKAGE
else:
kind = FindModuleType(filename)
constants = []
if kind in [object_types.PY_MODULE, object_types.PACKAGE]:
contents = open(filename, "rt").read()
consts = CONSTANT_RE.findall(contents)
for c in consts:
if "," in c:
c = c.split(",")
constants.extend([v.strip() for v in c])
else:
constants.append(c.strip())
module_class, count = describe_module(submod, kind=kind, constants=constants)
parent_class.Add(module_class)
return module_class, count
def ModuleHunter(init_name, import_name, version):
pickle_file = os.path.join(os.getcwd(), 'docs', 'sphinx', 'wxlib.pkl')
if os.path.isfile(pickle_file):
fid = open(pickle_file, 'rb')
library_class = cPickle.load(fid)
fid.close()
library_class.Walk(library_class)
return
path = list(sys.path)
directory, module_name = os.path.split(init_name)
path = list(sys.path)
sys.path.insert(0, os.path.dirname(directory))
mainmod = Import(init_name, import_name)
if mainmod is None:
return
message = "Importing main library '%s'..."%import_name
print "Message: %s"%message
module_name = os.path.splitext(getfile(mainmod))[0] + ".py"
contents = open(module_name, "rt").read()
constants = CONSTANT_RE.findall(contents)
library_class, count = describe_module(mainmod, kind=object_types.LIBRARY, constants=constants)
library_class.name = "%s-%s"%(import_name, version)
message = "Main library '%s' imported..."%library_class.name
print "Message: %s"%message
message = "Importing sub-modules and sub-packages...\n"
print "Message: %s"%message
looped_names = []
ancestors_dict = {import_name: library_class}
for importer, module_name, ispkg in pkgutil.walk_packages(path=[directory],
prefix=import_name+".",
onerror=lambda x: None):
import_string = module_name
splitted = module_name.split(".")
fromlist = splitted[-1]
parent_name = ".".join(splitted[0:-1])
parent_class = ancestors_dict[parent_name]
module_class, count = SubImport(import_string, fromlist, parent_class, ispkg)
if module_class is None:
continue
looped_names = PrintProgress(module_name, looped_names)
if module_name not in ancestors_dict:
ancestors_dict[module_name] = module_class
major, minor, micro, release = sys.version_info[0:-1]
pythonVersion = u"%d.%d.%d-%s"%(major, minor, micro, release)
library_class.python_version = pythonVersion
library_class.Save()
sys.path[:] = path # restore
fid = open(pickle_file, 'wb')
cPickle.dump(library_class, fid)
fid.close()
library_class.Walk(library_class)
if __name__ == "__main__":
argv = sys.argv[1:]
if len(argv) == 2:
init_name, import_name = argv
Import(init_name, import_name, full_process=False)
else:
init_name, import_name, version, save_dir = argv
ModuleHunter(init_name, import_name, version, save_dir)

View File

@@ -70,7 +70,7 @@ def SphinxIndexes(sphinxDir):
if file.endswith('functions.pkl'):
ReformatFunctions(file)
elif 'classindex' in file:
MakeClassIndex(file)
MakeClassIndex(sphinxDir, file)
BuildEnumsAndMethods(sphinxDir)
@@ -122,9 +122,10 @@ def BuildEnumsAndMethods(sphinxDir):
if widget_name in SECTIONS_EXCLUDE:
start, end = SECTIONS_EXCLUDE[widget_name]
lindex = text.index(start)
rindex = text.index(end)
text = text[0:lindex] + text[rindex:]
if start in text and end in text:
lindex = text.index(start)
rindex = text.index(end)
text = text[0:lindex] + text[rindex:]
# Replace the "Perl Note" stuff, we don't need it
newtext = ''
@@ -361,7 +362,7 @@ def ReformatFunctions(file):
# ----------------------------------------------------------------------- #
def MakeClassIndex(file):
def MakeClassIndex(sphinxDir, file):
text_file = os.path.splitext(file)[0] + '.txt'
local_file = os.path.split(file)[1]
@@ -376,13 +377,24 @@ def MakeClassIndex(file):
if local_file.count('.') == 1:
# Core functions
label = 'Core'
module = ''
enumDots = 1
else:
label = local_file.split('.')[0:-2][0]
module = label
enumDots = 2
enum_files = glob.glob(sphinxDir + '/%s*.enumeration.txt'%module)
enum_base = [os.path.split(os.path.splitext(enum)[0])[1] for enum in enum_files]
names = classes.keys()
names.sort()
text = templates.TEMPLATE_CLASS_INDEX % (label, label)
text = ''
if module:
text += '\n\n.. module:: %s\n\n'%module
text += templates.TEMPLATE_CLASS_INDEX % (label, label)
text += 80*'=' + ' ' + 80*'=' + '\n'
text += '%-80s **Short Description**\n'%'**Class**'
@@ -391,8 +403,24 @@ def MakeClassIndex(file):
for cls in names:
text += '%-80s %s\n'%(':ref:`%s`'%Wx2Sphinx(cls)[1], classes[cls])
text += 80*'=' + ' ' + 80*'=' + '\n'
text += 80*'=' + ' ' + 80*'=' + '\n\n'
contents = []
for cls in names:
contents.append(Wx2Sphinx(cls)[1])
for enum in enum_base:
if enum.count('.') == enumDots:
contents.append(enum)
contents.sort()
toctree = ''
for item in contents:
toctree += ' %s\n'%item
text += templates.TEMPLATE_TOCTREE%toctree
writeIfChanged(text_file, text)

View File

@@ -16,9 +16,9 @@ TEMPLATE_DESCRIPTION = '''
.. _%s:
=====================================================================================
==========================================================================================================================================
|phoenix_title| **%s**
=====================================================================================
==========================================================================================================================================
'''
@@ -93,6 +93,18 @@ TEMPLATE_SUBCLASSES = '''
'''
# Template for the superclasses of a class, with a string containing a list
# of comma separated class names with their ReST role as :ref: prepended
TEMPLATE_SUPERCLASSES = '''
|super_classes| Known Superclasses
==================================
%s
|
'''
# Template for the method summary of a class, containing a table made of
# ``method_name`` ``method description``
@@ -247,3 +259,53 @@ TEMPLATE_HEADINGS = '''
'''
# Templates for the summary of modules/packages, containing a table made of
# ``module name`` ``short description``
TEMPLATE_MODULE_SUMMARY = '''
|module_summary| Modules Summary
================================
%s
|
'''
TEMPLATE_PACKAGE_SUMMARY = '''
|package_summary| Packages Summary
==================================
%s
|
'''
TEMPLATE_STD_FUNCTION_SUMMARY = '''
|function_summary| Functions Summary
====================================
%s
|
'''
TEMPLATE_STD_CLASS_SUMMARY = '''
|class_summary| Classes Summary
===============================
%s
|
'''
TEMPLATE_TOCTREE = '''
.. toctree::
:maxdepth: 1
:hidden:
%s
'''

View File

@@ -458,26 +458,38 @@ def FindControlImages(element):
# ----------------------------------------------------------------------- #
def MakeSummary(name, item_list, template, kind):
def MakeSummary(item_list, template, kind, add_tilde=True):
"""
This function generates a table containing a method/property name
and a shortened version of its docstrings.
:param string `name`: the method/property class name.
:param list `item_list`: a list of tuples like `(method/property name, short docstrings)`.
:param string `template`: the template to use (from `sphinxtools/templates.py`, can
be the ``TEMPLATE_METHOD_SUMMARY`` or the ``TEMPLATE_PROPERTY_SUMMARY``.
:param string `kind`: can be ":meth:" or ":attr:".
:param string `kind`: can be ":meth:" or ":attr:" or ":ref:".
:rtype: `string`
"""
maxlen = 0
for method, simple_docs in item_list:
substr = ':%s:`~%s`'%(kind, method)
maxlen = max(maxlen, len(substr))
maxlen = max(80, maxlen)
summary = '='*80 + ' ' + '='*80 + "\n"
summary = '='*maxlen + ' ' + '='*80 + "\n"
format = '%-' + str(maxlen) + 's %s'
for method, simple_docs in item_list:
summary += '%-80s %s'%(':%s:`~%s.%s`'%(kind, name, method), simple_docs) + '\n'
if add_tilde:
substr = ':%s:`~%s`'%(kind, method)
else:
substr = ':%s:`%s`'%(kind, method)
summary += format%(substr, simple_docs) + '\n'
summary += '='*80 + ' ' + '='*80 + "\n"
summary += '='*maxlen + ' ' + '='*80 + "\n"
return template % summary
@@ -585,7 +597,7 @@ def PickleClassInfo(class_name, element):
for base in element.bases:
bases.append(Wx2Sphinx(base)[1])
items[class_name] = (method_list, bases)
fid = open(pickle_file, 'wb')
cPickle.dump(items, fid)