linecache.py 3.86 KB
Newer Older
1
"""Cache lines from Python source files.
2 3 4 5 6

This is intended to read lines from modules imported -- hence if a filename
is not found, it will look down the module search path for a file by
that name.
"""
Guido van Rossum's avatar
Guido van Rossum committed
7

8
import sys
Guido van Rossum's avatar
Guido van Rossum committed
9
import os
10
import tokenize
Guido van Rossum's avatar
Guido van Rossum committed
11

12
__all__ = ["getline", "clearcache", "checkcache"]
13

14 15
def getline(filename, lineno, module_globals=None):
    lines = getlines(filename, module_globals)
16 17 18 19
    if 1 <= lineno <= len(lines):
        return lines[lineno-1]
    else:
        return ''
Guido van Rossum's avatar
Guido van Rossum committed
20 21 22 23 24 25 26 27


# The cache

cache = {} # The cache


def clearcache():
28
    """Clear the cache entirely."""
Guido van Rossum's avatar
Guido van Rossum committed
29

30 31
    global cache
    cache = {}
Guido van Rossum's avatar
Guido van Rossum committed
32 33


34
def getlines(filename, module_globals=None):
35
    """Get the lines for a Python source file from the cache.
36
    Update the cache if it doesn't contain an entry for this file already."""
Guido van Rossum's avatar
Guido van Rossum committed
37

38
    if filename in cache:
39
        return cache[filename][2]
40 41

    try:
42
        return updatecache(filename, module_globals)
43 44 45
    except MemoryError:
        clearcache()
        return []
Guido van Rossum's avatar
Guido van Rossum committed
46 47


48
def checkcache(filename=None):
49 50
    """Discard cache entries that are out of date.
    (This is not checked upon each call!)"""
Guido van Rossum's avatar
Guido van Rossum committed
51

52
    if filename is None:
53
        filenames = list(cache.keys())
54 55 56 57 58 59 60
    else:
        if filename in cache:
            filenames = [filename]
        else:
            return

    for filename in filenames:
61
        size, mtime, lines, fullname = cache[filename]
62 63
        if mtime is None:
            continue   # no-op for files loaded via a __loader__
64 65
        try:
            stat = os.stat(fullname)
66
        except OSError:
67 68
            del cache[filename]
            continue
69
        if size != stat.st_size or mtime != stat.st_mtime:
70
            del cache[filename]
Guido van Rossum's avatar
Guido van Rossum committed
71 72


73
def updatecache(filename, module_globals=None):
74 75 76 77
    """Update a cache entry and return its list of lines.
    If something's wrong, print a message, discard the cache entry,
    and return an empty list."""

78
    if filename in cache:
79
        del cache[filename]
80
    if not filename or (filename.startswith('<') and filename.endswith('>')):
81
        return []
82

83 84 85
    fullname = filename
    try:
        stat = os.stat(fullname)
86
    except OSError:
87
        basename = filename
88 89 90 91 92 93 94 95

        # Try for a __loader__, if available
        if module_globals and '__loader__' in module_globals:
            name = module_globals.get('__name__')
            loader = module_globals['__loader__']
            get_source = getattr(loader, 'get_source', None)

            if name and get_source:
96 97
                try:
                    data = get_source(name)
98
                except (ImportError, OSError):
99 100 101 102 103 104 105 106 107 108 109
                    pass
                else:
                    if data is None:
                        # No luck, the PEP302 loader cannot find the source
                        # for this module.
                        return []
                    cache[filename] = (
                        len(data), None,
                        [line+'\n' for line in data.splitlines()], fullname
                    )
                    return cache[filename][2]
110

111 112 113 114
        # Try looking through the module search path, which is only useful
        # when handling a relative filename.
        if os.path.isabs(filename):
            return []
115

116 117
        for dirname in sys.path:
            try:
Tim Peters's avatar
Tim Peters committed
118 119 120
                fullname = os.path.join(dirname, basename)
            except (TypeError, AttributeError):
                # Not sufficiently string-like to do anything useful with.
121 122 123 124
                continue
            try:
                stat = os.stat(fullname)
                break
125
            except OSError:
126 127 128
                pass
        else:
            return []
129
    try:
130
        with tokenize.open(fullname) as fp:
131
            lines = fp.readlines()
132
    except OSError:
133
        return []
134 135
    if lines and not lines[-1].endswith('\n'):
        lines[-1] += '\n'
136
    size, mtime = stat.st_size, stat.st_mtime
137 138
    cache[filename] = size, mtime, lines, fullname
    return lines