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