Bug 460999 - Generate inheritance graphs for the base of the string hierarchy and upload them to MDC with image maps r=jorendorff
authorBenjamin Smedberg <benjamin@smedbergs.us>
Fri, 24 Oct 2008 16:29:33 -0400
changeset 20814 6a2e65f15f480c149f478ac92d5c515f37c28df9
parent 20813 882b3390d162332f072a2a65bdf4c6455171bc2f
child 20815 4ab369d23d95cd67c2f631c01b2a183fb3a13327
push id3165
push userbsmedberg@mozilla.com
push dateFri, 24 Oct 2008 20:35:59 +0000
treeherdermozilla-central@6a2e65f15f48 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjorendorff
bugs460999
milestone1.9.1b2pre
Bug 460999 - Generate inheritance graphs for the base of the string hierarchy and upload them to MDC with image maps r=jorendorff
xpcom/Makefile.in
xpcom/analysis/MDC-attach.py
xpcom/analysis/MDC-upload.py
xpcom/analysis/Makefile.in
xpcom/analysis/deki.py
xpcom/analysis/fix-srcrefs.py
xpcom/analysis/string-format.js
xpcom/analysis/type-printer.js
--- a/xpcom/Makefile.in
+++ b/xpcom/Makefile.in
@@ -89,10 +89,14 @@ TOOL_DIRS += \
 	reflect/xptcall/tests \
 	$(NULL)
 endif
 endif
 
 # xpcom-config.h is generated by configure
 SDK_HEADERS	= xpcom-config.h
 
+ifdef DEHYDRA_PATH
+DIRS += analysis
+endif
+
 include $(topsrcdir)/config/rules.mk
 
new file mode 100644
--- /dev/null
+++ b/xpcom/analysis/MDC-attach.py
@@ -0,0 +1,19 @@
+#!/usr/bin/env python
+
+"""
+Upload a file attachment to MDC
+
+Usage: python MDC-attach.py <file> <parent page name> <MIME type> <description>
+Please set MDC_USER and MDC_PASSWORD in the environment
+"""
+
+import os, sys, deki
+
+wikiuser = os.environ['MDC_USER']
+wikipw = os.environ['MDC_PASSWORD']
+
+file, pageid, mimetype, description = sys.argv[1:]
+
+wiki = deki.Deki("http://developer.mozilla.org/@api/deki/", wikiuser, wikipw)
+wiki.create_file(pageid, os.path.basename(file), open(file).read(), mimetype,
+                 description)
--- a/xpcom/analysis/MDC-upload.py
+++ b/xpcom/analysis/MDC-upload.py
@@ -1,18 +1,18 @@
 #!/usr/bin/env python
 
 """
-Upload a file to MDC
+Upload a page to MDC
 
 Usage: python MDC-upload.py <file> <MDC-path>
 Please set MDC_USER and MDC_PASSWORD in the environment
 """
 
-import os, sys, urllib, urllib2, deki
+import os, sys, deki
 
 wikiuser = os.environ['MDC_USER']
 wikipw = os.environ['MDC_PASSWORD']
 
 (file, wikipath) = sys.argv[1:]
 
 wiki = deki.Deki("http://developer.mozilla.org/@api/deki/", wikiuser, wikipw)
 wiki.create_page(wikipath, open(file).read(), overwrite=True)
--- a/xpcom/analysis/Makefile.in
+++ b/xpcom/analysis/Makefile.in
@@ -12,35 +12,43 @@ REQUIRES = \
   xpcom \
   $(NULL)
 
 include $(topsrcdir)/config/rules.mk
 
 DUMP_CLASSES = \
   nsAString_internal \
   nsACString_internal \
-  nsString \
-  nsCString \
-  nsAutoString \
-  nsCAutoString \
-  nsXPIDLString \
-  nsXPIDLCString \
   $(NULL)
 
 SPACE = $(NULL) $(NULL)
 COMMA = ,
 
 HGREV = $(shell hg -R $(topsrcdir) id -i)
 
 classapi: DEHYDRA_MODULES = $(srcdir)/type-printer.js
 classapi: TREEHYDRA_MODULES =
 classapi: DEHYDRA_ARGS += --dump-types=$(subst $(SPACE),$(COMMA),$(strip $(DUMP_CLASSES))) --rev=$(HGREV)
-classapi:
-	$(CCC) $(OUTOPTION)/dev/null -c $(COMPILE_CXXFLAGS) $(srcdir)/type-printer.cpp
+classapi: $(MDDEPDIR)
+	$(CCC) $(OUTOPTION)/dev/null -c $(COMPILE_CXXFLAGS) $(srcdir)/type-printer.cpp >classapi.out 2>&1
+	perl -e 'while (<>) {if (/DUMP-TYPE\((.*)\)/) {print "$$1 ";}}' <classapi.out >dumptypes.list
+	perl -e 'while (<>) {if (/GRAPH-TYPE\((.*)\)/) {print "$$1 ";}}' <classapi.out >graphtypes.list
 	$(EXIT_ON_ERROR) \
-	for class in $(DUMP_CLASSES); do \
+	for class in `cat graphtypes.list`; do \
+	  dot -Tpng -o$${class}-graph.png -Tcmapx -o$${class}-graph.map $${class}-graph.gv; \
+	done
+	$(EXIT_ON_ERROR) \
+	for class in `cat dumptypes.list`; do \
 	  $(PYTHON) $(srcdir)/fix-srcrefs.py $(topsrcdir) < $${class}.html > $${class}-fixed.html; \
 	done
 
 upload_classapi:
-	for class in $(DUMP_CLASSES); do \
+	$(EXIT_ON_ERROR) \
+	for class in `cat dumptypes.list`; do \
 	  $(PYTHON) $(srcdir)/MDC-upload.py $${class}-fixed.html en/$${class}; \
 	done
+	$(EXIT_ON_ERROR) \
+	for class in `cat graphtypes.list`; do \
+	  $(PYTHON) $(srcdir)/MDC-attach.py $${class}-graph.png en/$${class} "image/png" "Class inheritance graph"; \
+	done
+
+GARBAGE += $(wildcard *.html) $(wildcard *.png) $(wildcard *.map) \
+  $(wildcard *.gv) classapi.out graphtypes.list dumptypes.list
--- a/xpcom/analysis/deki.py
+++ b/xpcom/analysis/deki.py
@@ -15,17 +15,17 @@ There are also some additional methods:
   wiki.move_page(old, new)
   wiki.get_subpages(page)
 
 This module does not try to mimic the MindTouch "Plug" API.  It's meant to be
 higher-level than that.
 """
 
 import sys
-import urllib2, cookielib
+import urllib2, cookielib, httplib
 import xml.dom.minidom as dom
 from urllib import quote as _urllib_quote
 from urllib import urlencode as _urlencode
 import urlparse
 from datetime import datetime
 import re
 
 __all__ = ['Deki']
@@ -42,16 +42,20 @@ def _urlquote(s, *args):
 
 def _make_url(*dirs, **params):
     """ dirs must already be url-encoded, params must not """
     url = '/'.join(dirs)
     if params:
         url += '?' + _urlencode(params)
     return url
 
+class PutRequest(urllib2.Request):
+    def get_method(self):
+        return "PUT"
+
 # === Dream framework client code
 
 # This handler causes python to "always be logged in" when it's talking to the
 # server.  If you're just accessing public pages, it generates more requests
 # than are strictly needed, but this is the behavior you want for a bot.
 #
 # The users/authenticate request is sent twice: once without any basic auth and
 # once with.  Dumb.  Feel free to fix.
@@ -91,32 +95,42 @@ class DreamClient:
 
     def login(self):
         response = self._opener.open(self.base + 'users/authenticate')
         response.close()
 
     def open(self, url):
         return self._opener.open(self.base + url)
 
-    def post(self, url, data, type):
-        #print "DEBUG- posting to:", self.base + url
-        req = urllib2.Request(self.base + url, data, {'Content-Type': type})
+    def _handleResponse(self, req):
+        """Helper method shared between post() and put()"""
         resp = self._opener.open(req)
         try:
             ct = resp.headers.get('Content-Type', '(none)')
             if '/xml' in ct or '+xml' in ct:
                 return dom.parse(resp)
             else:
                 #print "DEBUG- Content-Type:", ct
                 crud = resp.read()
                 #print 'DEBUG- crud:\n---\n%s\n---' % re.sub(r'(?m)^', '    ', crud)
                 return None
         finally:
             resp.close()
 
+
+    def post(self, url, data, type):
+        #print "DEBUG- posting to:", self.base + url
+        req = urllib2.Request(self.base + url, data, {'Content-Type': type})
+        return self._handleResponse(req)
+
+    def put(self, url, data, type):
+        #print "DEBUG- putting to:", self.base + url
+        req = PutRequest(self.base + url, data, {'Content-Type': type})
+        return self._handleResponse(req)
+
     def get_xml(self, url):
         resp = self.open(url)
         try:
             return dom.parse(resp)
         finally:
             resp.close()
 
 
@@ -198,16 +212,37 @@ class Deki(DreamClient):
         """
         if title is None:
             title = path.split('/')[-1]
         doc = dom.parseString(content)
         _check(doc.documentElement.tagName == 'body')
         p = Page(self)
         p._create(path, title, doc, overwrite)
 
+    def attach_file(self, page, name, data, mimetype, description=None):
+        """Create or update a file attachment.
+
+        Parameters:
+          page - str - the page ID this file is related to
+          name - str - the name of the file
+          data - str - the file data
+          mimetype - str - the MIME type of the file
+          description - str - a description of the file
+        """
+
+        p = {}
+        if description is not None:
+            p['description'] = description
+
+        url = _make_url('pages', _format_page_id(page),
+                        'files', _format_page_id(name), **p)
+
+        r = self.put(url, data, mimetype)
+        _check(r.documentElement.nodeName == u'file')
+
     def get_subpages(self, page_id):
         """ Return the ids of all subpages of the given page. """
         doc = self.get_xml(_make_url("pages", _format_page_id(page_id),
                                      "files,subpages"))
         for elt in _find_elements(doc, u'page/subpages/page.subpage/path'):
             yield _text_of(elt)
 
     def move_page(self, page_id, new_title, redirects=True):
--- a/xpcom/analysis/fix-srcrefs.py
+++ b/xpcom/analysis/fix-srcrefs.py
@@ -1,13 +1,15 @@
 #!/usr/bin/env python
 
 """
 Fix references to source files of the form [LOCpath]
 so that they are relative to a given source directory.
+
+Substitute the DOT-generated image map into the document.
 """
 
 import os, sys, re
 
 (srcdir, ) = sys.argv[1:]
 srcdir = os.path.realpath(srcdir)
 
 f = re.compile(r'\[LOC(.*?)\]')
@@ -16,12 +18,20 @@ def replacer(m):
     file = m.group(1)
     file = os.path.realpath(file)
     if not file.startswith(srcdir):
         raise Exception("File %s doesn't start with %s" % (file, srcdir))
 
     file = file[len(srcdir) + 1:]
     return file
 
+s = re.compile(r'\[MAP(.*?)\]')
+
+def mapreplace(m):
+    file = m.group(1)
+    c = open(file).read()
+    return c
+
 for line in sys.stdin:
     line = f.sub(replacer, line)
+    line = s.sub(mapreplace, line)
+
     sys.stdout.write(line)
-
--- a/xpcom/analysis/string-format.js
+++ b/xpcom/analysis/string-format.js
@@ -8,16 +8,19 @@ String.prototype.format = function strin
 
   if (arguments.length > 1) {
     d = arguments;
   }
   else
     d = arguments[0];
 
   function r(s, key, type) {
+    if (type == '%')
+      return '%';
+    
     let v;
     if (key == "") {
       if (curindex == -1)
         throw Error("Cannot mix named and positional indices in string formatting.");
 
       if (curindex == 0 && (!(d instanceof Object) || !(0 in d))) {
         v = d;
       }
@@ -45,16 +48,14 @@ String.prototype.format = function strin
         return "<undefined>";
       return v.toString();
     case "r":
       return uneval(v);
     case "i":
       return parseInt(v);
     case "f":
       return Number(v);
-    case "%":
-      return "%";
     default:
       throw Error("Unexpected format character '%s'.".format(type));
     }
   }
   return this.replace(/%(\([^)]+\))?(.)/g, r);
 };
--- a/xpcom/analysis/type-printer.js
+++ b/xpcom/analysis/type-printer.js
@@ -1,34 +1,51 @@
 let dumpTypes = options['dump-types'].split(',');
-function interestingType(name) dumpTypes.some(function(n) n == name);
+
+let interestingList = {};
+let typelist = {};
 
-let typelist = {};
+function interestingType(t)
+{
+  let name = t.name;
+  
+  if (dumpTypes.some(function(n) n == name)) {
+    interestingList[name] = t;
+    typelist[name] = t;
+    return true;
+  }
+  
+  for each (let base in t.bases) {
+    if (base.access == 'public' && interestingType(base.type)) {
+      typelist[name] = t;
+      return true;
+    }
+  }
+  
+  return false;
+}
 
 function addSubtype(t, subt)
 {
   if (subt.typedef === undefined &&
       subt.kind === undefined)
     throw Error("Unexpected subtype: not class or typedef: " + subt);
 
   if (t.subtypes === undefined)
     t.subtypes = [];
   
   t.subtypes.push(subt);
 }
 
 function process_type(t)
 {
-  let name = t.name;
+  interestingType(t);
   
-  if (interestingType(t.name))
-    typelist[t.name] = t;
-  
-  if (t.memberOf)
-    addSubtype(t.memberOf, t);
+  for each (let base in t.bases)
+    addSubtype(base.type, t);
 }
 
 function process_decl(d)
 {
   if (d.typedef !== undefined && d.memberOf)
     addSubtype(d.memberOf, d);
 }
 
@@ -218,16 +235,18 @@ function publicBaseList(t)
 function getLocLink(loc, text)
 {
   return <a class="loc"
             href={"http://hg.mozilla.org/mozilla-central/file/%s/[LOC%s]#l%i".format(options.rev, loc.file, loc.line)}>{text}</a>;
 }
 
 function dumpType(t)
 {
+  print("DUMP-TYPE(%s)".format(t.name));
+
   let methodOverview = <tbody />;
   let methodList = <div/>;
   let memberList = <></>;
 
   let shortNameMap = {};
 
   let members = [m for (m in publicMembers(t))];
   
@@ -317,16 +336,23 @@ function dumpType(t)
     }
   }
 
   let r =
     <body>
       <p>{getLocLink(t.loc, "Class Declaration")}</p>
   
       {getDocComment(t.loc)}
+
+      {dumpTypes.some(function(n) n == t.name) ?
+         <>
+           [MAP{t.name}-graph.map]
+           <img src={"/@api/deki/pages/=en%%252F%s/files/=%s-graph.png".format(t.name, t.name)} usemap="#classes" />
+         </> : <></>
+      }
   
       {methodOverview.*.length() > 0 ?
          <>
            <h2>Method Overview</h2>
            <table class="standard-table">{methodOverview}</table>
          </> :
          ""
       }
@@ -347,13 +373,41 @@ function dumpType(t)
          <p><em>No public methods.</em></p>
       }
   
     </body>;
 
   write_file(t.name + ".html", r.toXMLString());
 }
 
+function graphType(t)
+{
+  print("GRAPH-TYPE(%s)".format(t.name));
+
+  let contents = "digraph classes {\n"
+               + "  node [shape=rectangle fontsize=11]\n"
+               + "  %s;\n".format(t.name);
+
+  function graphClass(c)
+  {
+    contents += '%s [URL="http://developer.mozilla.org/en/%s"]\n'.format(c.name, c.name);
+    
+    for each (let st in c.subtypes) {
+      contents += "  %s -> %s;\n".format(c.name, st.name);
+      graphClass(st);
+    }
+  }
+
+  graphClass(t);
+  
+  contents += "}\n";
+  
+  write_file(t.name + "-graph.gv", contents);
+}
+  
 function input_end()
 {
   for (let p in typelist)
     dumpType(typelist[p]);
+
+  for (let n in interestingList)
+    graphType(interestingList[n]);
 }