]> git.lyx.org Git - lyx.git/blob - lib/configure.py
Improved copy operation for user directory contents of previous major releases
[lyx.git] / lib / configure.py
1 #! /usr/bin/python3
2 # -*- coding: utf-8 -*-
3 #
4 # file configure.py
5 # This file is part of LyX, the document processor.
6 # Licence details can be found in the file COPYING.
7
8 # \author Bo Peng
9 # Full author contact details are available in file CREDITS.
10
11 from __future__ import print_function
12 import glob, logging, os, errno, re, shutil, subprocess, sys, stat
13
14 if sys.version_info[0] < 3:
15     import codecs
16     open = codecs.open
17
18
19 # set up logging
20 logging.basicConfig(level = logging.DEBUG,
21     format = '%(levelname)s: %(message)s', # ignore application name
22     filename = 'configure.log',
23     filemode = 'w')
24 #
25 # Add a handler to log to console
26 console = logging.StreamHandler()
27 console.setLevel(logging.INFO) # the console only print out general information
28 formatter = logging.Formatter('%(message)s') # only print out the message itself
29 console.setFormatter(formatter)
30 logger = logging.getLogger('LyX')
31 logger.addHandler(console)
32
33 def quoteIfSpace(name):
34     " utility function: quote name if it contains spaces "
35     if ' ' in name:
36         return '"' + name + '"'
37     else:
38         return name
39
40 def writeToFile(filename, lines, append = False):
41     " utility function: write or append lines to filename "
42     if append:
43         file = open(filename, 'a')
44     else:
45         file = open(filename, 'w')
46     file.write(lines)
47     file.close()
48
49
50 def addToRC(lines):
51     ''' utility function: shortcut for appending lines to outfile
52         add newline at the end of lines.
53     '''
54     if lines.strip():
55         writeToFile(outfile, lines + '\n', append = True)
56         logger.debug('Add to RC:\n' + lines + '\n\n')
57
58
59 def removeFiles(filenames):
60     '''utility function: 'rm -f'
61         ignore errors when file does not exist, or is a directory.
62     '''
63     for file in filenames:
64         try:
65             os.remove(file)
66             logger.debug('Removing file %s' % file)
67         except OSError as e:
68             if e.errno == errno.ENOENT: # no such file or directory
69                 logger.debug('No need to remove file %s (it does not exists)' % file)
70             elif e.errno == errno.EISDIR: # is a directory
71                 logger.debug('Failed to remove file %s (it is a directory)' % file)
72             else:
73                 logger.debug('Failed to remove file %s' % file)
74             pass
75
76
77 def cmdOutput(cmd, asynchronous = False):
78     '''utility function: run a command and get its output as a string
79         cmd: command to run
80         asynchronous: if False, return whole output as a string, otherwise
81                return the stdout handle from which the output can be
82                read (the caller is then responsible for closing it)
83     '''
84     if os.name == 'nt':
85         b = False
86         if sys.version_info[0] < 3:
87             cmd = 'cmd /d /c pushd ' + shortPath(os.getcwdu()) + '&' + cmd
88         else:
89             cmd = 'cmd /d /c pushd ' + shortPath(os.getcwd()) + '&' + cmd
90     else:
91         b = True
92     pipe = subprocess.Popen(cmd, shell=b, close_fds=b, stdin=subprocess.PIPE,
93                             stdout=subprocess.PIPE, universal_newlines=True)
94     pipe.stdin.close()
95     if asynchronous:
96         return pipe.stdout
97     output = pipe.stdout.read()
98     pipe.stdout.close()
99     return output.strip()
100
101
102 def shortPath(path):
103     ''' On Windows, return the short version of "path" if possible '''
104     if os.name == 'nt':
105         from ctypes import windll, create_unicode_buffer
106         GetShortPathName = windll.kernel32.GetShortPathNameW
107         shortlen = GetShortPathName(path, 0, 0)
108         shortpath = create_unicode_buffer(shortlen)
109         if GetShortPathName(path, shortpath, shortlen):
110             return shortpath.value
111     return path
112
113
114 def setEnviron():
115     ''' I do not really know why this is useful, but we might as well keep it.
116         NLS nuisances.
117         Only set these to C if already set.  These must not be set unconditionally
118         because not all systems understand e.g. LANG=C (notably SCO).
119         Fixing LC_MESSAGES prevents Solaris sh from translating var values in set!
120         Non-C LC_CTYPE values break the ctype check.
121     '''
122     os.environ['LANG'] = os.getenv('LANG', 'C')
123     os.environ['LC'] = os.getenv('LC_ALL', 'C')
124     os.environ['LC_MESSAGE'] = os.getenv('LC_MESSAGE', 'C')
125     os.environ['LC_CTYPE'] = os.getenv('LC_CTYPE', 'C')
126
127
128 def copy_tree(src, dst, preserve_symlinks=False, level=0):
129     ''' Copy an entire directory tree 'src' to a new location 'dst'.
130
131     Code inspired from distutils.copy_tree.
132          Copying ignores non-regular files and the cache directory.
133     Pipes may be present as leftovers from LyX for lyx-server.
134
135     If 'preserve_symlinks' is true, symlinks will be
136     copied as symlinks (on platforms that support them!); otherwise
137     (the default), the destination of the symlink will be copied.
138     '''
139
140     if not os.path.isdir(src):
141         raise FileError("cannot copy tree '%s': not a directory" % src)
142     try:
143         names = os.listdir(src)
144     except os.error as oserror:
145         (errno, errstr) = oserror.args
146         raise FileError("error listing files in '%s': %s" % (src, errstr))
147
148     if not os.path.isdir(dst):
149         os.makedirs(dst)
150
151     outputs = []
152
153     for name in names:
154         src_name = os.path.join(src, name)
155         dst_name = os.path.join(dst, name)
156         if preserve_symlinks and os.path.islink(src_name):
157             link_dest = os.readlink(src_name)
158             os.symlink(link_dest, dst_name)
159             outputs.append(dst_name)
160         elif level == 0 and name in [ 'cache', 'configure.log', 'chkconfig.ltx' ]:
161             logger.info("Skip copy of %s", src_name)
162         elif os.path.isdir(src_name):
163             outputs.extend(
164                 copy_tree(src_name, dst_name, preserve_symlinks, level=(level + 1)))
165         elif stat.S_ISREG(os.stat(src_name).st_mode) or os.path.islink(src_name):
166             shutil.copy2(src_name, dst_name)
167             outputs.append(dst_name)
168         else:
169             logger.info("Ignore non-regular file %s", src_name)
170
171     return outputs
172
173
174 def checkUpgrade():
175     ''' Check for upgrade from previous version '''
176     cwd = os.getcwd()
177     basename = os.path.basename( cwd )
178     lyxrc = os.path.join(cwd, outfile)
179     if not os.path.isfile( lyxrc ) and basename.endswith( version_suffix ) :
180         logger.info('Checking for upgrade from previous version.')
181         parent = os.path.dirname(cwd)
182         appname = basename[:(-len(version_suffix))]
183         for version in ['-2.3', '-2.2', '-2.1', '-2.0', '-1.6' ]:
184             logger.debug('Checking for upgrade from previous version ' + version)
185             previous = os.path.join(parent, appname + version)
186             logger.debug('previous = ' + previous)
187             if os.path.isdir( previous ):
188                 logger.info('Found directory "%s".', previous)
189                 copy_tree( previous, cwd, True )
190                 logger.info('Content copied from directory "%s".', previous)
191                 return
192
193
194 def createDirectories():
195     ''' Create the build directories if necessary '''
196     for dir in ['bind', 'clipart', 'doc', 'examples', 'images', 'kbd',
197         'layouts', 'scripts', 'templates', 'ui' ]:
198         if not os.path.isdir( dir ):
199             try:
200                 os.mkdir( dir)
201                 logger.debug('Create directory %s.' % dir)
202             except:
203                 logger.error('Failed to create directory %s.' % dir)
204                 sys.exit(1)
205
206
207 def checkTeXPaths():
208     ''' Determine the path-style needed by the TeX engine on Win32 (Cygwin) '''
209     windows_style_tex_paths = ''
210     if LATEX == '':
211         return windows_style_tex_paths
212     if os.name == 'nt' or sys.platform == 'cygwin':
213         from tempfile import mkstemp
214         fd, tmpfname = mkstemp(suffix='.ltx')
215         if os.name == 'nt':
216             encoding = sys.getfilesystemencoding()
217             if sys.version_info[0] < 3:
218                 inpname = shortPath(unicode(tmpfname, encoding)).replace('\\', '/')
219             else:
220                 inpname = shortPath(tmpfname).replace('\\', '/')
221         else:
222             inpname = cmdOutput('cygpath -m ' + tmpfname)
223         logname = os.path.basename(re.sub("(?i).ltx", ".log", inpname))
224         inpname = inpname.replace('~', '\\string~')
225         os.write(fd, b'\\relax')
226         os.close(fd)
227         latex_out = cmdOutput(r'latex "\nonstopmode\input{%s}\makeatletter\@@end"' % inpname)
228         if 'Error' in latex_out:
229             latex_out = cmdOutput(r'latex "\nonstopmode\input{\"%s\"}\makeatletter\@@end"' % inpname)
230         if 'Error' in latex_out:
231             logger.warning("configure: TeX engine needs posix-style paths in latex files")
232             windows_style_tex_paths = 'false'
233         else:
234             logger.info("configure: TeX engine needs windows-style paths in latex files")
235             windows_style_tex_paths = 'true'
236         removeFiles([tmpfname, logname, 'texput.log'])
237     return windows_style_tex_paths
238
239
240 ## Searching some useful programs
241 def checkProg(description, progs, rc_entry=None, path=None, not_found =''):
242     '''
243         This function will search a program in $PATH plus given path
244         If found, return directory and program name (not the options).
245
246         description: description of the program
247
248         progs: check programs, for each prog, the first word is used
249             for searching but the whole string is used to replace
250             %% for a rc_entry. So, feel free to add '$$i' etc for programs.
251
252         path: additional paths (will be prepended to the program name)
253
254         rc_entry: entry to outfile, can be
255             1. emtpy: no rc entry will be added
256             2. one pattern: %% will be replaced by the first found program,
257                 or '' if no program is found.
258             3. several patterns for each prog and not_found. This is used
259                 when different programs have different usages. If you do not
260                 want not_found entry to be added to the RC file, you can specify
261                 an entry for each prog and use '' for the not_found entry.
262
263         not_found: the value that should be used instead of '' if no program
264             was found
265
266     '''
267     if path is None:
268         path = []
269     if rc_entry is None:
270         rc_entry = []
271
272     # one rc entry for each progs plus not_found entry
273     if len(rc_entry) > 1 and len(rc_entry) != len(progs) + 1:
274         logger.error("rc entry should have one item or item "
275                      "for each prog and not_found.")
276         sys.exit(2)
277     logger.info('checking for ' + description + '...')
278     logger.debug('(' + ','.join(progs) + ')')
279     additional_path = path
280     path = os.environ["PATH"].split(os.pathsep) + additional_path
281     extlist = ['']
282     if "PATHEXT" in os.environ:
283         extlist = extlist + os.environ["PATHEXT"].split(os.pathsep)
284     global java, perl
285     unquoted_space = re.compile(r'''((?:[^ "']|"[^"]*"|'[^']*')+)''')
286     for idx in range(len(progs)):
287         # ac_prog may have options, ac_word is the command name
288         ac_prog = progs[idx].replace('"', '\\"')
289         ac_word = unquoted_space.split(progs[idx])[1::2][0].strip('"')
290         if (ac_word.endswith('.class') or ac_word.endswith('.jar')) and java == '':
291             continue
292         if ac_word.endswith('.pl') and perl == '':
293             continue
294         msg = '+checking for "' + ac_word + '"... '
295         for ac_dir in path:
296             if hasattr(os, "access") and not os.access(ac_dir, os.F_OK):
297                 continue
298             for ext in extlist:
299                 if os.path.isfile( os.path.join(ac_dir, ac_word + ext) ):
300                     logger.info(msg + ' yes')
301                     # deal with java and perl
302                     if ac_word.endswith('.class'):
303                         ac_prog = ac_prog.replace(ac_word, r'%s \"%s\"'
304                                     % (java, os.path.join(ac_dir, ac_word[:-6])))
305                     elif ac_word.endswith('.jar'):
306                         ac_prog = ac_prog.replace(ac_word, r'%s -jar \"%s\"'
307                                     % (java, os.path.join(ac_dir, ac_word)))
308                     elif ac_word.endswith('.pl'):
309                         ac_prog = ac_prog.replace(ac_word, r'%s -w \"%s\"'
310                                     % (perl, os.path.join(ac_dir, ac_word)))
311                     elif ac_dir in additional_path:
312                         ac_prog = ac_prog.replace(ac_word, r'\"%s\"'
313                                     % (os.path.join(ac_dir, ac_word)))
314                     # write rc entries for this command
315                     if len(rc_entry) == 1:
316                         addToRC(rc_entry[0].replace('%%', ac_prog))
317                     elif len(rc_entry) > 1:
318                         addToRC(rc_entry[idx].replace('%%', ac_prog))
319                     return [ac_dir, ac_word]
320         # if not successful
321         logger.info(msg + ' not in path')
322     # write rc entries for 'not found'
323     if len(rc_entry) > 0:  # the last one.
324         addToRC(rc_entry[-1].replace('%%', not_found))
325     return ['', not_found]
326
327
328 def check_java():
329     """ Check for Java, don't give up as often as checkProg, using platform-dependent techniques """
330     if os.name == 'nt':
331         # Check in the registry.
332         try:  # Python 3.
333             import winreg
334         except ImportError:  # Python 2.
335             import _winreg as winreg
336
337         potential_keys_64b = ["SOFTWARE\\JavaSoft\\Java Runtime Environment", "SOFTWARE\\JavaSoft\\Java Development Kit",
338                               "SOFTWARE\\JavaSoft\\JDK", "SOFTWARE\\JavaSoft\\JRE"]
339         potential_keys_32b = [k.replace('SOFTWARE', 'SOFTWARE\\WOW6432Node') for k in potential_keys_64b]
340         potential_keys = potential_keys_64b + potential_keys_32b
341
342         reg_hive = winreg.HKEY_LOCAL_MACHINE
343         for key in potential_keys:
344             try:
345                 with winreg.OpenKey(reg_hive, key) as reg_key:
346                     version = winreg.QueryValueEx(reg_key, "CurrentVersion")[0]
347                 with winreg.OpenKey(reg_hive, key + '\\' + version) as reg_key:
348                     java_bin = winreg.QueryValueEx(reg_key, "JavaHome")[0] + '\\bin\\java.exe'
349                     logger.info('+checking for java: found in Windows registry, ' + str(java_bin))
350                     return java_bin
351             except OSError:
352                 pass
353
354         # The test failed, no Java found.
355         return ''
356     else:
357         return ''
358
359
360 def checkMacOSappInstalled(prog):
361     '''
362         Use metadata lookup to search for an "installed" macOS application bundle.
363     '''
364     if sys.platform == 'darwin' and len(prog) >= 1:
365         command = r'mdfind "kMDItemContentTypeTree == \"com.apple.application\"c && kMDItemFSName == \"%s\""' % prog
366         result = cmdOutput(command)
367         logger.debug(command + ": " + result)
368         return result != ''
369     return False
370
371
372 def checkProgAlternatives(description, progs, rc_entry=None,
373                           alt_rc_entry=None, path=None, not_found=''):
374     '''
375         The same as checkProg, but additionally, all found programs will be added
376         as alt_rc_entries
377     '''
378     if path is None:
379         path = []
380     if alt_rc_entry is None:
381         alt_rc_entry = []
382     if rc_entry is None:
383         rc_entry = []
384
385     # one rc entry for each progs plus not_found entry
386     if len(rc_entry) > 1 and len(rc_entry) != len(progs) + 1:
387         logger.error("rc entry should have one item or item for each prog and not_found.")
388         sys.exit(2)
389     logger.info('checking for ' + description + '...')
390     logger.debug('(' + ','.join(progs) + ')')
391     additional_path = path
392     path = os.environ["PATH"].split(os.pathsep) + additional_path
393     extlist = ['']
394     if "PATHEXT" in os.environ:
395         extlist = extlist + os.environ["PATHEXT"].split(os.pathsep)
396     found_prime = False
397     real_ac_dir = ''
398     real_ac_word = not_found
399     global java, perl
400     for idx in range(len(progs)):
401         # ac_prog may have options, ac_word is the command name
402         ac_prog = progs[idx]
403         ac_word = ac_prog.split(' ')[0]
404         if (ac_word.endswith('.class') or ac_word.endswith('.jar')) and java == '':
405             continue
406         if ac_word.endswith('.pl') and perl == '':
407             continue
408         msg = '+checking for "' + ac_word + '"... '
409         found_alt = False
410         if len(alt_rc_entry) >= 1 and ac_word.endswith('.app') and checkMacOSappInstalled(ac_word):
411             logger.info('+add alternative app ' + ac_word)
412             addToRC(alt_rc_entry[0].replace('%%', ac_word))
413             found_alt = True
414         for ac_dir in path:
415             if hasattr(os, "access") and not os.access(ac_dir, os.F_OK):
416                 continue
417             for ext in extlist:
418                 if os.path.isfile( os.path.join(ac_dir, ac_word + ext) ):
419                     logger.info(msg + ' yes')
420                     pr = re.compile(r'(\\\S+)(.*)$')
421                     m = None
422                     # deal with java and perl
423                     if ac_word.endswith('.class'):
424                         ac_prog = ac_prog.replace(ac_word, r'%s \"%s\"'
425                                     % (java, os.path.join(ac_dir, ac_word[:-6])))
426                     elif ac_word.endswith('.jar'):
427                         ac_prog = ac_prog.replace(ac_word, r'%s -jar \"%s\"'
428                                     % (java, os.path.join(ac_dir, ac_word)))
429                     elif ac_word.endswith('.pl'):
430                         ac_prog = ac_prog.replace(ac_word, r'%s -w \"%s\"'
431                                     % (perl, os.path.join(ac_dir, ac_word)))
432                     elif ac_dir in additional_path:
433                         ac_prog = ac_prog.replace(ac_word, r'\"%s\"'
434                                     % (os.path.join(ac_dir, ac_word)))
435                     # write rc entries for this command
436                     if found_prime == False:
437                         if len(rc_entry) == 1:
438                             addToRC(rc_entry[0].replace('%%', ac_prog))
439                         elif len(rc_entry) > 1:
440                             addToRC(rc_entry[idx].replace('%%', ac_prog))
441                         real_ac_dir = ac_dir
442                         real_ac_word = ac_word
443                         found_prime = True
444                     if len(alt_rc_entry) == 1:
445                         alt_rc = alt_rc_entry[0]
446                         if alt_rc == "":
447                             # if no explicit alt_rc is given, construct one
448                             m = pr.match(rc_entry[0])
449                             if m:
450                                 alt_rc = m.group(1) + "_alternatives" + m.group(2)
451                         addToRC(alt_rc.replace('%%', ac_prog))
452                     elif len(alt_rc_entry) > 1:
453                         alt_rc = alt_rc_entry[idx]
454                         if alt_rc == "":
455                             # if no explicit alt_rc is given, construct one
456                             m = pr.match(rc_entry[idx])
457                             if m:
458                                 alt_rc = m.group(1) + "_alternatives" + m.group(2)
459                         addToRC(alt_rc.replace('%%', ac_prog))
460                     found_alt = True
461                     break
462             if found_alt:
463                 break
464         if not found_alt:
465             # if not successful
466             logger.info(msg + ' no')
467     if found_prime:
468         return [real_ac_dir, real_ac_word]
469     # write rc entries for 'not found'
470     if len(rc_entry) > 0:  # the last one.
471         addToRC(rc_entry[-1].replace('%%', not_found))
472     return ['', not_found]
473
474
475 def addAlternatives(rcs, alt_type):
476     '''
477         Returns a \\prog_alternatives string to be used as an alternative
478         rc entry.  alt_type can be a string or a list of strings.
479     '''
480     r = re.compile(r'\\Format (\S+).*$')
481     m = None
482     alt = ''
483     alt_token = '\\%s_alternatives '
484     if isinstance(alt_type, str):
485         alt_tokens = [alt_token % alt_type]
486     else:
487         alt_tokens = [alt_token % s for s in alt_type]
488     for idxx in range(len(rcs)):
489         if len(rcs) == 1:
490             m = r.match(rcs[0])
491             if m:
492                 alt = '\n'.join([s + m.group(1) + ' "%%"' for s in alt_tokens])
493         elif len(rcs) > 1:
494             m = r.match(rcs[idxx])
495             if m:
496                 if idxx > 0:
497                     alt += '\n'
498                 alt += '\n'.join([s + m.group(1) + ' "%%"' for s in alt_tokens])
499     return alt
500
501
502 def listAlternatives(progs, alt_type, rc_entry=None):
503     '''
504         Returns a list of \\prog_alternatives strings to be used as alternative
505         rc entries.  alt_type can be a string or a list of strings.
506     '''
507     if rc_entry is None:
508         rc_entry = []
509
510     if len(rc_entry) > 1 and len(rc_entry) != len(progs) + 1:
511         logger.error("rc entry should have one item or item for each prog and not_found.")
512         sys.exit(2)
513     alt_rc_entry = []
514     for idx in range(len(progs)):
515         if len(rc_entry) == 1:
516             rcs = rc_entry[0].split('\n')
517             alt = addAlternatives(rcs, alt_type)
518             alt_rc_entry.insert(0, alt)
519         elif len(rc_entry) > 1:
520             rcs = rc_entry[idx].split('\n')
521             alt = addAlternatives(rcs, alt_type)
522             alt_rc_entry.insert(idx, alt)
523     return alt_rc_entry
524
525
526 def checkViewer(description, progs, rc_entry=None, path=None):
527     ''' The same as checkProgAlternatives, but for viewers '''
528     if path is None:
529         path = []
530     if rc_entry is None:
531         rc_entry = []
532
533     alt_rc_entry = listAlternatives(progs, 'viewer', rc_entry)
534     return checkProgAlternatives(description, progs, rc_entry,
535                                  alt_rc_entry, path, not_found = 'auto')
536
537
538 def checkEditor(description, progs, rc_entry=None, path=None):
539     ''' The same as checkProgAlternatives, but for editors '''
540     if path is None:
541         path = []
542     if rc_entry is None:
543         rc_entry = []
544
545     alt_rc_entry = listAlternatives(progs, 'editor', rc_entry)
546     return checkProgAlternatives(description, progs, rc_entry,
547                                  alt_rc_entry, path, not_found = 'auto')
548
549
550 def checkViewerNoRC(description, progs, rc_entry=None, path=None):
551     ''' The same as checkViewer, but do not add rc entry '''
552     if path is None:
553         path = []
554     if rc_entry is None:
555         rc_entry = []
556
557     alt_rc_entry = listAlternatives(progs, 'viewer', rc_entry)
558     rc_entry = []
559     return checkProgAlternatives(description, progs, rc_entry,
560                                  alt_rc_entry, path, not_found = 'auto')
561
562
563 def checkEditorNoRC(description, progs, rc_entry=None, path=None):
564     ''' The same as checkEditor, but do not add rc entry '''
565     if rc_entry is None:
566         rc_entry = []
567     if path is None:
568         path = []
569
570     alt_rc_entry = listAlternatives(progs, 'editor', rc_entry)
571     rc_entry = []
572     return checkProgAlternatives(description, progs, rc_entry,
573                                  alt_rc_entry, path, not_found = 'auto')
574
575
576 def checkViewerEditor(description, progs, rc_entry=None, path=None):
577     ''' The same as checkProgAlternatives, but for viewers and editors '''
578     if rc_entry is None:
579         rc_entry = []
580     if path is None:
581         path = []
582
583     alt_rc_entry = listAlternatives(progs, ['editor', 'viewer'], rc_entry)
584     return checkProgAlternatives(description, progs, rc_entry,
585                                  alt_rc_entry, path, not_found = 'auto')
586
587
588 def checkDTLtools():
589     ''' Check whether DTL tools are available (Windows only) '''
590     # Find programs! Returned path is not used now
591     if ((os.name == 'nt' or sys.platform == 'cygwin') and
592             checkProg('DVI to DTL converter', ['dv2dt']) != ['', ''] and
593             checkProg('DTL to DVI converter', ['dt2dv']) != ['', '']):
594         dtl_tools = True
595     else:
596         dtl_tools = False
597     return dtl_tools
598
599 def checkInkscape():
600     ''' Check whether Inkscape is available and return the full path (Windows only) '''
601     ''' On Mac OS (darwin) a wrapper is used - therefore the version is checked '''
602     ''' The answer of the real inkscape is validated and a fake binary used if this fails '''
603     if sys.platform == 'darwin':
604         version_string = cmdOutput("inkscape --version")
605         if version_string.startswith('Inkscape'):
606             return 'inkscape'
607         else:
608             return 'inkscape-binary'
609     elif os.name != 'nt':
610         return 'inkscape'
611     if sys.version_info[0] < 3:
612         import _winreg as winreg
613     else:
614         import winreg
615     aReg = winreg.ConnectRegistry(None, winreg.HKEY_CLASSES_ROOT)
616     try:
617         aKey = winreg.OpenKey(aReg, r"inkscape.svg\DefaultIcon")
618         val = winreg.QueryValueEx(aKey, "")
619         valentry = str(val[0])
620         if valentry.find('"') > 0:
621             return valentry.split('"')[1]
622         elif valentry.find(',') > 0:
623             return valentry.split(',')[0]
624         else:
625             return 'inkscape'
626     except EnvironmentError:
627         try:
628             aKey = winreg.OpenKey(aReg, r"inkscape.SVG\shell\open\command")
629             val = winreg.QueryValueEx(aKey, "")
630             return str(val[0]).split('"')[1]
631         except EnvironmentError:
632             try:
633                 aKey = winreg.OpenKey(aReg, r"Applications\inkscape.exe\shell\open\command")
634                 val = winreg.QueryValueEx(aKey, "")
635                 return str(val[0]).split('"')[1]
636             except EnvironmentError:
637                 return 'inkscape'
638
639
640 def checkInkscapeStable():
641     ''' Check whether we use Inkscape >= 1.0 '''
642     inkscape_bin = inkscape_cl
643     if os.name == 'nt':
644         # Windows needs the full path, quoted if it contains spaces
645         inkscape_bin = quoteIfSpace(os.path.join(inkscape_path, inkscape_cl))
646     version_string = cmdOutput(inkscape_bin + " --version")
647     if version_string.find(' 0.') > 0:
648         return False
649     else:
650         return True
651
652
653 def checkLatex(dtl_tools):
654     ''' Check latex, return lyx_check_config '''
655     path, LATEX = checkProg('a Latex2e program', ['latex $$i', 'latex2e $$i'])
656     #-----------------------------------------------------------------
657     path, PLATEX = checkProg('pLaTeX, the Japanese LaTeX', ['platex $$i'])
658     if PLATEX:
659         # check if PLATEX is pLaTeX2e
660         writeToFile('chklatex.ltx', r'\nonstopmode\makeatletter\@@end')
661         # run platex on chklatex.ltx and check result
662         if cmdOutput(PLATEX + ' chklatex.ltx').find('pLaTeX2e') != -1:
663             # We have the Japanese pLaTeX2e
664             addToRC(r'\converter platex   dvi       "%s"   "latex=platex"' % PLATEX)
665         else:
666             PLATEX = ''
667             removeFiles(['chklatex.ltx', 'chklatex.log'])
668     #-----------------------------------------------------------------
669     if dtl_tools:
670         # Windows only: DraftDVI
671         addToRC(r'''\converter latex      dvi2       "%s"       "latex,hyperref-driver=dvips"
672 \converter dvi2       dvi        "$${python} $$s/scripts/clean_dvi.py $$i $$o"  ""''' % LATEX)
673     else:
674         addToRC(r'\converter latex      dvi        "%s" "latex,hyperref-driver=dvips"' % LATEX)
675     # no latex
676     if LATEX:
677         # Check if latex is usable
678         writeToFile('chklatex.ltx', r'''
679 \nonstopmode
680 \ifx\undefined\documentclass\else
681   \message{ThisIsLaTeX2e}
682 \fi
683 \makeatletter
684 \@@end
685 ''')
686         # run latex on chklatex.ltx and check result
687         if cmdOutput(LATEX + ' chklatex.ltx').find('ThisIsLaTeX2e') != -1:
688             # valid latex2e
689             return LATEX
690         else:
691             logger.warning("Latex not usable (not LaTeX2e) ")
692         # remove temporary files
693         removeFiles(['chklatex.ltx', 'chklatex.log'])
694     return ''
695
696
697 def checkLuatex():
698     ''' Check if luatex is there '''
699     path, LUATEX = checkProg('LuaTeX', ['lualatex $$i'])
700     path, DVILUATEX = checkProg('LuaTeX (DVI)', ['dvilualatex $$i'])
701     if LUATEX:
702         addToRC(r'\converter luatex      pdf5       "%s"        "latex=lualatex"' % LUATEX)
703     if DVILUATEX:
704         addToRC(r'\converter dviluatex   dvi3        "%s"       "latex=dvilualatex"' % DVILUATEX)
705
706
707 def checkModule(module):
708     ''' Check for a Python module, return the status '''
709     msg = 'checking for "' + module + ' module"... '
710     try:
711       __import__(module)
712       logger.info(msg + ' yes')
713       return True
714     except ImportError:
715       logger.info(msg + ' no')
716       return False
717
718
719 texteditors = ['xemacs', 'gvim', 'kedit', 'kwrite', 'kate',
720                'nedit', 'gedit', 'geany', 'leafpad', 'mousepad',
721                'xed', 'notepad', 'WinEdt', 'WinShell', 'PSPad']
722
723 def checkFormatEntries(dtl_tools):
724     r''' Check all formats (\Format entries) '''
725     checkViewerEditor('a Tgif viewer and editor', ['tgif'],
726         rc_entry = [r'\Format tgif      "obj, tgo" Tgif                 "" "%%" "%%"    "vector"        "application/x-tgif"'])
727     #
728     checkViewerEditor('a FIG viewer and editor', ['xfig', 'jfig3-itext.jar', 'jfig3.jar'],
729         rc_entry = [r'\Format fig        fig     FIG                    "" "%%" "%%"    "vector"        "application/x-xfig"'])
730     #
731     checkViewerEditor('a Dia viewer and editor', ['dia'],
732         rc_entry = [r'\Format dia        dia     DIA                    "" "%%" "%%"    "vector,zipped=native", "application/x-dia-diagram"'])
733     #
734     checkViewerEditor('an OpenDocument drawing viewer and editor', ['libreoffice', 'lodraw', 'ooffice', 'oodraw', 'soffice'],
735         rc_entry = [r'\Format odg        "odg, sxd" "OpenDocument drawing"   "" "%%"    "%%"    "vector,zipped=native"  "application/vnd.oasis.opendocument.graphics"'])
736     #
737     checkViewerEditor('a Grace viewer and editor', ['xmgrace'],
738         rc_entry = [r'\Format agr        agr     Grace                  "" "%%" "%%"    "vector"        ""'])
739     #
740     checkViewerEditor('a FEN viewer and editor', ['xboard -lpf $$i -mode EditPosition'],
741         rc_entry = [r'\Format fen        fen     FEN                    "" "%%" "%%"    ""      ""'])
742     #
743     checkViewerEditor('a SVG viewer and editor', [inkscape_gui],
744         rc_entry = [r'''\Format svg        "svg" SVG                "" "%%" "%%"        "vector"        "image/svg+xml"
745 \Format svgz       "svgz" "SVG (compressed)" "" "%%" "%%"       "vector,zipped=native"  ""'''],
746         path = [inkscape_path])
747     #
748     imageformats = r'''\Format bmp        bmp     BMP                    "" "%s"        "%s"    ""      "image/x-bmp"
749 \Format gif        gif     GIF                    "" "%s"       "%s"    ""      "image/gif"
750 \Format jpg       "jpg, jpeg" JPEG                "" "%s"       "%s"    ""      "image/jpeg"
751 \Format pbm        pbm     PBM                    "" "%s"       "%s"    ""      "image/x-portable-bitmap"
752 \Format pgm        pgm     PGM                    "" "%s"       "%s"    ""      "image/x-portable-graymap"
753 \Format png        png     PNG                    "" "%s"       "%s"    ""      "image/x-png"
754 \Format ppm        ppm     PPM                    "" "%s"       "%s"    ""      "image/x-portable-pixmap"
755 \Format tiff       tif     TIFF                   "" "%s"       "%s"    ""      "image/tiff"
756 \Format xbm        xbm     XBM                    "" "%s"       "%s"    ""      "image/x-xbitmap"
757 \Format xpm        xpm     XPM                    "" "%s"       "%s"    ""      "image/x-xpixmap"'''
758     path, iv = checkViewerNoRC('a raster image viewer',
759         ['xv', 'gwenview', 'kview',
760          'eog', 'xviewer', 'ristretto', 'gpicview', 'lximage-qt',
761          'xdg-open', 'gimp-remote', 'gimp'],
762         rc_entry = [imageformats])
763     path, ie = checkEditorNoRC('a raster image editor',
764         ['gimp-remote', 'gimp'], rc_entry = [imageformats])
765     addToRC(imageformats % ((iv, ie)*10))
766     #
767     checkViewerEditor('a text editor', texteditors,
768         rc_entry = [r'''\Format asciichess asc    "Plain text (chess output)"  "" ""    "%%"    ""      ""
769 \Format docbook5   xml    "DocBook 5"             "" "" "%%"    "document,menu=export"  "application/docbook+xml"
770 \Format dot        dot    "Graphviz Dot"          "" "" "%%"    "vector"        "text/vnd.graphviz"
771 \Format dviluatex  tex    "LaTeX (dviluatex)"     "" "" "%%"    "document,menu=export"  ""
772 \Format epub       epub    ePub                   "" "" "%%"    "document,menu=export"  "application/epub+zip"
773 \Format platex     tex    "LaTeX (pLaTeX)"        "" "" "%%"    "document,menu=export"  ""
774 \Format literate   nw      NoWeb                  N  "" "%%"    "document,menu=export"  ""
775 \Format sweave     Rnw    "Sweave"                S  "" "%%"    "document,menu=export"  ""
776 \Format sweave-ja  Rnw    "Sweave (Japanese)"     S  "" "%%"    "document,menu=export"  ""
777 \Format r          R      "R/S code"              "" "" "%%"    "document,menu=export"  ""
778 \Format knitr      Rnw    "Rnw (knitr)"           "" "" "%%"    "document,menu=export"  ""
779 \Format knitr-ja   Rnw    "Rnw (knitr, Japanese)" "" "" "%%"    "document,menu=export"  ""
780 \Format lilypond-book    lytex "LilyPond book (LaTeX)"   "" ""  "%%"    "document,menu=export"  ""
781 \Format lilypond-book-ja lytex "LilyPond book (pLaTeX)"   "" "" "%%"    "document,menu=export"  ""
782 \Format latex      tex    "LaTeX (plain)"         L  "" "%%"    "document,menu=export"  "text/x-tex"
783 \Format luatex     tex    "LaTeX (LuaTeX)"        "" "" "%%"    "document,menu=export"  ""
784 \Format pdflatex   tex    "LaTeX (pdflatex)"      "" "" "%%"    "document,menu=export"  ""
785 \Format xetex      tex    "LaTeX (XeTeX)"         "" "" "%%"    "document,menu=export"  ""
786 \Format latexclipboard tex "LaTeX (clipboard)"    "" "" "%%"    "menu=none"     ""
787 \Format text       txt    "Plain text"            a  "" "%%"    "document,menu=export"  "text/plain"
788 \Format text2      txt    "Plain text (pstotext)" "" "" "%%"    "document"      ""
789 \Format text3      txt    "Plain text (ps2ascii)" "" "" "%%"    "document"      ""
790 \Format text4      txt    "Plain text (catdvi)"   "" "" "%%"    "document"      ""
791 \Format textparagraph txt "Plain Text, Join Lines" "" ""        "%%"    "document"      ""
792 \Format beamer.info pdf.info   "Info (Beamer)"         "" ""   "%%"    "document,menu=export"   ""''' ])
793    #Lilypond files have special editors, but fall back to plain text editors
794     checkViewerEditor('a lilypond editor',
795         ['frescobaldi'] + texteditors,
796         rc_entry = [r'''\Format lilypond   ly     "LilyPond music"        "" "" "%%"    "vector"        "text/x-lilypond"''' ])
797    #Spreadsheets using ssconvert from gnumeric
798     checkViewer('gnumeric spreadsheet software', ['gnumeric'],
799       rc_entry = [r'''\Format gnumeric gnumeric "Gnumeric spreadsheet" "" ""    "%%"   "document"       "application/x-gnumeric"
800 \Format excel      xls    "Excel spreadsheet"      "" "" "%%"    "document"     "application/vnd.ms-excel"
801 \Format excel2     xlsx   "MS Excel Office Open XML" "" "" "%%" "document"      "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
802 \Format xhtml_table xhtml "XHTML Table (for spreadsheets)"     "" "" "%%"    "document" ""
803 \Format html_table html   "HTML Table (for spreadsheets)"      "" "" "%%"    "document" ""
804 \Format oocalc     ods    "OpenDocument spreadsheet" "" "" "%%"    "document"   "application/vnd.oasis.opendocument.spreadsheet"'''])
805  #
806     checkViewer('an HTML previewer', ['firefox', 'mozilla file://$$p$$i', 'netscape'],
807         rc_entry = [r'\Format xhtml      xhtml   "LyXHTML"              y "%%" ""    "document,menu=export"     "application/xhtml+xml"'])
808  #
809     checkEditor('a BibTeX editor', ['jabref', 'JabRef',
810         'pybliographic', 'bibdesk', 'gbib', 'kbib',
811         'kbibtex', 'sixpack', 'bibedit', 'tkbibtex', 'TeXnicCenter'] +
812         texteditors,
813         rc_entry = [r'''\Format bibtex bib    "BibTeX"         "" ""    "%%"    ""      "text/x-bibtex"''' ])
814     #
815     #checkProg('a Postscript interpreter', ['gs'],
816     #  rc_entry = [ r'\ps_command "%%"' ])
817     checkViewer('a Postscript previewer',
818                 ['kghostview', 'okular', 'qpdfview --unique',
819                  'evince', 'xreader',
820                  'gv', 'ghostview -swap', 'gsview64', 'gsview32'],
821         rc_entry = [r'''\Format eps        eps     EPS                    "" "%%"       ""      "vector"        "image/x-eps"
822 \Format eps2       eps    "EPS (uncropped)"       "" "%%"       ""      "vector"        ""
823 \Format eps3       eps    "EPS (cropped)"         "" "%%"       ""      "document"      ""
824 \Format ps         ps      Postscript             t  "%%"       ""      "document,vector,menu=export"   "application/postscript"'''])
825     # for xdg-open issues look here: http://www.mail-archive.com/lyx-devel@lists.lyx.org/msg151818.html
826     # maybe use "bestApplication()" from https://github.com/jleclanche/python-mime
827     # the MIME type is set for pdf6, because that one needs to be autodetectable by libmime
828     checkViewer('a PDF previewer',
829                 ['pdfview', 'kpdf', 'okular', 'qpdfview --unique',
830                  'evince', 'xreader', 'kghostview', 'xpdf', 'SumatraPDF',
831                  'acrobat', 'acroread', 'mupdf', 'Skim.app',
832                  'gv', 'ghostview', 'AcroRd32', 'gsview64', 'gsview32'],
833         rc_entry = [r'''\Format pdf        pdf    "PDF (ps2pdf)"          P  "%%"       ""      "document,vector,menu=export"   ""
834 \Format pdf2       pdf    "PDF (pdflatex)"        F  "%%"       ""      "document,vector,menu=export"   ""
835 \Format pdf3       pdf    "PDF (dvipdfm)"         m  "%%"       ""      "document,vector,menu=export"   ""
836 \Format pdf4       pdf    "PDF (XeTeX)"           X  "%%"       ""      "document,vector,menu=export"   ""
837 \Format pdf5       pdf    "PDF (LuaTeX)"          u  "%%"       ""      "document,vector,menu=export"   ""
838 \Format pdf6       pdf    "PDF (graphics)"        "" "%%"       ""      "vector"        "application/pdf"
839 \Format pdf7       pdf    "PDF (cropped)"         "" "%%"       ""      "document,vector"       ""
840 \Format pdf8       pdf    "PDF (lower resolution)"         "" "%%"      ""      "document,vector"       ""
841 \Format pdf9       pdf    "PDF (DocBook)"         "" "%%"       ""      "document,vector,menu=export"   ""'''])
842     #
843     checkViewer('a DVI previewer', ['xdvi', 'kdvi', 'okular',
844                                     'evince', 'xreader',
845                                     'yap', 'dviout -Set=!m'],
846         rc_entry = [r'''\Format dvi        dvi     DVI                    D  "%%"       ""      "document,vector,menu=export"   "application/x-dvi"
847 \Format dvi3       dvi     "DVI (LuaTeX)"          V  "%%"      ""      "document,vector,menu=export"   ""'''])
848     if dtl_tools:
849         # Windows only: DraftDVI
850         addToRC(r'\Format dvi2       dvi     DraftDVI               ""  ""      ""      "vector"        ""')
851     #
852     checkViewer('an HTML previewer', ['firefox', 'mozilla file://$$p$$i', 'netscape'],
853         rc_entry = [r'\Format html      "html, htm" HTML                H  "%%" ""      "document,menu=export"  "text/html"'])
854     #
855     checkViewerEditor('Noteedit', ['noteedit'],
856         rc_entry = [r'\Format noteedit   not     Noteedit               "" "%%" "%%"    "vector"        ""'])
857     #
858     checkViewerEditor('an OpenDocument viewer', ['libreoffice', 'lwriter', 'lowriter', 'oowriter', 'swriter', 'abiword'],
859         rc_entry = [r'''\Format odt        odt     "OpenDocument (tex4ht)"  "" "%%"     "%%"    "document,vector,menu=export"   "application/vnd.oasis.opendocument.text"
860 \Format odt2       odt    "OpenDocument (eLyXer)"  "" "%%"      "%%"    "document,vector,menu=export"   "application/vnd.oasis.opendocument.text"
861 \Format odt3       odt    "OpenDocument (Pandoc)"  "" "%%"      "%%"    "document,vector,menu=export"   "application/vnd.oasis.opendocument.text"
862 \Format sxw        sxw    "OpenOffice.Org (sxw)"  "" "" ""      "document,vector"       "application/vnd.sun.xml.writer"'''])
863     #
864     checkViewerEditor('a Rich Text and Word viewer', ['libreoffice', 'lwriter', 'lowriter', 'oowriter', 'swriter', 'abiword'],
865         rc_entry = [r'''\Format rtf        rtf    "Rich Text Format"      "" "%%"       "%%"    "document,vector,menu=export"   "application/rtf"
866 \Format word       doc    "MS Word"               W  "%%"       "%%"    "document,vector,menu=export"   "application/msword"
867 \Format word2      docx    "MS Word Office Open XML"               O  "%%"      "%%"    "document,vector,menu=export"   "application/vnd.openxmlformats-officedocument.wordprocessingml.document"'''])
868     #
869     # entries that do not need checkProg
870     addToRC(r'''\Format csv        csv    "Table (CSV)"           "" "" ""      "document"      "text/csv"
871 \Format fax        ""      Fax                    "" "" ""      "document"      ""
872 \Format lyx        lyx     LyX                    "" "" ""      ""      "application/x-lyx"
873 \Format lyx13x     13.lyx "LyX 1.3.x"             "" "" ""      "document"      ""
874 \Format lyx14x     14.lyx "LyX 1.4.x"             "" "" ""      "document"      ""
875 \Format lyx15x     15.lyx "LyX 1.5.x"             "" "" ""      "document"      ""
876 \Format lyx16x     16.lyx "LyX 1.6.x"             "" "" ""      "document"      ""
877 \Format lyx20x     20.lyx "LyX 2.0.x"             "" "" ""      "document"      ""
878 \Format lyx21x     21.lyx "LyX 2.1.x"             "" "" ""      "document"      ""
879 \Format lyx22x     22.lyx "LyX 2.2.x"             "" "" ""      "document"      ""
880 \Format lyx23x     23.lyx "LyX 2.3.x"             "" "" ""      "document,menu=export"  ""
881 \Format clyx       cjklyx "CJK LyX 1.4.x (big5)"  "" "" ""      "document"      ""
882 \Format jlyx       cjklyx "CJK LyX 1.4.x (euc-jp)" "" ""        ""      "document"      ""
883 \Format klyx       cjklyx "CJK LyX 1.4.x (euc-kr)" "" ""        ""      "document"      ""
884 \Format lyxpreview lyxpreview "LyX Preview"       "" "" ""      ""      ""
885 \Format pdftex     "pdftex_t, pdf_tex" PDFTEX                "" ""      ""      ""      ""
886 \Format program    ""      Program                "" "" ""      ""      ""
887 \Format pstex      "pstex_t, ps_tex" PSTEX                  "" ""       ""      ""      ""
888 \Format wmf        wmf    "Windows Metafile"      "" "" ""      "vector"        "image/x-wmf"
889 \Format emf        emf    "Enhanced Metafile"     "" "" ""      "vector"        "image/x-emf"
890 \Format wordhtml  "html, htm" "HTML (MS Word)"    "" "" ""      "document"      ""
891 ''')
892
893
894 def checkConverterEntries():
895     r''' Check all converters (\converter entries) '''
896     checkProg('the pdflatex program', ['pdflatex $$i'],
897         rc_entry = [ r'\converter pdflatex   pdf2       "%%"    "latex=pdflatex,hyperref-driver=pdftex"' ])
898
899     checkProg('XeTeX', ['xelatex $$i'],
900         rc_entry = [ r'\converter xetex      pdf4       "%%"    "latex=xelatex,hyperref-driver=xetex"' ])
901
902     checkLuatex()
903
904     # Look for tex2lyx in this order (see bugs #3308 and #6986):
905     #   1)  If we're building LyX with autotools then tex2lyx is found
906     #       in the subdirectory tex2lyx with respect to the binary dir.
907     #   2)  If we're building LyX with cmake then tex2lyx is found
908     #       in the binary dir.
909     #   3)  If LyX was configured with a version suffix then tex2lyx
910     #       will also have this version suffix.
911     #   4)  Otherwise always use tex2lyx.
912     in_binary_subdir = os.path.join(lyx_binary_dir, 'tex2lyx', 'tex2lyx')
913     in_binary_subdir = os.path.abspath(in_binary_subdir).replace('\\', '/')
914
915     in_binary_dir = os.path.join(lyx_binary_dir, 'tex2lyx')
916     in_binary_dir = os.path.abspath(in_binary_dir).replace('\\', '/')
917
918     path, t2l = checkProg('a LaTeX/Noweb -> LyX converter', [quoteIfSpace(in_binary_subdir), quoteIfSpace(in_binary_subdir + version_suffix), quoteIfSpace(in_binary_dir), quoteIfSpace(in_binary_dir + version_suffix), 'tex2lyx' + version_suffix, 'tex2lyx'],
919         rc_entry = [r'''\converter latex      lyx        "%% -f $$i $$o"        ""
920 \converter latexclipboard lyx        "%% -fixedenc utf8 -c $$c -m $$m -f $$i $$o"       ""
921 \converter literate   lyx        "%% -n -m noweb -f $$i $$o"    ""
922 \converter sweave   lyx        "%% -n -m sweave -f $$i $$o"     ""
923 \converter knitr   lyx        "%% -n -m knitr -f $$i $$o"       ""'''], not_found = 'tex2lyx')
924     if path == '':
925         logger.warning("Failed to find tex2lyx on your system.")
926
927     #
928     checkProg('a Noweb -> LaTeX converter', ['noweave -delay -index $$i > $$o'],
929         rc_entry = [r'''\converter literate   latex      "%%"   ""
930 \converter literate   pdflatex      "%%"        ""
931 \converter literate   xetex         "%%"        ""
932 \converter literate   luatex        "%%"        ""
933 \converter literate   dviluatex     "%%"        ""'''])
934     #
935     checkProg('a Sweave -> LaTeX converter', ['Rscript --verbose --no-save --no-restore $$s/scripts/lyxsweave.R $$p$$i $$p$$o $$e $$r'],
936         rc_entry = [r'''\converter sweave   latex      "%%"     "needauth"
937 \converter sweave   pdflatex   "%%"     "needauth"
938 \converter sweave-ja   platex     "%%"  "needauth"
939 \converter sweave   xetex      "%%"     "needauth"
940 \converter sweave   luatex     "%%"     "needauth"
941 \converter sweave   dviluatex  "%%"     "needauth"'''])
942     #
943     checkProg('a knitr -> LaTeX converter', ['Rscript --verbose --no-save --no-restore $$s/scripts/lyxknitr.R $$p$$i $$p$$o $$e $$r'],
944         rc_entry = [r'''\converter knitr   latex      "%%"      "needauth"
945 \converter knitr   pdflatex   "%%"      "needauth"
946 \converter knitr-ja   platex     "%%"   "needauth"
947 \converter knitr   xetex      "%%"      "needauth"
948 \converter knitr   luatex     "%%"      "needauth"
949 \converter knitr   dviluatex  "%%"      "needauth"'''])
950     #
951     checkProg('a Sweave -> R/S code converter', ['Rscript --verbose --no-save --no-restore $$s/scripts/lyxstangle.R $$i $$e $$r'],
952         rc_entry = [ r'\converter sweave      r      "%%"    ""',
953                      r'\converter sweave-ja   r      "%%"    ""' ])
954     #
955     checkProg('a knitr -> R/S code converter', ['Rscript --verbose --no-save --no-restore $$s/scripts/lyxknitr.R $$p$$i $$p$$o $$e $$r tangle'],
956         rc_entry = [ r'\converter knitr      r      "%%"    ""',
957                      r'\converter knitr-ja   r      "%%"    ""' ])
958     #
959     checkProg('an HTML -> LaTeX converter', ['html2latex $$i', 'gnuhtml2latex',
960         'htmltolatex -input $$i -output $$o', 'htmltolatex.jar -input $$i -output $$o'],
961         rc_entry = [ r'\converter html       latex      "%%"    ""',
962                      r'\converter html       latex      "$${python} $$s/scripts/html2latexwrapper.py %% $$i $$o"        ""',
963                      r'\converter html       latex      "%%"    ""',
964                      r'\converter html       latex      "%%"    ""', '' ])
965     #
966     checkProg('an MS Word -> LaTeX converter', ['wvCleanLatex $$i $$o'],
967         rc_entry = [ r'\converter word       latex      "%%"    ""' ])
968
969     # eLyXer: search as an executable (elyxer.py, elyxer)
970     path, elyxer = checkProg('a LyX -> HTML converter',
971         ['elyxer.py --nofooter --directory $$r $$i $$o', 'elyxer --nofooter --directory $$r $$i $$o'],
972         rc_entry = [ r'\converter lyx      html       "%%"      ""' ])
973     path, elyxer = checkProg('a LyX -> HTML (MS Word) converter',
974         ['elyxer.py --nofooter --html --directory $$r $$i $$o', 'elyxer --nofooter --html --directory $$r $$i $$o'],
975         rc_entry = [ r'\converter lyx      wordhtml       "%%"  ""' ])
976     path, elyxer = checkProg('a LyX -> OpenDocument (eLyXer) converter',
977         ['elyxer.py --html --nofooter --unicode --directory $$r $$i $$o', 'elyxer --html --nofooter --unicode --directory $$r $$i $$o'],
978         rc_entry = [ r'\converter lyx      odt2       "%%"      ""' ])
979     path, elyxer = checkProg('a LyX -> Word converter',
980         ['elyxer.py --html --nofooter --unicode --directory $$r $$i $$o', 'elyxer --html --nofooter --unicode --directory $$r $$i $$o'],
981         rc_entry = [ r'\converter lyx      word      "%%"       ""' ])
982     if elyxer.find('elyxer') >= 0:
983       addToRC(r'''\copier    html       "$${python} $$s/scripts/ext_copy.py -e html,png,jpg,jpeg,css $$i $$o"''')
984       addToRC(r'''\copier    wordhtml       "$${python} $$s/scripts/ext_copy.py -e html,png,jpg,jpeg,css $$i $$o"''')
985     else:
986       # search for HTML converters other than eLyXer
987       # On SuSE the scripts have a .sh suffix, and on debian they are in /usr/share/tex4ht/
988       path, htmlconv = checkProg('a LaTeX -> HTML converter', ['htlatex $$i', 'htlatex.sh $$i',
989           '/usr/share/tex4ht/htlatex $$i', 'tth  -t -e2 -L$$b < $$i > $$o',
990           'latex2html -no_subdir -split 0 -show_section_numbers $$i', 'hevea -s $$i'],
991           rc_entry = [ r'\converter latex      html       "%%"  "needaux"' ])
992       if htmlconv.find('htlatex') >= 0 or htmlconv == 'latex2html':
993         addToRC(r'''\copier    html       "$${python} $$s/scripts/ext_copy.py -e html,png,css $$i $$o"''')
994       else:
995         addToRC(r'''\copier    html       "$${python} $$s/scripts/ext_copy.py $$i $$o"''')
996       path, htmlconv = checkProg('a LaTeX -> HTML (MS Word) converter', ["htlatex $$i 'html,word' 'symbol/!' '-cvalidate'",
997           "htlatex.sh $$i 'html,word' 'symbol/!' '-cvalidate'",
998           "/usr/share/tex4ht/htlatex $$i 'html,word' 'symbol/!' '-cvalidate'"],
999           rc_entry = [ r'\converter latex      wordhtml   "%%"  "needaux"' ])
1000       if htmlconv.find('htlatex') >= 0:
1001         addToRC(r'''\copier    wordhtml       "$${python} $$s/scripts/ext_copy.py -e html,png,css $$i $$o"''')
1002       else:
1003         addToRC(r'''\copier    wordhtml       "$${python} $$s/scripts/ext_copy.py $$i $$o"''')
1004
1005
1006     # Check if LyXBlogger is installed
1007     lyxblogger_found = checkModule('lyxblogger')
1008     if lyxblogger_found:
1009       addToRC(r'\Format    blog       blog       "LyXBlogger"           "" "" ""  "document"  ""')
1010       addToRC(r'\converter xhtml      blog       "python -m lyxblogger $$i"       ""')
1011
1012     #
1013     checkProg('an OpenOffice.org -> LaTeX converter', ['w2l -clean $$i'],
1014         rc_entry = [ r'\converter sxw        latex      "%%"    ""' ])
1015     #
1016     checkProg('an OpenDocument -> LaTeX converter', ['w2l -clean $$i'],
1017         rc_entry = [ r'\converter odt        latex      "%%"    ""' ])
1018     #
1019     checkProg('an Open Document (Pandoc) -> LaTeX converter', ['pandoc -s -f odt -o $$o -t latex $$i'],
1020         rc_entry = [ r'\converter odt3        latex      "%%"   ""' ])
1021     #
1022     checkProg('DocBook converter -> PDF (docbook)',
1023               ['pandoc -f docbook -t latex --pdf-engine=lualatex --toc -o $$o $$i',  # Since Pandoc 2.0
1024                'pandoc -f docbook -t latex --latex-engine=lualatex --toc -o $$o $$i'],  # Up to Pandoc 1.19
1025               rc_entry = [ r'\converter docbook5      pdf9      "%%"    ""' ])
1026     #
1027     xpath, xslt_sheet = checkProg('XSLT stylesheets for ePub', ['chunk.xsl'], '', ['/usr/share/xml/docbook/stylesheet/docbook-xsl-ns/epub3'])
1028     if xslt_sheet == 'chunk.xsl':
1029         xpath = '/usr/share/xml/docbook/stylesheet/docbook-xsl-ns'
1030     else:
1031         xpath = 'none'
1032     global java
1033     if xsltproc != '':
1034         addToRC(r'\converter docbook5 epub "$${python} $$s/scripts/docbook2epub.py none none \"' + xsltproc + r'\" ' + xpath + ' $$i $$r $$o" ""')
1035     elif java != '':
1036         addToRC(r'\converter docbook5 epub "$${python} $$s/scripts/docbook2epub.py \"' + java + r'\" none none ' + xpath + ' $$i $$r $$o" ""')
1037     #
1038     checkProg('a MS Word Office Open XML converter -> LaTeX', ['pandoc -s -f docx -o $$o -t latex $$i'],
1039         rc_entry = [ r'\converter word2      latex      "%%"    ""' ])
1040     # Only define a converter to pdf6, otherwise the odt format could be
1041     # used as an intermediate step for export to pdf, which is not wanted.
1042     checkProg('an OpenDocument -> PDF converter', ['unoconv -f pdf --stdout $$i > $$o'],
1043         rc_entry = [ r'\converter odt        pdf6       "%%"    ""' ])
1044     # According to http://www.tug.org/applications/tex4ht/mn-commands.html
1045     # the command mk4ht oolatex $$i has to be used as default,
1046     # but as this would require to have Perl installed, in MiKTeX oolatex is
1047     # directly available as application.
1048     # On SuSE the scripts have a .sh suffix, and on debian they are in /usr/share/tex4ht/
1049     # Both SuSE and debian have oolatex
1050     checkProg('a LaTeX -> Open Document (tex4ht) converter', [
1051         'oolatex $$i', 'make4ht -f odt $$i', 'oolatex.sh $$i', '/usr/share/tex4ht/oolatex $$i',
1052         'htlatex $$i \'xhtml,ooffice\' \'ooffice/! -cmozhtf\' \'-coo\' \'-cvalidate\''],
1053         rc_entry = [ r'\converter latex      odt        "%%"    "needaux"' ])
1054     # On windows it is called latex2rt.exe
1055     checkProg('a LaTeX -> RTF converter', ['latex2rtf -p -S -o $$o $$i', 'latex2rt -p -S -o $$o $$i'],
1056         rc_entry = [ r'\converter latex      rtf        "%%"    "needaux"' ])
1057     #
1058     checkProg('a LaTeX -> Open Document (Pandoc) converter', ['pandoc -s -f latex -o $$o -t odt $$i'],
1059         rc_entry = [ r'\converter latex      odt3        "%%"   ""' ])
1060     #
1061     checkProg('a LaTeX -> MS Word Office Open XML converter', ['pandoc -s -f latex -o $$o -t docx $$i'],
1062         rc_entry = [ r'\converter latex      word2       "%%"   ""' ])
1063     #
1064     checkProg('a RTF -> HTML converter', ['unrtf --html  $$i > $$o'],
1065         rc_entry = [ r'\converter rtf      html        "%%"     ""' ])
1066     # Do not define a converter to pdf6, ps is a pure export format
1067     checkProg('a PS to PDF converter', ['ps2pdf -dALLOWPSTRANSPARENCY $$i $$o'],
1068         rc_entry = [ r'\converter ps         pdf        "%%"    "hyperref-driver=dvips"' ])
1069     #
1070     checkProg('a PS to TXT converter', ['pstotext $$i > $$o'],
1071         rc_entry = [ r'\converter ps         text2      "%%"    ""' ])
1072     #
1073     checkProg('a PS to TXT converter', ['ps2ascii $$i $$o'],
1074         rc_entry = [ r'\converter ps         text3      "%%"    ""' ])
1075     # Need to call ps2eps in a pipe, otherwise it would name the output file
1076     # depending on the extension of the input file. We do not know the input
1077     # file extension in general, so the resultfile= flag would not help.
1078     # Since ps2eps crops the image, we do not use it to convert from ps->eps.
1079     # This would create additional paths in the converter graph with unwanted
1080     # side effects (e.g. ps->pdf via ps2pdf would create a different result
1081     # than ps->eps->pdf via ps2eps and epstopdf).
1082     checkProg('a PS to EPS converter', ['ps2eps -- < $$i > $$o'],
1083         rc_entry = [ r'\converter eps2       eps      "%%"      ""' ])
1084     #
1085     checkProg('a PDF to PS converter', ['pdftops $$i $$o', 'pdf2ps $$i $$o'],
1086         rc_entry = [ r'\converter pdf         ps        "%%"    ""' ])
1087     # Only define a converter from pdf6 for graphics
1088     checkProg('a PDF to EPS converter', ['pdftops -eps -f 1 -l 1 $$i $$o'],
1089         rc_entry = [ r'\converter pdf6        eps        "%%"   ""' ])
1090     # Create one converter for a PDF produced using TeX fonts and one for a
1091     # PDF produced using non-TeX fonts. This does not produce non-unique
1092     # conversion paths, since a given document either uses TeX fonts or not.
1093     checkProg('a PDF cropping tool', ['pdfcrop $$i $$o'],
1094         rc_entry = [ r'''\converter pdf2   pdf7       "%%"      ""
1095 \converter pdf4   pdf7       "%%"       ""''' ])
1096     # Create one converter for a PDF produced using TeX fonts and one for a
1097     # PDF produced using non-TeX fonts. This does not produce non-unique
1098     # conversion paths, since a given document either uses TeX fonts or not.
1099     checkProg('Ghostscript', ["gswin32c", "gswin64c", "gs"],
1100         rc_entry = [ r'''\converter pdf2   pdf8       "$${python} $$s/scripts/convert_pdf.py $$i $$o ebook"     ""
1101 \converter pdf4   pdf8       "$${python} $$s/scripts/convert_pdf.py $$i $$o ebook"      ""''' ])
1102     #
1103     checkProg('a Beamer info extractor', ['makebeamerinfo -p $$i'],
1104         rc_entry = [ r'\converter pdf2         beamer.info        "%%"  ""' ])
1105     #
1106     checkProg('a DVI to TXT converter', ['catdvi $$i > $$o'],
1107         rc_entry = [ r'\converter dvi        text4      "%%"    ""' ])
1108     #
1109     checkProg('a DVI to PS converter', ['dvips -o $$o $$i'],
1110         rc_entry = [ r'\converter dvi        ps         "%%"    "hyperref-driver=dvips"' ])
1111     #
1112     checkProg('a DVI to cropped EPS converter', ['dvips -E -o $$o $$i'],
1113         rc_entry = [ r'\converter dvi        eps3         "%%"  ""' ])
1114     #
1115     checkProg('a DVI to PDF converter', ['dvipdfmx', 'dvipdfm'],
1116         rc_entry = [ r'\converter dvi        pdf3       "%%  -o $$o $$i"        "hyperref-driver=%%"' ])
1117     #
1118     checkProg('a fax program', ['kdeprintfax $$i', 'ksendfax $$i', 'hylapex $$i'],
1119         rc_entry = [ r'\converter ps         fax        "%%"    ""'])
1120     #
1121     path, fig2dev = checkProg('a FIG -> Image converter', ['fig2dev'])
1122     if fig2dev == "fig2dev":
1123         addToRC(r'''\converter fig        eps        "fig2dev -L eps $$i $$o"   ""
1124 \converter fig        ppm        "fig2dev -L ppm $$i $$o"       ""
1125 \converter fig        svg        "fig2dev -L svg $$i $$o"       ""
1126 \converter fig        png        "fig2dev -L png $$i $$o"       ""
1127 \converter fig        pdftex     "$${python} $$s/scripts/fig2pdftex.py $$i $$o" ""
1128 \converter fig        pstex      "$${python} $$s/scripts/fig2pstex.py $$i $$o"  ""''')
1129     #
1130     if inkscape_stable:
1131         checkProg('a SVG -> PDFTeX converter', [inkscape_cl],
1132             rc_entry = [ r'\converter svg        pdftex     "$${python} $$s/scripts/svg2pdftex.py %% $$p$$i $$p$$o" ""'],
1133             path = [inkscape_path])
1134         #
1135         checkProg('a SVG -> PSTeX converter', [inkscape_cl],
1136             rc_entry = [ r'\converter svg        pstex     "$${python} $$s/scripts/svg2pstex.py %% $$p$$i $$p$$o" ""'],
1137             path = [inkscape_path])
1138     else:
1139         checkProg('a SVG -> PDFTeX converter', [inkscape_cl],
1140             rc_entry = [ r'\converter svg        pdftex     "$${python} $$s/scripts/svg2pdftex.py --unstable %% $$p$$i $$p$$o" ""'],
1141             path = [inkscape_path])
1142         #
1143         checkProg('a SVG -> PSTeX converter', [inkscape_cl],
1144             rc_entry = [ r'\converter svg        pstex     "$${python} $$s/scripts/svg2pstex.py --unstable %% $$p$$i $$p$$o" ""'],
1145             path = [inkscape_path])
1146     #
1147     checkProg('a TIFF -> PS converter', ['tiff2ps $$i > $$o'],
1148         rc_entry = [ r'\converter tiff       eps        "%%"    ""'])
1149     #
1150     checkProg('a TGIF -> EPS/PPM converter', ['tgif'],
1151         rc_entry = [
1152             r'''\converter tgif       eps        "tgif -print -color -eps -stdout $$i > $$o"    ""
1153 \converter tgif       png        "tgif -print -color -png -o $$d $$i"   ""
1154 \converter tgif       pdf6       "tgif -print -color -pdf -stdout $$i > $$o"    ""'''])
1155     #
1156     # inkscape 1.0 has changed cl options
1157     if inkscape_stable:
1158         checkProg('a WMF -> EPS converter', ['metafile2eps $$i $$o', 'wmf2eps -o $$o $$i', inkscape_cl + ' $$i --export-area-drawing --export-filename=$$o'],
1159             rc_entry = [ r'\converter wmf        eps        "%%"        ""'])
1160         #
1161         checkProg('an EMF -> EPS converter', ['metafile2eps $$i $$o', inkscape_cl + ' $$i --export-area-drawing --export-filename=$$o'],
1162             rc_entry = [ r'\converter emf        eps        "%%"        ""'])
1163         #
1164         checkProg('a WMF -> PDF converter', [inkscape_cl + ' $$i --export-area-drawing --export-filename=$$o'],
1165             rc_entry = [ r'\converter wmf        pdf6        "%%"       ""'])
1166         #
1167         checkProg('an EMF -> PDF converter', [inkscape_cl + ' $$i --export-area-drawing --export-filename=$$o'],
1168             rc_entry = [ r'\converter emf        pdf6        "%%"       ""'])
1169     else:
1170         checkProg('a WMF -> EPS converter', ['metafile2eps $$i $$o', 'wmf2eps -o $$o $$i', inkscape_cl + ' --file=$$i --export-area-drawing --without-gui --export-eps=$$o'],
1171             rc_entry = [ r'\converter wmf        eps        "%%"        ""'])
1172         #
1173         checkProg('an EMF -> EPS converter', ['metafile2eps $$i $$o', inkscape_cl + ' --file=$$i --export-area-drawing --without-gui --export-eps=$$o'],
1174             rc_entry = [ r'\converter emf        eps        "%%"        ""'])
1175         #
1176         checkProg('a WMF -> PDF converter', [inkscape_cl + ' --file=$$i --export-area-drawing --without-gui --export-pdf=$$o'],
1177             rc_entry = [ r'\converter wmf        pdf6        "%%"       ""'])
1178         #
1179         checkProg('an EMF -> PDF converter', [inkscape_cl + ' --file=$$i --export-area-drawing --without-gui --export-pdf=$$o'],
1180             rc_entry = [ r'\converter emf        pdf6        "%%"       ""'])
1181     # Only define a converter to pdf6 for graphics
1182     checkProg('an EPS -> PDF converter', ['epstopdf'],
1183         rc_entry = [ r'\converter eps        pdf6       "epstopdf --outfile=$$o $$i"    ""'])
1184     #
1185     #prepare for pdf -> png, 2nd part depends on IM ban below
1186     pdftopng = ['sips --resampleWidth 800 --setProperty format png $$i --out $$o' ]
1187     #
1188     # Due to more restrictive policies, it is possible that (image)magick
1189     # does not allow conversions from eps to png.
1190     # So before setting the converter test it it on a mock file
1191     _, cmd = checkProg('an EPS -> PNG converter', ['magick', 'convert'])
1192     if cmd:
1193         writeToFile('mock.eps', r'%!PS')
1194         try:
1195             subprocess.check_call([cmd, "mock.eps", "mock.png"])
1196             removeFiles(['mock.eps', 'mock.png'])
1197             rc_entry = r'\converter eps        png        "%s $$i[0] $$o"       ""'
1198             addToRC(rc_entry % cmd)
1199         except:
1200             removeFiles(['mock.eps'])
1201             #needs empty record otherwise default converter will be issued
1202             addToRC(r'''\converter eps        png        ""     ""
1203 \converter png        eps        ""     ""
1204 \converter jpg        tiff        "convert $$i $$o"     ""
1205 \converter png        tiff        "convert $$i $$o"     ""''')
1206             logger.info('ImageMagick seems to ban conversions from EPS. Disabling direct EPS->PNG.')
1207             pdftopng.append('pdftoppm -r 72 -png -singlefile $$i >  $$o')
1208     #
1209     # PDF -> PNG: sips (mac), IM convert (windows, linux), pdftoppm (linux with IM ban)
1210     # sips:Define a converter from pdf6 to png for Macs where pdftops is missing.
1211     # The converter utility sips allows to force the dimensions of the resulting
1212     # png image. The value of 800 pixel for the width is arbitrary and not
1213     # related to the current screen resolution or width.
1214     # There is no converter parameter for this information.
1215     #
1216     #pdftoppm: Some systems ban IM eps->png conversion. We will offer eps->pdf->png route instead.
1217     checkProg('a PDF to PNG converter', pdftopng,
1218         rc_entry = [ r'\converter pdf6        png        "%%" ""' ])
1219
1220     #
1221     # no agr -> pdf6 converter, since the pdf library used by gracebat is not
1222     # free software and therefore not compiled in in many installations.
1223     # Fortunately, this is not a big problem, because we will use epstopdf to
1224     # convert from agr to pdf6 via eps without loss of quality.
1225     checkProg('a Grace -> Image converter', ['gracebat'],
1226         rc_entry = [
1227             r'''\converter agr        eps        "gracebat -hardcopy -printfile $$o -hdevice EPS $$i 2>/dev/null"       ""
1228 \converter agr        png        "gracebat -hardcopy -printfile $$o -hdevice PNG $$i 2>/dev/null"       ""
1229 \converter agr        jpg        "gracebat -hardcopy -printfile $$o -hdevice JPEG $$i 2>/dev/null"      ""
1230 \converter agr        ppm        "gracebat -hardcopy -printfile $$o -hdevice PNM $$i 2>/dev/null"       ""'''])
1231     #
1232     checkProg('a Dot -> Image converter', ['dot'],
1233         rc_entry = [
1234             r'''\converter dot        eps        "dot -Teps $$i -o $$o" ""
1235 \converter dot        png        "dot -Tpng $$i -o $$o" ""'''])
1236     #
1237     path, dia = checkProg('a Dia -> Image converter', ['dia'])
1238     if dia == 'dia':
1239         addToRC(r'''\converter dia        png        "dia -e $$o -t png $$i"    ""
1240 \converter dia        eps        "dia -e $$o -t eps $$i"        ""
1241 \converter dia        svg        "dia -e $$o -t svg $$i"        ""''')
1242
1243     #
1244     # Actually, this produces EPS, but with a wrong bounding box (usually A4 or letter).
1245     # The eps2->eps converter then fixes the bounding box by cropping.
1246     # Although unoconv can convert to png and pdf as well, do not define
1247     # odg->png and odg->pdf converters, since the bb would be too large as well.
1248     checkProg('an OpenDocument -> EPS converter', ['libreoffice --headless --nologo --convert-to eps $$i', 'unoconv -f eps --stdout $$i > $$o'],
1249         rc_entry = [ r'\converter odg        eps2       "%%"    ""'])
1250     #
1251     checkProg('a SVG (compressed) -> SVG converter', ['gunzip -c $$i > $$o'],
1252         rc_entry = [ r'\converter svgz       svg        "%%"    ""'])
1253     #
1254     checkProg('a SVG -> SVG (compressed) converter', ['gzip -c $$i > $$o'],
1255         rc_entry = [ r'\converter svg        svgz       "%%"    ""'])
1256     # Only define a converter to pdf6 for graphics
1257     # Prefer rsvg-convert over inkscape since it is faster (see http://www.lyx.org/trac/ticket/9891)
1258     # inkscape 1.0 has changed cl options
1259     if inkscape_stable:
1260         checkProg('a SVG -> PDF converter', ['rsvg-convert -f pdf -o $$o $$i', inkscape_cl + ' $$i --export-area-drawing --export-filename=$$o'],
1261             rc_entry = [ r'''\converter svg        pdf6       "%%"    ""
1262 \converter svgz       pdf6       "%%"    ""'''],
1263             path = ['', inkscape_path])
1264         #
1265         checkProg('a SVG -> EPS converter', ['rsvg-convert -f ps -o $$o $$i', inkscape_cl + ' $$i --export-area-drawing --export-filename=$$o'],
1266             rc_entry = [ r'''\converter svg        eps        "%%"    ""
1267 \converter svgz       eps        "%%"    ""'''],
1268             path = ['', inkscape_path])
1269         #
1270         checkProg('a SVG -> PNG converter', ['rsvg-convert -f png -o $$o $$i', inkscape_cl + ' $$i --export-filename=$$o'],
1271             rc_entry = [ r'''\converter svg        png        "%%"    "",
1272 \converter svgz       png        "%%"    ""'''],
1273             path = ['', inkscape_path])
1274     else:
1275         checkProg('a SVG -> PDF converter', ['rsvg-convert -f pdf -o $$o $$i', inkscape_cl + ' --file=$$i --export-area-drawing --without-gui --export-pdf=$$o'],
1276             rc_entry = [ r'''\converter svg        pdf6       "%%"    ""
1277 \converter svgz       pdf6       "%%"    ""'''],
1278             path = ['', inkscape_path])
1279         #
1280         checkProg('a SVG -> EPS converter', ['rsvg-convert -f ps -o $$o $$i', inkscape_cl + ' --file=$$i --export-area-drawing --without-gui --export-eps=$$o'],
1281             rc_entry = [ r'''\converter svg        eps        "%%"    ""
1282 \converter svgz       eps        "%%"    ""'''],
1283             path = ['', inkscape_path])
1284         #
1285         checkProg('a SVG -> PNG converter', ['rsvg-convert -f png -o $$o $$i', inkscape_cl + ' --without-gui --file=$$i --export-png=$$o'],
1286             rc_entry = [ r'''\converter svg        png        "%%"    "",
1287 \converter svgz       png        "%%"    ""'''],
1288             path = ['', inkscape_path])
1289     #
1290     checkProg('Gnuplot', ['gnuplot'],
1291         rc_entry = [ r'''\Format gnuplot     "gp, gnuplot"    "Gnuplot"     "" "" ""  "vector"  "text/plain"
1292 \converter gnuplot      pdf6      "$${python} $$s/scripts/gnuplot2pdf.py $$i $$o"    "needauth"''' ])
1293     #
1294     # gnumeric/xls/ods to tex
1295     checkProg('a spreadsheet -> latex converter', ['ssconvert'],
1296        rc_entry = [ r'''\converter gnumeric latex "ssconvert --export-type=Gnumeric_html:latex $$i $$o" ""
1297 \converter oocalc latex "ssconvert --export-type=Gnumeric_html:latex $$i $$o" ""
1298 \converter excel  latex "ssconvert --export-type=Gnumeric_html:latex $$i $$o" ""
1299 \converter excel2 latex "ssconvert --export-type=Gnumeric_html:latex $$i $$o" ""
1300 \converter gnumeric html_table "ssconvert --export-type=Gnumeric_html:html40frag $$i $$o" ""
1301 \converter oocalc html_table "ssconvert --export-type=Gnumeric_html:html40frag $$i $$o" ""
1302 \converter excel  html_table "ssconvert --export-type=Gnumeric_html:html40frag $$i $$o" ""
1303 \converter excel2 html_table "ssconvert --export-type=Gnumeric_html:html40frag $$i $$o" ""
1304 \converter gnumeric xhtml_table "$${python} $$s/scripts/spreadsheet_to_docbook.py $$i $$o" ""
1305 \converter oocalc xhtml_table "$${python} $$s/scripts/spreadsheet_to_docbook.py $$i $$o" ""
1306 \converter excel  xhtml_table "$${python} $$s/scripts/spreadsheet_to_docbook.py $$i $$o" ""
1307 \converter excel2 xhtml_table "$${python} $$s/scripts/spreadsheet_to_docbook.py $$i $$o" ""
1308 '''])
1309
1310     path, lilypond = checkProg('a LilyPond -> EPS/PDF/PNG converter', ['lilypond'])
1311     if (lilypond):
1312         version_string = cmdOutput("lilypond --version")
1313         match = re.match(r'GNU LilyPond (\S+)', version_string)
1314         if match:
1315             version_number = match.groups()[0]
1316             version = version_number.split('.')
1317             if int(version[0]) > 2 or (len(version) > 1 and int(version[0]) == 2 and int(version[1]) >= 11):
1318                 addToRC(r'''\converter lilypond   eps        "lilypond -dbackend=eps --ps $$i"  "needauth"
1319 \converter lilypond   png        "lilypond -dbackend=eps --png $$i"     "needauth"''')
1320                 addToRC(r'\converter lilypond   pdf6       "lilypond -dbackend=eps --pdf $$i"   "needauth"')
1321                 logger.info('+  found LilyPond version %s.' % version_number)
1322             elif int(version[0]) > 2 or (len(version) > 1 and int(version[0]) == 2 and int(version[1]) >= 6):
1323                 addToRC(r'''\converter lilypond   eps        "lilypond -b eps --ps $$i" "needauth"
1324 \converter lilypond   png        "lilypond -b eps --png $$i"    ""''')
1325                 if int(version[0]) > 2 or (len(version) > 1 and int(version[0]) == 2 and int(version[1]) >= 9):
1326                     addToRC(r'\converter lilypond   pdf6       "lilypond -b eps --pdf $$i"      "needauth"')
1327                 logger.info('+  found LilyPond version %s.' % version_number)
1328             else:
1329                 logger.info('+  found LilyPond, but version %s is too old.' % version_number)
1330         else:
1331             logger.info('+  found LilyPond, but could not extract version number.')
1332     #
1333     path, lilypond_book = checkProg('a LilyPond book (LaTeX) -> LaTeX converter', ['lilypond-book'])
1334     if lilypond_book:
1335         found_lilypond_book = False
1336         # On Windows, the file lilypond-book is not directly callable, it must be passed as argument to python.
1337         for cmd in ["lilypond-book", os.path.basename(sys.executable) + ' "' + path + '/lilypond-book"']:
1338             version_string = cmdOutput(cmd + " --version")
1339             if len(version_string) == 0:
1340                 continue
1341             found_lilypond_book = True
1342
1343             match = re.match(r'(\S+)$', version_string)
1344             if match:
1345                 version_number = match.groups()[0]
1346                 version = version_number.split('.')
1347                 if int(version[0]) > 2 or (len(version) > 1 and int(version[0]) == 2 and int(version[1]) >= 13):
1348                     # Note: The --lily-output-dir flag is required because lilypond-book
1349                     #       does not process input again unless the input has changed,
1350                     #       even if the output format being requested is different. So
1351                     #       once a .eps file exists, lilypond-book won't create a .pdf
1352                     #       even when requested with --pdf. This is a problem if a user
1353                     #       clicks View PDF after having done a View DVI. To circumvent
1354                     #       this, use different output folders for eps and pdf outputs.
1355                     cmd = cmd.replace('"', r'\"')
1356                     addToRC(r'\converter lilypond-book latex     "' + cmd + ' --lily-output-dir=ly-eps $$i"                                "needauth"')
1357                     addToRC(r'\converter lilypond-book pdflatex  "' + cmd + ' --pdf --latex-program=pdflatex --lily-output-dir=ly-pdf $$i" "needauth"')
1358                     addToRC(r'\converter lilypond-book-ja platex "' + cmd + ' --pdf --latex-program=platex --lily-output-dir=ly-pdf $$i" "needauth"')
1359                     addToRC(r'\converter lilypond-book xetex     "' + cmd + ' --pdf --latex-program=xelatex --lily-output-dir=ly-pdf $$i"  "needauth"')
1360                     addToRC(r'\converter lilypond-book luatex    "' + cmd + ' --pdf --latex-program=lualatex --lily-output-dir=ly-pdf $$i" "needauth"')
1361                     addToRC(r'\converter lilypond-book dviluatex "' + cmd + ' --latex-program=dvilualatex --lily-output-dir=ly-eps $$i" "needauth"')
1362
1363                     # Also create the entry to apply LilyPond on DocBook files. However,
1364                     # command must be passed as argument, and it might already have
1365                     # quoted parts. LyX doesn't yet handle double-quoting of commands.
1366                     # Hence, pass as argument either cmd (if it's a simple command) or
1367                     # the Python file that should be called (typical on Windows).
1368                     docbook_lilypond_cmd = cmd
1369                     if "python" in docbook_lilypond_cmd:
1370                         docbook_lilypond_cmd = '"' + path + '/lilypond-book"'
1371                     addToRC(r'\copier docbook5 "$${python} $$s/scripts/docbook_copy.py ' + docbook_lilypond_cmd.replace('"', r'\"') + r' $$i $$o"')
1372
1373                     logger.info('+  found LilyPond-book version %s.' % version_number)
1374
1375                     # early exit on first match, avoid 2nd try with python call
1376                     # in case of lilypond-book being an executable or shell script the python call is useless
1377                     break
1378                 else:
1379                     logger.info('+  found LilyPond-book, but version %s is too old.' % version_number)
1380             else:
1381                 logger.info('+  found LilyPond book, but version string does not match: %s' % version_string)
1382
1383             # If not on Windows, skip the check as argument to python.
1384             if os.name != 'nt':
1385                 break
1386
1387         if not found_lilypond_book:
1388             logger.info('+  found LilyPond-book, but could not extract version number.')
1389     #
1390     checkProg('a Noteedit -> LilyPond converter', ['noteedit --export-lilypond $$i'],
1391         rc_entry = [ r'\converter noteedit   lilypond   "%%"    ""' ])
1392     #
1393     # Currently, lyxpak outputs a gzip compressed tar archive on *nix
1394     # and a zip archive on Windows.
1395     # So, we configure the appropriate version according to the platform.
1396     cmd = r'\converter lyx %s "$${python} $$s/scripts/lyxpak.py $$r/$$f" ""'
1397     if os.name == 'nt':
1398         addToRC(r'\Format lyxzip     zip    "LyX Archive (zip)"     "" "" ""  "document,menu=export"    ""')
1399         addToRC(cmd % "lyxzip")
1400     else:
1401         addToRC(r'\Format lyxgz      gz     "LyX Archive (tar.gz)"  "" "" ""  "document,menu=export"    ""')
1402         addToRC(cmd % "lyxgz")
1403
1404     #
1405     # FIXME: no rc_entry? comment it out
1406     # checkProg('Image converter', ['convert $$i $$o'])
1407     #
1408     # Entries that do not need checkProg
1409     addToRC(r'''
1410 \converter csv        lyx        "$${python} $$s/scripts/csv2lyx.py $$i $$o"    ""
1411 \converter fen        asciichess "$${python} $$s/scripts/fen2ascii.py $$i $$o"  ""
1412 \converter lyx        lyx13x     "$${python} $$s/lyx2lyx/lyx2lyx -V 1.3 -o $$o $$i"     ""
1413 \converter lyx        lyx14x     "$${python} $$s/lyx2lyx/lyx2lyx -V 1.4 -o $$o $$i"     ""
1414 \converter lyx        lyx15x     "$${python} $$s/lyx2lyx/lyx2lyx -V 1.5 -o $$o $$i"     ""
1415 \converter lyx        lyx16x     "$${python} $$s/lyx2lyx/lyx2lyx -V 1.6 -o $$o $$i"     ""
1416 \converter lyx        lyx20x     "$${python} $$s/lyx2lyx/lyx2lyx -V 2.0 -o $$o $$i"     ""
1417 \converter lyx        lyx21x     "$${python} $$s/lyx2lyx/lyx2lyx -V 2.1 -o $$o $$i"     ""
1418 \converter lyx        lyx22x     "$${python} $$s/lyx2lyx/lyx2lyx -V 2.2 -o $$o $$i"     ""
1419 \converter lyx        lyx23x     "$${python} $$s/lyx2lyx/lyx2lyx -V 2.3 -o $$o $$i"     ""
1420 \converter lyx        clyx       "$${python} $$s/lyx2lyx/lyx2lyx -V 1.4 -o $$o -c big5   $$i"   ""
1421 \converter lyx        jlyx       "$${python} $$s/lyx2lyx/lyx2lyx -V 1.4 -o $$o -c euc_jp $$i"   ""
1422 \converter lyx        klyx       "$${python} $$s/lyx2lyx/lyx2lyx -V 1.4 -o $$o -c euc_kr $$i"   ""
1423 \converter clyx       lyx        "$${python} $$s/lyx2lyx/lyx2lyx -c big5   -o $$o $$i"  ""
1424 \converter jlyx       lyx        "$${python} $$s/lyx2lyx/lyx2lyx -c euc_jp -o $$o $$i"  ""
1425 \converter klyx       lyx        "$${python} $$s/lyx2lyx/lyx2lyx -c euc_kr -o $$o $$i"  ""
1426 \converter lyxpreview png        "$${python} $$s/scripts/lyxpreview2bitmap.py --png"    ""
1427 \converter lyxpreview ppm        "$${python} $$s/scripts/lyxpreview2bitmap.py --ppm"    ""
1428 ''')
1429
1430
1431 def checkOtherEntries():
1432     ''' entries other than Format and Converter '''
1433     checkProg('ChkTeX', ['chktex -n1 -n3 -n6 -n9 -n22 -n25 -n30 -n38'],
1434         rc_entry = [ r'\chktex_command "%%"' ])
1435     checkProgAlternatives('BibTeX or alternative programs',
1436         ['bibtex', 'bibtex8', 'biber'],
1437         rc_entry = [ r'\bibtex_command "automatic"' ],
1438         alt_rc_entry = [ r'\bibtex_alternatives "%%"' ])
1439     checkProgAlternatives('a specific Japanese BibTeX variant',
1440         ['pbibtex', 'upbibtex', 'jbibtex', 'bibtex', 'biber'],
1441         rc_entry = [ r'\jbibtex_command "automatic"' ],
1442         alt_rc_entry = [ r'\jbibtex_alternatives "%%"' ])
1443     checkProgAlternatives('available index processors',
1444         ['texindy $$x -t $$b.ilg', 'xindex -l $$lcode', 'makeindex -c -q', 'xindy -M texindy $$x -t $$b.ilg'],
1445         rc_entry = [ r'\index_command "%%"' ],
1446         alt_rc_entry = [ r'\index_alternatives "%%"' ])
1447     checkProg('an index processor appropriate to Japanese',
1448         ['mendex -c -q', 'jmakeindex -c -q', 'makeindex -c -q'],
1449         rc_entry = [ r'\jindex_command "%%"' ])
1450     checkProg('the splitindex processor', ['splitindex.pl', 'splitindex',
1451         'splitindex.class'], rc_entry = [ r'\splitindex_command "%%"' ])
1452     checkProg('a nomenclature processor', ['makeindex'],
1453         rc_entry = [ r'\nomencl_command "makeindex -s nomencl.ist"' ])
1454     checkProg('a python-pygments driver command', ['pygmentize'],
1455         rc_entry = [ r'\pygmentize_command "%%"' ])
1456     ## FIXME: OCTAVE is not used anywhere
1457     # path, OCTAVE = checkProg('Octave', ['octave'])
1458     ## FIXME: MAPLE is not used anywhere
1459     # path, MAPLE = checkProg('Maple', ['maple'])
1460     # Add the rest of the entries (no checkProg is required)
1461     addToRC(r'''\citation_search_view "$${python} $$s/scripts/lyxpaperview.py"''')
1462     addToRC(r'''\copier    fig        "$${python} $$s/scripts/fig_copy.py $$i $$o"
1463 \copier    pstex      "$${python} $$s/scripts/tex_copy.py $$i $$o $$l"
1464 \copier    pdftex     "$${python} $$s/scripts/tex_copy.py $$i $$o $$l"
1465 \copier    program    "$${python} $$s/scripts/ext_copy.py $$i $$o"
1466 ''')
1467
1468 def _checkForClassExtension(x):
1469     '''if the extension for a latex class is not
1470         provided, add .cls to the classname'''
1471     if not '.' in x:
1472         return x.strip() + '.cls'
1473     else:
1474         return x.strip()
1475
1476 def processLayoutFile(file):
1477     r""" process layout file and get a line of result
1478
1479         Declare lines look like this:
1480
1481         \DeclareLaTeXClass[<requirements>]{<description>}
1482
1483         Optionally, a \DeclareCategory line follows:
1484
1485         \DeclareCategory{<category>}
1486
1487         So for example (article.layout, scrbook.layout, svjog.layout)
1488
1489         \DeclareLaTeXClass{article}
1490         \DeclareCategory{Articles}
1491
1492         \DeclareLaTeXClass[scrbook]{book (koma-script)}
1493         \DeclareCategory{Books}
1494
1495         \DeclareLaTeXClass[svjour,svjog.clo]{article (Springer - svjour/jog)}
1496
1497         we'd expect this output:
1498
1499         "article" "article" "article" "false" "article.cls" "Articles"
1500         "scrbook" "scrbook" "book (koma-script)" "false" "scrbook.cls" "Books"
1501         "svjog" "svjour" "article (Springer - svjour/jog)" "false" "svjour.cls,svjog.clo" ""
1502     """
1503     classname = file.split(os.sep)[-1].split('.')[0]
1504     # return ('[a,b]', 'a', ',b,c', 'article') for \DeclareLaTeXClass[a,b,c]{article}
1505     p = re.compile('\\s*#\\s*\\\\DeclareLaTeXClass\\s*(\\[([^,]*)(,.*)*])*\\s*{(.*)}\\s*$')
1506     q = re.compile('\\s*#\\s*\\\\DeclareCategory{(.*)}\\s*$')
1507     classdeclaration = ""
1508     categorydeclaration = '""'
1509     for line in open(file, 'r', encoding='utf8').readlines():
1510         res = p.match(line)
1511         qres = q.match(line)
1512         if res is not None:
1513             (optAll, opt, opt1, desc) = res.groups()
1514             if opt is None:
1515                 opt = classname
1516                 prereq = _checkForClassExtension(classname)
1517             else:
1518                 prereq_list = optAll[1:-1].split(',')
1519                 prereq_list = list(map(_checkForClassExtension, prereq_list))
1520                 prereq = ','.join(prereq_list)
1521             classdeclaration = ('"%s" "%s" "%s" "%s" "%s"'
1522                                % (classname, opt, desc, 'false', prereq))
1523             if categorydeclaration != '""':
1524                 return classdeclaration + " " + categorydeclaration
1525         if qres is not None:
1526             categorydeclaration = '"%s"' % (qres.groups()[0])
1527             if classdeclaration:
1528                 return classdeclaration + " " + categorydeclaration
1529     if classdeclaration:
1530         return classdeclaration + " " + categorydeclaration
1531     logger.warning("Layout file " + file + " has no \\DeclareLaTeXClass line. ")
1532     return ""
1533
1534
1535 def checkLatexConfig(check_config):
1536     ''' Explore the LaTeX configuration
1537         Return None (will be passed to sys.exit()) for success.
1538     '''
1539     msg = 'checking LaTeX configuration... '
1540     # if --without-latex-config is forced, or if there is no previous
1541     # version of textclass.lst, re-generate a default file.
1542     if not os.path.isfile('textclass.lst') or not check_config:
1543         # remove the files only if we want to regenerate
1544         removeFiles(['textclass.lst', 'packages.lst'])
1545         #
1546         # Then, generate a default textclass.lst. In case configure.py
1547         # fails, we still have something to start lyx.
1548         logger.info(msg + ' default values')
1549         logger.info('+checking list of textclasses... ')
1550         tx = open('textclass.lst', 'w', encoding='utf8')
1551         tx.write('''
1552 # This file declares layouts and their associated definition files
1553 # (include dir. relative to the place where this file is).
1554 # It contains only default values, since chkconfig.ltx could not be run
1555 # for some reason. Run ./configure.py if you need to update it after a
1556 # configuration change.
1557 ''')
1558         # build the list of available layout files and convert it to commands
1559         # for chkconfig.ltx
1560         foundClasses = []
1561         for file in (glob.glob(os.path.join('layouts', '*.layout'))
1562                      + glob.glob(os.path.join(srcdir, 'layouts', '*.layout'))):
1563             # valid file?
1564             if not os.path.isfile(file):
1565                 continue
1566             # get stuff between /xxxx.layout .
1567             classname = file.split(os.sep)[-1].split('.')[0]
1568             #  tr ' -' '__'`
1569             cleanclass = classname.replace(' ', '_').replace('-', '_')
1570             # make sure the same class is not considered twice
1571             if foundClasses.count(cleanclass) == 0: # not found before
1572                 foundClasses.append(cleanclass)
1573                 retval = processLayoutFile(file)
1574                 if retval:
1575                     tx.write(retval + os.linesep)
1576         tx.close()
1577         logger.info('\tdone')
1578     if not os.path.isfile('packages.lst') or not check_config:
1579         logger.info('+generating default list of packages... ')
1580         removeFiles(['packages.lst'])
1581         tx = open('packages.lst', 'w', encoding='utf8')
1582         tx.close()
1583         logger.info('\tdone')
1584     if not check_config:
1585         return None
1586     # the following will generate textclass.lst.tmp, and packages.lst.tmp
1587     logger.info(msg + '\tauto')
1588     removeFiles(['chkconfig.classes', 'chkconfig.vars', 'chklayouts.tex',
1589         'wrap_chkconfig.ltx'])
1590     rmcopy = False
1591     if not os.path.isfile( 'chkconfig.ltx' ):
1592         shutil.copyfile( os.path.join(srcdir, 'chkconfig.ltx'), 'chkconfig.ltx' )
1593         rmcopy = True
1594     writeToFile('wrap_chkconfig.ltx', '\\def\\hasdocbook{yes}\n\\input{chkconfig.ltx}\n')
1595     # Construct the list of classes to test for.
1596     # build the list of available layout files and convert it to commands
1597     # for chkconfig.ltx
1598     declare = re.compile('\\s*#\\s*\\\\DeclareLaTeXClass\\s*(\\[([^,]*)(,.*)*\\])*\\s*{(.*)}\\s*$')
1599     category = re.compile('\\s*#\\s*\\\\DeclareCategory{(.*)}\\s*$')
1600     empty = re.compile('\\s*$')
1601     testclasses = list()
1602     for file in (glob.glob( os.path.join('layouts', '*.layout') )
1603                  + glob.glob( os.path.join(srcdir, 'layouts', '*.layout' ) ) ):
1604         nodeclaration = False
1605         if not os.path.isfile(file):
1606             continue
1607         classname = file.split(os.sep)[-1].split('.')[0]
1608         decline = ""
1609         catline = ""
1610         try:
1611             for line in open(file, 'r', encoding='utf8').readlines():
1612                 if not empty.match(line) and line[0] != '#'[0]:
1613                     if decline == "":
1614                         logger.warning(r"Failed to find valid \Declare line "
1615                             "for layout file `%s'.\n\t=> Skipping this file!" % file)
1616                         nodeclaration = True
1617                     # A class, but no category declaration. Just break.
1618                     break
1619                 if declare.match(line) is not None:
1620                     decline = "\\TestDocClass{%s}{%s}" % (classname, line[1:].strip())
1621                     testclasses.append(decline)
1622                 elif category.match(line) is not None:
1623                     catline = ("\\DeclareCategory{%s}{%s}"
1624                                % (classname, category.match(line).groups()[0]))
1625                     testclasses.append(catline)
1626                 if catline == "" or decline == "":
1627                     continue
1628                 break
1629             if nodeclaration:
1630                 continue
1631         except UnicodeDecodeError:
1632             logger.warning("**************************************************\n"
1633                            "Layout file '%s'\n"
1634                            "cannot be decoded in utf-8.\n"
1635                            "Please check if the file has the correct encoding.\n"
1636                            "Skipping this file!\n"
1637                            "**************************************************" % file)
1638             continue
1639     testclasses.sort()
1640     cl = open('chklayouts.tex', 'w', encoding='utf8')
1641     for line in testclasses:
1642         cl.write(line + '\n')
1643     cl.close()
1644     #
1645     # we have chklayouts.tex, then process it
1646     latex_out = cmdOutput(LATEX + ' wrap_chkconfig.ltx', True)
1647     while True:
1648         line = latex_out.readline()
1649         if not line:
1650             break;
1651         if line.startswith('+'):
1652             logger.info(line.strip())
1653     # if the command succeeds, None will be returned
1654     ret = latex_out.close()
1655     #
1656     # remove the copied file
1657     if rmcopy:
1658         removeFiles( [ 'chkconfig.ltx' ] )
1659     #
1660     # values in chkconfig were only used to set
1661     # \font_encoding, which is obsolete
1662 #    values = {}
1663 #    for line in open('chkconfig.vars').readlines():
1664 #        key, val = re.sub('-', '_', line).split('=')
1665 #        val = val.strip()
1666 #        values[key] = val.strip("'")
1667     # if configure successed, move textclass.lst.tmp to textclass.lst
1668     # and packages.lst.tmp to packages.lst
1669     if (os.path.isfile('textclass.lst.tmp')
1670           and len(open('textclass.lst.tmp', encoding='utf8').read()) > 0
1671         and os.path.isfile('packages.lst.tmp')
1672           and len(open('packages.lst.tmp', encoding='utf8').read()) > 0):
1673         shutil.move('textclass.lst.tmp', 'textclass.lst')
1674         shutil.move('packages.lst.tmp', 'packages.lst')
1675     return ret
1676
1677
1678 def checkModulesConfig():
1679   removeFiles(['lyxmodules.lst', 'chkmodules.tex'])
1680
1681   logger.info('+checking list of modules... ')
1682   tx = open('lyxmodules.lst', 'w', encoding='utf8')
1683   tx.write('''## This file declares modules and their associated definition files.
1684 ## It has been automatically generated by configure
1685 ## Use "Options/Reconfigure" if you need to update it after a
1686 ## configuration change.
1687 ## "ModuleName" "filename" "Description" "Packages" "Requires" "Excludes" "Category" "Local"
1688 ''')
1689
1690   # build the list of available modules
1691   seen = []
1692   # note that this searches the local directory first, then the
1693   # system directory. that way, we pick up the user's version first.
1694   for file in (glob.glob( os.path.join('layouts', '*.module') )
1695                + glob.glob( os.path.join(srcdir, 'layouts', '*.module' ) ) ):
1696       # valid file?
1697       logger.info(file)
1698       if not os.path.isfile(file):
1699           continue
1700
1701       filename = file.split(os.sep)[-1]
1702       filename = filename[:-7]
1703       if seen.count(filename):
1704           continue
1705
1706       seen.append(filename)
1707       try:
1708           retval = processModuleFile(file, filename)
1709           if retval:
1710               tx.write(retval)
1711       except UnicodeDecodeError:
1712           logger.warning("**************************************************\n"
1713                          "Module file '%s'\n"
1714                          "cannot be decoded in utf-8.\n"
1715                          "Please check if the file has the correct encoding.\n"
1716                          "Skipping this file!\n"
1717                          "**************************************************" % filename)
1718   tx.close()
1719   logger.info('\tdone')
1720
1721
1722 def processModuleFile(file, filename):
1723     r''' process module file and get a line of result
1724
1725         The top of a module file should look like this:
1726           #\DeclareLyXModule[LaTeX Packages]{ModuleName}
1727           #DescriptionBegin
1728           #...body of description...
1729           #DescriptionEnd
1730           #Requires: [list of required modules]
1731           #Excludes: [list of excluded modules]
1732           #Category: [category name]
1733         The last three lines are optional (though do give a category).
1734         We expect output:
1735           "ModuleName" "filename" "Description" "Packages" "Requires" "Excludes" "Category"
1736     '''
1737     remods = re.compile('\\s*#\\s*\\\\DeclareLyXModule\\s*(?:\\[([^]]*?)\\])?{(.*)}')
1738     rereqs = re.compile(r'\s*#+\s*Requires: (.*)')
1739     reexcs = re.compile(r'\s*#+\s*Excludes: (.*)')
1740     recaty = re.compile('\\s*#\\s*\\\\DeclareCategory{(.*)}\\s*$')
1741     redbeg = re.compile(r'\s*#+\s*DescriptionBegin\s*$')
1742     redend = re.compile(r'\s*#+\s*DescriptionEnd\s*$')
1743
1744     modname = desc = pkgs = req = excl = catgy = ""
1745     readingDescription = False
1746     descLines = []
1747
1748     for line in open(file, 'r', encoding='utf8').readlines():
1749       if readingDescription:
1750         res = redend.match(line)
1751         if res != None:
1752           readingDescription = False
1753           desc = " ".join(descLines)
1754           # Escape quotes.
1755           desc = desc.replace('"', '\\"')
1756           continue
1757         descLines.append(line[1:].strip())
1758         continue
1759       res = redbeg.match(line)
1760       if res != None:
1761         readingDescription = True
1762         continue
1763       res = remods.match(line)
1764       if res != None:
1765           (pkgs, modname) = res.groups()
1766           if pkgs == None:
1767             pkgs = ""
1768           else:
1769             tmp = [s.strip() for s in pkgs.split(",")]
1770             pkgs = ",".join(tmp)
1771           continue
1772       res = rereqs.match(line)
1773       if res != None:
1774         req = res.group(1)
1775         tmp = [s.strip() for s in req.split("|")]
1776         req = "|".join(tmp)
1777         continue
1778       res = reexcs.match(line)
1779       if res != None:
1780         excl = res.group(1)
1781         tmp = [s.strip() for s in excl.split("|")]
1782         excl = "|".join(tmp)
1783         continue
1784       res = recaty.match(line)
1785       if res != None:
1786         catgy = res.group(1)
1787         continue
1788
1789     if modname == "":
1790       logger.warning(r"Module file without \DeclareLyXModule line. ")
1791       return ""
1792
1793     if pkgs:
1794         # this module has some latex dependencies:
1795         # append the dependencies to chkmodules.tex,
1796         # which is \input'ed by chkconfig.ltx
1797         testpackages = list()
1798         for pkg in pkgs.split(","):
1799             if "->" in pkg:
1800                 # this is a converter dependency: skip
1801                 continue
1802             if pkg.endswith(".sty"):
1803                 pkg = pkg[:-4]
1804             testpackages.append("\\TestPackage{%s}" % pkg)
1805         cm = open('chkmodules.tex', 'a', encoding='utf8')
1806         for line in testpackages:
1807             cm.write(line + '\n')
1808         cm.close()
1809
1810     local = "true"
1811     if (file.startswith(srcdir)):
1812         local = "false"
1813     return ('"%s" "%s" "%s" "%s" "%s" "%s" "%s" "%s"\n'
1814             % (modname, filename, desc, pkgs, req, excl, catgy, local))
1815
1816
1817 def checkCiteEnginesConfig():
1818   removeFiles(['lyxciteengines.lst', 'chkciteengines.tex'])
1819
1820   logger.info('+checking list of cite engines... ')
1821   tx = open('lyxciteengines.lst', 'w', encoding='utf8')
1822   tx.write('''## This file declares cite engines and their associated definition files.
1823 ## It has been automatically generated by configure
1824 ## Use "Options/Reconfigure" if you need to update it after a
1825 ## configuration change.
1826 ## "CiteEngineName" "filename" "CiteEngineType" "CiteFramework" "DefaultBiblio" "Description" "Packages"
1827 ''')
1828
1829   # build the list of available modules
1830   seen = []
1831   # note that this searches the local directory first, then the
1832   # system directory. that way, we pick up the user's version first.
1833   for file in glob.glob( os.path.join('citeengines', '*.citeengine') ) + \
1834       glob.glob( os.path.join(srcdir, 'citeengines', '*.citeengine' ) ) :
1835       # valid file?
1836       logger.info(file)
1837       if not os.path.isfile(file):
1838           continue
1839
1840       filename = file.split(os.sep)[-1]
1841       filename = filename[:-11]
1842       if seen.count(filename):
1843           continue
1844
1845       seen.append(filename)
1846       retval = processCiteEngineFile(file, filename)
1847       if retval:
1848           tx.write(retval)
1849   tx.close()
1850   logger.info('\tdone')
1851
1852
1853 def processCiteEngineFile(file, filename):
1854     r''' process cite engines file and get a line of result
1855
1856         The top of a cite engine file should look like this:
1857           #\DeclareLyXCiteEngine[LaTeX Packages]{CiteEngineName}
1858           #DescriptionBegin
1859           #...body of description...
1860           #DescriptionEnd
1861         We expect output:
1862           "CiteEngineName" "filename" "CiteEngineType" "CiteFramework" "DefaultBiblio" "Description" "Packages"
1863     '''
1864     remods = re.compile('\\s*#\\s*\\\\DeclareLyXCiteEngine\\s*(?:\\[([^]]*?)\\])?{(.*)}')
1865     redbeg = re.compile(r'\s*#+\s*DescriptionBegin\s*$')
1866     redend = re.compile(r'\s*#+\s*DescriptionEnd\s*$')
1867     recet = re.compile(r'\s*CiteEngineType\s*(.*)')
1868     redb = re.compile(r'\s*DefaultBiblio\s*(.*)')
1869     resfm = re.compile(r'\s*CiteFramework\s*(.*)')
1870
1871     modname = desc = pkgs = cet = db = cfm = ""
1872     readingDescription = False
1873     descLines = []
1874
1875     for line in open(file, 'r', encoding='utf8').readlines():
1876       if readingDescription:
1877         res = redend.match(line)
1878         if res != None:
1879           readingDescription = False
1880           desc = " ".join(descLines)
1881           # Escape quotes.
1882           desc = desc.replace('"', '\\"')
1883           continue
1884         descLines.append(line[1:].strip())
1885         continue
1886       res = redbeg.match(line)
1887       if res != None:
1888         readingDescription = True
1889         continue
1890       res = remods.match(line)
1891       if res != None:
1892           (pkgs, modname) = res.groups()
1893           if pkgs == None:
1894             pkgs = ""
1895           else:
1896             tmp = [s.strip() for s in pkgs.split(",")]
1897             pkgs = ",".join(tmp)
1898           continue
1899       res = recet.match(line)
1900       if res != None:
1901         cet = res.group(1)
1902         continue
1903       res = redb.match(line)
1904       if res != None:
1905         db = res.group(1)
1906         continue
1907       res = resfm.match(line)
1908       if res != None:
1909         cfm = res.group(1)
1910         continue
1911
1912     if modname == "":
1913       logger.warning(r"Cite Engine File file without \DeclareLyXCiteEngine line. ")
1914       return ""
1915
1916     if pkgs:
1917         # this cite engine has some latex dependencies:
1918         # append the dependencies to chkciteengines.tex,
1919         # which is \input'ed by chkconfig.ltx
1920         testpackages = list()
1921         for pkg in pkgs.split(","):
1922             if "->" in pkg:
1923                 # this is a converter dependency: skip
1924                 continue
1925             if pkg.endswith(".sty"):
1926                 pkg = pkg[:-4]
1927             testpackages.append("\\TestPackage{%s}" % pkg)
1928         cm = open('chkciteengines.tex', 'a', encoding='utf8')
1929         for line in testpackages:
1930             cm.write(line + '\n')
1931         cm.close()
1932
1933     return ('"%s" "%s" "%s" "%s" "%s" "%s" "%s"\n'
1934             % (modname, filename, cet, cfm, db, desc, pkgs))
1935
1936
1937 def checkXTemplates():
1938   removeFiles(['xtemplates.lst'])
1939
1940   logger.info('+checking list of external templates... ')
1941   tx = open('xtemplates.lst', 'w', encoding='utf8')
1942   tx.write('''## This file lists external templates.
1943 ## It has been automatically generated by configure
1944 ## Use "Options/Reconfigure" if you need to update it after a
1945 ## configuration change.
1946 ''')
1947
1948   # build the list of available templates
1949   seen = []
1950   # note that this searches the local directory first, then the
1951   # system directory. that way, we pick up the user's version first.
1952   for file in glob.glob( os.path.join('xtemplates', '*.xtemplate') ) + \
1953       glob.glob( os.path.join(srcdir, 'xtemplates', '*.xtemplate' ) ) :
1954       # valid file?
1955       logger.info(file)
1956       if not os.path.isfile(file):
1957           continue
1958
1959       filename = file.split(os.sep)[-1]
1960       if seen.count(filename):
1961           continue
1962
1963       seen.append(filename)
1964       if filename:
1965           tx.write(filename + "\n")
1966   tx.close()
1967   logger.info('\tdone')
1968
1969
1970 def checkTeXAllowSpaces():
1971     ''' Let's check whether spaces are allowed in TeX file names '''
1972     tex_allows_spaces = 'false'
1973     if lyx_check_config:
1974         msg = "Checking whether TeX allows spaces in file names... "
1975         writeToFile('a b.tex', r'\message{working^^J}' )
1976         if LATEX:
1977             if os.name == 'nt' or sys.platform == 'cygwin':
1978                 latex_out = cmdOutput(LATEX + r""" "\nonstopmode\input{\"a b\"}\makeatletter\@@end" """)
1979             else:
1980                 latex_out = cmdOutput(LATEX + r""" '\nonstopmode\input{"a b"}\makeatletter\@@end' """)
1981         else:
1982             latex_out = ''
1983         if 'working' in latex_out:
1984             logger.info(msg + 'yes')
1985             tex_allows_spaces = 'true'
1986         else:
1987             logger.info(msg + 'no')
1988             tex_allows_spaces = 'false'
1989         addToRC(r'\tex_allows_spaces ' + tex_allows_spaces)
1990         removeFiles( [ 'a b.tex', 'a b.log', 'texput.log' ])
1991
1992
1993 def rescanTeXFiles():
1994     ''' Run kpsewhich to update information about TeX files '''
1995     logger.info("+Indexing TeX files... ")
1996     tfscript = os.path.join(srcdir, 'scripts', 'TeXFiles.py')
1997     if not os.path.isfile(tfscript):
1998         logger.error("configure: error: cannot find TeXFiles.py script")
1999         sys.exit(1)
2000     interpreter = sys.executable
2001     if interpreter == '':
2002         interpreter = "python"
2003     tfp = cmdOutput('"%s" -tt "%s"' % (interpreter, tfscript))
2004     logger.info(tfp)
2005     logger.info("\tdone")
2006
2007
2008 def removeTempFiles():
2009     # Final clean-up
2010     if not lyx_keep_temps:
2011         removeFiles(['chkconfig.vars', 'chklatex.ltx', 'chklatex.log',
2012             'chklayouts.tex', 'chkmodules.tex', 'chkciteengines.tex',
2013             'missfont.log', 'wrap_chkconfig.ltx', 'wrap_chkconfig.log'])
2014
2015
2016 if __name__ == '__main__':
2017     lyx_check_config = True
2018     lyx_kpsewhich = True
2019     outfile = 'lyxrc.defaults'
2020     lyxrc_fileformat = 38
2021     rc_entries = ''
2022     lyx_keep_temps = False
2023     version_suffix = ''
2024     lyx_binary_dir = ''
2025     logger.info("+Running LyX configure with Python %s.%s.%s", sys.version_info[0], sys.version_info[1], sys.version_info[2])
2026     ## Parse the command line
2027     for op in sys.argv[1:]:   # default shell/for list is $*, the options
2028         if op in [ '-help', '--help', '-h' ]:
2029             print('''Usage: configure [options]
2030 Options:
2031     --help                   show this help lines
2032     --keep-temps             keep temporary files (for debug. purposes)
2033     --without-kpsewhich      do not update TeX files information via kpsewhich
2034     --without-latex-config   do not run LaTeX to determine configuration
2035     --with-version-suffix=suffix suffix of binary installed files
2036     --binary-dir=directory   directory of binary installed files
2037 ''')
2038             sys.exit(0)
2039         elif op == '--without-kpsewhich':
2040             lyx_kpsewhich = False
2041         elif op == '--without-latex-config':
2042             lyx_check_config = False
2043         elif op == '--keep-temps':
2044             lyx_keep_temps = True
2045         elif op[0:22] == '--with-version-suffix=':  # never mind if op is not long enough
2046             version_suffix = op[22:]
2047         elif op[0:13] == '--binary-dir=':
2048             lyx_binary_dir = op[13:]
2049         else:
2050             print("Unknown option %s" % op)
2051             sys.exit(1)
2052     #
2053     # check if we run from the right directory
2054     srcdir = os.path.dirname(sys.argv[0])
2055     if srcdir == '':
2056         srcdir = '.'
2057     if not os.path.isfile( os.path.join(srcdir, 'chkconfig.ltx') ):
2058         logger.error("configure: error: cannot find chkconfig.ltx script")
2059         sys.exit(1)
2060     setEnviron()
2061     if sys.platform == 'darwin' and len(version_suffix) > 0:
2062         checkUpgrade()
2063     createDirectories()
2064     dtl_tools = checkDTLtools()
2065     ## Write the first part of outfile
2066     writeToFile(outfile, '''# This file has been automatically generated by LyX' lib/configure.py
2067 # script. It contains default settings that have been determined by
2068 # examining your system. PLEASE DO NOT MODIFY ANYTHING HERE! If you
2069 # want to customize LyX, use LyX' Preferences dialog or modify directly
2070 # the "preferences" file instead. Any setting in that file will
2071 # override the values given here.
2072
2073 Format %i
2074
2075 ''' % lyxrc_fileformat)
2076     # check latex
2077     LATEX = checkLatex(dtl_tools)
2078     # check java and perl before any checkProg that may require them
2079     java = checkProg('a java interpreter', ['java'])[1]
2080     if java == '':
2081         java = check_java()
2082     perl = checkProg('a perl interpreter', ['perl'])[1]
2083     xsltproc = checkProg('xsltproc', ['xsltproc'])[1]
2084     (inkscape_path, inkscape_gui) = os.path.split(checkInkscape())
2085     # On Windows, we need to call the "inkscape.com" wrapper
2086     # for command line purposes. Other OSes do not differentiate.
2087     inkscape_cl = inkscape_gui
2088     if os.name == 'nt':
2089         inkscape_cl = inkscape_gui.replace('.exe', '.com')
2090     inkscape_stable = checkInkscapeStable()
2091     checkFormatEntries(dtl_tools)
2092     checkConverterEntries()
2093     checkTeXAllowSpaces()
2094     windows_style_tex_paths = checkTeXPaths()
2095     if windows_style_tex_paths:
2096         addToRC(r'\tex_expects_windows_paths %s' % windows_style_tex_paths)
2097     checkOtherEntries()
2098     if lyx_kpsewhich:
2099         rescanTeXFiles()
2100     checkModulesConfig()
2101     checkCiteEnginesConfig()
2102     checkXTemplates()
2103     # --without-latex-config can disable lyx_check_config
2104     ret = checkLatexConfig(lyx_check_config and LATEX)
2105     removeTempFiles()
2106     # The return error code can be 256. Because most systems expect an error code
2107     # in the range 0-127, 256 can be interpretted as 'success'. Because we expect
2108     # a None for success, 'ret is not None' is used to exit.
2109     sys.exit(ret is not None)