]> git.0d.be Git - empathy.git/blob - tools/libtpcodegen.py
tp-chat: prepare CONNECTED on the TpConnection first
[empathy.git] / tools / libtpcodegen.py
1 """Library code for language-independent D-Bus-related code generation.
2
3 The master copy of this library is in the telepathy-glib repository -
4 please make any changes there.
5 """
6
7 # Copyright (C) 2006-2008 Collabora Limited
8 #
9 # This library is free software; you can redistribute it and/or
10 # modify it under the terms of the GNU Lesser General Public
11 # License as published by the Free Software Foundation; either
12 # version 2.1 of the License, or (at your option) any later version.
13 #
14 # This library is distributed in the hope that it will be useful,
15 # but WITHOUT ANY WARRANTY; without even the implied warranty of
16 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
17 # Lesser General Public License for more details.
18 #
19 # You should have received a copy of the GNU Lesser General Public
20 # License along with this library; if not, write to the Free Software
21 # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
22
23 import os
24 from string import ascii_letters, digits
25
26
27 NS_TP = "http://telepathy.freedesktop.org/wiki/DbusSpec#extensions-v0"
28
29 _ASCII_ALNUM = ascii_letters + digits
30
31 def file_set_contents(filename, contents):
32     try:
33         os.remove(filename)
34     except OSError:
35         pass
36     try:
37         os.remove(filename + '.tmp')
38     except OSError:
39         pass
40
41     open(filename + '.tmp', 'w').write(contents)
42     os.rename(filename + '.tmp', filename)
43
44 def cmp_by_name(node1, node2):
45     return cmp(node1.getAttributeNode("name").nodeValue,
46                node2.getAttributeNode("name").nodeValue)
47
48
49 def escape_as_identifier(identifier):
50     """Escape the given string to be a valid D-Bus object path or service
51     name component, using a reversible encoding to ensure uniqueness.
52
53     The reversible encoding is as follows:
54
55     * The empty string becomes '_'
56     * Otherwise, each non-alphanumeric character is replaced by '_' plus
57       two lower-case hex digits; the same replacement is carried out on
58       the first character, if it's a digit
59     """
60     # '' -> '_'
61     if not identifier:
62         return '_'
63
64     # A bit of a fast path for strings which are already OK.
65     # We deliberately omit '_' because, for reversibility, that must also
66     # be escaped.
67     if (identifier.strip(_ASCII_ALNUM) == '' and
68         identifier[0] in ascii_letters):
69         return identifier
70
71     # The first character may not be a digit
72     if identifier[0] not in ascii_letters:
73         ret = ['_%02x' % ord(identifier[0])]
74     else:
75         ret = [identifier[0]]
76
77     # Subsequent characters may be digits or ASCII letters
78     for c in identifier[1:]:
79         if c in _ASCII_ALNUM:
80             ret.append(c)
81         else:
82             ret.append('_%02x' % ord(c))
83
84     return ''.join(ret)
85
86
87 def get_by_path(element, path):
88     branches = path.split('/')
89     branch = branches[0]
90
91     # Is the current branch an attribute, if so, return the attribute value
92     if branch[0] == '@':
93         return element.getAttribute(branch[1:])
94
95     # Find matching children for the branch
96     children = []
97     if branch == '..':
98         children.append(element.parentNode)
99     else:
100         for x in element.childNodes:
101             if x.localName == branch:
102                 children.append(x)
103
104     ret = []
105     # If this is not the last path element, recursively gather results from
106     # children
107     if len(branches) > 1:
108         for x in children:
109             add = get_by_path(x, '/'.join(branches[1:]))
110             if isinstance(add, list):
111                 ret += add
112             else:
113                 return add
114     else:
115         ret = children
116
117     return ret
118
119
120 def get_docstring(element):
121     docstring = None
122     for x in element.childNodes:
123         if x.namespaceURI == NS_TP and x.localName == 'docstring':
124             docstring = x
125     if docstring is not None:
126         docstring = docstring.toxml().replace('\n', ' ').strip()
127         if docstring.startswith('<tp:docstring>'):
128             docstring = docstring[14:].lstrip()
129         if docstring.endswith('</tp:docstring>'):
130             docstring = docstring[:-15].rstrip()
131         if docstring in ('<tp:docstring/>', ''):
132             docstring = ''
133     return docstring
134
135 def get_deprecated(element):
136     text = []
137     for x in element.childNodes:
138         if hasattr(x, 'data'):
139             text.append(x.data.replace('\n', ' ').strip())
140         else:
141             # This caters for tp:dbus-ref elements, but little else.
142             if x.childNodes and hasattr(x.childNodes[0], 'data'):
143                 text.append(x.childNodes[0].data.replace('\n', ' ').strip())
144     return ' '.join(text)
145
146 def get_descendant_text(element_or_elements):
147     if not element_or_elements:
148         return ''
149     if isinstance(element_or_elements, list):
150         return ''.join(map(get_descendant_text, element_or_elements))
151     parts = []
152     for x in element_or_elements.childNodes:
153         if x.nodeType == x.TEXT_NODE:
154             parts.append(x.nodeValue)
155         elif x.nodeType == x.ELEMENT_NODE:
156             parts.append(get_descendant_text(x))
157         else:
158             pass
159     return ''.join(parts)
160
161
162 class _SignatureIter:
163     """Iterator over a D-Bus signature. Copied from dbus-python 0.71 so we
164     can run genginterface in a limited environment with only Python
165     (like Scratchbox).
166     """
167     def __init__(self, string):
168         self.remaining = string
169
170     def next(self):
171         if self.remaining == '':
172             raise StopIteration
173
174         signature = self.remaining
175         block_depth = 0
176         block_type = None
177         end = len(signature)
178
179         for marker in range(0, end):
180             cur_sig = signature[marker]
181
182             if cur_sig == 'a':
183                 pass
184             elif cur_sig == '{' or cur_sig == '(':
185                 if block_type == None:
186                     block_type = cur_sig
187
188                 if block_type == cur_sig:
189                     block_depth = block_depth + 1
190
191             elif cur_sig == '}':
192                 if block_type == '{':
193                     block_depth = block_depth - 1
194
195                 if block_depth == 0:
196                     end = marker
197                     break
198
199             elif cur_sig == ')':
200                 if block_type == '(':
201                     block_depth = block_depth - 1
202
203                 if block_depth == 0:
204                     end = marker
205                     break
206
207             else:
208                 if block_depth == 0:
209                     end = marker
210                     break
211
212         end = end + 1
213         self.remaining = signature[end:]
214         return Signature(signature[0:end])
215
216
217 class Signature(str):
218     """A string, iteration over which is by D-Bus single complete types
219     rather than characters.
220     """
221     def __iter__(self):
222         return _SignatureIter(self)
223
224
225 def xml_escape(s):
226     s = s.replace('&', '&amp;').replace("'", '&apos;').replace('"', '&quot;')
227     return s.replace('<', '&lt;').replace('>', '&gt;')