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