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