Lichen

Annotated importer.py

2:3dff1f10598f
2016-08-30 Paul Boddie Added tests from PythonLight.
paul@0 1
#!/usr/bin/env python
paul@0 2
paul@0 3
"""
paul@0 4
Import logic.
paul@0 5
paul@0 6
Copyright (C) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013,
paul@0 7
              2014, 2015, 2016 Paul Boddie <paul@boddie.org.uk>
paul@0 8
paul@0 9
This program is free software; you can redistribute it and/or modify it under
paul@0 10
the terms of the GNU General Public License as published by the Free Software
paul@0 11
Foundation; either version 3 of the License, or (at your option) any later
paul@0 12
version.
paul@0 13
paul@0 14
This program is distributed in the hope that it will be useful, but WITHOUT
paul@0 15
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
paul@0 16
FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
paul@0 17
details.
paul@0 18
paul@0 19
You should have received a copy of the GNU General Public License along with
paul@0 20
this program.  If not, see <http://www.gnu.org/licenses/>.
paul@0 21
"""
paul@0 22
paul@0 23
from errors import ProgramError
paul@0 24
from os.path import exists, extsep, getmtime, join
paul@0 25
from os import listdir, makedirs, remove
paul@0 26
from common import init_item, readfile, writefile
paul@0 27
from referencing import Reference
paul@0 28
import inspector
paul@0 29
import sys
paul@0 30
paul@0 31
class Importer:
paul@0 32
paul@0 33
    "An import machine, searching for and loading modules."
paul@0 34
paul@0 35
    def __init__(self, path, cache=None, verbose=False):
paul@0 36
paul@0 37
        """
paul@0 38
        Initialise the importer with the given search 'path' - a list of
paul@0 39
        directories to search for Python modules.
paul@0 40
paul@0 41
        The optional 'cache' should be the name of a directory used to store
paul@0 42
        cached module information.
paul@0 43
paul@0 44
        The optional 'verbose' parameter causes output concerning the activities
paul@0 45
        of the object to be produced if set to a true value (not the default).
paul@0 46
        """
paul@0 47
paul@0 48
        self.path = path
paul@0 49
        self.cache = cache
paul@0 50
        self.verbose = verbose
paul@0 51
paul@0 52
        self.modules = {}
paul@0 53
        self.modules_ordered = []
paul@0 54
        self.loading = set()
paul@0 55
        self.hidden = {}
paul@0 56
        self.revealing = {}
paul@0 57
        self.invalidated = set()
paul@0 58
paul@0 59
        self.objects = {}
paul@0 60
        self.classes = {}
paul@0 61
        self.function_parameters = {}
paul@0 62
        self.function_defaults = {}
paul@0 63
        self.function_targets = {}
paul@0 64
        self.function_arguments = {}
paul@0 65
paul@0 66
        # Derived information.
paul@0 67
paul@0 68
        self.subclasses = {}
paul@0 69
paul@0 70
        # Attributes of different object types.
paul@0 71
paul@0 72
        self.all_class_attrs = {}
paul@0 73
        self.all_instance_attrs = {}
paul@0 74
        self.all_instance_attr_constants = {}
paul@0 75
        self.all_combined_attrs = {}
paul@0 76
        self.all_module_attrs = {}
paul@0 77
        self.all_shadowed_attrs = {}
paul@0 78
paul@0 79
        # References to external names and aliases within program units.
paul@0 80
paul@0 81
        self.all_name_references = {}
paul@0 82
        self.all_initialised_names = {}
paul@0 83
        self.all_aliased_names = {}
paul@0 84
paul@0 85
        # General attribute accesses.
paul@0 86
paul@0 87
        self.all_attr_accesses = {}
paul@0 88
        self.all_const_accesses = {}
paul@0 89
        self.all_attr_access_modifiers = {}
paul@0 90
paul@0 91
        # Constant literals and values.
paul@0 92
paul@0 93
        self.all_constants = {}
paul@0 94
        self.all_constant_values = {}
paul@0 95
paul@0 96
        self.make_cache()
paul@0 97
paul@0 98
    def make_cache(self):
paul@0 99
        if self.cache and not exists(self.cache):
paul@0 100
            makedirs(self.cache)
paul@0 101
paul@0 102
    def check_cache(self, details):
paul@0 103
paul@0 104
        """
paul@0 105
        Check whether the cache applies for the given 'details', invalidating it
paul@0 106
        if it does not.
paul@0 107
        """
paul@0 108
paul@0 109
        recorded_details = self.get_cache_details()
paul@0 110
paul@0 111
        if recorded_details != details:
paul@0 112
            self.remove_cache()
paul@0 113
paul@0 114
        writefile(self.get_cache_details_filename(), details)
paul@0 115
paul@0 116
    def get_cache_details_filename(self):
paul@0 117
paul@0 118
        "Return the filename for the cache details."
paul@0 119
paul@0 120
        return join(self.cache, "$details")
paul@0 121
paul@0 122
    def get_cache_details(self):
paul@0 123
paul@0 124
        "Return details of the cache."
paul@0 125
paul@0 126
        details_filename = self.get_cache_details_filename()
paul@0 127
paul@0 128
        if not exists(details_filename):
paul@0 129
            return None
paul@0 130
        else:
paul@0 131
            return readfile(details_filename)
paul@0 132
paul@0 133
    def remove_cache(self):
paul@0 134
paul@0 135
        "Remove the contents of the cache."
paul@0 136
paul@0 137
        for filename in listdir(self.cache):
paul@0 138
            remove(join(self.cache, filename))
paul@0 139
paul@0 140
    def to_cache(self):
paul@0 141
paul@0 142
        "Write modules to the cache."
paul@0 143
paul@0 144
        if self.cache:
paul@0 145
            for module_name, module in self.modules.items():
paul@0 146
                module.to_cache(join(self.cache, module_name))
paul@0 147
paul@0 148
    # Object retrieval and storage.
paul@0 149
paul@0 150
    def get_object(self, name):
paul@0 151
paul@0 152
        """
paul@0 153
        Return a reference for the given 'name' or None if no such object
paul@0 154
        exists.
paul@0 155
        """
paul@0 156
paul@0 157
        return self.objects.get(name)
paul@0 158
paul@0 159
    def set_object(self, name, value=None):
paul@0 160
paul@0 161
        "Set the object with the given 'name' and the given 'value'."
paul@0 162
paul@0 163
        if isinstance(value, Reference):
paul@0 164
            ref = value.alias(name)
paul@0 165
        else:
paul@0 166
            ref = Reference(value, name)
paul@0 167
paul@0 168
        self.objects[name] = ref
paul@0 169
paul@0 170
    # Indirect object retrieval.
paul@0 171
paul@0 172
    def get_attributes(self, ref, attrname):
paul@0 173
paul@0 174
        """
paul@0 175
        Return attributes provided by 'ref' for 'attrname'. Class attributes
paul@0 176
        may be provided by instances.
paul@0 177
        """
paul@0 178
paul@0 179
        kind = ref.get_kind()
paul@0 180
        if kind == "<class>":
paul@0 181
            ref = self.get_class_attribute(ref.get_origin(), attrname)
paul@0 182
            return ref and set([ref]) or set()
paul@0 183
        elif kind == "<instance>":
paul@0 184
            return self.get_combined_attributes(ref.get_origin(), attrname)
paul@0 185
        elif kind == "<module>":
paul@0 186
            ref = self.get_module_attribute(ref.get_origin(), attrname)
paul@0 187
            return ref and set([ref]) or set()
paul@0 188
        else:
paul@0 189
            return set()
paul@0 190
paul@0 191
    def get_class_attribute(self, object_type, attrname):
paul@0 192
paul@0 193
        "Return from 'object_type' the details of class attribute 'attrname'."
paul@0 194
paul@0 195
        attr = self.all_class_attrs[object_type].get(attrname)
paul@0 196
        return attr and self.get_object(attr)
paul@0 197
paul@0 198
    def get_instance_attributes(self, object_type, attrname):
paul@0 199
paul@0 200
        """
paul@0 201
        Return from 'object_type' the details of instance attribute 'attrname'.
paul@0 202
        """
paul@0 203
paul@0 204
        consts = self.all_instance_attr_constants.get(object_type)
paul@0 205
        attrs = set()
paul@0 206
        for attr in self.all_instance_attrs[object_type].get(attrname, []):
paul@0 207
            attrs.add(consts and consts.get(attrname) or Reference("<var>", attr))
paul@0 208
        return attrs
paul@0 209
paul@0 210
    def get_combined_attributes(self, object_type, attrname):
paul@0 211
paul@0 212
        """
paul@0 213
        Return from 'object_type' the details of class or instance attribute
paul@0 214
        'attrname'.
paul@0 215
        """
paul@0 216
paul@0 217
        ref = self.get_class_attribute(object_type, attrname)
paul@0 218
        refs = ref and set([ref]) or set()
paul@0 219
        refs.update(self.get_instance_attributes(object_type, attrname))
paul@0 220
        return refs
paul@0 221
paul@0 222
    def get_module_attribute(self, object_type, attrname):
paul@0 223
paul@0 224
        "Return from 'object_type' the details of module attribute 'attrname'."
paul@0 225
paul@0 226
        if attrname in self.all_module_attrs[object_type]:
paul@0 227
            return self.get_object("%s.%s" % (object_type, attrname))
paul@0 228
        else:
paul@0 229
            return None
paul@0 230
paul@0 231
    # Module management.
paul@0 232
paul@0 233
    def get_modules(self):
paul@0 234
paul@0 235
        "Return all modules known to the importer."
paul@0 236
paul@0 237
        return self.modules.values()
paul@0 238
paul@0 239
    def get_module(self, name, hidden=False):
paul@0 240
paul@0 241
        "Return the module with the given 'name'."
paul@0 242
paul@0 243
        if not self.modules.has_key(name):
paul@0 244
            return None
paul@0 245
paul@0 246
        # Obtain the module and attempt to reveal it.
paul@0 247
paul@0 248
        module = self.modules[name]
paul@0 249
        if not hidden:
paul@0 250
            self.reveal_module(module)
paul@0 251
        return module
paul@0 252
paul@0 253
    def reveal_module(self, module):
paul@0 254
paul@0 255
        "Check if 'module' is hidden and reveal it."
paul@0 256
paul@0 257
        if module.name in self.hidden:
paul@0 258
            del self.hidden[module.name]
paul@0 259
paul@0 260
            # Reveal referenced modules.
paul@0 261
paul@0 262
            module.reveal_referenced()
paul@0 263
paul@0 264
    def set_revealing(self, module, name, instigator):
paul@0 265
paul@0 266
        """
paul@0 267
        Make the revealing of 'module' conditional on 'name' for the given
paul@0 268
        'instigator' of the reveal operation.
paul@0 269
        """
paul@0 270
paul@0 271
        self.revealing[module.name].add((name, instigator))
paul@0 272
paul@0 273
    # Program operations.
paul@0 274
paul@0 275
    def initialise(self, filename, reset=False):
paul@0 276
paul@0 277
        """
paul@0 278
        Initialise a program whose main module is 'filename', resetting the
paul@0 279
        cache if 'reset' is true. Return the main module.
paul@0 280
        """
paul@0 281
paul@0 282
        if reset:
paul@0 283
            self.remove_cache()
paul@0 284
        self.check_cache(filename)
paul@0 285
paul@0 286
        # Load the program itself.
paul@0 287
paul@0 288
        m = self.load_from_file(filename)
paul@0 289
paul@0 290
        # Resolve dependencies within the program.
paul@0 291
paul@0 292
        for module in self.modules_ordered:
paul@0 293
            module.resolve()
paul@0 294
paul@0 295
        return m
paul@0 296
paul@0 297
    def finalise(self):
paul@0 298
paul@0 299
        "Finalise the inspected program."
paul@0 300
paul@0 301
        self.finalise_classes()
paul@0 302
        self.remove_hidden()
paul@0 303
        self.to_cache()
paul@0 304
        self.set_class_types()
paul@0 305
        self.define_instantiators()
paul@0 306
        self.collect_constants()
paul@0 307
paul@0 308
    def finalise_classes(self):
paul@0 309
paul@0 310
        "Finalise the class relationships and attributes."
paul@0 311
paul@0 312
        self.derive_inherited_attrs()
paul@0 313
        self.derive_subclasses()
paul@0 314
        self.derive_shadowed_attrs()
paul@0 315
paul@0 316
    def derive_inherited_attrs(self):
paul@0 317
paul@0 318
        "Derive inherited attributes for classes throughout the program."
paul@0 319
paul@0 320
        for name in self.classes.keys():
paul@0 321
            self.propagate_attrs_for_class(name)
paul@0 322
paul@0 323
    def propagate_attrs_for_class(self, name, visited=None):
paul@0 324
paul@0 325
        "Propagate inherited attributes for class 'name'."
paul@0 326
paul@0 327
        # Visit classes only once.
paul@0 328
paul@0 329
        if self.all_combined_attrs.has_key(name):
paul@0 330
            return
paul@0 331
paul@0 332
        visited = visited or []
paul@0 333
paul@0 334
        if name in visited:
paul@0 335
            raise ProgramError, "Class %s may not inherit from itself: %s -> %s." % (name, " -> ".join(visited), name)
paul@0 336
paul@0 337
        visited.append(name)
paul@0 338
paul@0 339
        class_attrs = {}
paul@0 340
        instance_attrs = {}
paul@0 341
paul@0 342
        # Aggregate the attributes from base classes, recording the origins of
paul@0 343
        # applicable attributes.
paul@0 344
paul@0 345
        for base in self.classes[name][::-1]:
paul@0 346
paul@0 347
            # Get the identity of the class from the reference.
paul@0 348
paul@0 349
            base = base.get_origin()
paul@0 350
paul@0 351
            # Define the base class completely before continuing with this
paul@0 352
            # class.
paul@0 353
paul@0 354
            self.propagate_attrs_for_class(base, visited)
paul@0 355
            class_attrs.update(self.all_class_attrs[base])
paul@0 356
paul@0 357
            # Instance attribute origins are combined if different.
paul@0 358
paul@0 359
            for key, values in self.all_instance_attrs[base].items():
paul@0 360
                init_item(instance_attrs, key, set)
paul@0 361
                instance_attrs[key].update(values)
paul@0 362
paul@0 363
        # Class attributes override those defined earlier in the hierarchy.
paul@0 364
paul@0 365
        class_attrs.update(self.all_class_attrs.get(name, {}))
paul@0 366
paul@0 367
        # Instance attributes are merely added if not already defined.
paul@0 368
paul@0 369
        for key in self.all_instance_attrs.get(name, []):
paul@0 370
            if not instance_attrs.has_key(key):
paul@0 371
                instance_attrs[key] = set(["%s.%s" % (name, key)])
paul@0 372
paul@0 373
        self.all_class_attrs[name] = class_attrs
paul@0 374
        self.all_instance_attrs[name] = instance_attrs
paul@0 375
        self.all_combined_attrs[name] = set(class_attrs.keys()).union(instance_attrs.keys())
paul@0 376
paul@0 377
    def derive_subclasses(self):
paul@0 378
paul@0 379
        "Derive subclass details for classes."
paul@0 380
paul@0 381
        for name, bases in self.classes.items():
paul@0 382
            for base in bases:
paul@0 383
paul@0 384
                # Get the identity of the class from the reference.
paul@0 385
paul@0 386
                base = base.get_origin()
paul@0 387
                self.subclasses[base].add(name)
paul@0 388
paul@0 389
    def derive_shadowed_attrs(self):
paul@0 390
paul@0 391
        "Derive shadowed attributes for classes."
paul@0 392
paul@0 393
        for name, attrs in self.all_instance_attrs.items():
paul@0 394
            attrs = set(attrs.keys()).intersection(self.all_class_attrs[name].keys())
paul@0 395
            if attrs:
paul@0 396
                self.all_shadowed_attrs[name] = attrs
paul@0 397
paul@0 398
    def remove_hidden(self):
paul@0 399
paul@0 400
        "Remove all hidden modules."
paul@0 401
paul@0 402
        # First reveal any modules exposing names.
paul@0 403
paul@0 404
        for modname, names in self.revealing.items():
paul@0 405
            module = self.modules[modname]
paul@0 406
paul@0 407
            # Obtain the imported names and determine whether they should cause
paul@0 408
            # the module to be revealed.
paul@0 409
paul@0 410
            for (name, instigator) in names:
paul@0 411
                if module is not instigator:
paul@0 412
paul@0 413
                    # Only if an object is provided by the module should the
paul@0 414
                    # module be revealed. References to objects in other modules
paul@0 415
                    # should not in themselves expose the module in which those
paul@0 416
                    # references occur.
paul@0 417
paul@0 418
                    ref = module.get_global(name)
paul@0 419
                    if ref and ref.provided_by_module(module.name):
paul@0 420
                        self.reveal_module(module)
paul@0 421
                        instigator.revealed.add(module)
paul@0 422
paul@0 423
        # Then remove all modules that are still hidden.
paul@0 424
paul@0 425
        for modname in self.hidden:
paul@0 426
            module = self.modules[modname]
paul@0 427
            module.unpropagate()
paul@0 428
            del self.modules[modname]
paul@0 429
            ref = self.objects.get(modname)
paul@0 430
            if ref and ref.get_kind() == "<module>":
paul@0 431
                del self.objects[modname]
paul@0 432
paul@0 433
    def set_class_types(self):
paul@0 434
paul@0 435
        "Set the type of each class."
paul@0 436
paul@0 437
        ref = self.get_object("__builtins__.type")
paul@0 438
        for attrs in self.all_class_attrs.values():
paul@0 439
            attrs["__class__"] = ref.get_origin()
paul@0 440
paul@0 441
    def define_instantiators(self):
paul@0 442
paul@0 443
        """
paul@0 444
        Consolidate parameter and default details, incorporating initialiser
paul@0 445
        details to define instantiator signatures.
paul@0 446
        """
paul@0 447
paul@0 448
        for cls, attrs in self.all_class_attrs.items():
paul@0 449
            initialiser = attrs["__init__"]
paul@0 450
            self.function_parameters[cls] = self.function_parameters[initialiser][1:]
paul@0 451
            self.function_defaults[cls] = self.function_defaults[initialiser]
paul@0 452
paul@0 453
    def collect_constants(self):
paul@0 454
paul@0 455
        "Get constants from all active modules."
paul@0 456
paul@0 457
        for module in self.modules.values():
paul@0 458
            self.all_constants.update(module.constants)
paul@0 459
paul@0 460
    # Import methods.
paul@0 461
paul@0 462
    def find_in_path(self, name):
paul@0 463
paul@0 464
        """
paul@0 465
        Find the given module 'name' in the search path, returning None where no
paul@0 466
        such module could be found, or a 2-tuple from the 'find' method
paul@0 467
        otherwise.
paul@0 468
        """
paul@0 469
paul@0 470
        for d in self.path:
paul@0 471
            m = self.find(d, name)
paul@0 472
            if m: return m
paul@0 473
        return None
paul@0 474
paul@0 475
    def find(self, d, name):
paul@0 476
paul@0 477
        """
paul@0 478
        In the directory 'd', find the given module 'name', where 'name' can
paul@0 479
        either refer to a single file module or to a package. Return None if the
paul@0 480
        'name' cannot be associated with either a file or a package directory,
paul@0 481
        or a 2-tuple from '_find_package' or '_find_module' otherwise.
paul@0 482
        """
paul@0 483
paul@0 484
        m = self._find_package(d, name)
paul@0 485
        if m: return m
paul@0 486
        m = self._find_module(d, name)
paul@0 487
        if m: return m
paul@0 488
        return None
paul@0 489
paul@0 490
    def _find_module(self, d, name):
paul@0 491
paul@0 492
        """
paul@0 493
        In the directory 'd', find the given module 'name', returning None where
paul@0 494
        no suitable file exists in the directory, or a 2-tuple consisting of
paul@0 495
        None (indicating that no package directory is involved) and a filename
paul@0 496
        indicating the location of the module.
paul@0 497
        """
paul@0 498
paul@0 499
        name_py = name + extsep + "py"
paul@0 500
        filename = self._find_file(d, name_py)
paul@0 501
        if filename:
paul@0 502
            return None, filename
paul@0 503
        return None
paul@0 504
paul@0 505
    def _find_package(self, d, name):
paul@0 506
paul@0 507
        """
paul@0 508
        In the directory 'd', find the given package 'name', returning None
paul@0 509
        where no suitable package directory exists, or a 2-tuple consisting of
paul@0 510
        a directory (indicating the location of the package directory itself)
paul@0 511
        and a filename indicating the location of the __init__.py module which
paul@0 512
        declares the package's top-level contents.
paul@0 513
        """
paul@0 514
paul@0 515
        filename = self._find_file(d, name)
paul@0 516
        if filename:
paul@0 517
            init_py = "__init__" + extsep + "py"
paul@0 518
            init_py_filename = self._find_file(filename, init_py)
paul@0 519
            if init_py_filename:
paul@0 520
                return filename, init_py_filename
paul@0 521
        return None
paul@0 522
paul@0 523
    def _find_file(self, d, filename):
paul@0 524
paul@0 525
        """
paul@0 526
        Return the filename obtained when searching the directory 'd' for the
paul@0 527
        given 'filename', or None if no actual file exists for the filename.
paul@0 528
        """
paul@0 529
paul@0 530
        filename = join(d, filename)
paul@0 531
        if exists(filename):
paul@0 532
            return filename
paul@0 533
        else:
paul@0 534
            return None
paul@0 535
paul@0 536
    def load(self, name, return_leaf=False, hidden=False):
paul@0 537
paul@0 538
        """
paul@0 539
        Load the module or package with the given 'name'. Return an object
paul@0 540
        referencing the loaded module or package, or None if no such module or
paul@0 541
        package exists.
paul@0 542
paul@0 543
        Where 'return_leaf' is specified, the final module in the chain is
paul@0 544
        returned. Where 'hidden' is specified, the module is marked as hidden.
paul@0 545
        """
paul@0 546
paul@0 547
        if return_leaf:
paul@0 548
            name_for_return = name
paul@0 549
        else:
paul@0 550
            name_for_return = name.split(".")[0]
paul@0 551
paul@0 552
        # Loaded modules are returned immediately.
paul@0 553
        # Modules may be known but not yet loading (having been registered as
paul@0 554
        # submodules), loading, loaded, or completely unknown.
paul@0 555
paul@0 556
        module = self.get_module(name, hidden)
paul@0 557
paul@0 558
        if module:
paul@0 559
            return self.modules[name_for_return]
paul@0 560
paul@0 561
        # Otherwise, modules are loaded.
paul@0 562
paul@0 563
        if self.verbose:
paul@0 564
            print >>sys.stderr, "Loading", name
paul@0 565
paul@0 566
        # Split the name into path components, and try to find the uppermost in
paul@0 567
        # the search path.
paul@0 568
paul@0 569
        path = name.split(".")
paul@0 570
        path_so_far = []
paul@0 571
        top = module = None
paul@0 572
paul@0 573
        for p in path:
paul@0 574
paul@0 575
            # Get the module's filesystem details.
paul@0 576
paul@0 577
            if not path_so_far:
paul@0 578
                m = self.find_in_path(p)
paul@0 579
            elif d:
paul@0 580
                m = self.find(d, p)
paul@0 581
            else:
paul@0 582
                m = None
paul@0 583
paul@0 584
            path_so_far.append(p)
paul@0 585
            module_name = ".".join(path_so_far)
paul@0 586
paul@0 587
            if not m:
paul@0 588
                if self.verbose:
paul@0 589
                    print >>sys.stderr, "Not found (%s)" % name
paul@0 590
paul@0 591
                return None # NOTE: Import error.
paul@0 592
paul@0 593
            # Get the module itself.
paul@0 594
paul@0 595
            d, filename = m
paul@0 596
            submodule = self.load_from_file(filename, module_name, hidden)
paul@0 597
paul@0 598
            if module is None:
paul@0 599
                top = submodule
paul@0 600
paul@0 601
            module = submodule
paul@0 602
paul@0 603
        # Return either the deepest or the uppermost module.
paul@0 604
paul@0 605
        return return_leaf and module or top
paul@0 606
paul@0 607
    def load_from_file(self, filename, module_name=None, hidden=False):
paul@0 608
paul@0 609
        "Load the module from the given 'filename'."
paul@0 610
paul@0 611
        if module_name is None:
paul@0 612
            module_name = "__main__"
paul@0 613
paul@0 614
        module = self.modules.get(module_name)
paul@0 615
paul@0 616
        if not module:
paul@0 617
paul@0 618
            # Try to load from cache.
paul@0 619
paul@0 620
            module = self.load_from_cache(filename, module_name, hidden)
paul@0 621
            if module:
paul@0 622
                return module
paul@0 623
paul@0 624
            # If no cache entry exists, load from file.
paul@0 625
paul@0 626
            module = inspector.InspectedModule(module_name, self)
paul@0 627
            self.add_module(module_name, module)
paul@0 628
            self.update_cache_validity(module)
paul@0 629
paul@0 630
        # Initiate loading if not already in progress.
paul@0 631
paul@0 632
        if not module.loaded and module not in self.loading:
paul@0 633
            self._load(module, module_name, hidden, lambda m: m.parse, filename)
paul@0 634
paul@0 635
        return module
paul@0 636
paul@0 637
    def update_cache_validity(self, module):
paul@0 638
paul@0 639
        "Make 'module' valid in the cache, but invalidate accessing modules."
paul@0 640
paul@0 641
        self.invalidated.update(module.accessing_modules)
paul@0 642
        if module.name in self.invalidated:
paul@0 643
            self.invalidated.remove(module.name)
paul@0 644
paul@0 645
    def source_is_new(self, filename, module_name):
paul@0 646
paul@0 647
        "Return whether 'filename' is newer than the cached 'module_name'."
paul@0 648
paul@0 649
        if self.cache:
paul@0 650
            cache_filename = join(self.cache, module_name)
paul@0 651
            return not exists(cache_filename) or \
paul@0 652
                getmtime(filename) > getmtime(cache_filename) or \
paul@0 653
                module_name in self.invalidated
paul@0 654
        else:
paul@0 655
            return True
paul@0 656
paul@0 657
    def load_from_cache(self, filename, module_name, hidden=False):
paul@0 658
paul@0 659
        "Return a module residing in the cache."
paul@0 660
paul@0 661
        module = self.modules.get(module_name)
paul@0 662
paul@0 663
        if not self.source_is_new(filename, module_name):
paul@0 664
paul@0 665
            if not module:
paul@0 666
                module = inspector.CachedModule(module_name, self)
paul@0 667
                self.add_module(module_name, module)
paul@0 668
paul@0 669
            if not module.loaded and module not in self.loading:
paul@0 670
                filename = join(self.cache, module_name)
paul@0 671
                self._load(module, module_name, hidden, lambda m: m.from_cache, filename)
paul@0 672
paul@0 673
        return module
paul@0 674
paul@0 675
    def _load(self, module, module_name, hidden, fn, filename):
paul@0 676
paul@0 677
        """
paul@0 678
        Load 'module' for the given 'module_name', with the module being hidden
paul@0 679
        if 'hidden' is a true value, and with 'fn' performing an invocation on
paul@0 680
        the module with the given 'filename'.
paul@0 681
        """
paul@0 682
paul@0 683
        # Indicate that the module is hidden if requested.
paul@0 684
paul@0 685
        if hidden:
paul@0 686
            self.hidden[module_name] = module
paul@0 687
paul@0 688
        # Indicate that loading is in progress and load the module.
paul@0 689
paul@0 690
        self.loading.add(module)
paul@0 691
        if self.verbose:
paul@0 692
            print >>sys.stderr, "Loading", filename
paul@0 693
        fn(module)(filename)
paul@0 694
        if self.verbose:
paul@0 695
            print >>sys.stderr, "Loaded", filename
paul@0 696
        self.loading.remove(module)
paul@0 697
paul@0 698
        self.modules_ordered.append(module)
paul@0 699
paul@0 700
    def add_module(self, module_name, module):
paul@0 701
paul@0 702
        """
paul@0 703
        Return the module with the given 'module_name', adding a new module
paul@0 704
        object if one does not already exist.
paul@0 705
        """
paul@0 706
paul@0 707
        self.modules[module_name] = module
paul@0 708
        self.objects[module_name] = Reference("<module>", module_name)
paul@0 709
paul@0 710
# vim: tabstop=4 expandtab shiftwidth=4