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