[py-svn] r38439 - in py/trunk/py: apigen apigen/testing doc

guido at codespeak.net guido at codespeak.net
Sun Feb 11 03:04:40 CET 2007


Author: guido
Date: Sun Feb 11 03:04:36 2007
New Revision: 38439

Modified:
   py/trunk/py/apigen/apigen.py
   py/trunk/py/apigen/html.py
   py/trunk/py/apigen/htmlgen.py
   py/trunk/py/apigen/layout.py
   py/trunk/py/apigen/style.css
   py/trunk/py/apigen/testing/test_apigen_example.py
   py/trunk/py/apigen/todo.txt
   py/trunk/py/doc/confrest.py
Log:
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).


Modified: py/trunk/py/apigen/apigen.py
==============================================================================
--- py/trunk/py/apigen/apigen.py	(original)
+++ py/trunk/py/apigen/apigen.py	Sun Feb 11 03:04:36 2007
@@ -30,6 +30,7 @@
     pkgname, pkgdict = get_documentable_items_pkgdir(pkgdir)
     from py.__.execnet.channel import Channel
     pkgdict['execnet.Channel'] = Channel
+    Channel.__apigen_hide_from_nav__ = True
     return pkgname, pkgdict
 
 def build(pkgdir, dsa, capture):

Modified: py/trunk/py/apigen/html.py
==============================================================================
--- py/trunk/py/apigen/html.py	(original)
+++ py/trunk/py/apigen/html.py	Sun Feb 11 03:04:36 2007
@@ -25,7 +25,7 @@
 
     class ClassDef(html.div):
         def __init__(self, classname, bases, docstring, sourcelink,
-                     properties, methods):
+                     attrs, methods):
             header = H.h1('class %s(' % (classname,))
             for i, (name, href) in py.builtin.enumerate(bases):
                 if i > 0:
@@ -40,9 +40,9 @@
                                           '*no docstring available*'),
                               sourcelink,
                               class_='classdoc'))
-            if properties:
-                self.append(H.h2('properties:'))
-                for name, val in properties:
+            if attrs:
+                self.append(H.h2('class attributes and properties:'))
+                for name, val in attrs:
                     self.append(H.PropertyDescription(name, val))
             if methods:
                 self.append(H.h2('methods:'))
@@ -58,20 +58,32 @@
     class FunctionDescription(Description):
         def __init__(self, localname, argdesc, docstring, valuedesc, csource,
                      callstack):
-            fd = H.FunctionDef(localname, argdesc)
-            ds = H.Docstring(docstring or '*no docstring available*')
-            fi = H.FunctionInfo(valuedesc, csource, callstack)
+            infoid = 'info_%s' % (localname.replace('.', '_dot_'),)
+            docstringid = 'docstring_%s' % (localname.replace('.', '_dot_'),)
+            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)
 
     class FunctionDef(html.h2):
-        def __init__(self, name, argdesc):
-            super(H.FunctionDef, self).__init__('def %s%s:' % (name, argdesc))
+        style = html.Style(cursor='pointer', color='blue')
+        def __init__(self, name, argdesc, **kwargs):
+            super(H.FunctionDef, self).__init__('def %s%s:' % (name, argdesc),
+                                                **kwargs)
 
     class FunctionInfo(html.div):
-        def __init__(self, valuedesc, csource, callstack):
-            super(H.FunctionInfo, self).__init__(
-                H.Hideable('funcinfo', 'funcinfo', valuedesc, H.br(), csource,
-                           callstack))
+        def __init__(self, valuedesc, csource, callstack, **kwargs):
+            super(H.FunctionInfo, self).__init__(valuedesc, H.br(), csource,
+                                                 callstack, class_='funcinfo',
+                                                 **kwargs)
     
     class PropertyDescription(html.div):
         def __init__(self, name, value):
@@ -86,8 +98,9 @@
     class ParameterDescription(html.div):
         pass
 
-    class Docstring(html.pre):
-        pass
+    class Docstring(html.div):
+        style = html.Style(white_space='pre', color='#666',
+                           margin_left='1em', margin_bottom='1em')
 
     class Navigation(html.div):
         #style = html.Style(min_height='99%', float='left', margin_top='1.2em',

Modified: py/trunk/py/apigen/htmlgen.py
==============================================================================
--- py/trunk/py/apigen/htmlgen.py	(original)
+++ py/trunk/py/apigen/htmlgen.py	Sun Feb 11 03:04:36 2007
@@ -14,6 +14,8 @@
 html = py.xml.html
 raw = py.xml.raw
 
+REDUCE_CALLSITES = True
+
 def is_navigateable(name):
     return (not is_private(name) and name != '__doc__')
 
@@ -24,7 +26,7 @@
         # XXX do we need to skip more manually here?
         if (name not in dir(object) and
                 name not in ['__doc__', '__dict__', '__name__', '__module__',
-                             '__weakref__']):
+                             '__weakref__', '__apigen_hide_from_nav__']):
             return True
     return False
 
@@ -136,10 +138,15 @@
             break
     return snippet
 
+_get_obj_cache = {}
 def get_obj(dsa, pkg, dotted_name):
     full_dotted_name = '%s.%s' % (pkg.__name__, dotted_name)
     if dotted_name == '':
         return pkg
+    try:
+        return _get_obj_cache[dotted_name]
+    except KeyError:
+        pass
     path = dotted_name.split('.')
     ret = pkg
     for item in path:
@@ -147,10 +154,13 @@
         ret = getattr(ret, item, marker)
         if ret is marker:
             try:
-                return dsa.get_obj(dotted_name)
+                ret = dsa.get_obj(dotted_name)
             except KeyError:
                 raise NameError('can not access %s in %s' % (item,
                                  full_dotted_name))
+            else:
+                break
+    _get_obj_cache[dotted_name] = ret
     return ret
 
 def get_rel_sourcepath(projpath, filename, default=None):
@@ -419,6 +429,10 @@
     def build_methods(self, dotted_name):
         ret = []
         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:
             methods.remove('__init__')
             methods.insert(0, '__init__')
@@ -437,7 +451,8 @@
         )
         for dotted_name in sorted(item_dotted_names):
             itemname = dotted_name.split('.')[-1]
-            if not is_navigateable(itemname):
+            if (not is_navigateable(itemname) or
+                    self.is_hidden_from_nav(dotted_name)):
                 continue
             snippet.append(
                 H.NamespaceItem(
@@ -463,7 +478,10 @@
             nav = self.build_navigation(dotted_name, False)
             reltargetpath = "api/%s.html" % (dotted_name,)
             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)
         return passed
         
@@ -479,7 +497,10 @@
             nav = self.build_navigation(dotted_name, False)
             reltargetpath = "api/%s.html" % (dotted_name,)
             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)
         return passed
 
@@ -528,6 +549,8 @@
                 sibname = sibpath[-1]
                 if not is_navigateable(sibname):
                     continue
+                if self.is_hidden_from_nav(dn):
+                    continue
                 navitems.append(H.NavigationItem(self.linker, dn, sibname,
                                                  depth, selected))
                 if selected:
@@ -595,10 +618,18 @@
     def is_in_pkg(self, sourcefile):
         return py.path.local(sourcefile).relto(self.projpath)
 
+    _processed_callsites = {}
     def build_callsites(self, dotted_name):
         callstack = self.dsa.get_function_callpoints(dotted_name)
         cslinks = []
         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)
             cslinks.append(link)
         return cslinks
@@ -660,3 +691,22 @@
             tbtag.append(H.div(*colored))
         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
+

Modified: py/trunk/py/apigen/layout.py
==============================================================================
--- py/trunk/py/apigen/layout.py	(original)
+++ py/trunk/py/apigen/layout.py	Sun Feb 11 03:04:36 2007
@@ -20,6 +20,7 @@
         self.nav = kwargs.pop('nav')
         self.relpath = kwargs.pop('relpath')
         super(LayoutPage, self).__init__(*args, **kwargs)
+        self.project.logo.attr.id = 'logo'
 
     def set_content(self, contentel):
         self.contentspace.append(contentel)

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

Modified: py/trunk/py/apigen/testing/test_apigen_example.py
==============================================================================
--- py/trunk/py/apigen/testing/test_apigen_example.py	(original)
+++ py/trunk/py/apigen/testing/test_apigen_example.py	Sun Feb 11 03:04:36 2007
@@ -37,6 +37,9 @@
                 " get_somevar docstring "
                 return self.somevar
         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("""\
         from someclass import SomeClass
@@ -59,6 +62,7 @@
             'main.SomeInstance': ('./someclass.py', 'SomeInstance'),
             'main.SomeSubClass': ('./somesubclass.py', 'SomeSubClass'),
             'main.SomeSubClass': ('./somesubclass.py', 'SomeSubClass'),
+            'main.SomeHiddenClass': ('./someclass.py', 'SomeHiddenClass'),
             'other':             ('./somenamespace.py', '*'),
             '_test':             ('./somenamespace.py', '*'),
         })
@@ -105,8 +109,9 @@
                                                 'main.SomeClass',
                                                 'main.SomeSubClass',
                                                 'main.SomeInstance',
+                                                'main.SomeHiddenClass',
                                                 'other.foo',
-                                                'other.bar',
+                                                'other.baz',
                                                 '_test'])
         self.namespace_tree = namespace_tree
         self.apb = ApiPageBuilder(base, linker, self.dsa,
@@ -284,7 +289,8 @@
         self.apb.build_function_pages(['main.sub.func'])
         self.apb.build_class_pages(['main.SomeClass',
                                     'main.SomeSubClass',
-                                    'main.SomeInstance'])
+                                    'main.SomeInstance',
+                                    'main.SomeHiddenClass'])
         self.linker.replace_dirpath(self.base, False)
         html = self.base.join('api/main.sub.func.html').read()
         print html

Modified: py/trunk/py/apigen/todo.txt
==============================================================================
--- py/trunk/py/apigen/todo.txt	(original)
+++ py/trunk/py/apigen/todo.txt	Sun Feb 11 03:04:36 2007
@@ -3,6 +3,8 @@
   special "__*__" methods should come last except for __init__ 
   which comes first 
 
+  DONE
+
 * the page header should read:
   
     py.path.local API documentation [rev XYZ]
@@ -11,10 +13,15 @@
 
     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
   an api and source link in the menu bar 
   (e.g.: home doc api source contact getting-started issue)
 
+  DONE
+
 * function view: 
 
      def __init__(self, rawcode):
@@ -28,13 +35,21 @@
   be "sticking" out (the show/hide info link IMO disrupts this
   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
   primary object but still have it documented/linked from 
   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
   section? 
 
+  DONE: renamed title to 'class attributes and properties'
+        (as discussed)
+
 * stacktraces: a lot are "duplicates" like:
 
     /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,
   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 
   py/doc/*.txt documents to apigen with respect
   to where apigen/ docs are located. 
+
+  LATER, as discussed
+

Modified: py/trunk/py/doc/confrest.py
==============================================================================
--- py/trunk/py/doc/confrest.py	(original)
+++ py/trunk/py/doc/confrest.py	Sun Feb 11 03:04:36 2007
@@ -33,6 +33,10 @@
         self.menubar = html.div(
             html.a("home", href="home.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("getting-started", href="getting-started.html", class_="menu"), " ",
             id="menubar", 



More information about the pytest-commit mailing list