- Add hyperlinks to external references (such as threading, code, exceptions and so on) to inheritance diagrams and sub/super classes;
- Add a stub for the overview "Writing Non-English Applications";
- Better handling of `wx.lib` documentation extraction.

git-svn-id: https://svn.wxwidgets.org/svn/wx/wxPython/Phoenix/trunk@71156 c3d73ce0-8a6f-49c7-b76d-6d57e0e08775
This commit is contained in:
Andrea Gavana
2012-04-08 23:51:22 +00:00
parent cb33bc0d7d
commit f748a0b068
9 changed files with 254 additions and 64 deletions

View File

@@ -46,9 +46,9 @@ The left location precedes the protocol in the URL string.
It is not used by global protocols like HTTP but it becomes handy when nesting protocols - for example you may want to access files in a ZIP archive:
file:archives/cpp_doc.zip#zip:reference/fopen.htm#syntax
``file:archives/cpp_doc.zip#zip:reference/fopen.htm#syntax``
In this example, the protocol is "zip", right location is "reference/fopen.htm", anchor is "syntax" and left location is "file:archives/cpp_doc.zip".
In this example, the protocol is "zip", right location is "reference/fopen.htm", anchor is "syntax" and left location is ``file:archives/cpp_doc.zip``.
There are two protocols used in this example: "zip" and "file".

View File

@@ -4,7 +4,7 @@
.. _writing non-english applications:
=====================================================
|phoenix_title| **writing Non-English Applications**
|phoenix_title| **Writing Non-English Applications**
=====================================================
This article describes how to write applications that communicate with the

View File

@@ -2237,7 +2237,7 @@ class XMLDocString(object):
inheritance_diagram = InheritanceDiagram(klass.nodeBases)
png, map = inheritance_diagram.MakeInheritanceDiagram()
image_desc = templates.TEMPLATE_INHERITANCE % (name, png, name, map)
image_desc = templates.TEMPLATE_INHERITANCE % ('class', name, png, name, map)
stream.write(image_desc)
if self.appearance:

View File

@@ -145,6 +145,19 @@ HTML_REPLACE = ['module', 'function', 'method', 'class', 'classmethod', 'staticm
# Today's date representation for the Sphinx HTML docs
TODAY = datetime.date.today().strftime('%d %B %Y')
# Inheritance diagram external hyperlinks
PYTHON_DOCS = 'http://docs.python.org/library/'
NUMPY_DOCS = 'http://docs.scipy.org/doc/numpy/reference/generated/'
EXTERN_INHERITANCE = {'UserDict.' : PYTHON_DOCS,
'ctypes.' : PYTHON_DOCS,
'code.' : PYTHON_DOCS,
'exceptions.': PYTHON_DOCS,
'threading.' : PYTHON_DOCS,
'numpy.' : NUMPY_DOCS
}
# wx.lib and other pure-Python stuff
class Enumeration(object):

View File

@@ -18,7 +18,7 @@ from subprocess import Popen, PIPE
# Phoenix-specific imports
from utilities import Wx2Sphinx
from utilities import Wx2Sphinx, FormatExternalLink
from constants import INHERITANCEROOT
ENOENT = getattr(errno, 'ENOENT', 0)
@@ -91,8 +91,10 @@ class InheritanceDiagram(object):
nodename = fullname = name_parts[-1]
else:
# Just the last 2 parts
nodename = '.'.join(name_parts[-2:])
nodename = '.'.join(name_parts[-2:])
if fullname.startswith('wx.'):
fullname = fullname[3:]
return nodename, fullname
@@ -120,14 +122,12 @@ class InheritanceDiagram(object):
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={}):
def generate_dot(self, class_summary, name="dummy", 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.
"""
@@ -165,8 +165,21 @@ class InheritanceDiagram(object):
newname, fullname = Wx2Sphinx(name)
else:
newname = name
this_node_attrs['URL'] = '"%s.html"'%fullname
if class_summary is None:
# Phoenix base classes, assume there is always a link
this_node_attrs['URL'] = '"%s.html"'%fullname
else:
if 'wx.' in fullname:
fullname = fullname[3:]
if fullname in class_summary:
this_node_attrs['URL'] = '"%s.html"'%fullname
else:
full_page = FormatExternalLink(fullname, inheritance=True)
if full_page:
this_node_attrs['URL'] = full_page
res.append(' "%s" [%s];\n' %
(newname, self._format_node_attrs(this_node_attrs)))
@@ -189,13 +202,16 @@ class InheritanceDiagram(object):
# ----------------------------------------------------------------------- #
def MakeInheritanceDiagram(self):
def MakeInheritanceDiagram(self, class_summary):
"""
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).
for more information).
:param `class_summary`: if not ``None``, used to identify if a class is actually been
wrapped or not (to avoid links pointing to non-existent pages).
:rtype: `tuple`
@@ -221,7 +237,7 @@ class InheritanceDiagram(object):
fid.close()
return os.path.split(outfn)[1], map.replace('\n', ' ')
code = self.generate_dot()
code = self.generate_dot(class_summary)
# graphviz expects UTF-8 by default
if isinstance(code, unicode):

View File

@@ -1,22 +1,18 @@
import sys
import os
import operator
import errno
import re
import cPickle
from StringIO import StringIO
from subprocess import Popen, PIPE
from inspect import getmro, getclasstree, getdoc, getcomments
from utilities import MakeSummary, ChopDescription, WriteSphinxOutput
from utilities import FindControlImages, PickleClassInfo
from constants import object_types, MODULE_TO_ICON, DOXY_2_REST
from utilities import FindControlImages, FormatExternalLink
from constants import object_types, MODULE_TO_ICON, DOXY_2_REST, SPHINXROOT
import templates
ENOENT = getattr(errno, 'ENOENT', 0)
EPIPE = getattr(errno, 'EPIPE', 0)
EPYDOC_PATTERN = re.compile(r'\S+{\S+}', re.DOTALL)
reload(sys)
@@ -75,7 +71,7 @@ def generic_summary(libraryItem, stream):
table = []
for item in sub_list:
if item.is_redundant or item.GetShortName().startswith('__test') or '.extern.' in item.name:
if item.is_redundant:
continue
item_docs = ReplaceWxDot(item.docs)
@@ -140,7 +136,13 @@ def ReplaceWxDot(text):
newtext += header + '\n'
newtext += '-'*len(header) + '\n\n'
# Try and replace True with ``True`` and False with ``False``
# ``None`` gives trouble sometimes...
for keyword in ['True', 'False']:
newtext = re.sub(r'\s%s\s'%keyword, ' ``%s`` '%keyword, newtext)
return newtext
@@ -183,8 +185,10 @@ def FindBestLink(klass, newlink):
return ':mod:`~%s`'%child.name
elif child.kind in range(object_types.FUNCTION, object_types.INSTANCE_METHOD):
return ':meth:`~%s`'%child.name
else:
elif child.kind == object_types.KLASS:
return ':class:`~%s`'%child.name
else:
return ':attr:`~%s`'%child.name
full_loop = FindInHierarchy(klass, newlink)
@@ -290,6 +294,9 @@ class ParentBase(object):
def Save(self):
if self.GetShortName().startswith('__test') or '.extern.' in self.name:
self.is_redundant = True
self.children = sorted(self.children, key=lambda k: (getattr(k, "order"), getattr(k, "name").lower()))
if self.docs is None:
@@ -377,7 +384,7 @@ class ParentBase(object):
return items
def ToRest(self):
def ToRest(self, class_summary):
pass
@@ -403,10 +410,10 @@ class Library(ParentBase):
return self.name
def Walk(self, obj):
def Walk(self, obj, class_summary):
if obj == self:
obj.ToRest()
obj.ToRest(class_summary)
# must have at least root folder
children = obj.GetChildren()
@@ -420,10 +427,10 @@ class Library(ParentBase):
if child.is_redundant:
continue
child.ToRest()
child.ToRest(class_summary)
# recursively scan other folders, appending results
self.Walk(child)
self.Walk(child, class_summary)
def FindItem(self, newlink, obj=None):
@@ -451,8 +458,10 @@ class Library(ParentBase):
return ':mod:`~%s`'%child.name
elif child.kind in range(object_types.FUNCTION, object_types.INSTANCE_METHOD+1):
return ':meth:`~%s`'%child.name
else:
elif child.kind == object_types.KLASS:
return ':class:`~%s`'%child.name
else:
return ':attr:`~%s`'%child.name
bestlink = self.FindItem(newlink, child)
@@ -467,7 +476,7 @@ class Library(ParentBase):
return self.python_version
def ToRest(self):
def ToRest(self, class_summary):
print '\n\nReST-ifying %s...\n\n'%self.base_name
stream = StringIO()
@@ -483,7 +492,51 @@ class Library(ParentBase):
generic_summary(self, stream)
WriteSphinxOutput(stream, self.sphinx_file)
def ClassesToPickle(self, obj, class_dict):
# must have at least root folder
children = obj.GetChildren()
if not children:
return class_dict
# check each name
for child in children:
if child.kind == object_types.KLASS:
if child.is_redundant:
continue
class_dict[child.name] = (child.method_list, child.bases)
# recursively scan other folders, appending results
class_dict = self.ClassesToPickle(child, class_dict)
return class_dict
def Save(self):
ParentBase.Save(self)
class_dict = {}
class_dict = self.ClassesToPickle(self, class_dict)
pickle_file = os.path.join(SPHINXROOT, 'class_summary.lst')
if os.path.isfile(pickle_file):
fid = open(pickle_file, 'rb')
items = cPickle.load(fid)
fid.close()
else:
items = {}
items.update(class_dict)
fid = open(pickle_file, 'wb')
cPickle.dump(items, fid)
fid.close()
class Module(ParentBase):
def __init__(self, name, kind):
@@ -508,9 +561,9 @@ class Module(ParentBase):
self.inheritance_diagram = None
def ToRest(self):
def ToRest(self, class_summary):
if self.is_redundant or self.GetShortName().startswith('__test') or '.extern.' in self.name:
if self.is_redundant:
return
stream = StringIO()
@@ -537,9 +590,9 @@ class Module(ParentBase):
if self.kind != object_types.PACKAGE:
print '%s - %s (module)'%(spacer, self.name)
if self.inheritance_diagram:
png, map = self.inheritance_diagram.MakeInheritanceDiagram()
png, map = self.inheritance_diagram.MakeInheritanceDiagram(class_summary)
short_name = self.GetShortName()
image_desc = templates.TEMPLATE_INHERITANCE % (short_name, png, short_name, map)
image_desc = templates.TEMPLATE_INHERITANCE % ('module', short_name, png, short_name, map)
stream.write(image_desc)
else:
print '%s - %s (package)'%(spacer, self.name)
@@ -564,7 +617,14 @@ class Module(ParentBase):
WriteSphinxOutput(stream, self.sphinx_file)
def Save(self):
ParentBase.Save(self)
if self.GetShortName().startswith('__test') or '.extern.' in self.name:
self.is_redundant = True
class Class(ParentBase):
@@ -635,9 +695,9 @@ class Class(ParentBase):
self.sphinx_file = MakeSphinxFile(name)
def ToRest(self):
def ToRest(self, class_summary):
if self.is_redundant or self.GetShortName().startswith('__test') or '.extern.' in self.name:
if self.is_redundant:
return
stream = StringIO()
@@ -656,9 +716,9 @@ class Class(ParentBase):
stream.write(class_docs + '\n\n')
if self.inheritance_diagram:
png, map = self.inheritance_diagram.MakeInheritanceDiagram()
png, map = self.inheritance_diagram.MakeInheritanceDiagram(class_summary)
short_name = self.GetShortName()
image_desc = templates.TEMPLATE_INHERITANCE % (short_name, png, short_name, map)
image_desc = templates.TEMPLATE_INHERITANCE % ('class', short_name, png, short_name, map)
stream.write(image_desc)
appearance = FindControlImages(self.name.lower())
@@ -667,13 +727,13 @@ class Class(ParentBase):
stream.write(appearance_desc + '\n\n')
if self.subClasses:
subs = [':ref:`%s`'%cls for cls in self.subClasses]
subs = [FormatExternalLink(cls) for cls in self.subClasses]
subs = ', '.join(subs)
subs_desc = templates.TEMPLATE_SUBCLASSES % subs
stream.write(subs_desc)
if self.superClasses:
sups = [':ref:`%s`'%cls for cls in self.superClasses]
sups = [FormatExternalLink(cls) for cls in self.superClasses]
sups = ', '.join(sups)
sups_desc = templates.TEMPLATE_SUPERCLASSES % sups
stream.write(sups_desc)
@@ -691,23 +751,16 @@ class Class(ParentBase):
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):
@@ -736,6 +789,22 @@ class Class(ParentBase):
if len(self.signature) < 2:
self.is_redundant = True
if self.GetShortName().startswith('__test') or '.extern.' in self.name:
self.is_redundant = True
if self.is_redundant:
return
methods = self.GetItemByKind(object_types.METHOD, object_types.INSTANCE_METHOD)
method_list = []
for meth in methods:
if not meth.is_redundant:
method_list.append(meth.GetShortName())
self.method_list = method_list
self.bases = self.superClasses
class ChildrenBase(object):
@@ -793,7 +862,7 @@ class ChildrenBase(object):
self.comments = u''
def ToRest(self):
def ToRest(self, class_summary):
pass
@@ -972,7 +1041,7 @@ class Attribute(ChildrenBase):
self.order = 7
def ToRest(self):
def ToRest(self, class_summary):
pass

View File

@@ -15,6 +15,8 @@ import pkgutil
import __builtin__
from buildtools.config import phoenixDir
from inspect import getargspec, ismodule, getdoc, getmodule, getcomments, isfunction
from inspect import ismethoddescriptor, getsource, ismemberdescriptor, isgetsetdescriptor
from inspect import isbuiltin, isclass, getfile, ismethod
@@ -491,7 +493,7 @@ def Import(init_name, import_name, full_process=True):
def PrintProgress(name, looped_names):
looped_names.append(name)
if len(looped_names) == 5:
if len(looped_names) == 4:
message = ", ".join(looped_names)
looped_names = []
print message
@@ -552,15 +554,29 @@ def SubImport(import_string, module, parent_class, ispkg):
return module_class, count
def ToRest(import_name):
sphinxDir = os.path.join(phoenixDir(), 'docs', 'sphinx')
pickle_file = os.path.join(sphinxDir, 'wx%s.pkl'%import_name)
fid = open(pickle_file, 'rb')
library_class = cPickle.load(fid)
fid.close()
fid = open(os.path.join(sphinxDir, 'class_summary.lst'), 'rb')
class_summary = cPickle.load(fid)
fid.close()
library_class.Walk(library_class, class_summary)
def ModuleHunter(init_name, import_name, version):
pickle_file = os.path.join(os.getcwd(), 'docs', 'sphinx', 'wx%s.pkl'%import_name)
sphinxDir = os.path.join(phoenixDir(), 'docs', 'sphinx')
pickle_file = os.path.join(sphinxDir, 'wx%s.pkl'%import_name)
if os.path.isfile(pickle_file):
fid = open(pickle_file, 'rb')
library_class = cPickle.load(fid)
fid.close()
library_class.Walk(library_class)
ToRest(import_name)
return
path = list(sys.path)
@@ -627,7 +643,7 @@ def ModuleHunter(init_name, import_name, version):
cPickle.dump(library_class, fid)
fid.close()
library_class.Walk(library_class)
ToRest(import_name)
if __name__ == "__main__":

View File

@@ -33,7 +33,7 @@ TEMPLATE_INHERITANCE = '''
|class_hierarchy| Inheritance Diagram
=====================================
Inheritance diagram for: **%s**
Inheritance diagram for %s **%s**
.. raw:: html

View File

@@ -23,7 +23,7 @@ from templates import TEMPLATE_CONTRIB
from constants import IGNORE, PUNCTUATION
from constants import CPP_ITEMS, VERSION, VALUE_MAP
from constants import RE_KEEP_SPACES
from constants import RE_KEEP_SPACES, EXTERN_INHERITANCE
from constants import DOXYROOT, SPHINXROOT, WIDGETS_IMAGES_ROOT
@@ -472,9 +472,11 @@ def MakeSummary(item_list, template, kind, add_tilde=True):
: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:" or ":ref:".
:param string `kind`: can be ``:meth:`` or ``:attr:`` or ``:ref:`` or ``:mod:``;
:param bool `add_tilde`: ``True`` to add the ``~`` character in front of the first
summary table column, ``False`` otherwise.
:rtype: `string`
:rtype: `string`
"""
maxlen = 0
@@ -507,7 +509,9 @@ def WriteSphinxOutput(stream, filename, append=False):
Writes the text contained in the `stream` to the `filename` output file.
:param StringIO.StringIO `stream`: the stream where the text lives.
:param string `filename`: the output file we want to write the text in.
:param string `filename`: the output file we want to write the text in;
:param bool `append`: ``True`` to append to the file, ``False`` to simply
write to it.
"""
text_file = os.path.join(SPHINXROOT, filename)
@@ -587,6 +591,13 @@ def PickleItem(description, current_module, name, kind):
# ----------------------------------------------------------------------- #
def PickleClassInfo(class_name, element):
"""
Saves some information about a class in a cPickle-compatible file., i.e. the
list of methods in that class and its super-classes.
:param string `class_name`: the name of the class we want to pickle;
:param xml.etree.ElementTree.Element `element`: the XML element we want to examine.
"""
pickle_file = os.path.join(SPHINXROOT, 'class_summary.lst')
@@ -649,6 +660,19 @@ RAW_2 = """
"""
def FormatContributedSnippets(kind, contrib_snippets):
"""
This method will include and properly ReST-ify contributed snippets
of wxPython code (at the moment only 2 snippets are available), by
including the Python code into the ReST files and allowing the user to
show/hide the snippets using a JavaScript "Accordion" script thanks to
the ``.. raw::`` directive (default for snippets is to be hidden).
:param string `kind`: can be "method", "function" or "class" depending on the
current item being scanned by the `sphinxgenerator.py` tool;
:param list `contrib_snippets`: a list of file names (with the ``*.py`` extension)
containing the contributed snippets of code. Normally these snippets live
in the ``SPHINXROOT/rest_substitutions/snippets/python/contrib`` folder.
"""
spacer = ''
if kind == 'function':
@@ -678,3 +702,55 @@ def FormatContributedSnippets(kind, contrib_snippets):
text += RAW_2%(spacer, spacer)
return text
def FormatExternalLink(fullname, inheritance=False):
"""
Analyzes the input `fullname` string to check whether a class description
is actually coming from an external documentation tool
(like http://docs.python.org/library/ or http://docs.scipy.org/doc/numpy/reference/generated/).
If the method finds such an external link, the associated inheritance
diagram (if `inheritance` is ``True``) or the ``:ref:`` directive are
modified accordingly to link it to the correct external documentation.
:param string `fullname`: the fully qualified name for a class, method or function,
i.e. `exceptions.Exception` or `threading.Thread`;
:param bool `inheritance`: ``True`` if the call is coming from :mod:`inheritance`,
``False`` otherwise.
"""
if fullname.count('.') == 0:
if not inheritance:
return ':class:`%s`'%fullname
return ''
parts = fullname.split('.')
possible_external = parts[-2] + '.'
real_name = '.'.join(parts[-2:])
if possible_external.startswith('_'):
# funny ctypes...
possible_external = possible_external[1:]
real_name = real_name[1:]
if possible_external not in EXTERN_INHERITANCE:
if not inheritance:
return ':class:`%s`'%fullname
return ''
base_address = EXTERN_INHERITANCE[possible_external]
if 'numpy' in real_name:
htmlpage = '%s.html#%s'%(real_name.lower(), real_name)
else:
htmlpage = '%shtml#%s'%(possible_external.lower(), real_name)
if inheritance:
full_page = '"%s"'%(base_address + htmlpage)
else:
full_page = '`%s <%s>`_'%(fullname, base_address + htmlpage)
return full_page