[svn r38439] Fixed a list of things suggested by hpk: changed method order in class pages,

changed page titles, added links to the api and source index from the nav bar
(also in py/doc html), changed function views, made it possible to remove an
item from the navigation, changed header 'properties' to 'class attributes and
properties', removed duplicate stack traces (in a somewhat unsatisfying way,
needs revisiting later I think).

--HG--
branch : trunk
This commit is contained in:
guido 2007-02-11 03:04:36 +01:00
parent ac5c05b688
commit a6fd3c241e
8 changed files with 130 additions and 22 deletions

View File

@ -30,6 +30,7 @@ def get_documentable_items(pkgdir):
pkgname, pkgdict = get_documentable_items_pkgdir(pkgdir) pkgname, pkgdict = get_documentable_items_pkgdir(pkgdir)
from py.__.execnet.channel import Channel from py.__.execnet.channel import Channel
pkgdict['execnet.Channel'] = Channel pkgdict['execnet.Channel'] = Channel
Channel.__apigen_hide_from_nav__ = True
return pkgname, pkgdict return pkgname, pkgdict
def build(pkgdir, dsa, capture): def build(pkgdir, dsa, capture):

View File

@ -25,7 +25,7 @@ class H(html):
class ClassDef(html.div): class ClassDef(html.div):
def __init__(self, classname, bases, docstring, sourcelink, def __init__(self, classname, bases, docstring, sourcelink,
properties, methods): attrs, methods):
header = H.h1('class %s(' % (classname,)) header = H.h1('class %s(' % (classname,))
for i, (name, href) in py.builtin.enumerate(bases): for i, (name, href) in py.builtin.enumerate(bases):
if i > 0: if i > 0:
@ -40,9 +40,9 @@ class H(html):
'*no docstring available*'), '*no docstring available*'),
sourcelink, sourcelink,
class_='classdoc')) class_='classdoc'))
if properties: if attrs:
self.append(H.h2('properties:')) self.append(H.h2('class attributes and properties:'))
for name, val in properties: for name, val in attrs:
self.append(H.PropertyDescription(name, val)) self.append(H.PropertyDescription(name, val))
if methods: if methods:
self.append(H.h2('methods:')) self.append(H.h2('methods:'))
@ -58,20 +58,32 @@ class H(html):
class FunctionDescription(Description): class FunctionDescription(Description):
def __init__(self, localname, argdesc, docstring, valuedesc, csource, def __init__(self, localname, argdesc, docstring, valuedesc, csource,
callstack): callstack):
fd = H.FunctionDef(localname, argdesc) infoid = 'info_%s' % (localname.replace('.', '_dot_'),)
ds = H.Docstring(docstring or '*no docstring available*') docstringid = 'docstring_%s' % (localname.replace('.', '_dot_'),)
fi = H.FunctionInfo(valuedesc, csource, callstack) fd = H.FunctionDef(localname, argdesc,
onclick=('showhideel('
'document.getElementById("%s")); '
'showhideel('
'document.getElementById("%s")); '
'this.scrollIntoView()' % (
infoid, docstringid)))
ds = H.Docstring(docstring or '*no docstring available*',
id=docstringid)
fi = H.FunctionInfo(valuedesc, csource, callstack,
id=infoid, style="display: none")
super(H.FunctionDescription, self).__init__(fd, ds, fi) super(H.FunctionDescription, self).__init__(fd, ds, fi)
class FunctionDef(html.h2): class FunctionDef(html.h2):
def __init__(self, name, argdesc): style = html.Style(cursor='pointer', color='blue')
super(H.FunctionDef, self).__init__('def %s%s:' % (name, argdesc)) def __init__(self, name, argdesc, **kwargs):
super(H.FunctionDef, self).__init__('def %s%s:' % (name, argdesc),
**kwargs)
class FunctionInfo(html.div): class FunctionInfo(html.div):
def __init__(self, valuedesc, csource, callstack): def __init__(self, valuedesc, csource, callstack, **kwargs):
super(H.FunctionInfo, self).__init__( super(H.FunctionInfo, self).__init__(valuedesc, H.br(), csource,
H.Hideable('funcinfo', 'funcinfo', valuedesc, H.br(), csource, callstack, class_='funcinfo',
callstack)) **kwargs)
class PropertyDescription(html.div): class PropertyDescription(html.div):
def __init__(self, name, value): def __init__(self, name, value):
@ -86,8 +98,9 @@ class H(html):
class ParameterDescription(html.div): class ParameterDescription(html.div):
pass pass
class Docstring(html.pre): class Docstring(html.div):
pass style = html.Style(white_space='pre', color='#666',
margin_left='1em', margin_bottom='1em')
class Navigation(html.div): class Navigation(html.div):
#style = html.Style(min_height='99%', float='left', margin_top='1.2em', #style = html.Style(min_height='99%', float='left', margin_top='1.2em',

View File

@ -14,6 +14,8 @@ sorted = py.builtin.sorted
html = py.xml.html html = py.xml.html
raw = py.xml.raw raw = py.xml.raw
REDUCE_CALLSITES = True
def is_navigateable(name): def is_navigateable(name):
return (not is_private(name) and name != '__doc__') return (not is_private(name) and name != '__doc__')
@ -24,7 +26,7 @@ def show_property(name):
# XXX do we need to skip more manually here? # XXX do we need to skip more manually here?
if (name not in dir(object) and if (name not in dir(object) and
name not in ['__doc__', '__dict__', '__name__', '__module__', name not in ['__doc__', '__dict__', '__name__', '__module__',
'__weakref__']): '__weakref__', '__apigen_hide_from_nav__']):
return True return True
return False return False
@ -136,10 +138,15 @@ def enumerate_and_color(codelines, firstlineno, enc):
break break
return snippet return snippet
_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)
if dotted_name == '': if dotted_name == '':
return pkg return pkg
try:
return _get_obj_cache[dotted_name]
except KeyError:
pass
path = dotted_name.split('.') path = dotted_name.split('.')
ret = pkg ret = pkg
for item in path: for item in path:
@ -147,10 +154,13 @@ def get_obj(dsa, pkg, dotted_name):
ret = getattr(ret, item, marker) ret = getattr(ret, item, marker)
if ret is marker: if ret is marker:
try: try:
return dsa.get_obj(dotted_name) ret = dsa.get_obj(dotted_name)
except KeyError: except KeyError:
raise NameError('can not access %s in %s' % (item, raise NameError('can not access %s in %s' % (item,
full_dotted_name)) full_dotted_name))
else:
break
_get_obj_cache[dotted_name] = ret
return ret return ret
def get_rel_sourcepath(projpath, filename, default=None): def get_rel_sourcepath(projpath, filename, default=None):
@ -419,6 +429,10 @@ class ApiPageBuilder(AbstractPageBuilder):
def build_methods(self, dotted_name): def build_methods(self, dotted_name):
ret = [] ret = []
methods = self.dsa.get_class_methods(dotted_name) methods = self.dsa.get_class_methods(dotted_name)
# move all __*__ methods to the back
methods = ([m for m in methods if not m.startswith('_')] +
[m for m in methods if m.startswith('_')])
# except for __init__, which should be first
if '__init__' in methods: if '__init__' in methods:
methods.remove('__init__') methods.remove('__init__')
methods.insert(0, '__init__') methods.insert(0, '__init__')
@ -437,7 +451,8 @@ class ApiPageBuilder(AbstractPageBuilder):
) )
for dotted_name in sorted(item_dotted_names): for dotted_name in sorted(item_dotted_names):
itemname = dotted_name.split('.')[-1] itemname = dotted_name.split('.')[-1]
if not is_navigateable(itemname): if (not is_navigateable(itemname) or
self.is_hidden_from_nav(dotted_name)):
continue continue
snippet.append( snippet.append(
H.NamespaceItem( H.NamespaceItem(
@ -463,7 +478,10 @@ class ApiPageBuilder(AbstractPageBuilder):
nav = self.build_navigation(dotted_name, False) nav = self.build_navigation(dotted_name, False)
reltargetpath = "api/%s.html" % (dotted_name,) reltargetpath = "api/%s.html" % (dotted_name,)
self.linker.set_link(dotted_name, reltargetpath) self.linker.set_link(dotted_name, reltargetpath)
title = 'api documentation for %s' % (dotted_name,) title = '%s API documentation' % (dotted_name,)
rev = self.get_revision(dotted_name)
if rev:
title += ' [rev. %s]' % (rev,)
self.write_page(title, reltargetpath, tag, nav) self.write_page(title, reltargetpath, tag, nav)
return passed return passed
@ -479,7 +497,10 @@ class ApiPageBuilder(AbstractPageBuilder):
nav = self.build_navigation(dotted_name, False) nav = self.build_navigation(dotted_name, False)
reltargetpath = "api/%s.html" % (dotted_name,) reltargetpath = "api/%s.html" % (dotted_name,)
self.linker.set_link(dotted_name, reltargetpath) self.linker.set_link(dotted_name, reltargetpath)
title = 'api documentation for %s' % (dotted_name,) title = '%s API documentation' % (dotted_name,)
rev = self.get_revision(dotted_name)
if rev:
title += ' [rev. %s]' % (rev,)
self.write_page(title, reltargetpath, tag, nav) self.write_page(title, reltargetpath, tag, nav)
return passed return passed
@ -528,6 +549,8 @@ class ApiPageBuilder(AbstractPageBuilder):
sibname = sibpath[-1] sibname = sibpath[-1]
if not is_navigateable(sibname): if not is_navigateable(sibname):
continue continue
if self.is_hidden_from_nav(dn):
continue
navitems.append(H.NavigationItem(self.linker, dn, sibname, navitems.append(H.NavigationItem(self.linker, dn, sibname,
depth, selected)) depth, selected))
if selected: if selected:
@ -595,10 +618,18 @@ class ApiPageBuilder(AbstractPageBuilder):
def is_in_pkg(self, sourcefile): def is_in_pkg(self, sourcefile):
return py.path.local(sourcefile).relto(self.projpath) return py.path.local(sourcefile).relto(self.projpath)
_processed_callsites = {}
def build_callsites(self, dotted_name): def build_callsites(self, dotted_name):
callstack = self.dsa.get_function_callpoints(dotted_name) callstack = self.dsa.get_function_callpoints(dotted_name)
cslinks = [] cslinks = []
for i, (cs, _) in enumerate(callstack): for i, (cs, _) in enumerate(callstack):
if REDUCE_CALLSITES:
key = (cs[0].filename, cs[0].lineno)
if key in self._processed_callsites:
# process one call site per line of test code when
# REDUCE_CALLSITES is set to True
continue
self._processed_callsites[key] = 1
link = self.build_callsite(dotted_name, cs, i) link = self.build_callsite(dotted_name, cs, i)
cslinks.append(link) cslinks.append(link)
return cslinks return cslinks
@ -660,3 +691,22 @@ class ApiPageBuilder(AbstractPageBuilder):
tbtag.append(H.div(*colored)) tbtag.append(H.div(*colored))
return tbtag return tbtag
def is_hidden_from_nav(self, dotted_name):
obj = get_obj(self.dsa, self.pkg, dotted_name)
return getattr(obj, '__apigen_hide_from_nav__', False)
def get_revision(self, dotted_name):
obj = get_obj(self.dsa, self.pkg, dotted_name)
try:
sourcefile = inspect.getsourcefile(obj)
except TypeError:
return None
if sourcefile is None:
return None
if sourcefile[-1] in ['o', 'c']:
sourcefile = sourcefile[:-1]
wc = py.path.svnwc(sourcefile)
if wc.check(versioned=True):
return wc.status().rev
return None

View File

@ -20,6 +20,7 @@ class LayoutPage(confrest.PyPage):
self.nav = kwargs.pop('nav') self.nav = kwargs.pop('nav')
self.relpath = kwargs.pop('relpath') self.relpath = kwargs.pop('relpath')
super(LayoutPage, self).__init__(*args, **kwargs) super(LayoutPage, self).__init__(*args, **kwargs)
self.project.logo.attr.id = 'logo'
def set_content(self, contentel): def set_content(self, contentel):
self.contentspace.append(contentel) self.contentspace.append(contentel)

View File

@ -2,6 +2,11 @@
font-size: 0.8em; font-size: 0.8em;
} }
#logo {
position: relative;
position: fixed;
}
div.sidebar { div.sidebar {
font-family: Verdana, Helvetica, Arial, sans-serif; font-family: Verdana, Helvetica, Arial, sans-serif;
font-size: 0.9em; font-size: 0.9em;
@ -9,6 +14,7 @@ div.sidebar {
vertical-align: top; vertical-align: top;
margin-top: 0.5em; margin-top: 0.5em;
position: absolute; position: absolute;
position: fixed;
top: 130px; top: 130px;
left: 4px; left: 4px;
bottom: 4px; bottom: 4px;
@ -34,6 +40,10 @@ ul li {
list-style-type: none; list-style-type: none;
} }
h2 {
padding-top: 0.5em;
}
.code a { .code a {
color: blue; color: blue;
font-weight: bold; font-weight: bold;
@ -42,6 +52,7 @@ ul li {
.lineno { .lineno {
line-height: 1.4em; line-height: 1.4em;
height: 1.4em;
text-align: right; text-align: right;
color: #555; color: #555;
width: 3em; width: 3em;
@ -52,6 +63,7 @@ ul li {
.code { .code {
line-height: 1.4em; line-height: 1.4em;
height: 1.4em;
padding-left: 1em; padding-left: 1em;
white-space: pre; white-space: pre;
font-family: monospace, Monaco; font-family: monospace, Monaco;

View File

@ -37,6 +37,9 @@ def setup_fs_project():
" get_somevar docstring " " get_somevar docstring "
return self.somevar return self.somevar
SomeInstance = SomeClass(10) SomeInstance = SomeClass(10)
class SomeHiddenClass(object):
" docstring somehiddenclass "
__apigen_hide_from_nav__ = True # hide it from the navigation
""")) """))
temp.ensure('pkg/somesubclass.py').write(py.code.Source("""\ temp.ensure('pkg/somesubclass.py').write(py.code.Source("""\
from someclass import SomeClass from someclass import SomeClass
@ -59,6 +62,7 @@ def setup_fs_project():
'main.SomeInstance': ('./someclass.py', 'SomeInstance'), 'main.SomeInstance': ('./someclass.py', 'SomeInstance'),
'main.SomeSubClass': ('./somesubclass.py', 'SomeSubClass'), 'main.SomeSubClass': ('./somesubclass.py', 'SomeSubClass'),
'main.SomeSubClass': ('./somesubclass.py', 'SomeSubClass'), 'main.SomeSubClass': ('./somesubclass.py', 'SomeSubClass'),
'main.SomeHiddenClass': ('./someclass.py', 'SomeHiddenClass'),
'other': ('./somenamespace.py', '*'), 'other': ('./somenamespace.py', '*'),
'_test': ('./somenamespace.py', '*'), '_test': ('./somenamespace.py', '*'),
}) })
@ -105,8 +109,9 @@ class AbstractBuilderTest(object):
'main.SomeClass', 'main.SomeClass',
'main.SomeSubClass', 'main.SomeSubClass',
'main.SomeInstance', 'main.SomeInstance',
'main.SomeHiddenClass',
'other.foo', 'other.foo',
'other.bar', 'other.baz',
'_test']) '_test'])
self.namespace_tree = namespace_tree self.namespace_tree = namespace_tree
self.apb = ApiPageBuilder(base, linker, self.dsa, self.apb = ApiPageBuilder(base, linker, self.dsa,
@ -284,7 +289,8 @@ class TestApiPageBuilder(AbstractBuilderTest):
self.apb.build_function_pages(['main.sub.func']) self.apb.build_function_pages(['main.sub.func'])
self.apb.build_class_pages(['main.SomeClass', self.apb.build_class_pages(['main.SomeClass',
'main.SomeSubClass', 'main.SomeSubClass',
'main.SomeInstance']) 'main.SomeInstance',
'main.SomeHiddenClass'])
self.linker.replace_dirpath(self.base, False) self.linker.replace_dirpath(self.base, False)
html = self.base.join('api/main.sub.func.html').read() html = self.base.join('api/main.sub.func.html').read()
print html print html

View File

@ -3,6 +3,8 @@
special "__*__" methods should come last except for __init__ special "__*__" methods should come last except for __init__
which comes first which comes first
DONE
* the page header should read: * the page header should read:
py.path.local API documentation [rev XYZ] py.path.local API documentation [rev XYZ]
@ -11,10 +13,15 @@
api documentation for path.local api documentation for path.local
DONE, title changed and if possible (read: if source file in SVN) rev is
retrieved and added
* have the py/doc/ and apigen page layout have * have the py/doc/ and apigen page layout have
an api and source link in the menu bar an api and source link in the menu bar
(e.g.: home doc api source contact getting-started issue) (e.g.: home doc api source contact getting-started issue)
DONE
* function view: * function view:
def __init__(self, rawcode): def __init__(self, rawcode):
@ -28,13 +35,21 @@
be "sticking" out (the show/hide info link IMO disrupts this be "sticking" out (the show/hide info link IMO disrupts this
and it's not visually clear it belongs to the function above it) and it's not visually clear it belongs to the function above it)
DONE, but please review if you like it like this...
* can it be avoided that py.execnet.Channel shows up as a * can it be avoided that py.execnet.Channel shows up as a
primary object but still have it documented/linked from primary object but still have it documented/linked from
remote_exec()'s "return value"? remote_exec()'s "return value"?
DONE: if you set an attribute __hide_from_nav__ to True on an
object somehow, it is hidden from the navigation
* class attributes are not "properties". can they get their * class attributes are not "properties". can they get their
section? section?
DONE: renamed title to 'class attributes and properties'
(as discussed)
* stacktraces: a lot are "duplicates" like: * stacktraces: a lot are "duplicates" like:
/home/hpk/py-trunk/py/test/rsession/hostmanage.py - line 37 /home/hpk/py-trunk/py/test/rsession/hostmanage.py - line 37
@ -45,6 +60,12 @@
i think we should by default strip out these duplicates, i think we should by default strip out these duplicates,
this would also reduce the generated html files, right? this would also reduce the generated html files, right?
DONE, although I'm not happy with it... I'd rather only display call sites
from calls in the test somehow or something...
* allow for flexibility regarding linking from * allow for flexibility regarding linking from
py/doc/*.txt documents to apigen with respect py/doc/*.txt documents to apigen with respect
to where apigen/ docs are located. to where apigen/ docs are located.
LATER, as discussed

View File

@ -33,6 +33,10 @@ class Page(object):
self.menubar = html.div( self.menubar = html.div(
html.a("home", href="home.html", class_="menu"), " ", html.a("home", href="home.html", class_="menu"), " ",
html.a("doc", href="index.html", class_="menu"), " ", html.a("doc", href="index.html", class_="menu"), " ",
html.a("api", href="../../apigen/api/index.html", class_="menu"),
" ",
html.a("source", href="../../apigen/source/index.html",
class_="menu"), " ",
html.a("contact", href="contact.html", class_="menu"), " ", html.a("contact", href="contact.html", class_="menu"), " ",
html.a("getting-started", href="getting-started.html", class_="menu"), " ", html.a("getting-started", href="getting-started.html", class_="menu"), " ",
id="menubar", id="menubar",