silently disable pykpathsea locator if extension module is not available
[PyX.git] / pyx / filelocator.py
blob292411df8e25b404721fecdc2e413c68de4294f4
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
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)]
73 class internal_open:
74 """locates files within the pyx data tree (via an open relative to the path of this file)"""
76 def openers(self, filename, names, extensions, mode):
77 result = []
78 for extension in extensions:
79 full_filename = filename+extension
80 dir = os.path.splitext(full_filename)[1][1:]
81 result.append(lambda: builtinopen(os.path.join(os.path.dirname(__file__), "data", dir, full_filename), mode))
82 return result
84 try:
85 pkgutil.get_data
86 except AttributeError:
87 locator_classes["internal"] = internal_open # fallback for python < 2.6
88 else:
89 locator_classes["internal"] = internal_pkgutil
92 class recursivedir:
93 """locates files by searching recursively in a list of directories"""
95 def __init__(self):
96 self.dirs = config.getlist("locator", "recursivedir")
97 self.full_filenames = {}
99 def openers(self, filename, names, extensions, mode):
100 for extension in extensions:
101 if filename+extension in self.full_filenames:
102 return [lambda: builtinopen(self.full_filenames[filename], mode)]
103 while self.dirs:
104 dir = self.dirs.pop(0)
105 for item in os.listdir(dir):
106 full_item = os.path.join(dir, item)
107 if os.path.isdir(full_item):
108 self.dirs.insert(0, full_item)
109 else:
110 self.full_filenames[item] = full_item
111 for extension in extensions:
112 if filename+extension in self.full_filenames:
113 return [lambda: builtinopen(self.full_filenames[filename], mode)]
115 locator_classes["recursivedir"] = recursivedir
118 class ls_R:
119 """locates files by searching a list of ls-R files"""
121 def __init__(self):
122 self.ls_Rs = config.getlist("locator", "ls-R")
123 self.full_filenames = {}
125 def openers(self, filename, names, extensions, mode):
126 while self.ls_Rs and not any([filename+extension in self.full_filenames for extension in extensions]):
127 lsr = self.ls_Rs.pop(0)
128 base_dir = os.path.dirname(lsr)
129 dir = None
130 first = True
131 for line in builtinopen(lsr):
132 line = line.rstrip()
133 if first and line.startswith("%"):
134 continue
135 first = False
136 if line.endswith(":"):
137 dir = os.path.join(base_dir, line[:-1])
138 elif line:
139 self.full_filenames[line] = os.path.join(dir, line)
140 for extension in extensions:
141 if filename+extension in self.full_filenames:
142 def _opener():
143 try:
144 return builtinopen(self.full_filenames[filename+extension], mode)
145 except IOError:
146 warnings.warn("'%s' should be available at '%s' according to the ls-R file, "
147 "but the file is not available at this location; "
148 "update your ls-R file" % (filename, self.full_filenames[filename]))
149 return [_opener]
151 locator_classes["ls-R"] = ls_R
154 class pykpathsea:
155 """locate files by pykpathsea (a C extension module wrapping libkpathsea)"""
157 def openers(self, filename, names, extensions, mode):
158 if not has_pykpathsea:
159 return []
160 for name in names:
161 full_filename = pykpathsea.find_file(filename, name)
162 if full_filename:
163 break
164 else:
165 return
166 def _opener():
167 try:
168 return builtinopen(full_filename, mode)
169 except IOError:
170 warnings.warn("'%s' should be available at '%s' according to libkpathsea, "
171 "but the file is not available at this location; "
172 "update your kpsewhich database" % (filename, full_filename))
173 return [_opener]
175 locator_classes["pykpathsea"] = pykpathsea
178 # class libkpathsea:
179 # """locate files by libkpathsea using ctypes"""
181 # def openers(self, filename, names, extensions, mode):
182 # raise NotImplemented
184 # locator_classes["libpathsea"] = libkpathsea
187 class kpsewhich:
188 """locate files using the kpsewhich executable"""
190 def openers(self, filename, names, extensions, mode):
191 for name in names:
192 try:
193 full_filenames = pycompat.popen('kpsewhich --format="%s" "%s"' % (name, filename)).read()
194 except OSError:
195 return
196 if full_filenames:
197 break
198 else:
199 return
200 full_filename = full_filenames.split("\n")[0]
201 def _opener():
202 try:
203 return builtinopen(full_filename, mode)
204 except IOError:
205 warnings.warn("'%s' should be available at '%s' according to kpsewhich, "
206 "but the file is not available at this location; "
207 "update your kpsewhich database" % (filename, full_filename))
208 return [_opener]
210 locator_classes["kpsewhich"] = kpsewhich
213 class locate:
214 """locate files using a locate executable"""
216 def openers(self, filename, names, extensions, mode):
217 for extension in extensions:
218 full_filenames = pycompat.popen("locate \"%s\"" % (filename+extension)).read()
219 if full_filenames:
220 break
221 else:
222 return
223 full_filename = full_filenames.split("\n")[0]
224 def _opener():
225 try:
226 return builtinopen(full_filenames, mode)
227 except IOError:
228 warnings.warn("'%s' should be available at '%s' according to the locate, "
229 "but the file is not available at this location; "
230 "update your locate database" % (filename, self.full_filenames[filename]))
231 return [_opener]
233 locator_classes["locate"] = locate
236 def init():
237 global methods, opener_cache
238 print "a"*100
239 print config.getlist("locator", "methods", "local internal pykpathsea kpsewhich")
240 methods = [locator_classes[method]()
241 for method in config.getlist("locator", "methods", "local internal pykpathsea kpsewhich")]
242 opener_cache = {}
245 def open(filename, formats, mode="r"):
246 """returns an open file searched according the list of formats"""
248 # When using an empty list of formats, the names list is empty
249 # and the extensions list contains an empty string only. For that
250 # case some locators (notably local and internal) return an open
251 # function for the requested file whereas other locators might not
252 # return anything (like pykpathsea and kpsewhich).
253 # This is useful for files not to be searched in the latex
254 # installation at all (like lfs files).
255 extensions = pycompat.set([""])
256 for format in formats:
257 for extension in format.extensions:
258 extensions.add(extension)
259 names = tuple([format.name for format in formats])
260 if (filename, names) in opener_cache:
261 return opener_cache[(filename, names)]()
262 for method in methods:
263 openers = method.openers(filename, names, extensions, mode)
264 if openers:
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", [])