blob: 3afcce1f0a14566c67e97527e95f90afba89c07f [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
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):
Victor Stinner376658f2015-03-18 14:16:50 +010038 """Get the lines for a Python source file from the cache.
Guido van Rossum54f22ed2000-02-04 15:10:34 +000039 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]
Serhiy Storchaka05ddbf02015-04-01 16:56:13 +030043 if len(entry) != 1:
44 return cache[filename][2]
Serhiy Storchakac512adc2015-04-01 16:54:05 +030045
46 try:
Thomas Wouters49fd7fa2006-04-21 10:40:58 +000047 return updatecache(filename, module_globals)
Serhiy Storchakac512adc2015-04-01 16:54:05 +030048 except MemoryError:
49 clearcache()
50 return []
Guido van Rossum921c8241992-01-10 14:54:42 +000051
Guido van Rossum921c8241992-01-10 14:54:42 +000052
Hye-Shik Chang182ac852004-10-26 09:16:42 +000053def checkcache(filename=None):
Guido van Rossum54f22ed2000-02-04 15:10:34 +000054 """Discard cache entries that are out of date.
55 (This is not checked upon each call!)"""
Guido van Rossum921c8241992-01-10 14:54:42 +000056
Hye-Shik Chang182ac852004-10-26 09:16:42 +000057 if filename is None:
Guido van Rossumf5433482007-02-26 22:21:25 +000058 filenames = list(cache.keys())
Hye-Shik Chang182ac852004-10-26 09:16:42 +000059 else:
60 if filename in cache:
61 filenames = [filename]
62 else:
63 return
64
65 for filename in filenames:
Robert Collins6bc2c1e2015-03-05 12:07:57 +130066 entry = cache[filename]
67 if len(entry) == 1:
68 # lazy cache entry, leave it lazy.
69 continue
70 size, mtime, lines, fullname = entry
Thomas Wouters49fd7fa2006-04-21 10:40:58 +000071 if mtime is None:
72 continue # no-op for files loaded via a __loader__
Guido van Rossum54f22ed2000-02-04 15:10:34 +000073 try:
74 stat = os.stat(fullname)
Andrew Svetlovad28c7f2012-12-18 22:02:39 +020075 except OSError:
Guido van Rossum54f22ed2000-02-04 15:10:34 +000076 del cache[filename]
77 continue
Raymond Hettinger32200ae2002-06-01 19:51:15 +000078 if size != stat.st_size or mtime != stat.st_mtime:
Guido van Rossum54f22ed2000-02-04 15:10:34 +000079 del cache[filename]
Guido van Rossum921c8241992-01-10 14:54:42 +000080
Guido van Rossum921c8241992-01-10 14:54:42 +000081
Thomas Wouters49fd7fa2006-04-21 10:40:58 +000082def updatecache(filename, module_globals=None):
Guido van Rossum54f22ed2000-02-04 15:10:34 +000083 """Update a cache entry and return its list of lines.
84 If something's wrong, print a message, discard the cache entry,
85 and return an empty list."""
86
Raymond Hettinger54f02222002-06-01 14:18:47 +000087 if filename in cache:
Robert Collins6bc2c1e2015-03-05 12:07:57 +130088 if len(cache[filename]) != 1:
89 del cache[filename]
Benjamin Petersonaada7b82010-05-21 21:45:06 +000090 if not filename or (filename.startswith('<') and filename.endswith('>')):
Guido van Rossum54f22ed2000-02-04 15:10:34 +000091 return []
Thomas Wouters49fd7fa2006-04-21 10:40:58 +000092
Guido van Rossum54f22ed2000-02-04 15:10:34 +000093 fullname = filename
94 try:
95 stat = os.stat(fullname)
Benjamin Petersonaada7b82010-05-21 21:45:06 +000096 except OSError:
Georg Brandl991f9202009-05-05 08:31:54 +000097 basename = filename
Thomas Wouters49fd7fa2006-04-21 10:40:58 +000098
Robert Collins6bc2c1e2015-03-05 12:07:57 +130099 # Realise a lazy loader based lookup if there is one
100 # otherwise try to lookup right now.
101 if lazycache(filename, module_globals):
102 try:
103 data = cache[filename][0]()
104 except (ImportError, OSError):
105 pass
106 else:
107 if data is None:
108 # No luck, the PEP302 loader cannot find the source
109 # for this module.
110 return []
111 cache[filename] = (
112 len(data), None,
113 [line+'\n' for line in data.splitlines()], fullname
114 )
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
168 if module_globals and '__loader__' in module_globals:
169 name = module_globals.get('__name__')
170 loader = module_globals['__loader__']
171 get_source = getattr(loader, 'get_source', None)
172
173 if name and get_source:
174 get_lines = functools.partial(get_source, name)
175 cache[filename] = (get_lines,)
176 return True
177 return False