[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:
guido 2007-02-04 15:35:28 +01:00
parent 7852ead1fe
commit 98b4dcf155
6 changed files with 111 additions and 58 deletions

View File

@ -12,13 +12,23 @@ from py.__.apigen import linker
from py.__.apigen import project
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()))
rootmod = __import__(pkgdir.basename)
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
#d['execnet.Channel'] = Channel # XXX doesn't work
return 'py', d
# pkgdict['execnet.Channel'] = Channel # XXX doesn't work
return pkgname, pkgdict
def build(pkgdir, dsa, capture):
l = linker.Linker()

View File

@ -82,16 +82,25 @@ class H(html):
link, H.div(class_='code', *sourceels))
class SourceDef(html.div):
pass
def __init__(self, *sourceels):
super(H.SourceDef, self).__init__(
H.div(class_='code', *sourceels))
class NonPythonSource(html.pre):
pass # style = html.Style(margin_left='15em')
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):
pass
def __init__(self, text, href):
super(H.DirListItem, self).__init__(H.a(text, href=href))
class ValueDescList(html.ul):
def __init__(self, *args, **kwargs):

View File

@ -14,6 +14,9 @@ sorted = py.builtin.sorted
html = py.xml.html
raw = py.xml.raw
def is_navigateable(name):
return (not is_private(name) and name != '__doc__')
def deindent(str, linesep='\n'):
""" de-indent string
@ -46,6 +49,16 @@ def deindent(str, linesep='\n'):
ret.append(line[deindent:])
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):
""" get the html for the parameters of a function """
import inspect
@ -161,27 +174,21 @@ class SourcePageBuilder(AbstractPageBuilder):
re = py.std.re
_reg_body = re.compile(r'<body[^>]*>(.*)</body>', re.S)
def build_python_page(self, fspath):
mod = source_browser.parse_path(fspath)
# XXX let's cheat a bit here... there should be a different function
# using the linker, and returning a proper py.xml.html element,
# at some point
html = source_html.create_html(mod)
snippet = self._reg_body.search(html).group(1)
tag = H.SourceDef(raw(snippet))
# XXX two reads of the same file here... not very bad (disk caches
# and such) but also not very nice...
enc = source_html.get_module_encoding(fspath.strpath)
source = fspath.read()
sep = get_linesep(source)
colored = enumerate_and_color(source.split(sep), 0, enc)
tag = H.SourceDef(*colored)
nav = self.build_navigation(fspath)
return tag, nav
def build_dir_page(self, fspath):
tag = H.DirList()
dirs, files = source_dirs_files(fspath)
tag.append(H.h2('directories'))
for path in dirs:
tag.append(H.DirListItem(H.a(path.basename,
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)))))
dirs = [(p.basename, self.linker.get_lazyhref(str(p))) for p in dirs]
files = [(p.basename, self.linker.get_lazyhref(str(p))) for p in files]
tag = H.DirList(dirs, files)
nav = self.build_navigation(fspath)
return tag, nav
@ -238,7 +245,8 @@ class SourcePageBuilder(AbstractPageBuilder):
else:
tag, nav = self.build_nonpython_page(fspath)
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)
def enumerate_and_color(codelines, firstlineno, enc):
@ -255,6 +263,20 @@ def enumerate_and_color(codelines, firstlineno, enc):
break
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):
""" builds the html for an api docs page """
def __init__(self, base, linker, dsa, projroot, namespace_tree,
@ -267,10 +289,13 @@ class ApiPageBuilder(AbstractPageBuilder):
self.namespace_tree = namespace_tree
self.capture = capture
pkgname = self.dsa.get_module_name().split('/')[-1]
self.pkg = __import__(pkgname)
def build_callable_view(self, dotted_name):
""" build the html for a class method """
# XXX we may want to have seperate
func = self.dsa.get_obj(dotted_name)
func = get_obj(self.pkg, dotted_name)
docstring = func.__doc__
if docstring:
docstring = deindent(docstring)
@ -289,7 +314,8 @@ class ApiPageBuilder(AbstractPageBuilder):
enc = source_html.get_module_encoding(sourcefile)
tokenizer = source_color.Tokenizer(source_color.PythonSchema)
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)
text = 'source: %s' % (sourcefile,)
if is_in_pkg:
@ -307,7 +333,7 @@ class ApiPageBuilder(AbstractPageBuilder):
def build_class_view(self, dotted_name):
""" 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?
try:
sourcefile = inspect.getsourcefile(cls)
@ -360,21 +386,15 @@ class ApiPageBuilder(AbstractPageBuilder):
def build_namespace_view(self, namespace_dotted_name, item_dotted_names):
""" build the html for a namespace (module) """
try:
obj = self.dsa.get_obj(namespace_dotted_name)
except KeyError:
docstring = None
else:
docstring = obj.__doc__
if docstring:
docstring = deindent(docstring)
obj = get_obj(self.pkg, namespace_dotted_name)
docstring = obj.__doc__
snippet = H.NamespaceDescription(
H.NamespaceDef(namespace_dotted_name),
H.Docstring(docstring or '*no docstring available*')
)
for dotted_name in sorted(item_dotted_names):
itemname = dotted_name.split('.')[-1]
if is_private(itemname):
if not is_navigateable(itemname):
continue
snippet.append(
H.NamespaceItem(
@ -490,7 +510,7 @@ class ApiPageBuilder(AbstractPageBuilder):
selected = dn == '.'.join(path)
sibpath = dn.split('.')
sibname = sibpath[-1]
if is_private(sibname):
if not is_navigateable(sibname):
continue
navitems.append(H.NavigationItem(self.linker, dn, sibname,
depth, selected))
@ -560,7 +580,6 @@ class ApiPageBuilder(AbstractPageBuilder):
return py.path.local(sourcefile).relto(self.projpath)
def build_callsite(self, functionname, call_site):
print 'building callsite for', functionname
tbtag = self.gen_traceback(functionname, reversed(call_site))
return H.CallStackItem(call_site[0].filename, call_site[0].lineno + 1,
tbtag)
@ -575,7 +594,10 @@ class ApiPageBuilder(AbstractPageBuilder):
tokenizer = source_color.Tokenizer(source_color.PythonSchema)
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:
l = '-> %s' % (sline,)
else:

View File

@ -116,7 +116,7 @@ class AbstractBuilderTest(object):
self.namespace_tree = namespace_tree
self.apb = ApiPageBuilder(base, linker, self.dsa,
self.fs_root.join(self.pkg_name),
namespace_tree)
namespace_tree, 'root docstring')
self.spb = SourcePageBuilder(base, linker,
self.fs_root.join(self.pkg_name))
@ -130,7 +130,7 @@ class TestApiPageBuilder(AbstractBuilderTest):
pkg.main.sub.func(pkg.main.SomeClass(10))
t.end_tracing()
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')
html = snippet.unicode()
print html
@ -371,8 +371,7 @@ class TestSourcePageBuilder(AbstractBuilderTest):
assert funcsource.check(file=True)
html = funcsource.read()
print html
assert ('<span class="alt_keyword">def</span> '
'<a href="#func" name="func">func</a>(arg1):') in html
assert ('<span class="alt_keyword">def</span> func(arg1)') in html
def test_build_navigation_root(self):
self.spb.prepare_pages(self.fs_root)

View File

@ -42,15 +42,24 @@ def setup_fs_project(name):
return 'quux'
"""))
temp.ensure("pak/__init__.py").write(py.code.Source("""\
'''pkg docstring'''
from py.initpkg import initpkg
initpkg(__name__, exportdefs = {
'main.sub.func': ("./func.py", "func"),
'main.func': ("./func.py", "func_2"),
'main.SomeTestClass': ('./sometestclass.py', 'SomeTestClass'),
'main.SomeTestSubClass': ('./sometestsubclass.py',
'SomeTestSubClass'),
'somenamespace': ('./somenamespace.py', '*'),
})
initpkg(__name__,
long_description=globals()['__doc__'],
exportdefs={'main.sub.func': ("./func.py", "func"),
'main.func': ("./func.py", "func_2"),
'main.SomeTestClass': ('./sometestclass.py',
'SomeTestClass'),
'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("""\
import py
@ -87,8 +96,8 @@ def setup_fs_project(name):
def test_get_documentable_items():
fs_root, package_name = setup_fs_project('test_get_documentable_items')
pkgname, documentable = apigen.get_documentable_items(
fs_root.join(package_name))
assert pkgname == 'py'
fs_root.join(package_name))
assert pkgname == 'pak'
assert sorted(documentable.keys()) == [
'main.SomeTestClass', 'main.SomeTestSubClass', 'main.func',
'main.sub.func', 'somenamespace.baz', 'somenamespace.foo']
@ -101,14 +110,14 @@ def test_apigen_functional():
pydir = py.magic.autopath().dirpath().dirpath().dirpath()
pakdir = fs_root.join('pak')
if py.std.sys.platform == 'win32':
cmd = 'set APIGEN_TARGET=%s && python "%s/bin/py.test"' % (tempdir,
pydir)
cmd = ('set APIGEN_TARGET=%s && set PYTHONPATH=%s && '
'python "%s/bin/py.test"') % (tempdir, fs_root, pydir)
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:
output = py.process.cmdexec('%s --apigen="%s/apigen.py" "%s"' % (
cmd, pydir.join('apigen'),
pakdir))
cmd, fs_root, pakdir))
except py.error.Error, e:
print e.out
raise
@ -130,6 +139,10 @@ def test_apigen_functional():
assert namespace_api.check(file=True)
html = namespace_api.read()
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')
assert sourcedir.check(dir=True)

View File

@ -10,7 +10,7 @@
viewed. method views (when navigating there through
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