diff --git a/py/apigen/apigen.py b/py/apigen/apigen.py index d2953dcd3..9a2ab7dbd 100644 --- a/py/apigen/apigen.py +++ b/py/apigen/apigen.py @@ -34,7 +34,7 @@ def get_documentable_items(pkgdir): def build(pkgdir, dsa, capture): # create a linker (link database) for cross-linking - l = linker.Linker() + l = linker.TempLinker() # create a project.Project instance to contain the LayoutPage instances proj = project.Project() @@ -52,27 +52,26 @@ def build(pkgdir, dsa, capture): # and build it apb = htmlgen.ApiPageBuilder(targetdir, l, dsa, pkgdir, namespace_tree, - capture, LayoutPage) - spb = htmlgen.SourcePageBuilder(targetdir, l, pkgdir, capture, LayoutPage) - - capture.err.writeorg('preparing namespace pages\n') - ns_data = apb.prepare_namespace_pages() - capture.err.writeorg('preparing class pages\n') - class_names = dsa.get_class_names() - class_data = apb.prepare_class_pages(class_names) - capture.err.writeorg('preparing function pages\n') - function_names = dsa.get_function_names() - func_data = apb.prepare_function_pages(function_names) - capture.err.writeorg('preparing source pages\n') - source_data = spb.prepare_pages(pkgdir) + proj, capture, LayoutPage) + spb = htmlgen.SourcePageBuilder(targetdir, l, pkgdir, proj, capture, + LayoutPage) capture.err.writeorg('building namespace pages\n') - apb.build_namespace_pages(ns_data, proj) + apb.build_namespace_pages() + capture.err.writeorg('building class pages\n') - apb.build_class_pages(class_data, proj) + class_names = dsa.get_class_names() + apb.build_class_pages(class_names) + capture.err.writeorg('building function pages\n') - apb.build_function_pages(func_data, proj) + function_names = dsa.get_function_names() + apb.build_function_pages(function_names) + capture.err.writeorg('building source pages\n') - spb.build_pages(source_data, proj, pkgdir) + spb.build_pages(pkgdir) + + capture.err.writeorg('replacing temporary links\n') + l.replace_dirpath(targetdir) + capture.err.writeorg('done building documentation\n') diff --git a/py/apigen/html.py b/py/apigen/html.py index cfb13eb73..dbd9d733b 100644 --- a/py/apigen/html.py +++ b/py/apigen/html.py @@ -128,6 +128,8 @@ class H(html): super(H.SourceCode, self).__init__(lntable, ltable) def add_line(self, lineno, els): + if els == []: + els = [u'\xa0'] self.linenotbody.append(H.tr(H.td(lineno, class_='lineno'))) self.linetbody.append(H.tr(H.td(class_='code', *els))) diff --git a/py/apigen/htmlgen.py b/py/apigen/htmlgen.py index fbd938bb5..d9048b7ca 100644 --- a/py/apigen/htmlgen.py +++ b/py/apigen/htmlgen.py @@ -155,23 +155,26 @@ def get_obj(pkg, dotted_name): class AbstractPageBuilder(object): pageclass = LayoutPage - def write_page(self, title, reltargetpath, project, tag, nav): + def write_page(self, title, reltargetpath, tag, nav): targetpath = self.base.join(reltargetpath) relbase= relpath('%s%s' % (targetpath.dirpath(), targetpath.sep), self.base.strpath + '/') - page = wrap_page(project, title, tag, nav, relbase, self.base, + page = wrap_page(self.project, title, tag, nav, relbase, self.base, self.pageclass) - content = self.linker.call_withbase(reltargetpath, page.unicode) + # we write the page with _temporary_ hrefs here, need to be replaced + # from the TempLinker later + content = 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, capture=None, + def __init__(self, base, linker, projroot, project, capture=None, pageclass=LayoutPage): self.base = base self.linker = linker self.projroot = projroot + self.project = project self.capture = capture self.pageclass = pageclass @@ -240,13 +243,11 @@ class SourcePageBuilder(AbstractPageBuilder): try: tag = H.NonPythonSource(unicode(fspath.read(), 'utf-8')) except UnicodeError: - # XXX we should fix non-ascii support here!! tag = H.NonPythonSource('no source available (binary file?)') nav = self.build_navigation(fspath) return tag, nav - def prepare_pages(self, base): - passed = [] + def build_pages(self, base): for fspath in [base] + list(base.visit()): if fspath.ext in ['.pyc', '.pyo']: continue @@ -264,38 +265,36 @@ class SourcePageBuilder(AbstractPageBuilder): reloutputpath = reloutputpath.replace(os.path.sep, '/') outputpath = self.base.join(reloutputpath) self.linker.set_link(str(fspath), reloutputpath) - passed.append((fspath, outputpath)) - return passed + self.build_page(fspath, outputpath, base) - def build_pages(self, data, project, base): + def build_page(self, fspath, outputpath, 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 - raise - 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: + 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 + raise + 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) - title = 'sources for %s' % (fspath.basename,) - reltargetpath = outputpath.relto(self.base).replace(os.path.sep, - '/') - self.write_page(title, reltargetpath, project, tag, nav) + 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).replace(os.path.sep, + '/') + self.write_page(title, reltargetpath, tag, nav) class ApiPageBuilder(AbstractPageBuilder): """ builds the html for an api docs page """ - def __init__(self, base, linker, dsa, projroot, namespace_tree, + def __init__(self, base, linker, dsa, projroot, namespace_tree, project, capture=None, pageclass=LayoutPage): self.base = base self.linker = linker @@ -303,6 +302,7 @@ class ApiPageBuilder(AbstractPageBuilder): self.projroot = projroot self.projpath = py.path.local(projroot) self.namespace_tree = namespace_tree + self.project = project self.capture = capture self.pageclass = pageclass @@ -440,11 +440,9 @@ class ApiPageBuilder(AbstractPageBuilder): ) return snippet - def prepare_class_pages(self, classes_dotted_names): + def build_class_pages(self, classes_dotted_names): passed = [] for dotted_name in sorted(classes_dotted_names): - #if self.capture: - # self.capture.err.writeorg('preparing: %s\n' % (dotted_name,)) parent_dotted_name, _ = split_of_last_part(dotted_name) try: sibling_dotted_names = self.namespace_tree[parent_dotted_name] @@ -455,42 +453,13 @@ class ApiPageBuilder(AbstractPageBuilder): nav = self.build_navigation(dotted_name, False) reltargetpath = "api/%s.html" % (dotted_name,) self.linker.set_link(dotted_name, reltargetpath) - passed.append((dotted_name, tag, nav, reltargetpath)) + title = 'api documentation for %s' % (dotted_name,) + self.write_page(title, reltargetpath, tag, nav) return passed - 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: - #if self.capture: - # self.capture.err.writeorg('building: %s\n' % (dotted_name,)) - title = 'api documentation for %s' % (dotted_name,) - self.write_page(title, reltargetpath, project, tag, nav) - - def prepare_method_pages(self, method_dotted_names): - # XXX note that even though these pages are still built, there's no nav - # pointing to them anymore... + def build_function_pages(self, method_dotted_names): passed = [] for dotted_name in sorted(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 = self.namespace_tree[module_dotted_name] - tag = self.build_callable_view(dotted_name) - nav = self.build_navigation(dotted_name, False) - 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, method_dotted_names): - passed = [] - for dotted_name in sorted(method_dotted_names): - #if self.capture: - # self.capture.err.writeorg('preparing: %s\n' % (dotted_name,)) # XXX should we create a build_function_view instead? parent_dotted_name, _ = split_of_last_part(dotted_name) sibling_dotted_names = self.namespace_tree[parent_dotted_name] @@ -498,17 +467,11 @@ class ApiPageBuilder(AbstractPageBuilder): nav = self.build_navigation(dotted_name, False) reltargetpath = "api/%s.html" % (dotted_name,) self.linker.set_link(dotted_name, reltargetpath) - passed.append((dotted_name, tag, nav, reltargetpath)) + title = 'api documentation for %s' % (dotted_name,) + self.write_page(title, reltargetpath, tag, nav) return passed - def build_function_pages(self, data, project): - for dotted_name, tag, nav, reltargetpath in data: - #if self.capture: - # self.capture.err.writeorg('building: %s\n' % (dotted_name,)) - title = 'api documentation for %s' % (dotted_name,) - self.write_page(title, reltargetpath, project, tag, nav) - - def prepare_namespace_pages(self): + def build_namespace_pages(self): passed = [] module_name = self.dsa.get_module_name().split('/')[-1] @@ -517,8 +480,6 @@ class ApiPageBuilder(AbstractPageBuilder): function_names = self.dsa.get_function_names() class_names = self.dsa.get_class_names() for dotted_name in sorted(names): - #if self.capture: - # self.capture.err.writeorg('preparing: %s\n' % (dotted_name,)) if dotted_name in function_names or dotted_name in class_names: continue subitem_dotted_names = self.namespace_tree[dotted_name] @@ -530,17 +491,11 @@ class ApiPageBuilder(AbstractPageBuilder): 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 self.capture: - # self.capture.err.writeorg('building: %s\n' % (dotted_name,)) 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) + self.write_page(title, reltargetpath, tag, nav) + return passed def build_navigation(self, dotted_name, build_children=True): navitems = [] diff --git a/py/apigen/linker.py b/py/apigen/linker.py index 1fd64cb66..6930441b3 100644 --- a/py/apigen/linker.py +++ b/py/apigen/linker.py @@ -42,6 +42,57 @@ class Linker(object): finally: del self.fromlocation +class TempLinker(object): + """ performs a similar role to the Linker, but with a different approach + + instead of returning 'lazy' hrefs, this returns a simple URL-style + string + + the 'temporary urls' are replaced on the filesystem after building the + files, so that means even though a second pass is still required, + things don't have to be built in-memory (as with the Linker) + """ + fromlocation = None + + def __init__(self): + self._linkid2target = {} + + def get_lazyhref(self, linkid): + return 'apigen.linker://%s' % (linkid,) + + def set_link(self, linkid, target): + assert linkid not in self._linkid2target + self._linkid2target[linkid] = target + + def get_target(self, tempurl, fromlocation=None): + linkid = '://'.join(tempurl.split('://')[1:]) + linktarget = self._linkid2target[linkid] + if fromlocation is not None: + linktarget = relpath(fromlocation, linktarget) + return linktarget + + _reg_tempurl = py.std.re.compile('"(apigen.linker:\/\/[^"\s]*)"') + def replace_dirpath(self, dirpath, stoponerrors=True): + """ replace temporary links in all html files in dirpath and below """ + for fpath in dirpath.visit('*.html'): + html = fpath.read() + while 1: + match = self._reg_tempurl.search(html) + if not match: + break + tempurl = match.group(1) + try: + html = html.replace('"' + tempurl + '"', + '"' + self.get_target(tempurl, + fpath.relto(dirpath)) + '"') + except KeyError: + if stoponerrors: + raise + html = html.replace('"' + tempurl + '"', + '"apigen.notfound://%s"' % (tempurl,)) + fpath.write(html) + + def relpath(p1, p2, sep=os.path.sep, back='..', normalize=True): """ create a relative path from p1 to p2 diff --git a/py/apigen/style.css b/py/apigen/style.css index 71ee2631a..65b2bc303 100644 --- a/py/apigen/style.css +++ b/py/apigen/style.css @@ -89,3 +89,10 @@ ul li { margin-bottom: 1em; } +td.lineno { + line-height: 1.1em; +} + +td.code { + line-height: 1.1em; +} diff --git a/py/apigen/testing/test_apigen_example.py b/py/apigen/testing/test_apigen_example.py index 318bea34d..c9714de6f 100644 --- a/py/apigen/testing/test_apigen_example.py +++ b/py/apigen/testing/test_apigen_example.py @@ -1,6 +1,7 @@ +# -*- coding: UTF-8 -*- import py html = py.xml.html -from py.__.apigen.linker import Linker +from py.__.apigen.linker import TempLinker from py.__.apigen.htmlgen import * from py.__.apigen.tracer.docstorage import DocStorage, DocStorageAccessor from py.__.apigen.tracer.tracer import Tracer @@ -96,15 +97,9 @@ class AbstractBuilderTest(object): cls.project = Project() def setup_method(self, meth): - class LinkerForTests(Linker): - def get_target(self, linkid): - try: - return super(LinkerForTests, self).get_target(linkid) - except KeyError: - return 'unknown_link_%s' % (linkid,) self.base = base = py.test.ensuretemp('%s_%s' % ( self.__class__.__name__, meth.im_func.func_name)) - self.linker = linker = LinkerForTests() + self.linker = linker = TempLinker() namespace_tree = create_namespace_tree(['main.sub', 'main.sub.func', 'main.SomeClass', @@ -116,9 +111,10 @@ 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, self.project) self.spb = SourcePageBuilder(base, linker, - self.fs_root.join(self.pkg_name)) + self.fs_root.join(self.pkg_name), + self.project) class TestApiPageBuilder(AbstractBuilderTest): def test_build_callable_view(self): @@ -156,8 +152,7 @@ class TestApiPageBuilder(AbstractBuilderTest): _checkhtmlsnippet(html) def test_build_function_pages(self): - data = self.apb.prepare_function_pages(['main.sub.func']) - self.apb.build_function_pages(data, self.project) + self.apb.build_function_pages(['main.sub.func']) funcfile = self.base.join('api/main.sub.func.html') assert funcfile.check() html = funcfile.read() @@ -169,19 +164,16 @@ class TestApiPageBuilder(AbstractBuilderTest): _checkhtmlsnippet(html) def test_build_class_pages(self): - data = self.apb.prepare_class_pages(['main.SomeClass', - 'main.SomeSubClass']) - self.apb.build_class_pages(data, self.project) + self.apb.build_class_pages(['main.SomeClass', 'main.SomeSubClass']) clsfile = self.base.join('api/main.SomeClass.html') assert clsfile.check() html = clsfile.read() _checkhtml(html) def test_build_class_pages_instance(self): - data = self.apb.prepare_class_pages(['main.SomeClass', - 'main.SomeSubClass', - 'main.SomeInstance']) - self.apb.build_class_pages(data, self.project) + self.apb.build_class_pages(['main.SomeClass', + 'main.SomeSubClass', + 'main.SomeInstance']) clsfile = self.base.join('api/main.SomeInstance.html') assert clsfile.check() html = clsfile.read() @@ -191,13 +183,11 @@ class TestApiPageBuilder(AbstractBuilderTest): ]) def test_build_class_pages_nav_links(self): - data = self.apb.prepare_class_pages(['main.SomeSubClass', - 'main.SomeClass']) - self.apb.prepare_namespace_pages() + 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.set_link('', 'api/index.html') - self.linker.set_link('main', 'api/main.html') - self.apb.build_class_pages(data, self.project) + self.linker.replace_dirpath(self.base, False) clsfile = self.base.join('api/main.SomeClass.html') assert clsfile.check() html = clsfile.read() @@ -217,9 +207,9 @@ class TestApiPageBuilder(AbstractBuilderTest): _checkhtml(html) def test_build_class_pages_base_link(self): - data = self.apb.prepare_class_pages(['main.SomeSubClass', - 'main.SomeClass']) - self.apb.build_class_pages(data, self.project) + self.apb.build_class_pages(['main.SomeSubClass', + 'main.SomeClass']) + self.linker.replace_dirpath(self.base, False) clsfile = self.base.join('api/main.SomeSubClass.html') assert clsfile.check() html = clsfile.read() @@ -231,18 +221,15 @@ class TestApiPageBuilder(AbstractBuilderTest): _checkhtml(html) def test_source_links(self): - data = self.apb.prepare_class_pages(['main.SomeSubClass', - 'main.SomeClass']) - sourcedata = self.spb.prepare_pages(self.fs_root) - self.apb.build_class_pages(data, self.project) - self.spb.build_pages(sourcedata, self.project, self.fs_root) + self.apb.build_class_pages(['main.SomeSubClass', 'main.SomeClass']) + 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 _checkhtml(funchtml) def test_build_namespace_pages(self): - data = self.apb.prepare_namespace_pages() - self.apb.build_namespace_pages(data, self.project) + self.apb.build_namespace_pages() mainfile = self.base.join('api/main.html') assert mainfile.check() html = mainfile.read() @@ -261,8 +248,7 @@ class TestApiPageBuilder(AbstractBuilderTest): _checkhtml(otherhtml) def test_build_namespace_pages_index(self): - data = self.apb.prepare_namespace_pages() - self.apb.build_namespace_pages(data, self.project) + self.apb.build_namespace_pages() pkgfile = self.base.join('api/index.html') assert pkgfile.check() html = pkgfile.read() @@ -270,19 +256,18 @@ class TestApiPageBuilder(AbstractBuilderTest): _checkhtml(html) def test_build_namespace_pages_subnamespace(self): - data = self.apb.prepare_namespace_pages() - self.apb.build_namespace_pages(data, self.project) + self.apb.build_namespace_pages() subfile = self.base.join('api/main.sub.html') assert subfile.check() html = subfile.read() _checkhtml(html) def test_build_function_api_pages_nav(self): - data = self.apb.prepare_function_pages(['main.sub.func']) + self.linker.set_link('main.sub', 'api/main.sub.html') self.linker.set_link('', 'api/index.html') self.linker.set_link('main', 'api/main.html') - self.linker.set_link('main.sub', 'api/main.sub.html') - self.apb.build_function_pages(data, self.project) + self.apb.build_function_pages(['main.sub.func']) + self.linker.replace_dirpath(self.base, False) funcfile = self.base.join('api/main.sub.func.html') html = funcfile.read() print html @@ -295,31 +280,32 @@ class TestApiPageBuilder(AbstractBuilderTest): _checkhtml(html) def test_build_function_navigation(self): - self.apb.prepare_namespace_pages() - self.apb.prepare_function_pages(['main.sub.func']) - self.apb.prepare_class_pages(['main.SomeClass', - 'main.SomeSubClass', - 'main.SomeInstance']) - nav = self.apb.build_navigation('main.sub.func', False) - html = nav.unicode(indent=0) - print html.encode('UTF-8') - assert (u'
' - u'