diff --git a/py/apigen/html.py b/py/apigen/html.py
index 9149e9dfc..0d7ef9bbf 100644
--- a/py/apigen/html.py
+++ b/py/apigen/html.py
@@ -61,6 +61,7 @@ class H(html):
infoid = 'info_%s' % (localname.replace('.', '_dot_'),)
docstringid = 'docstring_%s' % (localname.replace('.', '_dot_'),)
fd = H.FunctionDef(localname, argdesc,
+ title='click to view details',
onclick=('showhideel('
'document.getElementById("%s")); '
% (infoid,)))
diff --git a/py/apigen/htmlgen.py b/py/apigen/htmlgen.py
index af51eaf1e..72aa7fb54 100644
--- a/py/apigen/htmlgen.py
+++ b/py/apigen/htmlgen.py
@@ -18,6 +18,25 @@ raw = py.xml.raw
REDUCE_CALLSITES = True
+def find_method_origin(meth):
+ cls = getattr(meth, 'im_class', None)
+ if cls is None:
+ return None # XXX unknown origin (built-in function or method or sth)
+ name = meth.im_func.func_name
+ origin = cls
+ # XXX old-style classes support required? :|
+ mro = inspect.getmro(cls)
+ for base in mro:
+ m = getattr(base, name, None)
+ if m is None:
+ continue
+ if not hasattr(m, 'im_func'):
+ # builtin
+ return None
+ if m.im_func is meth.im_func:
+ origin = base
+ return origin
+
def is_navigateable(name):
return (not is_private(name) and name != '__doc__')
@@ -135,10 +154,17 @@ def enumerate_and_color(codelines, firstlineno, enc):
source_html.prepare_line([line], tokenizer, enc))
except py.error.ENOENT:
# error reading source code, giving up
- snippet = org
+ snippet = codelines
break
return snippet
+def enumerate_and_color_module(path, enc):
+ snippet = H.SourceBlock()
+ tokenizer = source_color.Tokenizer(source_color.PythonSchema)
+ for i, text in enumerate(source_html.prepare_module(path, tokenizer, enc)):
+ snippet.add_line(i + 1, text)
+ return snippet
+
_get_obj_cache = {}
def get_obj(dsa, pkg, dotted_name):
full_dotted_name = '%s.%s' % (pkg.__name__, dotted_name)
@@ -258,9 +284,16 @@ class SourcePageBuilder(AbstractPageBuilder):
# 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)]
+ try:
+ colored = [enumerate_and_color_module(fspath, enc)]
+ except (KeyboardInterrupt, SystemExit):
+ raise
+ except Exception, e:
+ #self.capture.err.writeorg('\ncompilation exception: %s\n' % (e,))
+ # problem building HTML with anchors; let's try without...
+ source = fspath.read()
+ sep = get_linesep(source)
+ colored = [enumerate_and_color(source.split(sep), 0, enc)]
tag = H.PythonSource(colored)
nav = self.build_navigation(fspath)
return tag, nav
@@ -316,7 +349,7 @@ class SourcePageBuilder(AbstractPageBuilder):
if fspath.check(ext='.py'):
try:
tag, nav = self.build_python_page(fspath)
- except (KeyboardInterrupt, SystemError):
+ except (KeyboardInterrupt, SystemExit):
raise
except: # XXX strange stuff going wrong at times... need to fix
raise
@@ -398,8 +431,8 @@ class ApiPageBuilder(AbstractPageBuilder):
relpath = get_rel_sourcepath(self.projroot, sourcefile, sourcefile)
text = 'source: %s' % (relpath,)
if is_in_pkg:
- href = self.linker.get_lazyhref(sourcefile)
-
+ href = self.linker.get_lazyhref(sourcefile,
+ self.get_anchor(func))
csource = H.SourceSnippet(text, href, colored)
cslinks = self.build_callsites(dotted_name)
snippet = H.FunctionDescription(localname, argdesc, docstring,
@@ -432,7 +465,8 @@ class ApiPageBuilder(AbstractPageBuilder):
if sourcefile[-1] in ['o', 'c']:
sourcefile = sourcefile[:-1]
sourcelink = H.div(H.a('view source',
- href=self.linker.get_lazyhref(sourcefile)))
+ href=self.linker.get_lazyhref(sourcefile,
+ self.get_anchor(cls))))
snippet = H.ClassDescription(
# XXX bases HTML
@@ -651,6 +685,10 @@ class ApiPageBuilder(AbstractPageBuilder):
return lst
name, _desc_type, is_degenerated = data
if not is_degenerated:
+ try:
+ obj = self.dsa.get_obj(name)
+ except KeyError:
+ obj = None
linktarget = self.linker.get_lazyhref(name)
lst.append(H.a(str(_type), href=linktarget))
else:
@@ -695,6 +733,7 @@ class ApiPageBuilder(AbstractPageBuilder):
_reg_source = py.std.re.compile(r'([^>]*)<(.*)>')
def gen_traceback(self, dotted_name, call_site):
tbtag = H.CallStackDescription()
+ obj = self.dsa.get_obj(dotted_name)
for frame in call_site:
lineno = frame.lineno - frame.firstlineno
source = frame.source
@@ -772,3 +811,20 @@ class ApiPageBuilder(AbstractPageBuilder):
self._revcache[dotted_name] = rev
return rev
+ def get_anchor(self, obj):
+ # XXX may not always return the right results...
+ anchor = None
+ if hasattr(obj, 'im_func'):
+ # method
+ origin = find_method_origin(obj)
+ if origin:
+ anchor = '%s.%s' % (origin.__name__,
+ obj.im_func.func_name)
+ elif hasattr(obj, 'func_name'):
+ anchor = obj.func_name
+ elif hasattr(obj, '__name__'):
+ anchor = obj.__name__
+ elif hasattr(obj, '__class__'):
+ anchor = obj.__class__.__name__
+ return anchor
+
diff --git a/py/apigen/linker.py b/py/apigen/linker.py
index 9655467e5..e8a80b533 100644
--- a/py/apigen/linker.py
+++ b/py/apigen/linker.py
@@ -63,8 +63,11 @@ class TempLinker(object):
def __init__(self):
self._linkid2target = {}
- def get_lazyhref(self, linkid):
- return '%s://%s' % (TEMPLINK_PROTO, linkid)
+ def get_lazyhref(self, linkid, anchor=None):
+ href = '%s://%s' % (TEMPLINK_PROTO, linkid)
+ if anchor:
+ href += '#' + anchor
+ return href
def set_link(self, linkid, target):
assert linkid not in self._linkid2target
@@ -72,13 +75,18 @@ class TempLinker(object):
def get_target(self, tempurl, fromlocation=None):
assert tempurl.startswith('%s://' % (TEMPLINK_PROTO,))
- linkid = '://'.join(tempurl.split('://')[1:])
+ anchor = None
+ if '#' in tempurl:
+ tempurl, anchor = tempurl.split('#', 1)
+ linkid = tempurl.split('://', 1)[1]
linktarget = self._linkid2target[linkid]
if fromlocation is not None:
linktarget = relpath(fromlocation, linktarget)
+ if anchor is not None:
+ linktarget += '#' + anchor
return linktarget
- _reg_tempurl = py.std.re.compile('["\'](%s:\/\/[^"\s]*)["\']' % (
+ _reg_tempurl = py.std.re.compile('(["\'])(%s:\/\/[^"\'\s#]*)(["\'#])' % (
TEMPLINK_PROTO,))
def replace_dirpath(self, dirpath, stoponerrors=True):
""" replace temporary links in all html files in dirpath and below """
@@ -88,16 +96,19 @@ class TempLinker(object):
match = self._reg_tempurl.search(html)
if not match:
break
- tempurl = match.group(1)
+ tempurl = match.group(2)
+ pre = match.group(1)
+ post = match.group(3)
try:
- html = html.replace('"' + tempurl + '"',
- '"' + self.get_target(tempurl,
- fpath.relto(dirpath)) + '"')
+ html = html.replace(match.group(0), pre +
+ self.get_target(tempurl,
+ fpath.relto(dirpath)) + post)
except KeyError:
if stoponerrors:
raise
- html = html.replace('"' + tempurl + '"',
- '"apigen.notfound://%s"' % (tempurl,))
+ html = html.replace(match.group(0), pre +
+ 'apigen.notfound://%s' % (tempurl,) +
+ post)
fpath.write(html)
diff --git a/py/apigen/source/html.py b/py/apigen/source/html.py
index dbcd7cbc6..a63ac7682 100644
--- a/py/apigen/source/html.py
+++ b/py/apigen/source/html.py
@@ -7,6 +7,10 @@ from py.xml import html, raw
from compiler import ast
import time
from py.__.apigen.source.color import Tokenizer, PythonSchema
+from py.__.apigen.source.browser import parse_path
+
+class CompilationException(Exception):
+ """ raised when something goes wrong while importing a module """
class HtmlEnchanter(object):
def __init__(self, mod):
@@ -59,6 +63,30 @@ def prepare_line(text, tokenizer, encoding):
ret.append(item)
return ret
+def prepare_module(path, tokenizer, encoding):
+ path = py.path.local(path)
+ try:
+ mod = parse_path(path)
+ except:
+ # XXX don't try to catch SystemExit: it's actually raised by one
+ # of the modules in the py lib on import :(
+ exc, e, tb = py.std.sys.exc_info()
+ del tb
+ raise CompilationException('while compiling %s: %s - %s' % (
+ path, e.__class__.__name__, e))
+ lines = [unicode(l, encoding) for l in path.readlines()]
+
+ enchanter = HtmlEnchanter(mod)
+ ret = []
+ for i, line in enumerate(lines):
+ text = enchanter.enchant_row(i + 1, line)
+ if text == ['']:
+ text = [raw(' ')]
+ else:
+ text = prepare_line(text, tokenizer, encoding)
+ ret.append(text)
+ return ret
+
class HTMLDocument(object):
def __init__(self, encoding, tokenizer=None):
self.encoding = encoding
diff --git a/py/apigen/testing/test_apigen_example.py b/py/apigen/testing/test_apigen_example.py
index bea6c247a..7c692cd79 100644
--- a/py/apigen/testing/test_apigen_example.py
+++ b/py/apigen/testing/test_apigen_example.py
@@ -198,12 +198,10 @@ class TestApiPageBuilder(AbstractBuilderTest):
self.apb.build_class_pages(['main.SomeSubClass',
'main.SomeClass'])
self.apb.build_namespace_pages()
- # fake some stuff that would be built from other methods
self.linker.replace_dirpath(self.base, False)
clsfile = self.base.join('api/main.SomeClass.html')
assert clsfile.check()
html = clsfile.read()
- print html
run_string_sequence_test(html, [
'href="../style.css"',
'href="../apigen_style.css"',
@@ -237,7 +235,8 @@ class TestApiPageBuilder(AbstractBuilderTest):
self.spb.build_pages(self.fs_root)
self.linker.replace_dirpath(self.base, False)
funchtml = self.base.join('api/main.SomeClass.html').read()
- assert funchtml.find('href="../source/pkg/someclass.py.html"') > -1
+ print funchtml
+ assert funchtml.find('href="../source/pkg/someclass.py.html#SomeClass"') > -1
_checkhtml(funchtml)
def test_build_namespace_pages(self):
@@ -433,7 +432,8 @@ class TestSourcePageBuilder(AbstractBuilderTest):
assert funcsource.check(file=True)
html = funcsource.read()
print html
- assert ('def func(arg1)') in html
+ assert ('def '
+ 'func(arg1)') in html
def test_build_navigation_root(self):
self.spb.build_pages(self.fs_root)
diff --git a/py/apigen/testing/test_htmlgen.py b/py/apigen/testing/test_htmlgen.py
index 9ca27652c..fc964c42c 100644
--- a/py/apigen/testing/test_htmlgen.py
+++ b/py/apigen/testing/test_htmlgen.py
@@ -165,3 +165,30 @@ def test_get_rel_sourcepath():
assert (htmlgen.get_rel_sourcepath(projpath, py.path.local('')) is
None)
+def test_find_method_origin():
+ class Foo(object):
+ def foo(self):
+ pass
+ class Bar(Foo):
+ def bar(self):
+ pass
+ class Baz(Bar):
+ pass
+ assert htmlgen.find_method_origin(Baz.bar) is Bar
+ assert htmlgen.find_method_origin(Baz.foo) is Foo
+ assert htmlgen.find_method_origin(Bar.bar) is Bar
+ assert htmlgen.find_method_origin(Baz.__init__) is None
+
+def test_find_method_origin_old_style():
+ class Foo:
+ def foo(self):
+ pass
+ class Bar(Foo):
+ def bar(self):
+ pass
+ class Baz(Bar):
+ pass
+ assert htmlgen.find_method_origin(Baz.bar) is Bar
+ assert htmlgen.find_method_origin(Baz.foo) is Foo
+ assert htmlgen.find_method_origin(Bar.bar) is Bar
+
diff --git a/py/apigen/testing/test_linker.py b/py/apigen/testing/test_linker.py
index 3522841e9..6b345dff6 100644
--- a/py/apigen/testing/test_linker.py
+++ b/py/apigen/testing/test_linker.py
@@ -47,6 +47,13 @@ class TestTempLinker(object):
l.replace_dirpath(temp)
assert bar.read() == 'baz'
+ def test_with_anchor(self):
+ linker = TempLinker()
+ temphref = linker.get_lazyhref('py.path.local', 'LocalPath.join')
+ linker.set_link('py.path.local', 'py/path/local.html')
+ relpath = linker.get_target(temphref)
+ assert relpath == 'py/path/local.html#LocalPath.join'
+
def gen_check(frompath, topath, sep, expected):
result = relpath(frompath, topath, sep=sep)
assert result == expected