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
31 import pykpathsea
as pykpathsea_module
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.
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
59 """locates files within the PyX data tree"""
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:]
66 data
= pkgutil
.get_data("pyx", "data/%s/%s" % (dir, full_filename
))
71 return [lambda: io
.BytesIO(data
)]
74 locator_classes
["internal"] = internal
78 """locates files by searching recursively in a list of directories"""
81 self
.dirs
= getlist("filelocator", "recursivedir")
82 self
.full_filenames
= {}
84 def openers(self
, filename
, names
, extensions
):
85 for extension
in extensions
:
86 if filename
+extension
in self
.full_filenames
:
87 return [lambda: builtinopen(self
.full_filenames
[filename
], "rb")]
89 dir = self
.dirs
.pop(0)
90 for item
in os
.listdir(dir):
91 full_item
= os
.path
.join(dir, item
)
92 if os
.path
.isdir(full_item
):
93 self
.dirs
.insert(0, full_item
)
95 self
.full_filenames
[item
] = full_item
96 for extension
in extensions
:
97 if filename
+extension
in self
.full_filenames
:
98 return [lambda: builtinopen(self
.full_filenames
[filename
], "rb")]
101 locator_classes
["recursivedir"] = recursivedir
105 """locates files by searching a list of ls-R files"""
108 self
.ls_Rs
= getlist("filelocator", "ls-R")
109 self
.full_filenames
= {}
111 def openers(self
, filename
, names
, extensions
):
112 while self
.ls_Rs
and not any([filename
+extension
in self
.full_filenames
for extension
in extensions
]):
113 lsr
= self
.ls_Rs
.pop(0)
114 base_dir
= os
.path
.dirname(lsr
)
117 with
builtinopen(lsr
, "r", encoding
="ascii", errors
="surrogateescape") as lsrfile
:
120 if first
and line
.startswith("%"):
123 if line
.endswith(":"):
124 dir = os
.path
.join(base_dir
, line
[:-1])
126 self
.full_filenames
[line
] = os
.path
.join(dir, line
)
127 for extension
in extensions
:
128 if filename
+extension
in self
.full_filenames
:
131 return builtinopen(self
.full_filenames
[filename
+extension
], "rb")
133 warnings
.warn("'%s' should be available at '%s' according to the ls-R file, "
134 "but the file is not available at this location; "
135 "update your ls-R file" % (filename
, self
.full_filenames
[filename
]))
139 locator_classes
["ls-R"] = ls_R
143 """locate files by pykpathsea (a C extension module wrapping libkpathsea)"""
145 def openers(self
, filename
, names
, extensions
):
146 if not has_pykpathsea
:
149 full_filename
= pykpathsea_module
.find_file(filename
, name
)
156 return builtinopen(full_filename
, "rb")
158 warnings
.warn("'%s' should be available at '%s' according to libkpathsea, "
159 "but the file is not available at this location; "
160 "update your kpsewhich database" % (filename
, full_filename
))
163 locator_classes
["pykpathsea"] = pykpathsea
167 # """locate files by libkpathsea using ctypes"""
169 # def openers(self, filename, names, extensions):
170 # raise NotImplemented
172 # locator_classes["libpathsea"] = libkpathsea
176 """locate files using the kpsewhich executable"""
178 def openers(self
, filename
, names
, extensions
):
181 with pycompat
.popen('kpsewhich --format="%s" "%s"' % (name
, filename
)) as output
:
182 full_filenames
= output
.read()
189 full_filename
= full_filenames
.decode("ascii").split("\n")[0].rstrip("\r")
191 # Detect Cygwin kpsewhich on Windows Python
192 if os
.name
== "nt" and full_filename
.startswith("/"):
193 full_filename
= pycompat
.popen('cygpath -w "%s"' % full_filename
).read().strip()
197 return builtinopen(full_filename
, "rb")
199 warnings
.warn("'%s' should be available at '%s' according to kpsewhich, "
200 "but the file is not available at this location; "
201 "update your kpsewhich database" % (filename
, full_filename
))
204 locator_classes
["kpsewhich"] = kpsewhich
208 """locate files using a locate executable"""
210 def openers(self
, filename
, names
, extensions
):
211 for extension
in extensions
:
212 full_filenames
= pycompat
.popen("locate \"%s\"" % (filename
+extension
)).read()
217 full_filename
= full_filenames
.split("\n")[0].rstrip("\r")
220 return builtinopen(full_filenames
, "rb")
222 warnings
.warn("'%s' should be available at '%s' according to the locate, "
223 "but the file is not available at this location; "
224 "update your locate database" % (filename
, self
.full_filenames
[filename
]))
227 locator_classes
["locate"] = locate
233 config
= configparser
.ConfigParser()
234 config
.read_string(locator_classes
["internal"]().openers("pyxrc", [], [""])[0]().read().decode("utf-8"), source
="(internal pyxrc)")
236 user_pyxrc
= os
.path
.join(os
.environ
['APPDATA'], "pyxrc")
238 user_pyxrc
= os
.path
.expanduser("~/.pyxrc")
239 config
.read(user_pyxrc
, encoding
="utf-8")
241 def get(section
, option
, default
=_marker
):
242 if default
is _marker
:
243 return config
.get(section
, option
)
246 return config
.get(section
, option
)
247 except configparser
.Error
:
250 def getint(section
, option
, default
=_marker
):
251 if default
is _marker
:
252 return config
.getint(section
, option
)
255 return config
.getint(section
, option
)
256 except configparser
.Error
:
259 def getfloat(section
, option
, default
=_marker
):
260 if default
is _marker
:
261 return config
.getfloat(section
, option
)
264 return config
.getfloat(section
, option
)
265 except configparser
.Error
:
268 def getboolean(section
, option
, default
=_marker
):
269 if default
is _marker
:
270 return config
.getboolean(section
, option
)
273 return config
.getboolean(section
, option
)
274 except configparser
.Error
:
277 def getlist(section
, option
, default
=_marker
):
278 if default
is _marker
:
279 l
= config
.get(section
, option
).split()
282 l
= config
.get(section
, option
).split()
283 except configparser
.Error
:
286 l
= [item
.replace(space
, " ") for item
in l
]
290 space
= get("general", "space", "SPACE")
291 formatWarnings
= get("general", "warnings", "default")
292 if formatWarnings
not in ["default", "short", "shortest"]:
293 raise RuntimeError("invalid config value for option 'warnings' in section 'general'")
294 if formatWarnings
!= "default":
295 def formatwarning(message
, category
, filename
, lineno
, line
=None):
296 if formatWarnings
== "short":
297 return "%s:%s: %s: %s\n" % (filename
, lineno
, category
.__name
__, message
)
299 return "%s\n" % message
300 warnings
.formatwarning
= formatwarning
303 methods
= [locator_classes
[method
]()
304 for method
in getlist("filelocator", "methods", ["local", "internal", "pykpathsea", "kpsewhich"])]
308 def open(filename
, formats
, ascii
=False):
309 """returns an open file searched according the list of formats"""
311 # When using an empty list of formats, the names list is empty
312 # and the extensions list contains an empty string only. For that
313 # case some locators (notably local and internal) return an open
314 # function for the requested file whereas other locators might not
315 # return anything (like pykpathsea and kpsewhich).
316 # This is useful for files not to be searched in the latex
317 # installation at all (like lfs files).
318 extensions
= set([""])
319 for format
in formats
:
320 for extension
in format
.extensions
:
321 extensions
.add(extension
)
322 names
= tuple([format
.name
for format
in formats
])
323 if (filename
, names
) in opener_cache
:
324 file = opener_cache
[(filename
, names
)]()
326 for method
in methods
:
327 openers
= method
.openers(filename
, names
, extensions
)
328 for opener
in openers
:
331 except EnvironmentError:
334 opener_cache
[(filename
, names
)] = opener
336 # break two loops here
341 raise IOError("Could not locate the file '%s'." % filename
)
343 return io
.TextIOWrapper(file, encoding
="ascii", errors
="surrogateescape")
349 def __init__(self
, name
, extensions
):
351 self
.extensions
= extensions
353 format
.tfm
= format("tfm", [".tfm"])
354 format
.afm
= format("afm", [".afm"])
355 format
.fontmap
= format("map", [])
356 format
.pict
= format("graphic/figure", [".eps", ".epsi"])
357 format
.tex_ps_header
= format("PostScript header", [".pro"]) # contains also: enc files
358 format
.type1
= format("type1 fonts", [".pfa", ".pfb"])
359 format
.vf
= format("vf", [".vf"])
360 format
.dvips_config
= format("dvips config", [])