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