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

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 36
    """Get the lines for a file from the cache.
    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 40
        return cache[filename][2]
    else:
41
        return updatecache(filename, module_globals)
Guido van Rossum's avatar
Guido van Rossum committed
42 43


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

48
    if filename is None:
49
        filenames = list(cache.keys())
50 51 52 53 54 55 56
    else:
        if filename in cache:
            filenames = [filename]
        else:
            return

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


69
def updatecache(filename, module_globals=None):
70 71 72 73
    """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."""

74
    if filename in cache:
75
        del cache[filename]
76
    if not filename or (filename.startswith('<') and filename.endswith('>')):
77
        return []
78

79 80 81
    fullname = filename
    try:
        stat = os.stat(fullname)
82
    except OSError:
83
        basename = filename
84 85 86 87 88 89 90 91

        # 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:
92 93
                try:
                    data = get_source(name)
94
                except (ImportError, OSError):
95 96 97 98 99 100 101 102 103 104 105
                    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]
106

107 108 109 110
        # Try looking through the module search path, which is only useful
        # when handling a relative filename.
        if os.path.isabs(filename):
            return []
111

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