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