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
, io
, logging
, os
, pkgutil
, subprocess
, shutil
25 logger
= logging
.getLogger("pyx")
26 logger_execute
= logging
.getLogger("pyx.execute")
27 logger_filelocator
= logging
.getLogger("pyx.filelocator")
32 import pykpathsea
as pykpathsea_module
35 has_pykpathsea
= False
38 # Locators implement an open method which returns a list of functions
39 # by searching for a file according to a specific rule. Each of the functions
40 # returned can be called (multiple times) and return an open file. The
41 # opening of the file might fail with a IOError which indicates, that the
42 # file could not be found at the given location.
43 # names is a list of kpsewhich format names to be used for searching where as
44 # extensions is a list of file extensions to be tried (including the dot). Note
45 # that the list of file extenions should include an empty string to not add
46 # an extension at all.
51 """locates files in the current directory"""
53 def openers(self
, filename
, names
, extensions
):
54 return [lambda extension
=extension
: builtinopen(filename
+extension
, "rb") for extension
in extensions
]
56 locator_classes
["local"] = local
60 """locates files within the PyX data tree"""
62 def openers(self
, filename
, names
, extensions
):
63 for extension
in extensions
:
64 full_filename
= filename
+extension
65 dir = os
.path
.splitext(full_filename
)[1][1:]
67 data
= pkgutil
.get_data("pyx", "data/%s/%s" % (dir, full_filename
))
72 return [lambda: io
.BytesIO(data
)]
75 locator_classes
["internal"] = internal
79 """locates files by searching recursively in a list of directories"""
82 self
.dirs
= getlist("filelocator", "recursivedir")
83 self
.full_filenames
= {}
85 def openers(self
, filename
, names
, extensions
):
86 for extension
in extensions
:
87 if filename
+extension
in self
.full_filenames
:
88 return [lambda: builtinopen(self
.full_filenames
[filename
+extension
], "rb")]
90 dir = self
.dirs
.pop(0)
91 for item
in os
.listdir(dir):
92 full_item
= os
.path
.join(dir, item
)
93 if os
.path
.isdir(full_item
):
94 self
.dirs
.insert(0, full_item
)
96 self
.full_filenames
[item
] = full_item
97 for extension
in extensions
:
98 if filename
+extension
in self
.full_filenames
:
99 return [lambda: builtinopen(self
.full_filenames
[filename
+extension
], "rb")]
102 locator_classes
["recursivedir"] = recursivedir
106 """locates files by searching a list of ls-R files"""
109 self
.ls_Rs
= getlist("filelocator", "ls-R")
110 self
.full_filenames
= {}
112 def openers(self
, filename
, names
, extensions
):
113 while self
.ls_Rs
and not any([filename
+extension
in self
.full_filenames
for extension
in extensions
]):
114 lsr
= self
.ls_Rs
.pop(0)
115 base_dir
= os
.path
.dirname(lsr
)
118 with
builtinopen(lsr
, "r", encoding
="ascii", errors
="surrogateescape") as lsrfile
:
121 if first
and line
.startswith("%"):
124 if line
.endswith(":"):
125 dir = os
.path
.join(base_dir
, line
[:-1])
127 self
.full_filenames
[line
] = os
.path
.join(dir, line
)
128 for extension
in extensions
:
129 if filename
+extension
in self
.full_filenames
:
132 return builtinopen(self
.full_filenames
[filename
+extension
], "rb")
134 logger
.warning("'%s' should be available at '%s' according to the ls-R file, "
135 "but the file is not available at this location; "
136 "update your ls-R file" % (filename
, self
.full_filenames
[filename
+extension
]))
140 locator_classes
["ls-R"] = ls_R
144 """locate files by pykpathsea (a C extension module wrapping libkpathsea)"""
146 def openers(self
, filename
, names
, extensions
):
147 if not has_pykpathsea
:
150 full_filename
= pykpathsea_module
.find_file(filename
, name
)
157 return builtinopen(full_filename
, "rb")
159 logger
.warning("'%s' should be available at '%s' according to libkpathsea, "
160 "but the file is not available at this location; "
161 "update your kpsewhich database" % (filename
, full_filename
))
164 locator_classes
["pykpathsea"] = pykpathsea
168 # """locate files by libkpathsea using ctypes"""
170 # def openers(self, filename, names, extensions):
171 # raise NotImplemented
173 # locator_classes["libpathsea"] = libkpathsea
175 def Popen(cmd
, *args
, **kwargs
):
181 raise ValueError("pyx.config.Popen must not be used with a string cmd")
182 info
= "PyX executes {} with args {}".format(cmd
[0], cmd
[1:])
188 info
+= " located at {}".format(shutil
.which(cmd
[0]))
189 logger_execute
.info(info
)
190 return subprocess
.Popen(cmd
, *args
, **kwargs
)
192 PIPE
= subprocess
.PIPE
193 STDOUT
= subprocess
.STDOUT
196 def fix_cygwin(full_filename
):
197 # detect cygwin result on windows python
198 if os
.name
== "nt" and full_filename
.startswith("/"):
199 with
Popen(['cygpath', '-w', full_filename
], stdout
=subprocess
.PIPE
).stdout
as output
:
200 return io
.TextIOWrapper(output
, encoding
="ascii", errors
="surrogateescape").readline().rstrip()
205 """locate files using the kpsewhich executable"""
208 self
.kpsewhich
= get("filelocator", "kpsewhich", "kpsewhich")
210 def openers(self
, filename
, names
, extensions
):
214 with
Popen([self
.kpsewhich
, '--format', name
, filename
], stdout
=subprocess
.PIPE
).stdout
as output
:
215 with io
.TextIOWrapper(output
, encoding
="ascii", errors
="surrogateescape") as text_output
:
216 full_filename
= text_output
.readline().rstrip()
224 full_filename
= fix_cygwin(full_filename
)
228 return builtinopen(full_filename
, "rb")
230 logger
.warning("'%s' should be available at '%s' according to kpsewhich, "
231 "but the file is not available at this location; "
232 "update your kpsewhich database" % (filename
, full_filename
))
235 locator_classes
["kpsewhich"] = kpsewhich
239 """locate files using a locate executable"""
242 self
.locate
= get("filelocator", "locate", "locate")
244 def openers(self
, filename
, names
, extensions
):
246 for extension
in extensions
:
247 with
Popen([self
.locate
, filename
+extension
], stdout
=subprocess
.PIPE
).stdout
as output
:
248 with io
.TextIOWrapper(output
, encoding
="ascii", errors
="surrogateescape") as text_output
:
249 for line
in text_output
:
251 if os
.path
.basename(line
) == filename
+extension
:
259 full_filename
= fix_cygwin(full_filename
)
263 return builtinopen(full_filename
, "rb")
265 logger
.warning("'%s' should be available at '%s' according to the locate, "
266 "but the file is not available at this location; "
267 "update your locate database" % (filename
+extension
, full_filename
))
270 locator_classes
["locate"] = locate
276 config
= configparser
.ConfigParser()
277 config
.read_string(locator_classes
["internal"]().openers("pyxrc", [], [""])[0]().read().decode("utf-8"), source
="(internal pyxrc)")
279 user_pyxrc
= os
.path
.join(os
.environ
['APPDATA'], "pyxrc")
281 user_pyxrc
= os
.path
.expanduser("~/.pyxrc")
282 config
.read(user_pyxrc
, encoding
="utf-8")
284 def get(section
, option
, default
=_marker
):
285 if default
is _marker
:
286 return config
.get(section
, option
)
289 return config
.get(section
, option
)
290 except configparser
.Error
:
293 def getint(section
, option
, default
=_marker
):
294 if default
is _marker
:
295 return config
.getint(section
, option
)
298 return config
.getint(section
, option
)
299 except configparser
.Error
:
302 def getfloat(section
, option
, default
=_marker
):
303 if default
is _marker
:
304 return config
.getfloat(section
, option
)
307 return config
.getfloat(section
, option
)
308 except configparser
.Error
:
311 def getboolean(section
, option
, default
=_marker
):
312 if default
is _marker
:
313 return config
.getboolean(section
, option
)
316 return config
.getboolean(section
, option
)
317 except configparser
.Error
:
320 def getlist(section
, option
, default
=_marker
):
321 if default
is _marker
:
322 l
= config
.get(section
, option
).split()
325 l
= config
.get(section
, option
).split()
326 except configparser
.Error
:
329 l
= [item
.replace(space
, " ") for item
in l
]
333 space
= get("general", "space", "SPACE")
334 methods
= [locator_classes
[method
]()
335 for method
in getlist("filelocator", "methods", ["local", "internal", "pykpathsea", "kpsewhich"])]
339 def open(filename
, formats
, ascii
=False):
340 """returns an open file searched according the list of formats"""
342 # When using an empty list of formats, the names list is empty
343 # and the extensions list contains an empty string only. For that
344 # case some locators (notably local and internal) return an open
345 # function for the requested file whereas other locators might not
346 # return anything (like pykpathsea and kpsewhich).
347 # This is useful for files not to be searched in the latex
348 # installation at all (like lfs files).
349 extensions
= set([""])
350 for format
in formats
:
351 for extension
in format
.extensions
:
352 extensions
.add(extension
)
353 names
= tuple([format
.name
for format
in formats
])
354 if (filename
, names
) in opener_cache
:
355 file = opener_cache
[(filename
, names
)]()
357 for method
in methods
:
358 openers
= method
.openers(filename
, names
, extensions
)
359 for opener
in openers
:
362 except EnvironmentError:
365 info
= "PyX filelocator found {} by method {}".format(filename
, method
.__class
__.__name
__)
366 if hasattr(file, "name"):
367 info
+= " at {}".format(file.name
)
368 logger_filelocator
.info(info
)
369 opener_cache
[(filename
, names
)] = opener
371 # break two loops here
376 logger_filelocator
.info("PyX filelocator failed to find {} of type {} and extensions {}".format(filename
, names
, extensions
))
377 raise IOError("Could not locate the file '%s'." % filename
)
379 return io
.TextIOWrapper(file, encoding
="ascii", errors
="surrogateescape")
385 def __init__(self
, name
, extensions
):
387 self
.extensions
= extensions
389 format
.tfm
= format("tfm", [".tfm"])
390 format
.afm
= format("afm", [".afm"])
391 format
.fontmap
= format("map", [])
392 format
.pict
= format("graphic/figure", [".eps", ".epsi"])
393 format
.tex_ps_header
= format("PostScript header", [".pro"]) # contains also: enc files
394 format
.type1
= format("type1 fonts", [".pfa", ".pfb"])
395 format
.vf
= format("vf", [".vf"])
396 format
.dvips_config
= format("dvips config", [])