File indexing completed on 2024-04-28 16:13:13

0001 """Library code for Qt D-Bus-related code generation.
0002 
0003 The master copy of this library is in the telepathy-qt repository -
0004 please make any changes there.
0005 """
0006 
0007 # Copyright (C) 2008 Collabora Limited <http://www.collabora.co.uk>
0008 # Copyright (C) 2008 Nokia Corporation
0009 #
0010 # This library is free software; you can redistribute it and/or
0011 # modify it under the terms of the GNU Lesser General Public
0012 # License as published by the Free Software Foundation; either
0013 # version 2.1 of the License, or (at your option) any later version.
0014 #
0015 # This library is distributed in the hope that it will be useful,
0016 # but WITHOUT ANY WARRANTY; without even the implied warranty of
0017 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
0018 # Lesser General Public License for more details.
0019 #
0020 # You should have received a copy of the GNU Lesser General Public
0021 # License along with this library; if not, write to the Free Software
0022 # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
0023 
0024 from sys import maxint, stderr
0025 import re
0026 from libtpcodegen import get_by_path, get_descendant_text, NS_TP, xml_escape
0027 
0028 class Xzibit(Exception):
0029     def __init__(self, parent, child):
0030         self.parent = parent
0031         self.child = child
0032 
0033     def __str__(self):
0034         print """
0035     Nested <%s>s are forbidden.
0036     Parent:
0037         %s...
0038     Child:
0039         %s...
0040         """ % (self.parent.nodeName, self.parent.toxml()[:100],
0041                self.child.toxml()[:100])
0042 
0043 class _QtTypeBinding:
0044     def __init__(self, val, inarg, outarg, array_val, custom_type, array_of,
0045             array_depth=None):
0046         self.val = val
0047         self.inarg = inarg
0048         self.outarg = outarg
0049         self.array_val = array_val
0050         self.custom_type = custom_type
0051         self.array_of = array_of
0052         self.array_depth = array_depth
0053 
0054         if array_depth is None:
0055             self.array_depth = int(bool(array_val))
0056         elif array_depth >= 1:
0057             assert array_val
0058         else:
0059             assert not array_val
0060 
0061 class RefTarget(object):
0062     KIND_INTERFACE, KIND_METHOD, KIND_SIGNAL, KIND_PROPERTY = 'node', 'method', 'signal', 'property'
0063 
0064     def __init__(self, el):
0065         self.kind = el.localName
0066         assert self.kind in (self.KIND_INTERFACE, self.KIND_METHOD, self.KIND_SIGNAL, self.KIND_PROPERTY)
0067 
0068         if self.kind == self.KIND_INTERFACE:
0069             self.dbus_text = el.getAttribute('name').lstrip('/').replace('_', '') + 'Interface'
0070         else:
0071             self.member_text = el.getAttribute('name')
0072 
0073             assert el.parentNode.parentNode.localName == self.KIND_INTERFACE
0074             host_class = el.parentNode.parentNode.getAttribute('name').lstrip('/').replace('_', '') + 'Interface'
0075 
0076             if self.kind == self.KIND_PROPERTY:
0077                 self.member_link = 'requestProperty%s()' % (self.member_text)
0078                 self.dbus_link = '%s::%s' % (host_class, self.member_link)
0079             else:
0080                 self.member_text = '%s()' % self.member_text
0081 
0082             self.dbus_text = '%s::%s' % (host_class, self.member_text)
0083 
0084 class RefRegistry(object):
0085     def __init__(self, spec):
0086         self.targets = {}
0087         for node in spec.getElementsByTagName('node'):
0088             iface, = get_by_path(node, 'interface')
0089             iface_name = iface.getAttribute('name')
0090 
0091             self.targets[iface_name] = RefTarget(node)
0092 
0093             for method in iface.getElementsByTagName(RefTarget.KIND_METHOD):
0094                 self.targets[iface_name + '.' + method.getAttribute('name')] = RefTarget(method)
0095 
0096             for signal in iface.getElementsByTagName(RefTarget.KIND_SIGNAL):
0097                 self.targets[iface_name + '.' + signal.getAttribute('name')] = RefTarget(signal)
0098 
0099             for prop in iface.getElementsByTagName(RefTarget.KIND_PROPERTY):
0100                 self.targets[iface_name + '.' + prop.getAttribute('name')] = RefTarget(prop)
0101 
0102     def process(self, ref):
0103         assert ref.namespaceURI == NS_TP
0104 
0105         def get_closest_parent(el, needle):
0106             node = el
0107             while node is not None and node.localName != needle:
0108                 node = node.parentNode
0109             return node
0110 
0111         local = get_descendant_text(ref).strip()
0112         if ref.localName == 'member-ref':
0113             ns = get_closest_parent(ref, 'interface').getAttribute('name')
0114             path = ns + '.' + local.strip()
0115         else:
0116             if ref.hasAttribute('namespace'):
0117                 ns = ref.getAttribute('namespace').replace('ofdT', 'org.freedesktop.Telepathy')
0118                 path = ns + '.' + local.strip()
0119             else:
0120                 path = local
0121 
0122         target = self.targets.get(path)
0123 
0124         if target is None:
0125             parent = get_closest_parent(ref, 'interface') or get_closest_parent(ref, 'error')
0126             parent_name = parent.getAttribute('name')
0127             if (path + parent_name).find('.DRAFT') == -1 and (path + parent_name).find('.FUTURE') == -1:
0128                 print >> stderr, 'WARNING: Failed to resolve %s to "%s" in "%s"' % (
0129                         ref.localName, path, parent_name)
0130             return path
0131 
0132         if ref.localName == 'member-ref':
0133             if target.kind == target.KIND_PROPERTY:
0134                 return '\\link %s %s \\endlink' % (target.member_link, target.member_text)
0135             else:
0136                 return target.member_text
0137         else:
0138             if target.kind == target.KIND_PROPERTY:
0139                 return '\\link %s %s \\endlink' % (target.dbus_link, target.dbus_text)
0140             else:
0141                 return target.dbus_text
0142 
0143 def binding_from_usage(sig, tptype, custom_lists, external=False, explicit_own_ns=None):
0144     # 'signature' : ('qt-type', 'pass-by-reference', 'array-type')
0145     natives = {
0146             'y' : ('uchar', False, None),
0147             'b' : ('bool', False, 'BoolList'),
0148             'n' : ('short', False, 'ShortList'),
0149             'q' : ('ushort', False, 'UShortList'),
0150             'i' : ('int', False, 'IntList'),
0151             'u' : ('uint', False, 'UIntList'),
0152             'x' : ('qlonglong', False, 'LongLongList'),
0153             't' : ('qulonglong', False, 'ULongLongList'),
0154             'd' : ('double', False, 'DoubleList'),
0155             's' : ('QString', True, None),
0156             'v' : ('QDBusVariant', True, None),
0157             'o' : ('QDBusObjectPath', True, 'ObjectPathList'),
0158             'g' : ('QDBusSignature', True, 'SignatureList'),
0159             'as' : ('QStringList', True, "StringListList"),
0160             'ay' : ('QByteArray', True, "ByteArrayList"),
0161             'av' : ('QVariantList', True, "VariantListList"),
0162             'a{sv}' : ('QVariantMap', True, None)
0163             }
0164 
0165     val, inarg = None, None
0166     custom_type = False
0167     array_of = None
0168 
0169     if natives.has_key(sig):
0170         typename, pass_by_ref, array_name = natives[sig]
0171         val = typename
0172         inarg = (pass_by_ref and ('const %s&' % val)) or val
0173     elif sig[0] == 'a' and natives.has_key(sig[1:]) and natives[sig[1:]][2]:
0174         val = natives[sig[1:]][2]
0175         if explicit_own_ns:
0176             val = explicit_own_ns + '::' + val
0177         inarg = 'const %s&' % val
0178         array_of = natives[sig[1:]][0]
0179     elif tptype:
0180         tptype = tptype.replace('_', '')
0181         custom_type = True
0182 
0183         if external:
0184             tptype = 'Tp::' + tptype
0185         elif explicit_own_ns:
0186             tptype = explicit_own_ns + '::' + tptype
0187 
0188         if tptype.endswith('[]'):
0189             tptype = tptype[:-2]
0190             extra_list_nesting = 0
0191 
0192             while tptype.endswith('[]'):
0193                 extra_list_nesting += 1
0194                 tptype = tptype[:-2]
0195 
0196             assert custom_lists.has_key(tptype), ('No array version of custom type %s in the spec, but array version used' % tptype)
0197             val = custom_lists[tptype] + 'List' * extra_list_nesting
0198         else:
0199             val = tptype
0200 
0201         inarg = 'const %s&' % val
0202     else:
0203         assert False, 'Don\'t know how to map type (%s, %s)' % (sig, tptype)
0204 
0205     outarg = val + '&'
0206     return _QtTypeBinding(val, inarg, outarg, None, custom_type, array_of)
0207 
0208 def binding_from_decl(name, array_name, array_depth=None, external=False, explicit_own_ns=''):
0209     val = name.replace('_', '')
0210     if external:
0211         val = 'Tp::' + val
0212     elif explicit_own_ns:
0213         val = explicit_own_ns + '::' + val
0214     inarg = 'const %s&' % val
0215     outarg = '%s&' % val
0216     return _QtTypeBinding(val, inarg, outarg, array_name.replace('_', ''), True, None, array_depth)
0217 
0218 def extract_arg_or_member_info(els, custom_lists, externals, typesns, refs, docstring_indent=' * ', docstring_brackets=None, docstring_maxwidth=80):
0219     names = []
0220     docstrings = []
0221     bindings = []
0222 
0223     for el in els:
0224         names.append(get_qt_name(el))
0225         docstrings.append(format_docstring(el, refs, docstring_indent, docstring_brackets, docstring_maxwidth))
0226 
0227         sig = el.getAttribute('type')
0228         tptype = el.getAttributeNS(NS_TP, 'type')
0229         bindings.append(binding_from_usage(sig, tptype, custom_lists, (sig, tptype) in externals, typesns))
0230 
0231     return names, docstrings, bindings
0232 
0233 def format_docstring(el, refs, indent=' * ', brackets=None, maxwidth=80):
0234     docstring_el = None
0235 
0236     for x in el.childNodes:
0237         if x.namespaceURI == NS_TP and x.localName == 'docstring':
0238             docstring_el = x
0239 
0240     if not docstring_el:
0241         return ''
0242 
0243     lines = []
0244 
0245     # escape backslashes, so they won't be interpreted starting doxygen commands and we can later
0246     # insert doxygen commands we actually want
0247     def escape_slashes(x):
0248         if x.nodeType == x.TEXT_NODE:
0249             x.data = x.data.replace('\\', '\\\\')
0250         elif x.nodeType == x.ELEMENT_NODE:
0251             for y in x.childNodes:
0252                 escape_slashes(y)
0253         else:
0254             return
0255 
0256     escape_slashes(docstring_el)
0257     doc = docstring_el.ownerDocument
0258 
0259     for n in docstring_el.getElementsByTagNameNS(NS_TP, 'rationale'):
0260         nested = n.getElementsByTagNameNS(NS_TP, 'rationale')
0261         if nested:
0262             raise Xzibit(n, nested[0])
0263 
0264         div = doc.createElement('div')
0265         div.setAttribute('class', 'rationale')
0266 
0267         for rationale_body in n.childNodes:
0268             div.appendChild(rationale_body.cloneNode(True))
0269 
0270         n.parentNode.replaceChild(div, n)
0271 
0272     if docstring_el.getAttribute('xmlns') == 'http://www.w3.org/1999/xhtml':
0273         for ref in docstring_el.getElementsByTagNameNS(NS_TP, 'member-ref') + docstring_el.getElementsByTagNameNS(NS_TP, 'dbus-ref'):
0274             nested = ref.getElementsByTagNameNS(NS_TP, 'member-ref') + ref.getElementsByTagNameNS(NS_TP, 'dbus-ref')
0275             if nested:
0276                 raise Xzibit(n, nested[0])
0277 
0278             text = doc.createTextNode(' \\endhtmlonly ')
0279             text.data += refs.process(ref)
0280             text.data += ' \\htmlonly '
0281 
0282             ref.parentNode.replaceChild(text, ref)
0283 
0284         splitted = ''.join([el.toxml() for el in docstring_el.childNodes]).strip(' ').strip('\n').split('\n')
0285         level = min([not match and maxint or match.end() - 1 for match in [re.match('^ *[^ ]', line) for line in splitted]])
0286         assert level != maxint
0287         lines = ['\\htmlonly'] + [line[level:] for line in splitted] + ['\\endhtmlonly']
0288     else:
0289         content = xml_escape(get_descendant_text(docstring_el).replace('\n', ' ').strip())
0290 
0291         while content.find('  ') != -1:
0292             content = content.replace('  ', ' ')
0293 
0294         left = maxwidth - len(indent) - 1
0295         line = ''
0296 
0297         while content:
0298             step = (content.find(' ') + 1) or len(content)
0299 
0300             if step > left:
0301                 lines.append(line)
0302                 line = ''
0303                 left = maxwidth - len(indent) - 1
0304 
0305             left = left - step
0306             line = line + content[:step]
0307             content = content[step:]
0308 
0309         if line:
0310             lines.append(line)
0311 
0312     output = []
0313 
0314     if lines:
0315         if brackets:
0316             output.append(brackets[0])
0317         else:
0318             output.append(indent)
0319 
0320         output.append('\n')
0321 
0322     for line in lines:
0323         output.append(indent)
0324         output.append(line)
0325         output.append('\n')
0326 
0327     if lines and brackets:
0328         output.append(brackets[1])
0329         output.append('\n')
0330 
0331     return ''.join(output)
0332 
0333 def gather_externals(spec):
0334     externals = []
0335 
0336     for ext in spec.getElementsByTagNameNS(NS_TP, 'external-type'):
0337         sig = ext.getAttribute('type')
0338         tptype = ext.getAttribute('name')
0339         externals.append((sig, tptype))
0340 
0341     return externals
0342 
0343 def gather_custom_lists(spec, typesns):
0344     custom_lists = {}
0345     structs = [(provider, typesns) for provider in spec.getElementsByTagNameNS(NS_TP, 'struct')]
0346     mappings = [(provider, typesns) for provider in spec.getElementsByTagNameNS(NS_TP, 'mapping')]
0347     exts = [(provider, 'Telepathy') for provider in spec.getElementsByTagNameNS(NS_TP, 'external-type')]
0348 
0349     for (provider, ns) in structs + mappings + exts:
0350         tptype = provider.getAttribute('name').replace('_', '')
0351         array_val = provider.getAttribute('array-name').replace('_', '')
0352         array_depth = provider.getAttribute('array-depth')
0353         if array_depth:
0354             array_depth = int(array_depth)
0355         else:
0356             array_depth = None
0357 
0358         if array_val:
0359             custom_lists[tptype] = array_val
0360             custom_lists[ns + '::' + tptype] = ns + '::' + array_val
0361             if array_depth >= 2:
0362                 for i in xrange(array_depth):
0363                     custom_lists[tptype + ('[]' * (i+1))] = (
0364                             array_val + ('List' * i))
0365                     custom_lists[ns + '::' + tptype + ('[]' * (i+1))] = (
0366                             ns + '::' + array_val + ('List' * i))
0367 
0368     return custom_lists
0369 
0370 def get_headerfile_cmd(realinclude, prettyinclude, indent=' * '):
0371     prettyinclude = prettyinclude or realinclude
0372     if realinclude:
0373         if prettyinclude:
0374             return indent + ('\\headerfile %s <%s>\n' % (realinclude, prettyinclude))
0375         else:
0376             return indent + ('\\headerfile %s <%s>\n' % (realinclude))
0377     else:
0378         return ''
0379 
0380 def get_qt_name(el):
0381     name = el.getAttribute('name')
0382 
0383     if el.localName in ('method', 'signal', 'property'):
0384         bname = el.getAttributeNS(NS_TP, 'name-for-bindings')
0385 
0386         if bname:
0387             name = bname
0388 
0389     if not name:
0390         return None
0391 
0392     if name[0].isupper() and name[1].islower():
0393         name = name[0].lower() + name[1:]
0394 
0395     return qt_identifier_escape(name.replace('_', ''))
0396 
0397 def qt_identifier_escape(str):
0398     built = (str[0].isdigit() and ['_']) or []
0399 
0400     for c in str:
0401         if c.isalnum():
0402             built.append(c)
0403         else:
0404             built.append('_')
0405 
0406     str = ''.join(built)
0407 
0408     # List of reserved identifiers
0409     # Initial list from http://cs.smu.ca/~porter/csc/ref/cpp_keywords.html
0410 
0411     # Keywords inherited from C90
0412     reserved = ['auto',
0413                 'const',
0414                 'double',
0415                 'float',
0416                 'int',
0417                 'short',
0418                 'struct',
0419                 'unsigned',
0420                 'break',
0421                 'continue',
0422                 'else',
0423                 'for',
0424                 'long',
0425                 'signed',
0426                 'switch',
0427                 'void',
0428                 'case',
0429                 'default',
0430                 'enum',
0431                 'goto',
0432                 'register',
0433                 'sizeof',
0434                 'typedef',
0435                 'volatile',
0436                 'char',
0437                 'do',
0438                 'extern',
0439                 'if',
0440                 'return',
0441                 'static',
0442                 'union',
0443                 'while',
0444     # C++-only keywords
0445                 'asm',
0446                 'dynamic_cast',
0447                 'namespace',
0448                 'reinterpret_cast',
0449                 'try',
0450                 'bool',
0451                 'explicit',
0452                 'new',
0453                 'static_cast',
0454                 'typeid',
0455                 'catch',
0456                 'false',
0457                 'operator',
0458                 'template',
0459                 'typename',
0460                 'class',
0461                 'friend',
0462                 'private',
0463                 'this',
0464                 'using',
0465                 'const_cast',
0466                 'inline',
0467                 'public',
0468                 'throw',
0469                 'virtual',
0470                 'delete',
0471                 'mutable',
0472                 'protected',
0473                 'true',
0474                 'wchar_t',
0475     # Operator replacements
0476                 'and',
0477                 'bitand',
0478                 'compl',
0479                 'not_eq',
0480                 'or_eq',
0481                 'xor_eq',
0482                 'and_eq',
0483                 'bitor',
0484                 'not',
0485                 'or',
0486                 'xor',
0487     # Predefined identifiers
0488                 'INT_MIN',
0489                 'INT_MAX',
0490                 'MAX_RAND',
0491                 'NULL',
0492     # Qt
0493                 'SIGNAL',
0494                 'SLOT',
0495                 'signals',
0496                 'slots']
0497 
0498     while str in reserved:
0499         str = str + '_'
0500 
0501     return str
0502