linecache.py 4.13 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 re
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 62 63 64
        try:
            stat = os.stat(fullname)
        except os.error:
            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 76 77
        del cache[filename]
    if not filename or filename[0] + filename[-1] == '<>':
        return []
78

79 80 81
    fullname = filename
    try:
        stat = os.stat(fullname)
82
    except os.error as msg:
83
        basename = os.path.split(filename)[1]
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 94 95 96 97 98 99 100 101 102 103 104 105
                try:
                    data = get_source(name)
                except (ImportError, IOError):
                    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

        # Try looking through the module search path.

109 110
        for dirname in sys.path:
            try:
Tim Peters's avatar
Tim Peters committed
111 112 113
                fullname = os.path.join(dirname, basename)
            except (TypeError, AttributeError):
                # Not sufficiently string-like to do anything useful with.
114
                pass
Tim Peters's avatar
Tim Peters committed
115 116 117 118 119 120
            else:
                try:
                    stat = os.stat(fullname)
                    break
                except os.error:
                    pass
121 122
        else:
            # No luck
123
##          print '*** Cannot stat', filename, ':', msg
124
            return []
125 126 127
##  print("Refreshing cache for %s..." % fullname)
    try:
        fp = open(fullname, 'rU')
128
        lines = fp.readlines()
129 130 131 132 133 134 135 136 137 138 139 140 141 142 143
        fp.close()
    except Exception as msg:
##      print '*** Cannot open', fullname, ':', msg
        return []
    coding = "utf-8"
    for line in lines[:2]:
        m = re.search(r"coding[:=]\s*([-\w.]+)", line)
        if m:
            coding = m.group(1)
            break
    try:
        lines = [line if isinstance(line, str) else str(line, coding)
                 for line in lines]
    except:
        pass  # Hope for the best
144
    size, mtime = stat.st_size, stat.st_mtime
145 146
    cache[filename] = size, mtime, lines, fullname
    return lines