you know the drill: use keyword argument to bind the the current value to a lambda...
[PyX.git] / pyx / config.py
blob78f9e91d85c0747a68554822aa964ea48ad13d74
1 # -*- encoding: utf-8 -*-
4 # Copyright (C) 2003-2011 Jörg Lehmann <joergl@users.sourceforge.net>
5 # Copyright (C) 2003-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 import configparser, os.path, warnings
24 import os, io, warnings, pkgutil
26 from . import pycompat
28 builtinopen = open
30 try:
31 import pykpathsea as pykpathsea_module
32 has_pykpathsea = True
33 except ImportError:
34 has_pykpathsea = False
37 # Locators implement an open method which returns a list of functions
38 # by searching for a file according to a specific rule. Each of the functions
39 # returned can be called (multiple times) and return an open file. The
40 # opening of the file might fail with a IOError which indicates, that the
41 # file could not be found at the given location.
42 # names is a list of kpsewhich format names to be used for searching where as
43 # extensions is a list of file extensions to be tried (including the dot). Note
44 # that the list of file extenions should include an empty string to not add
45 # an extension at all.
47 locator_classes = {}
49 class local:
50 """locates files in the current directory"""
52 def openers(self, filename, names, extensions):
53 return [lambda extension=extension: builtinopen(filename+extension, "rb") for extension in extensions]
55 locator_classes["local"] = local
58 class internal_pkgutil:
59 """locates files within the PyX data tree (via pkgutil)"""
61 def openers(self, filename, names, extensions):
62 for extension in extensions:
63 full_filename = filename+extension
64 dir = os.path.splitext(full_filename)[1][1:]
65 try:
66 data = pkgutil.get_data("pyx", "data/%s/%s" % (dir, full_filename))
67 except IOError:
68 pass
69 else:
70 if data:
71 return [lambda: io.BytesIO(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):
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), "rb"))
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 = getlist("filelocator", "recursivedir")
98 self.full_filenames = {}
100 def openers(self, filename, names, extensions):
101 for extension in extensions:
102 if filename+extension in self.full_filenames:
103 return [lambda: builtinopen(self.full_filenames[filename], "rb")]
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], "rb")]
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 = getlist("filelocator", "ls-R")
125 self.full_filenames = {}
127 def openers(self, filename, names, extensions):
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 with builtinopen(lsr, "r", encoding="ascii", errors="surrogateescape") as lsrfile:
134 for line in lsrfile:
135 line = line.rstrip()
136 if first and line.startswith("%"):
137 continue
138 first = False
139 if line.endswith(":"):
140 dir = os.path.join(base_dir, line[:-1])
141 elif line:
142 self.full_filenames[line] = os.path.join(dir, line)
143 for extension in extensions:
144 if filename+extension in self.full_filenames:
145 def _opener():
146 try:
147 return builtinopen(self.full_filenames[filename+extension], "rb")
148 except IOError:
149 warnings.warn("'%s' should be available at '%s' according to the ls-R file, "
150 "but the file is not available at this location; "
151 "update your ls-R file" % (filename, self.full_filenames[filename]))
152 return [_opener]
153 return []
155 locator_classes["ls-R"] = ls_R
158 class pykpathsea:
159 """locate files by pykpathsea (a C extension module wrapping libkpathsea)"""
161 def openers(self, filename, names, extensions):
162 if not has_pykpathsea:
163 return []
164 for name in names:
165 full_filename = pykpathsea_module.find_file(filename, name)
166 if full_filename:
167 break
168 else:
169 return []
170 def _opener():
171 try:
172 return builtinopen(full_filename, "rb")
173 except IOError:
174 warnings.warn("'%s' should be available at '%s' according to libkpathsea, "
175 "but the file is not available at this location; "
176 "update your kpsewhich database" % (filename, full_filename))
177 return [_opener]
179 locator_classes["pykpathsea"] = pykpathsea
182 # class libkpathsea:
183 # """locate files by libkpathsea using ctypes"""
185 # def openers(self, filename, names, extensions):
186 # raise NotImplemented
188 # locator_classes["libpathsea"] = libkpathsea
191 class kpsewhich:
192 """locate files using the kpsewhich executable"""
194 def openers(self, filename, names, extensions):
195 for name in names:
196 try:
197 with pycompat.popen('kpsewhich --format="%s" "%s"' % (name, filename)) as output:
198 full_filenames = output.read()
199 except OSError:
200 return []
201 if full_filenames:
202 break
203 else:
204 return []
205 full_filename = full_filenames.decode("ascii").split("\n")[0].rstrip("\r")
207 # Detect Cygwin kpsewhich on Windows Python
208 if os.name == "nt" and full_filename.startswith("/"):
209 full_filename = pycompat.popen('cygpath -w "%s"' % full_filename).read().strip()
211 def _opener():
212 try:
213 return builtinopen(full_filename, "rb")
214 except IOError:
215 warnings.warn("'%s' should be available at '%s' according to kpsewhich, "
216 "but the file is not available at this location; "
217 "update your kpsewhich database" % (filename, full_filename))
218 return [_opener]
220 locator_classes["kpsewhich"] = kpsewhich
223 class locate:
224 """locate files using a locate executable"""
226 def openers(self, filename, names, extensions):
227 for extension in extensions:
228 full_filenames = pycompat.popen("locate \"%s\"" % (filename+extension)).read()
229 if full_filenames:
230 break
231 else:
232 return []
233 full_filename = full_filenames.split("\n")[0].rstrip("\r")
234 def _opener():
235 try:
236 return builtinopen(full_filenames, "rb")
237 except IOError:
238 warnings.warn("'%s' should be available at '%s' according to the locate, "
239 "but the file is not available at this location; "
240 "update your locate database" % (filename, self.full_filenames[filename]))
241 return [_opener]
243 locator_classes["locate"] = locate
247 class _marker: pass
249 config = configparser.ConfigParser()
250 config.read_string(locator_classes["internal"]().openers("pyxrc", [], [""])[0]().read().decode("utf-8"), source="(internal pyxrc)")
251 config.read(os.path.expanduser("~/.pyxrc"), encoding="utf-8")
253 def get(section, option, default=_marker):
254 if default is _marker:
255 return config.get(section, option)
256 else:
257 try:
258 return config.get(section, option)
259 except configparser.Error:
260 return default
262 def getint(section, option, default=_marker):
263 if default is _marker:
264 return config.getint(section, option)
265 else:
266 try:
267 return config.getint(section, option)
268 except configparser.Error:
269 return default
271 def getfloat(section, option, default=_marker):
272 if default is _marker:
273 return config.getfloat(section, option)
274 else:
275 try:
276 return config.getfloat(section, option)
277 except configparser.Error:
278 return default
280 def getboolean(section, option, default=_marker):
281 if default is _marker:
282 return config.getboolean(section, option)
283 else:
284 try:
285 return config.getboolean(section, option)
286 except configparser.Error:
287 return default
289 def getlist(section, option, default=_marker):
290 if default is _marker:
291 l = config.get(section, option).split()
292 else:
293 try:
294 l = config.get(section, option).split()
295 except configparser.Error:
296 return default
297 if space:
298 l = [item.replace(space, " ") for item in l]
299 return l
302 space = get("general", "space", None)
303 formatWarnings = get("general", "warnings", "default")
304 if formatWarnings not in ["default", "short", "shortest"]:
305 raise RuntimeError("invalid config value for option 'warnings' in section 'general'")
306 if formatWarnings != "default":
307 def formatwarning(message, category, filename, lineno, line=None):
308 if formatWarnings == "short":
309 return "%s:%s: %s: %s\n" % (filename, lineno, category.__name__, message)
310 else:
311 return "%s\n" % message
312 warnings.formatwarning = formatwarning
315 methods = [locator_classes[method]()
316 for method in getlist("filelocator", "methods", ["local", "internal", "pykpathsea", "kpsewhich"])]
317 opener_cache = {}
320 def open(filename, formats, ascii=False):
321 """returns an open file searched according the list of formats"""
323 # When using an empty list of formats, the names list is empty
324 # and the extensions list contains an empty string only. For that
325 # case some locators (notably local and internal) return an open
326 # function for the requested file whereas other locators might not
327 # return anything (like pykpathsea and kpsewhich).
328 # This is useful for files not to be searched in the latex
329 # installation at all (like lfs files).
330 extensions = set([""])
331 for format in formats:
332 for extension in format.extensions:
333 extensions.add(extension)
334 names = tuple([format.name for format in formats])
335 if (filename, names) in opener_cache:
336 file = opener_cache[(filename, names)]()
337 else:
338 for method in methods:
339 openers = method.openers(filename, names, extensions)
340 for opener in openers:
341 try:
342 file = opener()
343 except EnvironmentError:
344 file = None
345 if file:
346 opener_cache[(filename, names)] = opener
347 break
348 # break two loops here
349 else:
350 continue
351 break
352 else:
353 raise IOError("Could not locate the file '%s'." % filename)
354 if ascii:
355 return io.TextIOWrapper(file, encoding="ascii", errors="surrogateescape")
356 else:
357 return file
360 class format:
361 def __init__(self, name, extensions):
362 self.name = name
363 self.extensions = extensions
365 format.tfm = format("tfm", [".tfm"])
366 format.afm = format("afm", [".afm"])
367 format.fontmap = format("map", [])
368 format.pict = format("graphic/figure", [".eps", ".epsi"])
369 format.tex_ps_header = format("PostScript header", [".pro"]) # contains also: enc files
370 format.type1 = format("type1 fonts", [".pfa", ".pfb"])
371 format.vf = format("vf", [".vf"])
372 format.dvips_config = format("dvips config", [])