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