[svn r37913] Some more cleanups in HTML generation, fixed support for docstrings in
namespaces, in order to do this I had to change the way objects are retrieved: instead of getting them from the DSA I now walk the package tree, small change in apigen.py: to allow re-using the get_documentable_items function I split it up in a generic and a specific part. --HG-- branch : trunk
This commit is contained in:
parent
7852ead1fe
commit
98b4dcf155
|
@ -12,13 +12,23 @@ from py.__.apigen import linker
|
||||||
from py.__.apigen import project
|
from py.__.apigen import project
|
||||||
from py.__.apigen.tracer.docstorage import pkg_to_dict
|
from py.__.apigen.tracer.docstorage import pkg_to_dict
|
||||||
|
|
||||||
def get_documentable_items(pkgdir):
|
def get_documentable_items_pkgdir(pkgdir):
|
||||||
|
""" get all documentable items from an initpkg pkgdir
|
||||||
|
|
||||||
|
this is a generic implementation, import as 'get_documentable_items'
|
||||||
|
from your module when using initpkg to get all public stuff in the
|
||||||
|
package documented
|
||||||
|
"""
|
||||||
sys.path.insert(0, str(pkgdir.dirpath()))
|
sys.path.insert(0, str(pkgdir.dirpath()))
|
||||||
rootmod = __import__(pkgdir.basename)
|
rootmod = __import__(pkgdir.basename)
|
||||||
d = pkg_to_dict(rootmod)
|
d = pkg_to_dict(rootmod)
|
||||||
|
return pkgdir.basename, d
|
||||||
|
|
||||||
|
def get_documentable_items(pkgdir):
|
||||||
|
pkgname, pkgdict = get_documentable_items_pkgdir(pkgdir)
|
||||||
from py.__.execnet.channel import Channel
|
from py.__.execnet.channel import Channel
|
||||||
#d['execnet.Channel'] = Channel # XXX doesn't work
|
# pkgdict['execnet.Channel'] = Channel # XXX doesn't work
|
||||||
return 'py', d
|
return pkgname, pkgdict
|
||||||
|
|
||||||
def build(pkgdir, dsa, capture):
|
def build(pkgdir, dsa, capture):
|
||||||
l = linker.Linker()
|
l = linker.Linker()
|
||||||
|
|
|
@ -82,16 +82,25 @@ class H(html):
|
||||||
link, H.div(class_='code', *sourceels))
|
link, H.div(class_='code', *sourceels))
|
||||||
|
|
||||||
class SourceDef(html.div):
|
class SourceDef(html.div):
|
||||||
pass
|
def __init__(self, *sourceels):
|
||||||
|
super(H.SourceDef, self).__init__(
|
||||||
|
H.div(class_='code', *sourceels))
|
||||||
|
|
||||||
class NonPythonSource(html.pre):
|
class NonPythonSource(html.pre):
|
||||||
pass # style = html.Style(margin_left='15em')
|
pass # style = html.Style(margin_left='15em')
|
||||||
|
|
||||||
class DirList(html.div):
|
class DirList(html.div):
|
||||||
pass # style = html.Style(margin_left='15em')
|
def __init__(self, dirs, files):
|
||||||
|
dirs = [H.DirListItem(text, href) for (text, href) in dirs]
|
||||||
|
files = [H.DirListItem(text, href) for (text, href) in files]
|
||||||
|
super(H.DirList, self).__init__(
|
||||||
|
H.h2('directories'), dirs,
|
||||||
|
H.h2('files'), files,
|
||||||
|
)
|
||||||
|
|
||||||
class DirListItem(html.div):
|
class DirListItem(html.div):
|
||||||
pass
|
def __init__(self, text, href):
|
||||||
|
super(H.DirListItem, self).__init__(H.a(text, href=href))
|
||||||
|
|
||||||
class ValueDescList(html.ul):
|
class ValueDescList(html.ul):
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
|
|
|
@ -14,6 +14,9 @@ sorted = py.builtin.sorted
|
||||||
html = py.xml.html
|
html = py.xml.html
|
||||||
raw = py.xml.raw
|
raw = py.xml.raw
|
||||||
|
|
||||||
|
def is_navigateable(name):
|
||||||
|
return (not is_private(name) and name != '__doc__')
|
||||||
|
|
||||||
def deindent(str, linesep='\n'):
|
def deindent(str, linesep='\n'):
|
||||||
""" de-indent string
|
""" de-indent string
|
||||||
|
|
||||||
|
@ -46,6 +49,16 @@ def deindent(str, linesep='\n'):
|
||||||
ret.append(line[deindent:])
|
ret.append(line[deindent:])
|
||||||
return '%s\n' % (linesep.join(ret),)
|
return '%s\n' % (linesep.join(ret),)
|
||||||
|
|
||||||
|
def get_linesep(s, default='\n'):
|
||||||
|
""" return the line seperator of a string
|
||||||
|
|
||||||
|
returns 'default' if no seperator can be found
|
||||||
|
"""
|
||||||
|
for sep in ('\r\n', '\r', '\n'):
|
||||||
|
if sep in s:
|
||||||
|
return sep
|
||||||
|
return default
|
||||||
|
|
||||||
def get_param_htmldesc(linker, func):
|
def get_param_htmldesc(linker, func):
|
||||||
""" get the html for the parameters of a function """
|
""" get the html for the parameters of a function """
|
||||||
import inspect
|
import inspect
|
||||||
|
@ -161,27 +174,21 @@ class SourcePageBuilder(AbstractPageBuilder):
|
||||||
re = py.std.re
|
re = py.std.re
|
||||||
_reg_body = re.compile(r'<body[^>]*>(.*)</body>', re.S)
|
_reg_body = re.compile(r'<body[^>]*>(.*)</body>', re.S)
|
||||||
def build_python_page(self, fspath):
|
def build_python_page(self, fspath):
|
||||||
mod = source_browser.parse_path(fspath)
|
# XXX two reads of the same file here... not very bad (disk caches
|
||||||
# XXX let's cheat a bit here... there should be a different function
|
# and such) but also not very nice...
|
||||||
# using the linker, and returning a proper py.xml.html element,
|
enc = source_html.get_module_encoding(fspath.strpath)
|
||||||
# at some point
|
source = fspath.read()
|
||||||
html = source_html.create_html(mod)
|
sep = get_linesep(source)
|
||||||
snippet = self._reg_body.search(html).group(1)
|
colored = enumerate_and_color(source.split(sep), 0, enc)
|
||||||
tag = H.SourceDef(raw(snippet))
|
tag = H.SourceDef(*colored)
|
||||||
nav = self.build_navigation(fspath)
|
nav = self.build_navigation(fspath)
|
||||||
return tag, nav
|
return tag, nav
|
||||||
|
|
||||||
def build_dir_page(self, fspath):
|
def build_dir_page(self, fspath):
|
||||||
tag = H.DirList()
|
|
||||||
dirs, files = source_dirs_files(fspath)
|
dirs, files = source_dirs_files(fspath)
|
||||||
tag.append(H.h2('directories'))
|
dirs = [(p.basename, self.linker.get_lazyhref(str(p))) for p in dirs]
|
||||||
for path in dirs:
|
files = [(p.basename, self.linker.get_lazyhref(str(p))) for p in files]
|
||||||
tag.append(H.DirListItem(H.a(path.basename,
|
tag = H.DirList(dirs, files)
|
||||||
href=self.linker.get_lazyhref(str(path)))))
|
|
||||||
tag.append(H.h2('files'))
|
|
||||||
for path in files:
|
|
||||||
tag.append(H.DirListItem(H.a(path.basename,
|
|
||||||
href=self.linker.get_lazyhref(str(path)))))
|
|
||||||
nav = self.build_navigation(fspath)
|
nav = self.build_navigation(fspath)
|
||||||
return tag, nav
|
return tag, nav
|
||||||
|
|
||||||
|
@ -238,7 +245,8 @@ class SourcePageBuilder(AbstractPageBuilder):
|
||||||
else:
|
else:
|
||||||
tag, nav = self.build_nonpython_page(fspath)
|
tag, nav = self.build_nonpython_page(fspath)
|
||||||
title = 'sources for %s' % (fspath.basename,)
|
title = 'sources for %s' % (fspath.basename,)
|
||||||
reltargetpath = outputpath.relto(self.base).replace(os.path.sep, '/')
|
reltargetpath = outputpath.relto(self.base).replace(os.path.sep,
|
||||||
|
'/')
|
||||||
self.write_page(title, reltargetpath, project, tag, nav)
|
self.write_page(title, reltargetpath, project, tag, nav)
|
||||||
|
|
||||||
def enumerate_and_color(codelines, firstlineno, enc):
|
def enumerate_and_color(codelines, firstlineno, enc):
|
||||||
|
@ -255,6 +263,20 @@ def enumerate_and_color(codelines, firstlineno, enc):
|
||||||
break
|
break
|
||||||
return colored
|
return colored
|
||||||
|
|
||||||
|
def get_obj(pkg, dotted_name):
|
||||||
|
full_dotted_name = '%s.%s' % (pkg.__name__, dotted_name)
|
||||||
|
if dotted_name == '':
|
||||||
|
return pkg
|
||||||
|
path = dotted_name.split('.')
|
||||||
|
ret = pkg
|
||||||
|
for item in path:
|
||||||
|
marker = []
|
||||||
|
ret = getattr(ret, item, marker)
|
||||||
|
if ret is marker:
|
||||||
|
raise NameError('can not access %s in %s' % (item,
|
||||||
|
full_dotted_name))
|
||||||
|
return ret
|
||||||
|
|
||||||
class ApiPageBuilder(AbstractPageBuilder):
|
class ApiPageBuilder(AbstractPageBuilder):
|
||||||
""" builds the html for an api docs page """
|
""" builds the html for an api docs page """
|
||||||
def __init__(self, base, linker, dsa, projroot, namespace_tree,
|
def __init__(self, base, linker, dsa, projroot, namespace_tree,
|
||||||
|
@ -267,10 +289,13 @@ class ApiPageBuilder(AbstractPageBuilder):
|
||||||
self.namespace_tree = namespace_tree
|
self.namespace_tree = namespace_tree
|
||||||
self.capture = capture
|
self.capture = capture
|
||||||
|
|
||||||
|
pkgname = self.dsa.get_module_name().split('/')[-1]
|
||||||
|
self.pkg = __import__(pkgname)
|
||||||
|
|
||||||
def build_callable_view(self, dotted_name):
|
def build_callable_view(self, dotted_name):
|
||||||
""" build the html for a class method """
|
""" build the html for a class method """
|
||||||
# XXX we may want to have seperate
|
# XXX we may want to have seperate
|
||||||
func = self.dsa.get_obj(dotted_name)
|
func = get_obj(self.pkg, dotted_name)
|
||||||
docstring = func.__doc__
|
docstring = func.__doc__
|
||||||
if docstring:
|
if docstring:
|
||||||
docstring = deindent(docstring)
|
docstring = deindent(docstring)
|
||||||
|
@ -289,7 +314,8 @@ class ApiPageBuilder(AbstractPageBuilder):
|
||||||
enc = source_html.get_module_encoding(sourcefile)
|
enc = source_html.get_module_encoding(sourcefile)
|
||||||
tokenizer = source_color.Tokenizer(source_color.PythonSchema)
|
tokenizer = source_color.Tokenizer(source_color.PythonSchema)
|
||||||
firstlineno = func.func_code.co_firstlineno
|
firstlineno = func.func_code.co_firstlineno
|
||||||
org = callable_source.split('\n')
|
sep = get_linesep(callable_source)
|
||||||
|
org = callable_source.split(sep)
|
||||||
colored = enumerate_and_color(org, firstlineno, enc)
|
colored = enumerate_and_color(org, firstlineno, enc)
|
||||||
text = 'source: %s' % (sourcefile,)
|
text = 'source: %s' % (sourcefile,)
|
||||||
if is_in_pkg:
|
if is_in_pkg:
|
||||||
|
@ -307,7 +333,7 @@ class ApiPageBuilder(AbstractPageBuilder):
|
||||||
|
|
||||||
def build_class_view(self, dotted_name):
|
def build_class_view(self, dotted_name):
|
||||||
""" build the html for a class """
|
""" build the html for a class """
|
||||||
cls = self.dsa.get_obj(dotted_name)
|
cls = get_obj(self.pkg, dotted_name)
|
||||||
# XXX is this a safe check?
|
# XXX is this a safe check?
|
||||||
try:
|
try:
|
||||||
sourcefile = inspect.getsourcefile(cls)
|
sourcefile = inspect.getsourcefile(cls)
|
||||||
|
@ -360,21 +386,15 @@ class ApiPageBuilder(AbstractPageBuilder):
|
||||||
|
|
||||||
def build_namespace_view(self, namespace_dotted_name, item_dotted_names):
|
def build_namespace_view(self, namespace_dotted_name, item_dotted_names):
|
||||||
""" build the html for a namespace (module) """
|
""" build the html for a namespace (module) """
|
||||||
try:
|
obj = get_obj(self.pkg, namespace_dotted_name)
|
||||||
obj = self.dsa.get_obj(namespace_dotted_name)
|
docstring = obj.__doc__
|
||||||
except KeyError:
|
|
||||||
docstring = None
|
|
||||||
else:
|
|
||||||
docstring = obj.__doc__
|
|
||||||
if docstring:
|
|
||||||
docstring = deindent(docstring)
|
|
||||||
snippet = H.NamespaceDescription(
|
snippet = H.NamespaceDescription(
|
||||||
H.NamespaceDef(namespace_dotted_name),
|
H.NamespaceDef(namespace_dotted_name),
|
||||||
H.Docstring(docstring or '*no docstring available*')
|
H.Docstring(docstring or '*no docstring available*')
|
||||||
)
|
)
|
||||||
for dotted_name in sorted(item_dotted_names):
|
for dotted_name in sorted(item_dotted_names):
|
||||||
itemname = dotted_name.split('.')[-1]
|
itemname = dotted_name.split('.')[-1]
|
||||||
if is_private(itemname):
|
if not is_navigateable(itemname):
|
||||||
continue
|
continue
|
||||||
snippet.append(
|
snippet.append(
|
||||||
H.NamespaceItem(
|
H.NamespaceItem(
|
||||||
|
@ -490,7 +510,7 @@ class ApiPageBuilder(AbstractPageBuilder):
|
||||||
selected = dn == '.'.join(path)
|
selected = dn == '.'.join(path)
|
||||||
sibpath = dn.split('.')
|
sibpath = dn.split('.')
|
||||||
sibname = sibpath[-1]
|
sibname = sibpath[-1]
|
||||||
if is_private(sibname):
|
if not is_navigateable(sibname):
|
||||||
continue
|
continue
|
||||||
navitems.append(H.NavigationItem(self.linker, dn, sibname,
|
navitems.append(H.NavigationItem(self.linker, dn, sibname,
|
||||||
depth, selected))
|
depth, selected))
|
||||||
|
@ -560,7 +580,6 @@ class ApiPageBuilder(AbstractPageBuilder):
|
||||||
return py.path.local(sourcefile).relto(self.projpath)
|
return py.path.local(sourcefile).relto(self.projpath)
|
||||||
|
|
||||||
def build_callsite(self, functionname, call_site):
|
def build_callsite(self, functionname, call_site):
|
||||||
print 'building callsite for', functionname
|
|
||||||
tbtag = self.gen_traceback(functionname, reversed(call_site))
|
tbtag = self.gen_traceback(functionname, reversed(call_site))
|
||||||
return H.CallStackItem(call_site[0].filename, call_site[0].lineno + 1,
|
return H.CallStackItem(call_site[0].filename, call_site[0].lineno + 1,
|
||||||
tbtag)
|
tbtag)
|
||||||
|
@ -575,7 +594,10 @@ class ApiPageBuilder(AbstractPageBuilder):
|
||||||
|
|
||||||
tokenizer = source_color.Tokenizer(source_color.PythonSchema)
|
tokenizer = source_color.Tokenizer(source_color.PythonSchema)
|
||||||
mangled = []
|
mangled = []
|
||||||
for i, sline in enumerate(str(source).split('\n')):
|
|
||||||
|
source = str(source)
|
||||||
|
sep = get_linesep(source)
|
||||||
|
for i, sline in enumerate(source.split(sep)):
|
||||||
if i == lineno:
|
if i == lineno:
|
||||||
l = '-> %s' % (sline,)
|
l = '-> %s' % (sline,)
|
||||||
else:
|
else:
|
||||||
|
|
|
@ -116,7 +116,7 @@ class AbstractBuilderTest(object):
|
||||||
self.namespace_tree = namespace_tree
|
self.namespace_tree = namespace_tree
|
||||||
self.apb = ApiPageBuilder(base, linker, self.dsa,
|
self.apb = ApiPageBuilder(base, linker, self.dsa,
|
||||||
self.fs_root.join(self.pkg_name),
|
self.fs_root.join(self.pkg_name),
|
||||||
namespace_tree)
|
namespace_tree, 'root docstring')
|
||||||
self.spb = SourcePageBuilder(base, linker,
|
self.spb = SourcePageBuilder(base, linker,
|
||||||
self.fs_root.join(self.pkg_name))
|
self.fs_root.join(self.pkg_name))
|
||||||
|
|
||||||
|
@ -130,7 +130,7 @@ class TestApiPageBuilder(AbstractBuilderTest):
|
||||||
pkg.main.sub.func(pkg.main.SomeClass(10))
|
pkg.main.sub.func(pkg.main.SomeClass(10))
|
||||||
t.end_tracing()
|
t.end_tracing()
|
||||||
apb = ApiPageBuilder(self.base, self.linker, dsa, self.fs_root,
|
apb = ApiPageBuilder(self.base, self.linker, dsa, self.fs_root,
|
||||||
self.namespace_tree)
|
self.namespace_tree, 'root docstring')
|
||||||
snippet = apb.build_callable_view('main.sub.func')
|
snippet = apb.build_callable_view('main.sub.func')
|
||||||
html = snippet.unicode()
|
html = snippet.unicode()
|
||||||
print html
|
print html
|
||||||
|
@ -371,8 +371,7 @@ class TestSourcePageBuilder(AbstractBuilderTest):
|
||||||
assert funcsource.check(file=True)
|
assert funcsource.check(file=True)
|
||||||
html = funcsource.read()
|
html = funcsource.read()
|
||||||
print html
|
print html
|
||||||
assert ('<span class="alt_keyword">def</span> '
|
assert ('<span class="alt_keyword">def</span> func(arg1)') in html
|
||||||
'<a href="#func" name="func">func</a>(arg1):') in html
|
|
||||||
|
|
||||||
def test_build_navigation_root(self):
|
def test_build_navigation_root(self):
|
||||||
self.spb.prepare_pages(self.fs_root)
|
self.spb.prepare_pages(self.fs_root)
|
||||||
|
|
|
@ -42,15 +42,24 @@ def setup_fs_project(name):
|
||||||
return 'quux'
|
return 'quux'
|
||||||
"""))
|
"""))
|
||||||
temp.ensure("pak/__init__.py").write(py.code.Source("""\
|
temp.ensure("pak/__init__.py").write(py.code.Source("""\
|
||||||
|
'''pkg docstring'''
|
||||||
from py.initpkg import initpkg
|
from py.initpkg import initpkg
|
||||||
initpkg(__name__, exportdefs = {
|
initpkg(__name__,
|
||||||
'main.sub.func': ("./func.py", "func"),
|
long_description=globals()['__doc__'],
|
||||||
'main.func': ("./func.py", "func_2"),
|
exportdefs={'main.sub.func': ("./func.py", "func"),
|
||||||
'main.SomeTestClass': ('./sometestclass.py', 'SomeTestClass'),
|
'main.func': ("./func.py", "func_2"),
|
||||||
'main.SomeTestSubClass': ('./sometestsubclass.py',
|
'main.SomeTestClass': ('./sometestclass.py',
|
||||||
'SomeTestSubClass'),
|
'SomeTestClass'),
|
||||||
'somenamespace': ('./somenamespace.py', '*'),
|
'main.SomeTestSubClass': ('./sometestsubclass.py',
|
||||||
})
|
'SomeTestSubClass'),
|
||||||
|
'somenamespace': ('./somenamespace.py', '*')})
|
||||||
|
"""))
|
||||||
|
temp.ensure('apigen.py').write(py.code.Source("""\
|
||||||
|
import py
|
||||||
|
py.std.sys.path.insert(0,
|
||||||
|
py.magic.autopath().dirpath().dirpath().dirpath().strpath)
|
||||||
|
from py.__.apigen.apigen import build, \
|
||||||
|
get_documentable_items_pkgdir as get_documentable_items
|
||||||
"""))
|
"""))
|
||||||
temp.ensure('pak/test/test_pak.py').write(py.code.Source("""\
|
temp.ensure('pak/test/test_pak.py').write(py.code.Source("""\
|
||||||
import py
|
import py
|
||||||
|
@ -87,8 +96,8 @@ def setup_fs_project(name):
|
||||||
def test_get_documentable_items():
|
def test_get_documentable_items():
|
||||||
fs_root, package_name = setup_fs_project('test_get_documentable_items')
|
fs_root, package_name = setup_fs_project('test_get_documentable_items')
|
||||||
pkgname, documentable = apigen.get_documentable_items(
|
pkgname, documentable = apigen.get_documentable_items(
|
||||||
fs_root.join(package_name))
|
fs_root.join(package_name))
|
||||||
assert pkgname == 'py'
|
assert pkgname == 'pak'
|
||||||
assert sorted(documentable.keys()) == [
|
assert sorted(documentable.keys()) == [
|
||||||
'main.SomeTestClass', 'main.SomeTestSubClass', 'main.func',
|
'main.SomeTestClass', 'main.SomeTestSubClass', 'main.func',
|
||||||
'main.sub.func', 'somenamespace.baz', 'somenamespace.foo']
|
'main.sub.func', 'somenamespace.baz', 'somenamespace.foo']
|
||||||
|
@ -101,14 +110,14 @@ def test_apigen_functional():
|
||||||
pydir = py.magic.autopath().dirpath().dirpath().dirpath()
|
pydir = py.magic.autopath().dirpath().dirpath().dirpath()
|
||||||
pakdir = fs_root.join('pak')
|
pakdir = fs_root.join('pak')
|
||||||
if py.std.sys.platform == 'win32':
|
if py.std.sys.platform == 'win32':
|
||||||
cmd = 'set APIGEN_TARGET=%s && python "%s/bin/py.test"' % (tempdir,
|
cmd = ('set APIGEN_TARGET=%s && set PYTHONPATH=%s && '
|
||||||
pydir)
|
'python "%s/bin/py.test"') % (tempdir, fs_root, pydir)
|
||||||
else:
|
else:
|
||||||
cmd = 'APIGEN_TARGET="%s" "%s/bin/py.test"' % (tempdir, pydir)
|
cmd = ('APIGEN_TARGET="%s" PYTHONPATH="%s" '
|
||||||
|
'"%s/bin/py.test"') % (tempdir, fs_root, pydir)
|
||||||
try:
|
try:
|
||||||
output = py.process.cmdexec('%s --apigen="%s/apigen.py" "%s"' % (
|
output = py.process.cmdexec('%s --apigen="%s/apigen.py" "%s"' % (
|
||||||
cmd, pydir.join('apigen'),
|
cmd, fs_root, pakdir))
|
||||||
pakdir))
|
|
||||||
except py.error.Error, e:
|
except py.error.Error, e:
|
||||||
print e.out
|
print e.out
|
||||||
raise
|
raise
|
||||||
|
@ -130,6 +139,10 @@ def test_apigen_functional():
|
||||||
assert namespace_api.check(file=True)
|
assert namespace_api.check(file=True)
|
||||||
html = namespace_api.read()
|
html = namespace_api.read()
|
||||||
assert '<a href="main.SomeTestClass.html">SomeTestClass</a>' in html
|
assert '<a href="main.SomeTestClass.html">SomeTestClass</a>' in html
|
||||||
|
index = apidir.join('index.html')
|
||||||
|
assert index.check(file=True)
|
||||||
|
html = index.read()
|
||||||
|
assert 'pkg docstring' in html
|
||||||
|
|
||||||
sourcedir = tempdir.join('source')
|
sourcedir = tempdir.join('source')
|
||||||
assert sourcedir.check(dir=True)
|
assert sourcedir.check(dir=True)
|
||||||
|
|
|
@ -10,7 +10,7 @@
|
||||||
viewed. method views (when navigating there through
|
viewed. method views (when navigating there through
|
||||||
the class view) should also have the source there
|
the class view) should also have the source there
|
||||||
|
|
||||||
DONE I guess (todo: add syntax coloring)
|
DONE I think
|
||||||
|
|
||||||
* have class-level attributes be displayed
|
* have class-level attributes be displayed
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue