fix file opening for bbox reading when using filelocator (reported by Michael J Gruber)
[PyX/mjg.git] / pyx / filelocator.py
blob69bec5c828f8709e8d3e50b13447ec1e741b6683
1 # -*- encoding: utf-8 -*-
4 # Copyright (C) 2011 Jörg Lehmann <joergl@users.sourceforge.net>
5 # Copyright (C) 2009-2011 André Wobst <wobsta@users.sourceforge.net>
7 # This file is part of PyX (http://pyx.sourceforge.net/).
9 # PyX is free software; you can redistribute it and/or modify
10 # it under the terms of the GNU General Public License as published by
11 # the Free Software Foundation; either version 2 of the License, or
12 # (at your option) any later version.
14 # PyX is distributed in the hope that it will be useful,
15 # but WITHOUT ANY WARRANTY; without even the implied warranty of
16 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 # GNU General Public License for more details.
19 # You should have received a copy of the GNU General Public License
20 # along with PyX; if not, write to the Free Software
21 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
23 builtinopen = open
25 import os, cStringIO, warnings, pkgutil
27 import config, pycompat
29 try:
30 import pykpathsea as pykpathsea_module
31 has_pykpathsea = True
32 except ImportError:
33 has_pykpathsea = False
36 # Locators implement an open method which returns a list of functions
37 # by searching for a file according to a specific rule. Each of the functions
38 # returned can be called (multiple times) and return an open file. The
39 # opening of the file might fail with a IOError which indicates, that the
40 # file could not be found at the given location.
41 # names is a list of kpsewhich format names to be used for searching where as
42 # extensions is a list of file extensions to be tried (including the dot). Note
43 # that the list of file extenions should include an empty string to not add
44 # an extension at all.
46 locator_classes = {}
48 class local:
49 """locates files in the current directory"""
51 def openers(self, filename, names, extensions, mode):
52 return [lambda: builtinopen(filename+extension, mode) for extension in extensions]
54 locator_classes["local"] = local
57 class internal_pkgutil:
58 """locates files within the PyX data tree (via pkgutil)"""
60 def openers(self, filename, names, extensions, mode):
61 for extension in extensions:
62 full_filename = filename+extension
63 dir = os.path.splitext(full_filename)[1][1:]
64 try:
65 data = pkgutil.get_data("pyx", "data/%s/%s" % (dir, full_filename))
66 except IOError:
67 pass
68 else:
69 if data:
70 # ignoring mode?!
71 return [lambda: cStringIO.StringIO(data)]
72 return []
74 class internal_open:
75 """locates files within the PyX data tree (via an open relative to the path of this file)"""
77 def openers(self, filename, names, extensions, mode):
78 result = []
79 for extension in extensions:
80 full_filename = filename+extension
81 dir = os.path.splitext(full_filename)[1][1:]
82 result.append(lambda: builtinopen(os.path.join(os.path.dirname(__file__), "data", dir, full_filename), mode))
83 return result
85 try:
86 pkgutil.get_data
87 except AttributeError:
88 locator_classes["internal"] = internal_open # fallback for python < 2.6
89 else:
90 locator_classes["internal"] = internal_pkgutil
93 class recursivedir:
94 """locates files by searching recursively in a list of directories"""
96 def __init__(self):
97 self.dirs = config.getlist("locator", "recursivedir")
98 self.full_filenames = {}
100 def openers(self, filename, names, extensions, mode):
101 for extension in extensions:
102 if filename+extension in self.full_filenames:
103 return [lambda: builtinopen(self.full_filenames[filename], mode)]
104 while self.dirs:
105 dir = self.dirs.pop(0)
106 for item in os.listdir(dir):
107 full_item = os.path.join(dir, item)
108 if os.path.isdir(full_item):
109 self.dirs.insert(0, full_item)
110 else:
111 self.full_filenames[item] = full_item
112 for extension in extensions:
113 if filename+extension in self.full_filenames:
114 return [lambda: builtinopen(self.full_filenames[filename], mode)]
115 return []
117 locator_classes["recursivedir"] = recursivedir
120 class ls_R:
121 """locates files by searching a list of ls-R files"""
123 def __init__(self):
124 self.ls_Rs = config.getlist("locator", "ls-R")
125 self.full_filenames = {}
127 def openers(self, filename, names, extensions, mode):
128 while self.ls_Rs and not any([filename+extension in self.full_filenames for extension in extensions]):
129 lsr = self.ls_Rs.pop(0)
130 base_dir = os.path.dirname(lsr)
131 dir = None
132 first = True
133 for line in builtinopen(lsr):
134 line = line.rstrip()
135 if first and line.startswith("%"):
136 continue
137 first = False
138 if line.endswith(":"):
139 dir = os.path.join(base_dir, line[:-1])
140 elif line:
141 self.full_filenames[line] = os.path.join(dir, line)
142 for extension in extensions:
143 if filename+extension in self.full_filenames:
144 def _opener():
145 try:
146 return builtinopen(self.full_filenames[filename+extension], mode)
147 except IOError:
148 warnings.warn("'%s' should be available at '%s' according to the ls-R file, "
149 "but the file is not available at this location; "
150 "update your ls-R file" % (filename, self.full_filenames[filename]))
151 return [_opener]
152 return []
154 locator_classes["ls-R"] = ls_R
157 class pykpathsea:
158 """locate files by pykpathsea (a C extension module wrapping libkpathsea)"""
160 def openers(self, filename, names, extensions, mode):
161 if not has_pykpathsea:
162 return []
163 for name in names:
164 full_filename = pykpathsea_module.find_file(filename, name)
165 if full_filename:
166 break
167 else:
168 return []
169 def _opener():
170 try:
171 return builtinopen(full_filename, mode)
172 except IOError:
173 warnings.warn("'%s' should be available at '%s' according to libkpathsea, "
174 "but the file is not available at this location; "
175 "update your kpsewhich database" % (filename, full_filename))
176 return [_opener]
178 locator_classes["pykpathsea"] = pykpathsea
181 # class libkpathsea:
182 # """locate files by libkpathsea using ctypes"""
184 # def openers(self, filename, names, extensions, mode):
185 # raise NotImplemented
187 # locator_classes["libpathsea"] = libkpathsea
190 class kpsewhich:
191 """locate files using the kpsewhich executable"""
193 def openers(self, filename, names, extensions, mode):
194 for name in names:
195 try:
196 full_filenames = pycompat.popen('kpsewhich --format="%s" "%s"' % (name, filename)).read()
197 except OSError:
198 return []
199 if full_filenames:
200 break
201 else:
202 return []
203 full_filename = full_filenames.split("\n")[0].rstrip("\r")
204 def _opener():
205 try:
206 return builtinopen(full_filename, mode)
207 except IOError:
208 warnings.warn("'%s' should be available at '%s' according to kpsewhich, "
209 "but the file is not available at this location; "
210 "update your kpsewhich database" % (filename, full_filename))
211 return [_opener]
213 locator_classes["kpsewhich"] = kpsewhich
216 class locate:
217 """locate files using a locate executable"""
219 def openers(self, filename, names, extensions, mode):
220 for extension in extensions:
221 full_filenames = pycompat.popen("locate \"%s\"" % (filename+extension)).read()
222 if full_filenames:
223 break
224 else:
225 return []
226 full_filename = full_filenames.split("\n")[0].rstrip("\r")
227 def _opener():
228 try:
229 return builtinopen(full_filenames, mode)
230 except IOError:
231 warnings.warn("'%s' should be available at '%s' according to the locate, "
232 "but the file is not available at this location; "
233 "update your locate database" % (filename, self.full_filenames[filename]))
234 return [_opener]
236 locator_classes["locate"] = locate
239 def init():
240 global methods, opener_cache
241 methods = [locator_classes[method]()
242 for method in config.getlist("filelocator", "methods", ["local", "internal", "pykpathsea", "kpsewhich"])]
243 opener_cache = {}
246 def open(filename, formats, mode="r"):
247 """returns an open file searched according the list of formats"""
249 # When using an empty list of formats, the names list is empty
250 # and the extensions list contains an empty string only. For that
251 # case some locators (notably local and internal) return an open
252 # function for the requested file whereas other locators might not
253 # return anything (like pykpathsea and kpsewhich).
254 # This is useful for files not to be searched in the latex
255 # installation at all (like lfs files).
256 extensions = pycompat.set([""])
257 for format in formats:
258 for extension in format.extensions:
259 extensions.add(extension)
260 names = tuple([format.name for format in formats])
261 if (filename, names) in opener_cache:
262 return opener_cache[(filename, names)]()
263 for method in methods:
264 openers = method.openers(filename, names, extensions, mode)
265 for opener in openers:
266 try:
267 file = opener()
268 except IOError:
269 file = None
270 if file:
271 opener_cache[(filename, names)] = opener
272 return file
273 raise IOError("Could not locate the file '%s'." % filename)
276 class format:
277 def __init__(self, name, extensions):
278 self.name = name
279 self.extensions = extensions
281 format.tfm = format("tfm", [".tfm"])
282 format.afm = format("afm", [".afm"])
283 format.fontmap = format("map", [])
284 format.pict = format("graphics/figure", [".eps", ".epsi"])
285 format.tex_ps_header = format("PostScript header", [".pro"]) # contains also: enc files
286 format.type1 = format("type1 fonts", [".pfa", ".pfb"])
287 format.vf = format("vf", [".vf"])
288 format.dvips_config = format("dvips config", [])