test_ok1/py/apigen/htmlgen.py

579 lines
22 KiB
Python
Raw Normal View History

import py
import inspect
from py.__.apigen.layout import LayoutPage
from py.__.apigen.source import browser as source_browser
from py.__.apigen.source import html as source_html
from py.__.apigen.tracer.description import is_private
from py.__.apigen.rest.genrest import split_of_last_part
from py.__.apigen.linker import relpath
sorted = py.builtin.sorted
html = py.xml.html
raw = py.xml.raw
# HTML related stuff
class H(html):
class Description(html.div):
style = html.Style(margin_left='15em')
class NamespaceDescription(Description):
pass
class NamespaceItem(html.div):
pass
class NamespaceDef(html.h1):
pass
class ClassDescription(Description):
pass
class ClassDef(html.h1):
pass
class MethodDescription(Description):
pass
class MethodDef(html.h2):
pass
class FunctionDescription(Description):
pass
class FunctionDef(html.h2):
pass
class ParameterDescription(html.div):
pass
class Docstring(html.div):
style = html.Style(white_space='pre', min_height='3em')
class Navigation(html.div):
style = html.Style(min_height='99%', float='left', margin_top='1.2em',
overflow='auto', width='15em', white_space='nowrap')
class NavigationItem(html.div):
pass
class BaseDescription(html.a):
pass
class SourceDef(html.div):
pass
class NonPythonSource(html.pre):
style = html.Style(margin_left='15em')
class DirList(html.div):
style = html.Style(margin_left='15em')
class DirListItem(html.div):
pass
class ValueDescList(html.ul):
def __init__(self, *args, **kwargs):
super(H.ValueDescList, self).__init__(*args, **kwargs)
class ValueDescItem(html.li):
pass
def get_param_htmldesc(linker, func):
""" get the html for the parameters of a function """
import inspect
# XXX copy and modify formatargspec to produce html
return H.em(inspect.formatargspec(*inspect.getargspec(func)))
def build_navitem_html(linker, name, linkid, indent, selected):
href = linker.get_lazyhref(linkid)
navitem = H.NavigationItem((indent * 2 * u'\xa0'), H.a(name, href=href))
if selected:
navitem.attr.class_ = 'selected'
return navitem
# some helper functionality
def source_dirs_files(fspath):
""" returns a tuple (dirs, files) for fspath
dirs are all the subdirs, files are the files which are interesting
in building source documentation for a Python code tree (basically all
normal files excluding .pyc and .pyo ones)
all files and dirs that have a name starting with . are considered
hidden
"""
dirs = []
files = []
for child in fspath.listdir():
if child.basename.startswith('.'):
continue
if child.check(dir=True):
dirs.append(child)
elif child.check(file=True):
if child.ext in ['.pyc', '.pyo']:
continue
files.append(child)
return sorted(dirs), sorted(files)
def create_namespace_tree(dotted_names):
""" creates a tree (in dict form) from a set of dotted names
"""
ret = {}
for dn in dotted_names:
path = dn.split('.')
for i in xrange(len(path)):
ns = '.'.join(path[:i])
itempath = '.'.join(path[:i + 1])
if ns not in ret:
ret[ns] = []
if itempath not in ret[ns]:
ret[ns].append(itempath)
return ret
def wrap_page(project, title, contentel, navel, outputpath, stylesheeturl):
page = LayoutPage(project, title, nav=navel, encoding='UTF-8',
stylesheeturl=stylesheeturl)
page.set_content(contentel)
here = py.magic.autopath().dirpath()
style = here.join('style.css').read()
outputpath.join('style.css').write(style)
return page
# the PageBuilder classes take care of producing the docs (using the stuff
# above)
class AbstractPageBuilder(object):
def write_page(self, title, reltargetpath, project, tag, nav):
targetpath = self.base.join(reltargetpath)
stylesheeturl = relpath('%s/' % (targetpath.dirpath(),),
self.base.join('style.css').strpath)
page = wrap_page(project, title,
tag, nav, self.base, stylesheeturl)
content = self.linker.call_withbase(reltargetpath, page.unicode)
targetpath.ensure()
targetpath.write(content.encode("utf8"))
class SourcePageBuilder(AbstractPageBuilder):
""" builds the html for a source docs page """
def __init__(self, base, linker, projroot):
self.base = base
self.linker = linker
self.projroot = projroot
def build_navigation(self, fspath):
nav = H.Navigation()
relpath = fspath.relto(self.projroot)
path = relpath.split('/')
indent = 0
# build links to parents
for i in xrange(len(path)):
dirpath = '/'.join(path[:i])
abspath = self.projroot.join(dirpath).strpath
if i == 0:
text = 'root'
else:
text = path[i-1]
nav.append(build_navitem_html(self.linker, text, abspath,
indent, False))
indent += 1
# build siblings or children and self
if fspath.check(dir=True):
# we're a dir, build ourselves and our children
dirpath = fspath
nav.append(build_navitem_html(self.linker, dirpath.basename,
dirpath.strpath, indent, True))
indent += 1
elif fspath.strpath == self.projroot.strpath:
dirpath = fspath
else:
# we're a file, build our parent's children only
dirpath = fspath.dirpath()
diritems, fileitems = source_dirs_files(dirpath)
for dir in diritems:
nav.append(build_navitem_html(self.linker, dir.basename,
dir.strpath, indent, False))
for file in fileitems:
selected = (fspath.check(file=True) and
file.basename == fspath.basename)
nav.append(build_navitem_html(self.linker, file.basename,
file.strpath, indent, selected))
return nav
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))
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)))))
nav = self.build_navigation(fspath)
return tag, nav
def build_nonpython_page(self, fspath):
try:
tag = H.NonPythonSource(unicode(fspath.read(), 'utf-8'))
except UnicodeError:
tag = H.NonPythonSource('no source available (binary file?)')
nav = self.build_navigation(fspath)
return tag, nav
def prepare_pages(self, base):
passed = []
for fspath in [base] + list(base.visit()):
if fspath.ext in ['.pyc', '.pyo']:
continue
relfspath = fspath.relto(base)
if relfspath.find('/.') > -1:
# skip hidden dirs and files
continue
elif fspath.check(dir=True):
if relfspath != '':
relfspath += '/'
reloutputpath = 'source/%sindex.html' % (relfspath,)
else:
reloutputpath = "source/%s.html" % (relfspath,)
outputpath = self.base.join(reloutputpath)
self.linker.set_link(str(fspath), reloutputpath)
passed.append((fspath, outputpath))
return passed
def build_pages(self, data, project, base):
""" build syntax-colored source views """
for fspath, outputpath in data:
if fspath.check(ext='.py'):
try:
tag, nav = self.build_python_page(fspath)
except (KeyboardInterrupt, SystemError):
raise
except: # XXX strange stuff going wrong at times... need to fix
exc, e, tb = py.std.sys.exc_info()
print '%s - %s' % (exc, e)
print
print ''.join(py.std.traceback.format_tb(tb))
print '-' * 79
del tb
tag, nav = self.build_nonpython_page(fspath)
elif fspath.check(dir=True):
tag, nav = self.build_dir_page(fspath)
else:
tag, nav = self.build_nonpython_page(fspath)
title = 'sources for %s' % (fspath.basename,)
reltargetpath = outputpath.relto(self.base)
self.write_page(title, reltargetpath, project, tag, nav)
class ApiPageBuilder(AbstractPageBuilder):
""" builds the html for an api docs page """
def __init__(self, base, linker, dsa, projroot):
self.base = base
self.linker = linker
self.dsa = dsa
self.projroot = projroot
self.projpath = py.path.local(projroot)
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)
docstring = func.__doc__
localname = func.__name__
argdesc = get_param_htmldesc(self.linker, func)
valuedesc = self.build_callable_signature_description(dotted_name)
sourcefile = inspect.getsourcefile(func)
callable_source = self.dsa.get_function_source(dotted_name)
# i assume they're both either available or unavailable(XXX ?)
is_in_pkg = self.is_in_pkg(sourcefile)
if is_in_pkg and sourcefile and callable_source:
csource = H.div(H.br(),
H.a('source: %s' % (sourcefile,),
href=self.linker.get_lazyhref(sourcefile)),
H.br(),
H.SourceDef(H.pre(callable_source)))
elif not is_in_pkg and sourcefile and callable_source:
csource = H.div(H.br(),
H.em('source: %s' % (sourcefile,)),
H.br(),
H.SourceDef(H.pre(callable_source)))
else:
csource = H.SourceDef('could not get source file')
snippet = H.FunctionDescription(
H.FunctionDef(localname, argdesc),
valuedesc,
H.Docstring(docstring or H.em('no docstring available')),
csource,
)
return snippet
def build_class_view(self, dotted_name):
""" build the html for a class """
cls = self.dsa.get_obj(dotted_name)
# XXX is this a safe check?
try:
sourcefile = inspect.getsourcefile(cls)
except TypeError:
sourcelink = 'builtin file, no source available'
else:
if sourcefile is None:
sourcelink = H.div('no source available')
else:
if sourcefile[-1] in ['o', 'c']:
sourcefile = sourcefile[:-1]
sourcelink = H.div(H.a('view source',
href=self.linker.get_lazyhref(sourcefile)))
docstring = cls.__doc__
methods = self.dsa.get_class_methods(dotted_name)
basehtml = []
bases = self.dsa.get_possible_base_classes(dotted_name)
for base in bases:
try:
obj = self.dsa.get_obj(base.name)
except KeyError:
basehtml.append(base.name)
else:
href = self.linker.get_lazyhref(base.name)
basehtml.append(H.BaseDescription(base.name, href=href))
basehtml.append(',')
if basehtml:
basehtml.pop()
basehtml.append('):')
if not hasattr(cls, '__name__'):
clsname = 'instance of %s' % (cls.__class__.__name__,)
else:
clsname = cls.__name__
snippet = H.ClassDescription(
# XXX bases HTML
H.ClassDef('%s(' % (clsname,), *basehtml),
H.Docstring(docstring or H.em('no docstring available')),
sourcelink,
)
if methods:
snippet.append(H.h2('methods:'))
for method in methods:
snippet += self.build_callable_view('%s.%s' % (dotted_name,
method))
# XXX properties
return snippet
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__
snippet = H.NamespaceDescription(
H.NamespaceDef(namespace_dotted_name),
H.Docstring(docstring or H.em('no docstring available'))
)
for dotted_name in item_dotted_names:
itemname = dotted_name.split('.')[-1]
if is_private(itemname):
continue
snippet.append(
H.NamespaceItem(
H.a(itemname,
href=self.linker.get_lazyhref(dotted_name)
)
)
)
return snippet
def prepare_class_pages(self, namespace_tree, classes_dotted_names):
passed = []
methodsdata = []
for dotted_name in classes_dotted_names:
parent_dotted_name, _ = split_of_last_part(dotted_name)
try:
sibling_dotted_names = namespace_tree[parent_dotted_name]
except KeyError:
# no siblings (built-in module or sth)
sibling_dotted_names = []
tag = self.build_class_view(dotted_name)
nav = self.build_navigation(parent_dotted_name,
sibling_dotted_names, dotted_name)
reltargetpath = "api/%s.html" % (dotted_name,)
self.linker.set_link(dotted_name, reltargetpath)
passed.append((dotted_name, tag, nav, reltargetpath))
method_dotted_names = ['%s.%s' % (dotted_name, method_name) for
method_name in
self.dsa.get_class_methods(dotted_name)]
methodsdata += self.prepare_method_pages(namespace_tree,
method_dotted_names)
return passed, methodsdata
def build_class_pages(self, data, project):
""" build the full api pages for a set of classes """
for dotted_name, tag, nav, reltargetpath in data:
title = 'api documentation for %s' % (dotted_name,)
self.write_page(title, reltargetpath, project, tag, nav)
def prepare_method_pages(self, namespace_tree, method_dotted_names):
# XXX note that even though these pages are still built, there's no nav
# pointing to them anymore...
passed = []
for dotted_name in method_dotted_names:
parent_dotted_name, _ = split_of_last_part(dotted_name)
module_dotted_name, _ = split_of_last_part(parent_dotted_name)
sibling_dotted_names = namespace_tree[module_dotted_name]
tag = self.build_callable_view(dotted_name)
nav = self.build_navigation(parent_dotted_name,
sibling_dotted_names, dotted_name)
reltargetpath = "api/%s.html" % (dotted_name,)
self.linker.set_link(dotted_name, reltargetpath)
passed.append((dotted_name, tag, nav, reltargetpath))
return passed
def build_method_pages(self, data, project):
for dotted_name, tag, nav, reltargetpath in data:
title = 'api documentation for %s' % (dotted_name,)
self.write_page(title, reltargetpath, project, tag, nav)
def prepare_function_pages(self, namespace_tree, method_dotted_names):
passed = []
for dotted_name in method_dotted_names:
# XXX should we create a build_function_view instead?
parent_dotted_name, _ = split_of_last_part(dotted_name)
sibling_dotted_names = namespace_tree[parent_dotted_name]
tag = self.build_callable_view(dotted_name)
nav = self.build_navigation(parent_dotted_name,
sibling_dotted_names, dotted_name)
reltargetpath = "api/%s.html" % (dotted_name,)
self.linker.set_link(dotted_name, reltargetpath)
passed.append((dotted_name, tag, nav, reltargetpath))
return passed
def build_function_pages(self, data, project):
for dotted_name, tag, nav, reltargetpath in data:
title = 'api documentation for %s' % (dotted_name,)
self.write_page(title, reltargetpath, project, tag, nav)
def prepare_namespace_pages(self, namespace_tree):
passed = []
module_name = self.dsa.get_module_name().split('/')[-1]
names = namespace_tree.keys()
names.sort()
for dotted_name in names:
subitem_dotted_names = namespace_tree[dotted_name]
tag = self.build_namespace_view(dotted_name, subitem_dotted_names)
nav = self.build_navigation(dotted_name, subitem_dotted_names,
dotted_name)
if dotted_name == '':
reltargetpath = 'api/index.html'
else:
reltargetpath = 'api/%s.html' % (dotted_name,)
self.linker.set_link(dotted_name, reltargetpath)
passed.append((dotted_name, tag, nav, reltargetpath))
return passed
def build_namespace_pages(self, data, project):
for dotted_name, tag, nav, reltargetpath in data:
if dotted_name == '':
dotted_name = self.dsa.get_module_name().split('/')[-1]
title = 'index of %s namespace' % (dotted_name,)
self.write_page(title, reltargetpath, project, tag, nav)
def build_navigation(self, dotted_name, item_dotted_names, selection):
navitems = []
# top namespace, index.html
module_name = self.dsa.get_module_name().split('/')[-1]
navitems.append(build_navitem_html(self.linker, module_name, '', 0,
(selection == '')))
indent = 1
path = dotted_name.split('.')
if dotted_name != '':
# build html for each item in path to dotted_name item
for i in xrange(len(path)):
name = path[i]
item_dotted_name = '.'.join(path[:i+1])
selected = (selection == item_dotted_name)
navitems.append(build_navitem_html(self.linker, name,
item_dotted_name, indent,
selected))
indent += 1
# build sub items of dotted_name item
for item_dotted_name in py.builtin.sorted(item_dotted_names):
itemname = item_dotted_name.split('.')[-1]
if is_private(itemname):
continue
selected = (item_dotted_name == selection)
navitems.append(build_navitem_html(self.linker, itemname,
item_dotted_name, indent,
selected))
return H.Navigation(*navitems)
def build_callable_signature_description(self, dotted_name):
args, retval = self.dsa.get_function_signature(dotted_name)
valuedesc = H.ValueDescList()
for name, _type in args:
valuedesc.append(self.build_sig_value_description(name, _type))
if retval:
retval = self.process_type_link(retval)
ret = H.div(H.div('where:'), valuedesc, H.div('return value:'),
retval or 'None')
return ret
def build_sig_value_description(self, name, _type):
l = self.process_type_link(_type)
items = []
next = "%s : " % name
for item in l:
if isinstance(item, str):
next += item
else:
if next:
items.append(next)
next = ""
items.append(item)
if next:
items.append(next)
return H.ValueDescItem(*items)
def process_type_link(self, _type):
# now we do simple type dispatching and provide a link in this case
lst = []
data = self.dsa.get_type_desc(_type)
if not data:
for i in _type.striter():
if isinstance(i, str):
lst.append(i)
else:
lst += self.process_type_link(i)
return lst
name, _desc_type, is_degenerated = data
if not is_degenerated:
linktarget = self.linker.get_lazyhref(name)
lst.append(H.a(str(_type), href=linktarget))
else:
raise IOError('do not think we ever get here?')
# we should provide here some way of linking to sourcegen directly
lst.append(name)
return lst
def is_in_pkg(self, sourcefile):
return py.path.local(sourcefile).relto(self.projpath)