diff --git a/py/apigen/apigen.py b/py/apigen/apigen.py index 539c69c52..3990236d4 100644 --- a/py/apigen/apigen.py +++ b/py/apigen/apigen.py @@ -10,11 +10,12 @@ import sys from py.__.apigen import htmlgen from py.__.apigen import linker from py.__.apigen import project +from py.__.apigen.tracer.docstorage import pkg_to_dict def get_documentable_items(pkgdir): sys.path.insert(0, str(pkgdir.dirpath())) rootmod = __import__(pkgdir.basename) - return rootmod + return pkg_to_dict(rootmod) def build(pkgdir, dsa): l = linker.Linker() diff --git a/py/apigen/htmlgen.py b/py/apigen/htmlgen.py index 60510fb15..3e67b6e06 100644 --- a/py/apigen/htmlgen.py +++ b/py/apigen/htmlgen.py @@ -19,26 +19,23 @@ def deindent(str, linesep=os.linesep): line to the side always, and determines the indentation of the rest of the text by taking that of the least indented (filled) line """ - lines = str.split(linesep) + lines = str.strip().split(linesep) normalized = [] deindent = None normalized.append(lines[0].strip()) + # replace tabs with spaces, empty lines that contain spaces only, and + # find out what the smallest indentation is for line in lines[1:]: - if not line.strip(): + line = line.replace('\t', ' ') + stripped = line.strip() + if not stripped: normalized.append('') else: - line = line.rstrip() - line = line.replace('\t', ' ') - indent = 0 - for c in line: - if c != ' ': - break - indent += 1 + rstripped = line.rstrip() + indent = len(rstripped) - len(stripped) if deindent is None or indent < deindent: deindent = indent normalized.append(line) - while normalized[-1] == '': - normalized.pop() ret = [normalized[0]] for line in normalized[1:]: if not line: @@ -182,10 +179,12 @@ def wrap_page(project, title, contentel, navel, outputpath, stylesheeturl, stylesheeturl=stylesheeturl, scripturls=scripturls) page.set_content(contentel) here = py.magic.autopath().dirpath() - style = here.join('style.css').read() - outputpath.join('style.css').write(style) - apijs = here.join('api.js').read() - outputpath.join('api.js').write(apijs) + style = here.join(stylesheeturl.split('/')[-1]).read() + outputpath.join(stylesheeturl.split('/')[-1]).write(style) + for spath in scripturls: + sname = spath.split('/')[-1] + sdata = here.join(sname).read() + outputpath.join(sname).write(sdata) return page # the PageBuilder classes take care of producing the docs (using the stuff @@ -281,6 +280,7 @@ 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 @@ -380,7 +380,7 @@ class ApiPageBuilder(AbstractPageBuilder): csdiv, ) snippet = H.FunctionDescription( - H.FunctionDef(localname, argdesc), + H.FunctionDef('def %s' % (localname,), argdesc), H.Docstring(docstring or '*no docstring available*'), H.div(H.a('show/hide info', href='#', @@ -684,7 +684,7 @@ class ApiPageBuilder(AbstractPageBuilder): call_site[0].filename, call_site[0].lineno + 1), href='#', onclick="showhideel(getnextsibling(this)); return false;"), - H.div(tbtag, style='display: none') + H.div(tbtag, style='display: none', class_='callstackitem'), ) return tag diff --git a/py/apigen/style.css b/py/apigen/style.css index 02e5ea0c8..c0c70f27a 100644 --- a/py/apigen/style.css +++ b/py/apigen/style.css @@ -42,12 +42,15 @@ font-weight: bold; } -body, div, p, h1, h2, h3, h4 { - font-family: Trebuchet MS, Verdana, Arial; +body { background-color: #FFE; color: black; } +body, div, p, h1, h2, h3, h4 { + font-family: Trebuchet MS, Verdana, Arial; +} + a { color: #006; text-decoration: none; @@ -107,5 +110,11 @@ a:hover { border: 1px solid black; color: black; padding: 1em; + background-color: white; +} + +.callstackitem { + border: 1px solid black; + margin-bottom: 1em; } diff --git a/py/apigen/testing/test_apigen_functional.py b/py/apigen/testing/test_apigen_functional.py index 19cb24e58..12efe459a 100644 --- a/py/apigen/testing/test_apigen_functional.py +++ b/py/apigen/testing/test_apigen_functional.py @@ -47,6 +47,7 @@ def setup_fs_project(name): 'main.SomeTestClass': ('./sometestclass.py', 'SomeTestClass'), 'main.SomeTestSubClass': ('./sometestsubclass.py', 'SomeTestSubClass'), + 'somenamespace': ('./somenamespace.py', '*'), }) """)) temp.ensure('pak/test/test_pak.py').write(py.code.Source("""\ @@ -82,9 +83,9 @@ def setup_fs_project(name): def test_get_documentable_items(): fs_root, package_name = setup_fs_project('test_get_documentable_items') documentable = apigen.get_documentable_items(fs_root.join(package_name)) - assert sorted(documentable.__package__.exportdefs.keys()) == [ + assert sorted(documentable.keys()) == [ 'main.SomeTestClass', 'main.SomeTestSubClass', 'main.func', - 'main.sub.func'] + 'main.sub.func', 'somenamespace.baz', 'somenamespace.foo'] def test_apigen_functional(): fs_root, package_name = setup_fs_project('test_apigen_functional') diff --git a/py/apigen/tracer/docstorage.py b/py/apigen/tracer/docstorage.py index 47ba39d61..194bf1b7a 100644 --- a/py/apigen/tracer/docstorage.py +++ b/py/apigen/tracer/docstorage.py @@ -15,6 +15,44 @@ from py.__.apigen.tracer import model sorted = py.builtin.sorted +def pkg_to_dict(module): + defs = module.__package__.exportdefs + d = {} + for key, value in defs.iteritems(): + chain = key.split('.') + base = module + for elem in chain: + base = getattr(base, elem) + if value[1] == '*': + d.update(get_star_import_tree(base, key)) + else: + d[key] = base + return d + +def get_star_import_tree(module, modname): + """ deal with '*' entries in an initpkg situation """ + ret = {} + modpath = py.path.local(inspect.getsourcefile(module)) + pkgpath = module.__package__.getpath() + for objname in dir(module): + if objname.startswith('_'): + continue # also skip __*__ attributes + obj = getattr(module, objname) + if (isinstance(obj, types.ClassType) or + isinstance(obj, types.ObjectType)): + try: + sourcefile_object = py.path.local( + inspect.getsourcefile(obj)) + except TypeError: + continue + else: + if sourcefile_object.strpath != modpath.strpath: + # not in this package + continue + dotted_name = '%s.%s' % (modname, objname) + ret[dotted_name] = obj + return ret + class DocStorage(object): """ Class storing info about API """ @@ -117,45 +155,10 @@ class DocStorage(object): def from_pkg(self, module, keep_frames=False): self.module = module - defs = module.__package__.exportdefs - d = {} - for key, value in defs.iteritems(): - chain = key.split('.') - base = module - for elem in chain: - base = getattr(base, elem) - if value[1] == '*': - d.update(self.get_star_import_tree(base, key)) - else: - d[key] = base - self.from_dict(d, keep_frames) + self.from_dict(pkg_to_dict(module), keep_frames) # XXX return self - def get_star_import_tree(self, module, modname): - """ deal with '*' entries in an initpkg situation """ - ret = {} - modpath = py.path.local(inspect.getsourcefile(module)) - pkgpath = module.__package__.getpath() - for objname in dir(module): - if objname.startswith('_'): - continue # also skip __*__ attributes - obj = getattr(module, objname) - if (isinstance(obj, types.ClassType) or - isinstance(obj, types.ObjectType)): - try: - sourcefile_object = py.path.local( - inspect.getsourcefile(obj)) - except TypeError: - continue - else: - if sourcefile_object.strpath != modpath.strpath: - # not in this package - continue - dotted_name = '%s.%s' % (modname, objname) - ret[dotted_name] = obj - return ret - def from_module(self, func): raise NotImplementedError("From module") diff --git a/py/apigen/tracer/testing/test_docgen.py b/py/apigen/tracer/testing/test_docgen.py index 89e2236cb..28783ac5e 100644 --- a/py/apigen/tracer/testing/test_docgen.py +++ b/py/apigen/tracer/testing/test_docgen.py @@ -6,8 +6,9 @@ import py import sys #try: -from py.__.apigen.tracer.tracer import DocStorage, Tracer -from py.__.apigen.tracer.docstorage import DocStorageAccessor +from py.__.apigen.tracer.tracer import Tracer +from py.__.apigen.tracer.docstorage import DocStorageAccessor, DocStorage, \ + get_star_import_tree, pkg_to_dict from py.__.apigen.tracer.testing.runtest import cut_pyc from py.__.apigen.tracer.description import FunctionDesc from py.__.apigen.tracer import model @@ -426,7 +427,7 @@ def setup_pkg_docstorage(): def test_get_initpkg_star_items(): pkg, ds = setup_pkg_docstorage() - sit = ds.get_star_import_tree(pkg.other, 'pkg.other') + sit = get_star_import_tree(pkg.other, 'pkg.other') assert sorted(sit.keys()) == ['pkg.other.baz', 'pkg.other.foo'] t = Tracer(ds) t.start_tracing() @@ -446,3 +447,11 @@ def test_get_initpkg_star_items(): assert isinstance(cell, model.SomeInstance) assert cell.classdef.cls is desc.pyobj +def test_pkg_to_dict(): + pkg, ds = setup_pkg_docstorage() + assert sorted(pkg_to_dict(pkg).keys()) == ['main.SomeClass', + 'main.SomeSubClass', + 'main.sub.func', + 'other.baz', + 'other.foo'] +