[svn r41620] Re-added anchors to the (full) source files, and using them from the API
documentation. --HG-- branch : trunk
This commit is contained in:
parent
9466961ee4
commit
cd6471e71f
|
@ -61,6 +61,7 @@ class H(html):
|
||||||
infoid = 'info_%s' % (localname.replace('.', '_dot_'),)
|
infoid = 'info_%s' % (localname.replace('.', '_dot_'),)
|
||||||
docstringid = 'docstring_%s' % (localname.replace('.', '_dot_'),)
|
docstringid = 'docstring_%s' % (localname.replace('.', '_dot_'),)
|
||||||
fd = H.FunctionDef(localname, argdesc,
|
fd = H.FunctionDef(localname, argdesc,
|
||||||
|
title='click to view details',
|
||||||
onclick=('showhideel('
|
onclick=('showhideel('
|
||||||
'document.getElementById("%s")); '
|
'document.getElementById("%s")); '
|
||||||
% (infoid,)))
|
% (infoid,)))
|
||||||
|
|
|
@ -18,6 +18,25 @@ raw = py.xml.raw
|
||||||
|
|
||||||
REDUCE_CALLSITES = True
|
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):
|
def is_navigateable(name):
|
||||||
return (not is_private(name) and name != '__doc__')
|
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))
|
source_html.prepare_line([line], tokenizer, enc))
|
||||||
except py.error.ENOENT:
|
except py.error.ENOENT:
|
||||||
# error reading source code, giving up
|
# error reading source code, giving up
|
||||||
snippet = org
|
snippet = codelines
|
||||||
break
|
break
|
||||||
return snippet
|
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 = {}
|
_get_obj_cache = {}
|
||||||
def get_obj(dsa, pkg, dotted_name):
|
def get_obj(dsa, pkg, dotted_name):
|
||||||
full_dotted_name = '%s.%s' % (pkg.__name__, 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
|
# XXX two reads of the same file here... not very bad (disk caches
|
||||||
# and such) but also not very nice...
|
# and such) but also not very nice...
|
||||||
enc = source_html.get_module_encoding(fspath.strpath)
|
enc = source_html.get_module_encoding(fspath.strpath)
|
||||||
source = fspath.read()
|
try:
|
||||||
sep = get_linesep(source)
|
colored = [enumerate_and_color_module(fspath, enc)]
|
||||||
colored = [enumerate_and_color(source.split(sep), 0, 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)
|
tag = H.PythonSource(colored)
|
||||||
nav = self.build_navigation(fspath)
|
nav = self.build_navigation(fspath)
|
||||||
return tag, nav
|
return tag, nav
|
||||||
|
@ -316,7 +349,7 @@ class SourcePageBuilder(AbstractPageBuilder):
|
||||||
if fspath.check(ext='.py'):
|
if fspath.check(ext='.py'):
|
||||||
try:
|
try:
|
||||||
tag, nav = self.build_python_page(fspath)
|
tag, nav = self.build_python_page(fspath)
|
||||||
except (KeyboardInterrupt, SystemError):
|
except (KeyboardInterrupt, SystemExit):
|
||||||
raise
|
raise
|
||||||
except: # XXX strange stuff going wrong at times... need to fix
|
except: # XXX strange stuff going wrong at times... need to fix
|
||||||
raise
|
raise
|
||||||
|
@ -398,8 +431,8 @@ class ApiPageBuilder(AbstractPageBuilder):
|
||||||
relpath = get_rel_sourcepath(self.projroot, sourcefile, sourcefile)
|
relpath = get_rel_sourcepath(self.projroot, sourcefile, sourcefile)
|
||||||
text = 'source: %s' % (relpath,)
|
text = 'source: %s' % (relpath,)
|
||||||
if is_in_pkg:
|
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)
|
csource = H.SourceSnippet(text, href, colored)
|
||||||
cslinks = self.build_callsites(dotted_name)
|
cslinks = self.build_callsites(dotted_name)
|
||||||
snippet = H.FunctionDescription(localname, argdesc, docstring,
|
snippet = H.FunctionDescription(localname, argdesc, docstring,
|
||||||
|
@ -432,7 +465,8 @@ class ApiPageBuilder(AbstractPageBuilder):
|
||||||
if sourcefile[-1] in ['o', 'c']:
|
if sourcefile[-1] in ['o', 'c']:
|
||||||
sourcefile = sourcefile[:-1]
|
sourcefile = sourcefile[:-1]
|
||||||
sourcelink = H.div(H.a('view source',
|
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(
|
snippet = H.ClassDescription(
|
||||||
# XXX bases HTML
|
# XXX bases HTML
|
||||||
|
@ -651,6 +685,10 @@ class ApiPageBuilder(AbstractPageBuilder):
|
||||||
return lst
|
return lst
|
||||||
name, _desc_type, is_degenerated = data
|
name, _desc_type, is_degenerated = data
|
||||||
if not is_degenerated:
|
if not is_degenerated:
|
||||||
|
try:
|
||||||
|
obj = self.dsa.get_obj(name)
|
||||||
|
except KeyError:
|
||||||
|
obj = None
|
||||||
linktarget = self.linker.get_lazyhref(name)
|
linktarget = self.linker.get_lazyhref(name)
|
||||||
lst.append(H.a(str(_type), href=linktarget))
|
lst.append(H.a(str(_type), href=linktarget))
|
||||||
else:
|
else:
|
||||||
|
@ -695,6 +733,7 @@ class ApiPageBuilder(AbstractPageBuilder):
|
||||||
_reg_source = py.std.re.compile(r'([^>]*)<(.*)>')
|
_reg_source = py.std.re.compile(r'([^>]*)<(.*)>')
|
||||||
def gen_traceback(self, dotted_name, call_site):
|
def gen_traceback(self, dotted_name, call_site):
|
||||||
tbtag = H.CallStackDescription()
|
tbtag = H.CallStackDescription()
|
||||||
|
obj = self.dsa.get_obj(dotted_name)
|
||||||
for frame in call_site:
|
for frame in call_site:
|
||||||
lineno = frame.lineno - frame.firstlineno
|
lineno = frame.lineno - frame.firstlineno
|
||||||
source = frame.source
|
source = frame.source
|
||||||
|
@ -772,3 +811,20 @@ class ApiPageBuilder(AbstractPageBuilder):
|
||||||
self._revcache[dotted_name] = rev
|
self._revcache[dotted_name] = rev
|
||||||
return 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
|
||||||
|
|
||||||
|
|
|
@ -63,8 +63,11 @@ class TempLinker(object):
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self._linkid2target = {}
|
self._linkid2target = {}
|
||||||
|
|
||||||
def get_lazyhref(self, linkid):
|
def get_lazyhref(self, linkid, anchor=None):
|
||||||
return '%s://%s' % (TEMPLINK_PROTO, linkid)
|
href = '%s://%s' % (TEMPLINK_PROTO, linkid)
|
||||||
|
if anchor:
|
||||||
|
href += '#' + anchor
|
||||||
|
return href
|
||||||
|
|
||||||
def set_link(self, linkid, target):
|
def set_link(self, linkid, target):
|
||||||
assert linkid not in self._linkid2target
|
assert linkid not in self._linkid2target
|
||||||
|
@ -72,13 +75,18 @@ class TempLinker(object):
|
||||||
|
|
||||||
def get_target(self, tempurl, fromlocation=None):
|
def get_target(self, tempurl, fromlocation=None):
|
||||||
assert tempurl.startswith('%s://' % (TEMPLINK_PROTO,))
|
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]
|
linktarget = self._linkid2target[linkid]
|
||||||
if fromlocation is not None:
|
if fromlocation is not None:
|
||||||
linktarget = relpath(fromlocation, linktarget)
|
linktarget = relpath(fromlocation, linktarget)
|
||||||
|
if anchor is not None:
|
||||||
|
linktarget += '#' + anchor
|
||||||
return linktarget
|
return linktarget
|
||||||
|
|
||||||
_reg_tempurl = py.std.re.compile('["\'](%s:\/\/[^"\s]*)["\']' % (
|
_reg_tempurl = py.std.re.compile('(["\'])(%s:\/\/[^"\'\s#]*)(["\'#])' % (
|
||||||
TEMPLINK_PROTO,))
|
TEMPLINK_PROTO,))
|
||||||
def replace_dirpath(self, dirpath, stoponerrors=True):
|
def replace_dirpath(self, dirpath, stoponerrors=True):
|
||||||
""" replace temporary links in all html files in dirpath and below """
|
""" replace temporary links in all html files in dirpath and below """
|
||||||
|
@ -88,16 +96,19 @@ class TempLinker(object):
|
||||||
match = self._reg_tempurl.search(html)
|
match = self._reg_tempurl.search(html)
|
||||||
if not match:
|
if not match:
|
||||||
break
|
break
|
||||||
tempurl = match.group(1)
|
tempurl = match.group(2)
|
||||||
|
pre = match.group(1)
|
||||||
|
post = match.group(3)
|
||||||
try:
|
try:
|
||||||
html = html.replace('"' + tempurl + '"',
|
html = html.replace(match.group(0), pre +
|
||||||
'"' + self.get_target(tempurl,
|
self.get_target(tempurl,
|
||||||
fpath.relto(dirpath)) + '"')
|
fpath.relto(dirpath)) + post)
|
||||||
except KeyError:
|
except KeyError:
|
||||||
if stoponerrors:
|
if stoponerrors:
|
||||||
raise
|
raise
|
||||||
html = html.replace('"' + tempurl + '"',
|
html = html.replace(match.group(0), pre +
|
||||||
'"apigen.notfound://%s"' % (tempurl,))
|
'apigen.notfound://%s' % (tempurl,) +
|
||||||
|
post)
|
||||||
fpath.write(html)
|
fpath.write(html)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -7,6 +7,10 @@ from py.xml import html, raw
|
||||||
from compiler import ast
|
from compiler import ast
|
||||||
import time
|
import time
|
||||||
from py.__.apigen.source.color import Tokenizer, PythonSchema
|
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):
|
class HtmlEnchanter(object):
|
||||||
def __init__(self, mod):
|
def __init__(self, mod):
|
||||||
|
@ -59,6 +63,30 @@ def prepare_line(text, tokenizer, encoding):
|
||||||
ret.append(item)
|
ret.append(item)
|
||||||
return ret
|
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):
|
class HTMLDocument(object):
|
||||||
def __init__(self, encoding, tokenizer=None):
|
def __init__(self, encoding, tokenizer=None):
|
||||||
self.encoding = encoding
|
self.encoding = encoding
|
||||||
|
|
|
@ -198,12 +198,10 @@ class TestApiPageBuilder(AbstractBuilderTest):
|
||||||
self.apb.build_class_pages(['main.SomeSubClass',
|
self.apb.build_class_pages(['main.SomeSubClass',
|
||||||
'main.SomeClass'])
|
'main.SomeClass'])
|
||||||
self.apb.build_namespace_pages()
|
self.apb.build_namespace_pages()
|
||||||
# fake some stuff that would be built from other methods
|
|
||||||
self.linker.replace_dirpath(self.base, False)
|
self.linker.replace_dirpath(self.base, False)
|
||||||
clsfile = self.base.join('api/main.SomeClass.html')
|
clsfile = self.base.join('api/main.SomeClass.html')
|
||||||
assert clsfile.check()
|
assert clsfile.check()
|
||||||
html = clsfile.read()
|
html = clsfile.read()
|
||||||
print html
|
|
||||||
run_string_sequence_test(html, [
|
run_string_sequence_test(html, [
|
||||||
'href="../style.css"',
|
'href="../style.css"',
|
||||||
'href="../apigen_style.css"',
|
'href="../apigen_style.css"',
|
||||||
|
@ -237,7 +235,8 @@ class TestApiPageBuilder(AbstractBuilderTest):
|
||||||
self.spb.build_pages(self.fs_root)
|
self.spb.build_pages(self.fs_root)
|
||||||
self.linker.replace_dirpath(self.base, False)
|
self.linker.replace_dirpath(self.base, False)
|
||||||
funchtml = self.base.join('api/main.SomeClass.html').read()
|
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)
|
_checkhtml(funchtml)
|
||||||
|
|
||||||
def test_build_namespace_pages(self):
|
def test_build_namespace_pages(self):
|
||||||
|
@ -433,7 +432,8 @@ 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> func(arg1)') in html
|
assert ('<span class="alt_keyword">def</span> '
|
||||||
|
'<a href="#func" name="func">func</a>(arg1)') in html
|
||||||
|
|
||||||
def test_build_navigation_root(self):
|
def test_build_navigation_root(self):
|
||||||
self.spb.build_pages(self.fs_root)
|
self.spb.build_pages(self.fs_root)
|
||||||
|
|
|
@ -165,3 +165,30 @@ def test_get_rel_sourcepath():
|
||||||
assert (htmlgen.get_rel_sourcepath(projpath, py.path.local('<string>')) is
|
assert (htmlgen.get_rel_sourcepath(projpath, py.path.local('<string>')) is
|
||||||
None)
|
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
|
||||||
|
|
||||||
|
|
|
@ -47,6 +47,13 @@ class TestTempLinker(object):
|
||||||
l.replace_dirpath(temp)
|
l.replace_dirpath(temp)
|
||||||
assert bar.read() == '<a href="baz.html">baz</a>'
|
assert bar.read() == '<a href="baz.html">baz</a>'
|
||||||
|
|
||||||
|
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):
|
def gen_check(frompath, topath, sep, expected):
|
||||||
result = relpath(frompath, topath, sep=sep)
|
result = relpath(frompath, topath, sep=sep)
|
||||||
assert result == expected
|
assert result == expected
|
||||||
|
|
Loading…
Reference in New Issue