Package slum :: Module collectClasses
[hide private]
[frames] | no frames]

Source Code for Module slum.collectClasses

  1  # 
  2  # collectClasses.py -   collect classes defined inside .slum files and return as 
  3  #                                               a dictionary 
  4  # 
  5  #    Copyright (C) 2008 - Roberto Hradec 
  6  # 
  7  # --------------------------------------------------------------------------- 
  8  #        This file is part of SLUM. 
  9  # 
 10  #    SLUM is free software: you can redistribute it and/or modify 
 11  #    it under the terms of the GNU General Public License as published by 
 12  #    the Free Software Foundation, either version 3 of the License, or 
 13  #    (at your option) any later version. 
 14  # 
 15  #    SLUM is distributed in the hope that it will be useful, 
 16  #    but WITHOUT ANY WARRANTY; without even the implied warranty of 
 17  #    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the 
 18  #    GNU General Public License for more details. 
 19  # 
 20  #    You should have received a copy of the GNU General Public License 
 21  #    along with SLUM.  If not, see <http://www.gnu.org/licenses/>. 
 22  # --------------------------------------------------------------------------- 
 23   
 24   
 25  import os, glob,  traceback 
 26  import md5 as __md5 
 27   
 28   
 29  defaultSearchPath = 'SLUM_SEARCH_PATH' 
 30  defaultOnlineRepositorie = 'http://slum.hradec.com/repositorie' 
 31   
 32  #shortcut methods 
33 -def all():
34 ''' 35 high level function 36 return all classes found on disk/online, that suscessfully passed the runtime/syntax error check. 37 ''' 38 return collectSlumClasses().allClasses
39
40 -def refresh():
41 ''' 42 high level function 43 force a refresh of all templates from disk/online 44 ''' 45 collectSlumClasses(refresh=True)
46
47 -def init():
48 ''' 49 high level function 50 loads all templates from disk/online 51 ''' 52 collectSlumClasses()
53
54 -def path(name):
55 ''' 56 high level function 57 returns a updated path for a given template name. 58 ''' 59 classes = collectSlumClasses().allClasses 60 return classes[name]['path']
61
62 -def md5(name):
63 ''' 64 high level function 65 returns a updated path for a given template name. 66 ''' 67 classes = collectSlumClasses().allClasses 68 return classes[name]['md5']
69 70
71 -def template(name):
72 ''' 73 high level function 74 returns a class obj for the givem template name. 75 The returned object will be the class defined in the template, properly evaluated for runtime/syntax errors. 76 ''' 77 classes = collectSlumClasses().allClasses 78 if not _test(classes[name]): 79 return None 80 c = evalSlumClass(classes[name]['code'], name) 81 c.name = name 82 c.md5 = classes[name]['md5'] 83 c.path = classes[name]['path'] 84 c.refresh = refresh() 85 return c
86 87
88 -def _test(classe):
89 ''' 90 low level class (shouldn't be directly used - refer to high level functions/classes) 91 test methods in a template for runtime/syntax errors. 92 93 todo: only delight() method being test. Need to implement a 94 loop that tests all methods in the given class. 95 ''' 96 ret = True 97 c = evalSlumClass(classe['code'], classe['name']) 98 #for each in c._renderers(): 99 try: 100 tmp=c.delight(c._dictParameters(value=True)) 101 except: 102 print "Runtime error in %s delight method.\n%s" % (classe['path'], traceback.format_exc()) 103 ret = False 104 return ret
105 106
107 -def _readSlumFile(path):
108 ''' 109 low level class (shouldn't be directly used - refer to high level functions/classes) 110 reads a template from a file and returns it as a list (one line per record) 111 ''' 112 slumCode = open(path).readlines() 113 # fix txt if writen in windows 114 for n in range(len(slumCode)): 115 slumCode[n] = slumCode[n].replace('\r','') 116 return slumCode
117 118
119 -def evalSlumClass(code, classeName):
120 ''' 121 low level class (shouldn't be directly used - refer to high level functions/classes) 122 execute "code" string, and returns the class object for "classeName" 123 ''' 124 125 ret = '' 126 newCode = 'import traceback\n' 127 newCode += 'from slum import *\n' 128 newCode += 'try:\n\t' 129 newCode += code.replace('\n','\n\t') 130 newCode += '\nexcept:\n' 131 newCode += '\traise Exception("Syntax error in slum template: \\n%s" % traceback.format_exc() )\n\n' 132 #print newCode 133 #print 'bummmm', classeName 134 try: 135 exec newCode in globals() 136 except: 137 tmp = "" 138 lineNumber = 1 139 for each in newCode.split('\n'): 140 tmp += "%4d: %s\n" % (lineNumber, each) 141 lineNumber += 1 142 raise Exception("Error: %s\n\nOn slum template code: \n%s" % (traceback.format_exc(), tmp ) ) 143 #print 'bummmm2' 144 ret = eval('%s()' % classeName) 145 #print 'bummmm3' 146 return ret
147
148 -def getMD5(code):
149 ''' 150 low level class (shouldn't be directly used - refer to high level functions/classes) 151 calculates md5 for the given code 152 ''' 153 return __md5.md5( code ).digest()
154
155 -def checkMD5(md5data, file):
156 ''' 157 low level class (shouldn't be directly used - refer to high level functions/classes) 158 check if the md5 matchs the md5 of the source file. 159 ''' 160 return md5data == getMD5( ''.join( _readSlumFile(file) ) )
161
162 -class collectSlumClasses:
163 ''' 164 low level class (shouldn't be directly used - refer to high level functions/classes) 165 class responsible for deal with slum files and classes 166 initializing a new class object will trigger the search of *.slum files in the local search path and online repositories. 167 a cache system is in place to avoid un-necessary re-caching by clients. 168 clients dont need to care about it. just re-create the class and the new object will pick up the data from the cache 169 automatically, unless refresh parameter is True. 170 '''
171 - def __init__(self, refresh=None, searchPath = None, onlineRepositories = None):
172 ''' 173 gathers slum classes from local disk/network and online repositories 174 175 this class stores the gathered classes and search paths as global variables, so 176 if the class is re-created, it will retain the data from a previous object. (a sort of cache) 177 178 this is very useful when using this class in clients, so a call for the class in a initialization context 179 will store the data, and the data can be quickly retrieve later in a diferent context, even whitout the original 180 search paths. 181 182 also, a call to _refresh method (or creating the class object using the refresh parameter = True) 183 will refresh the class caches using the cached search paths. This way, 184 the search paths only need to be defined in the initialization context. As the class caches are global, even 185 the _refresh method can be called from a totally separated context. 186 187 This cache mechanism frees the client from the tedious and error prone tasks of store all this data so it can 188 be accessed in diferent contexts. It also speeds up the whole proccess, avoiding multiple un-necessary 189 disk/network accesses. 190 ''' 191 global searchPathCache 192 global onlineRepositoriesCache 193 194 # it paths specified, update caches 195 if searchPath: 196 searchPathCache = searchPath 197 else: 198 searchPathCache = defaultSearchPath 199 200 if onlineRepositories: 201 onlineRepositoriesCache = onlineRepositories 202 else: 203 onlineRepositoriesCache = defaultOnlineRepositorie 204 205 # store in the class for later use by methods 206 self.searchPath = searchPathCache 207 self.onlineRepositories = onlineRepositoriesCache 208 209 # makes sure searchpaths are lists 210 if type(self.searchPath) == str: 211 self.searchPath = [self.searchPath] 212 if type(self.onlineRepositories) == str: 213 self.onlineRepositories = [self.onlineRepositories] 214 215 #cache classes if refresh is set or caches are empty 216 global localClasses 217 global onlineClasses 218 try: # check if variables are defined or not 219 localClasses 220 onlineClasses 221 if refresh or (not localClasses and not onlineClasses): 222 raise 223 except: 224 localClasses, onlineClasses = self._refresh() 225 226 # store then separeted for now... 227 self.localClasses = localClasses 228 self.onlineClasses = onlineClasses 229 230 # combine then 231 self.allClasses = self.localClasses 232 self.allClasses.update( self.onlineClasses )
233
234 - def _refresh(self):
235 global localClasses 236 global onlineClasses 237 def printClasses(classes): 238 idz = [] 239 countIDz = {} 240 for classe in classes.keys(): 241 id = classes[classe]['ID'] 242 idz.append('slum: found ID %4d Class %s' % ( id,classe)) 243 if not countIDz.has_key(id): 244 countIDz[id] = [] 245 countIDz[id].append(classe) 246 247 idz.sort() 248 clash = None 249 for each in idz: 250 id = int( each.split('ID ')[1].split(' C')[0] ) 251 print each, 252 if len(countIDz[id])>1: 253 print "%s> ERROR: ID Clashing -" % ("="*(50-len(each))),countIDz[id] 254 clash = True 255 else: 256 print 257 258 if clash: 259 global localClasses 260 global onlineClasses 261 localClasses = None 262 onlineClasses = None 263 raise Exception("\n\nClash of templates in slum initialization. Fix it!")
264 265 print 'slum: local caching...' 266 localClasses = self.local() 267 printClasses(localClasses) 268 print 'slum: online caching' 269 onlineClasses = self.online() 270 printClasses(onlineClasses ) 271 print 'slum: all done.' 272 return ( localClasses, onlineClasses )
273
274 - def _registerSlumFile(self, slumCode, path):
275 ''' 276 based on a string with slum code on it, registers all class names in it into a temp db 277 for each name, it adds a "code" key with the source code, so later a client 278 can execute it and retrieve the class object at runtime. 279 280 this class is a support class for local and online methods! 281 ''' 282 283 284 # keep dir() to compare with new dir() after 285 # code execution to find the new classes 286 # defined inside slum file 287 288 # execute code 289 env = {} 290 registry = None 291 newClasses = None 292 exec 'from slum import *' in env 293 registry = env.copy() 294 exec '\n'.join(slumCode) in env 295 newClasses = env 296 #print filter(lambda x: x not in registry.keys(), newClasses.keys()), env 297 298 # compare current dir() with old one and get the new ones 299 # loop trough all defined classes inside the current slum file 300 # and register then in a dict, if no other with the same name is already registered. 301 slumClasses={} 302 idz = [] 303 for classe in filter(lambda x: x not in registry, newClasses): 304 #print 'slum: found %s' % filter(lambda x: 'class %s' % classe in ' '.join(x.split()), slumCode)[0].strip().strip(':') 305 if not slumClasses.has_key(classe): 306 slumClasses[classe] = {} 307 slumClasses[classe]['code'] = ''.join(slumCode) 308 # we also store the class name so it can be retrieve later in the client, 309 # even if the data is stored in a diferent format than a dict. 310 slumClasses[classe]['name'] = classe 311 slumClasses[classe]['md5'] = getMD5( slumClasses[classe]['code'] ) 312 slumClasses[classe]['path'] = path 313 314 # execute code to catch potential runtime errors so clients don't have to 315 if not _test(slumClasses[classe]): 316 del slumClasses[classe] 317 318 slumClasses[classe]['ID'] = evalSlumClass(slumClasses[classe]['code'], classe).ID() 319 320 return slumClasses
321 322
323 - def online(self):
324 ''' 325 same as local, but for online repositories. 326 returns data in the same format as local 327 ''' 328 return {}
329
330 - def readSlumFile(self, path):
331 return self._registerSlumFile( _readSlumFile(path), path )
332
333 - def local(self):
334 ''' 335 returns a dictionary with all classes found in the searchpath 336 the dictionary is organized as: 337 338 { 'class name' : 339 {'code' : 'code for the class'} 340 } 341 ''' 342 # loop trough searchpath an look for all *.slum files 343 slumClasses={} 344 for searchPath in self.searchPath: 345 if os.environ.has_key(searchPath): 346 env = os.environ[searchPath] 347 for path in env.split(os.path.pathsep): 348 for each in glob.glob( os.path.join( path, '*.slum' ) ): 349 slumClasses.update( self.readSlumFile(each) ) 350 351 352 return slumClasses
353