1 #!/usr/bin/env python 2 3 """ 4 The micropython package for processing Python source code. The code originates 5 from the simplify package but has had various details related to that package 6 removed. 7 8 Copyright (C) 2006, 2007 Paul Boddie <paul@boddie.org.uk> 9 10 This program is free software; you can redistribute it and/or modify it under 11 the terms of the GNU General Public License as published by the Free Software 12 Foundation; either version 3 of the License, or (at your option) any later 13 version. 14 15 This program is distributed in the hope that it will be useful, but WITHOUT 16 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 17 FOR A PARTICULAR PURPOSE. See the GNU General Public License for more 18 details. 19 20 You should have received a copy of the GNU General Public License along with 21 this program. If not, see <http://www.gnu.org/licenses/>. 22 23 -------- 24 25 To use this module, an importer should be constructed and the load_from_file 26 method used. Here, the standard path for module searching is employed: 27 28 importer = Importer(sys.path) 29 importer.load_from_file(filename) 30 importer.vacuum() 31 32 Such importer objects are the most convenient mechanism through which the 33 functionality of the micropython package may be accessed. 34 """ 35 36 import micropython.ast 37 import micropython.inspect 38 import micropython.table 39 import os 40 try: 41 set 42 except NameError: 43 from sets import Set as set 44 45 InspectError = micropython.inspect.InspectError 46 47 class Importer: 48 49 "An import machine, searching for and loading modules." 50 51 def __init__(self, path=None, verbose=0): 52 53 """ 54 Initialise the importer with the given search 'path' - a list of 55 directories to search for Python modules. 56 57 The optional 'verbose' parameter causes output concerning the activities 58 of the object to be produced if set to a true value (not the default). 59 """ 60 61 self.path = path or [os.getcwd()] 62 self.verbose = verbose 63 self.modules = {} 64 self.loading = set() 65 66 def vacuum(self): 67 68 "Tidy up the modules." 69 70 for name, module in self.modules.items(): 71 if module.loaded: 72 module.vacuum() 73 else: 74 del self.modules[name] 75 76 def get_modules(self): 77 78 "Return all modules known to the importer." 79 80 return self.modules.values() 81 82 def get_image(self, objtable=None, paramtable=None): 83 84 "Return a dictionary mapping modules to structures." 85 86 objtable = objtable or self.get_object_table() 87 paramtable = paramtable or self.get_parameter_table() 88 89 image = [] 90 91 for module_name, module in self.modules.items(): 92 pos = len(image) 93 94 # Position the module in the image and make a translation. 95 96 module.location = pos 97 trans = micropython.ast.Translation(module, objtable, paramtable) 98 99 # Append constants to the image. 100 101 for const in module.constants(): 102 const.location = pos 103 image.append(const) 104 pos += 1 105 106 # Add header details. 107 108 image.append(module) 109 pos += 1 110 111 # Append module attributes to the image. 112 113 attributes = module.module_attributes() 114 image += module.to_list(attributes) 115 pos += len(attributes.keys()) 116 117 # Append classes and functions to the image. 118 119 for obj in module.all_objects: 120 if isinstance(obj, micropython.inspect.Class): 121 122 # Position the class in the image. 123 124 obj.location = pos 125 126 # Add header details. 127 128 image.append(obj) 129 pos += 1 130 131 # Append class attributes to the image. 132 133 attributes = obj.class_attributes() 134 image += module.to_list(attributes) 135 pos += len(attributes.keys()) 136 137 # Append the class-level code to the image. 138 # NOTE: An extra optimisation would involve 139 # NOTE: pre-initialisation of attributes and no code being 140 # NOTE: generated here, or perhaps the pre-initialisation of 141 # NOTE: methods and only other attribute-related code being 142 # NOTE: generated here. 143 144 obj.code_location = pos 145 code = trans.get_code(obj) 146 image += code 147 pos += len(code) 148 149 # NOTE: Generate module and function code here. 150 151 elif isinstance(obj, micropython.inspect.Function): 152 153 # Position the function in the image. 154 155 obj.location = pos 156 157 # Add header details. 158 159 image.append(obj) 160 pos += 1 161 162 # Append the function code to the image. 163 164 obj.code_location = obj.location = pos 165 code = trans.get_code(obj) 166 image += code 167 pos += len(code) 168 169 # Remember the position of the module code. 170 171 module.code_location = pos 172 173 # Append the module top-level code to the image. 174 175 code = trans.get_module_code() 176 image += code 177 pos += len(code) 178 179 return image 180 181 def get_object_table(self): 182 183 "Return a table with details of attributes for classes and modules." 184 185 t = micropython.table.Table() 186 for module in self.get_modules(): 187 t.add(module.full_name(), module.module_attributes()) 188 for obj in module.all_objects: 189 if isinstance(obj, micropython.inspect.Class): 190 t.add(obj.full_name(), obj.all_attributes()) 191 return t 192 193 def get_parameter_table(self): 194 195 "Return a table with details of parameters for functions and methods." 196 197 t = micropython.table.Table() 198 for module in self.get_modules(): 199 for obj in module.all_objects: 200 if isinstance(obj, micropython.inspect.Function): 201 t.add(obj.full_name(), obj.parameters()) 202 return t 203 204 # Import methods. 205 206 def find_in_path(self, name): 207 208 """ 209 Find the given module 'name' in the search path, returning None where no 210 such module could be found, or a 2-tuple from the 'find' method 211 otherwise. 212 """ 213 214 for d in self.path: 215 m = self.find(d, name) 216 if m: return m 217 return None 218 219 def find(self, d, name): 220 221 """ 222 In the directory 'd', find the given module 'name', where 'name' can 223 either refer to a single file module or to a package. Return None if the 224 'name' cannot be associated with either a file or a package directory, 225 or a 2-tuple from '_find_package' or '_find_module' otherwise. 226 """ 227 228 m = self._find_package(d, name) 229 if m: return m 230 m = self._find_module(d, name) 231 if m: return m 232 return None 233 234 def _find_module(self, d, name): 235 236 """ 237 In the directory 'd', find the given module 'name', returning None where 238 no suitable file exists in the directory, or a 2-tuple consisting of 239 None (indicating that no package directory is involved) and a filename 240 indicating the location of the module. 241 """ 242 243 name_py = name + os.extsep + "py" 244 filename = self._find_file(d, name_py) 245 if filename: 246 return None, filename 247 return None 248 249 def _find_package(self, d, name): 250 251 """ 252 In the directory 'd', find the given package 'name', returning None 253 where no suitable package directory exists, or a 2-tuple consisting of 254 a directory (indicating the location of the package directory itself) 255 and a filename indicating the location of the __init__.py module which 256 declares the package's top-level contents. 257 """ 258 259 filename = self._find_file(d, name) 260 if filename: 261 init_py = "__init__" + os.path.extsep + "py" 262 init_py_filename = self._find_file(filename, init_py) 263 if init_py_filename: 264 return filename, init_py_filename 265 return None 266 267 def _find_file(self, d, filename): 268 269 """ 270 Return the filename obtained when searching the directory 'd' for the 271 given 'filename', or None if no actual file exists for the filename. 272 """ 273 274 filename = os.path.join(d, filename) 275 if os.path.exists(filename): 276 return filename 277 else: 278 return None 279 280 def load(self, name, return_leaf=0): 281 282 """ 283 Load the module or package with the given 'name'. Return an object 284 referencing the loaded module or package, or None if no such module or 285 package exists. 286 """ 287 288 if self.modules.has_key(name) and self.modules[name].loaded: 289 #print "Cached (%s)" % name 290 return self.modules[name] 291 if self.verbose: 292 print "Loading", name 293 294 # Split the name into path components, and try to find the uppermost in 295 # the search path. 296 297 path = name.split(".") 298 m = self.find_in_path(path[0]) 299 if not m: 300 if self.verbose: 301 print "Not found (%s)" % path[0] 302 return None # NOTE: Import error. 303 d, filename = m 304 305 # Either acquire a reference to an already-imported module, or load the 306 # module from a file. 307 308 top = module = self.load_from_file(filename, path[0]) 309 310 # For hierarchical names, traverse each path component... 311 312 if len(path) > 1: 313 if not d: 314 if self.verbose: 315 print "No package (%s)" % filename 316 return None # NOTE: Import error (package not found). 317 else: 318 self.add_submodules(d, module) 319 320 path_so_far = path[:1] 321 for p in path[1:]: 322 path_so_far.append(p) 323 324 # Find the package or module concerned. 325 326 m = self.find(d, p) 327 if not m: 328 if self.verbose: 329 print "Not found (%s)" % p 330 return None # NOTE: Import error. 331 d, filename = m 332 module_name = ".".join(path_so_far) 333 334 # Either reference an imported module or load one from a file. 335 336 submodule = self.load_from_file(filename, module_name) 337 338 if d: 339 self.add_submodules(d, module) 340 341 # Store the submodule within its parent module. 342 343 module.set_module(p, submodule) 344 module = submodule 345 346 # Return either the deepest or the uppermost module. 347 348 if return_leaf: 349 return module 350 else: 351 return top 352 353 def load_from_file(self, name, module_name=None): 354 355 """ 356 Load the module with the given 'name' (which may be a full module path). 357 """ 358 359 if module_name is None: 360 module_name = "__main__" 361 362 module = self.add_module(module_name) 363 if not module.loaded and module not in self.loading: 364 self.loading.add(module) 365 #print "Parsing", name 366 module.parse(name) 367 #print "Done", name 368 self.loading.remove(module) 369 module.loaded = 1 370 371 # Record the module. 372 373 #print "Loaded", module_name, "with namespace", module.namespace.keys() 374 return module 375 376 def add_module(self, module_name): 377 378 """ 379 Return the module with the given 'module_name', adding a new module 380 object if one does not already exist. 381 """ 382 383 if not self.modules.has_key(module_name): 384 self.modules[module_name] = module = micropython.inspect.InspectedModule(module_name, self) 385 else: 386 module = self.modules[module_name] 387 return module 388 389 def add_submodules(self, pathname, module): 390 391 """ 392 Work around insufficient __all__ declarations and examine the directory 393 with the given 'pathname', adding submodules to the given 'module'. 394 """ 395 396 for filename in os.listdir(pathname): 397 submodule = os.path.splitext(filename)[0] 398 module.set_module(submodule, self.add_module(module.name + "." + submodule)) 399 400 # vim: tabstop=4 expandtab shiftwidth=4