blob: 513b17e999880b5cb0f018e21ec7ce90fd73caed [file] [log] [blame]
Victor Stinner376658f2015-03-18 14:16:50 +01001"""Cache lines from Python source files.
Guido van Rossum54f22ed2000-02-04 15:10:34 +00002
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
加和4515a592020-01-26 10:07:40 +080013__all__ = ["getline", "clearcache", "checkcache", "lazycache"]
Skip Montanaro17ab1232001-01-24 06:27:27 +000014
Guido van Rossum921c8241992-01-10 14:54:42 +000015
Robert Collins6bc2c1e2015-03-05 12:07:57 +130016# The cache. Maps filenames to either a thunk which will provide source code,
17# or a tuple (size, mtime, lines, fullname) once loaded.
18cache = {}
Guido van Rossum921c8241992-01-10 14:54:42 +000019
20
Guido van Rossum921c8241992-01-10 14:54:42 +000021def clearcache():
Guido van Rossum54f22ed2000-02-04 15:10:34 +000022 """Clear the cache entirely."""
加和4515a592020-01-26 10:07:40 +080023 cache.clear()
Guido van Rossum921c8241992-01-10 14:54:42 +000024
加和4515a592020-01-26 10:07:40 +080025
26def getline(filename, lineno, module_globals=None):
27 """Get a line for a Python source file from the cache.
28 Update the cache if it doesn't contain an entry for this file already."""
29
30 lines = getlines(filename, module_globals)
31 if 1 <= lineno <= len(lines):
32 return lines[lineno - 1]
33 return ''
Guido van Rossum921c8241992-01-10 14:54:42 +000034
Guido van Rossum921c8241992-01-10 14:54:42 +000035
Thomas Wouters49fd7fa2006-04-21 10:40:58 +000036def getlines(filename, module_globals=None):
Victor Stinner376658f2015-03-18 14:16:50 +010037 """Get the lines for a Python source file from the cache.
Guido van Rossum54f22ed2000-02-04 15:10:34 +000038 Update the cache if it doesn't contain an entry for this file already."""
Guido van Rossum921c8241992-01-10 14:54:42 +000039
Raymond Hettinger54f02222002-06-01 14:18:47 +000040 if filename in cache:
Robert Collins6bc2c1e2015-03-05 12:07:57 +130041 entry = cache[filename]
Serhiy Storchaka05ddbf02015-04-01 16:56:13 +030042 if len(entry) != 1:
43 return cache[filename][2]
Serhiy Storchakac512adc2015-04-01 16:54:05 +030044
45 try:
Thomas Wouters49fd7fa2006-04-21 10:40:58 +000046 return updatecache(filename, module_globals)
Serhiy Storchakac512adc2015-04-01 16:54:05 +030047 except MemoryError:
48 clearcache()
49 return []
Guido van Rossum921c8241992-01-10 14:54:42 +000050
Guido van Rossum921c8241992-01-10 14:54:42 +000051
Hye-Shik Chang182ac852004-10-26 09:16:42 +000052def checkcache(filename=None):
Guido van Rossum54f22ed2000-02-04 15:10:34 +000053 """Discard cache entries that are out of date.
54 (This is not checked upon each call!)"""
Guido van Rossum921c8241992-01-10 14:54:42 +000055
Hye-Shik Chang182ac852004-10-26 09:16:42 +000056 if filename is None:
Guido van Rossumf5433482007-02-26 22:21:25 +000057 filenames = list(cache.keys())
加和4515a592020-01-26 10:07:40 +080058 elif filename in cache:
59 filenames = [filename]
Hye-Shik Chang182ac852004-10-26 09:16:42 +000060 else:
加和4515a592020-01-26 10:07:40 +080061 return
Hye-Shik Chang182ac852004-10-26 09:16:42 +000062
63 for filename in filenames:
Robert Collins6bc2c1e2015-03-05 12:07:57 +130064 entry = cache[filename]
65 if len(entry) == 1:
66 # lazy cache entry, leave it lazy.
67 continue
68 size, mtime, lines, fullname = entry
Thomas Wouters49fd7fa2006-04-21 10:40:58 +000069 if mtime is None:
70 continue # no-op for files loaded via a __loader__
Guido van Rossum54f22ed2000-02-04 15:10:34 +000071 try:
72 stat = os.stat(fullname)
Andrew Svetlovad28c7f2012-12-18 22:02:39 +020073 except OSError:
Michael Graczykd72ea602020-05-13 17:41:57 -050074 cache.pop(filename, None)
Guido van Rossum54f22ed2000-02-04 15:10:34 +000075 continue
Raymond Hettinger32200ae2002-06-01 19:51:15 +000076 if size != stat.st_size or mtime != stat.st_mtime:
Michael Graczykd72ea602020-05-13 17:41:57 -050077 cache.pop(filename, None)
Guido van Rossum921c8241992-01-10 14:54:42 +000078
Guido van Rossum921c8241992-01-10 14:54:42 +000079
Thomas Wouters49fd7fa2006-04-21 10:40:58 +000080def updatecache(filename, module_globals=None):
Guido van Rossum54f22ed2000-02-04 15:10:34 +000081 """Update a cache entry and return its list of lines.
82 If something's wrong, print a message, discard the cache entry,
83 and return an empty list."""
84
Raymond Hettinger54f02222002-06-01 14:18:47 +000085 if filename in cache:
Robert Collins6bc2c1e2015-03-05 12:07:57 +130086 if len(cache[filename]) != 1:
Michael Graczykd72ea602020-05-13 17:41:57 -050087 cache.pop(filename, None)
Benjamin Petersonaada7b82010-05-21 21:45:06 +000088 if not filename or (filename.startswith('<') and filename.endswith('>')):
Guido van Rossum54f22ed2000-02-04 15:10:34 +000089 return []
Thomas Wouters49fd7fa2006-04-21 10:40:58 +000090
Guido van Rossum54f22ed2000-02-04 15:10:34 +000091 fullname = filename
92 try:
93 stat = os.stat(fullname)
Benjamin Petersonaada7b82010-05-21 21:45:06 +000094 except OSError:
Georg Brandl991f9202009-05-05 08:31:54 +000095 basename = filename
Thomas Wouters49fd7fa2006-04-21 10:40:58 +000096
Robert Collins6bc2c1e2015-03-05 12:07:57 +130097 # Realise a lazy loader based lookup if there is one
98 # otherwise try to lookup right now.
99 if lazycache(filename, module_globals):
100 try:
101 data = cache[filename][0]()
102 except (ImportError, OSError):
103 pass
104 else:
105 if data is None:
106 # No luck, the PEP302 loader cannot find the source
107 # for this module.
108 return []
109 cache[filename] = (
加和4515a592020-01-26 10:07:40 +0800110 len(data),
111 None,
112 [line + '\n' for line in data.splitlines()],
113 fullname
Robert Collins6bc2c1e2015-03-05 12:07:57 +1300114 )
115 return cache[filename][2]
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000116
Georg Brandl991f9202009-05-05 08:31:54 +0000117 # Try looking through the module search path, which is only useful
118 # when handling a relative filename.
119 if os.path.isabs(filename):
120 return []
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000121
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000122 for dirname in sys.path:
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000123 try:
Tim Peters12f21ae2001-05-29 04:27:01 +0000124 fullname = os.path.join(dirname, basename)
125 except (TypeError, AttributeError):
126 # Not sufficiently string-like to do anything useful with.
Benjamin Petersonaada7b82010-05-21 21:45:06 +0000127 continue
128 try:
129 stat = os.stat(fullname)
130 break
Andrew Svetlovad28c7f2012-12-18 22:02:39 +0200131 except OSError:
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000132 pass
133 else:
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000134 return []
Benjamin Petersonaada7b82010-05-21 21:45:06 +0000135 try:
Victor Stinner58c07522010-11-09 01:08:59 +0000136 with tokenize.open(fullname) as fp:
Benjamin Petersonaada7b82010-05-21 21:45:06 +0000137 lines = fp.readlines()
Andrew Svetlovf7a17b42012-12-25 16:47:37 +0200138 except OSError:
Victor Stinner41a64a52010-09-29 01:30:45 +0000139 return []
Benjamin Petersonaada7b82010-05-21 21:45:06 +0000140 if lines and not lines[-1].endswith('\n'):
141 lines[-1] += '\n'
Raymond Hettinger32200ae2002-06-01 19:51:15 +0000142 size, mtime = stat.st_size, stat.st_mtime
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000143 cache[filename] = size, mtime, lines, fullname
144 return lines
Robert Collins6bc2c1e2015-03-05 12:07:57 +1300145
146
147def lazycache(filename, module_globals):
148 """Seed the cache for filename with module_globals.
149
150 The module loader will be asked for the source only when getlines is
151 called, not immediately.
152
153 If there is an entry in the cache already, it is not altered.
154
155 :return: True if a lazy load is registered in the cache,
156 otherwise False. To register such a load a module loader with a
157 get_source method must be found, the filename must be a cachable
158 filename, and the filename must not be already cached.
159 """
160 if filename in cache:
161 if len(cache[filename]) == 1:
162 return True
163 else:
164 return False
165 if not filename or (filename.startswith('<') and filename.endswith('>')):
166 return False
167 # Try for a __loader__, if available
Brett Cannon825ac382020-11-06 18:45:56 -0800168 if module_globals and '__name__' in module_globals:
169 name = module_globals['__name__']
170 if (loader := module_globals.get('__loader__')) is None:
171 if spec := module_globals.get('__spec__'):
172 try:
173 loader = spec.loader
174 except AttributeError:
175 pass
Robert Collins6bc2c1e2015-03-05 12:07:57 +1300176 get_source = getattr(loader, 'get_source', None)
177
178 if name and get_source:
179 get_lines = functools.partial(get_source, name)
180 cache[filename] = (get_lines,)
181 return True
182 return False