1from __future__ import absolute_import, division, print_function
2import re
3import string
4import sys
5
6# types translated into "int"
7simpletypes = ["int", "gint", "guint", "gboolean", "size_t", "gssize", "time_t"]
8
9# List "excluded" contains functions that shouldn't be exported via
10# DBus.  If you remove a function from this list, please make sure
11# that it does not break "make" with the configure option
12# "--enable-dbus" turned on.
13
14excluded = [\
15    # I don't remember why this function is excluded; something to do
16    # with the fact that it takes a (const) GList as a parameter.
17    "purple_presence_add_list",
18
19    # These functions are excluded because they involve value of the
20    # type PurpleConvPlacementFunc, which is a pointer to a function and
21    # (currently?) can't be translated into a DBus type.  Normally,
22    # functions with untranslatable types are skipped, but this script
23    # assumes that all non-pointer type names beginning with "Purple"
24    # are enums, which is not true in this case.
25    "purple_conv_placement_add_fnc",
26    "purple_conv_placement_get_fnc",
27    "purple_conv_placement_get_current_func",
28    "purple_conv_placement_set_current_func",
29
30    # Similar to the above:
31    "purple_account_set_register_callback",
32    "purple_account_unregister",
33    "purple_connection_new_unregister",
34
35    # These functions are excluded because they involve setting arbitrary
36    # data via pointers for protocols and UIs.  This just won't work.
37    "purple_blist_get_ui_data",
38    "purple_blist_set_ui_data",
39    "purple_blist_node_get_ui_data",
40    "purple_blist_node_set_ui_data",
41    "purple_buddy_get_protocol_data",
42    "purple_buddy_set_protocol_data",
43
44    # This is excluded because this script treats PurpleLogReadFlags*
45    # as pointer to a struct, instead of a pointer to an enum.  This
46    # causes a compilation error. Someone should fix this script.
47    "purple_log_read",
48    ]
49
50# This is a list of functions that return a GList* or GSList * whose elements
51# are strings, not pointers to objects.
52stringlists = [
53    "purple_prefs_get_path_list",
54    "purple_prefs_get_string_list",
55    "purple_uri_list_extract_filenames",
56    "purple_uri_list_extract_uris",
57    "purple_prefs_get_children_names",
58]
59
60# This is a list of functions that return a GList* or GSList* that should
61# not be freed.  Ideally, this information should be obtained from the Doxygen
62# documentation at some point.
63constlists = [
64    "purple_account_get_status_types",
65    "purple_accounts_get_all",
66    "purple_account_option_get_list",
67    "purple_connections_get_all",
68    "purple_connections_get_connecting",
69    "purple_get_conversations",
70    "purple_get_ims",
71    "purple_get_chats",
72    "purple_conv_chat_get_users",
73    "purple_conv_chat_get_ignored",
74    "purple_mime_document_get_fields",
75    "purple_mime_document_get_parts",
76    "purple_mime_part_get_fields",
77    "purple_notify_user_info_get_entries",
78    "purple_request_fields_get_required",
79    "purple_request_field_list_get_selected",
80    "purple_request_field_list_get_items",
81    "purple_savedstatuses_get_all",
82    "purple_status_type_get_attrs",
83    "purple_presence_get_statuses",
84    "purple_conversation_get_message_history",
85]
86
87pointer = "#pointer#"
88
89class MyException(Exception):
90    pass
91
92myexception = MyException()
93
94def ctopascal(name):
95    newname = ""
96    for word in name.split("_"):
97        newname += word.capitalize()
98    return newname
99
100class Parameter(object):
101    def __init__(self, type, name):
102        self.name = name
103        self.type = type
104
105    def fromtokens(tokens, parameternumber = -1):
106        if len(tokens) == 0:
107            raise myexception
108        if (len(tokens) == 1) or (tokens[-1] == pointer):
109            if parameternumber >= 0:
110                return Parameter(tokens, "param%i" % parameternumber)
111            else:
112                raise myexception
113        else:
114            return Parameter(tokens[:-1], tokens[-1])
115
116    fromtokens = staticmethod(fromtokens)
117
118class Binding(object):
119    def __init__(self, functiontext, paramtexts):
120        self.function = Parameter.fromtokens(functiontext.split())
121
122        if self.function.name in excluded:
123            raise myexception
124
125        self.params = []
126        for i in range(len(paramtexts)):
127            self.params.append(Parameter.fromtokens(paramtexts[i].split(), i))
128
129        self.call = "%s(%s)" % (self.function.name,
130                                ", ".join(param.name for param in self.params))
131
132
133    def process(self):
134        for param in self.params:
135            self.processinput(param.type, param.name)
136
137        self.processoutput(self.function.type, "RESULT")
138        self.flush()
139
140
141    def processinput(self, type, name):
142        const = False
143        unsigned = False
144        if type[0] == "const":
145            type = type[1:]
146            const = True
147
148        if type[0] == "unsigned":
149            type = type[1:]
150            unsigned = True
151
152        if len(type) == 1:
153            # simple types (int, gboolean, etc.) and enums
154            if (type[0] in simpletypes) or ((type[0].startswith("Purple") and not type[0].endswith("Callback"))):
155                return self.inputsimple(type, name, unsigned)
156
157        # pointers ...
158        if (len(type) == 2) and (type[1] == pointer):
159            # strings
160            if type[0] in ["char", "gchar"]:
161                if const:
162                    return self.inputstring(type, name, unsigned)
163                else:
164                    raise myexception
165
166            elif type[0] == "GHashTable":
167                return self.inputhash(type, name)
168
169            # known object types are transformed to integer handles
170            elif type[0].startswith("Purple") or type[0] == "xmlnode":
171                return self.inputpurplestructure(type, name)
172
173            # special case for *_get_data functions, be careful here...
174            elif (type[0] == "size_t" or type[0] == "gsize") and name == "len":
175                return self.inputgetdata(type, name)
176
177            # unknown pointers are always replaced with NULL
178            else:
179                return self.inputpointer(type, name)
180
181        raise myexception
182
183
184    def processoutput(self, type, name):
185        const = False
186        unsigned = False
187        # the "void" type is simple ...
188        if type == ["void"]:
189            return self.outputvoid(type, name)
190
191        if type[0] == "const":
192            type = type[1:]
193            const = True
194
195        if type[0] == "unsigned":
196            type = type[1:]
197            unsigned = True
198
199        # a string
200        if type == ["char", pointer] or type == ["gchar", pointer]:
201            return self.outputstring(type, name, const)
202
203        # simple types (ints, booleans, enums, ...)
204        if (len(type) == 1) and \
205               ((type[0] in simpletypes) or (type[0].startswith("Purple"))):
206            return self.outputsimple(type, name, unsigned)
207
208        # pointers ...
209        if (len(type) == 2) and (type[1] == pointer):
210
211            # handles
212            if type[0].startswith("Purple"):
213                return self.outputpurplestructure(type, name)
214
215            if type[0] in ["GList", "GSList"]:
216                return self.outputlist(type, name)
217
218        # Special case for *_get_data functions
219        if type[0] == "gconstpointer":
220            return self.outputgetdata(type, name)
221
222        raise myexception
223
224
225class ClientBinding (Binding):
226    def __init__(self, functiontext, paramtexts, knowntypes, headersonly):
227        Binding.__init__(self, functiontext, paramtexts)
228        self.knowntypes = knowntypes
229        self.headersonly = headersonly
230        self.paramshdr = []
231        self.decls = []
232        self.inputparams = []
233        self.outputparams = []
234        self.returncode = []
235
236    def flush(self):
237        paramslist = ", ".join(self.paramshdr)
238        if (paramslist == "") :
239            paramslist = "void"
240        print("%s %s(%s)" % (self.functiontype, self.function.name,
241                             paramslist), end=' ')
242
243        if self.headersonly:
244            print(";")
245            return
246
247        print("{")
248
249        for decl in self.decls:
250            print(decl)
251
252        print('dbus_g_proxy_call(purple_proxy, "%s", NULL,' % ctopascal(self.function.name))
253
254        for type_name in self.inputparams:
255            print("\t%s, %s, " % type_name, end=' ')
256        print("G_TYPE_INVALID,")
257
258        for type_name in self.outputparams:
259            print("\t%s, &%s, " % type_name, end=' ')
260        print("G_TYPE_INVALID);")
261
262        for code in self.returncode:
263            print(code)
264
265        print("}\n")
266
267
268    def definepurplestructure(self, type):
269        if (self.headersonly) and (type[0] not in self.knowntypes):
270            print("struct _%s;" % type[0])
271            print("typedef struct _%s %s;" % (type[0], type[0]))
272            self.knowntypes.append(type[0])
273
274    def inputsimple(self, type, name, us):
275        self.paramshdr.append("%s %s" % (type[0], name))
276        if us:
277            self.inputparams.append(("G_TYPE_UINT", name))
278        else:
279            self.inputparams.append(("G_TYPE_INT", name))
280
281    def inputstring(self, type, name, us):
282        if us:
283            self.paramshdr.append("const unsigned char *%s" % name)
284        else:
285            self.paramshdr.append("const char *%s" % name)
286        self.inputparams.append(("G_TYPE_STRING", name))
287
288    def inputpurplestructure(self, type, name):
289        self.paramshdr.append("const %s *%s" % (type[0], name))
290        self.inputparams.append(("G_TYPE_INT", "GPOINTER_TO_INT(%s)" % name))
291        self.definepurplestructure(type)
292
293    def inputpointer(self, type, name):
294        name += "_NULL"
295        self.paramshdr.append("const %s *%s" % (type[0], name))
296        self.inputparams.append(("G_TYPE_INT", "0"))
297
298    def inputhash(self, type, name):
299        self.paramshdr.append("const GHashTable *%s" % name)
300        self.inputparams.append(('dbus_g_type_get_map ("GHashTable", G_TYPE_STRING, G_TYPE_STRING)', name))
301
302    def outputvoid(self, type, name):
303        self.functiontype = "void"
304
305    def outputstring(self, type, name, const):
306        self.functiontype = "char*"
307        self.decls.append("char *%s = NULL;" % name)
308        self.outputparams.append(("G_TYPE_STRING", name))
309#        self.returncode.append("NULLIFY(%s);" % name)
310        self.returncode.append("return %s;" % name);
311
312    def outputsimple(self, type, name, us):
313        self.functiontype = type[0]
314        self.decls.append("%s %s = 0;" % (type[0], name))
315        if us:
316            self.outputparams.append(("G_TYPE_UINT", name))
317        else:
318            self.outputparams.append(("G_TYPE_INT", name))
319        self.returncode.append("return %s;" % name);
320
321    # we could add "const" to the return type but this would probably
322    # be a nuisance
323    def outputpurplestructure(self, type, name):
324        name = name + "_ID"
325        self.functiontype = "%s*" % type[0]
326        self.decls.append("int %s = 0;" % name)
327        self.outputparams.append(("G_TYPE_INT", "%s" % name))
328        self.returncode.append("return (%s*) GINT_TO_POINTER(%s);" % (type[0], name));
329        self.definepurplestructure(type)
330
331    def outputlist(self, type, name):
332        self.functiontype = "%s*" % type[0]
333        self.decls.append("GArray *%s;" % name)
334        self.outputparams.append(('dbus_g_type_get_collection("GArray", G_TYPE_INT)', name))
335        self.returncode.append("return garray_int_to_%s(%s);" %
336                               (type[0].lower(), name));
337
338    # Special case for *_get_data functions, don't need client bindings,
339    #  but do need the name so it doesn't crash
340    def inputgetdata(self, type, name):
341        raise myexception
342    def outputgetdata(self, type, name):
343        raise myexception
344
345class ServerBinding (Binding):
346    def __init__(self, functiontext, paramtexts):
347        Binding.__init__(self, functiontext, paramtexts)
348        self.dparams = ""
349        self.cparams = []
350        self.cdecls  = []
351        self.ccode  = []
352        self.cparamsout = []
353        self.ccodeout = []
354        self.argfunc = "dbus_message_get_args"
355
356    def flush(self):
357        print("static DBusMessage*")
358        print("%s_DBUS(DBusMessage *message_DBUS, DBusError *error_DBUS) {" % \
359              self.function.name)
360
361        print("\tDBusMessage *reply_DBUS;")
362
363        for decl in self.cdecls:
364            print(decl)
365
366        print("\t%s(message_DBUS, error_DBUS," % self.argfunc,end=' ')
367        for param in self.cparams:
368            print("DBUS_TYPE_%s, &%s," % param, end=' ')
369        print("DBUS_TYPE_INVALID);")
370
371        print("\tCHECK_ERROR(error_DBUS);")
372
373        for code in self.ccode:
374            print(code)
375
376        print("\treply_DBUS = dbus_message_new_method_return (message_DBUS);")
377
378        print("\tdbus_message_append_args(reply_DBUS,", end=' ')
379        for param in self.cparamsout:
380            if type(param) is str:
381                print("%s," % param, end=' ')
382            else:
383                print("DBUS_TYPE_%s, &%s," % param, end=' ')
384        print("DBUS_TYPE_INVALID);")
385
386        for code in self.ccodeout:
387            print(code)
388
389        print("\treturn reply_DBUS;\n}\n")
390
391
392    def addstring(self, *items):
393        for item in items:
394            self.dparams += item + r"\0"
395
396    def addintype(self, type, name):
397        self.addstring("in", type, name)
398
399    def addouttype(self, type, name):
400        self.addstring("out", type, name)
401
402
403    # input parameters
404
405    def inputsimple(self, type, name, us):
406        if us:
407            self.cdecls.append("\tdbus_uint32_t %s;" % name)
408            self.cparams.append(("UINT32", name))
409            self.addintype("u", name)
410        else:
411            self.cdecls.append("\tdbus_int32_t %s;" % name)
412            self.cparams.append(("INT32", name))
413            self.addintype("i", name)
414
415    def inputstring(self, type, name, us):
416        if us:
417            self.cdecls.append("\tconst unsigned char *%s;" % name)
418        else:
419            self.cdecls.append("\tconst char *%s;" % name)
420        self.cparams.append(("STRING", name))
421        self.ccode.append("\t%s = (%s && %s[0]) ? %s : NULL;" % (name,name,name,name))
422        self.addintype("s", name)
423
424    def inputhash(self, type, name):
425        self.argfunc = "purple_dbus_message_get_args"
426        self.cdecls.append("\tDBusMessageIter %s_ITER;" % name)
427        self.cdecls.append("\tGHashTable *%s;" % name)
428        self.cparams.append(("ARRAY", "%s_ITER" % name))
429        self.ccode.append("\t%s = purple_dbus_iter_hash_table(&%s_ITER, error_DBUS);" \
430                     % (name, name))
431        self.ccode.append("\tCHECK_ERROR(error_DBUS);")
432        self.ccodeout.append("\tg_hash_table_destroy(%s);" % name)
433        self.addintype("a{ss}", name)
434
435    def inputpurplestructure(self, type, name):
436        self.cdecls.append("\tdbus_int32_t %s_ID;" %  name)
437        self.cdecls.append("\t%s *%s;" % (type[0], name))
438        self.cparams.append(("INT32", name + "_ID"))
439        self.ccode.append("\tPURPLE_DBUS_ID_TO_POINTER(%s, %s_ID, %s, error_DBUS);"  % \
440                          (name, name, type[0]))
441        self.addintype("i", name)
442
443    def inputpointer(self, type, name):
444        self.cdecls.append("\tdbus_int32_t %s_NULL;" %  name)
445        self.cdecls .append("\t%s *%s;" % (type[0], name))
446        self.cparams.append(("INT32", name + "_NULL"))
447        self.ccode  .append("\t%s = NULL;" % name)
448        self.addintype("i", name)
449
450    # output parameters
451
452    def outputvoid(self, type, name):
453        self.ccode.append("\t%s;" % self.call) # just call the function
454
455    def outputstring(self, type, name, const):
456        if const:
457            self.cdecls.append("\tconst char *%s;" % name)
458        else:
459            self.cdecls.append("\tchar *%s;" % name)
460        self.ccode.append("\tif ((%s = %s) == NULL)" % (name, self.call))
461        self.ccode.append("\t\t%s = \"\";" % (name))
462        self.cparamsout.append(("STRING", name))
463        self.addouttype("s", name)
464        if not const:
465            self.ccodeout.append("\tg_free(%s);" % name)
466
467    def outputsimple(self, type, name, us):
468        if us:
469            self.cdecls.append("\tdbus_uint32_t %s;" % name)
470            self.cparamsout.append(("UINT32", name))
471            self.addouttype("u", name)
472        else:
473            self.cdecls.append("\tdbus_int32_t %s;" % name)
474            self.cparamsout.append(("INT32", name))
475            self.addouttype("i", name)
476        self.ccode.append("\t%s = %s;" % (name, self.call))
477
478    def outputpurplestructure(self, type, name):
479        self.cdecls.append("\tdbus_int32_t %s;" % name)
480        self.ccode .append("\tPURPLE_DBUS_POINTER_TO_ID(%s, %s, error_DBUS);" % (name, self.call))
481        self.cparamsout.append(("INT32", name))
482        self.addouttype("i", name)
483
484    # GList*, GSList*, assume that list is a list of objects
485    # unless the function is in stringlists
486    def outputlist(self, type, name):
487        self.cdecls.append("\tdbus_int32_t %s_LEN;" % name)
488        self.ccodeout.append("\tg_free(%s);" % name)
489
490        self.cdecls.append("\t%s *list;" % type[0]);
491
492        if self.function.name in stringlists:
493            self.cdecls.append("\tchar **%s;" % name)
494            self.ccode.append("\tlist = %s;" % self.call)
495            self.ccode.append("\t%s = (char **)purple_%s_to_array(list, FALSE, &%s_LEN);" % \
496                         (name, type[0], name))
497            self.cparamsout.append("DBUS_TYPE_ARRAY, DBUS_TYPE_STRING, &%s, %s_LEN" \
498                          % (name, name))
499            if (not (self.function.name in constlists)):
500                type_name = type[0].lower()[1:]
501                self.ccodeout.append("\tg_%s_foreach(list, (GFunc)g_free, NULL);" % type_name)
502                self.ccodeout.append("\tg_%s_free(list);" % type_name)
503            self.addouttype("as", name)
504        else:
505            self.cdecls.append("\tdbus_int32_t *%s;" % name)
506            self.ccode.append("\tlist = %s;" % self.call)
507            self.ccode.append("\t%s = purple_dbusify_%s(list, FALSE, &%s_LEN);" % \
508                         (name, type[0], name))
509            if (not (self.function.name in constlists)):
510                self.ccode.append("\tg_%s_free(list);" % type[0].lower()[1:])
511            self.cparamsout.append("DBUS_TYPE_ARRAY, DBUS_TYPE_INT32, &%s, %s_LEN" \
512                              % (name, name))
513            self.addouttype("ai", name)
514
515    # Special case for *_get_data functions
516    def inputgetdata(self, type, name):
517        self.cdecls.append("\tsize_t %s = 0;" % name)
518        return True
519    def outputgetdata(self, type, name):
520        # This is a total hack, but self.call is set up before the parameters
521        #  are processed, so we can't tell it to pass a parameter by reference.
522        self.call = "%s(%s)" % (self.function.name,
523                                ", ".join([(param.name, "&len")[param.name == "len"] for param in self.params]))
524
525        self.cdecls.append("\tgconstpointer %s;" % name)
526        self.ccode.append("\t%s = %s;" % (name, self.call))
527        self.cparamsout.append("DBUS_TYPE_ARRAY, DBUS_TYPE_BYTE, &%s, %s" \
528                               % (name, "len"))
529        self.addouttype("ay", name)
530
531class BindingSet(object):
532    regexp = r"^(\w[^()]*)\(([^()]*)\)\s*;\s*$";
533
534    def __init__(self, inputfile, fprefix):
535        self.inputiter = iter(inputfile)
536        self.functionregexp = \
537             re.compile("^%s(\w[^()]*)\(([^()]*)\)\s*;\s*$" % fprefix)
538        self.typeregexp = re.compile("^\w+\s*\*?\s*$")
539
540
541
542    def process(self):
543        print("/* Generated by %s.  Do not edit! */" % sys.argv[0])
544
545        for line in self.inputiter:
546            words = line.split()
547            if len(words) == 0:             # empty line
548                continue
549            if line[0] == "#":              # preprocessor directive
550                continue
551            if words[0] in ["typedef", "struct", "enum", "static"]:
552                continue
553
554            # accumulate lines until the parentheses are balance or an
555            # empty line has been encountered
556            myline = line.strip()
557            while (myline.count("(") > myline.count(")")) or self.typeregexp.match(myline):
558                newline = next(self.inputiter).strip()
559                if len(newline) == 0:
560                    break
561                myline += " " + newline
562
563            # is this a function declaration?
564            thematch = self.functionregexp.match(
565                myline.replace("*", " " + pointer + " "))
566
567            if thematch is None:
568                continue
569
570            functiontext = thematch.group(1)
571            paramstext = thematch.group(2).strip()
572
573            if (paramstext == "void") or (paramstext == ""):
574                paramtexts = []
575            else:
576                paramtexts = paramstext.split(",")
577
578            try:
579                self.processfunction(functiontext, paramtexts)
580            except MyException:
581#                sys.stderr.write(myline + "\n")
582                 pass
583            except:
584#                sys.stderr.write(myline + "\n")
585                raise
586
587        self.flush()
588
589class ServerBindingSet (BindingSet):
590    def __init__(self, inputfile, fprefix):
591        BindingSet.__init__(self, inputfile, fprefix)
592        self.functions = []
593
594
595    def processfunction(self, functiontext, paramtexts):
596        binding = ServerBinding(functiontext, paramtexts)
597        binding.process()
598        self.functions.append((binding.function.name, binding.dparams))
599
600    def flush(self):
601        print("static PurpleDBusBinding bindings_DBUS[] = { ")
602        for function, params in self.functions:
603            print('{"%s", "%s", %s_DBUS},' % \
604                  (ctopascal(function), params, function))
605
606        print("{NULL, NULL, NULL}")
607        print("};")
608
609        print("#define PURPLE_DBUS_REGISTER_BINDINGS(handle) purple_dbus_register_bindings(handle, bindings_DBUS)")
610
611class ClientBindingSet (BindingSet):
612    def __init__(self, inputfile, fprefix, headersonly):
613        BindingSet.__init__(self, inputfile, fprefix)
614        self.functions = []
615        self.knowntypes = []
616        self.headersonly = headersonly
617
618    def processfunction(self, functiontext, paramtexts):
619        binding = ClientBinding(functiontext, paramtexts, self.knowntypes, self.headersonly)
620        binding.process()
621
622    def flush(self):
623        pass
624
625# Main program
626
627options = {}
628
629for arg in sys.argv[1:]:
630    if arg[0:2] == "--":
631        mylist = arg[2:].split("=",1)
632        command = mylist[0]
633        if len(mylist) > 1:
634            options[command] = mylist[1]
635        else:
636            options[command] = None
637
638if "export-only" in options:
639    fprefix = "DBUS_EXPORT\s+"
640else:
641    fprefix = ""
642
643#sys.stderr.write("%s: Functions not exported:\n" % sys.argv[0])
644
645if "client" in options:
646    bindings = ClientBindingSet(sys.stdin, fprefix,
647                                "headers" in options)
648else:
649    bindings = ServerBindingSet(sys.stdin, fprefix)
650bindings.process()
651
652
653
654
655