]> git.lyx.org Git - lyx.git/blob - lib/configure.py
Amend b8502a3ea2735c4
[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 $$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             rc_entry = r'\converter eps        png        ""    ""'
1210             addToRC(rc_entry)
1211             logger.info('ImageMagick seems to ban conversions from EPS. Disabling direct EPS->PNG.')
1212     #
1213     # no agr -> pdf6 converter, since the pdf library used by gracebat is not
1214     # free software and therefore not compiled in in many installations.
1215     # Fortunately, this is not a big problem, because we will use epstopdf to
1216     # convert from agr to pdf6 via eps without loss of quality.
1217     checkProg('a Grace -> Image converter', ['gracebat'],
1218         rc_entry = [
1219             r'''\converter agr        eps        "gracebat -hardcopy -printfile $$o -hdevice EPS $$i 2>/dev/null"       ""
1220 \converter agr        png        "gracebat -hardcopy -printfile $$o -hdevice PNG $$i 2>/dev/null"       ""
1221 \converter agr        jpg        "gracebat -hardcopy -printfile $$o -hdevice JPEG $$i 2>/dev/null"      ""
1222 \converter agr        ppm        "gracebat -hardcopy -printfile $$o -hdevice PNM $$i 2>/dev/null"       ""'''])
1223     #
1224     checkProg('a Dot -> Image converter', ['dot'],
1225         rc_entry = [
1226             r'''\converter dot        eps        "dot -Teps $$i -o $$o" ""
1227 \converter dot        png        "dot -Tpng $$i -o $$o" ""'''])
1228     #
1229     path, dia = checkProg('a Dia -> Image converter', ['dia'])
1230     if dia == 'dia':
1231         addToRC(r'''\converter dia        png        "dia -e $$o -t png $$i"    ""
1232 \converter dia        eps        "dia -e $$o -t eps $$i"        ""
1233 \converter dia        svg        "dia -e $$o -t svg $$i"        ""''')
1234
1235     #
1236     # Actually, this produces EPS, but with a wrong bounding box (usually A4 or letter).
1237     # The eps2->eps converter then fixes the bounding box by cropping.
1238     # Although unoconv can convert to png and pdf as well, do not define
1239     # odg->png and odg->pdf converters, since the bb would be too large as well.
1240     checkProg('an OpenDocument -> EPS converter', ['libreoffice --headless --nologo --convert-to eps $$i', 'unoconv -f eps --stdout $$i > $$o'],
1241         rc_entry = [ r'\converter odg        eps2       "%%"    ""'])
1242     #
1243     checkProg('a SVG (compressed) -> SVG converter', ['gunzip -c $$i > $$o'],
1244         rc_entry = [ r'\converter svgz       svg        "%%"    ""'])
1245     #
1246     checkProg('a SVG -> SVG (compressed) converter', ['gzip -c $$i > $$o'],
1247         rc_entry = [ r'\converter svg        svgz       "%%"    ""'])
1248     # Only define a converter to pdf6 for graphics
1249     # Prefer rsvg-convert over inkscape since it is faster (see http://www.lyx.org/trac/ticket/9891)
1250     # inkscape 1.0 has changed cl options
1251     if inkscape_stable:
1252         checkProg('a SVG -> PDF converter', ['rsvg-convert -f pdf -o $$o $$i', inkscape_cl + ' $$i --export-area-drawing --export-filename=$$o'],
1253             rc_entry = [ r'''\converter svg        pdf6       "%%"    ""
1254 \converter svgz       pdf6       "%%"    ""'''],
1255             path = ['', inkscape_path])
1256         #
1257         checkProg('a SVG -> EPS converter', ['rsvg-convert -f ps -o $$o $$i', inkscape_cl + ' $$i --export-area-drawing --export-filename=$$o'],
1258             rc_entry = [ r'''\converter svg        eps        "%%"    ""
1259 \converter svgz       eps        "%%"    ""'''],
1260             path = ['', inkscape_path])
1261         #
1262         checkProg('a SVG -> PNG converter', ['rsvg-convert -f png -o $$o $$i', inkscape_cl + ' $$i --export-filename=$$o'],
1263             rc_entry = [ r'''\converter svg        png        "%%"    "",
1264 \converter svgz       png        "%%"    ""'''],
1265             path = ['', inkscape_path])
1266     else:
1267         checkProg('a SVG -> PDF converter', ['rsvg-convert -f pdf -o $$o $$i', inkscape_cl + ' --file=$$i --export-area-drawing --without-gui --export-pdf=$$o'],
1268             rc_entry = [ r'''\converter svg        pdf6       "%%"    ""
1269 \converter svgz       pdf6       "%%"    ""'''],
1270             path = ['', inkscape_path])
1271         #
1272         checkProg('a SVG -> EPS converter', ['rsvg-convert -f ps -o $$o $$i', inkscape_cl + ' --file=$$i --export-area-drawing --without-gui --export-eps=$$o'],
1273             rc_entry = [ r'''\converter svg        eps        "%%"    ""
1274 \converter svgz       eps        "%%"    ""'''],
1275             path = ['', inkscape_path])
1276         #
1277         checkProg('a SVG -> PNG converter', ['rsvg-convert -f png -o $$o $$i', inkscape_cl + ' --without-gui --file=$$i --export-png=$$o'],
1278             rc_entry = [ r'''\converter svg        png        "%%"    "",
1279 \converter svgz       png        "%%"    ""'''],
1280             path = ['', inkscape_path])
1281     #
1282     checkProg('Gnuplot', ['gnuplot'],
1283         rc_entry = [ r'''\Format gnuplot     "gp, gnuplot"    "Gnuplot"     "" "" ""  "vector"  "text/plain"
1284 \converter gnuplot      pdf6      "$${python} $$s/scripts/gnuplot2pdf.py $$i $$o"    "needauth"''' ])
1285     #
1286     # gnumeric/xls/ods to tex
1287     checkProg('a spreadsheet -> latex converter', ['ssconvert'],
1288        rc_entry = [ r'''\converter gnumeric latex "ssconvert --export-type=Gnumeric_html:latex $$i $$o" ""
1289 \converter oocalc latex "ssconvert --export-type=Gnumeric_html:latex $$i $$o" ""
1290 \converter excel  latex "ssconvert --export-type=Gnumeric_html:latex $$i $$o" ""
1291 \converter excel2 latex "ssconvert --export-type=Gnumeric_html:latex $$i $$o" ""
1292 \converter gnumeric html_table "ssconvert --export-type=Gnumeric_html:html40frag $$i $$o" ""
1293 \converter oocalc html_table "ssconvert --export-type=Gnumeric_html:html40frag $$i $$o" ""
1294 \converter excel  html_table "ssconvert --export-type=Gnumeric_html:html40frag $$i $$o" ""
1295 \converter excel2 html_table "ssconvert --export-type=Gnumeric_html:html40frag $$i $$o" ""
1296 \converter gnumeric xhtml_table "$${python} $$s/scripts/spreadsheet_to_docbook.py $$i $$o" ""
1297 \converter oocalc xhtml_table "$${python} $$s/scripts/spreadsheet_to_docbook.py $$i $$o" ""
1298 \converter excel  xhtml_table "$${python} $$s/scripts/spreadsheet_to_docbook.py $$i $$o" ""
1299 \converter excel2 xhtml_table "$${python} $$s/scripts/spreadsheet_to_docbook.py $$i $$o" ""
1300 '''])
1301
1302     path, lilypond = checkProg('a LilyPond -> EPS/PDF/PNG converter', ['lilypond'])
1303     if (lilypond):
1304         version_string = cmdOutput("lilypond --version")
1305         match = re.match(r'GNU LilyPond (\S+)', version_string)
1306         if match:
1307             version_number = match.groups()[0]
1308             version = version_number.split('.')
1309             if int(version[0]) > 2 or (len(version) > 1 and int(version[0]) == 2 and int(version[1]) >= 11):
1310                 addToRC(r'''\converter lilypond   eps        "lilypond -dbackend=eps -dsafe --ps $$i"   ""
1311 \converter lilypond   png        "lilypond -dbackend=eps -dsafe --png $$i"      ""''')
1312                 addToRC(r'\converter lilypond   pdf6       "lilypond -dbackend=eps -dsafe --pdf $$i"    ""')
1313                 logger.info('+  found LilyPond version %s.' % version_number)
1314             elif int(version[0]) > 2 or (len(version) > 1 and int(version[0]) == 2 and int(version[1]) >= 6):
1315                 addToRC(r'''\converter lilypond   eps        "lilypond -b eps --ps --safe $$i"  ""
1316 \converter lilypond   png        "lilypond -b eps --png $$i"    ""''')
1317                 if int(version[0]) > 2 or (len(version) > 1 and int(version[0]) == 2 and int(version[1]) >= 9):
1318                     addToRC(r'\converter lilypond   pdf6       "lilypond -b eps --pdf --safe $$i"       ""')
1319                 logger.info('+  found LilyPond version %s.' % version_number)
1320             else:
1321                 logger.info('+  found LilyPond, but version %s is too old.' % version_number)
1322         else:
1323             logger.info('+  found LilyPond, but could not extract version number.')
1324     #
1325     path, lilypond_book = checkProg('a LilyPond book (LaTeX) -> LaTeX converter', ['lilypond-book'])
1326     if lilypond_book:
1327         found_lilypond_book = False
1328         # On Windows, the file lilypond-book is not directly callable, it must be passed as argument to python.
1329         for cmd in ["lilypond-book", os.path.basename(sys.executable) + ' "' + path + '/lilypond-book"']:
1330             version_string = cmdOutput(cmd + " --version")
1331             if len(version_string) == 0:
1332                 continue
1333             found_lilypond_book = True
1334
1335             match = re.match(r'(\S+)$', version_string)
1336             if match:
1337                 version_number = match.groups()[0]
1338                 version = version_number.split('.')
1339                 if int(version[0]) > 2 or (len(version) > 1 and int(version[0]) == 2 and int(version[1]) >= 13):
1340                     # Note: The --lily-output-dir flag is required because lilypond-book
1341                     #       does not process input again unless the input has changed,
1342                     #       even if the output format being requested is different. So
1343                     #       once a .eps file exists, lilypond-book won't create a .pdf
1344                     #       even when requested with --pdf. This is a problem if a user
1345                     #       clicks View PDF after having done a View DVI. To circumvent
1346                     #       this, use different output folders for eps and pdf outputs.
1347                     cmd = cmd.replace('"', r'\"')
1348                     addToRC(r'\converter lilypond-book latex     "' + cmd + ' --safe --lily-output-dir=ly-eps $$i"                                ""')
1349                     addToRC(r'\converter lilypond-book pdflatex  "' + cmd + ' --safe --pdf --latex-program=pdflatex --lily-output-dir=ly-pdf $$i" ""')
1350                     addToRC(r'\converter lilypond-book-ja platex "' + cmd + ' --safe --pdf --latex-program=platex --lily-output-dir=ly-pdf $$i" ""')
1351                     addToRC(r'\converter lilypond-book xetex     "' + cmd + ' --safe --pdf --latex-program=xelatex --lily-output-dir=ly-pdf $$i"  ""')
1352                     addToRC(r'\converter lilypond-book luatex    "' + cmd + ' --safe --pdf --latex-program=lualatex --lily-output-dir=ly-pdf $$i" ""')
1353                     addToRC(r'\converter lilypond-book dviluatex "' + cmd + ' --safe --latex-program=dvilualatex --lily-output-dir=ly-eps $$i" ""')
1354
1355                     # Also create the entry to apply LilyPond on DocBook files. However,
1356                     # command must be passed as argument, and it might already have
1357                     # quoted parts. LyX doesn't yet handle double-quoting of commands.
1358                     # Hence, pass as argument either cmd (if it's a simple command) or
1359                     # the Python file that should be called (typical on Windows).
1360                     docbook_lilypond_cmd = cmd
1361                     if "python" in docbook_lilypond_cmd:
1362                         docbook_lilypond_cmd = '"' + path + '/lilypond-book"'
1363                     addToRC(r'\copier docbook5 "$${python} $$s/scripts/docbook_copy.py ' + docbook_lilypond_cmd.replace('"', r'\"') + r' $$i $$o"')
1364
1365                     logger.info('+  found LilyPond-book version %s.' % version_number)
1366
1367                     # early exit on first match, avoid 2nd try with python call
1368                     # in case of lilypond-book being an executable or shell script the python call is useless
1369                     break
1370                 else:
1371                     logger.info('+  found LilyPond-book, but version %s is too old.' % version_number)
1372         if not found_lilypond_book:
1373             logger.info('+  found LilyPond-book, but could not extract version number.')
1374     #
1375     checkProg('a Noteedit -> LilyPond converter', ['noteedit --export-lilypond $$i'],
1376         rc_entry = [ r'\converter noteedit   lilypond   "%%"    ""' ])
1377     #
1378     # Currently, lyxpak outputs a gzip compressed tar archive on *nix
1379     # and a zip archive on Windows.
1380     # So, we configure the appropriate version according to the platform.
1381     cmd = r'\converter lyx %s "$${python} $$s/scripts/lyxpak.py $$r/$$f" ""'
1382     if os.name == 'nt':
1383         addToRC(r'\Format lyxzip     zip    "LyX Archive (zip)"     "" "" ""  "document,menu=export"    ""')
1384         addToRC(cmd % "lyxzip")
1385     else:
1386         addToRC(r'\Format lyxgz      gz     "LyX Archive (tar.gz)"  "" "" ""  "document,menu=export"    ""')
1387         addToRC(cmd % "lyxgz")
1388
1389     #
1390     # FIXME: no rc_entry? comment it out
1391     # checkProg('Image converter', ['convert $$i $$o'])
1392     #
1393     # Entries that do not need checkProg
1394     addToRC(r'''
1395 \converter csv        lyx        "$${python} $$s/scripts/csv2lyx.py $$i $$o"    ""
1396 \converter fen        asciichess "$${python} $$s/scripts/fen2ascii.py $$i $$o"  ""
1397 \converter lyx        lyx13x     "$${python} $$s/lyx2lyx/lyx2lyx -V 1.3 -o $$o $$i"     ""
1398 \converter lyx        lyx14x     "$${python} $$s/lyx2lyx/lyx2lyx -V 1.4 -o $$o $$i"     ""
1399 \converter lyx        lyx15x     "$${python} $$s/lyx2lyx/lyx2lyx -V 1.5 -o $$o $$i"     ""
1400 \converter lyx        lyx16x     "$${python} $$s/lyx2lyx/lyx2lyx -V 1.6 -o $$o $$i"     ""
1401 \converter lyx        lyx20x     "$${python} $$s/lyx2lyx/lyx2lyx -V 2.0 -o $$o $$i"     ""
1402 \converter lyx        lyx21x     "$${python} $$s/lyx2lyx/lyx2lyx -V 2.1 -o $$o $$i"     ""
1403 \converter lyx        lyx22x     "$${python} $$s/lyx2lyx/lyx2lyx -V 2.2 -o $$o $$i"     ""
1404 \converter lyx        lyx23x     "$${python} $$s/lyx2lyx/lyx2lyx -V 2.3 -o $$o $$i"     ""
1405 \converter lyx        clyx       "$${python} $$s/lyx2lyx/lyx2lyx -V 1.4 -o $$o -c big5   $$i"   ""
1406 \converter lyx        jlyx       "$${python} $$s/lyx2lyx/lyx2lyx -V 1.4 -o $$o -c euc_jp $$i"   ""
1407 \converter lyx        klyx       "$${python} $$s/lyx2lyx/lyx2lyx -V 1.4 -o $$o -c euc_kr $$i"   ""
1408 \converter clyx       lyx        "$${python} $$s/lyx2lyx/lyx2lyx -c big5   -o $$o $$i"  ""
1409 \converter jlyx       lyx        "$${python} $$s/lyx2lyx/lyx2lyx -c euc_jp -o $$o $$i"  ""
1410 \converter klyx       lyx        "$${python} $$s/lyx2lyx/lyx2lyx -c euc_kr -o $$o $$i"  ""
1411 \converter lyxpreview png        "$${python} $$s/scripts/lyxpreview2bitmap.py --png"    ""
1412 \converter lyxpreview ppm        "$${python} $$s/scripts/lyxpreview2bitmap.py --ppm"    ""
1413 ''')
1414
1415
1416 def checkOtherEntries():
1417     ''' entries other than Format and Converter '''
1418     checkProg('ChkTeX', ['chktex -n1 -n3 -n6 -n9 -n22 -n25 -n30 -n38'],
1419         rc_entry = [ r'\chktex_command "%%"' ])
1420     checkProgAlternatives('BibTeX or alternative programs',
1421         ['bibtex', 'bibtex8', 'biber'],
1422         rc_entry = [ r'\bibtex_command "automatic"' ],
1423         alt_rc_entry = [ r'\bibtex_alternatives "%%"' ])
1424     checkProgAlternatives('a specific Japanese BibTeX variant',
1425         ['pbibtex', 'upbibtex', 'jbibtex', 'bibtex', 'biber'],
1426         rc_entry = [ r'\jbibtex_command "automatic"' ],
1427         alt_rc_entry = [ r'\jbibtex_alternatives "%%"' ])
1428     checkProgAlternatives('available index processors',
1429         ['texindy $$x -t $$b.ilg', 'xindex -l $$lcode', 'makeindex -c -q', 'xindy -M texindy $$x -t $$b.ilg'],
1430         rc_entry = [ r'\index_command "%%"' ],
1431         alt_rc_entry = [ r'\index_alternatives "%%"' ])
1432     checkProg('an index processor appropriate to Japanese',
1433         ['mendex -c -q', 'jmakeindex -c -q', 'makeindex -c -q'],
1434         rc_entry = [ r'\jindex_command "%%"' ])
1435     checkProg('the splitindex processor', ['splitindex.pl', 'splitindex',
1436         'splitindex.class'], rc_entry = [ r'\splitindex_command "%%"' ])
1437     checkProg('a nomenclature processor', ['makeindex'],
1438         rc_entry = [ r'\nomencl_command "makeindex -s nomencl.ist"' ])
1439     checkProg('a python-pygments driver command', ['pygmentize'],
1440         rc_entry = [ r'\pygmentize_command "%%"' ])
1441     ## FIXME: OCTAVE is not used anywhere
1442     # path, OCTAVE = checkProg('Octave', ['octave'])
1443     ## FIXME: MAPLE is not used anywhere
1444     # path, MAPLE = checkProg('Maple', ['maple'])
1445     # Add the rest of the entries (no checkProg is required)
1446     addToRC(r'''\citation_search_view "$${python} $$s/scripts/lyxpaperview.py"''')
1447     addToRC(r'''\copier    fig        "$${python} $$s/scripts/fig_copy.py $$i $$o"
1448 \copier    pstex      "$${python} $$s/scripts/tex_copy.py $$i $$o $$l"
1449 \copier    pdftex     "$${python} $$s/scripts/tex_copy.py $$i $$o $$l"
1450 \copier    program    "$${python} $$s/scripts/ext_copy.py $$i $$o"
1451 ''')
1452
1453 def _checkForClassExtension(x):
1454     '''if the extension for a latex class is not
1455         provided, add .cls to the classname'''
1456     if not '.' in x:
1457         return x.strip() + '.cls'
1458     else:
1459         return x.strip()
1460
1461 def processLayoutFile(file):
1462     r""" process layout file and get a line of result
1463
1464         Declare lines look like this:
1465
1466         \DeclareLaTeXClass[<requirements>]{<description>}
1467
1468         Optionally, a \DeclareCategory line follows:
1469
1470         \DeclareCategory{<category>}
1471
1472         So for example (article.layout, scrbook.layout, svjog.layout)
1473
1474         \DeclareLaTeXClass{article}
1475         \DeclareCategory{Articles}
1476
1477         \DeclareLaTeXClass[scrbook]{book (koma-script)}
1478         \DeclareCategory{Books}
1479
1480         \DeclareLaTeXClass[svjour,svjog.clo]{article (Springer - svjour/jog)}
1481
1482         we'd expect this output:
1483
1484         "article" "article" "article" "false" "article.cls" "Articles"
1485         "scrbook" "scrbook" "book (koma-script)" "false" "scrbook.cls" "Books"
1486         "svjog" "svjour" "article (Springer - svjour/jog)" "false" "svjour.cls,svjog.clo" ""
1487     """
1488     classname = file.split(os.sep)[-1].split('.')[0]
1489     # return ('[a,b]', 'a', ',b,c', 'article') for \DeclareLaTeXClass[a,b,c]{article}
1490     p = re.compile('\\s*#\\s*\\\\DeclareLaTeXClass\\s*(\\[([^,]*)(,.*)*])*\\s*{(.*)}\\s*$')
1491     q = re.compile('\\s*#\\s*\\\\DeclareCategory{(.*)}\\s*$')
1492     classdeclaration = ""
1493     categorydeclaration = '""'
1494     for line in open(file, 'r', encoding='utf8').readlines():
1495         res = p.match(line)
1496         qres = q.match(line)
1497         if res is not None:
1498             (optAll, opt, opt1, desc) = res.groups()
1499             if opt is None:
1500                 opt = classname
1501                 prereq = _checkForClassExtension(classname)
1502             else:
1503                 prereq_list = optAll[1:-1].split(',')
1504                 prereq_list = list(map(_checkForClassExtension, prereq_list))
1505                 prereq = ','.join(prereq_list)
1506             classdeclaration = ('"%s" "%s" "%s" "%s" "%s"'
1507                                % (classname, opt, desc, 'false', prereq))
1508             if categorydeclaration != '""':
1509                 return classdeclaration + " " + categorydeclaration
1510         if qres is not None:
1511             categorydeclaration = '"%s"' % (qres.groups()[0])
1512             if classdeclaration:
1513                 return classdeclaration + " " + categorydeclaration
1514     if classdeclaration:
1515         return classdeclaration + " " + categorydeclaration
1516     logger.warning("Layout file " + file + " has no \\DeclareLaTeXClass line. ")
1517     return ""
1518
1519
1520 def checkLatexConfig(check_config):
1521     ''' Explore the LaTeX configuration
1522         Return None (will be passed to sys.exit()) for success.
1523     '''
1524     msg = 'checking LaTeX configuration... '
1525     # if --without-latex-config is forced, or if there is no previous
1526     # version of textclass.lst, re-generate a default file.
1527     if not os.path.isfile('textclass.lst') or not check_config:
1528         # remove the files only if we want to regenerate
1529         removeFiles(['textclass.lst', 'packages.lst'])
1530         #
1531         # Then, generate a default textclass.lst. In case configure.py
1532         # fails, we still have something to start lyx.
1533         logger.info(msg + ' default values')
1534         logger.info('+checking list of textclasses... ')
1535         tx = open('textclass.lst', 'w', encoding='utf8')
1536         tx.write('''
1537 # This file declares layouts and their associated definition files
1538 # (include dir. relative to the place where this file is).
1539 # It contains only default values, since chkconfig.ltx could not be run
1540 # for some reason. Run ./configure.py if you need to update it after a
1541 # configuration change.
1542 ''')
1543         # build the list of available layout files and convert it to commands
1544         # for chkconfig.ltx
1545         foundClasses = []
1546         for file in (glob.glob(os.path.join('layouts', '*.layout'))
1547                      + glob.glob(os.path.join(srcdir, 'layouts', '*.layout'))):
1548             # valid file?
1549             if not os.path.isfile(file):
1550                 continue
1551             # get stuff between /xxxx.layout .
1552             classname = file.split(os.sep)[-1].split('.')[0]
1553             #  tr ' -' '__'`
1554             cleanclass = classname.replace(' ', '_').replace('-', '_')
1555             # make sure the same class is not considered twice
1556             if foundClasses.count(cleanclass) == 0: # not found before
1557                 foundClasses.append(cleanclass)
1558                 retval = processLayoutFile(file)
1559                 if retval:
1560                     tx.write(retval + os.linesep)
1561         tx.close()
1562         logger.info('\tdone')
1563     if not os.path.isfile('packages.lst') or not check_config:
1564         logger.info('+generating default list of packages... ')
1565         removeFiles(['packages.lst'])
1566         tx = open('packages.lst', 'w', encoding='utf8')
1567         tx.close()
1568         logger.info('\tdone')
1569     if not check_config:
1570         return None
1571     # the following will generate textclass.lst.tmp, and packages.lst.tmp
1572     logger.info(msg + '\tauto')
1573     removeFiles(['chkconfig.classes', 'chkconfig.vars', 'chklayouts.tex',
1574         'wrap_chkconfig.ltx'])
1575     rmcopy = False
1576     if not os.path.isfile( 'chkconfig.ltx' ):
1577         shutil.copyfile( os.path.join(srcdir, 'chkconfig.ltx'), 'chkconfig.ltx' )
1578         rmcopy = True
1579     writeToFile('wrap_chkconfig.ltx', '\\def\\hasdocbook{yes}\n\\input{chkconfig.ltx}\n')
1580     # Construct the list of classes to test for.
1581     # build the list of available layout files and convert it to commands
1582     # for chkconfig.ltx
1583     declare = re.compile('\\s*#\\s*\\\\DeclareLaTeXClass\\s*(\\[([^,]*)(,.*)*\\])*\\s*{(.*)}\\s*$')
1584     category = re.compile('\\s*#\\s*\\\\DeclareCategory{(.*)}\\s*$')
1585     empty = re.compile('\\s*$')
1586     testclasses = list()
1587     for file in (glob.glob( os.path.join('layouts', '*.layout') )
1588                  + glob.glob( os.path.join(srcdir, 'layouts', '*.layout' ) ) ):
1589         nodeclaration = False
1590         if not os.path.isfile(file):
1591             continue
1592         classname = file.split(os.sep)[-1].split('.')[0]
1593         decline = ""
1594         catline = ""
1595         try:
1596             for line in open(file, 'r', encoding='utf8').readlines():
1597                 if not empty.match(line) and line[0] != '#'[0]:
1598                     if decline == "":
1599                         logger.warning(r"Failed to find valid \Declare line "
1600                             "for layout file `%s'.\n\t=> Skipping this file!" % file)
1601                         nodeclaration = True
1602                     # A class, but no category declaration. Just break.
1603                     break
1604                 if declare.match(line) is not None:
1605                     decline = "\\TestDocClass{%s}{%s}" % (classname, line[1:].strip())
1606                     testclasses.append(decline)
1607                 elif category.match(line) is not None:
1608                     catline = ("\\DeclareCategory{%s}{%s}"
1609                                % (classname, category.match(line).groups()[0]))
1610                     testclasses.append(catline)
1611                 if catline == "" or decline == "":
1612                     continue
1613                 break
1614             if nodeclaration:
1615                 continue
1616         except UnicodeDecodeError:
1617             logger.warning("**************************************************\n"
1618                            "Layout file '%s'\n"
1619                            "cannot be decoded in utf-8.\n"
1620                            "Please check if the file has the correct encoding.\n"
1621                            "Skipping this file!\n"
1622                            "**************************************************" % file)
1623             continue
1624     testclasses.sort()
1625     cl = open('chklayouts.tex', 'w', encoding='utf8')
1626     for line in testclasses:
1627         cl.write(line + '\n')
1628     cl.close()
1629     #
1630     # we have chklayouts.tex, then process it
1631     latex_out = cmdOutput(LATEX + ' wrap_chkconfig.ltx', True)
1632     while True:
1633         line = latex_out.readline()
1634         if not line:
1635             break;
1636         if line.startswith('+'):
1637             logger.info(line.strip())
1638     # if the command succeeds, None will be returned
1639     ret = latex_out.close()
1640     #
1641     # remove the copied file
1642     if rmcopy:
1643         removeFiles( [ 'chkconfig.ltx' ] )
1644     #
1645     # values in chkconfig were only used to set
1646     # \font_encoding, which is obsolete
1647 #    values = {}
1648 #    for line in open('chkconfig.vars').readlines():
1649 #        key, val = re.sub('-', '_', line).split('=')
1650 #        val = val.strip()
1651 #        values[key] = val.strip("'")
1652     # if configure successed, move textclass.lst.tmp to textclass.lst
1653     # and packages.lst.tmp to packages.lst
1654     if (os.path.isfile('textclass.lst.tmp')
1655           and len(open('textclass.lst.tmp', encoding='utf8').read()) > 0
1656         and os.path.isfile('packages.lst.tmp')
1657           and len(open('packages.lst.tmp', encoding='utf8').read()) > 0):
1658         shutil.move('textclass.lst.tmp', 'textclass.lst')
1659         shutil.move('packages.lst.tmp', 'packages.lst')
1660     return ret
1661
1662
1663 def checkModulesConfig():
1664   removeFiles(['lyxmodules.lst', 'chkmodules.tex'])
1665
1666   logger.info('+checking list of modules... ')
1667   tx = open('lyxmodules.lst', 'w', encoding='utf8')
1668   tx.write('''## This file declares modules and their associated definition files.
1669 ## It has been automatically generated by configure
1670 ## Use "Options/Reconfigure" if you need to update it after a
1671 ## configuration change.
1672 ## "ModuleName" "filename" "Description" "Packages" "Requires" "Excludes" "Category" "Local"
1673 ''')
1674
1675   # build the list of available modules
1676   seen = []
1677   # note that this searches the local directory first, then the
1678   # system directory. that way, we pick up the user's version first.
1679   for file in (glob.glob( os.path.join('layouts', '*.module') )
1680                + glob.glob( os.path.join(srcdir, 'layouts', '*.module' ) ) ):
1681       # valid file?
1682       logger.info(file)
1683       if not os.path.isfile(file):
1684           continue
1685
1686       filename = file.split(os.sep)[-1]
1687       filename = filename[:-7]
1688       if seen.count(filename):
1689           continue
1690
1691       seen.append(filename)
1692       try:
1693           retval = processModuleFile(file, filename)
1694           if retval:
1695               tx.write(retval)
1696       except UnicodeDecodeError:
1697           logger.warning("**************************************************\n"
1698                          "Module file '%s'\n"
1699                          "cannot be decoded in utf-8.\n"
1700                          "Please check if the file has the correct encoding.\n"
1701                          "Skipping this file!\n"
1702                          "**************************************************" % filename)
1703   tx.close()
1704   logger.info('\tdone')
1705
1706
1707 def processModuleFile(file, filename):
1708     r''' process module file and get a line of result
1709
1710         The top of a module file should look like this:
1711           #\DeclareLyXModule[LaTeX Packages]{ModuleName}
1712           #DescriptionBegin
1713           #...body of description...
1714           #DescriptionEnd
1715           #Requires: [list of required modules]
1716           #Excludes: [list of excluded modules]
1717           #Category: [category name]
1718         The last three lines are optional (though do give a category).
1719         We expect output:
1720           "ModuleName" "filename" "Description" "Packages" "Requires" "Excludes" "Category"
1721     '''
1722     remods = re.compile('\\s*#\\s*\\\\DeclareLyXModule\\s*(?:\\[([^]]*?)\\])?{(.*)}')
1723     rereqs = re.compile(r'\s*#+\s*Requires: (.*)')
1724     reexcs = re.compile(r'\s*#+\s*Excludes: (.*)')
1725     recaty = re.compile('\\s*#\\s*\\\\DeclareCategory{(.*)}\\s*$')
1726     redbeg = re.compile(r'\s*#+\s*DescriptionBegin\s*$')
1727     redend = re.compile(r'\s*#+\s*DescriptionEnd\s*$')
1728
1729     modname = desc = pkgs = req = excl = catgy = ""
1730     readingDescription = False
1731     descLines = []
1732
1733     for line in open(file, 'r', encoding='utf8').readlines():
1734       if readingDescription:
1735         res = redend.match(line)
1736         if res != None:
1737           readingDescription = False
1738           desc = " ".join(descLines)
1739           # Escape quotes.
1740           desc = desc.replace('"', '\\"')
1741           continue
1742         descLines.append(line[1:].strip())
1743         continue
1744       res = redbeg.match(line)
1745       if res != None:
1746         readingDescription = True
1747         continue
1748       res = remods.match(line)
1749       if res != None:
1750           (pkgs, modname) = res.groups()
1751           if pkgs == None:
1752             pkgs = ""
1753           else:
1754             tmp = [s.strip() for s in pkgs.split(",")]
1755             pkgs = ",".join(tmp)
1756           continue
1757       res = rereqs.match(line)
1758       if res != None:
1759         req = res.group(1)
1760         tmp = [s.strip() for s in req.split("|")]
1761         req = "|".join(tmp)
1762         continue
1763       res = reexcs.match(line)
1764       if res != None:
1765         excl = res.group(1)
1766         tmp = [s.strip() for s in excl.split("|")]
1767         excl = "|".join(tmp)
1768         continue
1769       res = recaty.match(line)
1770       if res != None:
1771         catgy = res.group(1)
1772         continue
1773
1774     if modname == "":
1775       logger.warning(r"Module file without \DeclareLyXModule line. ")
1776       return ""
1777
1778     if pkgs:
1779         # this module has some latex dependencies:
1780         # append the dependencies to chkmodules.tex,
1781         # which is \input'ed by chkconfig.ltx
1782         testpackages = list()
1783         for pkg in pkgs.split(","):
1784             if "->" in pkg:
1785                 # this is a converter dependency: skip
1786                 continue
1787             if pkg.endswith(".sty"):
1788                 pkg = pkg[:-4]
1789             testpackages.append("\\TestPackage{%s}" % pkg)
1790         cm = open('chkmodules.tex', 'a', encoding='utf8')
1791         for line in testpackages:
1792             cm.write(line + '\n')
1793         cm.close()
1794
1795     local = "true"
1796     if (file.startswith(srcdir)):
1797         local = "false"
1798     return ('"%s" "%s" "%s" "%s" "%s" "%s" "%s" "%s"\n'
1799             % (modname, filename, desc, pkgs, req, excl, catgy, local))
1800
1801
1802 def checkCiteEnginesConfig():
1803   removeFiles(['lyxciteengines.lst', 'chkciteengines.tex'])
1804
1805   logger.info('+checking list of cite engines... ')
1806   tx = open('lyxciteengines.lst', 'w', encoding='utf8')
1807   tx.write('''## This file declares cite engines and their associated definition files.
1808 ## It has been automatically generated by configure
1809 ## Use "Options/Reconfigure" if you need to update it after a
1810 ## configuration change.
1811 ## "CiteEngineName" "filename" "CiteEngineType" "CiteFramework" "DefaultBiblio" "Description" "Packages"
1812 ''')
1813
1814   # build the list of available modules
1815   seen = []
1816   # note that this searches the local directory first, then the
1817   # system directory. that way, we pick up the user's version first.
1818   for file in glob.glob( os.path.join('citeengines', '*.citeengine') ) + \
1819       glob.glob( os.path.join(srcdir, 'citeengines', '*.citeengine' ) ) :
1820       # valid file?
1821       logger.info(file)
1822       if not os.path.isfile(file):
1823           continue
1824
1825       filename = file.split(os.sep)[-1]
1826       filename = filename[:-11]
1827       if seen.count(filename):
1828           continue
1829
1830       seen.append(filename)
1831       retval = processCiteEngineFile(file, filename)
1832       if retval:
1833           tx.write(retval)
1834   tx.close()
1835   logger.info('\tdone')
1836
1837
1838 def processCiteEngineFile(file, filename):
1839     r''' process cite engines file and get a line of result
1840
1841         The top of a cite engine file should look like this:
1842           #\DeclareLyXCiteEngine[LaTeX Packages]{CiteEngineName}
1843           #DescriptionBegin
1844           #...body of description...
1845           #DescriptionEnd
1846         We expect output:
1847           "CiteEngineName" "filename" "CiteEngineType" "CiteFramework" "DefaultBiblio" "Description" "Packages"
1848     '''
1849     remods = re.compile('\\s*#\\s*\\\\DeclareLyXCiteEngine\\s*(?:\\[([^]]*?)\\])?{(.*)}')
1850     redbeg = re.compile(r'\s*#+\s*DescriptionBegin\s*$')
1851     redend = re.compile(r'\s*#+\s*DescriptionEnd\s*$')
1852     recet = re.compile(r'\s*CiteEngineType\s*(.*)')
1853     redb = re.compile(r'\s*DefaultBiblio\s*(.*)')
1854     resfm = re.compile(r'\s*CiteFramework\s*(.*)')
1855
1856     modname = desc = pkgs = cet = db = cfm = ""
1857     readingDescription = False
1858     descLines = []
1859
1860     for line in open(file, 'r', encoding='utf8').readlines():
1861       if readingDescription:
1862         res = redend.match(line)
1863         if res != None:
1864           readingDescription = False
1865           desc = " ".join(descLines)
1866           # Escape quotes.
1867           desc = desc.replace('"', '\\"')
1868           continue
1869         descLines.append(line[1:].strip())
1870         continue
1871       res = redbeg.match(line)
1872       if res != None:
1873         readingDescription = True
1874         continue
1875       res = remods.match(line)
1876       if res != None:
1877           (pkgs, modname) = res.groups()
1878           if pkgs == None:
1879             pkgs = ""
1880           else:
1881             tmp = [s.strip() for s in pkgs.split(",")]
1882             pkgs = ",".join(tmp)
1883           continue
1884       res = recet.match(line)
1885       if res != None:
1886         cet = res.group(1)
1887         continue
1888       res = redb.match(line)
1889       if res != None:
1890         db = res.group(1)
1891         continue
1892       res = resfm.match(line)
1893       if res != None:
1894         cfm = res.group(1)
1895         continue
1896
1897     if modname == "":
1898       logger.warning(r"Cite Engine File file without \DeclareLyXCiteEngine line. ")
1899       return ""
1900
1901     if pkgs:
1902         # this cite engine has some latex dependencies:
1903         # append the dependencies to chkciteengines.tex,
1904         # which is \input'ed by chkconfig.ltx
1905         testpackages = list()
1906         for pkg in pkgs.split(","):
1907             if "->" in pkg:
1908                 # this is a converter dependency: skip
1909                 continue
1910             if pkg.endswith(".sty"):
1911                 pkg = pkg[:-4]
1912             testpackages.append("\\TestPackage{%s}" % pkg)
1913         cm = open('chkciteengines.tex', 'a', encoding='utf8')
1914         for line in testpackages:
1915             cm.write(line + '\n')
1916         cm.close()
1917
1918     return ('"%s" "%s" "%s" "%s" "%s" "%s" "%s"\n'
1919             % (modname, filename, cet, cfm, db, desc, pkgs))
1920
1921
1922 def checkXTemplates():
1923   removeFiles(['xtemplates.lst'])
1924
1925   logger.info('+checking list of external templates... ')
1926   tx = open('xtemplates.lst', 'w', encoding='utf8')
1927   tx.write('''## This file lists external templates.
1928 ## It has been automatically generated by configure
1929 ## Use "Options/Reconfigure" if you need to update it after a
1930 ## configuration change.
1931 ''')
1932
1933   # build the list of available templates
1934   seen = []
1935   # note that this searches the local directory first, then the
1936   # system directory. that way, we pick up the user's version first.
1937   for file in glob.glob( os.path.join('xtemplates', '*.xtemplate') ) + \
1938       glob.glob( os.path.join(srcdir, 'xtemplates', '*.xtemplate' ) ) :
1939       # valid file?
1940       logger.info(file)
1941       if not os.path.isfile(file):
1942           continue
1943
1944       filename = file.split(os.sep)[-1]
1945       if seen.count(filename):
1946           continue
1947
1948       seen.append(filename)
1949       if filename:
1950           tx.write(filename + "\n")
1951   tx.close()
1952   logger.info('\tdone')
1953
1954
1955 def checkTeXAllowSpaces():
1956     ''' Let's check whether spaces are allowed in TeX file names '''
1957     tex_allows_spaces = 'false'
1958     if lyx_check_config:
1959         msg = "Checking whether TeX allows spaces in file names... "
1960         writeToFile('a b.tex', r'\message{working^^J}' )
1961         if LATEX:
1962             if os.name == 'nt' or sys.platform == 'cygwin':
1963                 latex_out = cmdOutput(LATEX + r""" "\nonstopmode\input{\"a b\"}\makeatletter\@@end" """)
1964             else:
1965                 latex_out = cmdOutput(LATEX + r""" '\nonstopmode\input{"a b"}\makeatletter\@@end' """)
1966         else:
1967             latex_out = ''
1968         if 'working' in latex_out:
1969             logger.info(msg + 'yes')
1970             tex_allows_spaces = 'true'
1971         else:
1972             logger.info(msg + 'no')
1973             tex_allows_spaces = 'false'
1974         addToRC(r'\tex_allows_spaces ' + tex_allows_spaces)
1975         removeFiles( [ 'a b.tex', 'a b.log', 'texput.log' ])
1976
1977
1978 def rescanTeXFiles():
1979     ''' Run kpsewhich to update information about TeX files '''
1980     logger.info("+Indexing TeX files... ")
1981     tfscript = os.path.join(srcdir, 'scripts', 'TeXFiles.py')
1982     if not os.path.isfile(tfscript):
1983         logger.error("configure: error: cannot find TeXFiles.py script")
1984         sys.exit(1)
1985     interpreter = sys.executable
1986     if interpreter == '':
1987         interpreter = "python"
1988     tfp = cmdOutput('"%s" -tt "%s"' % (interpreter, tfscript))
1989     logger.info(tfp)
1990     logger.info("\tdone")
1991
1992
1993 def removeTempFiles():
1994     # Final clean-up
1995     if not lyx_keep_temps:
1996         removeFiles(['chkconfig.vars', 'chklatex.ltx', 'chklatex.log',
1997             'chklayouts.tex', 'chkmodules.tex', 'chkciteengines.tex',
1998             'missfont.log', 'wrap_chkconfig.ltx', 'wrap_chkconfig.log'])
1999
2000
2001 if __name__ == '__main__':
2002     lyx_check_config = True
2003     lyx_kpsewhich = True
2004     outfile = 'lyxrc.defaults'
2005     lyxrc_fileformat = 37
2006     rc_entries = ''
2007     lyx_keep_temps = False
2008     version_suffix = ''
2009     lyx_binary_dir = ''
2010     logger.info("+Running LyX configure with Python %s.%s.%s", sys.version_info[0], sys.version_info[1], sys.version_info[2])
2011     ## Parse the command line
2012     for op in sys.argv[1:]:   # default shell/for list is $*, the options
2013         if op in [ '-help', '--help', '-h' ]:
2014             print('''Usage: configure [options]
2015 Options:
2016     --help                   show this help lines
2017     --keep-temps             keep temporary files (for debug. purposes)
2018     --without-kpsewhich      do not update TeX files information via kpsewhich
2019     --without-latex-config   do not run LaTeX to determine configuration
2020     --with-version-suffix=suffix suffix of binary installed files
2021     --binary-dir=directory   directory of binary installed files
2022 ''')
2023             sys.exit(0)
2024         elif op == '--without-kpsewhich':
2025             lyx_kpsewhich = False
2026         elif op == '--without-latex-config':
2027             lyx_check_config = False
2028         elif op == '--keep-temps':
2029             lyx_keep_temps = True
2030         elif op[0:22] == '--with-version-suffix=':  # never mind if op is not long enough
2031             version_suffix = op[22:]
2032         elif op[0:13] == '--binary-dir=':
2033             lyx_binary_dir = op[13:]
2034         else:
2035             print("Unknown option %s" % op)
2036             sys.exit(1)
2037     #
2038     # check if we run from the right directory
2039     srcdir = os.path.dirname(sys.argv[0])
2040     if srcdir == '':
2041         srcdir = '.'
2042     if not os.path.isfile( os.path.join(srcdir, 'chkconfig.ltx') ):
2043         logger.error("configure: error: cannot find chkconfig.ltx script")
2044         sys.exit(1)
2045     setEnviron()
2046     if sys.platform == 'darwin' and len(version_suffix) > 0:
2047         checkUpgrade()
2048     createDirectories()
2049     dtl_tools = checkDTLtools()
2050     ## Write the first part of outfile
2051     writeToFile(outfile, '''# This file has been automatically generated by LyX' lib/configure.py
2052 # script. It contains default settings that have been determined by
2053 # examining your system. PLEASE DO NOT MODIFY ANYTHING HERE! If you
2054 # want to customize LyX, use LyX' Preferences dialog or modify directly
2055 # the "preferences" file instead. Any setting in that file will
2056 # override the values given here.
2057
2058 Format %i
2059
2060 ''' % lyxrc_fileformat)
2061     # check latex
2062     LATEX = checkLatex(dtl_tools)
2063     # check java and perl before any checkProg that may require them
2064     java = checkProg('a java interpreter', ['java'])[1]
2065     if java == '':
2066         java = check_java()
2067     perl = checkProg('a perl interpreter', ['perl'])[1]
2068     xsltproc = checkProg('xsltproc', ['xsltproc'])[1]
2069     (inkscape_path, inkscape_gui) = os.path.split(checkInkscape())
2070     # On Windows, we need to call the "inkscape.com" wrapper
2071     # for command line purposes. Other OSes do not differentiate.
2072     inkscape_cl = inkscape_gui
2073     if os.name == 'nt':
2074         inkscape_cl = inkscape_gui.replace('.exe', '.com')
2075     inkscape_stable = checkInkscapeStable()
2076     checkFormatEntries(dtl_tools)
2077     checkConverterEntries()
2078     checkTeXAllowSpaces()
2079     windows_style_tex_paths = checkTeXPaths()
2080     if windows_style_tex_paths:
2081         addToRC(r'\tex_expects_windows_paths %s' % windows_style_tex_paths)
2082     checkOtherEntries()
2083     if lyx_kpsewhich:
2084         rescanTeXFiles()
2085     checkModulesConfig()
2086     checkCiteEnginesConfig()
2087     checkXTemplates()
2088     # --without-latex-config can disable lyx_check_config
2089     ret = checkLatexConfig(lyx_check_config and LATEX)
2090     removeTempFiles()
2091     # The return error code can be 256. Because most systems expect an error code
2092     # in the range 0-127, 256 can be interpretted as 'success'. Because we expect
2093     # a None for success, 'ret is not None' is used to exit.
2094     sys.exit(ret is not None)