#! /usr/bin/env python # # Lasso - A free implementation of the Liberty Alliance specifications. # # Copyright (C) 2004-2007 Entr'ouvert # http://lasso.entrouvert.org # # Authors: See AUTHORS file in top-level directory. # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program 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 General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA import os import re import sys from utils import * from optparse import OptionParser try: from lxml import etree as ET except ImportError: try: import cElementTree as ET except ImportError: try: import elementtree.ElementTree as ET except ImportError: import xml.etree.ElementTree as ET sys.path.append(os.path.dirname(__file__)) class BindingData: src_dir = os.path.dirname(__file__) def __init__(self, options = None): self.headers = [] self.constants = [] self.structs = [] self.struct_dict = {} self.functions = [] self.enums = [] self.options = options self.overrides = ET.parse(os.path.join(self.src_dir, 'overrides.xml')) self.functions_toskip = dict() for func in self.overrides.findall('func'): if func.attrib.get('skip') == 'true': self.functions_toskip[func.attrib.get('name')] = 1 def match_tag_language(self,tag): if self.options and self.options.language: languages = tag.attrib.get('language') if languages: lang_list = languages.split(' ') if self.options.language in lang_list: return True else: return False else: return True else: return True def display_structs(self): for struct in self.structs: struct.display() def display_funcs(self): for func in self.functions: print func.return_type, func.name for a in func.args: print ' ', a def order_class_hierarchy(self): new_order = [] while self.structs: for c in self.structs: if c.parent == 'GObject' or c.parent in [x.name for x in new_order]: self.structs.remove(c) new_order.append(c) break self.structs = new_order def create_struct_dict(self): for c in self.structs: self.struct_dict[c.name] = c def attach_methods(self): self.create_struct_dict() for f in self.functions[:]: if len(f.args) == 0: continue if f.name.endswith('_new') or '_new_' in f.name: # constructor for another class continue arg_type = f.args[0][0] if arg_type[-1] == '*': arg_type = arg_type[:-1] arg_type = arg_type.replace('const ','') c = self.struct_dict.get(arg_type) if not c: continue c.methods.append(f) if f.docstring and f.docstring.parameters: # remove first parameter, which is self/this/etc. f.docstring.parameters = f.docstring.parameters[1:] self.functions.remove(f) def look_for_docstrings(self, srcdir, exception_doc): def getfunc(name): funcs = [f for f in self.functions if f.name == name] if not funcs: return None else: return funcs[0] regex = re.compile(r'\/\*\*\s(.*?)\*\/', re.DOTALL) for base, dirnames, filenames in os.walk(srcdir): if base.endswith('/.svn'): # ignore svn directories continue if not 'Makefile.am' in filenames: # not a source dir continue makefile_am = open(os.path.join(base, 'Makefile.am')).read() filenames = [x for x in filenames if x.endswith('.c') if x in makefile_am] for filename in filenames: s = open(os.path.join(base, filename)).read() docstrings = regex.findall(s) for d in docstrings: docstring = '\n'.join([x[3:] for x in d.splitlines()]) function_name = docstring.splitlines(1)[0].strip().strip(':') func = getfunc(function_name) if not func: continue func.docstring = DocString(func, docstring, self) if exception_doc: lines = os.popen('perl ../utility-scripts/error-analyzer.pl %s' % srcdir, 'r').readlines() for line in lines: elts = re.split(r' +',line.strip()) func = getfunc(elts[0]) if func: func.errors = elts[1:] class Struct: def __init__(self, name): self.name = name[1:] # skip leading _ self.parent = None self.members = [] self.methods = [] def __repr__(self): return '' % (self.name, self.parent) def display(self): print self.__repr__() for m in self.members: print ' ', m for m in self.methods: print ' ', m toskip = None class Function: return_type = None return_type_qualifier = None name = None rename = None args = None docstring = None return_owner = True skip = False errors = None def __repr__(self): return '' % ( self.return_type, self.name, self.args) def apply_overrides(self): for func in binding.overrides.findall('func'): if not binding.match_tag_language(func): continue if func.attrib.get('name') != self.name: continue for param in func.findall('param'): try: arg = [x for x in self.args if x[1] == param.attrib.get('name')][0] except IndexError: print >> sys.stderr, 'W: no such param (%s) in function (%s)' % ( param.attrib.get('name'), self.name) continue if param.attrib.get('optional') == 'true': arg[2]['optional'] = True if param.attrib.get('default'): arg[2]['default'] = param.attrib.get('default') if param.attrib.get('type'): arg[0] = param.attrib.get('type') if param.attrib.get('elem_type'): arg[2]['element-type'] = param.attrib.get('elem_type') if func.attrib.get('rename'): self.rename = func.attrib.get('rename') if func.attrib.get('return_owner'): self.return_owner = (func.attrib.get('return_owner') != 'false') if func.attrib.get('return_type'): self.return_type = func.attrib.get('return_type') if func.attrib.get('skip'): skip = func.attrib.get('skip') if skip == 'true': self.skip = True elif skip == 'unless-id-wsf' and not binding.options.idwsf: self.skip = True elif binding.options.language in skip.split(','): self.skip = True if func.attrib.get('return_type_qualifier'): self.return_type_qualifier = func.attrib.get('return_type_qualifier') for param in binding.overrides.findall('arg'): if not binding.match_tag_language(param): continue arg_name = param.attrib.get('name') arg_sub = param.attrib.get('rename') if arg_name and arg_sub: args = [ x for x in self.args if x[1] == arg_name] for arg in args: arg[2]['original-name'] = arg[1] arg[1] = arg_sub class DocString: orig_docstring = None parameters = None return_value = None description = None def __init__(self, function, docstring, binding_data): self.binding_data = binding_data self.orig_docstring = docstring self.parameters = [] self.params = {} lines = docstring.splitlines() # ignore the first line, it has the symbol name lines = lines[1:] # look for parameters while lines[0].strip(): if not self.parameters and not lines[0].startswith('@'): # function without parameters break if not self.parameters: self.parameters = [] if lines[0][0] == '@': splits = lines[0][1:].split(':', 2) param_name = splits[0] if len(splits) > 2: param_options = splits[1] param_desc = splits[2] self.parameters.append([param_name, param_desc, param_options]) self.params[param_name] = { 'desc': param_desc, 'options': param_options } for a in function.args: if a[1] == param_name or a[2].get('original-name') == param_name: arg = a break else: raise Exception('should not happen ' + param_name + ' ' + lines[0] + repr(function)) self.annotation2arg(arg, param_options) else: param_desc = splits[1] self.parameters.append([param_name, param_desc]) self.params[param_name] = { 'desc': param_desc } else: # continuation of previous description self.parameters[-1][1] = self.parameters[-1][1] + ' ' + lines[0].strip() lines = lines[1:] # blank line then description, till the end or the return value lines = lines[1:] self.description = '' while not lines[0].startswith('Return value'): self.description += lines[0] + '\n' if len(lines) == 1: self.description = self.description.strip() return lines = lines[1:] self.description = self.description.strip() # return value if lines[0].startswith('Return value') or lines[0].startswith('Returns'): lines[0] = lines[0].split(':', 1)[1] accu = '' while lines[0].strip(): accu = accu + ' ' + lines[0].strip() if len(lines) == 1: break lines = lines[1:] # find GObject-introspection annotations if re.match(r'\s*\(', accu): annotation, accu = accu.split(':', 1) self.annotation2arg(function.return_arg, annotation) self.return_value = accu.strip() # remove leading space def annotation2arg(self, arg, annotation): '''Convert GObject-introspection annotations to arg options''' if 'allow-none' in annotation: arg[2]['optional'] = True if re.search('\(\s*out\s*\)', annotation): arg[2]['out'] = True if re.search('\(\s*in\s*\)', annotation): arg[2]['in'] = True if arg[2].get('optional'): m = re.search('\(\s*default\s*(.*)\s*\)', annotation) if m: prefix = '' if is_boolean(arg): prefix = 'b:' elif is_int(arg, self.binding_data): prefix = 'c:' else: raise Exception('should not happen: could not found type for default: ' + annotation) arg[2]['default'] = prefix + m.group(1) m = re.search('\(\s*element-type\s+(\w+)(?:\s+(\w+))?', annotation) if m: if len(m.groups()) > 2: arg[2]['key-type'] = \ convert_type_from_gobject_annotation(m.group(1)) arg[2]['value-type'] = \ convert_type_from_gobject_annotation(m.group(2)) else: arg[2]['element-type'] = \ convert_type_from_gobject_annotation(m.group(1)) m = re.search('\(\s*transfer\s+(\w+)', annotation) if m: arg[2]['transfer'] = m.group(1) def normalise_var(type, name): if name[0] == '*': type += '*' name = name[1:] return type, name exclude_private = True def parse_header(header_file): global binding struct_names = {} in_comment = False in_enum = False in_struct = None in_struct_private = False in_ifdef_zero = False lines = file(header_file).readlines() i = 0 while i < len(lines): line = lines[i] while line.endswith('\\\n'): i += 1 line = line[:-2] + ' ' + lines[i].lstrip() if in_comment: if '*/' in line: in_comment = False elif '/*' in line and not '*/' in line: in_comment = True elif in_ifdef_zero: # minimal support for code sections commented with #if 0 if line.startswith('#endif'): in_ifdef_zero = False elif line.startswith('#if 0'): in_ifdef_zero = True elif in_enum: if line.startswith('}'): in_enum = False enum_name = line[2:].strip().strip(';') binding.enums.append(enum_name) else: m = re.match('\s*([a-zA-Z0-9_]+)', line) if m: binding.constants.append(('i', m.group(1))) elif line.startswith('#define'): m = re.match(r'#define\s+([a-zA-Z0-9_]+)\s+[-\w"]', line) if m: constant = m.group(1) if constant[0] != '_': # ignore private constants if '"' in line: constant_type = 's' else: constant_type = 'i' binding.constants.append((constant_type, constant)) elif line.startswith('typedef enum {'): in_enum = True elif line.startswith('typedef struct'): m = re.match('typedef struct ([a-zA-Z0-9_]+)', line) if m: struct_name = m.group(1) if not (struct_name.endswith('Class') or struct_name.endswith('Private')): struct_names[struct_name] = True elif line.startswith('struct _'): m = re.match('struct ([a-zA-Z0-9_]+)', line) struct_name = m.group(1) if struct_name in struct_names: in_struct = Struct(struct_name) in_struct_private = False elif in_struct: if line.startswith('}'): binding.structs.append(in_struct) in_struct = None elif '/*< public >*/' in line: in_struct_private = False elif '/*< private >*/' in line: in_struct_private = True elif in_struct_private and exclude_private: pass elif 'DEPRECATED' in line and exclude_private: pass else: # TODO: Add parsing of OFTYPE member_match = re.match('\s+(\w+)\s+(\*?\w+)', line) if member_match: member_type = member_match.group(1) member_name = member_match.group(2) if member_name == 'parent': in_struct.parent = member_type else: in_struct.members.append( list(normalise_var(member_type, member_name)) + [{}]) if member_type == 'GList': options = in_struct.members[-1][-1] if '/* of' in line: of_type = line[line.index('/* of')+6:].split()[0] if of_type == 'strings': of_type = 'char*' options['element-type'] = of_type elif line.startswith('LASSO_EXPORT '): while not line.strip().endswith(';'): i += 1 line = line[:-1] + ' ' + lines[i].lstrip() # parse the type, then the name, then argument list m = re.match(r'LASSO_EXPORT\s+([^(]*(?:\s|\*))(\w+)\s*\(\s*(.*?)\s*\)\s*;', line) if m and (not exclude_private or not m.group(2).endswith('_get_type')): return_type, function_name, args = m.groups() return_type = return_type.strip() f = Function() if function_name[0] == '*': return_type += '*' function_name = function_name[1:] if binding.functions_toskip.get(function_name) != 1: if re.search('\', return_type): f.return_owner = False # clean the type return_type = clean_type(return_type) if return_type != 'void': f.return_type = return_type f.return_arg = (return_type, None, {}) if function_name.endswith('_destroy') and exclude_private: # skip the _destroy functions, they are just wrapper over # g_object_unref pass else: f.name = function_name f.args = [] for arg in [x.strip() for x in args.split(',')]: arg = clean_type(arg) if arg == 'void' or arg == '': continue m = re.match(r'(.*(?:\s|\*))(\w+)', arg) if m: type, name = m.groups() type = clean_type(type) f.args.append(list((type, name, {}))) else: print >>sys.stderr, 'failed to process:', arg, 'in line:', line f.skip = True f.apply_overrides() if not f.skip: binding.functions.append(f) i += 1 def parse_headers(srcdir): wsf_prefixes = ['disco', 'dst', 'is', 'profile_service', 'discovery', 'wsf', 'interaction', 'utility', 'sa', 'soap', 'authentication', 'wsse', 'sec', 'idwsf2', 'wsf2', 'wsa', 'wsu'] for base, dirnames, filenames in os.walk(srcdir): if base.endswith('/.svn'): # ignore svn directories continue if not 'Makefile.am' in filenames: # not a source dir continue if not binding.options.idwsf and (base.endswith('/id-wsf') or \ base.endswith('/id-wsf-2.0') or base.endswith('/ws')): # ignore ID-WSF continue makefile_am = open(os.path.join(base, 'Makefile.am')).read() filenames = [x for x in filenames if x.endswith('.h') if x in makefile_am] for filename in filenames: if filename in ('xml_idff.h', 'xml_idwsf.h', 'xml_saml2.h', 'idwsf_strings.h'): continue if filename == 'lasso_config.h' or 'private' in filename: continue if not binding.options.idwsf and filename.split('_')[0] in wsf_prefixes: continue binding.headers.append(os.path.join(base, filename)[3:]) parse_header(os.path.join(base, filename)) binding.headers.insert(0, 'lasso/xml/saml-2.0/saml2_assertion.h') binding.constants.append(('b', 'LASSO_WSF_ENABLED')) def main(): global binding parser = OptionParser() parser.add_option('-l', '--language', dest = 'language') parser.add_option('-s', '--src-dir', dest = 'srcdir', default = '../lasso/') parser.add_option('--enable-id-wsf', dest = 'idwsf', action = 'store_true') parser.add_option('--enable-exception-docs', dest= 'exception_doc', action = 'store_true') options, args = parser.parse_args() if not options.language: parser.print_help() sys.exit(1) binding = BindingData(options) parse_headers(options.srcdir) binding.look_for_docstrings(options.srcdir, options.exception_doc) binding.order_class_hierarchy() binding.attach_methods() if options.language == 'python': from python import lang python_binding = lang.Binding(binding) python_binding.generate() elif options.language == 'php5': from php5 import lang php5_binding = lang.Binding(binding) php5_binding.generate() elif options.language == 'java': from java import lang java_binding = lang.Binding(binding) java_binding.generate(); elif options.language == 'java-list': from java import lang java_binding = lang.Binding(binding) java_binding.print_list_of_files(); if __name__ == '__main__': main()