blob: 33b0af754faa943387bc35b75d01887fd39f8531 [file] [log] [blame]
Guido van Rossum54f22ed2000-02-04 15:10:34 +00001"""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"""
Guido van Rossum921c8241992-01-10 14:54:42 +00007
Robert Collins6bc2c1e2015-03-05 12:07:57 +13008import functools
Guido van Rossumc341c621992-03-27 15:12:43 +00009import sys
Guido van Rossum921c8241992-01-10 14:54:42 +000010import os
Benjamin Peterson9b8d24b2009-03-24 22:30:15 +000011import tokenize
Guido van Rossum921c8241992-01-10 14:54:42 +000012
Jeremy Hylton97b2e842003-06-29 16:59:43 +000013__all__ = ["getline", "clearcache", "checkcache"]
Skip Montanaro17ab1232001-01-24 06:27:27 +000014
Thomas Wouters49fd7fa2006-04-21 10:40:58 +000015def getline(filename, lineno, module_globals=None):
16 lines = getlines(filename, module_globals)
Guido van Rossum54f22ed2000-02-04 15:10:34 +000017 if 1 <= lineno <= len(lines):
18 return lines[lineno-1]
19 else:
20 return ''
Guido van Rossum921c8241992-01-10 14:54:42 +000021
22
23# The cache
24
Robert Collins6bc2c1e2015-03-05 12:07:57 +130025# The cache. Maps filenames to either a thunk which will provide source code,
26# or a tuple (size, mtime, lines, fullname) once loaded.
27cache = {}
Guido van Rossum921c8241992-01-10 14:54:42 +000028
29
Guido van Rossum921c8241992-01-10 14:54:42 +000030def clearcache():
Guido van Rossum54f22ed2000-02-04 15:10:34 +000031 """Clear the cache entirely."""
Guido van Rossum921c8241992-01-10 14:54:42 +000032
Guido van Rossum54f22ed2000-02-04 15:10:34 +000033 global cache
34 cache = {}
Guido van Rossum921c8241992-01-10 14:54:42 +000035
Guido van Rossum921c8241992-01-10 14:54:42 +000036
Thomas Wouters49fd7fa2006-04-21 10:40:58 +000037def getlines(filename, module_globals=None):
Guido van Rossum54f22ed2000-02-04 15:10:34 +000038 """Get the lines for a file from the cache.
39 Update the cache if it doesn't contain an entry for this file already."""
Guido van Rossum921c8241992-01-10 14:54:42 +000040
Raymond Hettinger54f02222002-06-01 14:18:47 +000041 if filename in cache:
Robert Collins6bc2c1e2015-03-05 12:07:57 +130042 entry = cache[filename]
43 if len(entry) == 1:
44 return updatecache(filename, module_globals)
Guido van Rossum54f22ed2000-02-04 15:10:34 +000045 return cache[filename][2]
46 else:
Thomas Wouters49fd7fa2006-04-21 10:40:58 +000047 return updatecache(filename, module_globals)
Guido van Rossum921c8241992-01-10 14:54:42 +000048
Guido van Rossum921c8241992-01-10 14:54:42 +000049
Hye-Shik Chang182ac852004-10-26 09:16:42 +000050def checkcache(filename=None):
Guido van Rossum54f22ed2000-02-04 15:10:34 +000051 """Discard cache entries that are out of date.
52 (This is not checked upon each call!)"""
Guido van Rossum921c8241992-01-10 14:54:42 +000053
Hye-Shik Chang182ac852004-10-26 09:16:42 +000054 if filename is None:
Guido van Rossumf5433482007-02-26 22:21:25 +000055 filenames = list(cache.keys())
Hye-Shik Chang182ac852004-10-26 09:16:42 +000056 else:
57 if filename in cache:
58 filenames = [filename]
59 else:
60 return
61
62 for filename in filenames:
Robert Collins6bc2c1e2015-03-05 12:07:57 +130063 entry = cache[filename]
64 if len(entry) == 1:
65 # lazy cache entry, leave it lazy.
66 continue
67 size, mtime, lines, fullname = entry
Thomas Wouters49fd7fa2006-04-21 10:40:58 +000068 if mtime is None:
69 continue # no-op for files loaded via a __loader__
Guido van Rossum54f22ed2000-02-04 15:10:34 +000070 try:
71 stat = os.stat(fullname)
Andrew Svetlovad28c7f2012-12-18 22:02:39 +020072 except OSError:
Guido van Rossum54f22ed2000-02-04 15:10:34 +000073 del cache[filename]
74 continue
Raymond Hettinger32200ae2002-06-01 19:51:15 +000075 if size != stat.st_size or mtime != stat.st_mtime:
Guido van Rossum54f22ed2000-02-04 15:10:34 +000076 del cache[filename]
Guido van Rossum921c8241992-01-10 14:54:42 +000077
Guido van Rossum921c8241992-01-10 14:54:42 +000078
Thomas Wouters49fd7fa2006-04-21 10:40:58 +000079def updatecache(filename, module_globals=None):
Guido van Rossum54f22ed2000-02-04 15:10:34 +000080 """Update a cache entry and return its list of lines.
81 If something's wrong, print a message, discard the cache entry,
82 and return an empty list."""
83
Raymond Hettinger54f02222002-06-01 14:18:47 +000084 if filename in cache:
Robert Collins6bc2c1e2015-03-05 12:07:57 +130085 if len(cache[filename]) != 1:
86 del cache[filename]
Benjamin Petersonaada7b82010-05-21 21:45:06 +000087 if not filename or (filename.startswith('<') and filename.endswith('>')):
Guido van Rossum54f22ed2000-02-04 15:10:34 +000088 return []
Thomas Wouters49fd7fa2006-04-21 10:40:58 +000089
Guido van Rossum54f22ed2000-02-04 15:10:34 +000090 fullname = filename
91 try:
92 stat = os.stat(fullname)
Benjamin Petersonaada7b82010-05-21 21:45:06 +000093 except OSError:
Georg Brandl991f9202009-05-05 08:31:54 +000094 basename = filename
Thomas Wouters49fd7fa2006-04-21 10:40:58 +000095
Robert Collins6bc2c1e2015-03-05 12:07:57 +130096 # Realise a lazy loader based lookup if there is one
97 # otherwise try to lookup right now.
98 if lazycache(filename, module_globals):
99 try:
100 data = cache[filename][0]()
101 except (ImportError, OSError):
102 pass
103 else:
104 if data is None:
105 # No luck, the PEP302 loader cannot find the source
106 # for this module.
107 return []
108 cache[filename] = (
109 len(data), None,
110 [line+'\n' for line in data.splitlines()], fullname
111 )
112 return cache[filename][2]
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000113
Georg Brandl991f9202009-05-05 08:31:54 +0000114 # Try looking through the module search path, which is only useful
115 # when handling a relative filename.
116 if os.path.isabs(filename):
117 return []
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000118
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000119 for dirname in sys.path:
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000120 try:
Tim Peters12f21ae2001-05-29 04:27:01 +0000121 fullname = os.path.join(dirname, basename)
122 except (TypeError, AttributeError):
123 # Not sufficiently string-like to do anything useful with.
Benjamin Petersonaada7b82010-05-21 21:45:06 +0000124 continue
125 try:
126 stat = os.stat(fullname)
127 break
Andrew Svetlovad28c7f2012-12-18 22:02:39 +0200128 except OSError:
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000129 pass
130 else:
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000131 return []
Benjamin Petersonaada7b82010-05-21 21:45:06 +0000132 try:
Victor Stinner58c07522010-11-09 01:08:59 +0000133 with tokenize.open(fullname) as fp:
Benjamin Petersonaada7b82010-05-21 21:45:06 +0000134 lines = fp.readlines()
Andrew Svetlovf7a17b42012-12-25 16:47:37 +0200135 except OSError:
Victor Stinner41a64a52010-09-29 01:30:45 +0000136 return []
Benjamin Petersonaada7b82010-05-21 21:45:06 +0000137 if lines and not lines[-1].endswith('\n'):
138 lines[-1] += '\n'
Raymond Hettinger32200ae2002-06-01 19:51:15 +0000139 size, mtime = stat.st_size, stat.st_mtime
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000140 cache[filename] = size, mtime, lines, fullname
141 return lines
Robert Collins6bc2c1e2015-03-05 12:07:57 +1300142
143
144def lazycache(filename, module_globals):
145 """Seed the cache for filename with module_globals.
146
147 The module loader will be asked for the source only when getlines is
148 called, not immediately.
149
150 If there is an entry in the cache already, it is not altered.
151
152 :return: True if a lazy load is registered in the cache,
153 otherwise False. To register such a load a module loader with a
154 get_source method must be found, the filename must be a cachable
155 filename, and the filename must not be already cached.
156 """
157 if filename in cache:
158 if len(cache[filename]) == 1:
159 return True
160 else:
161 return False
162 if not filename or (filename.startswith('<') and filename.endswith('>')):
163 return False
164 # Try for a __loader__, if available
165 if module_globals and '__loader__' in module_globals:
166 name = module_globals.get('__name__')
167 loader = module_globals['__loader__']
168 get_source = getattr(loader, 'get_source', None)
169
170 if name and get_source:
171 get_lines = functools.partial(get_source, name)
172 cache[filename] = (get_lines,)
173 return True
174 return False