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