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
|