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 class Importer: 46 47 "An import machine, searching for and loading modules." 48 49 def __init__(self, path=None, verbose=0): 50 51 """ 52 Initialise the importer with the given search 'path' - a list of 53 directories to search for Python modules. 54 55 The optional 'verbose' parameter causes output concerning the activities 56 of the object to be produced if set to a true value (not the default). 57 """ 58 59 self.path = path or [os.getcwd()] 60 self.verbose = verbose 61 self.modules = {} 62 self.loading = set() 63 64 def vacuum(self): 65 66 "Tidy up the modules." 67 68 for name, module in self.modules.items(): 69 if module.loaded: 70 module.vacuum() 71 else: 72 del self.modules[name] 73 74 def get_modules(self): 75 76 "Return all modules known to the importer." 77 78 return self.modules.values() 79 80 def get_image(self, objtable=None, paramtable=None): 81 82 "Return a dictionary mapping modules to structures." 83 84 objtable = objtable or self.get_object_table() 85 paramtable = paramtable or self.get_parameter_table() 86 87 image = [] 88 89 for module_name, module in self.modules.items(): 90 pos = len(image) 91 92 # Position the module in the image and make a translation. 93 94 module.location = pos 95 trans = micropython.ast.Translation(module, objtable, paramtable) 96 97 # Add header details. 98 99 image.append(module) 100 pos += 1 101 102 # Append constants to the image. 103 104 for const in module.constants(): 105 const.location = pos 106 image.append(const) 107 pos += 1 108 109 # Append module attributes to the image. 110 111 attributes = module.module_attributes() 112 113 for name in module.module_attribute_names(): 114 attr = attributes[name] 115 image.append(attr) 116 pos += 1 117 118 # Append classes and functions to the image. 119 120 for obj in module.all_objects: 121 if isinstance(obj, micropython.inspect.Class): 122 123 # Position the class in the image. 124 125 obj.location = pos 126 127 # Add header details. 128 129 image.append(obj) 130 pos += 1 131 132 # Append class attributes to the image. 133 134 attributes = obj.class_attributes() 135 136 for name in obj.class_attribute_names(): 137 attr = attributes[name] 138 image.append(attr) 139 pos += 1 140 141 # Append the class-level code to the image. 142 # NOTE: An extra optimisation would involve 143 # NOTE: pre-initialisation of attributes and no code being 144 # NOTE: generated here, or perhaps the pre-initialisation of 145 # NOTE: methods and only other attribute-related code being 146 # NOTE: generated here. 147 148 obj.code_location = obj.location = pos 149 code = trans.get_code(obj) 150 image += code 151 pos += len(code) 152 153 # NOTE: Generate module and function code here. 154 155 elif isinstance(obj, micropython.inspect.Function): 156 157 # Position the function in the image. 158 159 obj.location = pos 160 161 # Add header details. 162 163 image.append(obj) 164 pos += 1 165 166 # Append the function code to the image. 167 168 obj.code_location = obj.location = pos 169 code = trans.get_code(obj) 170 image += code 171 pos += len(code) 172 173 # Remember the position of the module code. 174 175 module.code_location = pos 176 177 # Append the module top-level code to the image. 178 179 code = trans.get_module_code() 180 image += code 181 pos += len(code) 182 183 return image 184 185 def get_object_table(self): 186 187 "Return a table with details of attributes for classes and modules." 188 189 t = micropython.table.Table() 190 for module in self.get_modules(): 191 t.add(module.full_name(), module.module_attributes()) 192 for obj in module.all_objects: 193 if isinstance(obj, micropython.inspect.Class): 194 t.add(obj.full_name(), obj.all_attributes()) 195 return t 196 197 def get_parameter_table(self): 198 199 "Return a table with details of parameters for functions and methods." 200 201 t = micropython.table.Table() 202 for module in self.get_modules(): 203 for obj in module.all_objects: 204 if isinstance(obj, micropython.inspect.Function): 205 t.add(obj.full_name(), obj.parameters()) 206 return t 207 208 # Import methods. 209 210 def find_in_path(self, name): 211 212 """ 213 Find the given module 'name' in the search path, returning None where no 214 such module could be found, or a 2-tuple from the 'find' method 215 otherwise. 216 """ 217 218 for d in self.path: 219 m = self.find(d, name) 220 if m: return m 221 return None 222 223 def find(self, d, name): 224 225 """ 226 In the directory 'd', find the given module 'name', where 'name' can 227 either refer to a single file module or to a package. Return None if the 228 'name' cannot be associated with either a file or a package directory, 229 or a 2-tuple from '_find_package' or '_find_module' otherwise. 230 """ 231 232 m = self._find_package(d, name) 233 if m: return m 234 m = self._find_module(d, name) 235 if m: return m 236 return None 237 238 def _find_module(self, d, name): 239 240 """ 241 In the directory 'd', find the given module 'name', returning None where 242 no suitable file exists in the directory, or a 2-tuple consisting of 243 None (indicating that no package directory is involved) and a filename 244 indicating the location of the module. 245 """ 246 247 name_py = name + os.extsep + "py" 248 filename = self._find_file(d, name_py) 249 if filename: 250 return None, filename 251 return None 252 253 def _find_package(self, d, name): 254 255 """ 256 In the directory 'd', find the given package 'name', returning None 257 where no suitable package directory exists, or a 2-tuple consisting of 258 a directory (indicating the location of the package directory itself) 259 and a filename indicating the location of the __init__.py module which 260 declares the package's top-level contents. 261 """ 262 263 filename = self._find_file(d, name) 264 if filename: 265 init_py = "__init__" + os.path.extsep + "py" 266 init_py_filename = self._find_file(filename, init_py) 267 if init_py_filename: 268 return filename, init_py_filename 269 return None 270 271 def _find_file(self, d, filename): 272 273 """ 274 Return the filename obtained when searching the directory 'd' for the 275 given 'filename', or None if no actual file exists for the filename. 276 """ 277 278 filename = os.path.join(d, filename) 279 if os.path.exists(filename): 280 return filename 281 else: 282 return None 283 284 def load(self, name, return_leaf=0): 285 286 """ 287 Load the module or package with the given 'name'. Return an object 288 referencing the loaded module or package, or None if no such module or 289 package exists. 290 """ 291 292 if self.modules.has_key(name) and self.modules[name].loaded: 293 #print "Cached (%s)" % name 294 return self.modules[name] 295 if self.verbose: 296 print "Loading", name 297 298 # Split the name into path components, and try to find the uppermost in 299 # the search path. 300 301 path = name.split(".") 302 m = self.find_in_path(path[0]) 303 if not m: 304 if self.verbose: 305 print "Not found (%s)" % path[0] 306 return None # NOTE: Import error. 307 d, filename = m 308 309 # Either acquire a reference to an already-imported module, or load the 310 # module from a file. 311 312 top = module = self.load_from_file(filename, path[0]) 313 314 # For hierarchical names, traverse each path component... 315 316 if len(path) > 1: 317 if not d: 318 if self.verbose: 319 print "No package (%s)" % filename 320 return None # NOTE: Import error (package not found). 321 else: 322 self.add_submodules(d, module) 323 324 path_so_far = path[:1] 325 for p in path[1:]: 326 path_so_far.append(p) 327 328 # Find the package or module concerned. 329 330 m = self.find(d, p) 331 if not m: 332 if self.verbose: 333 print "Not found (%s)" % p 334 return None # NOTE: Import error. 335 d, filename = m 336 module_name = ".".join(path_so_far) 337 338 # Either reference an imported module or load one from a file. 339 340 submodule = self.load_from_file(filename, module_name) 341 342 if d: 343 self.add_submodules(d, module) 344 345 # Store the submodule within its parent module. 346 347 module.set_module(p, submodule) 348 module = submodule 349 350 # Return either the deepest or the uppermost module. 351 352 if return_leaf: 353 return module 354 else: 355 return top 356 357 def load_from_file(self, name, module_name=None): 358 359 """ 360 Load the module with the given 'name' (which may be a full module path). 361 """ 362 363 if module_name is None: 364 module_name = "__main__" 365 366 module = self.add_module(module_name) 367 if not module.loaded and module not in self.loading: 368 self.loading.add(module) 369 #print "Parsing", name 370 module.parse(name) 371 #print "Done", name 372 self.loading.remove(module) 373 module.loaded = 1 374 375 # Record the module. 376 377 #print "Loaded", module_name, "with namespace", module.namespace.keys() 378 return module 379 380 def add_module(self, module_name): 381 382 """ 383 Return the module with the given 'module_name', adding a new module 384 object if one does not already exist. 385 """ 386 387 if not self.modules.has_key(module_name): 388 self.modules[module_name] = module = micropython.inspect.InspectedModule(module_name, self) 389 else: 390 module = self.modules[module_name] 391 return module 392 393 def add_submodules(self, pathname, module): 394 395 """ 396 Work around insufficient __all__ declarations and examine the directory 397 with the given 'pathname', adding submodules to the given 'module'. 398 """ 399 400 for filename in os.listdir(pathname): 401 submodule = os.path.splitext(filename)[0] 402 module.set_module(submodule, self.add_module(module.name + "." + submodule)) 403 404 # vim: tabstop=4 expandtab shiftwidth=4