|
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[0] + filename[-1] == '<>': |
|
76 return [] |
|
77 |
|
78 fullname = filename |
|
79 try: |
|
80 stat = os.stat(fullname) |
|
81 except os.error, msg: |
|
82 basename = os.path.split(filename)[1] |
|
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 if basename.startswith(name.split('.')[-1]+'.'): |
|
92 try: |
|
93 data = get_source(name) |
|
94 except (ImportError, IOError): |
|
95 pass |
|
96 else: |
|
97 if data is None: |
|
98 # No luck, the PEP302 loader cannot find the source |
|
99 # for this module. |
|
100 return [] |
|
101 cache[filename] = ( |
|
102 len(data), None, |
|
103 [line+'\n' for line in data.splitlines()], fullname |
|
104 ) |
|
105 return cache[filename][2] |
|
106 |
|
107 # Try looking through the module search path. |
|
108 |
|
109 for dirname in sys.path: |
|
110 # When using imputil, sys.path may contain things other than |
|
111 # strings; ignore them when it happens. |
|
112 try: |
|
113 fullname = os.path.join(dirname, basename) |
|
114 except (TypeError, AttributeError): |
|
115 # Not sufficiently string-like to do anything useful with. |
|
116 pass |
|
117 else: |
|
118 try: |
|
119 stat = os.stat(fullname) |
|
120 break |
|
121 except os.error: |
|
122 pass |
|
123 else: |
|
124 # No luck |
|
125 ## print '*** Cannot stat', filename, ':', msg |
|
126 return [] |
|
127 try: |
|
128 fp = open(fullname, 'rU') |
|
129 lines = fp.readlines() |
|
130 fp.close() |
|
131 except IOError, msg: |
|
132 ## print '*** Cannot open', fullname, ':', msg |
|
133 return [] |
|
134 size, mtime = stat.st_size, stat.st_mtime |
|
135 cache[filename] = size, mtime, lines, fullname |
|
136 return lines |