use pycompat.popen at more places
[PyX.git] / pyx / filelocator.py
blob71a73d4120073b98e883d606868dcbb4f7f440dd
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
30 # Locators implement an open method which returns a list of functions
31 # by searching for a file according to a specific rule. Each of the functions
32 # returned can be called (multiple times) and return an open file. The
33 # opening of the file might fail with a IOError which indicates, that the
34 # file could not be found at the given location.
35 # names is a list of kpsewhich format names to be used for searching where as
36 # extensions is a list of file extensions to be tried (including the dot). Note
37 # that the list of file extenions should include an empty string to not add
38 # an extension at all.
40 locator_classes = {}
42 class local:
43 """locates files in the current directory"""
45 def openers(self, filename, names, extensions, mode):
46 return [lambda: builtinopen(filename+extension, mode) for extension in extensions]
48 locator_classes["local"] = local
51 class internal_pkgutil:
52 """locates files within the pyx data tree (via pkgutil)"""
54 def openers(self, filename, names, extensions, mode):
55 for extension in extensions:
56 full_filename = filename+extension
57 dir = os.path.splitext(full_filename)[1][1:]
58 try:
59 data = pkgutil.get_data("pyx", "data/%s/%s" % (dir, full_filename))
60 except IOError:
61 pass
62 else:
63 if data:
64 # ignoring mode?!
65 return [lambda: cStringIO.StringIO(data)]
67 class internal_open:
68 """locates files within the pyx data tree (via an open relative to the path of this file)"""
70 def openers(self, filename, names, extensions, mode):
71 result = []
72 for extension in extensions:
73 full_filename = filename+extension
74 dir = os.path.splitext(full_filename)[1][1:]
75 result.append(lambda: builtinopen(os.path.join(os.path.dirname(__file__), "data", dir, full_filename), mode))
76 return result
78 try:
79 pkgutil.get_data
80 except AttributeError:
81 locator_classes["internal"] = internal_open # fallback for python < 2.6
82 else:
83 locator_classes["internal"] = internal_pkgutil
86 class recursivedir:
87 """locates files by searching recursively in a list of directories"""
89 def __init__(self):
90 self.dirs = config.getlist("locator", "recursivedir")
91 self.full_filenames = {}
93 def openers(self, filename, names, extensions, mode):
94 for extension in extensions:
95 if filename+extension in self.full_filenames:
96 return [lambda: builtinopen(self.full_filenames[filename], mode)]
97 while self.dirs:
98 dir = self.dirs.pop(0)
99 for item in os.listdir(dir):
100 full_item = os.path.join(dir, item)
101 if os.path.isdir(full_item):
102 self.dirs.insert(0, full_item)
103 else:
104 self.full_filenames[item] = full_item
105 for extension in extensions:
106 if filename+extension in self.full_filenames:
107 return [lambda: builtinopen(self.full_filenames[filename], mode)]
109 locator_classes["recursivedir"] = recursivedir
112 class ls_R:
113 """locates files by searching a list of ls-R files"""
115 def __init__(self):
116 self.ls_Rs = config.getlist("locator", "ls-R")
117 self.full_filenames = {}
119 def openers(self, filename, names, extensions, mode):
120 while self.ls_Rs and not any([filename+extension in self.full_filenames for extension in extensions]):
121 lsr = self.ls_Rs.pop(0)
122 base_dir = os.path.dirname(lsr)
123 dir = None
124 first = True
125 for line in builtinopen(lsr):
126 line = line.rstrip()
127 if first and line.startswith("%"):
128 continue
129 first = False
130 if line.endswith(":"):
131 dir = os.path.join(base_dir, line[:-1])
132 elif line:
133 self.full_filenames[line] = os.path.join(dir, line)
134 for extension in extensions:
135 if filename+extension in self.full_filenames:
136 def _opener():
137 try:
138 return builtinopen(self.full_filenames[filename+extension], mode)
139 except IOError:
140 warnings.warn("'%s' should be available at '%s' according to the ls-R file, "
141 "but the file is not available at this location; "
142 "update your ls-R file" % (filename, self.full_filenames[filename]))
143 return [_opener]
145 locator_classes["ls-R"] = ls_R
148 class pykpathsea:
149 """locate files by pykpathsea (a C extension module wrapping libkpathsea)"""
151 def openers(self, filename, names, extensions, mode):
152 import pykpathsea
153 for name in names:
154 full_filename = pykpathsea.find_file(filename, name)
155 if full_filename:
156 break
157 else:
158 return
159 def _opener():
160 try:
161 return builtinopen(full_filename, mode)
162 except IOError:
163 warnings.warn("'%s' should be available at '%s' according to libkpathsea, "
164 "but the file is not available at this location; "
165 "update your kpsewhich database" % (filename, full_filename))
166 return [_opener]
168 locator_classes["pykpathsea"] = pykpathsea
171 # class libkpathsea:
172 # """locate files by libkpathsea using ctypes"""
174 # def openers(self, filename, names, extensions, mode):
175 # raise NotImplemented
177 # locator_classes["libpathsea"] = libkpathsea
180 class kpsewhich:
181 """locate files using the kpsewhich executable"""
183 def openers(self, filename, names, extensions, mode):
184 for name in names:
185 try:
186 full_filenames = pycompat.popen('kpsewhich --format="%s" "%s"' % (name, filename)).read()
187 except OSError:
188 return
189 if full_filenames:
190 break
191 else:
192 return
193 full_filename = full_filenames.split("\n")[0]
194 def _opener():
195 try:
196 return builtinopen(full_filename, mode)
197 except IOError:
198 warnings.warn("'%s' should be available at '%s' according to kpsewhich, "
199 "but the file is not available at this location; "
200 "update your kpsewhich database" % (filename, full_filename))
201 return [_opener]
203 locator_classes["kpsewhich"] = kpsewhich
206 class locate:
207 """locate files using a locate executable"""
209 def openers(self, filename, names, extensions, mode):
210 for extension in extensions:
211 full_filenames = pycompat.popen("locate \"%s\"" % (filename+extension)).read()
212 if full_filenames:
213 break
214 else:
215 return
216 full_filename = full_filenames.split("\n")[0]
217 def _opener():
218 try:
219 return builtinopen(full_filenames, mode)
220 except IOError:
221 warnings.warn("'%s' should be available at '%s' according to the locate, "
222 "but the file is not available at this location; "
223 "update your locate database" % (filename, self.full_filenames[filename]))
224 return [_opener]
226 locator_classes["locate"] = locate
229 methods = [locator_classes[method]()
230 for method in config.getlist("locator", "methods", "local internal pykpathsea kpsewhich")]
232 opener_cache = {}
234 def open(filename, formats, mode="r"):
235 """returns an open file searched according the list of formats
237 When using an empty list of formats, the names list is empty
238 and the extensions list contains an empty string only. For that
239 case some locators (notably local and internal) return an open
240 function for the requested file whereas other locators might not
241 return anything (like pykpathsea and kpsewhich) as the names list
242 is empty. This is useful for files not to be searched in the latex
243 installation at all (like lfs files)."""
244 extensions = pycompat.set([""])
245 for format in formats:
246 for extension in format.extensions:
247 extensions.add(extension)
248 names = tuple([format.name for format in formats])
249 if (filename, names) in opener_cache:
250 return opener_cache[(filename, names)]()
251 for method in methods:
252 openers = method.openers(filename, names, extensions, mode)
253 if openers:
254 for opener in openers:
255 try:
256 file = opener()
257 except IOError:
258 file = None
259 if file:
260 opener_cache[(filename, names)] = opener
261 return file
262 raise IOError("Could not locate the file '%s'." % filename)
265 class format:
266 def __init__(self, name, extensions):
267 self.name = name
268 self.extensions = extensions
270 format.tfm = format("tfm", [".tfm"])
271 format.afm = format("afm", [".afm"])
272 format.fontmap = format("map", [])
273 format.pict = format("graphics/figure", [".eps", ".epsi"])
274 format.tex_ps_header = format("PostScript header", [".pro"]) # contains also: enc files
275 format.type1 = format("type1 fonts", [".pfa", ".pfb"])
276 format.vf = format("vf", [".vf"])
277 format.dvips_config = format("dvips config", [])