]>
Commit | Line | Data |
---|---|---|
3257aa99 DM |
1 | """Cache lines from files.\r |
2 | \r | |
3 | This is intended to read lines from modules imported -- hence if a filename\r | |
4 | is not found, it will look down the module search path for a file by\r | |
5 | that name.\r | |
6 | """\r | |
7 | \r | |
8 | import sys\r | |
9 | import os\r | |
10 | \r | |
11 | __all__ = ["getline", "clearcache", "checkcache"]\r | |
12 | \r | |
13 | def getline(filename, lineno, module_globals=None):\r | |
14 | lines = getlines(filename, module_globals)\r | |
15 | if 1 <= lineno <= len(lines):\r | |
16 | return lines[lineno-1]\r | |
17 | else:\r | |
18 | return ''\r | |
19 | \r | |
20 | \r | |
21 | # The cache\r | |
22 | \r | |
23 | cache = {} # The cache\r | |
24 | \r | |
25 | \r | |
26 | def clearcache():\r | |
27 | """Clear the cache entirely."""\r | |
28 | \r | |
29 | global cache\r | |
30 | cache = {}\r | |
31 | \r | |
32 | \r | |
33 | def getlines(filename, module_globals=None):\r | |
34 | """Get the lines for a file from the cache.\r | |
35 | Update the cache if it doesn't contain an entry for this file already."""\r | |
36 | \r | |
37 | if filename in cache:\r | |
38 | return cache[filename][2]\r | |
39 | \r | |
40 | try:\r | |
41 | return updatecache(filename, module_globals)\r | |
42 | except MemoryError:\r | |
43 | clearcache()\r | |
44 | return []\r | |
45 | \r | |
46 | \r | |
47 | def checkcache(filename=None):\r | |
48 | """Discard cache entries that are out of date.\r | |
49 | (This is not checked upon each call!)"""\r | |
50 | \r | |
51 | if filename is None:\r | |
52 | filenames = cache.keys()\r | |
53 | else:\r | |
54 | if filename in cache:\r | |
55 | filenames = [filename]\r | |
56 | else:\r | |
57 | return\r | |
58 | \r | |
59 | for filename in filenames:\r | |
60 | size, mtime, lines, fullname = cache[filename]\r | |
61 | if mtime is None:\r | |
62 | continue # no-op for files loaded via a __loader__\r | |
63 | try:\r | |
64 | stat = os.stat(fullname)\r | |
65 | except os.error:\r | |
66 | del cache[filename]\r | |
67 | continue\r | |
68 | if size != stat.st_size or mtime != stat.st_mtime:\r | |
69 | del cache[filename]\r | |
70 | \r | |
71 | \r | |
72 | def updatecache(filename, module_globals=None):\r | |
73 | """Update a cache entry and return its list of lines.\r | |
74 | If something's wrong, print a message, discard the cache entry,\r | |
75 | and return an empty list."""\r | |
76 | \r | |
77 | if filename in cache:\r | |
78 | del cache[filename]\r | |
79 | if not filename or (filename.startswith('<') and filename.endswith('>')):\r | |
80 | return []\r | |
81 | \r | |
82 | fullname = filename\r | |
83 | try:\r | |
84 | stat = os.stat(fullname)\r | |
85 | except OSError:\r | |
86 | basename = filename\r | |
87 | \r | |
88 | # Try for a __loader__, if available\r | |
89 | if module_globals and '__loader__' in module_globals:\r | |
90 | name = module_globals.get('__name__')\r | |
91 | loader = module_globals['__loader__']\r | |
92 | get_source = getattr(loader, 'get_source', None)\r | |
93 | \r | |
94 | if name and get_source:\r | |
95 | try:\r | |
96 | data = get_source(name)\r | |
97 | except (ImportError, IOError):\r | |
98 | pass\r | |
99 | else:\r | |
100 | if data is None:\r | |
101 | # No luck, the PEP302 loader cannot find the source\r | |
102 | # for this module.\r | |
103 | return []\r | |
104 | cache[filename] = (\r | |
105 | len(data), None,\r | |
106 | [line+'\n' for line in data.splitlines()], fullname\r | |
107 | )\r | |
108 | return cache[filename][2]\r | |
109 | \r | |
110 | # Try looking through the module search path, which is only useful\r | |
111 | # when handling a relative filename.\r | |
112 | if os.path.isabs(filename):\r | |
113 | return []\r | |
114 | \r | |
115 | for dirname in sys.path:\r | |
116 | # When using imputil, sys.path may contain things other than\r | |
117 | # strings; ignore them when it happens.\r | |
118 | try:\r | |
119 | fullname = os.path.join(dirname, basename)\r | |
120 | except (TypeError, AttributeError):\r | |
121 | # Not sufficiently string-like to do anything useful with.\r | |
122 | continue\r | |
123 | try:\r | |
124 | stat = os.stat(fullname)\r | |
125 | break\r | |
126 | except os.error:\r | |
127 | pass\r | |
128 | else:\r | |
129 | return []\r | |
130 | try:\r | |
131 | with open(fullname, 'rU') as fp:\r | |
132 | lines = fp.readlines()\r | |
133 | except IOError:\r | |
134 | return []\r | |
135 | if lines and not lines[-1].endswith('\n'):\r | |
136 | lines[-1] += '\n'\r | |
137 | size, mtime = stat.st_size, stat.st_mtime\r | |
138 | cache[filename] = size, mtime, lines, fullname\r | |
139 | return lines\r |