diff --git a/py/apigen/html.py b/py/apigen/html.py
index d7d836185..1ee0f8f19 100644
--- a/py/apigen/html.py
+++ b/py/apigen/html.py
@@ -47,12 +47,21 @@ class H(html):
super(H.FunctionInfo, self).__init__(
H.Hideable('funcinfo', 'funcinfo', valuedesc, csource,
callstack))
+
+ class PropertyDescription(html.div):
+ def __init__(self, name, value):
+ if type(value) not in [str, unicode]:
+ value = str(value)
+ if len(value) > 100:
+ value = value[:100] + '...'
+ super(H.PropertyDescription, self).__init__(H.strong(name), ': ',
+ H.em(value),
+ class_='property')
class ParameterDescription(html.div):
pass
class Docstring(html.pre):
- style = html.Style(width='100%')
pass
class Navigation(html.div):
diff --git a/py/apigen/htmlgen.py b/py/apigen/htmlgen.py
index 62a0207d6..4c1aedcb8 100644
--- a/py/apigen/htmlgen.py
+++ b/py/apigen/htmlgen.py
@@ -17,6 +17,17 @@ raw = py.xml.raw
def is_navigateable(name):
return (not is_private(name) and name != '__doc__')
+def show_property(name):
+ if not name.startswith('_'):
+ return True
+ if name.startswith('__') and name.endswith('__'):
+ # XXX do we need to skip more manually here?
+ if (name not in dir(object) and
+ name not in ['__doc__', '__dict__', '__name__', '__module__',
+ '__weakref__']):
+ return True
+ return False
+
def deindent(str, linesep='\n'):
""" de-indent string
@@ -351,7 +362,24 @@ class ApiPageBuilder(AbstractPageBuilder):
docstring = cls.__doc__
if docstring:
docstring = deindent(docstring)
- methods = self.dsa.get_class_methods(dotted_name)
+ if not hasattr(cls, '__name__'):
+ clsname = 'instance of %s' % (cls.__class__.__name__,)
+ else:
+ clsname = cls.__name__
+ bases = self.build_bases(dotted_name)
+ properties = self.build_properties(cls)
+ methods = self.build_methods(dotted_name)
+ snippet = H.ClassDescription(
+ # XXX bases HTML
+ H.ClassDef('%s(' % (clsname,), *bases),
+ H.Docstring(docstring or '*no docstring available*'),
+ sourcelink,
+ *(properties+methods)
+ )
+
+ return snippet
+
+ def build_bases(self, dotted_name):
basehtml = []
bases = self.dsa.get_possible_base_classes(dotted_name)
for base in bases:
@@ -366,23 +394,33 @@ class ApiPageBuilder(AbstractPageBuilder):
if basehtml:
basehtml.pop()
basehtml.append('):')
- if not hasattr(cls, '__name__'):
- clsname = 'instance of %s' % (cls.__class__.__name__,)
- else:
- clsname = cls.__name__
- snippet = H.ClassDescription(
- # XXX bases HTML
- H.ClassDef('%s(' % (clsname,), *basehtml),
- H.Docstring(docstring or '*no docstring available*'),
- sourcelink,
- )
+ return basehtml
+
+ def build_properties(self, cls):
+ properties = []
+ for attr in dir(cls):
+ val = getattr(cls, attr)
+ if show_property(attr) and not callable(val):
+ if isinstance(val, property):
+ val = ''
+ properties.append((attr, val))
+ properties.sort(key=lambda a: a[0]) # sort on name
+ ret = []
+ if properties:
+ ret.append(H.h2('properties:'))
+ for name, val in properties:
+ ret.append(H.PropertyDescription(name, val))
+ return ret
+
+ def build_methods(self, dotted_name):
+ ret = []
+ methods = self.dsa.get_class_methods(dotted_name)
if methods:
- snippet.append(H.h2('methods:'))
+ ret.append(H.h2('methods:'))
for method in methods:
- snippet += self.build_callable_view('%s.%s' % (dotted_name,
- method))
- # XXX properties
- return snippet
+ ret += self.build_callable_view('%s.%s' % (dotted_name,
+ method))
+ return ret
def build_namespace_view(self, namespace_dotted_name, item_dotted_names):
""" build the html for a namespace (module) """
diff --git a/py/apigen/layout.py b/py/apigen/layout.py
index 92c6b062b..ef9e012be 100644
--- a/py/apigen/layout.py
+++ b/py/apigen/layout.py
@@ -26,9 +26,17 @@ class LayoutPage(confrest.PyPage):
def fill(self):
super(LayoutPage, self).fill()
- #self.menubar[:] = []
+ self.update_menubar_links(self.menubar)
self.body.insert(0, self.nav)
+ def update_menubar_links(self, node):
+ for item in node:
+ if not isinstance(item, py.xml.Tag):
+ continue
+ if (item.__class__.__name__ == 'a' and hasattr(item.attr, 'href')
+ and not item.attr.href.startswith('http://')):
+ item.attr.href = self.relpath + '../py/doc/' + item.attr.href
+
def setup_scripts_styles(self, copyto=None):
for path, name in self.stylesheets:
if copyto:
diff --git a/py/apigen/style.css b/py/apigen/style.css
index b84c5b5e8..991767005 100644
--- a/py/apigen/style.css
+++ b/py/apigen/style.css
@@ -18,7 +18,6 @@ div.sidebar .selected a {
#content {
border: 0px;
height: 95%;
- width: 100%;
}
ul {
@@ -76,6 +75,10 @@ ul li {
background-color: white;
}
+.property {
+ font-size: 1.2em;
+}
+
.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 8d2def970..17109dd26 100644
--- a/py/apigen/testing/test_apigen_functional.py
+++ b/py/apigen/testing/test_apigen_functional.py
@@ -19,6 +19,7 @@ def setup_fs_project(name):
temp.ensure('pak/sometestclass.py').write(py.code.Source("""\
class SomeTestClass(object):
" docstring sometestclass "
+ someattr = 'somevalue'
def __init__(self, somevar):
self.somevar = somevar
@@ -129,12 +130,10 @@ def test_apigen_functional():
sometestclass_api = apidir.join('main.SomeTestClass.html')
assert sometestclass_api.check(file=True)
html = sometestclass_api.read()
+ print html
assert 'SomeTestClass' in html
- # XXX not linking to method files anymore
- #sometestclass_init_api = apidir.join('main.SomeTestClass.__init__.html')
- #assert sometestclass_init_api.check(file=True)
- #assert sometestclass_init_api.read().find(
- # '__init__') > -1
+ assert 'someattr: somevalue' in html
+
namespace_api = apidir.join('main.html')
assert namespace_api.check(file=True)
html = namespace_api.read()
@@ -156,4 +155,5 @@ def test_apigen_functional():
html = index.read()
print html
assert 'test' in html
+ assert 'href="../../py/doc/home.html"'
diff --git a/py/apigen/testing/test_htmlgen.py b/py/apigen/testing/test_htmlgen.py
index ec3893293..f1953f8d7 100644
--- a/py/apigen/testing/test_htmlgen.py
+++ b/py/apigen/testing/test_htmlgen.py
@@ -55,3 +55,12 @@ def test_enumerate_and_color():
' "bar"\n'
'')
+def test_show_property():
+ assert htmlgen.show_property('foo')
+ assert not htmlgen.show_property('_foo')
+ assert htmlgen.show_property('__foo__')
+ assert not htmlgen.show_property('__doc__')
+ assert not htmlgen.show_property('__dict__')
+ assert not htmlgen.show_property('__name__')
+ assert not htmlgen.show_property('__class__')
+
diff --git a/py/apigen/todo-apigen.txt b/py/apigen/todo-apigen.txt
index 03d4a98cd..7676df8e5 100644
--- a/py/apigen/todo-apigen.txt
+++ b/py/apigen/todo-apigen.txt
@@ -1,5 +1,7 @@
-* format docstrings more nicely (with tests) - DONE I guess
+* format docstrings more nicely (with tests)
+
+ DONE I guess
* have the API function view be as informative as possible
without having to go to the "single method" view
@@ -10,10 +12,12 @@
viewed. method views (when navigating there through
the class view) should also have the source there
- DONE I think
+ DONE I think, single method view is gone
* have class-level attributes be displayed
+ DONE
+
* use "inherited" doc strings, i.e. for
class A:
def meth(self):
@@ -25,8 +29,12 @@
B.meth should display the A.meth docstring, probably
with special formatting (italics or so).
+ NOT YET DONE (later?)
+
* factor out some common code in the build_* functions
+ (mostly) DONE
+
* refactor the apigen/rsession interaction to become
cleaner (e.g. apigen's get_documentable_items should
be separately tested and the caller should not need
@@ -76,9 +84,15 @@
* add syntax coloring for Python source snippets
+ DONE
+
* remove py.test/apigen cruft from stack traces
+ DONE, thanks to fijal
+
* fix non-ascii source encoding support
+ DONE
+
* XXX