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