Home | History | Annotate | Download | only in python2.7
      1 """Cache lines from files.
      2 
      3 This is intended to read lines from modules imported -- hence if a filename
      4 is not found, it will look down the module search path for a file by
      5 that name.
      6 """
      7 
      8 import sys
      9 import os
     10 
     11 __all__ = ["getline", "clearcache", "checkcache"]
     12 
     13 def getline(filename, lineno, module_globals=None):
     14     lines = getlines(filename, module_globals)
     15     if 1 <= lineno <= len(lines):
     16         return lines[lineno-1]
     17     else:
     18         return ''
     19 
     20 
     21 # The cache
     22 
     23 cache = {} # The cache
     24 
     25 
     26 def clearcache():
     27     """Clear the cache entirely."""
     28 
     29     global cache
     30     cache = {}
     31 
     32 
     33 def getlines(filename, module_globals=None):
     34     """Get the lines for a file from the cache.
     35     Update the cache if it doesn't contain an entry for this file already."""
     36 
     37     if filename in cache:
     38         return cache[filename][2]
     39     else:
     40         return updatecache(filename, module_globals)
     41 
     42 
     43 def checkcache(filename=None):
     44     """Discard cache entries that are out of date.
     45     (This is not checked upon each call!)"""
     46 
     47     if filename is None:
     48         filenames = cache.keys()
     49     else:
     50         if filename in cache:
     51             filenames = [filename]
     52         else:
     53             return
     54 
     55     for filename in filenames:
     56         size, mtime, lines, fullname = cache[filename]
     57         if mtime is None:
     58             continue   # no-op for files loaded via a __loader__
     59         try:
     60             stat = os.stat(fullname)
     61         except os.error:
     62             del cache[filename]
     63             continue
     64         if size != stat.st_size or mtime != stat.st_mtime:
     65             del cache[filename]
     66 
     67 
     68 def updatecache(filename, module_globals=None):
     69     """Update a cache entry and return its list of lines.
     70     If something's wrong, print a message, discard the cache entry,
     71     and return an empty list."""
     72 
     73     if filename in cache:
     74         del cache[filename]
     75     if not filename or (filename.startswith('<') and filename.endswith('>')):
     76         return []
     77 
     78     fullname = filename
     79     try:
     80         stat = os.stat(fullname)
     81     except OSError:
     82         basename = filename
     83 
     84         # Try for a __loader__, if available
     85         if module_globals and '__loader__' in module_globals:
     86             name = module_globals.get('__name__')
     87             loader = module_globals['__loader__']
     88             get_source = getattr(loader, 'get_source', None)
     89 
     90             if name and get_source:
     91                 try:
     92                     data = get_source(name)
     93                 except (ImportError, IOError):
     94                     pass
     95                 else:
     96                     if data is None:
     97                         # No luck, the PEP302 loader cannot find the source
     98                         # for this module.
     99                         return []
    100                     cache[filename] = (
    101                         len(data), None,
    102                         [line+'\n' for line in data.splitlines()], fullname
    103                     )
    104                     return cache[filename][2]
    105 
    106         # Try looking through the module search path, which is only useful
    107         # when handling a relative filename.
    108         if os.path.isabs(filename):
    109             return []
    110 
    111         for dirname in sys.path:
    112             # When using imputil, sys.path may contain things other than
    113             # strings; ignore them when it happens.
    114             try:
    115                 fullname = os.path.join(dirname, basename)
    116             except (TypeError, AttributeError):
    117                 # Not sufficiently string-like to do anything useful with.
    118                 continue
    119             try:
    120                 stat = os.stat(fullname)
    121                 break
    122             except os.error:
    123                 pass
    124         else:
    125             return []
    126     try:
    127         with open(fullname, 'rU') as fp:
    128             lines = fp.readlines()
    129     except IOError:
    130         return []
    131     if lines and not lines[-1].endswith('\n'):
    132         lines[-1] += '\n'
    133     size, mtime = stat.st_size, stat.st_mtime
    134     cache[filename] = size, mtime, lines, fullname
    135     return lines
    136