diff --git a/py/apigen/htmlgen.py b/py/apigen/htmlgen.py index aeb022d53..60510fb15 100644 --- a/py/apigen/htmlgen.py +++ b/py/apigen/htmlgen.py @@ -12,6 +12,41 @@ sorted = py.builtin.sorted html = py.xml.html raw = py.xml.raw +def deindent(str, linesep=os.linesep): + """ de-indent string + + can be used to de-indent Python docstrings, it de-indents the first + 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) + normalized = [] + deindent = None + normalized.append(lines[0].strip()) + for line in lines[1:]: + if not line.strip(): + normalized.append('') + else: + line = line.rstrip() + line = line.replace('\t', ' ') + indent = 0 + for c in line: + if c != ' ': + break + indent += 1 + 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: + ret.append(line) + else: + ret.append(line[deindent:]) + return '%s\n' % (linesep.join(ret),) + # HTML related stuff class H(html): class Content(html.div): @@ -50,8 +85,9 @@ class H(html): class ParameterDescription(html.div): pass - class Docstring(html.div): - style = html.Style(white_space='pre', min_height='3em') + class Docstring(html.pre): + #style = html.Style(white_space='pre', min_height='3em') + pass class Navigation(html.div): style = html.Style(min_height='99%', float='left', margin_top='1.2em', @@ -309,7 +345,9 @@ class ApiPageBuilder(AbstractPageBuilder): """ build the html for a class method """ # XXX we may want to have seperate func = self.dsa.get_obj(dotted_name) - docstring = func.__doc__ + docstring = func.__doc__ + if docstring: + docstring = deindent(docstring) localname = func.__name__ argdesc = get_param_htmldesc(self.linker, func) valuedesc = self.build_callable_signature_description(dotted_name) @@ -343,7 +381,7 @@ class ApiPageBuilder(AbstractPageBuilder): ) snippet = H.FunctionDescription( H.FunctionDef(localname, argdesc), - H.Docstring(docstring or H.em('no docstring available')), + H.Docstring(docstring or '*no docstring available*'), H.div(H.a('show/hide info', href='#', onclick=('showhideel(getnextsibling(this));' @@ -372,6 +410,8 @@ class ApiPageBuilder(AbstractPageBuilder): href=self.linker.get_lazyhref(sourcefile))) docstring = cls.__doc__ + if docstring: + docstring = deindent(docstring) methods = self.dsa.get_class_methods(dotted_name) basehtml = [] bases = self.dsa.get_possible_base_classes(dotted_name) @@ -394,7 +434,7 @@ class ApiPageBuilder(AbstractPageBuilder): snippet = H.ClassDescription( # XXX bases HTML H.ClassDef('%s(' % (clsname,), *basehtml), - H.Docstring(docstring or H.em('no docstring available')), + H.Docstring(docstring or '*no docstring available*'), sourcelink, ) if methods: @@ -413,9 +453,11 @@ class ApiPageBuilder(AbstractPageBuilder): docstring = None else: docstring = obj.__doc__ + if docstring: + docstring = deindent(docstring) snippet = H.NamespaceDescription( H.NamespaceDef(namespace_dotted_name), - H.Docstring(docstring or H.em('no docstring available')) + H.Docstring(docstring or '*no docstring available*') ) for dotted_name in sorted(item_dotted_names): itemname = dotted_name.split('.')[-1] diff --git a/py/apigen/testing/test_apigen_functional.py b/py/apigen/testing/test_apigen_functional.py index 7c57c1ea9..19cb24e58 100644 --- a/py/apigen/testing/test_apigen_functional.py +++ b/py/apigen/testing/test_apigen_functional.py @@ -69,6 +69,13 @@ def setup_fs_project(name): assert pak.main.sub.func(20) is None s = pak.main.func(pak.main.SomeTestClass, 10) assert isinstance(s, pak.main.SomeTestClass) + + # some nice things to confuse the tracer/storage + source = py.code.Source('''\ + pak.main.sub.func(10) + ''') + c = compile(str(source), '', 'exec') + exec c in globals() """)) return temp, 'pak' diff --git a/py/apigen/testing/test_htmlgen.py b/py/apigen/testing/test_htmlgen.py index 25cc466b4..a6d09cc74 100644 --- a/py/apigen/testing/test_htmlgen.py +++ b/py/apigen/testing/test_htmlgen.py @@ -43,3 +43,12 @@ def test_source_dirs_files(): assert dirnames == ['sub'] assert filenames == ['file1.py', 'file3.c'] +def test_deindent(): + assert htmlgen.deindent('foo\n\n bar\n ') == 'foo\n\nbar\n' + assert htmlgen.deindent(' foo\n\n bar\n ') == 'foo\n\nbar\n' + assert htmlgen.deindent('foo\n\n bar\n baz') == 'foo\n\nbar\nbaz\n' + assert htmlgen.deindent(' foo\n\n bar\n baz\n') == ( + 'foo\n\nbar\n baz\n') + assert htmlgen.deindent('foo\n\n bar\n baz\n') == ( + 'foo\n\n bar\nbaz\n') + diff --git a/py/apigen/tracer/description.py b/py/apigen/tracer/description.py index 076a4b188..3a7b677cd 100644 --- a/py/apigen/tracer/description.py +++ b/py/apigen/tracer/description.py @@ -19,7 +19,10 @@ class CallFrame(object): self.filename = frame.code.raw.co_filename self.lineno = frame.lineno self.firstlineno = frame.code.firstlineno - self.source = getsource(frame.code.raw) + try: + self.source = getsource(frame.code.raw) + except IOError: + self.source = "could not get to source" def _getval(self): return (self.filename, self.lineno)