• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1"""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"""
7
8import sys
9import os
10
11__all__ = ["getline", "clearcache", "checkcache"]
12
13def 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
23cache = {} # The cache
24
25
26def clearcache():
27    """Clear the cache entirely."""
28
29    global cache
30    cache = {}
31
32
33def 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
47def 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
72def 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
140