1"""Cache lines from files. 2 3This is intended to read lines from modules imported -- hence if a filename 4is not found, it will look down the module search path for a file by 5that name. 6""" 7 8import sys 9import os 10 11__all__ = ["getline", "clearcache", "checkcache"] 12 13def 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 23cache = {} # The cache 24 25 26def clearcache(): 27 """Clear the cache entirely.""" 28 29 global cache 30 cache = {} 31 32 33def 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 47def 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 72def 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 140