| 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 | 
 | 
| 40 |     try:
 | 
| 41 |         return updatecache(filename, module_globals)
 | 
| 42 |     except MemoryError:
 | 
| 43 |         clearcache()
 | 
| 44 |         return []
 | 
| 45 | 
 | 
| 46 | 
 | 
| 47 | def checkcache(filename=None):
 | 
| 48 |     """Discard cache entries that are out of date.
 | 
| 49 |     (This is not checked upon each call!)"""
 | 
| 50 | 
 | 
| 51 |     if filename is None:
 | 
| 52 |         filenames = cache.keys()
 | 
| 53 |     else:
 | 
| 54 |         if filename in cache:
 | 
| 55 |             filenames = [filename]
 | 
| 56 |         else:
 | 
| 57 |             return
 | 
| 58 | 
 | 
| 59 |     for filename in filenames:
 | 
| 60 |         size, mtime, lines, fullname = cache[filename]
 | 
| 61 |         if mtime is None:
 | 
| 62 |             continue   # no-op for files loaded via a __loader__
 | 
| 63 |         try:
 | 
| 64 |             stat = os.stat(fullname)
 | 
| 65 |         except os.error:
 | 
| 66 |             del cache[filename]
 | 
| 67 |             continue
 | 
| 68 |         if size != stat.st_size or mtime != stat.st_mtime:
 | 
| 69 |             del cache[filename]
 | 
| 70 | 
 | 
| 71 | 
 | 
| 72 | def updatecache(filename, module_globals=None):
 | 
| 73 |     """Update a cache entry and return its list of lines.
 | 
| 74 |     If something's wrong, print a message, discard the cache entry,
 | 
| 75 |     and return an empty list."""
 | 
| 76 | 
 | 
| 77 |     if filename in cache:
 | 
| 78 |         del cache[filename]
 | 
| 79 |     if not filename or (filename.startswith('<') and filename.endswith('>')):
 | 
| 80 |         return []
 | 
| 81 | 
 | 
| 82 |     fullname = filename
 | 
| 83 |     try:
 | 
| 84 |         stat = os.stat(fullname)
 | 
| 85 |     except OSError:
 | 
| 86 |         basename = filename
 | 
| 87 | 
 | 
| 88 |         # Try for a __loader__, if available
 | 
| 89 |         if module_globals and '__loader__' in module_globals:
 | 
| 90 |             name = module_globals.get('__name__')
 | 
| 91 |             loader = module_globals['__loader__']
 | 
| 92 |             get_source = getattr(loader, 'get_source', None)
 | 
| 93 | 
 | 
| 94 |             if name and get_source:
 | 
| 95 |                 try:
 | 
| 96 |                     data = get_source(name)
 | 
| 97 |                 except (ImportError, IOError):
 | 
| 98 |                     pass
 | 
| 99 |                 else:
 | 
| 100 |                     if data is None:
 | 
| 101 |                         # No luck, the PEP302 loader cannot find the source
 | 
| 102 |                         # for this module.
 | 
| 103 |                         return []
 | 
| 104 |                     cache[filename] = (
 | 
| 105 |                         len(data), None,
 | 
| 106 |                         [line+'\n' for line in data.splitlines()], fullname
 | 
| 107 |                     )
 | 
| 108 |                     return cache[filename][2]
 | 
| 109 | 
 | 
| 110 |         # Try looking through the module search path, which is only useful
 | 
| 111 |         # when handling a relative filename.
 | 
| 112 |         if os.path.isabs(filename):
 | 
| 113 |             return []
 | 
| 114 | 
 | 
| 115 |         for dirname in sys.path:
 | 
| 116 |             # When using imputil, sys.path may contain things other than
 | 
| 117 |             # strings; ignore them when it happens.
 | 
| 118 |             try:
 | 
| 119 |                 fullname = os.path.join(dirname, basename)
 | 
| 120 |             except (TypeError, AttributeError):
 | 
| 121 |                 # Not sufficiently string-like to do anything useful with.
 | 
| 122 |                 continue
 | 
| 123 |             try:
 | 
| 124 |                 stat = os.stat(fullname)
 | 
| 125 |                 break
 | 
| 126 |             except os.error:
 | 
| 127 |                 pass
 | 
| 128 |         else:
 | 
| 129 |             return []
 | 
| 130 |     try:
 | 
| 131 |         with open(fullname, 'rU') as fp:
 | 
| 132 |             lines = fp.readlines()
 | 
| 133 |     except IOError:
 | 
| 134 |         return []
 | 
| 135 |     if lines and not lines[-1].endswith('\n'):
 | 
| 136 |         lines[-1] += '\n'
 | 
| 137 |     size, mtime = stat.st_size, stat.st_mtime
 | 
| 138 |     cache[filename] = size, mtime, lines, fullname
 | 
| 139 |     return lines
 |