|
""" Generating ReST output (raw, not python) |
out of data that we know about function calls |
""" |
|
import py |
import sys |
import re |
|
from py.__.apigen.tracer.docstorage import DocStorageAccessor |
from py.__.rest.rst import * |
from py.__.apigen.tracer import model |
from py.__.rest.transform import RestTransformer |
|
def split_of_last_part(name): |
name = name.split(".") |
return ".".join(name[:-1]), name[-1] |
|
class AbstractLinkWriter(object): |
""" Class implementing writing links to source code. |
There should exist various classes for that, different for Trac, |
different for CVSView, etc. |
""" |
def getlinkobj(self, obj, name): |
return None |
|
def getlink(self, filename, lineno, funcname): |
raise NotImplementedError("Abstract link writer") |
|
def getpkgpath(self, filename): |
|
path = py.path.local(filename).dirpath() |
while 1: |
try: |
path.join('__init__.py').stat() |
path = path.dirpath() |
except py.error.ENOENT: |
return path |
|
class ViewVC(AbstractLinkWriter): |
""" Link writer for ViewVC version control viewer |
""" |
def __init__(self, basepath): |
|
self.basepath = basepath |
|
def getlink(self, filename, lineno, funcname): |
path = str(self.getpkgpath(filename)) |
assert filename.startswith(path), ( |
"%s does not belong to package %s" % (filename, path)) |
relname = filename[len(path):] |
if relname.endswith('.pyc'): |
relname = relname[:-1] |
sep = py.std.os.sep |
if sep != '/': |
relname = relname.replace(sep, '/') |
return ('%s:%s' % (filename, lineno), |
self.basepath + relname[1:] + '?view=markup') |
|
class SourceView(AbstractLinkWriter): |
def __init__(self, baseurl): |
self.baseurl = baseurl |
if self.baseurl.endswith("/"): |
self.baseurl = baseurl[:-1] |
|
def getlink(self, filename, lineno, funcname): |
if filename.endswith('.pyc'): |
filename = filename[:-1] |
if filename is None: |
return "<UNKNOWN>:%s" % funcname,"" |
pkgpath = self.getpkgpath(filename) |
if not filename.startswith(str(pkgpath)): |
|
return "<UNKNOWN>:%s" % funcname,"" |
|
relname = filename[len(str(pkgpath)):] |
if relname.endswith('.pyc'): |
relname = relname[:-1] |
sep = py.std.os.sep |
if sep != '/': |
relname = relname.replace(sep, '/') |
return "%s:%s" % (relname, funcname),\ |
"%s%s#%s" % (self.baseurl, relname, funcname) |
|
def getlinkobj(self, name, obj): |
try: |
filename = sys.modules[obj.__module__].__file__ |
return self.getlink(filename, 0, name) |
except AttributeError: |
return None |
|
class DirectPaste(AbstractLinkWriter): |
""" No-link writer (inliner) |
""" |
def getlink(self, filename, lineno, funcname): |
return ('%s:%s' % (filename, lineno), "") |
|
class DirectFS(AbstractLinkWriter): |
""" Creates links to the files on the file system (for local docs) |
""" |
def getlink(self, filename, lineno, funcname): |
return ('%s:%s' % (filename, lineno), 'file://%s' % (filename,)) |
|
class PipeWriter(object): |
def __init__(self, output=sys.stdout): |
self.output = output |
|
def write_section(self, name, rest): |
text = "Contents of file %s.txt:" % (name,) |
self.output.write(text + "\n") |
self.output.write("=" * len(text) + "\n") |
self.output.write("\n") |
self.output.write(rest.text() + "\n") |
|
def getlink(self, type, targetname, targetfilename): |
return '%s.txt' % (targetfilename,) |
|
class DirWriter(object): |
def __init__(self, directory=None): |
if directory is None: |
self.directory = py.test.ensuretemp("rstoutput") |
else: |
self.directory = py.path.local(directory) |
|
def write_section(self, name, rest): |
filename = '%s.txt' % (name,) |
self.directory.ensure(filename).write(rest.text()) |
|
def getlink(self, type, targetname, targetfilename): |
|
return '%s.html' % (targetfilename,) |
|
class FileWriter(object): |
def __init__(self, fpath): |
self.fpath = fpath |
self.fp = fpath.open('w+') |
self._defined_targets = [] |
|
def write_section(self, name, rest): |
self.fp.write(rest.text()) |
self.fp.flush() |
|
def getlink(self, type, targetname, targetbasename): |
|
|
|
if targetname in self._defined_targets: |
return None |
self._defined_targets.append(targetname) |
targetname = targetname.lower().replace('.', '-').replace('_', '-') |
while '--' in targetname: |
targetname = targetname.replace('--', '-') |
if targetname.startswith('-'): |
targetname = targetname[1:] |
if targetname.endswith('-'): |
targetname = targetname[:-1] |
return '#%s-%s' % (type, targetname) |
|
class HTMLDirWriter(object): |
def __init__(self, indexhandler, filehandler, directory=None): |
self.indexhandler = indexhandler |
self.filehandler = filehandler |
if directory is None: |
self.directory = py.test.ensuretemp('dirwriter') |
else: |
self.directory = py.path.local(directory) |
|
def write_section(self, name, rest): |
if name == 'index': |
handler = self.indexhandler |
else: |
handler = self.filehandler |
h = handler(name) |
t = RestTransformer(rest) |
t.parse(h) |
self.directory.ensure('%s.html' % (name,)).write(h.html) |
|
def getlink(self, type, targetname, targetfilename): |
return '%s.html' % (targetfilename,) |
|
class RestGen(object): |
def __init__(self, dsa, linkgen, writer=PipeWriter()): |
|
|
self.dsa = dsa |
self.linkgen = linkgen |
self.writer = writer |
self.tracebacks = {} |
|
def write(self): |
"""write the data to the writer""" |
modlist = self.get_module_list() |
classlist = self.get_class_list(module='') |
funclist = self.get_function_list() |
modlist.insert(0, ['', classlist, funclist]) |
|
indexrest = self.build_index([t[0] for t in modlist]) |
self.writer.write_section('index', Rest(*indexrest)) |
|
self.build_modrest(modlist) |
|
def build_modrest(self, modlist): |
modrest = self.build_modules(modlist) |
for name, rest, classlist, funclist in modrest: |
mname = name |
if mname == '': |
mname = self.dsa.get_module_name() |
self.writer.write_section('module_%s' % (mname,), |
Rest(*rest)) |
for cname, crest, cfunclist in classlist: |
self.writer.write_section('class_%s' % (cname,), |
Rest(*crest)) |
for fname, frest, tbdata in cfunclist: |
self.writer.write_section('method_%s' % (fname,), |
Rest(*frest)) |
for tbname, tbrest in tbdata: |
self.writer.write_section('traceback_%s' % (tbname,), |
Rest(*tbrest)) |
for fname, frest, tbdata in funclist: |
self.writer.write_section('function_%s' % (fname,), |
Rest(*frest)) |
for tbname, tbrest in tbdata: |
self.writer.write_section('traceback_%s' % (tbname,), |
Rest(*tbrest)) |
|
def build_classrest(self, classlist): |
classrest = self.build_classes(classlist) |
for cname, rest, cfunclist in classrest: |
self.writer.write_section('class_%s' % (cname,), |
Rest(*rest)) |
for fname, rest in cfunclist: |
self.writer.write_section('method_%s' % (fname,), |
Rest(*rest)) |
|
def build_funcrest(self, funclist): |
funcrest = self.build_functions(funclist) |
for fname, rest, tbdata in funcrest: |
self.writer.write_section('function_%s' % (fname,), |
Rest(*rest)) |
for tbname, tbrest in tbdata: |
self.writer.write_section('traceback_%s' % (tbname,), |
Rest(*tbrest)) |
|
def build_index(self, modules): |
rest = [Title('index', abovechar='=', belowchar='=')] |
rest.append(Title('exported modules:', belowchar='=')) |
for module in modules: |
mtitle = module |
if module == '': |
module = self.dsa.get_module_name() |
mtitle = '%s (top-level)' % (module,) |
linktarget = self.writer.getlink('module', module, |
'module_%s' % (module,)) |
rest.append(ListItem(Link(mtitle, linktarget))) |
return rest |
|
def build_modules(self, modules): |
ret = [] |
for module, classes, functions in modules: |
mname = module |
if mname == '': |
mname = self.dsa.get_module_name() |
rest = [Title('module: %s' % (mname,), abovechar='=', |
belowchar='='), |
Title('index:', belowchar='=')] |
if classes: |
rest.append(Title('classes:', belowchar='^')) |
for cls, bases, cfunclist in classes: |
linktarget = self.writer.getlink('class', cls, |
'class_%s' % (cls,)) |
rest.append(ListItem(Link(cls, linktarget))) |
classrest = self.build_classes(classes) |
if functions: |
rest.append(Title('functions:', belowchar='^')) |
for func in functions: |
if module: |
func = '%s.%s' % (module, func) |
linktarget = self.writer.getlink('function', |
func, |
'function_%s' % (func,)) |
rest.append(ListItem(Link(func, linktarget))) |
funcrest = self.build_functions(functions, module, False) |
ret.append((module, rest, classrest, funcrest)) |
return ret |
|
def build_classes(self, classes): |
ret = [] |
for cls, bases, functions in classes: |
rest = [Title('class: %s' % (cls,), belowchar='='), |
LiteralBlock(self.dsa.get_doc(cls))] |
|
link_to_class = self.linkgen.getlinkobj(cls, self.dsa.get_obj(cls)) |
if link_to_class: |
rest.append(Paragraph(Text("source: "), Link(*link_to_class))) |
|
if bases: |
rest.append(Title('base classes:', belowchar='^')), |
for base in bases: |
rest.append(ListItem(self.make_class_link(base))) |
if functions: |
rest.append(Title('functions:', belowchar='^')) |
for (func, origin) in functions: |
linktarget = self.writer.getlink('method', |
'%s.%s' % (cls, func), |
'method_%s.%s' % (cls, |
func)) |
rest.append(ListItem(Link('%s.%s' % (cls, func), |
linktarget))) |
funcrest = self.build_functions(functions, cls, True) |
ret.append((cls, rest, funcrest)) |
return ret |
|
def build_functions(self, functions, parent='', methods=False): |
ret = [] |
for function in functions: |
origin = None |
if methods: |
function, origin = function |
if parent: |
function = '%s.%s' % (parent, function) |
rest, tbrest = self.write_function(function, origin=origin, |
ismethod=methods) |
ret.append((function, rest, tbrest)) |
return ret |
|
def get_module_list(self): |
visited = [] |
ret = [] |
for name in self.dsa.get_class_names(): |
if '.' in name: |
module, classname = split_of_last_part(name) |
if module in visited: |
continue |
visited.append(module) |
ret.append((module, self.get_class_list(module), |
self.get_function_list(module))) |
return ret |
|
def get_class_list(self, module): |
ret = [] |
for name in self.dsa.get_class_names(): |
classname = name |
if '.' in name: |
classmodule, classname = split_of_last_part(name) |
if classmodule != module: |
continue |
elif module != '': |
continue |
bases = self.dsa.get_possible_base_classes(name) |
ret.append((name, bases, self.get_method_list(name))) |
return ret |
|
def get_function_list(self, module=''): |
ret = [] |
for name in self.dsa.get_function_names(): |
funcname = name |
if '.' in name: |
funcpath, funcname = split_of_last_part(name) |
if funcpath != module: |
continue |
elif module != '': |
continue |
ret.append(funcname) |
return ret |
|
def get_method_list(self, classname): |
methodnames = self.dsa.get_class_methods(classname) |
return [(mn, self.dsa.get_method_origin('%s.%s' % (classname, mn))) |
for mn in methodnames] |
|
def process_type_link(self, _type): |
|
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.writer.getlink(_desc_type, name, |
'%s_%s' % (_desc_type, name)) |
lst.append(Link(str(_type), linktarget)) |
else: |
|
lst.append(name) |
return lst |
|
def write_function(self, functionname, origin=None, ismethod=False, |
belowchar='-'): |
|
|
|
if ismethod: |
title = Title('method: %s' % (functionname,), belowchar=belowchar) |
else: |
title = Title('function: %s' % (functionname,), |
belowchar=belowchar) |
|
lst = [title, LiteralBlock(self.dsa.get_doc(functionname)), |
LiteralBlock(self.dsa.get_function_definition(functionname))] |
link_to_function = self.linkgen.getlinkobj(functionname, self.dsa.get_obj(functionname)) |
if link_to_function: |
lst.insert(1, Paragraph(Text("source: "), Link(*link_to_function))) |
|
opar = Paragraph(Strong('origin'), ":") |
if origin: |
opar.add(self.make_class_link(origin)) |
else: |
opar.add(Text('<UNKNOWN>')) |
lst.append(opar) |
|
lst.append(Paragraph(Strong("where"), ":")) |
args, retval = self.dsa.get_function_signature(functionname) |
for name, _type in args + [('return value', retval)]: |
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(Text(next)) |
next = "" |
items.append(item) |
if next: |
items.append(Text(next)) |
lst.append(ListItem(*items)) |
|
local_changes = self.dsa.get_function_local_changes(functionname) |
if local_changes: |
lst.append(Paragraph(Strong('changes in __dict__ after execution'), ":")) |
for k, changeset in local_changes.iteritems(): |
lst.append(ListItem('%s: %s' % (k, ', '.join(changeset)))) |
|
exceptions = self.dsa.get_function_exceptions(functionname) |
if exceptions: |
lst.append(Paragraph(Strong('exceptions that might appear during ' |
'execution'), ":")) |
for exc in exceptions: |
lst.append(ListItem(exc)) |
|
|
|
|
|
|
|
|
|
source = self.dsa.get_function_source(functionname) |
if source: |
lst.append(Paragraph(Strong('function source'), ":")) |
lst.append(LiteralBlock(source)) |
|
|
call_sites = self.dsa.get_function_callpoints(functionname) |
tbrest = [] |
if call_sites: |
call_site_title = Title("call sites:", belowchar='+') |
lst.append(call_site_title) |
|
|
|
|
|
|
for call_site, _ in call_sites: |
fdata, tbdata = self.call_site_link(functionname, call_site) |
lst += fdata |
tbrest.append(tbdata) |
|
return lst, tbrest |
|
def call_site_link(self, functionname, call_site): |
tbid, tbrest = self.gen_traceback(functionname, call_site) |
tbname = '%s.%s' % (functionname, tbid) |
linktarget = self.writer.getlink('traceback', |
tbname, |
'traceback_%s' % (tbname,)) |
frest = [Paragraph("called in %s" % call_site[0].filename), |
Paragraph(Link("traceback %s" % (tbname,), |
linktarget))] |
return frest, (tbname, tbrest) |
|
def gen_traceback(self, funcname, call_site): |
tbid = len(self.tracebacks.setdefault(funcname, [])) |
self.tracebacks[funcname].append(call_site) |
tbrest = [Title('traceback for %s' % (funcname,))] |
for line in call_site: |
lineno = line.lineno - line.firstlineno |
linkname, linktarget = self.linkgen.getlink(line.filename, |
line.lineno + 1, |
funcname) |
if linktarget: |
tbrest.append(Paragraph(Link(linkname, linktarget))) |
else: |
tbrest.append(Paragraph(linkname)) |
try: |
source = line.source |
except IOError: |
source = "*cannot get source*" |
mangled = [] |
for i, sline in enumerate(str(source).split('\n')): |
if i == lineno: |
line = '-> %s' % (sline,) |
else: |
line = ' %s' % (sline,) |
mangled.append(line) |
tbrest.append(LiteralBlock('\n'.join(mangled))) |
return tbid, tbrest |
|
def make_class_link(self, desc): |
if not desc or desc.is_degenerated: |
|
return Strong(desc.name) |
else: |
linktarget = self.writer.getlink('class', desc.name, |
'class_%s' % (desc.name,)) |
return Link(desc.name, linktarget) |
|