]> git.lyx.org Git - lyx.git/blob - lib/configure.py
Fix bug #7552: Use of expression() in plot R command fails
[lyx.git] / lib / configure.py
1 #! /usr/bin/env python
2 # -*- coding: utf-8 -*-
3 #
4 # file configure.py
5 # This file is part of LyX, the document processor.
6 # Licence details can be found in the file COPYING.
7
8 # \author Bo Peng
9 # Full author contact details are available in file CREDITS.
10
11 import sys, os, re, shutil, glob, logging
12
13 # set up logging
14 logging.basicConfig(level = logging.DEBUG,
15     format = '%(levelname)s: %(message)s', # ignore application name
16     filename = 'configure.log',
17     filemode = 'w')
18 #
19 # Add a handler to log to console
20 console = logging.StreamHandler()
21 console.setLevel(logging.INFO) # the console only print out general information
22 formatter = logging.Formatter('%(message)s') # only print out the message itself
23 console.setFormatter(formatter)
24 logger = logging.getLogger('LyX')
25 logger.addHandler(console)
26
27 def writeToFile(filename, lines, append = False):
28     " utility function: write or append lines to filename "
29     if append:
30         file = open(filename, 'a')
31     else:
32         file = open(filename, 'w')
33     file.write(lines)
34     file.close()
35
36
37 def addToRC(lines):
38     ''' utility function: shortcut for appending lines to outfile
39         add newline at the end of lines.
40     '''
41     if lines.strip() != '':
42         writeToFile(outfile, lines + '\n', append = True)
43         logger.debug('Add to RC:\n' + lines + '\n\n')
44
45
46 def removeFiles(filenames):
47     '''utility function: 'rm -f'
48         ignore errors when file does not exist, or is a directory.
49     '''
50     for file in filenames:
51         try:
52             os.remove(file)
53             logger.debug('Removing file %s' % file)
54         except:
55             logger.debug('Failed to remove file %s' % file)
56             pass
57
58
59 def cmdOutput(cmd):
60     '''utility function: run a command and get its output as a string
61         cmd: command to run
62     '''
63     fout = os.popen(cmd)
64     output = fout.read()
65     fout.close()
66     return output.strip()
67
68
69 def setEnviron():
70     ''' I do not really know why this is useful, but we might as well keep it.
71         NLS nuisances.
72         Only set these to C if already set.  These must not be set unconditionally
73         because not all systems understand e.g. LANG=C (notably SCO).
74         Fixing LC_MESSAGES prevents Solaris sh from translating var values in set!
75         Non-C LC_CTYPE values break the ctype check.
76     '''
77     os.environ['LANG'] = os.getenv('LANG', 'C')
78     os.environ['LC'] = os.getenv('LC_ALL', 'C')
79     os.environ['LC_MESSAGE'] = os.getenv('LC_MESSAGE', 'C')
80     os.environ['LC_CTYPE'] = os.getenv('LC_CTYPE', 'C')
81
82
83 def createDirectories():
84     ''' Create the build directories if necessary '''
85     for dir in ['bind', 'clipart', 'doc', 'examples', 'images', 'kbd', \
86         'layouts', 'scripts', 'templates', 'ui' ]:
87         if not os.path.isdir( dir ):
88             try:
89                 os.mkdir( dir)
90                 logger.debug('Create directory %s.' % dir)
91             except:
92                 logger.error('Failed to create directory %s.' % dir)
93                 sys.exit(1)
94
95
96 def checkTeXPaths():
97     ''' Determine the path-style needed by the TeX engine on Win32 (Cygwin) '''
98     windows_style_tex_paths = ''
99     if LATEX == '':
100         return windows_style_tex_paths
101     if os.name == 'nt' or sys.platform == 'cygwin':
102         from tempfile import mkstemp
103         fd, tmpfname = mkstemp(suffix='.ltx')
104         if os.name == 'nt':
105             from ctypes import windll, create_unicode_buffer
106             GetShortPathName = windll.kernel32.GetShortPathNameW
107             longname = unicode(tmpfname)
108             shortname = create_unicode_buffer(len(longname)+1)
109             if GetShortPathName(longname, shortname, len(longname)+1):
110                 inpname = shortname.value.replace('\\', '/')
111             else:
112                 inpname = tmpfname.replace('\\', '/')
113         else:
114             inpname = cmdOutput('cygpath -m ' + tmpfname)
115         logname = os.path.basename(inpname.replace('.ltx', '.log'))
116         inpname = inpname.replace('~', '\\string~')
117         os.write(fd, r'\relax')
118         os.close(fd)
119         latex_out = cmdOutput(r'latex "\nonstopmode\input{%s}"' % inpname)
120         if 'Error' in latex_out:
121             logger.warning("configure: TeX engine needs posix-style paths in latex files")
122             windows_style_tex_paths = 'false'
123         else:
124             logger.info("configure: TeX engine needs windows-style paths in latex files")
125             windows_style_tex_paths = 'true'
126         removeFiles([tmpfname, logname, 'texput.log'])
127     return windows_style_tex_paths
128
129
130 ## Searching some useful programs
131 def checkProg(description, progs, rc_entry = [], path = [], not_found = ''):
132     '''
133         This function will search a program in $PATH plus given path
134         If found, return directory and program name (not the options).
135
136         description: description of the program
137
138         progs: check programs, for each prog, the first word is used
139             for searching but the whole string is used to replace
140             %% for a rc_entry. So, feel free to add '$$i' etc for programs.
141
142         path: additional pathes
143
144         rc_entry: entry to outfile, can be
145             1. emtpy: no rc entry will be added
146             2. one pattern: %% will be replaced by the first found program,
147                 or '' if no program is found.
148             3. several patterns for each prog and not_found. This is used 
149                 when different programs have different usages. If you do not 
150                 want not_found entry to be added to the RC file, you can specify 
151                 an entry for each prog and use '' for the not_found entry.
152
153         not_found: the value that should be used instead of '' if no program
154             was found
155
156     '''
157     # one rc entry for each progs plus not_found entry
158     if len(rc_entry) > 1 and len(rc_entry) != len(progs) + 1:
159         logger.error("rc entry should have one item or item for each prog and not_found.")
160         sys.exit(2)
161     logger.info('checking for ' + description + '...')
162     ## print '(' + ','.join(progs) + ')',
163     for idx in range(len(progs)):
164         # ac_prog may have options, ac_word is the command name
165         ac_prog = progs[idx]
166         ac_word = ac_prog.split(' ')[0]
167         msg = '+checking for "' + ac_word + '"... '
168         path = os.environ["PATH"].split(os.pathsep) + path
169         extlist = ['']
170         if "PATHEXT" in os.environ:
171             extlist = extlist + os.environ["PATHEXT"].split(os.pathsep)
172         for ac_dir in path:
173             for ext in extlist:
174                 if os.path.isfile( os.path.join(ac_dir, ac_word + ext) ):
175                     logger.info(msg + ' yes')
176                     # write rc entries for this command
177                     if len(rc_entry) == 1:
178                         addToRC(rc_entry[0].replace('%%', ac_prog))
179                     elif len(rc_entry) > 1:
180                         addToRC(rc_entry[idx].replace('%%', ac_prog))
181                     return [ac_dir, ac_word]
182         # if not successful
183         logger.info(msg + ' no')
184     # write rc entries for 'not found'
185     if len(rc_entry) > 0:  # the last one.
186         addToRC(rc_entry[-1].replace('%%', not_found))
187     return ['', not_found]
188
189
190 def checkProgAlternatives(description, progs, rc_entry = [], alt_rc_entry = [], path = [], not_found = ''):
191     ''' 
192         The same as checkProg, but additionally, all found programs will be added
193         as alt_rc_entries
194     '''
195     # one rc entry for each progs plus not_found entry
196     if len(rc_entry) > 1 and len(rc_entry) != len(progs) + 1:
197         logger.error("rc entry should have one item or item for each prog and not_found.")
198         sys.exit(2)
199     logger.info('checking for ' + description + '...')
200     ## print '(' + ','.join(progs) + ')',
201     found_prime = False
202     real_ac_dir = ''
203     real_ac_word = not_found
204     for idx in range(len(progs)):
205         # ac_prog may have options, ac_word is the command name
206         ac_prog = progs[idx]
207         ac_word = ac_prog.split(' ')[0]
208         msg = '+checking for "' + ac_word + '"... '
209         path = os.environ["PATH"].split(os.pathsep) + path
210         extlist = ['']
211         if "PATHEXT" in os.environ:
212             extlist = extlist + os.environ["PATHEXT"].split(os.pathsep)
213         found_alt = False
214         for ac_dir in path:
215             for ext in extlist:
216                 if os.path.isfile( os.path.join(ac_dir, ac_word + ext) ):
217                     logger.info(msg + ' yes')
218                     pr = re.compile(r'(\\\S+)(.*)$')
219                     m = None
220                     # write rc entries for this command
221                     if found_prime == False:
222                         if len(rc_entry) == 1:
223                             addToRC(rc_entry[0].replace('%%', ac_prog))
224                         elif len(rc_entry) > 1:
225                             addToRC(rc_entry[idx].replace('%%', ac_prog))
226                         real_ac_dir = ac_dir
227                         real_ac_word = ac_word
228                         found_prime = True
229                     if len(alt_rc_entry) == 1:
230                         alt_rc = alt_rc_entry[0]
231                         if alt_rc == "":
232                             # if no explicit alt_rc is given, construct one
233                             m = pr.match(rc_entry[0])
234                             if m:
235                                 alt_rc = m.group(1) + "_alternatives" + m.group(2)
236                         addToRC(alt_rc.replace('%%', ac_prog))
237                     elif len(alt_rc_entry) > 1:
238                         alt_rc = alt_rc_entry[idx]
239                         if alt_rc == "":
240                             # if no explicit alt_rc is given, construct one
241                             m = pr.match(rc_entry[idx])
242                             if m:
243                                 alt_rc = m.group(1) + "_alternatives" + m.group(2)
244                         addToRC(alt_rc.replace('%%', ac_prog))
245                     found_alt = True
246                     break
247             if found_alt:
248                 break
249         if found_alt == False:
250             # if not successful
251             logger.info(msg + ' no')
252     if found_prime:
253         return [real_ac_dir, real_ac_word]
254     # write rc entries for 'not found'
255     if len(rc_entry) > 0:  # the last one.
256         addToRC(rc_entry[-1].replace('%%', not_found))
257     return ['', not_found]
258
259
260 def addViewerAlternatives(rcs):
261     r = re.compile(r'\\Format (\S+).*$')
262     m = None
263     alt = ''
264     for idxx in range(len(rcs)):
265         if len(rcs) == 1:
266             m = r.match(rcs[0])
267             if m:
268                 alt = r'\viewer_alternatives ' + m.group(1) + " %%"
269         elif len(rcs) > 1:
270             m = r.match(rcs[idxx])
271             if m:
272                 if idxx > 0:
273                     alt += '\n'
274                 alt += r'\viewer_alternatives ' + m.group(1) + " %%"
275     return alt
276
277
278 def addEditorAlternatives(rcs):
279     r = re.compile(r'\\Format (\S+).*$')
280     m = None
281     alt = ''
282     for idxx in range(len(rcs)):
283         if len(rcs) == 1:
284             m = r.match(rcs[0])
285             if m:
286                 alt = r'\editor_alternatives ' + m.group(1) + " %%"
287         elif len(rcs) > 1:
288             m = r.match(rcs[idxx])
289             if m:
290                 if idxx > 0:
291                     alt += '\n'
292                 alt += r'\editor_alternatives ' + m.group(1) + " %%"
293     return alt
294
295
296 def checkViewer(description, progs, rc_entry = [], path = []):
297     ''' The same as checkProgAlternatives, but for viewers '''
298     if len(rc_entry) > 1 and len(rc_entry) != len(progs) + 1:
299         logger.error("rc entry should have one item or item for each prog and not_found.")
300         sys.exit(2)
301     alt_rc_entry = []
302     for idx in range(len(progs)):
303         if len(rc_entry) == 1:
304             rcs = rc_entry[0].split('\n')
305             alt = addViewerAlternatives(rcs)
306             alt_rc_entry.insert(0, alt)
307         elif len(rc_entry) > 1:
308             rcs = rc_entry[idx].split('\n')
309             alt = addViewerAlternatives(rcs)
310             alt_rc_entry.insert(idx, alt)
311     return checkProgAlternatives(description, progs, rc_entry, alt_rc_entry, path, not_found = 'auto')
312
313
314 def checkEditor(description, progs, rc_entry = [], path = []):
315     ''' The same as checkProgAlternatives, but for editors '''
316     if len(rc_entry) > 1 and len(rc_entry) != len(progs) + 1:
317         logger.error("rc entry should have one item or item for each prog and not_found.")
318         sys.exit(2)
319     alt_rc_entry = []
320     for idx in range(len(progs)):
321         if len(rc_entry) == 1:
322             rcs = rc_entry[0].split('\n')
323             alt = addEditorAlternatives(rcs)
324             alt_rc_entry.insert(0, alt)
325         elif len(rc_entry) > 1:
326             rcs = rc_entry[idx].split('\n')
327             alt = addEditorAlternatives(rcs)
328             alt_rc_entry.insert(idx, alt)
329     return checkProgAlternatives(description, progs, rc_entry, alt_rc_entry, path, not_found = 'auto')
330
331
332 def checkViewerNoRC(description, progs, rc_entry = [], path = []):
333     ''' The same as checkViewer, but do not add rc entry '''
334     if len(rc_entry) > 1 and len(rc_entry) != len(progs) + 1:
335         logger.error("rc entry should have one item or item for each prog and not_found.")
336         sys.exit(2)
337     alt_rc_entry = []
338     for idx in range(len(progs)):
339         if len(rc_entry) == 1:
340             rcs = rc_entry[0].split('\n')
341             alt = addViewerAlternatives(rcs)
342             alt_rc_entry.insert(0, alt)
343         elif len(rc_entry) > 1:
344             rcs = rc_entry[idx].split('\n')
345             alt = addViewerAlternatives(rcs)
346             alt_rc_entry.insert(idx, alt)
347     rc_entry = []
348     return checkProgAlternatives(description, progs, rc_entry, alt_rc_entry, path, not_found = 'auto')
349
350
351 def checkEditorNoRC(description, progs, rc_entry = [], path = []):
352     ''' The same as checkViewer, but do not add rc entry '''
353     if len(rc_entry) > 1 and len(rc_entry) != len(progs) + 1:
354         logger.error("rc entry should have one item or item for each prog and not_found.")
355         sys.exit(2)
356     alt_rc_entry = []
357     for idx in range(len(progs)):
358         if len(rc_entry) == 1:
359             rcs = rc_entry[0].split('\n')
360             alt = addEditorAlternatives(rcs)
361             alt_rc_entry.insert(0, alt)
362         elif len(rc_entry) > 1:
363             rcs = rc_entry[idx].split('\n')
364             alt = addEditorAlternatives(rcs)
365             alt_rc_entry.insert(idx, alt)
366     rc_entry = []
367     return checkProgAlternatives(description, progs, rc_entry, alt_rc_entry, path, not_found = 'auto')
368
369
370 def checkViewerEditor(description, progs, rc_entry = [], path = []):
371     ''' The same as checkProgAlternatives, but for viewers and editors '''
372     checkEditorNoRC(description, progs, rc_entry, path)
373     return checkViewer(description, progs, rc_entry, path)
374
375
376 def checkDTLtools():
377     ''' Check whether DTL tools are available (Windows only) '''
378     # Find programs! Returned path is not used now
379     if ((os.name == 'nt' or sys.platform == 'cygwin') and
380             checkProg('DVI to DTL converter', ['dv2dt']) != ['', ''] and
381             checkProg('DTL to DVI converter', ['dt2dv']) != ['', '']):
382         dtl_tools = True
383     else:
384         dtl_tools = False
385     return dtl_tools
386
387
388 def checkLatex(dtl_tools):
389     ''' Check latex, return lyx_check_config '''
390     path, LATEX = checkProg('a Latex2e program', ['latex $$i', 'latex2e $$i'])
391     path, PPLATEX = checkProg('a DVI postprocessing program', ['pplatex $$i'])
392     #-----------------------------------------------------------------
393     path, PLATEX = checkProg('pLaTeX, the Japanese LaTeX', ['platex $$i'])
394     if PLATEX != '':
395         # check if PLATEX is pLaTeX2e
396         writeToFile('chklatex.ltx', '''
397 \\nonstopmode
398 \\@@end
399 ''')
400         # run platex on chklatex.ltx and check result
401         if cmdOutput(PLATEX + ' chklatex.ltx').find('pLaTeX2e') != -1:
402             # We have the Japanese pLaTeX2e
403             addToRC(r'\converter platex   dvi       "%s"   "latex=platex"' % PLATEX)
404         else:
405             PLATEX = ''
406             removeFiles(['chklatex.ltx', 'chklatex.log'])
407     #-----------------------------------------------------------------
408     # use LATEX to convert from latex to dvi if PPLATEX is not available    
409     if PPLATEX == '':
410         PPLATEX = LATEX
411     if dtl_tools:
412         # Windows only: DraftDVI
413         addToRC(r'''\converter latex      dvi2       "%s"       "latex"
414 \converter dvi2       dvi        "python -tt $$s/scripts/clean_dvi.py $$i $$o"  ""''' % PPLATEX)
415     else:
416         addToRC(r'\converter latex      dvi        "%s" "latex"' % PPLATEX)
417     # no latex
418     if LATEX != '':
419         # Check if latex is usable
420         writeToFile('chklatex.ltx', '''
421 \\nonstopmode\\makeatletter
422 \\ifx\\undefined\\documentclass\\else
423   \\message{ThisIsLaTeX2e}
424 \\fi
425 \\@@end
426 ''')
427         # run latex on chklatex.ltx and check result
428         if cmdOutput(LATEX + ' chklatex.ltx').find('ThisIsLaTeX2e') != -1:
429             # valid latex2e
430             return LATEX
431         else:
432             logger.warning("Latex not usable (not LaTeX2e) ")
433         # remove temporary files
434         removeFiles(['chklatex.ltx', 'chklatex.log'])
435     return ''
436
437
438 def checkLuatex():
439     ''' Check if luatex is there and usable '''
440     path, LUATEX = checkProg('LuaTeX', ['lualatex $$i'])
441     path, DVILUATEX = checkProg('LuaTeX (DVI)', ['dvilualatex $$i'])
442     if LUATEX != '':
443     # luatex binary is there
444         msg = "checking if LuaTeX is usable ..."
445         # Check if luatex is usable
446         writeToFile('luatest.tex', '''
447 \\nonstopmode\\documentclass{minimal}
448 \\usepackage{fontspec}
449 \\begin{document}
450 .
451 \\end{document}
452 ''')
453         # run lualatex on luatest.tex and check result
454         luatest = cmdOutput(LUATEX + ' luatest.tex')
455         if luatest.find('XeTeX is required to compile this document') != -1:
456             # fontspec/luatex too old! We do not support this version.
457             logger.info(msg + ' no (probably not recent enough)')
458         elif luatest.find('! LaTeX Error: File `fontspec.sty\' not found') != -1:
459             # fontspec missing
460             logger.info(msg + ' no (missing fontspec)')
461         else:
462             # working luatex
463             logger.info(msg + ' yes')
464             addToRC(r'\converter luatex      pdf5       "%s"    "latex=lualatex"' % LUATEX)
465             if DVILUATEX != '':
466                 addToRC(r'\converter luatex      dvi3        "%s"       "latex=lualatex"' % DVILUATEX)   
467         # remove temporary files
468         removeFiles(['luatest.tex', 'luatest.log', 'luatest.aux', 'luatest.pdf'])
469
470
471 def checkModule(module):
472     ''' Check for a Python module, return the status '''
473     msg = 'checking for "' + module + ' module"... '
474     try:
475       __import__(module)
476       logger.info(msg + ' yes')
477       return True
478     except ImportError:
479       logger.info(msg + ' no')
480       return False
481
482
483 def checkFormatEntries(dtl_tools):  
484     ''' Check all formats (\Format entries) '''
485     checkViewerEditor('a Tgif viewer and editor', ['tgif'],
486         rc_entry = [r'\Format tgif       obj     Tgif                   "" "%%" "%%"    "vector"'])
487     #
488     checkViewerEditor('a FIG viewer and editor', ['xfig', 'jfig3-itext.jar', 'jfig3.jar'],
489         rc_entry = [r'\Format fig        fig     FIG                    "" "%%" "%%"    "vector"'])
490     #
491     checkViewerEditor('a Dia viewer and editor', ['dia'],
492         rc_entry = [r'\Format dia        dia     DIA                    "" "%%" "%%"    "vector"'])
493     #
494     checkViewerEditor('a Grace viewer and editor', ['xmgrace'],
495         rc_entry = [r'\Format agr        agr     Grace                  "" "%%" "%%"    "vector"'])
496     #
497     checkViewerEditor('a FEN viewer and editor', ['xboard -lpf $$i -mode EditPosition'],
498         rc_entry = [r'\Format fen        fen     FEN                    "" "%%" "%%"    ""'])
499     #
500     checkViewerEditor('a SVG viewer and editor', ['inkscape'],
501         rc_entry = [r'\Format svg        svg     SVG                    "" "%%" "%%"    "vector"'])
502     #
503     path, iv = checkViewerNoRC('a raster image viewer', ['xv', 'kview', 'gimp-remote', 'gimp'],
504         rc_entry = [r'''\Format bmp        bmp     BMP                    "" "%s"       "%s"    ""
505 \Format gif        gif     GIF                    "" "%s"       "%s"    ""
506 \Format jpg        jpg     JPEG                   "" "%s"       "%s"    ""
507 \Format pbm        pbm     PBM                    "" "%s"       "%s"    ""
508 \Format pgm        pgm     PGM                    "" "%s"       "%s"    ""
509 \Format png        png     PNG                    "" "%s"       "%s"    ""
510 \Format ppm        ppm     PPM                    "" "%s"       "%s"    ""
511 \Format tiff       tif     TIFF                   "" "%s"       "%s"    ""
512 \Format xbm        xbm     XBM                    "" "%s"       "%s"    ""
513 \Format xpm        xpm     XPM                    "" "%s"       "%s"    ""'''])
514     path, ie = checkEditorNoRC('a raster image editor', ['gimp-remote', 'gimp'],
515         rc_entry = [r'''\Format bmp        bmp     BMP                    "" "%s"       "%s"    ""
516 \Format gif        gif     GIF                    "" "%s"       "%s"    ""
517 \Format jpg        jpg     JPEG                   "" "%s"       "%s"    ""
518 \Format pbm        pbm     PBM                    "" "%s"       "%s"    ""
519 \Format pgm        pgm     PGM                    "" "%s"       "%s"    ""
520 \Format png        png     PNG                    "" "%s"       "%s"    ""
521 \Format ppm        ppm     PPM                    "" "%s"       "%s"    ""
522 \Format tiff       tif     TIFF                   "" "%s"       "%s"    ""
523 \Format xbm        xbm     XBM                    "" "%s"       "%s"    ""
524 \Format xpm        xpm     XPM                    "" "%s"       "%s"    ""'''])
525     addToRC(r'''\Format bmp        bmp     BMP                    "" "%s"       "%s"    ""
526 \Format gif        gif     GIF                    "" "%s"       "%s"    ""
527 \Format jpg        jpg     JPEG                   "" "%s"       "%s"    ""
528 \Format pbm        pbm     PBM                    "" "%s"       "%s"    ""
529 \Format pgm        pgm     PGM                    "" "%s"       "%s"    ""
530 \Format png        png     PNG                    "" "%s"       "%s"    ""
531 \Format ppm        ppm     PPM                    "" "%s"       "%s"    ""
532 \Format tiff       tif     TIFF                   "" "%s"       "%s"    ""
533 \Format xbm        xbm     XBM                    "" "%s"       "%s"    ""
534 \Format xpm        xpm     XPM                    "" "%s"       "%s"    ""''' % \
535         (iv, ie, iv, ie, iv, ie, iv, ie, iv, ie, iv, ie, iv, ie, iv, ie, iv, ie, iv, ie) )
536     #
537     checkViewerEditor('a text editor', ['xemacs', 'gvim', 'kedit', 'kwrite', 'kate', \
538         'nedit', 'gedit', 'notepad'],
539         rc_entry = [r'''\Format asciichess asc    "Plain text (chess output)"  "" ""    "%%"    ""
540 \Format asciiimage asc    "Plain text (image)"         "" ""    "%%"    ""
541 \Format asciixfig  asc    "Plain text (Xfig output)"   "" ""    "%%"    ""
542 \Format dateout    tmp    "date (output)"         "" "" "%%"    ""
543 \Format docbook    sgml    DocBook                B  "" "%%"    "document,menu=export"
544 \Format docbook-xml xml   "Docbook (XML)"         "" "" "%%"    "document,menu=export"
545 \Format dot        dot    "Graphviz Dot"          "" "" "%%"    "vector"
546 \Format platex     tex    "LaTeX (pLaTeX)"        "" "" "%%"    "document,menu=export"
547 \Format literate   nw      NoWeb                  N  "" "%%"    "document,menu=export"
548 \Format sweave     Rnw    "Sweave"                S  "" "%%"    "document,menu=export"
549 \Format lilypond   ly     "LilyPond music"        "" "" "%%"    "vector"
550 \Format lilypond-book    lytex "LilyPond book (LaTeX)"   "" ""  "%%"    "document,menu=export"
551 \Format latex      tex    "LaTeX (plain)"         L  "" "%%"    "document,menu=export"
552 \Format luatex     tex    "LaTeX (LuaTeX)"        "" "" "%%"    "document,menu=export"
553 \Format pdflatex   tex    "LaTeX (pdflatex)"      "" "" "%%"    "document,menu=export"
554 \Format xetex      tex    "LaTeX (XeTeX)"         "" "" "%%"    "document,menu=export"
555 \Format text       txt    "Plain text"            a  "" "%%"    "document,menu=export"
556 \Format text2      txt    "Plain text (pstotext)" "" "" "%%"    "document"
557 \Format text3      txt    "Plain text (ps2ascii)" "" "" "%%"    "document"
558 \Format text4      txt    "Plain text (catdvi)"   "" "" "%%"    "document"
559 \Format textparagraph txt "Plain Text, Join Lines" "" ""        "%%"    "document"''' ])
560    #Spreadsheets using ssconvert from gnumeric
561     checkViewer('gnumeric spreadsheet software', ['gnumeric'], 
562       rc_entry = [r'''\Format gnumeric gnumeric "Gnumeric spreadsheet" "" ""    "%%"   "document" 
563 \Format excel      xls    "Excel spreadsheet"      "" "" "%%"    "document" 
564 \Format oocalc     ods    "OpenOffice spreadsheet" "" "" "%%"    "document"''']) 
565  #
566     path, xhtmlview = checkViewer('an HTML previewer', ['firefox', 'mozilla file://$$p$$i', 'netscape'],
567         rc_entry = [r'\Format xhtml      xhtml   "LyXHTML"              y "%%" ""    "document,menu=export"'])
568     if xhtmlview == "":
569         addToRC(r'\Format xhtml      xhtml   "LyXHTML"              y "" ""  "document,menu=export"')
570  #
571     checkEditor('a BibTeX editor', ['jabref', 'JabRef', \
572         'pybliographic', 'bibdesk', 'gbib', 'kbib', \
573         'kbibtex', 'sixpack', 'bibedit', 'tkbibtex' \
574         'xemacs', 'gvim', 'kedit', 'kwrite', 'kate', \
575         'nedit', 'gedit', 'notepad'],
576         rc_entry = [r'''\Format bibtex bib    "BibTeX"         "" ""    "%%"    ""''' ])
577     #
578     #checkProg('a Postscript interpreter', ['gs'],
579     #  rc_entry = [ r'\ps_command "%%"' ])
580     checkViewer('a Postscript previewer', ['kghostview', 'okular', 'evince', 'gv', 'ghostview -swap'],
581         rc_entry = [r'''\Format eps        eps     EPS                    "" "%%"       ""      "vector"
582 \Format ps         ps      Postscript             t  "%%"       ""      "document,vector"'''])
583     # for xdg-open issues look here: http://www.mail-archive.com/lyx-devel@lists.lyx.org/msg151818.html
584     checkViewer('a PDF previewer', ['kpdf', 'okular', 'evince', 'kghostview', 'xpdf', 'acrobat', 'acroread', \
585                     'gv', 'ghostview'],
586         rc_entry = [r'''\Format pdf        pdf    "PDF (ps2pdf)"          P  "%%"       ""      "document,vector,menu=export"
587 \Format pdf2       pdf    "PDF (pdflatex)"        F  "%%"       ""      "document,vector,menu=export"
588 \Format pdf3       pdf    "PDF (dvipdfm)"         m  "%%"       ""      "document,vector,menu=export"
589 \Format pdf4       pdf    "PDF (XeTeX)"           X  "%%"       ""      "document,vector,menu=export"
590 \Format pdf5       pdf    "PDF (LuaTeX)"          u  "%%"       ""      "document,vector,menu=export"'''])
591     #
592     checkViewer('a DVI previewer', ['xdvi', 'kdvi', 'okular', 'yap', 'dviout -Set=!m'],
593         rc_entry = [r'''\Format dvi        dvi     DVI                    D  "%%"       ""      "document,vector,menu=export"
594 \Format dvi3       dvi     "DVI (LuaTeX)"          V  "%%"      ""      "document,vector,menu=export"'''])
595     if dtl_tools:
596         # Windows only: DraftDVI
597         addToRC(r'\Format dvi2       dvi     DraftDVI               ""  ""      ""      "vector"')
598     #
599     checkViewer('an HTML previewer', ['firefox', 'mozilla file://$$p$$i', 'netscape'],
600         rc_entry = [r'\Format html       html    HTML                   H  "%%" ""      "document,menu=export"'])
601     #
602     checkViewerEditor('Noteedit', ['noteedit'],
603         rc_entry = [r'\Format noteedit   not     Noteedit               "" "%%" "%%"    "vector"'])
604     #
605     checkViewerEditor('an OpenDocument/OpenOffice viewer', ['swriter', 'oowriter', 'abiword'],
606         rc_entry = [r'''\Format odt        odt     OpenDocument           "" "%%"       "%%"    "document,vector,menu=export"
607 \Format sxw        sxw    "OpenOffice.Org (sxw)"  "" "" ""      "document,vector"'''])
608     # 
609     checkViewerEditor('a Rich Text and Word viewer', ['swriter', 'oowriter', 'abiword'],
610         rc_entry = [r'''\Format rtf        rtf    "Rich Text Format"      "" "%%"       "%%"    "document,vector,menu=export"
611 \Format word       doc    "MS Word"               W  "%%"       "%%"    "document,vector,menu=export"'''])
612     #
613     # entries that do not need checkProg
614     addToRC(r'''\Format date       ""     "date command"          "" "" ""      ""
615 \Format csv        csv    "Table (CSV)"  "" ""  ""      "document"
616 \Format fax        ""      Fax                    "" "" ""      "document"
617 \Format lyx        lyx     LyX                    "" "" ""      ""
618 \Format lyx13x     13.lyx  "LyX 1.3.x"             "" ""        ""      "document"
619 \Format lyx14x     14.lyx  "LyX 1.4.x"             "" ""        ""      "document"
620 \Format lyx15x     15.lyx  "LyX 1.5.x"             "" ""        ""      "document"
621 \Format lyx16x     16.lyx  "LyX 1.6.x"             "" ""        ""      "document,menu=export"
622 \Format clyx       cjklyx "CJK LyX 1.4.x (big5)"  "" "" ""      "document"
623 \Format jlyx       cjklyx "CJK LyX 1.4.x (euc-jp)" "" ""        ""      "document"
624 \Format klyx       cjklyx "CJK LyX 1.4.x (euc-kr)" "" ""        ""      "document"
625 \Format lyxpreview lyxpreview "LyX Preview"       "" "" ""      ""
626 \Format lyxpreview-lytex  lyxpreview-lytex  "LyX Preview (LilyPond book)" "" "" ""      ""
627 \Format lyxpreview-platex lyxpreview-platex "LyX Preview (pLaTeX)"       "" ""  ""      ""
628 \Format pdftex     pdftex_t PDFTEX                "" "" ""      ""
629 \Format program    ""      Program                "" "" ""      ""
630 \Format pstex      pstex_t PSTEX                  "" "" ""      ""
631 \Format wmf        wmf    "Windows Metafile"      "" "" ""      "vector"
632 \Format emf        emf    "Enhanced Metafile"     "" "" ""      "vector"
633 \Format wordhtml   html   "HTML (MS Word)"        "" "" ""      "document"
634 ''')
635
636
637 def checkConverterEntries():
638     ''' Check all converters (\converter entries) '''
639     checkProg('the pdflatex program', ['pdflatex $$i'],
640         rc_entry = [ r'\converter pdflatex   pdf2       "%%"    "latex=pdflatex"' ])
641
642     checkProg('XeTeX', ['xelatex $$i'],
643         rc_entry = [ r'\converter xetex      pdf4       "%%"    "latex=xelatex"' ])
644
645     checkLuatex()
646     
647     ''' If we're running LyX in-place then tex2lyx will be found in
648             ../src/tex2lyx. Add this directory to the PATH temporarily and
649             search for tex2lyx.
650             Use PATH to avoid any problems with paths-with-spaces.
651     '''
652     path_orig = os.environ["PATH"]
653     os.environ["PATH"] = os.path.join('..', 'src', 'tex2lyx') + \
654         os.pathsep + path_orig
655
656 # First search for tex2lyx with version suffix (bug 6986)
657     checkProg('a LaTeX/Noweb -> LyX converter', ['tex2lyx' + version_suffix, 'tex2lyx'],
658         rc_entry = [r'''\converter latex      lyx        "%% -f $$i $$o"        ""
659 \converter literate   lyx        "%% -n -f $$i $$o"     ""'''])
660
661     os.environ["PATH"] = path_orig
662
663     #
664     checkProg('a Noweb -> LaTeX converter', ['noweave -delay -index $$i > $$o'],
665         rc_entry = [r'''\converter literate   latex      "%%"   ""
666 \converter literate   pdflatex      "%%"        ""'''])
667     #
668     checkProg('a Sweave -> LaTeX converter', ['Rscript --verbose --no-save --no-restore $$s/scripts/lyxsweave.R $$p$$i $$p$$o $$e $$r'],
669         rc_entry = [r'''\converter sweave   latex      "%%"     ""
670 \converter sweave   pdflatex   "%%"     ""
671 \converter sweave   xetex      "%%"     ""
672 \converter sweave   luatex     "%%"     ""'''])
673     #
674     checkProg('an HTML -> LaTeX converter', ['html2latex $$i', 'gnuhtml2latex $$i', \
675         'htmltolatex -input $$i -output $$o', 'java -jar htmltolatex.jar -input $$i -output $$o'],
676         rc_entry = [ r'\converter html       latex      "%%"    ""' ])
677     #
678     checkProg('an MS Word -> LaTeX converter', ['wvCleanLatex $$i $$o'],
679         rc_entry = [ r'\converter word       latex      "%%"    ""' ])
680
681     # eLyXer: search as an executable (elyxer.py, elyxer)
682     path, elyxer = checkProg('a LyX -> HTML converter',
683         ['elyxer.py --directory $$r $$i $$o', 'elyxer --directory $$r $$i $$o'],
684         rc_entry = [ r'\converter lyx      html       "%%"      ""' ])
685     path, elyxer = checkProg('a LyX -> HTML (MS Word) converter',
686         ['elyxer.py --html --directory $$r $$i $$o', 'elyxer --html --directory $$r $$i $$o'],
687         rc_entry = [ r'\converter lyx      wordhtml       "%%"  ""' ])
688     if elyxer.find('elyxer') >= 0:
689       addToRC(r'''\copier    html       "python -tt $$s/scripts/ext_copy.py -e html,png,jpg,jpeg,css $$i $$o"''')
690       addToRC(r'''\copier    wordhtml       "python -tt $$s/scripts/ext_copy.py -e html,png,jpg,jpeg,css $$i $$o"''')
691     else:
692       # search for HTML converters other than eLyXer
693       # On SuSE the scripts have a .sh suffix, and on debian they are in /usr/share/tex4ht/
694       path, htmlconv = checkProg('a LaTeX -> HTML converter', ['htlatex $$i', 'htlatex.sh $$i', \
695           '/usr/share/tex4ht/htlatex $$i', 'tth  -t -e2 -L$$b < $$i > $$o', \
696           'latex2html -no_subdir -split 0 -show_section_numbers $$i', 'hevea -s $$i'],
697           rc_entry = [ r'\converter latex      html       "%%"  "needaux"' ])
698       if htmlconv.find('htlatex') >= 0 or htmlconv == 'latex2html':
699         addToRC(r'''\copier    html       "python -tt $$s/scripts/ext_copy.py -e html,png,css $$i $$o"''')
700       else:
701         addToRC(r'''\copier    html       "python -tt $$s/scripts/ext_copy.py $$i $$o"''')
702       path, htmlconv = checkProg('a LaTeX -> HTML (MS Word) converter', ["htlatex $$i 'html,word' 'symbol/!' '-cvalidate'", \
703           "htlatex.sh $$i 'html,word' 'symbol/!' '-cvalidate'", \
704           "/usr/share/tex4ht/htlatex $$i 'html,word' 'symbol/!' '-cvalidate'"],
705           rc_entry = [ r'\converter latex      wordhtml   "%%"  "needaux"' ])
706       if htmlconv.find('htlatex') >= 0:
707         addToRC(r'''\copier    wordhtml       "python -tt $$s/scripts/ext_copy.py -e html,png,css $$i $$o"''')
708       else:
709         addToRC(r'''\copier    wordhtml       "python -tt $$s/scripts/ext_copy.py $$i $$o"''')
710
711
712     # Check if LyXBlogger is installed
713     lyxblogger_found = checkModule('lyxblogger')
714     if lyxblogger_found:
715       addToRC(r'\Format    blog       blog       "LyXBlogger"           "" "" ""  "document"')
716       addToRC(r'\converter xhtml      blog       "python -m lyxblogger $$i"       ""')
717
718     #
719     checkProg('an OpenOffice.org -> LaTeX converter', ['w2l -clean $$i'],
720         rc_entry = [ r'\converter sxw        latex      "%%"    ""' ])
721     #
722     checkProg('an OpenDocument -> LaTeX converter', ['w2l -clean $$i'],
723         rc_entry = [ r'\converter odt        latex      "%%"    ""' ])
724     # According to http://www.tug.org/applications/tex4ht/mn-commands.html
725     # the command mk4ht oolatex $$i has to be used as default,
726     # but as this would require to have Perl installed, in MiKTeX oolatex is
727     # directly available as application.
728     # On SuSE the scripts have a .sh suffix, and on debian they are in /usr/share/tex4ht/
729     # Both SuSE and debian have oolatex
730     checkProg('a LaTeX -> Open Document converter', [
731         'oolatex $$i', 'mk4ht oolatex $$i', 'oolatex.sh $$i', '/usr/share/tex4ht/oolatex $$i',
732         'htlatex $$i \'xhtml,ooffice\' \'ooffice/! -cmozhtf\' \'-coo\' \'-cvalidate\''],
733         rc_entry = [ r'\converter latex      odt        "%%"    "needaux"' ])
734     # On windows it is called latex2rt.exe
735     checkProg('a LaTeX -> RTF converter', ['latex2rtf -p -S -o $$o $$i', 'latex2rt -p -S -o $$o $$i'],
736         rc_entry = [ r'\converter latex      rtf        "%%"    "needaux"' ])
737     #
738     checkProg('a RTF -> HTML converter', ['unrtf --html  $$i > $$o'],
739         rc_entry = [ r'\converter rtf      html        "%%"     ""' ])
740     #
741     checkProg('a PS to PDF converter', ['ps2pdf13 $$i $$o'],
742         rc_entry = [ r'\converter ps         pdf        "%%"    ""' ])
743     #
744     checkProg('a PS to TXT converter', ['pstotext $$i > $$o'],
745         rc_entry = [ r'\converter ps         text2      "%%"    ""' ])
746     #
747     checkProg('a PS to TXT converter', ['ps2ascii $$i $$o'],
748         rc_entry = [ r'\converter ps         text3      "%%"    ""' ])
749     #
750     checkProg('a PS to EPS converter', ['ps2eps $$i'],
751         rc_entry = [ r'\converter ps         eps      "%%"      ""' ])
752     #
753     checkProg('a PDF to PS converter', ['pdf2ps $$i $$o', 'pdftops $$i $$o'],
754         rc_entry = [ r'\converter pdf         ps        "%%"    ""' ])
755     #
756     checkProg('a PDF to EPS converter', ['pdftops -eps -f 1 -l 1 $$i $$o'],
757         rc_entry = [ r'\converter pdf         eps        "%%"   ""' ])
758     #
759     checkProg('a DVI to TXT converter', ['catdvi $$i > $$o'],
760         rc_entry = [ r'\converter dvi        text4      "%%"    ""' ])
761     #
762     checkProg('a DVI to PS converter', ['dvips -o $$o $$i'],
763         rc_entry = [ r'\converter dvi        ps         "%%"    ""' ])
764     #
765     checkProg('a DVI to PDF converter', ['dvipdfmx -o $$o $$i', 'dvipdfm -o $$o $$i'],
766         rc_entry = [ r'\converter dvi        pdf3       "%%"    ""' ])
767     #
768     path, dvipng = checkProg('dvipng', ['dvipng'])
769     path, dv2dt  = checkProg('DVI to DTL converter', ['dv2dt'])
770     if dvipng == "dvipng" and dv2dt == 'dv2dt':
771         addToRC(r'\converter lyxpreview png        "python -tt $$s/scripts/lyxpreview2bitmap.py"        ""')
772     else:
773         # set empty converter to override the default imagemagick
774         addToRC(r'\converter lyxpreview png        ""   ""')
775     if dv2dt == 'dv2dt':
776         addToRC(r'\converter lyxpreview ppm        "python -tt $$s/scripts/lyxpreview2bitmap.py"        ""')
777     else:
778         # set empty converter to override the default imagemagick
779         addToRC(r'\converter lyxpreview ppm        ""   ""')
780     #  
781     checkProg('a fax program', ['kdeprintfax $$i', 'ksendfax $$i', 'hylapex $$i'],
782         rc_entry = [ r'\converter ps         fax        "%%"    ""'])
783     #
784     path, fig2dev = checkProg('a FIG -> Image converter', ['fig2dev'])
785     if fig2dev == "fig2dev":
786         addToRC(r'''\converter fig        eps        "fig2dev -L eps $$i $$o"   ""
787 \converter fig        ppm        "fig2dev -L ppm $$i $$o"       ""
788 \converter fig        png        "fig2dev -L png $$i $$o"       ""
789 \converter fig        pdftex     "python -tt $$s/scripts/fig2pdftex.py $$i $$o" ""
790 \converter fig        pstex      "python -tt $$s/scripts/fig2pstex.py $$i $$o"  ""''')
791     #
792     checkProg('a TIFF -> PS converter', ['tiff2ps $$i > $$o'],
793         rc_entry = [ r'\converter tiff       eps        "%%"    ""', ''])
794     #
795     checkProg('a TGIF -> EPS/PPM converter', ['tgif'],
796         rc_entry = [
797             r'''\converter tgif       eps        "tgif -print -color -eps -stdout $$i > $$o"    ""
798 \converter tgif       png        "tgif -print -color -png -o $$d $$i"   ""
799 \converter tgif       pdf        "tgif -print -color -pdf -stdout $$i > $$o"    ""''',
800             ''])
801     #
802     checkProg('a WMF -> EPS converter', ['metafile2eps $$i $$o', 'wmf2eps -o $$o $$i'],
803         rc_entry = [ r'\converter wmf        eps        "%%"    ""'])
804     #
805     checkProg('an EMF -> EPS converter', ['metafile2eps $$i $$o', 'wmf2eps -o $$o $$i'],
806         rc_entry = [ r'\converter emf        eps        "%%"    ""'])
807     #
808     checkProg('an EPS -> PDF converter', ['epstopdf'],
809         rc_entry = [ r'\converter eps        pdf        "epstopdf --outfile=$$o $$i"    ""', ''])
810     #
811     # no agr -> pdf converter, since the pdf library used by gracebat is not
812     # free software and therefore not compiled in in many installations.
813     # Fortunately, this is not a big problem, because we will use epstopdf to
814     # convert from agr to pdf via eps without loss of quality.
815     checkProg('a Grace -> Image converter', ['gracebat'],
816         rc_entry = [
817             r'''\converter agr        eps        "gracebat -hardcopy -printfile $$o -hdevice EPS $$i 2>/dev/null"       ""
818 \converter agr        png        "gracebat -hardcopy -printfile $$o -hdevice PNG $$i 2>/dev/null"       ""
819 \converter agr        jpg        "gracebat -hardcopy -printfile $$o -hdevice JPEG $$i 2>/dev/null"      ""
820 \converter agr        ppm        "gracebat -hardcopy -printfile $$o -hdevice PNM $$i 2>/dev/null"       ""''',
821             ''])
822     #
823     checkProg('a Dot -> Image converter', ['dot'],
824         rc_entry = [
825             r'''\converter dot        eps        "dot -Teps $$i -o $$o" ""
826 \converter dot        png        "dot -Tpng $$i -o $$o" ""''',
827             ''])
828     #
829     checkProg('a Dia -> PNG converter', ['dia -e $$o -t png $$i'],
830         rc_entry = [ r'\converter dia        png        "%%"    ""'])
831     #
832     checkProg('a Dia -> EPS converter', ['dia -e $$o -t eps $$i'],
833         rc_entry = [ r'\converter dia        eps        "%%"    ""'])
834     #
835     checkProg('a SVG -> PDF converter', ['rsvg-convert -f pdf -o $$o $$i', 'inkscape --file=$$p/$$i --export-area-drawing --without-gui --export-pdf=$$p/$$o'],
836         rc_entry = [ r'\converter svg        pdf        "%%"    ""'])
837     #
838     checkProg('a SVG -> EPS converter', ['rsvg-convert -f ps -o $$o $$i', 'inkscape --file=$$p/$$i --export-area-drawing --without-gui --export-eps=$$p/$$o'],
839         rc_entry = [ r'\converter svg        eps        "%%"    ""'])
840     # the PNG export via Inkscape must not have the full path ($$p) for the file
841     checkProg('a SVG -> PNG converter', ['rsvg-convert -f png -o $$o $$i', 'inkscape --without-gui --file=$$i --export-png=$$o'],
842         rc_entry = [ r'\converter svg        png        "%%"    ""'])
843     
844     #
845     # gnumeric/xls/ods to tex
846     checkProg('a spreadsheet -> latex converter', ['ssconvert'], 
847        rc_entry = [ r'''\converter gnumeric latex "ssconvert --export-type=Gnumeric_html:latex $$i $$o" "" 
848 \converter ods latex "ssconvert --export-type=Gnumeric_html:latex $$i $$o" "" 
849 \converter xls latex "ssconvert --export-type=Gnumeric_html:latex $$i $$o" ""''', 
850 '']) 
851
852     path, lilypond = checkProg('a LilyPond -> EPS/PDF/PNG converter', ['lilypond'])
853     if (lilypond != ''):
854         version_string = cmdOutput("lilypond --version")
855         match = re.match('GNU LilyPond (\S+)', version_string)
856         if match:
857             version_number = match.groups()[0]
858             version = version_number.split('.')
859             if int(version[0]) > 2 or (len(version) > 1 and int(version[0]) == 2 and int(version[1]) >= 11):
860                 addToRC(r'''\converter lilypond   eps        "lilypond -dbackend=eps -dsafe --ps $$i"   ""
861 \converter lilypond   png        "lilypond -dbackend=eps -dsafe --png $$i"      ""''')
862                 addToRC(r'\converter lilypond   pdf        "lilypond -dbackend=eps -dsafe --pdf $$i"    ""')
863                 logger.info('+  found LilyPond version %s.' % version_number)
864             elif int(version[0]) > 2 or (len(version) > 1 and int(version[0]) == 2 and int(version[1]) >= 6):
865                 addToRC(r'''\converter lilypond   eps        "lilypond -b eps --ps --safe $$i"  ""
866 \converter lilypond   png        "lilypond -b eps --png $$i"    ""''')
867                 if int(version[0]) > 2 or (len(version) > 1 and int(version[0]) == 2 and int(version[1]) >= 9):
868                     addToRC(r'\converter lilypond   pdf        "lilypond -b eps --pdf --safe $$i"       ""')
869                 logger.info('+  found LilyPond version %s.' % version_number)
870             else:
871                 logger.info('+  found LilyPond, but version %s is too old.' % version_number)
872         else:
873             logger.info('+  found LilyPond, but could not extract version number.')
874     #
875     path, lilypond_book = checkProg('a LilyPond book (LaTeX) -> LaTeX converter', ['lilypond-book'])
876     if (lilypond_book != ''):
877         version_string = cmdOutput("lilypond-book --version")
878         match = re.match('^(\S+)$', version_string)
879         if match:
880             version_number = match.groups()[0]
881             version = version_number.split('.')
882             if int(version[0]) > 2 or (len(version) > 1 and int(version[0]) == 2 and int(version[1]) >= 13):
883                 if dv2dt == 'dv2dt':
884                     addToRC(r'\converter lyxpreview-lytex ppm "python -tt $$s/scripts/lyxpreview-lytex2bitmap.py" ""')
885                 else:
886                     # set empty converter to override the default imagemagick
887                     addToRC(r'\converter lyxpreview-lytex ppm "" ""')
888                 if dvipng == "dvipng" and dv2dt == 'dv2dt':
889                     addToRC(r'\converter lyxpreview-lytex png "python -tt $$s/scripts/lyxpreview-lytex2bitmap.py" ""')
890                 else:
891                     # set empty converter to override the default imagemagick
892                     addToRC(r'\converter lyxpreview-lytex png "" ""')
893                 # Note: The --lily-output-dir flag is required because lilypond-book
894                 #       does not process input again unless the input has changed,
895                 #       even if the output format being requested is different. So
896                 #       once a .eps file exists, lilypond-book won't create a .pdf
897                 #       even when requested with --pdf. This is a problem if a user
898                 #       clicks View PDF after having done a View DVI. To circumvent
899                 #       this, use different output folders for eps and pdf outputs.
900                 addToRC(r'\converter lilypond-book latex    "lilypond-book --safe --lily-output-dir=ly-eps $$i"                                ""')
901                 addToRC(r'\converter lilypond-book pdflatex "lilypond-book --safe --pdf --latex-program=pdflatex --lily-output-dir=ly-pdf $$i" ""')
902                 addToRC(r'\converter lilypond-book xetex    "lilypond-book --safe --pdf --latex-program=xelatex --lily-output-dir=ly-pdf $$i"  ""')
903                 addToRC(r'\converter lilypond-book luatex   "lilypond-book --safe --pdf --latex-program=lualatex --lily-output-dir=ly-pdf $$i" ""')
904                 logger.info('+  found LilyPond-book version %s.' % version_number)
905             else:
906                 logger.info('+  found LilyPond-book, but version %s is too old.' % version_number)
907         else:
908             logger.info('+  found LilyPond-book, but could not extract version number.')
909     #
910     checkProg('a Noteedit -> LilyPond converter', ['noteedit --export-lilypond $$i'],
911         rc_entry = [ r'\converter noteedit   lilypond   "%%"    ""', ''])
912     #
913     # Currently, lyxpak outputs a gzip compressed tar archive on *nix
914     # and a zip archive on Windows.
915     # So, we configure the appropriate version according to the platform.
916     cmd = r'\converter lyx %s "python -tt $$s/scripts/lyxpak.py $$r/$$i" ""'
917     if os.name == 'nt':
918         addToRC(r'\Format lyxzip     zip    "LyX Archive (zip)"     "" "" ""  "document,menu=export"')
919         addToRC(cmd % "lyxzip")
920     else:
921         addToRC(r'\Format lyxgz      gz     "LyX Archive (tar.gz)"  "" "" ""  "document,menu=export"')
922         addToRC(cmd % "lyxgz")
923         
924     #
925     # FIXME: no rc_entry? comment it out
926     # checkProg('Image converter', ['convert $$i $$o'])
927     #
928     # Entries that do not need checkProg
929     addToRC(r'''\converter lyxpreview-platex ppm        "python -tt $$s/scripts/lyxpreview-platex2bitmap.py"    ""
930 \converter csv        lyx        "python -tt $$s/scripts/csv2lyx.py $$i $$o"    ""
931 \converter date       dateout    "python -tt $$s/scripts/date.py %d-%m-%Y > $$o"        ""
932 \converter docbook    docbook-xml "cp $$i $$o"  "xml"
933 \converter fen        asciichess "python -tt $$s/scripts/fen2ascii.py $$i $$o"  ""
934 \converter lyx        lyx13x     "python -tt $$s/lyx2lyx/lyx2lyx -t 221 $$i > $$o"      ""
935 \converter lyx        lyx14x     "python -tt $$s/lyx2lyx/lyx2lyx -t 245 $$i > $$o"      ""
936 \converter lyx        lyx15x     "python -tt $$s/lyx2lyx/lyx2lyx -t 276 $$i > $$o"      ""
937 \converter lyx        lyx16x     "python -tt $$s/lyx2lyx/lyx2lyx -t 345 $$i > $$o"      ""
938 \converter lyx        clyx       "python -tt $$s/lyx2lyx/lyx2lyx -c big5 -t 245 $$i > $$o"      ""
939 \converter lyx        jlyx       "python -tt $$s/lyx2lyx/lyx2lyx -c euc_jp -t 245 $$i > $$o"    ""
940 \converter lyx        klyx       "python -tt $$s/lyx2lyx/lyx2lyx -c euc_kr -t 245 $$i > $$o"    ""
941 \converter clyx       lyx        "python -tt $$s/lyx2lyx/lyx2lyx -c big5 $$i > $$o"     ""
942 \converter jlyx       lyx        "python -tt $$s/lyx2lyx/lyx2lyx -c euc_jp $$i > $$o"   ""
943 \converter klyx       lyx        "python -tt $$s/lyx2lyx/lyx2lyx -c euc_kr $$i > $$o"   ""
944 ''')
945
946
947 def checkDocBook():
948     ''' Check docbook '''
949     path, DOCBOOK = checkProg('SGML-tools 2.x (DocBook), db2x scripts or xsltproc', ['sgmltools', 'db2dvi', 'xsltproc'],
950         rc_entry = [
951             r'''\converter docbook    dvi        "sgmltools -b dvi $$i" ""
952 \converter docbook    html       "sgmltools -b html $$i"        ""''',
953             r'''\converter docbook    dvi        "db2dvi $$i"   ""
954 \converter docbook    html       "db2html $$i"  ""''',
955             r'''\converter docbook    dvi        ""     ""
956 \converter docbook    html       "" ""''',
957             r'''\converter docbook    dvi        ""     ""
958 \converter docbook    html       ""     ""'''])
959     #
960     if DOCBOOK != '':
961         return ('yes', 'true', '\\def\\hasdocbook{yes}')
962     else:
963         return ('no', 'false', '')
964
965
966 def checkOtherEntries():
967     ''' entries other than Format and Converter '''
968     checkProg('ChkTeX', ['chktex -n1 -n3 -n6 -n9 -n22 -n25 -n30 -n38'],
969         rc_entry = [ r'\chktex_command "%%"' ])
970     checkProgAlternatives('BibTeX or alternative programs', ['bibtex', 'bibtex8', 'biber'],
971         rc_entry = [ r'\bibtex_command "%%"' ],
972         alt_rc_entry = [ r'\bibtex_alternatives "%%"' ])
973     checkProg('a specific Japanese BibTeX variant', ['pbibtex', 'jbibtex', 'bibtex'],
974         rc_entry = [ r'\jbibtex_command "%%"' ])
975     checkProgAlternatives('available index processors', ['texindy', 'makeindex -c -q', 'xindy'],
976         rc_entry = [ r'\index_command "%%"' ],
977         alt_rc_entry = [ r'\index_alternatives "%%"' ])
978     checkProg('an index processor appropriate to Japanese', ['mendex -c -q', 'jmakeindex -c -q', 'makeindex -c -q'],
979         rc_entry = [ r'\jindex_command "%%"' ])
980     path, splitindex = checkProg('the splitindex processor', ['splitindex.pl', 'splitindex'],
981         rc_entry = [ r'\splitindex_command "%%"' ])
982     if splitindex == '':
983         checkProg('the splitindex processor (java version)', ['splitindex.class'],
984             rc_entry = [ r'\splitindex_command "java splitindex"' ])
985     checkProg('a nomenclature processor', ['makeindex'],
986         rc_entry = [ r'\nomencl_command "makeindex -s nomencl.ist"' ])
987     ## FIXME: OCTAVE is not used anywhere
988     # path, OCTAVE = checkProg('Octave', ['octave'])
989     ## FIXME: MAPLE is not used anywhere
990     # path, MAPLE = checkProg('Maple', ['maple'])
991     checkProg('a spool command', ['lp', 'lpr'],
992         rc_entry = [
993             r'''\print_spool_printerprefix "-d "
994 \print_spool_command "lp"''',
995             r'''\print_spool_printerprefix "-P",
996 \print_spool_command "lpr"''',
997             ''])
998     # Add the rest of the entries (no checkProg is required)
999     addToRC(r'''\copier    fig        "python -tt $$s/scripts/fig_copy.py $$i $$o"
1000 \copier    pstex      "python -tt $$s/scripts/tex_copy.py $$i $$o $$l"
1001 \copier    pdftex     "python -tt $$s/scripts/tex_copy.py $$i $$o $$l"
1002 \copier    program    "python -tt $$s/scripts/ext_copy.py $$i $$o"
1003 ''')
1004
1005
1006 def processLayoutFile(file, bool_docbook):
1007     ''' process layout file and get a line of result
1008         
1009         Declare lines look like this: (article.layout, scrbook.layout, svjog.layout)
1010         
1011         \DeclareLaTeXClass{article}
1012         \DeclareLaTeXClass[scrbook]{book (koma-script)}
1013         \DeclareLaTeXClass[svjour,svjog.clo]{article (Springer - svjour/jog)}
1014
1015         we expect output:
1016         
1017         "article" "article" "article" "false" "article.cls"
1018         "scrbook" "scrbook" "book (koma-script)" "false" "scrbook.cls"
1019         "svjog" "svjour" "article (Springer - svjour/jog)" "false" "svjour.cls,svjog.clo"
1020     '''
1021     def checkForClassExtension(x):
1022         '''if the extension for a latex class is not
1023            provided, add .cls to the classname'''
1024         if not '.' in x:
1025             return x.strip() + '.cls'
1026         else:
1027             return x.strip()
1028     classname = file.split(os.sep)[-1].split('.')[0]
1029     # return ('LaTeX', '[a,b]', 'a', ',b,c', 'article') for \DeclareLaTeXClass[a,b,c]{article}
1030     p = re.compile(r'\Declare(LaTeX|DocBook)Class\s*(\[([^,]*)(,.*)*\])*\s*{(.*)}')
1031     for line in open(file).readlines():
1032         res = p.search(line)
1033         if res != None:
1034             (classtype, optAll, opt, opt1, desc) = res.groups()
1035             avai = {'LaTeX':'false', 'DocBook':bool_docbook}[classtype]
1036             if opt == None:
1037                 opt = classname
1038                 prereq_latex = checkForClassExtension(classname)
1039             else:
1040                 prereq_list = optAll[1:-1].split(',')
1041                 prereq_list = map(checkForClassExtension, prereq_list)
1042                 prereq_latex = ','.join(prereq_list)
1043             prereq_docbook = {'true':'', 'false':'docbook'}[bool_docbook]
1044             prereq = {'LaTeX':prereq_latex, 'DocBook':prereq_docbook}[classtype]
1045             return '"%s" "%s" "%s" "%s" "%s"\n' % (classname, opt, desc, avai, prereq)
1046     logger.warning("Layout file " + file + " has no \DeclareXXClass line. ")
1047     return ""
1048
1049
1050 def checkLatexConfig(check_config, bool_docbook):
1051     ''' Explore the LaTeX configuration 
1052         Return None (will be passed to sys.exit()) for success.
1053     '''
1054     msg = 'checking LaTeX configuration... '
1055     # if --without-latex-config is forced, or if there is no previous 
1056     # version of textclass.lst, re-generate a default file.
1057     if not os.path.isfile('textclass.lst') or not check_config:
1058         # remove the files only if we want to regenerate
1059         removeFiles(['textclass.lst', 'packages.lst'])
1060         #
1061         # Then, generate a default textclass.lst. In case configure.py
1062         # fails, we still have something to start lyx.
1063         logger.info(msg + ' default values')
1064         logger.info('+checking list of textclasses... ')
1065         tx = open('textclass.lst', 'w')
1066         tx.write('''
1067 # This file declares layouts and their associated definition files
1068 # (include dir. relative to the place where this file is).
1069 # It contains only default values, since chkconfig.ltx could not be run
1070 # for some reason. Run ./configure.py if you need to update it after a
1071 # configuration change.
1072 ''')
1073         # build the list of available layout files and convert it to commands
1074         # for chkconfig.ltx
1075         foundClasses = []
1076         for file in glob.glob( os.path.join('layouts', '*.layout') ) + \
1077             glob.glob( os.path.join(srcdir, 'layouts', '*.layout' ) ) :
1078             # valid file?
1079             if not os.path.isfile(file): 
1080                 continue
1081             # get stuff between /xxxx.layout .
1082             classname = file.split(os.sep)[-1].split('.')[0]
1083             #  tr ' -' '__'`
1084             cleanclass = classname.replace(' ', '_')
1085             cleanclass = cleanclass.replace('-', '_')
1086             # make sure the same class is not considered twice
1087             if foundClasses.count(cleanclass) == 0: # not found before
1088                 foundClasses.append(cleanclass)
1089                 retval = processLayoutFile(file, bool_docbook)
1090                 if retval != "":
1091                     tx.write(retval)
1092         tx.close()
1093         logger.info('\tdone')
1094     if not check_config:
1095         return None
1096     # the following will generate textclass.lst.tmp, and packages.lst.tmp
1097     logger.info(msg + '\tauto')
1098     removeFiles(['wrap_chkconfig.ltx', 'chkconfig.vars', \
1099         'chkconfig.classes', 'chklayouts.tex'])
1100     rmcopy = False
1101     if not os.path.isfile( 'chkconfig.ltx' ):
1102         shutil.copyfile( os.path.join(srcdir, 'chkconfig.ltx'), 'chkconfig.ltx' )
1103         rmcopy = True
1104     writeToFile('wrap_chkconfig.ltx', '%s\n\\input{chkconfig.ltx}\n' % docbook_cmd)
1105     # Construct the list of classes to test for.
1106     # build the list of available layout files and convert it to commands
1107     # for chkconfig.ltx
1108     declare = re.compile(r'\Declare(LaTeX|DocBook)Class\s*(\[([^,]*)(,.*)*\])*\s*{(.*)}')
1109     empty = re.compile(r'^\s*$')
1110     testclasses = list()
1111     for file in glob.glob( os.path.join('layouts', '*.layout') ) + \
1112         glob.glob( os.path.join(srcdir, 'layouts', '*.layout' ) ) :
1113         nodeclaration = False
1114         if not os.path.isfile(file):
1115             continue
1116         classname = file.split(os.sep)[-1].split('.')[0]
1117         for line in open(file).readlines():
1118             if not empty.match(line) and line[0] != '#':
1119                 logger.warning("Failed to find valid \Declare line for layout file `" + file + "'.\n\t=> Skipping this file!")
1120                 nodeclaration = True
1121                 break
1122             if declare.search(line) == None:
1123                 continue
1124             testclasses.append("\\TestDocClass{%s}{%s}" % (classname, line[1:].strip()))
1125             break
1126         if nodeclaration:
1127             continue
1128     testclasses.sort()
1129     cl = open('chklayouts.tex', 'w')
1130     for line in testclasses:
1131         cl.write(line + '\n')
1132     cl.close()
1133     #
1134     # we have chklayouts.tex, then process it
1135     fout = os.popen(LATEX + ' wrap_chkconfig.ltx')
1136     while True:
1137         line = fout.readline()
1138         if not line:
1139             break;
1140         if re.match('^\+', line):
1141             logger.info(line.strip())
1142     # if the command succeeds, None will be returned
1143     ret = fout.close()
1144     #
1145     # currently, values in chhkconfig are only used to set
1146     # \font_encoding
1147     values = {}
1148     for line in open('chkconfig.vars').readlines():
1149         key, val = re.sub('-', '_', line).split('=')
1150         val = val.strip()
1151         values[key] = val.strip("'")
1152     # chk_fontenc may not exist 
1153     try:
1154         addToRC(r'\font_encoding "%s"' % values["chk_fontenc"])
1155     except:
1156         pass
1157     if rmcopy:   # remove the copied file
1158         removeFiles( [ 'chkconfig.ltx' ] )
1159     # if configure successed, move textclass.lst.tmp to textclass.lst
1160     # and packages.lst.tmp to packages.lst
1161     if os.path.isfile('textclass.lst.tmp') and len(open('textclass.lst.tmp').read()) > 0 \
1162         and os.path.isfile('packages.lst.tmp') and len(open('packages.lst.tmp').read()) > 0:
1163         shutil.move('textclass.lst.tmp', 'textclass.lst')
1164         shutil.move('packages.lst.tmp', 'packages.lst')
1165     return ret
1166
1167
1168 def checkModulesConfig():
1169   removeFiles(['lyxmodules.lst', 'chkmodules.tex'])
1170
1171   logger.info('+checking list of modules... ')
1172   tx = open('lyxmodules.lst', 'w')
1173   tx.write('''## This file declares modules and their associated definition files.
1174 ## It has been automatically generated by configure
1175 ## Use "Options/Reconfigure" if you need to update it after a
1176 ## configuration change. 
1177 ## "ModuleName" "filename" "Description" "Packages" "Requires" "Excludes" "Category"
1178 ''')
1179
1180   # build the list of available modules
1181   seen = []
1182   # note that this searches the local directory first, then the
1183   # system directory. that way, we pick up the user's version first.
1184   for file in glob.glob( os.path.join('layouts', '*.module') ) + \
1185       glob.glob( os.path.join(srcdir, 'layouts', '*.module' ) ) :
1186       # valid file?
1187       logger.info(file)
1188       if not os.path.isfile(file): 
1189           continue
1190
1191       filename = file.split(os.sep)[-1]
1192       filename = filename[:-7]
1193       if seen.count(filename):
1194           continue
1195
1196       seen.append(filename)
1197       retval = processModuleFile(file, filename, bool_docbook)
1198       if retval != "":
1199           tx.write(retval)
1200   tx.close()
1201   logger.info('\tdone')
1202
1203
1204 def processModuleFile(file, filename, bool_docbook):
1205     ''' process module file and get a line of result
1206
1207         The top of a module file should look like this:
1208           #\DeclareLyXModule[LaTeX Packages]{ModuleName}
1209           #DescriptionBegin
1210           #...body of description...
1211           #DescriptionEnd
1212           #Requires: [list of required modules]
1213           #Excludes: [list of excluded modules]
1214           #Category: [category name]
1215         The last three lines are optional (though do give a category).
1216         We expect output:
1217           "ModuleName" "filename" "Description" "Packages" "Requires" "Excludes" "Category"
1218     '''
1219     remods = re.compile(r'\DeclareLyXModule\s*(?:\[([^]]*?)\])?{(.*)}')
1220     rereqs = re.compile(r'#+\s*Requires: (.*)')
1221     reexcs = re.compile(r'#+\s*Excludes: (.*)')
1222     recaty = re.compile(r'#+\s*Category: (.*)')
1223     redbeg = re.compile(r'#+\s*DescriptionBegin\s*$')
1224     redend = re.compile(r'#+\s*DescriptionEnd\s*$')
1225
1226     modname = desc = pkgs = req = excl = catgy = ""
1227     readingDescription = False
1228     descLines = []
1229
1230     for line in open(file).readlines():
1231       if readingDescription:
1232         res = redend.search(line)
1233         if res != None:
1234           readingDescription = False
1235           desc = " ".join(descLines)
1236           # Escape quotes.
1237           desc = desc.replace('"', '\\"')
1238           continue
1239         descLines.append(line[1:].strip())
1240         continue
1241       res = redbeg.search(line)
1242       if res != None:
1243         readingDescription = True
1244         continue
1245       res = remods.search(line)
1246       if res != None:
1247           (pkgs, modname) = res.groups()
1248           if pkgs == None:
1249             pkgs = ""
1250           else:
1251             tmp = [s.strip() for s in pkgs.split(",")]
1252             pkgs = ",".join(tmp)
1253           continue
1254       res = rereqs.search(line)
1255       if res != None:
1256         req = res.group(1)
1257         tmp = [s.strip() for s in req.split("|")]
1258         req = "|".join(tmp)
1259         continue
1260       res = reexcs.search(line)
1261       if res != None:
1262         excl = res.group(1)
1263         tmp = [s.strip() for s in excl.split("|")]
1264         excl = "|".join(tmp)
1265         continue
1266       res = recaty.search(line)
1267       if res != None:
1268         catgy = res.group(1)
1269         continue
1270
1271     if modname == "":
1272       logger.warning("Module file without \DeclareLyXModule line. ")
1273       return ""
1274
1275     if pkgs != "":
1276         # this module has some latex dependencies:
1277         # append the dependencies to chkmodules.tex,
1278         # which is \input'ed by chkconfig.ltx
1279         testpackages = list()
1280         for pkg in pkgs.split(","):
1281             if "->" in pkg:
1282                 # this is a converter dependency: skip
1283                 continue
1284             if pkg.endswith(".sty"):
1285                 pkg = pkg[:-4]
1286             testpackages.append("\\TestPackage{%s}" % (pkg,))
1287         cm = open('chkmodules.tex', 'a')
1288         for line in testpackages:
1289             cm.write(line + '\n')
1290         cm.close()
1291
1292     return '"%s" "%s" "%s" "%s" "%s" "%s" "%s"\n' % (modname, filename, desc, pkgs, req, excl, catgy)
1293     
1294
1295
1296 def checkTeXAllowSpaces():
1297     ''' Let's check whether spaces are allowed in TeX file names '''
1298     tex_allows_spaces = 'false'
1299     if lyx_check_config:
1300         msg = "Checking whether TeX allows spaces in file names... "
1301         writeToFile('a b.tex', r'\message{working^^J}' )
1302         if LATEX != '':
1303             if os.name == 'nt':
1304                 latex_out = cmdOutput(LATEX + r""" "\nonstopmode\input{\"a b\"}" """)
1305             else:
1306                 latex_out = cmdOutput(LATEX + r""" '\nonstopmode\input{"a b"}' """)
1307         else:
1308             latex_out = ''
1309         if 'working' in latex_out:
1310             logger.info(msg + 'yes')
1311             tex_allows_spaces = 'true'
1312         else:
1313             logger.info(msg + 'no')
1314             tex_allows_spaces = 'false'
1315         addToRC(r'\tex_allows_spaces ' + tex_allows_spaces)
1316         removeFiles( [ 'a b.tex', 'a b.log', 'texput.log' ])
1317
1318
1319 def removeTempFiles():
1320     # Final clean-up
1321     if not lyx_keep_temps:
1322         removeFiles(['chkconfig.vars',  \
1323             'wrap_chkconfig.ltx', 'wrap_chkconfig.log', \
1324             'chklayouts.tex', 'chkmodules.tex', 'missfont.log', 
1325             'chklatex.ltx', 'chklatex.log'])
1326
1327
1328 if __name__ == '__main__':
1329     lyx_check_config = True
1330     outfile = 'lyxrc.defaults'
1331     rc_entries = ''
1332     lyx_keep_temps = False
1333     version_suffix = ''
1334     ## Parse the command line
1335     for op in sys.argv[1:]:   # default shell/for list is $*, the options
1336         if op in [ '-help', '--help', '-h' ]:
1337             print '''Usage: configure [options]
1338 Options:
1339     --help                   show this help lines
1340     --keep-temps             keep temporary files (for debug. purposes)
1341     --without-latex-config   do not run LaTeX to determine configuration
1342     --with-version-suffix=suffix suffix of binary installed files
1343 '''
1344             sys.exit(0)
1345         elif op == '--without-latex-config':
1346             lyx_check_config = False
1347         elif op == '--keep-temps':
1348             lyx_keep_temps = True
1349         elif op[0:22] == '--with-version-suffix=':  # never mind if op is not long enough
1350             version_suffix = op[22:]
1351         else:
1352             print "Unknown option", op
1353             sys.exit(1)
1354     #
1355     # check if we run from the right directory
1356     srcdir = os.path.dirname(sys.argv[0])
1357     if srcdir == '':
1358         srcdir = '.'
1359     if not os.path.isfile( os.path.join(srcdir, 'chkconfig.ltx') ):
1360         logger.error("configure: error: cannot find chkconfig.ltx script")
1361         sys.exit(1)
1362     setEnviron()
1363     createDirectories()
1364     dtl_tools = checkDTLtools()
1365     ## Write the first part of outfile
1366     writeToFile(outfile, '''# This file has been automatically generated by LyX' lib/configure.py
1367 # script. It contains default settings that have been determined by
1368 # examining your system. PLEASE DO NOT MODIFY ANYTHING HERE! If you
1369 # want to customize LyX, use LyX' Preferences dialog or modify directly 
1370 # the "preferences" file instead. Any setting in that file will
1371 # override the values given here.
1372 ''')
1373     # check latex
1374     LATEX = checkLatex(dtl_tools)
1375     checkFormatEntries(dtl_tools)
1376     checkConverterEntries()
1377     (chk_docbook, bool_docbook, docbook_cmd) = checkDocBook()
1378     checkTeXAllowSpaces()
1379     windows_style_tex_paths = checkTeXPaths()
1380     if windows_style_tex_paths != '':
1381         addToRC(r'\tex_expects_windows_paths %s' % windows_style_tex_paths)
1382     checkOtherEntries()
1383     checkModulesConfig()
1384     # --without-latex-config can disable lyx_check_config
1385     ret = checkLatexConfig(lyx_check_config and LATEX != '', bool_docbook)
1386     removeTempFiles()
1387     # The return error code can be 256. Because most systems expect an error code
1388     # in the range 0-127, 256 can be interpretted as 'success'. Because we expect
1389     # a None for success, 'ret is not None' is used to exit.
1390     sys.exit(ret is not None)