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