X-Git-Url: https://git.lyx.org/gitweb/?a=blobdiff_plain;f=lib%2Fscripts%2Flyxpak.py;h=bf66c70b6cc33d51859b294c2da98471c6df2fd0;hb=78ae4993559006dce1ef61defb14967d3bf421a2;hp=17ade0dab482a76a315b94b2c0747157ad3f85ab;hpb=1880933a87d2f9694106162dc0068beb58d81d31;p=lyx.git diff --git a/lib/scripts/lyxpak.py b/lib/scripts/lyxpak.py index 17ade0dab4..bf66c70b6c 100755 --- a/lib/scripts/lyxpak.py +++ b/lib/scripts/lyxpak.py @@ -1,4 +1,3 @@ -#! /usr/bin/env python # -*- coding: utf-8 -*- # file lyxpak.py @@ -16,20 +15,32 @@ # a gzip compressed tar archive on *nix. This can be controlled by command # line options, however. -import os, re, string, sys -if sys.version_info < (2, 4, 0): - from sets import Set as set +import gzip, os, re, string, sys from getopt import getopt +from cStringIO import StringIO +import subprocess + +# The path to the current python executable. sys.executable may fail, so in +# this case we revert to simply calling "python" from the path. +PYTHON_BIN = sys.executable if sys.executable else "python" + +running_on_windows = (os.name == 'nt') + +if running_on_windows: + from shutil import copyfile + from tempfile import NamedTemporaryFile # Pre-compiled regular expressions. re_lyxfile = re.compile("\.lyx$") -re_input = re.compile(r'^(.*)\\(input|include){(\s*)(\S+)(\s*)}.*$') -re_package = re.compile(r'^(.*)\\(usepackage){(\s*)(\S+)(\s*)}.*$') -re_class = re.compile(r'^(\\)(textclass)(\s+)(\S+)$') -re_norecur = re.compile(r'^(.*)\\(verbatiminput|lstinputlisting|includegraphics\[*.*\]*){(\s*)(\S+)(\s*)}.*$') -re_filename = re.compile(r'^(\s*)(filename)(\s+)(\S+)$') -re_options = re.compile(r'^(\s*)options(\s+)(\S+)$') -re_bibfiles = re.compile(r'^(\s*)bibfiles(\s+)(\S+)$') +re_input = re.compile(r'^(.*)\\(input|include){(\s*)(.+)(\s*)}.*$') +re_ertinput = re.compile(r'^(input|include)({)(\s*)(.+)(\s*)}.*$') +re_package = re.compile(r'^(.*)\\(usepackage){(\s*)(.+)(\s*)}.*$') +re_class = re.compile(r'^(\\)(textclass)(\s+)(.+)\s*$') +re_norecur = re.compile(r'^(.*)\\(verbatiminput|lstinputlisting|includegraphics\[*.*\]*){(\s*)(.+)(\s*)}.*$') +re_ertnorecur = re.compile(r'^(verbatiminput|lstinputlisting|includegraphics\[*.*\]*)({)(\s*)(.+)(\s*)}.*$') +re_filename = re.compile(r'^(\s*)(filename)(\s+)(.+)\s*$') +re_options = re.compile(r'^(\s*)options(\s+)(.+)\s*$') +re_bibfiles = re.compile(r'^(\s*)bibfiles(\s+)(.+)\s*$') def usage(prog_name): @@ -52,11 +63,13 @@ def error(message): sys.exit(1) -def run_cmd(cmd): - handle = os.popen(cmd, 'r') - cmd_stdout = handle.read() - cmd_status = handle.close() - return cmd_status, cmd_stdout +def gzopen(file, mode): + input = open(unicode(file, 'utf-8'), 'rb') + magicnum = input.read(2) + input.close() + if magicnum == "\x1f\x8b": + return gzip.open(unicode(file, 'utf-8'), mode) + return open(unicode(file, 'utf-8'), mode) def find_exe(candidates, extlist, path): @@ -72,7 +85,7 @@ def find_exe(candidates, extlist, path): def abspath(name): " Resolve symlinks and returns the absolute normalized name." newname = os.path.normpath(os.path.abspath(name)) - if os.name != 'nt': + if not running_on_windows: newname = os.path.realpath(newname) return newname @@ -81,17 +94,34 @@ def gather_files(curfile, incfiles, lyx2lyx): " Recursively gather files." curdir = os.path.dirname(abspath(curfile)) is_lyxfile = re_lyxfile.search(curfile) + if is_lyxfile: - lyx2lyx_cmd = 'python "%s" "%s"' % (lyx2lyx, curfile) - l2l_status, l2l_stdout = run_cmd(lyx2lyx_cmd) - if l2l_status != None: - error('%s failed to convert "%s"' % (lyx2lyx, curfile)) + if running_on_windows: + # os.popen cannot cope with unicode arguments and we cannot be + # sure that curfile can be correctly converted to the current + # code page. So, we resort to running lyx2lyx on a copy. + tmp = NamedTemporaryFile(delete=False) + tmp.close() + copyfile(unicode(curfile, 'utf-8'), tmp.name) + try: + l2l_stdout = subprocess.check_output([PYTHON_BIN, lyx2lyx, tmp.name]) + except subprocess.CalledProcessError: + error('%s failed to convert "%s"' % (lyx2lyx, curfile)) + os.unlink(tmp.name) + else: + try: + l2l_stdout = subprocess.check_output([PYTHON_BIN, lyx2lyx, curfile]) + except subprocess.CalledProcessError: + error('%s failed to convert "%s"' % (lyx2lyx, curfile)) + if l2l_stdout.startswith("\x1f\x8b"): + l2l_stdout = gzip.GzipFile("", "r", 0, StringIO(l2l_stdout)).read() lines = l2l_stdout.splitlines() else: - input = open(curfile, 'rU') + input = gzopen(curfile, 'rU') lines = input.readlines() input.close() + maybe_in_ert = False i = 0 while i < len(lines): # Gather used files. @@ -99,7 +129,10 @@ def gather_files(curfile, incfiles, lyx2lyx): extlist = [''] match = re_filename.match(lines[i]) if not match: - match = re_input.match(lines[i]) + if maybe_in_ert: + match = re_ertinput.match(lines[i]) + else: + match = re_input.match(lines[i]) if not match: match = re_package.match(lines[i]) extlist = ['.sty'] @@ -107,20 +140,25 @@ def gather_files(curfile, incfiles, lyx2lyx): match = re_class.match(lines[i]) extlist = ['.cls'] if not match: - match = re_norecur.match(lines[i]) + if maybe_in_ert: + match = re_ertnorecur.match(lines[i]) + else: + match = re_norecur.match(lines[i]) extlist = ['', '.eps', '.pdf', '.png', '.jpg'] recursive = False + maybe_in_ert = is_lyxfile and lines[i] == "\\backslash" if match: file = match.group(4).strip('"') if not os.path.isabs(file): file = os.path.join(curdir, file) file_exists = False - for ext in extlist: - if os.path.exists(file + ext): - file = file + ext - file_exists = True - break - if file_exists: + if not os.path.isdir(unicode(file, 'utf-8')): + for ext in extlist: + if os.path.exists(unicode(file + ext, 'utf-8')): + file = file + ext + file_exists = True + break + if file_exists and not abspath(file) in incfiles: incfiles.append(abspath(file)) if recursive: gather_files(file, incfiles, lyx2lyx) @@ -135,9 +173,11 @@ def gather_files(curfile, incfiles, lyx2lyx): match = re_options.match(lines[i]) if match: file = match.group(3).strip('"') + if file.startswith("bibtotoc,"): + file = file[9:] if not os.path.isabs(file): file = os.path.join(curdir, file + '.bst') - if os.path.exists(file): + if os.path.exists(unicode(file, 'utf-8')): incfiles.append(abspath(file)) i += 1 continue @@ -149,10 +189,10 @@ def gather_files(curfile, incfiles, lyx2lyx): j = 0 while j < len(bibfiles): if os.path.isabs(bibfiles[j]): - file = bibfiles[j] + file = bibfiles[j] + '.bib' else: file = os.path.join(curdir, bibfiles[j] + '.bib') - if os.path.exists(file): + if os.path.exists(unicode(file, 'utf-8')): incfiles.append(abspath(file)) j += 1 i += 1 @@ -163,7 +203,7 @@ def gather_files(curfile, incfiles, lyx2lyx): return 0 -def find_lyx2lyx(progloc): +def find_lyx2lyx(progloc, path): " Find a usable version of the lyx2lyx script. " # first we will see if the script is roughly where we are # i.e., we will assume we are in $SOMEDIR/scripts and look @@ -181,8 +221,9 @@ def find_lyx2lyx(progloc): lyx_exe, full_path = find_exe(["lyxc", "lyx"], extlist, path) if lyx_exe == None: error('Cannot find the LyX executable in the path.') - cmd_status, cmd_stdout = run_cmd("%s -version 2>&1" % lyx_exe) - if cmd_status != None: + try: + cmd_stdout = subprocess.check_output([lyx_exe, '-version'], stderr=subprocess.STDOUT) + except subprocess.CalledProcessError: error('Cannot query LyX about the lyx2lyx script.') re_msvc = re.compile(r'^(\s*)(Host type:)(\s+)(win32)$') re_sysdir = re.compile(r'^(\s*)(LyX files dir:)(\s+)(\S+)$') @@ -218,7 +259,7 @@ def main(args): if len(argv) != 1: error(usage(ourprog)) - makezip = (os.name == 'nt') + makezip = running_on_windows outdir = "" lyx2lyx = None @@ -234,15 +275,17 @@ def main(args): lyx2lyx = param elif opt == "-o": outdir = param - if not os.path.isdir(outdir): + if not os.path.isdir(unicode(outdir, 'utf-8')): error('Error: "%s" is not a directory.' % outdir) lyxfile = argv[0] - if not os.path.exists(lyxfile): + if not running_on_windows: + lyxfile = unicode(lyxfile, sys.getfilesystemencoding()).encode('utf-8') + if not os.path.exists(unicode(lyxfile, 'utf-8')): error('File "%s" not found.' % lyxfile) # Check that it actually is a LyX document - input = open(lyxfile, 'rU') + input = gzopen(lyxfile, 'rU') line = input.readline() input.close() if not (line and line.startswith('#LyX')): @@ -264,7 +307,7 @@ def main(args): path = string.split(os.environ["PATH"], os.pathsep) if lyx2lyx == None: - lyx2lyx = find_lyx2lyx(ourprog) + lyx2lyx = find_lyx2lyx(ourprog, path) # Initialize the list with the specified LyX file and recursively # gather all required files (also from child documents). @@ -274,6 +317,9 @@ def main(args): # Find the topmost dir common to all files if len(incfiles) > 1: topdir = os.path.commonprefix(incfiles) + # As os.path.commonprefix() works on a character by character basis, + # rather than on path elements, we need to remove any trailing bytes. + topdir = topdir.rpartition(os.path.sep)[0] + os.path.sep else: topdir = os.path.dirname(incfiles[0]) + os.path.sep @@ -288,14 +334,14 @@ def main(args): incfiles.sort() if topdir != '': - os.chdir(topdir) + os.chdir(unicode(topdir, 'utf-8')) # Create the archive try: if makezip: zip = zipfile.ZipFile(ar_name, "w", zipfile.ZIP_DEFLATED) for file in incfiles: - zip.write(file) + zip.write(file.decode('utf-8'), unicode(file, 'utf-8')) zip.close() else: tar = tarfile.open(ar_name, "w:gz") @@ -310,4 +356,31 @@ def main(args): if __name__ == "__main__": + if running_on_windows: + # This works around for Python 2. + # All arguments are retrieved in unicode format and converted to utf-8. + # In this way, when launched from the command line, lyxpak.py can deal + # with any non-ascii names. Unfortunately, this is not the case when + # launched by LyX, because LyX converts the arguments of the converters + # to the filesystem encoding. On Windows this corresponds to the current + # code page and not to the UTF-16 encoding used by NTFS, such that they + # are transliterated if not exactly encodable. As an example, α may + # become a, β may become ß, and so on. However, this is a problem only + # if the full path of the LyX document contains an unencodable character + # as all other paths are extracted from the document in utf-8 format. + from ctypes import WINFUNCTYPE, windll, POINTER, byref, c_int + from ctypes.wintypes import LPWSTR, LPCWSTR + GetCommandLineW = WINFUNCTYPE(LPWSTR)(("GetCommandLineW", windll.kernel32)) + CommandLineToArgvW = WINFUNCTYPE(POINTER(LPWSTR), LPCWSTR, POINTER(c_int))(("CommandLineToArgvW", windll.shell32)) + argc = c_int(0) + argv_unicode = CommandLineToArgvW(GetCommandLineW(), byref(argc)) + # unicode_argv[0] is the Python interpreter, so skip that. + argv = [argv_unicode[i].encode('utf-8') for i in xrange(1, argc.value)] + # Also skip option arguments to the Python interpreter. + while len(argv) > 0: + if not argv[0].startswith("-"): + break + argv = argv[1:] + sys.argv = argv + main(sys.argv)