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 # Class-level code is generated separately. 138 139 # NOTE: Generate module and function code here. 140 141 elif isinstance(obj, micropython.inspect.Function): 142 143 # Position the function in the image. 144 145 obj.location = pos 146 147 # Add header details. 148 149 image.append(obj) 150 pos += 1 151 152 # Append the function code to the image. 153 154 obj.code_location = obj.location = pos 155 code = trans.get_code(obj) 156 image += code 157 pos += len(code) 158 159 # Remember the position of the module code. 160 161 module.code_location = pos 162 163 # Append the module top-level code to the image. 164 165 code = trans.get_module_code() 166 image += code 167 pos += len(code) 168 169 return image 170 171 def get_object_table(self): 172 173 "Return a table with details of attributes for classes and modules." 174 175 t = micropython.table.Table() 176 for module in self.get_modules(): 177 t.add(module.full_name(), module.module_attributes()) 178 for obj in module.all_objects: 179 if isinstance(obj, micropython.inspect.Class): 180 t.add(obj.full_name(), obj.all_attributes()) 181 return t 182 183 def get_parameter_table(self): 184 185 "Return a table with details of parameters for functions and methods." 186 187 t = micropython.table.Table() 188 for module in self.get_modules(): 189 for obj in module.all_objects: 190 if isinstance(obj, micropython.inspect.Function): 191 t.add(obj.full_name(), obj.parameters()) 192 return t 193 194 # Import methods. 195 196 def find_in_path(self, name): 197 198 """ 199 Find the given module 'name' in the search path, returning None where no 200 such module could be found, or a 2-tuple from the 'find' method 201 otherwise. 202 """ 203 204 for d in self.path: 205 m = self.find(d, name) 206 if m: return m 207 return None 208 209 def find(self, d, name): 210 211 """ 212 In the directory 'd', find the given module 'name', where 'name' can 213 either refer to a single file module or to a package. Return None if the 214 'name' cannot be associated with either a file or a package directory, 215 or a 2-tuple from '_find_package' or '_find_module' otherwise. 216 """ 217 218 m = self._find_package(d, name) 219 if m: return m 220 m = self._find_module(d, name) 221 if m: return m 222 return None 223 224 def _find_module(self, d, name): 225 226 """ 227 In the directory 'd', find the given module 'name', returning None where 228 no suitable file exists in the directory, or a 2-tuple consisting of 229 None (indicating that no package directory is involved) and a filename 230 indicating the location of the module. 231 """ 232 233 name_py = name + os.extsep + "py" 234 filename = self._find_file(d, name_py) 235 if filename: 236 return None, filename 237 return None 238 239 def _find_package(self, d, name): 240 241 """ 242 In the directory 'd', find the given package 'name', returning None 243 where no suitable package directory exists, or a 2-tuple consisting of 244 a directory (indicating the location of the package directory itself) 245 and a filename indicating the location of the __init__.py module which 246 declares the package's top-level contents. 247 """ 248 249 filename = self._find_file(d, name) 250 if filename: 251 init_py = "__init__" + os.path.extsep + "py" 252 init_py_filename = self._find_file(filename, init_py) 253 if init_py_filename: 254 return filename, init_py_filename 255 return None 256 257 def _find_file(self, d, filename): 258 259 """ 260 Return the filename obtained when searching the directory 'd' for the 261 given 'filename', or None if no actual file exists for the filename. 262 """ 263 264 filename = os.path.join(d, filename) 265 if os.path.exists(filename): 266 return filename 267 else: 268 return None 269 270 def load(self, name, return_leaf=0): 271 272 """ 273 Load the module or package with the given 'name'. Return an object 274 referencing the loaded module or package, or None if no such module or 275 package exists. 276 """ 277 278 if self.modules.has_key(name) and self.modules[name].loaded: 279 #print "Cached (%s)" % name 280 return self.modules[name] 281 if self.verbose: 282 print "Loading", name 283 284 # Split the name into path components, and try to find the uppermost in 285 # the search path. 286 287 path = name.split(".") 288 m = self.find_in_path(path[0]) 289 if not m: 290 if self.verbose: 291 print "Not found (%s)" % path[0] 292 return None # NOTE: Import error. 293 d, filename = m 294 295 # Either acquire a reference to an already-imported module, or load the 296 # module from a file. 297 298 top = module = self.load_from_file(filename, path[0]) 299 300 # For hierarchical names, traverse each path component... 301 302 if len(path) > 1: 303 if not d: 304 if self.verbose: 305 print "No package (%s)" % filename 306 return None # NOTE: Import error (package not found). 307 else: 308 self.add_submodules(d, module) 309 310 path_so_far = path[:1] 311 for p in path[1:]: 312 path_so_far.append(p) 313 314 # Find the package or module concerned. 315 316 m = self.find(d, p) 317 if not m: 318 if self.verbose: 319 print "Not found (%s)" % p 320 return None # NOTE: Import error. 321 d, filename = m 322 module_name = ".".join(path_so_far) 323 324 # Either reference an imported module or load one from a file. 325 326 submodule = self.load_from_file(filename, module_name) 327 328 if d: 329 self.add_submodules(d, module) 330 331 # Store the submodule within its parent module. 332 333 module.set_module(p, submodule) 334 module = submodule 335 336 # Return either the deepest or the uppermost module. 337 338 if return_leaf: 339 return module 340 else: 341 return top 342 343 def load_from_file(self, name, module_name=None): 344 345 """ 346 Load the module with the given 'name' (which may be a full module path). 347 """ 348 349 if module_name is None: 350 module_name = "__main__" 351 352 module = self.add_module(module_name) 353 if not module.loaded and module not in self.loading: 354 self.loading.add(module) 355 #print "Parsing", name 356 module.parse(name) 357 #print "Done", name 358 self.loading.remove(module) 359 module.loaded = 1 360 361 # Record the module. 362 363 #print "Loaded", module_name, "with namespace", module.namespace.keys() 364 return module 365 366 def add_module(self, module_name): 367 368 """ 369 Return the module with the given 'module_name', adding a new module 370 object if one does not already exist. 371 """ 372 373 if not self.modules.has_key(module_name): 374 self.modules[module_name] = module = micropython.inspect.InspectedModule(module_name, self) 375 else: 376 module = self.modules[module_name] 377 return module 378 379 def add_submodules(self, pathname, module): 380 381 """ 382 Work around insufficient __all__ declarations and examine the directory 383 with the given 'pathname', adding submodules to the given 'module'. 384 """ 385 386 for filename in os.listdir(pathname): 387 submodule = os.path.splitext(filename)[0] 388 module.set_module(submodule, self.add_module(module.name + "." + submodule)) 389 390 # vim: tabstop=4 expandtab shiftwidth=4