Bug 460999 - Generate inheritance graphs for the base of the string hierarchy and upload them to MDC with image maps r=jorendorff
--- 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]);
}