#---------------------------------------------------------------------- # Name: methodparse.py # Purpose: # # Author: Riaan Booysen # # Created: 1999 # RCS-ID: $Id: methodparse.py,v 1.24 2005/05/18 13:21:11 riaan Exp $ # Copyright: (c) 1999 - 2005 Riaan Booysen # Licence: GPL #---------------------------------------------------------------------- """ Creation self. = () constructor compname classname params Initialisation self..?Set?() property compname propname value Event connection (2 identified formats) EVT_(, [], ) new formats .Bind(, , [id=]) event eventname compname windowid triggermeth --------------------------- Boilerplate class frame1(wxFrame): def _init_utils(self): pass def _init_ctrls(self): self._init_utils() def __init__(self): self._init_ctrls() """ import re, string import Preferences, Utils from Companions import EventCollections import sourceconst containers = [('(', ')'), ('{', '}'), ('[', ']')] containBegin = [_c[0] for _c in containers] containEnd = [_c[1] for _c in containers] def incLevel(level, pos): return level + 1, pos + 1 def decLevel(level, pos): return max(level - 1, 0), pos + 1 def safesplitfields(params, delim, returnBlanks = 0, containBegin=containBegin, containEnd=containEnd): """ Returns a list of parameters split on delim but not if delim are within containers (), {}, [], '', "" Also skip '', "" content Note that results are stripped Added returnBlanks flag for compatibility with python's split split('', ',') returns [''] Usually I want the result of such a split to be [] """ locparams = params list = [] i = 0 nestlevel = 0 singlequotelevel = 0 doublequotelevel = 0 if returnBlanks and not params.strip(): return [params] while i < len(locparams): curchar = locparams[i] # only check for delimiter if not inside some block if (not nestlevel) and (not singlequotelevel) and (not doublequotelevel)\ and (curchar == delim): param = locparams[:i] list.append(param) locparams = locparams[i +1:].strip() i = 0 continue if (not singlequotelevel) and (not doublequotelevel) and nestlevel and \ (curchar in containEnd): nestlevel, i = decLevel(nestlevel, i) continue if (not singlequotelevel) and (not doublequotelevel) and \ (curchar in containBegin): nestlevel, i = incLevel(nestlevel, i) continue # don't check anything except end quotes inside quote blocks if singlequotelevel and curchar == "'": singlequotelevel, i = decLevel(singlequotelevel, i) continue if doublequotelevel and curchar == '"': doublequotelevel, i = decLevel(doublequotelevel, i) continue if (not singlequotelevel) and curchar == '"': doublequotelevel, i = incLevel(doublequotelevel, i) elif (not doublequotelevel) and curchar == "'": singlequotelevel, i = incLevel(singlequotelevel, i) else: i = i + 1 # add last entry not delimited by comma lastentry = locparams.strip() if lastentry: list.append(lastentry) return list # XXX does not handle ",' yet def matchbracket(text, findBracket, dir=None): if findBracket in containBegin or (dir is not None and dir == -1): dir = -1 start = len(text)-1 end = -1 brktIdx = containBegin.index(findBracket) elif findBracket in containEnd or (dir is not None and dir == 1): dir = 1 start = 0 end = len(text) brktIdx = containEnd.index(findBracket) else: raise 'Unhandled bracket' # () {} [] levels = [0, 0, 0] for cIdx in range(start, end, dir): c = text[cIdx] if c in containBegin: idx = containBegin.index(c) levels[idx] = levels[idx] + dir elif c in containEnd: idx = containEnd.index(c) levels[idx] = levels[idx] - dir if levels[brktIdx] < 0: return cIdx return -1 def parseMixedBody(parseClasses, lines): """ Return a dictionary with keys representing classes that 'understood' the line and values a list of found instances of the found class """ cat = {} unmatched = [] for ParseClass in parseClasses: cat[ParseClass] = [] idx = 0 cont = '' while idx < len(lines): line = lines[idx].strip() ln = cont + line if (ln == 'pass') or (ln == ''): idx = idx + 1 continue for ParseClass in parseClasses: # build up the string if lines continue try: res = ParseClass(ln).value() except IncompleteLineError: cont = ln break else: if res: cat[ParseClass].append(res) cont = '' break else: if ln != '# %s'%sourceconst.code_gen_warning: unmatched.append(line) idx = idx + 1 return cat, unmatched class IncompleteLineError(Exception): pass class PerLineParser: """ Class which parses 1 line of source code """ def value(self): if self.m: return self else: return None def asText(self, stripFrameWinIdPrefix=''): """ Source representation of parsed line """ return '' def getIdPrefix(self, name): return 'wxID_%s'%name.upper() def checkId(self, id, idPrfx): return id not in EventCollections.reservedWxNames and id.startswith(idPrfx) def prependFrameWinId(self, frame): pass def __repr__(self): return self.asText() def renameCompName2(self, old_value, new_value): """ notification of a rename, override to catch renames to vars and params All parse lines should be notified of renames, not just entries which have that name """ if self.comp_name == old_value: self.comp_name = new_value def renameFrameName(self, old_value, new_value): self.frame_name = new_value def renameWindowId(self, windowid, old_frame_name, new_frame_name, old_ctrl_name, new_ctrl_name): xtra = windowid[len(Utils.windowIdentifier(old_frame_name, old_ctrl_name)):] return Utils.windowIdentifier(new_frame_name, new_ctrl_name)+xtra def extractKVParams(self, paramsStr): params = safesplitfields(paramsStr, ',') result = {} posArgs = 0 for param in params: try: sidx = param.index('=') except ValueError: result[posArgs] = param.strip() posArgs += 1 else: result[str(param[:sidx].strip())] = param[sidx+1:].strip() return result def KVParamsAsText(self, params): kvlist = [] sortedkeys = params.keys() sortedkeys.sort() for key in sortedkeys: if type(key) is type(0): kvlist.append(params[key]) else: kvlist.append(Preferences.cgKeywordArgFormat%{'keyword': key, 'value': params[key]}) return ', '.join(kvlist) def checkContinued(self, line): line = line.strip() if line and line[-1] in (',', '('): raise IncompleteLineError idc = '[A-Za-z_][A-Za-z0-9_]*' idc2 = '[A-Za-z_][A-Za-z0-9_/.]*' # self.name=class(params) is_constr = re.compile('^[ \t]*self[.](?P'+idc+')[ \t]*=[ \t]*(?P'+\ idc2+')\(((?P.*)(\)|,))?$') # class.__init__(self,params) is_constr_frm = re.compile('^[ \t]*(?P'+idc2+\ ')[.]__init__\(self,[ \t]*((?P.*)(\)|,))?$') # self.name=self.factory.method(params) is_constr_factory = re.compile('^[ \t]*self[.](?P'+idc+')[ \t]*=[ \t]*self[.](?P'+\ idc+')[.](?P'+idc+')\((?P.*)(\)|,)$') class ConstructorParse(PerLineParser): def __init__(self, line=None, comp_name='', class_name='', params=None): self.comp_name = comp_name self.class_name = class_name if params is None: self.params = {} else: self.params = params self.factory = None if line: self.m = is_constr_factory.search(line) if self.m: self.checkContinued(line) self.comp_name = self.m.group('name') self.class_name = '' self.factory = (self.m.group('factory'), self.m.group('method')) self.params = self.extractKVParams(self.m.group('params')) else: self.m = is_constr.search(line) if self.m: self.checkContinued(line) self.comp_name = self.m.group('name') self.class_name = self.m.group('class') self.params = self.extractKVParams(self.m.group('params')) else: self.m = is_constr_frm.search(line) if self.m: self.checkContinued(line) self.comp_name = '' self.class_name = self.m.group('class') self.params = self.extractKVParams(self.m.group('params')) def renameCompName2(self, old_value, new_value): if self.params.has_key('parent') and \ self.params['parent'] == Utils.srcRefFromCtrlName(old_value): self.params['parent'] = Utils.srcRefFromCtrlName(new_value) if self.comp_name == old_value: self.comp_name = new_value if self.params.has_key('name'): self.params['name'] = `new_value` if self.params.has_key('id') and \ self.params['id'] not in EventCollections.reservedWxNames: self.params['id'] = \ self.params['id'][:-len(old_value)]+new_value.upper() if self.factory and self.factory[0] == old_value: self.factory = (new_value, self.factory[1]) def prependFrameWinId(self, frame): idPrfx = self.getIdPrefix(frame) if self.params.has_key('id') and \ self.params['id'] not in EventCollections.reservedWxNames: self.params['id'] = idPrfx + self.params['id'] def asText(self, stripFrameWinIdPrefix=''): if stripFrameWinIdPrefix: idPrfx = self.getIdPrefix(stripFrameWinIdPrefix) params = {} params.update(self.params) if params.has_key('id') and self.checkId(params['id'], idPrfx): params['id'] = params['id'][len(idPrfx):] else: params = self.params if self.factory: factory, method = self.factory return 'self.%s = self.%s.%s(%s)' %(self.comp_name, factory, method, self.KVParamsAsText(params)) elif self.comp_name: return 'self.%s = %s(%s)' %(self.comp_name, self.class_name, self.KVParamsAsText(params)) else: return '%s.__init__(self, %s)' %(self.class_name, self.KVParamsAsText(params)) ##is_constr_col = re.compile('^[ \t]*self._init_coll_(?P'+idc+\ ## ')[.]__init__\(self,[ \t]*(?P.*)\)$') coll_init = '_init_coll_' idp = '[A-Za-z_][A-Za-z0-9_.]*' # self.name(params) is_prop = re.compile('^[ \t]*self[.](?P'+idp+')[ \t]*\([ \t]*(?P.*)[ \t]*(\)|,)$') class PropertyParse(PerLineParser): def __init__(self, line=None, comp_name='', prop_setter='', params=None, prop_name=''): self.comp_name = comp_name self.prop_setter = prop_setter if params is None: self.params = [] else: self.params = params self.params = params self.prop_name = prop_name if line: self.m = is_prop.search(line) if self.m: self.checkContinued(line) self.params = safesplitfields(self.m.group('params'), ',') compsetter = self.m.group('name').split('.') if len(compsetter) < 1: raise 'atleast 1 required '+`compsetter` if len(compsetter) == 1: self.comp_name = '' self.prop_setter = compsetter[0] elif len(compsetter) == 2: self.comp_name = compsetter[0] self.prop_setter = compsetter[1] else: raise 'Too many attribute levels' if self.prop_setter == 'Bind': self.m = None def renameCompName2(self, old_value, new_value): # XXX This is ugly but has to do until a better # XXX strategy is conceived. # XXX The problem is that the useful logic is in the # XXX companion which is not available for the clipboard # XXX The source's ctrl needs to be renamed for a companion # XXX to be created. # Rename references to ctrl in parameters of property oldCtrlSrcRef = Utils.srcRefFromCtrlName(old_value) newCtrlSrcRef = Utils.srcRefFromCtrlName(new_value) for idx in range(len(self.params)): segs = self.params[idx].split(oldCtrlSrcRef) if len(segs) > 1: lst = [segs[0]] for s in segs[1:]: if s and s[0] in string.letters+string.digits+'_': lst[-1] = lst[-1] + s else: lst.append(s) self.params[idx] = newCtrlSrcRef.join(lst) # Handle case where _init_coll_* methods are used as parameters param = self.params[idx] if param.startswith('self.'+coll_init): nameEnd = param.rfind('_') name = param[16:nameEnd] if name == old_value: self.params[idx] = 'self.'+coll_init+new_value+param[nameEnd:] PerLineParser.renameCompName2(self, old_value, new_value) def asText(self, stripFrameWinIdPrefix=''): return '%s.%s(%s)' %(Utils.srcRefFromCtrlName(self.comp_name), self.prop_setter, ', '.join(self.params)) def ctrlNameFromMeth(meth): return '_'.join(meth.split('_')[3:-1]) # self._init_coll_method(comp_name, params) is_coll_init = re.compile('^[ \t]*self[.](?P'+coll_init+idp+\ ')[ \t]*\((?P'+idp+')[ \t,]*(?P.*)(\)|,)$') class CollectionInitParse(PerLineParser): def __init__(self, line=None, comp_name='', method='', params=None, prop_name=''): self.comp_name = comp_name self.method = method if params is None: self.params = [] else: self.params = params self.prop_name = prop_name if line: self.m = is_coll_init.search(line) if self.m: self.checkContinued(line) self.params = safesplitfields(self.m.group('params'), ',') self.method = self.m.group('method') self.comp_name = self.m.group('comp_name')[5:] self.prop_name = self.method[len(coll_init)+len(self.comp_name)+1:] def getPropName(self): return self.method[len(coll_init)+len(self.comp_name)+1:] def renameCompName2(self, old_value, new_value): if self.comp_name == old_value: self.comp_name = new_value self.method = '%s%s_%s'%(coll_init, new_value, self.prop_name) def asText(self, stripFrameWinIdPrefix=False): return 'self.%s(%s)' %(self.method, ', '.join([Utils.srcRefFromCtrlName(self.comp_name)]+self.params)) # Because the object name of some collection items cannot be directly derived # from the source, the information is attached to the items def decorateParseItems(parseItems, ctrlName, frameName): for parseItem in parseItems: parseItem.ctrl_name = ctrlName parseItem.frame_name = frameName #item_parent = 'parent' # ident.method(params) is_coll_item_init = re.compile('^[ \t]*(?P'+idp+')[.](?P'+idc+\ ')[ \t]*\([ \t,]*(?P.*)(\)|,)') class CollectionItemInitParse(PerLineParser): def __init__(self, line=None, comp_name='', method='', params=None): self.comp_name = comp_name self.ctrl_name = self.frame_name = '&None&' self.method = method if params is None: self.params = {} else: self.params = params if line: self.m = is_coll_item_init.search(line) if self.m: self.checkContinued(line) self.comp_name = self.m.group('ident') self.method = self.m.group('method') self.params = self.extractKVParams(self.m.group('params')) def renameFrameName(self, old_value, new_value): PerLineParser.renameFrameName(self, old_value, new_value) if self.params.has_key('id'): self.params['id'] = self.renameWindowId(self.params['id'], old_value, new_value, self.ctrl_name, self.ctrl_name) def renameCompName2(self, old_value, new_value): # Regenerate window ids if self.ctrl_name == old_value: self.ctrl_name = new_value if self.params.has_key('id'): self.params['id'] = self.renameWindowId(self.params['id'], self.frame_name, self.frame_name, old_value, new_value) # Check for references src_old = Utils.srcRefFromCtrlName(old_value) for key, val in self.params.items()[:]: if val == src_old: self.params[key] = Utils.srcRefFromCtrlName(new_value) def prependFrameWinId(self, frame): idPrfx = self.getIdPrefix(frame) if self.params.has_key('id') and \ self.params['id'] not in EventCollections.reservedWxNames: self.params['id'] = idPrfx + self.params['id'] def asText(self, stripFrameWinIdPrefix=''): if stripFrameWinIdPrefix: idPrfx = self.getIdPrefix(stripFrameWinIdPrefix) params = {} params.update(self.params) if params.has_key('id') and self.checkId(params['id'], idPrfx): params['id'] = params['id'][len(idPrfx):] else: params = self.params return '%s.%s(%s)' %(self.comp_name, self.method, self.KVParamsAsText(params)) ### EVT_evtname(name, func) ##is_event2p = re.compile('^[ \t]*EVT_(?P'+idc+')[ \t]*\([ \t]*(?P'+\ ## idp+')[ \t]*\,[ \t]*self[.](?P'+idp+')(\)|,)$') ### EVT_evtname(name, wid, func) ##is_event3p = re.compile('^[ \t]*EVT_(?P'+idc+')[ \t]*\([ \t]*(?P'+\ ## idp+')[ \t]*\,(?P.*)[ \t]*\,[ \t]*self[.](?P'+idp+')(\)|,)$') # EVT_evtname(params) #is_event = re.compile('^[ \t]*EVT_(?P'+idc+')[ \t]*\([ \t]*(?P.*)(\)|,)$') is_event = re.compile('^self(?P[.]'+idc2+')*\.Bind\([ \t]*(?P'+idc2+'),[ \t]*(?P.*)(\)|,)$') ##class EventParse(PerLineParser): ## def __init__(self, line=None, comp_name='', event_name='', ## trigger_meth = '', windowid = None): ## self.comp_name = comp_name ## self.ctrl_name = self.frame_name = '&None&' ## self.event_name = event_name ## self.trigger_meth = trigger_meth ## self.prev_trigger_meth = '' ## self.windowid = windowid ## self.show_scope = 'own' # can be 'all', 'own' ## if line: ## self.m = is_event.search(line) ## if self.m: ## self.checkContinued(line) ## ## params = self.m.group('params').split(',') ## name = params[0].strip() ## if name != 'self': ## self.comp_name = name[5:] ## if len(params) == 2: ## self.trigger_meth = params[1].strip()[5:] ## elif len(params) == 3: ## self.windowid = params[1].strip() ## self.trigger_meth = params[2].strip()[5:] ## ## self.event_name = self.m.group('evtname') ## ## def renameFrameName(self, old_value, new_value): ## PerLineParser.renameFrameName(self, old_value, new_value) ## if self.windowid: ## self.windowid = self.renameWindowId(self.windowid, ## old_value, new_value, self.ctrl_name, self.ctrl_name) ## ## def renameCompName2(self, old_value, new_value): ## if self.ctrl_name == old_value: ## self.ctrl_name = new_value ## if self.comp_name: ## self.comp_name = new_value ## # Check for command events ## if self.windowid: ## self.windowid = self.renameWindowId(self.windowid, ## self.frame_name, self.frame_name, old_value, new_value) ## ## def prependFrameWinId(self, frame): ## if self.windowid: ## idPrfx = self.getIdPrefix(frame) ## if self.windowid not in EventCollections.reservedWxNames: ## self.windowid = idPrfx + self.windowid ## ## def asText(self, stripFrameWinIdPrefix=''): ## if self.windowid: ## windowid = self.windowid ## if stripFrameWinIdPrefix: ## idPrfx = self.getIdPrefix(stripFrameWinIdPrefix) ## if self.checkId(windowid, idPrfx): ## windowid = windowid[len(idPrfx):] ## ## return 'EVT_%s(%s, %s, self.%s)' %(self.event_name, ## Utils.srcRefFromCtrlName(self.comp_name), windowid, self.trigger_meth) ## else: ## return 'EVT_%s(%s, self.%s)' %(self.event_name, ## Utils.srcRefFromCtrlName(self.comp_name), self.trigger_meth) class EventParse(PerLineParser): def __init__(self, line=None, comp_name='', event_name='', trigger_meth = '', windowid = None): self.comp_name = comp_name self.ctrl_name = self.frame_name = '&None&' self.event_name = event_name self.trigger_meth = trigger_meth self.prev_trigger_meth = '' self.windowid = windowid self.show_scope = 'own' # can be 'all', 'own' if line: self.m = is_event.search(line) if self.m: self.checkContinued(line) params = self.m.group('otherparams').split(',') name = self.m.group('compname') if name is not None: self.comp_name = name[1:] if len(params) == 1: self.trigger_meth = params[0].strip()[5:] elif len(params) == 2: self.trigger_meth = params[0].strip()[5:] self.windowid = params[1].split('=')[-1].strip() self.event_name = self.m.group('evtname') def renameFrameName(self, old_value, new_value): PerLineParser.renameFrameName(self, old_value, new_value) if self.windowid: self.windowid = self.renameWindowId(self.windowid, old_value, new_value, self.ctrl_name, self.ctrl_name) def renameCompName2(self, old_value, new_value): if self.ctrl_name == old_value: self.ctrl_name = new_value if self.comp_name: self.comp_name = new_value # Check for command events if self.windowid: self.windowid = self.renameWindowId(self.windowid, self.frame_name, self.frame_name, old_value, new_value) def prependFrameWinId(self, frame): if self.windowid: idPrfx = self.getIdPrefix(frame) if self.windowid not in EventCollections.reservedWxNames: self.windowid = idPrfx + self.windowid def asText(self, stripFrameWinIdPrefix=''): if self.windowid: windowid = self.windowid if stripFrameWinIdPrefix: idPrfx = self.getIdPrefix(stripFrameWinIdPrefix) if self.checkId(windowid, idPrfx): windowid = windowid[len(idPrfx):] return '%s.Bind(%s, self.%s, id=%s)' %(Utils.srcRefFromCtrlName(self.comp_name), self.event_name, self.trigger_meth, windowid) else: return '%s.Bind(%s, self.%s)' %(Utils.srcRefFromCtrlName(self.comp_name), self.event_name, self.trigger_meth) def testRename(): cp = ConstructorParse("self.menu1 = wx.Menu(title = '')") print cp.asText('wx.Frame1') cp = ConstructorParse("self.button1 = wx.Button(id = wxID_WXFRAME1BUTTON1, label = 'button1', name = 'button1', parent = self, pos = wx.Point(232, 168), size = wx.Size(75, 23), style = 0)") print cp.asText('wx.Frame1') cp2 = ConstructorParse(cp.asText('wx.Frame1')) cp2.prependFrameWinId('wx.Frame2') print cp2.asText() ciip = CollectionItemInitParse("parent.Append(checkable = False, helpString = '', id = wxID_WXFRAME1MENU1ITEMS0, item = 'Items0')") print ciip.asText('wx.Frame1') ciip2 = CollectionItemInitParse(ciip.asText('wx.Frame1')) ciip2.prependFrameWinId('wx.Frame2') print ciip2.asText() #ep = EventParse("EVT_MENU(self, wxID_WXFRAME1MENU1ITEMS0, self.OnMenu1items0Menu)") #print ep.asText('wxFrame1') #ep2 = EventParse(ep.asText('wxFrame1')) #ep2.prependFrameWinId('wxFrame2') print ep2.asText() def testEvent(): pass def test(): cp = parseMixedBody([ConstructorParse], [ " wxFrame.__init__(self, style = wxDEFAULT_FRAME_STYLE, name = '', ", " parent = prnt, title = 'wxFrame2', id = wxID_WXFRAME2, ", " pos = (-1, -1), size = (-1, -1)) ", " self._init_utils()", ])#[0][ConstructorParse] print cp#[0].params if __name__ == '__main__': test()