2085
+ − 1 #!/usr/bin/env python2
+ − 2 #-*- coding: utf-8 -*-
+ − 3
+ − 4 # SàT: a XMPP client
+ − 5 # Copyright (C) 2009-2016 Jérôme Poisson (goffi@goffi.org)
+ − 6
+ − 7 # This program is free software: you can redistribute it and/or modify
+ − 8 # it under the terms of the GNU Affero General Public License as published by
+ − 9 # the Free Software Foundation, either version 3 of the License, or
+ − 10 # (at your option) any later version.
+ − 11
+ − 12 # This program is distributed in the hope that it will be useful,
+ − 13 # but WITHOUT ANY WARRANTY; without even the implied warranty of
+ − 14 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ − 15 # GNU Affero General Public License for more details.
+ − 16
+ − 17 # You should have received a copy of the GNU Affero General Public License
+ − 18 # along with this program. If not, see <http://www.gnu.org/licenses/>.
+ − 19
+ − 20 """base constructor class"""
+ − 21
+ − 22 from sat.bridge.bridge_constructor.constants import Const as C
+ − 23 from ConfigParser import NoOptionError
+ − 24 import sys
+ − 25 import os
+ − 26 import os.path
+ − 27 import re
+ − 28 from importlib import import_module
+ − 29
+ − 30
+ − 31 class ParseError ( Exception ):
+ − 32 #Used when the signature parsing is going wrong (invalid signature ?)
+ − 33 pass
+ − 34
+ − 35
+ − 36 class Constructor ( object ):
+ − 37 NAME = None # used in arguments parsing, filename will be used if not set
+ − 38 # following attribute are used by default generation method
+ − 39 # they can be set to dict of strings using python formatting syntax
+ − 40 # dict keys will be used to select part to replace (e.g. "signals" key will
+ − 41 # replace ##SIGNALS_PART## in template), while the value is the format
+ − 42 # keys starting with "signal" will be used for signals, while ones starting with
+ − 43 # "method" will be used for methods
+ − 44 # check D-Bus constructor for an example
+ − 45 CORE_FORMATS = None
+ − 46 CORE_TEMPLATE = None
+ − 47 CORE_DEST = None
+ − 48 FRONTEND_FORMATS = None
+ − 49 FRONTEND_TEMPLATE = None
+ − 50 FRONTEND_DEST = None
+ − 51
+ − 52 def __init__ ( self , bridge_template , options ):
+ − 53 self . bridge_template = bridge_template
+ − 54 self . args = options
+ − 55
+ − 56 @property
+ − 57 def constructor_dir ( self ):
+ − 58 constructor_mod = import_module ( self . __module__ )
+ − 59 return os . path . dirname ( constructor_mod . __file__ )
+ − 60
+ − 61 def getValues ( self , name ):
+ − 62 """Return values of a function in a dict
+ − 63 @param name: Name of the function to get
+ − 64 @return: dict, each key has the config value or None if the value is not set"""
+ − 65 function = {}
+ − 66 for option in [ 'type' , 'category' , 'sig_in' , 'sig_out' , 'doc' ]:
+ − 67 try :
+ − 68 value = self . bridge_template . get ( name , option )
+ − 69 except NoOptionError :
+ − 70 value = None
+ − 71 function [ option ] = value
+ − 72 return function
+ − 73
+ − 74 def getDefault ( self , name ):
+ − 75 """Return default values of a function in a dict
+ − 76 @param name: Name of the function to get
+ − 77 @return: dict, each key is the integer param number (no key if no default value)"""
+ − 78 default_dict = {}
+ − 79 def_re = re . compile ( r "param_(\d+)_default" )
+ − 80
+ − 81 for option in self . bridge_template . options ( name ):
+ − 82 match = def_re . match ( option )
+ − 83 if match :
+ − 84 try :
+ − 85 idx = int ( match . group ( 1 ))
+ − 86 except ValueError :
+ − 87 raise ParseError ( "Invalid value [ %s ] for parameter number" % match . group ( 1 ))
+ − 88 default_dict [ idx ] = self . bridge_template . get ( name , option )
+ − 89
+ − 90 return default_dict
+ − 91
+ − 92 def getFlags ( self , name ):
+ − 93 """Return list of flags set for this function
+ − 94
+ − 95 @param name: Name of the function to get
+ − 96 @return: List of flags (string)
+ − 97 """
+ − 98 flags = []
+ − 99 for option in self . bridge_template . options ( name ):
+ − 100 if option in C . DECLARATION_FLAGS :
+ − 101 flags . append ( option )
+ − 102 return flags
+ − 103
+ − 104 def getArgumentsDoc ( self , name ):
+ − 105 """Return documentation of arguments
+ − 106 @param name: Name of the function to get
+ − 107 @return: dict, each key is the integer param number (no key if no argument doc), value is a tuple (name, doc)"""
+ − 108 doc_dict = {}
+ − 109 option_re = re . compile ( r "doc_param_(\d+)" )
+ − 110 value_re = re . compile ( r "^(\w+): (.*)$" , re . MULTILINE | re . DOTALL )
+ − 111 for option in self . bridge_template . options ( name ):
+ − 112 if option == 'doc_return' :
+ − 113 doc_dict [ 'return' ] = self . bridge_template . get ( name , option )
+ − 114 continue
+ − 115 match = option_re . match ( option )
+ − 116 if match :
+ − 117 try :
+ − 118 idx = int ( match . group ( 1 ))
+ − 119 except ValueError :
+ − 120 raise ParseError ( "Invalid value [ %s ] for parameter number" % match . group ( 1 ))
+ − 121 value_match = value_re . match ( self . bridge_template . get ( name , option ))
+ − 122 if not value_match :
+ − 123 raise ParseError ( "Invalid value for parameter doc [ %i ]" % idx )
+ − 124 doc_dict [ idx ] = ( value_match . group ( 1 ), value_match . group ( 2 ))
+ − 125 return doc_dict
+ − 126
+ − 127 def getDoc ( self , name ):
+ − 128 """Return documentation of the method
+ − 129 @param name: Name of the function to get
+ − 130 @return: string documentation, or None"""
+ − 131 if self . bridge_template . has_option ( name , "doc" ):
+ − 132 return self . bridge_template . get ( name , "doc" )
+ − 133 return None
+ − 134
+ − 135 def argumentsParser ( self , signature ):
+ − 136 """Generator which return individual arguments signatures from a global signature"""
+ − 137 start = 0
+ − 138 i = 0
+ − 139
+ − 140 while i < len ( signature ):
+ − 141 if signature [ i ] not in [ 'b' , 'y' , 'n' , 'i' , 'x' , 'q' , 'u' , 't' , 'd' , 's' , 'a' ]:
+ − 142 raise ParseError ( "Unmanaged attribute type [ %c ]" % signature [ i ])
+ − 143
+ − 144 if signature [ i ] == 'a' :
+ − 145 i += 1
+ − 146 if signature [ i ] != '{' and signature [ i ] != '(' : # FIXME: must manage tuples out of arrays
+ − 147 i += 1
+ − 148 yield signature [ start : i ]
+ − 149 start = i
+ − 150 continue # we have a simple type for the array
+ − 151 opening_car = signature [ i ]
+ − 152 assert ( opening_car in [ '{' , '(' ])
+ − 153 closing_car = '}' if opening_car == '{' else ')'
+ − 154 opening_count = 1
+ − 155 while ( True ): # we have a dict or a list of tuples
+ − 156 i += 1
+ − 157 if i >= len ( signature ):
+ − 158 raise ParseError ( "missing }" )
+ − 159 if signature [ i ] == opening_car :
+ − 160 opening_count += 1
+ − 161 if signature [ i ] == closing_car :
+ − 162 opening_count -= 1
+ − 163 if opening_count == 0 :
+ − 164 break
+ − 165 i += 1
+ − 166 yield signature [ start : i ]
+ − 167 start = i
+ − 168
+ − 169 def getArguments ( self , signature , name = None , default = None , unicode_protect = False ):
+ − 170 """Return arguments to user given a signature
+ − 171
+ − 172 @param signature: signature in the short form (using s,a,i,b etc)
+ − 173 @param name: dictionary of arguments name like given by getArguments
+ − 174 @param default: dictionary of default values, like given by getDefault
+ − 175 @param unicode_protect: activate unicode protection on strings (return strings as unicode(str))
+ − 176 @return: list of arguments that correspond to a signature (e.g.: "sss" return "arg1, arg2, arg3")
+ − 177 """
+ − 178 idx = 0
+ − 179 attr_string = []
+ − 180
+ − 181 for arg in self . argumentsParser ( signature ):
+ − 182 attr_string . append (( "unicode( %(name)s ) %(default)s " if ( unicode_protect and arg == 's' ) else " %(name)s%(default)s " ) % {
+ − 183 'name' : name [ idx ][ 0 ] if ( name and idx in name ) else "arg_ %i " % idx ,
+ − 184 'default' : "=" + default [ idx ] if ( default and idx in default ) else '' })
+ − 185 # give arg_1, arg2, etc or name1, name2=default, etc.
+ − 186 #give unicode(arg_1), unicode(arg_2), etc. if unicode_protect is set and arg is a string
+ − 187 idx += 1
+ − 188
+ − 189 return ", " . join ( attr_string )
+ − 190
+ − 191 def getTemplatePath ( self , template_file ):
+ − 192 """return template path corresponding to file name
+ − 193
+ − 194 @param template_file(str): name of template file
+ − 195 """
+ − 196 return os . path . join ( self . constructor_dir , template_file )
+ − 197
+ − 198 def core_completion_method ( self , completion , function , default , arg_doc , async_ ):
+ − 199 """override this method to extend completion"""
+ − 200 pass
+ − 201
+ − 202 def core_completion_signal ( self , completion , function , default , arg_doc , async_ ):
+ − 203 """override this method to extend completion"""
+ − 204 pass
+ − 205
+ − 206 def frontend_completion_method ( self , completion , function , default , arg_doc , async_ ):
+ − 207 """override this method to extend completion"""
+ − 208 pass
+ − 209
+ − 210 def frontend_completion_signal ( self , completion , function , default , arg_doc , async_ ):
+ − 211 """override this method to extend completion"""
+ − 212 pass
+ − 213
+ − 214
+ − 215 def generate ( self , side ):
+ − 216 """generate bridge
+ − 217
+ − 218 call generateCoreSide or generateFrontendSide if they exists
+ − 219 else call generic self._generate method
+ − 220 """
+ − 221 try :
+ − 222 if side == "core" :
+ − 223 method = self . generateCoreSide
+ − 224 elif side == "frontend" :
+ − 225 method = self . generateFrontendSide
+ − 226 except AttributeError :
+ − 227 self . _generate ( side )
+ − 228 else :
+ − 229 method ()
+ − 230
+ − 231 def _generate ( self , side ):
+ − 232 """generate the backend
+ − 233
+ − 234 this is a generic method which will use formats found in self.CORE_SIGNAL_FORMAT
+ − 235 and self.CORE_METHOD_FORMAT (standard format method will be used)
+ − 236 @param side(str): core or frontend
+ − 237 """
+ − 238 side_vars = []
+ − 239 for var in ( 'FORMATS' , 'TEMPLATE' , 'DEST' ):
+ − 240 attr = " {} _ {} " . format ( side . upper (), var )
+ − 241 value = getattr ( self , attr )
+ − 242 if value is None :
+ − 243 raise NotImplementedError
+ − 244 side_vars . append ( value )
+ − 245
+ − 246 FORMATS , TEMPLATE , DEST = side_vars
+ − 247 del side_vars
+ − 248
+ − 249 parts = { part . upper ():[] for part in FORMATS }
+ − 250 sections = self . bridge_template . sections ()
+ − 251 sections . sort ()
+ − 252 for section in sections :
+ − 253 function = self . getValues ( section )
+ − 254 print ( "Adding %s %s " % ( section , function [ "type" ]))
+ − 255 default = self . getDefault ( section )
+ − 256 arg_doc = self . getArgumentsDoc ( section )
+ − 257 async_ = "async" in self . getFlags ( section )
+ − 258 completion = {
+ − 259 'sig_in' : function [ 'sig_in' ] or '' ,
+ − 260 'sig_out' : function [ 'sig_out' ] or '' ,
+ − 261 'category' : 'plugin' if function [ 'category' ] == 'plugin' else 'core' ,
+ − 262 'name' : section ,
+ − 263 'args' : self . getArguments ( function [ 'sig_in' ], name = arg_doc , default = default )}
+ − 264
+ − 265 extend_method = getattr ( self , " {} _completion_ {} " . format ( side , function [ "type" ]))
+ − 266 extend_method ( completion , function , default , arg_doc , async_ )
+ − 267
+ − 268 for part , fmt in FORMATS . iteritems ():
+ − 269 if part . startswith ( function [ "type" ]):
+ − 270 parts [ part . upper ()] . append ( fmt . format ( ** completion ))
+ − 271
+ − 272
+ − 273 #at this point, signals_part, methods_part and direct_calls should be filled,
+ − 274 #we just have to place them in the right part of the template
+ − 275 bridge = []
+ − 276 const_override = { env [ len ( C . ENV_OVERRIDE ):]: v for env , v in os . environ . iteritems () if env . startswith ( C . ENV_OVERRIDE )}
+ − 277 template_path = self . getTemplatePath ( TEMPLATE )
+ − 278 try :
+ − 279 with open ( template_path ) as template :
+ − 280 for line in template :
+ − 281
+ − 282 for part , extend_list in parts . iteritems ():
+ − 283 if line . startswith ( '## {} _PART##' . format ( part )):
+ − 284 bridge . extend ( extend_list )
+ − 285 break
+ − 286 else :
+ − 287 # the line is not a magic part replacement
+ − 288 if line . startswith ( 'const_' ):
+ − 289 const_name = line [ len ( 'const_' ): line . find ( ' = ' )] . strip ()
+ − 290 if const_name in const_override :
+ − 291 print ( "const {} overriden" . format ( const_name ))
+ − 292 bridge . append ( 'const_ {} = {} ' . format ( const_name , const_override [ const_name ]))
+ − 293 continue
+ − 294 bridge . append ( line . replace ( ' \n ' , '' ))
+ − 295 except IOError :
+ − 296 print ( "can't open template file [ {} ]" . format ( template_path ))
+ − 297 sys . exit ( 1 )
+ − 298
+ − 299 #now we write to final file
+ − 300 self . finalWrite ( DEST , bridge )
+ − 301
+ − 302 def finalWrite ( self , filename , file_buf ):
+ − 303 """Write the final generated file in [dest dir]/filename
+ − 304
+ − 305 @param filename: name of the file to generate
+ − 306 @param file_buf: list of lines (stings) of the file
+ − 307 """
+ − 308 if os . path . exists ( self . args . dest_dir ) and not os . path . isdir ( self . args . dest_dir ):
+ − 309 print ( "The destination dir [ %s ] can't be created: a file with this name already exists !" )
+ − 310 sys . exit ( 1 )
+ − 311 try :
+ − 312 if not os . path . exists ( self . args . dest_dir ):
+ − 313 os . mkdir ( self . args . dest_dir )
+ − 314 full_path = os . path . join ( self . args . dest_dir , filename )
+ − 315 if os . path . exists ( full_path ) and not self . args . force :
+ − 316 print ( "The destination file [ %s ] already exists ! Use --force to overwrite it" % full_path )
+ − 317 try :
+ − 318 with open ( full_path , 'w' ) as dest_file :
+ − 319 dest_file . write ( ' \n ' . join ( file_buf ))
+ − 320 except IOError :
+ − 321 print ( "Can't open destination file [ %s ]" % full_path )
+ − 322 except OSError :
+ − 323 print ( "It's not possible to generate the file, check your permissions" )
+ − 324 exit ( 1 )