diff options
author | Danielle Madeley <danielle.madeley@collabora.co.uk> | 2010-02-11 14:55:03 +1100 |
---|---|---|
committer | Danielle Madeley <danielle.madeley@collabora.co.uk> | 2010-02-11 15:41:39 +1100 |
commit | cc900e536d1fc4b1d9f8f98c3297e7007650b0b9 (patch) | |
tree | 40a02e78fd576f5007fdea6ba289c8f721f438e8 /tools | |
parent | ce83fc29a8a592f732e58559b1ac85f5929bfd7a (diff) |
Get org.freedesktop.Telepathy.Logger extension building
Diffstat (limited to 'tools')
-rwxr-xr-x | tools/doc-generator.py | 104 | ||||
-rw-r--r-- | tools/identity.xsl | 7 | ||||
-rw-r--r-- | tools/specparser.py | 885 |
3 files changed, 996 insertions, 0 deletions
diff --git a/tools/doc-generator.py b/tools/doc-generator.py new file mode 100755 index 0000000..5fc19ce --- /dev/null +++ b/tools/doc-generator.py @@ -0,0 +1,104 @@ +#!/usr/bin/env python +# +# doc-generator.py +# +# Generates HTML documentation from the parsed spec using Cheetah templates. +# +# Copyright (C) 2009 Collabora Ltd. +# +# This library is free software; you can redistribute it and/or modify it +# under the terms of the GNU Lesser General Public License as published by +# the Free Software Foundation; either version 2.1 of the License, or (at +# your option) any later version. +# +# This library is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +# FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License +# for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with this library; if not, write to the Free Software Foundation, +# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# Authors: Davyd Madeley <davyd.madeley@collabora.co.uk> +# + +import sys +import os +import os.path +import shutil + +try: + from Cheetah.Template import Template +except ImportError, e: + print >> sys.stderr, e + print >> sys.stderr, "Install `python-cheetah'?" + sys.exit(-1) + +import specparser + +program, spec_file, output_path, project, namespace = sys.argv + +template_path = os.path.join(os.path.dirname(program), '../doc/templates') + +# make the output path +try: + os.mkdir(output_path) +except OSError: + pass +# copy in the CSS +shutil.copy(os.path.join(template_path, 'style.css'), output_path) + +def load_template(filename): + try: + file = open(os.path.join(template_path, filename)) + template_def = file.read() + file.close() + except IOError, e: + print >> sys.stderr, "Could not load template file `%s'" % filename + print >> sys.stderr, e + sys.exit(-1) + + return template_def + +spec = specparser.parse(spec_file, namespace) + +# write out HTML files for each of the interfaces + +# Not using render_template here to avoid recompiling it n times. +namespace = {} +template_def = load_template('interface.html') +t = Template(template_def, namespaces = [namespace]) +for interface in spec.interfaces: + namespace['interface'] = interface + + # open the output file + out = open(os.path.join(output_path, '%s.html' % interface.name), 'w') + print >> out, unicode(t).encode('utf-8') + out.close() + +def render_template(name, namespaces, target=None): + if target is None: + target = name + + namespace = { 'spec': spec } + template_def = load_template(name) + t = Template(template_def, namespaces=namespaces) + out = open(os.path.join(output_path, target), 'w') + print >> out, unicode(t).encode('utf-8') + out.close() + +namespaces = { 'spec': spec } + +render_template('generic-types.html', namespaces) +render_template('errors.html', namespaces) +render_template('interfaces.html', namespaces) +render_template('fullindex.html', namespaces) + +dh_namespaces = { 'spec': spec, 'name': project } +render_template('devhelp.devhelp2', dh_namespaces, + target=('%s.devhelp2' % project)) + +# write out the TOC last, because this is the file used as the target in the +# Makefile. +render_template('index.html', namespaces) diff --git a/tools/identity.xsl b/tools/identity.xsl new file mode 100644 index 0000000..6630f84 --- /dev/null +++ b/tools/identity.xsl @@ -0,0 +1,7 @@ +<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"> + <xsl:template match="@*|node()"> + <xsl:copy> + <xsl:apply-templates select="@*|node()"/> + </xsl:copy> + </xsl:template> +</xsl:stylesheet> diff --git a/tools/specparser.py b/tools/specparser.py new file mode 100644 index 0000000..16b770e --- /dev/null +++ b/tools/specparser.py @@ -0,0 +1,885 @@ +# +# specparser.py +# +# Reads in a spec document and generates pretty data structures from it. +# +# Copyright (C) 2009 Collabora Ltd. +# +# This library is free software; you can redistribute it and/or modify it +# under the terms of the GNU Lesser General Public License as published by +# the Free Software Foundation; either version 2.1 of the License, or (at +# your option) any later version. +# +# This library is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +# FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License +# for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with this library; if not, write to the Free Software Foundation, +# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# Authors: Davyd Madeley <davyd.madeley@collabora.co.uk> +# + +import sys +import xml.dom.minidom + +import xincludator + +XMLNS_TP = 'http://telepathy.freedesktop.org/wiki/DbusSpec#extensions-v0' + +class UnknownAccess(Exception): pass +class UnknownDirection(Exception): pass +class UnknownType(Exception): pass +class UnnamedItem(Exception): pass +class UntypedItem(Exception): pass +class UnsupportedArray(Exception): pass +class BadNameForBindings(Exception): pass + +def getText(dom): + try: + if dom.childNodes[0].nodeType == dom.TEXT_NODE: + return dom.childNodes[0].data + else: + return '' + except IndexError: + return '' + +def getChildrenByName(dom, namespace, name): + return filter(lambda n: n.nodeType == n.ELEMENT_NODE and \ + n.namespaceURI == namespace and \ + n.localName == name, + dom.childNodes) + +def build_name(namespace, name): + """Returns a name by appending `name' to the namespace of this object. + """ + return '.'.join( + filter(lambda n: n is not None and n != '', + [namespace, name.replace(' ', '')]) + ) + +class Base(object): + """The base class for any type of XML node in the spec that implements the + 'name' attribute. + + Don't instantiate this class directly. + """ + devhelp_name = "" + + def __init__(self, parent, namespace, dom): + self.short_name = name = dom.getAttribute('name') + self.namespace = namespace + self.name = build_name(namespace, name) + self.parent = parent + + try: + self.docstring = getChildrenByName(dom, XMLNS_TP, 'docstring')[0] + except IndexError: + self.docstring = None + + try: + self.added = getChildrenByName(dom, XMLNS_TP, 'added')[0] + except IndexError: + self.added = None + + try: + self.deprecated = getChildrenByName(dom, XMLNS_TP, 'deprecated')[0] + except IndexError: + self.deprecated = None + + self.changed = getChildrenByName(dom, XMLNS_TP, 'changed') + + self.validate() + + def validate(self): + if self.short_name == '': + raise UnnamedItem("Node %s of %s has no name" % ( + self.__class__.__name__, self.parent)) + + def get_type_name(self): + return self.__class__.__name__ + + def get_spec(self): + return self.parent.get_spec() + + def get_root_namespace(self): + return self.get_interface().name + + def get_interface(self): + return self.parent.get_interface() + + def get_url(self): + return "%s#%s" % (self.get_interface().get_url(), self.name) + + def _get_generic_with_ver(self, nnode, htmlclass, txt): + if nnode is None: + return '' + else: + # make a copy of this node, turn it into a HTML <div> tag + node = nnode.cloneNode(True) + node.tagName = 'div' + node.baseURI = None + node.setAttribute('class', htmlclass) + + try: + node.removeAttribute('version') + + span = xml.dom.minidom.parseString( + ('<span class="version">%s\n</span>' % txt) % + nnode.getAttribute('version')).firstChild + node.insertBefore(span, node.firstChild) + except xml.dom.NotFoundErr: + print >> sys.stderr, \ + 'WARNING: %s was %s, but gives no version' % (self, htmlclass) + + self._convert_to_html(node) + + return node.toxml().encode('ascii', 'xmlcharrefreplace') + + def get_added(self): + return self._get_generic_with_ver(self.added, 'added', + "Added in %s.") + + def get_deprecated(self): + return self._get_generic_with_ver(self.deprecated, 'deprecated', + "Deprecated since %s.") + + def get_changed(self): + return '\n'.join(map(lambda n: + self._get_generic_with_ver(n, 'changed', "Changed in %s."), + self.changed)) + + def get_docstring(self): + """Get the docstring for this node, but do node substitution to + rewrite types, interfaces, etc. as links. + """ + if self.docstring is None: + return '' + else: + # make a copy of this node, turn it into a HTML <div> tag + node = self.docstring.cloneNode(True) + node.tagName = 'div' + node.baseURI = None + node.setAttribute('class', 'docstring') + + self._convert_to_html(node) + + return node.toxml().encode('ascii', 'xmlcharrefreplace') + + def _convert_to_html(self, node): + spec = self.get_spec() + namespace = self.get_root_namespace() + + # rewrite <tp:rationale> + for n in node.getElementsByTagNameNS(XMLNS_TP, 'rationale'): + n.tagName = 'div' + n.namespaceURI = None + n.setAttribute('class', 'rationale') + + # rewrite <tp:type> + for n in node.getElementsByTagNameNS(XMLNS_TP, 'type'): + t = spec.lookup_type(getText(n)) + n.tagName = 'a' + n.namespaceURI = None + n.setAttribute('href', t.get_url()) + + # rewrite <tp:member-ref> + for n in node.getElementsByTagNameNS(XMLNS_TP, 'member-ref'): + key = getText(n) + try: + o = spec.lookup(key, namespace=namespace) + except KeyError: + print >> sys.stderr, \ + "WARNING: Key '%s' not known in namespace '%s'" % ( + key, namespace) + continue + + n.tagName = 'a' + n.namespaceURI = None + n.setAttribute('href', o.get_url()) + n.setAttribute('title', o.get_title()) + + # rewrite <tp:dbus-ref> + for n in node.getElementsByTagNameNS(XMLNS_TP, 'dbus-ref'): + namespace = n.getAttribute('namespace') + key = getText(n) + try: + o = spec.lookup(key, namespace=namespace) + except KeyError: + print >> sys.stderr, \ + "WARNING: Key '%s' not known in namespace '%s'" % ( + key, namespace) + continue + + n.tagName = 'a' + n.namespaceURI = None + n.setAttribute('href', o.get_url()) + n.setAttribute('title', o.get_title()) + + def get_title(self): + return '%s %s' % (self.get_type_name(), self.name) + + def __repr__(self): + return '%s(%s)' % (self.__class__.__name__, self.name) + +class DBusConstruct(Base): + """Base class for signals, methods and properties.""" + + def __init__(self, parent, namespace, dom): + super(DBusConstruct, self).__init__(parent, namespace, dom) + + self.name_for_bindings = dom.getAttributeNS(XMLNS_TP, + 'name-for-bindings') + + if not self.name_for_bindings: + raise BadNameForBindings('%s has no name-for-bindings' + % self) + + if self.name_for_bindings.replace('_', '') != self.short_name: + raise BadNameForBindings('%s name-for-bindings = %s does not ' + 'match short_name = %s' % (self, self.name_for_bindings, + self.short_name)) + +class PossibleError(Base): + def __init__(self, parent, namespace, dom): + super(PossibleError, self).__init__(parent, namespace, dom) + + def get_error(self): + spec = self.get_spec() + try: + return spec.errors[self.name] + except KeyError: + return External(self.name) + + def get_url(self): + return self.get_error().get_url() + + def get_title(self): + return self.get_error().get_title() + + def get_docstring(self): + d = super(PossibleError, self).get_docstring() + if d == '': + return self.get_error().get_docstring() + else: + return d + +class Method(DBusConstruct): + devhelp_name = "function" + + def __init__(self, parent, namespace, dom): + super(Method, self).__init__(parent, namespace, dom) + + args = build_list(self, Arg, self.name, + dom.getElementsByTagName('arg')) + + # separate arguments as input and output arguments + self.in_args = filter(lambda a: a.direction == Arg.DIRECTION_IN, args) + self.out_args = filter(lambda a: a.direction == Arg.DIRECTION_OUT, args) + + for arg in args: + if arg.direction == Arg.DIRECTION_IN or \ + arg.direction == Arg.DIRECTION_OUT: + continue + + print >> sys.stderr, "WARNING: '%s' of method '%s' does not specify a suitable direction" % (arg, self) + + self.possible_errors = build_list(self, PossibleError, None, + dom.getElementsByTagNameNS(XMLNS_TP, 'error')) + + def get_in_args(self): + return ', '.join(map(lambda a: a.spec_name(), self.in_args)) + + def get_out_args(self): + if len(self.out_args) > 0: + return ', '.join(map(lambda a: a.spec_name(), self.out_args)) + else: + return 'nothing' + +class Typed(Base): + """The base class for all typed nodes (i.e. Arg and Property). + + Don't instantiate this class directly. + """ + + def __init__(self, parent, namespace, dom): + super(Typed, self).__init__(parent, namespace, dom) + + self.type = dom.getAttributeNS(XMLNS_TP, 'type') + self.dbus_type = dom.getAttribute('type') + + # check we have a dbus type + if self.dbus_type == '': + raise UntypedItem("Node referred to by '%s' has no type" % dom.toxml()) + def get_type(self): + return self.get_spec().lookup_type(self.type) + + def get_type_url(self): + t = self.get_type() + if t is None: return '' + else: return t.get_url() + + def get_type_title(self): + t = self.get_type() + if t is None: return '' + else: return t.get_title() + + def spec_name(self): + return '%s: %s' % (self.dbus_type, self.short_name) + + def __repr__(self): + return '%s(%s:%s)' % (self.__class__.__name__, self.name, self.dbus_type) + +class Property(DBusConstruct, Typed): + ACCESS_READ = 1 + ACCESS_WRITE = 2 + + ACCESS_READWRITE = ACCESS_READ | ACCESS_WRITE + + def __init__(self, parent, namespace, dom): + super(Property, self).__init__(parent, namespace, dom) + + access = dom.getAttribute('access') + if access == 'read': + self.access = self.ACCESS_READ + elif access == 'write': + self.access = self.ACCESS_WRITE + elif access == 'readwrite': + self.access = self.ACCESS_READWRITE + else: + raise UnknownAccess("Unknown access '%s' on %s" % (access, self)) + + def get_access(self): + if self.access & self.ACCESS_READ and self.access & self.ACCESS_WRITE: + return 'Read/Write' + elif self.access & self.ACCESS_READ: + return 'Read only' + elif self.access & self.ACCESS_WRITE: + return 'Write only' + +class AwkwardTelepathyProperty(Typed): + def get_type_name(self): + return 'Telepathy Property' + +class Arg(Typed): + DIRECTION_IN, DIRECTION_OUT, DIRECTION_UNSPECIFIED = range(3) + + def __init__(self, parent, namespace, dom): + super(Arg, self).__init__(parent, namespace, dom) + + direction = dom.getAttribute('direction') + if direction == 'in': + self.direction = self.DIRECTION_IN + elif direction == 'out': + self.direction = self.DIRECTION_OUT + elif direction == '': + self.direction = self.DIRECTION_UNSPECIFIED + else: + raise UnknownDirection("Unknown direction '%s' on %s" % ( + direction, self.parent)) + +class Signal(DBusConstruct): + def __init__(self, parent, namespace, dom): + super(Signal, self).__init__(parent, namespace, dom) + + self.args = build_list(self, Arg, self.name, + dom.getElementsByTagName('arg')) + + for arg in self.args: + if arg.direction == Arg.DIRECTION_UNSPECIFIED: + continue + + print >> sys.stderr, "WARNING: '%s' of signal '%s' does not specify a suitable direction" % (arg, self) + + def get_args(self): + return ', '.join(map(lambda a: a.spec_name(), self.args)) + +class External(object): + """External objects are objects that are referred to in another spec. + + We have to attempt to look them up if at all possible. + """ + + def __init__(self, name): + self.name = self.short_name = name + + def get_url(self): + return None + + def get_title(self): + return 'External %s' % self.name + + def get_docstring(self): + return None + + def __repr__(self): + return '%s(%s)' % (self.__class__.__name__, self.name) + +class Interface(Base): + def __init__(self, parent, namespace, dom, spec_namespace): + super(Interface, self).__init__(parent, namespace, dom) + + # If you're writing a spec with more than one top-level namespace, you + # probably want to replace spec_namespace with a list. + if self.name.startswith(spec_namespace + "."): + self.short_name = self.name[len(spec_namespace) + 1:] + else: + self.short_name = self.name + + # build lists of methods, etc., in this interface + self.methods = build_list(self, Method, self.name, + dom.getElementsByTagName('method')) + self.properties = build_list(self, Property, self.name, + dom.getElementsByTagName('property')) + self.signals = build_list(self, Signal, self.name, + dom.getElementsByTagName('signal')) + self.tpproperties = build_list(self, AwkwardTelepathyProperty, + self.name, dom.getElementsByTagNameNS(XMLNS_TP, 'property')) + self.handler_capability_tokens = build_list(self, + HandlerCapabilityToken, self.name, + dom.getElementsByTagNameNS(XMLNS_TP, + 'handler-capability-token')) + self.contact_attributes = build_list(self, ContactAttribute, self.name, + dom.getElementsByTagNameNS(XMLNS_TP, 'contact-attribute')) + + # build a list of types in this interface + self.types = parse_types(self, dom, self.name) + + # find out if this interface causes havoc + self.causes_havoc = dom.getAttributeNS(XMLNS_TP, 'causes-havoc') + if self.causes_havoc == '': self.causes_havoc = None + + # find out what we're required to also implement + self.requires = map(lambda n: n.getAttribute('interface'), + getChildrenByName(dom, XMLNS_TP, 'requires')) + + def get_interface(self): + return self + + def get_requires(self): + spec = self.get_spec() + + def lookup(r): + try: + return spec.lookup(r) + except KeyError: + return External(r) + + return map(lookup, self.requires) + + def get_url(self): + return '%s.html' % self.name + +class Error(Base): + def get_url(self): + return 'errors.html#%s' % self.name + + def get_root_namespace(self): + return self.namespace + +class DBusList(object): + """Stores a list of a given DBusType. Provides some basic validation to + determine whether or not the type is sane. + """ + def __init__(self, child): + self.child = child + + if isinstance(child, DBusType): + self.ultimate = child + self.depth = 1 + + if self.child.array_name == '': + raise UnsupportedArray("Type '%s' does not support being " + "used in an array" % self.child.name) + else: + self.name = build_name(self.child.namespace, + self.child.array_name) + self.short_name = self.child.array_name + + elif isinstance(child, DBusList): + self.ultimate = child.ultimate + self.depth = child.depth + 1 + self.name = self.child.name + '_List' + self.short_name = self.child.short_name + '_List' + + # check that our child can operate at this depth + maxdepth = int(self.ultimate.array_depth) + if self.depth > maxdepth: + raise TypeError("Type '%s' has exceeded its maximum depth (%i)" % (self, maxdepth)) + + else: + raise TypeError("DBusList can contain only a DBusType or DBusList not '%s'" % child) + + self.dbus_type = 'a' + self.child.dbus_type + + def get_url(self): + return self.ultimate.get_url() + + def get_title(self): + return "Array of %s" % self.child.get_title() + + def __repr__(self): + return 'Array(%s)' % self.child + +class DBusType(Base): + """The base class for all D-Bus types referred to in the spec. + + Don't instantiate this class directly. + """ + + devhelp_name = "typedef" + + def __init__(self, parent, namespace, dom): + super(DBusType, self).__init__(parent, namespace, dom) + + self.dbus_type = dom.getAttribute('type') + self.array_name = dom.getAttribute('array-name') + self.array_depth = dom.getAttribute('array-depth') + + def get_root_namespace(self): + return self.namespace + + def get_breakdown(self): + return '' + + def get_url(self): + if isinstance(self.parent, Interface): + html = self.parent.get_url() + else: + html = 'generic-types.html' + + return '%s#%s' % (html, self.name) + +class SimpleType(DBusType): + def get_type_name(self): + return 'Simple Type' + +class ExternalType(DBusType): + def __init__(self, parent, namespace, dom): + super(ExternalType, self).__init__(parent, namespace, dom) + + # FIXME: until we are able to cross reference external types to learn + # about their array names, we're just going to assume they work like + # this + self.array_name = self.short_name + '_List' + + def get_type_name(self): + return 'External Type' + +class StructLike(DBusType): + """Base class for all D-Bus types that look kind of like Structs + + Don't instantiate this class directly. + """ + + class StructMember(Typed): + def get_root_namespace(self): + return self.parent.get_root_namespace() + + def __init__(self, parent, namespace, dom): + super(StructLike, self).__init__(parent, namespace, dom) + + self.members = build_list(self, StructLike.StructMember, None, + dom.getElementsByTagNameNS(XMLNS_TP, 'member')) + + def get_breakdown(self): + str = '' + str += '<ul>\n' + for member in self.members: + # attempt to lookup the member up in the type system + t = member.get_type() + + str += '<li>%s — %s' % (member.name, member.dbus_type) + if t: str += ' (<a href="%s" title="%s">%s</a>)' % ( + t.get_url(), t.get_title(), t.short_name) + str += '</li>\n' + str += member.get_docstring() + str += '</ul>\n' + + return str + +class Mapping(StructLike): + def __init__(self, parent, namespace, dom): + super(Mapping, self).__init__(parent, namespace, dom) + + # rewrite the D-Bus type + self.dbus_type = 'a{%s}' % ''.join(map(lambda m: m.dbus_type, self.members)) + +class Struct(StructLike): + + devhelp_name = "struct" + + def __init__(self, parent, namespace, dom): + super(Struct, self).__init__(parent, namespace, dom) + + # rewrite the D-Bus type + self.dbus_type = '(%s)' % ''.join(map(lambda m: m.dbus_type, self.members)) + +class EnumLike(DBusType): + """Base class for all D-Bus types that look kind of like Enums + + Don't instantiate this class directly. + """ + class EnumValue(Base): + def __init__(self, parent, namespace, dom): + super(EnumLike.EnumValue, self).__init__(parent, namespace, dom) + + # rewrite self.name + self.short_name = dom.getAttribute('suffix') + self.name = build_name(namespace, self.short_name) + + self.value = dom.getAttribute('value') + + super(EnumLike.EnumValue, self).validate() + + def validate(self): + pass + + def get_root_namespace(self): + return self.parent.get_root_namespace() + + def get_breakdown(self): + str = '' + str += '<ul>\n' + for value in self.values: + # attempt to lookup the member.name as a type in the type system + str += '<li>%s (%s)</li>\n' % (value.short_name, value.value) + str += value.get_added() + str += value.get_changed() + str += value.get_deprecated() + str += value.get_docstring() + str += '</ul>\n' + + return str + +class Enum(EnumLike): + + devhelp_name = "enum" + + def __init__(self, parent, namespace, dom): + super(Enum, self).__init__(parent, namespace, dom) + + self.values = build_list(self, EnumLike.EnumValue, self.name, + dom.getElementsByTagNameNS(XMLNS_TP, 'enumvalue')) + +class Flags(EnumLike): + def __init__(self, parent, namespace, dom): + super(Flags, self).__init__(parent, namespace, dom) + + self.values = build_list(self, EnumLike.EnumValue, self.name, + dom.getElementsByTagNameNS(XMLNS_TP, 'flag')) + self.flags = self.values # in case you're looking for it + +class TokenBase(Base): + + devhelp_name = "macro" # it's a constant, which is near enough... + separator = '/' + + def __init__(self, parent, namespace, dom): + super(TokenBase, self).__init__(parent, namespace, dom) + self.name = namespace + '/' + self.short_name + +class ContactAttribute(TokenBase, Typed): + + def get_type_name(self): + return 'Contact Attribute' + +class HandlerCapabilityToken(TokenBase): + + def get_type_name(self): + return 'Handler Capability Token' + + def __init__(self, parent, namespace, dom): + super(HandlerCapabilityToken, self).__init__(parent, namespace, dom) + + is_family = dom.getAttribute('is-family') + assert is_family in ('yes', 'no', '') + self.is_family = (is_family == 'yes') + +class SectionBase(object): + """A SectionBase is an abstract base class for any type of node that can + contain a <tp:section>, which means the top-level Spec object, or any + Section object. + + It should not be instantiated directly. + """ + + def __init__(self, dom, spec_namespace): + + self.items = [] + + def recurse(nodes): + # iterate through the list of child nodes + for node in nodes: + if node.nodeType != node.ELEMENT_NODE: continue + + if node.tagName == 'node': + # recurse into this level for interesting items + recurse(node.childNodes) + elif node.namespaceURI == XMLNS_TP and \ + node.localName == 'section': + self.items.append(Section(self, None, node, + spec_namespace)) + elif node.tagName == 'interface': + self.items.append(Interface(self, None, node, + spec_namespace)) + + recurse(dom.childNodes) + +class Section(Base, SectionBase): + def __init__(self, parent, namespace, dom, spec_namespace): + Base.__init__(self, parent, namespace, dom) + SectionBase.__init__(self, dom, spec_namespace) + + def get_root_namespace(self): + return None + +class Spec(SectionBase): + def __init__(self, dom, spec_namespace): + # build a dictionary of errors in this spec + try: + errorsnode = dom.getElementsByTagNameNS(XMLNS_TP, 'errors')[0] + self.errors = build_dict(self, Error, + errorsnode.getAttribute('namespace'), + errorsnode.getElementsByTagNameNS(XMLNS_TP, 'error')) + except IndexError: + self.errors = {} + + # build a list of generic types + self.generic_types = reduce (lambda a, b: a + b, + map(lambda l: parse_types(self, l), + dom.getElementsByTagNameNS(XMLNS_TP, 'generic-types')), + []) + + # create a top-level section for this Spec + SectionBase.__init__(self, dom.documentElement, spec_namespace) + + # build a list of interfaces in this spec + self.interfaces = [] + def recurse(items): + for item in items: + if isinstance(item, Section): recurse(item.items) + elif isinstance(item, Interface): self.interfaces.append(item) + recurse(self.items) + + # build a giant dictionary of everything (interfaces, methods, signals + # and properties); also build a dictionary of types + self.everything = {} + self.types = {} + + for type in self.generic_types: self.types[type.short_name] = type + + for interface in self.interfaces: + self.everything[interface.name] = interface + + for method in interface.methods: + self.everything[method.name] = method + for signal in interface.signals: + self.everything[signal.name] = signal + for property in interface.properties: + self.everything[property.name] = property + for property in interface.tpproperties: + self.everything[property.name] = property + for token in interface.contact_attributes: + self.everything[token.name] = token + for token in interface.handler_capability_tokens: + self.everything[token.name] = token + + for type in interface.types: + self.types[type.short_name] = type + + # get some extra bits for the HTML + node = dom.getElementsByTagNameNS(XMLNS_TP, 'spec')[0] + self.title = getText(getChildrenByName(node, XMLNS_TP, 'title')[0]) + + try: + self.version = getText(getChildrenByName(node, XMLNS_TP, 'version')[0]) + except IndexError: + self.version = None + + self.copyrights = map(getText, + getChildrenByName(node, XMLNS_TP, 'copyright')) + + try: + license = getChildrenByName(node, XMLNS_TP, 'license')[0] + license.tagName = 'div' + license.namespaceURI = None + license.setAttribute('class', 'license') + self.license = license.toxml() + except IndexError: + self.license = '' + + # FIXME: we need to check all args for type correctness + + def get_spec(self): + return self + + def lookup(self, name, namespace=None): + key = build_name(namespace, name) + return self.everything[key] + + def lookup_type(self, type_): + if type_.endswith('[]'): + return DBusList(self.lookup_type(type_[:-2])) + + if type_ == '': return None + elif type_ in self.types: + return self.types[type_] + + raise UnknownType("Type '%s' is unknown" % type_) + + def __repr__(self): + return '%s(%s)' % (self.__class__.__name__, self.title) + +def build_dict(parent, type_, namespace, nodes): + """Build a dictionary of D-Bus names to Python objects representing that + name using the XML node for that item in the spec. + + e.g. 'org.freedesktop.Telepathy.Channel' : Interface(Channel) + + Works for any Python object inheriting from 'Base' whose XML node + implements the 'name' attribute. + """ + + def build_tuple(node): + o = type_(parent, namespace, node) + return(o.name, o) + + return dict(build_tuple(n) for n in nodes) + +def build_list(parent, type_, namespace, nodes): + return map(lambda node: type_(parent, namespace, node), nodes) + +def parse_types(parent, dom, namespace = None): + """Parse all of the types of type nodes mentioned in 't' from the node + 'dom' and insert them into the dictionary 'd'. + """ + t = [ + (SimpleType, 'simple-type'), + (Enum, 'enum'), + (Flags, 'flags'), + (Mapping, 'mapping'), + (Struct, 'struct'), + (ExternalType, 'external-type'), + ] + + types = [] + + for (type_, tagname) in t: + types += build_list(parent, type_, namespace, + dom.getElementsByTagNameNS(XMLNS_TP, tagname)) + + return types + +def parse(filename, spec_namespace): + dom = xml.dom.minidom.parse(filename) + xincludator.xincludate(dom, filename) + + spec = Spec(dom, spec_namespace) + + return spec + +if __name__ == '__main__': + parse(sys.argv[1]) |