]> git.lyx.org Git - lyx.git/blob - lib/configure.py
documentation: the macro chapter for the Math manual
[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
12
13
14 class Tee:
15     ''' Writing to a Tee object will write to all file objects it keeps.
16         That is to say, writing to Tee(sys.stdout, open(logfile, 'w')) will
17         write to sys.stdout as well as a log file.
18     '''
19     def __init__(self, *args):
20         self.files = args
21
22     def write(self, data):
23         for f in self.files:
24             result = f.write(data)
25         return result
26
27     def writelines(self, seq):
28         for i in seq:
29             self.write(i)
30
31
32 def writeToFile(filename, lines, append = False):
33     " utility function: write or append lines to filename "
34     if append:
35         file = open(filename, 'a')
36     else:
37         file = open(filename, 'w')
38     file.write(lines)
39     file.close()
40
41
42 def addToRC(lines):
43     ''' utility function: shortcut for appending lines to outfile
44         add newline at the end of lines.
45     '''
46     if lines.strip() != '':
47         writeToFile(outfile, lines + '\n', append = True)
48
49
50 def removeFiles(filenames):
51     '''utility function: 'rm -f'
52         ignore errors when file does not exist, or is a directory.
53     '''
54     for file in filenames:
55         try:
56             os.remove(file)
57         except:
58             pass
59
60
61 def cmdOutput(cmd):
62     '''utility function: run a command and get its output as a string
63         cmd: command to run
64     '''
65     fout = os.popen(cmd)
66     output = fout.read()
67     fout.close()
68     return output.strip()
69
70
71 def setEnviron():
72     ''' I do not really know why this is useful, but we might as well keep it.
73         NLS nuisances.
74         Only set these to C if already set.  These must not be set unconditionally
75         because not all systems understand e.g. LANG=C (notably SCO).
76         Fixing LC_MESSAGES prevents Solaris sh from translating var values in set!
77         Non-C LC_CTYPE values break the ctype check.
78     '''
79     os.environ['LANG'] = os.getenv('LANG', 'C')
80     os.environ['LC'] = os.getenv('LC_ALL', 'C')
81     os.environ['LC_MESSAGE'] = os.getenv('LC_MESSAGE', 'C')
82     os.environ['LC_CTYPE'] = os.getenv('LC_CTYPE', 'C')
83
84
85 def createDirectories():
86     ''' Create the build directories if necessary '''
87     for dir in ['bind', 'clipart', 'doc', 'examples', 'images', 'kbd', \
88         'layouts', 'scripts', 'templates', 'ui' ]:
89         if not os.path.isdir( dir ):
90             try:
91                 os.mkdir( dir)
92             except:
93                 print "Failed to create directory ", dir
94                 sys.exit(1)
95
96
97 def checkTeXPaths():
98     ''' Determine the path-style needed by the TeX engine on Win32 (Cygwin) '''
99     windows_style_tex_paths = ''
100     if os.name == 'nt' or sys.platform == 'cygwin':
101         from tempfile import mkstemp
102         fd, tmpfname = mkstemp(suffix='.ltx')
103         if os.name == 'nt':
104             inpname = tmpfname.replace('\\', '/')
105         else:
106             inpname = cmdOutput('cygpath -m ' + tmpfname)
107         logname = os.path.basename(inpname.replace('.ltx', '.log'))
108         inpname = inpname.replace('~', '\\string~')
109         os.write(fd, r'\relax')
110         os.close(fd)
111         latex_out = cmdOutput(r'latex "\nonstopmode\input{%s}"' % inpname)
112         if 'Error' in latex_out:
113             print "configure: TeX engine needs posix-style paths in latex files"
114             windows_style_tex_paths = 'false'
115         else:
116             print "configure: TeX engine needs windows-style paths in latex files"
117             windows_style_tex_paths = 'true'
118         removeFiles([tmpfname, logname, 'texput.log'])
119     return windows_style_tex_paths
120
121
122 ## Searching some useful programs
123 def checkProg(description, progs, rc_entry = [], path = [], not_found = ''):
124     '''
125         This function will search a program in $PATH plus given path
126         If found, return directory and program name (not the options).
127
128         description: description of the program
129
130         progs: check programs, for each prog, the first word is used
131             for searching but the whole string is used to replace
132             %% for a rc_entry. So, feel free to add '$$i' etc for programs.
133
134         path: additional pathes
135
136         rc_entry: entry to outfile, can be
137             1. emtpy: no rc entry will be added
138             2. one pattern: %% will be replaced by the first found program,
139                 or '' if no program is found.
140             3. several patterns for each prog and not_found. This is used 
141                 when different programs have different usages. If you do not 
142                 want not_found entry to be added to the RC file, you can specify 
143                 an entry for each prog and use '' for the not_found entry.
144
145         not_found: the value that should be used instead of '' if no program
146             was found
147
148     '''
149     # one rc entry for each progs plus not_found entry
150     if len(rc_entry) > 1 and len(rc_entry) != len(progs) + 1:
151         print "rc entry should have one item or item for each prog and not_found."
152         sys.exit(2)
153     print 'checking for ' + description + '...'
154     ## print '(' + ','.join(progs) + ')',
155     for idx in range(len(progs)):
156         # ac_prog may have options, ac_word is the command name
157         ac_prog = progs[idx]
158         ac_word = ac_prog.split(' ')[0]
159         print '+checking for "' + ac_word + '"... ',
160         path = os.environ["PATH"].split(os.pathsep) + path
161         extlist = ['']
162         if os.environ.has_key("PATHEXT"):
163             extlist = extlist + os.environ["PATHEXT"].split(os.pathsep)
164         for ac_dir in path:
165             for ext in extlist:
166                 if os.path.isfile( os.path.join(ac_dir, ac_word + ext) ):
167                     print ' yes'
168                     # write rc entries for this command
169                     if len(rc_entry) == 1:
170                         addToRC(rc_entry[0].replace('%%', ac_prog))
171                     elif len(rc_entry) > 1:
172                         addToRC(rc_entry[idx].replace('%%', ac_prog))
173                     return [ac_dir, ac_word]
174         # if not successful
175         print ' no'
176     # write rc entries for 'not found'
177     if len(rc_entry) > 0:  # the last one.
178         addToRC(rc_entry[-1].replace('%%', not_found))
179     return ['', not_found]
180
181
182 def checkViewer(description, progs, rc_entry = [], path = []):
183     ''' The same as checkProg, but for viewers and editors '''
184     return checkProg(description, progs, rc_entry, path, not_found = 'auto')
185
186
187 def checkDTLtools():
188     ''' Check whether DTL tools are available (Windows only) '''
189     # Find programs! Returned path is not used now
190     if ((os.name == 'nt' or sys.platform == 'cygwin') and
191             checkProg('DVI to DTL converter', ['dv2dt']) != ['', ''] and
192             checkProg('DTL to DVI converter', ['dt2dv']) != ['', '']):
193         dtl_tools = True
194     else:
195         dtl_tools = False
196     return dtl_tools
197
198
199 def checkLatex(dtl_tools):
200     ''' Check latex, return lyx_check_config '''
201     path, LATEX = checkProg('a Latex2e program', ['latex $$i', 'platex $$i', 'latex2e $$i'])
202     path, PPLATEX = checkProg('a DVI postprocessing program', ['pplatex $$i'])
203     #-----------------------------------------------------------------
204     path, PLATEX = checkProg('pLaTeX, the Japanese LaTeX', ['platex $$i'])
205     if PLATEX != '':
206         # check if PLATEX is pLaTeX2e
207         writeToFile('chklatex.ltx', '''
208 \\nonstopmode
209 \\@@end
210 ''')
211         # run platex on chklatex.ltx and check result
212         if cmdOutput(PLATEX + ' chklatex.ltx').find('pLaTeX2e') != -1:
213             # We have the Japanese pLaTeX2e
214             addToRC(r'\converter platex   dvi       "%s"   "latex"' % PLATEX)
215             LATEX = PLATEX
216         else:
217             PLATEX = ''
218             removeFiles(['chklatex.ltx', 'chklatex.log'])
219     #-----------------------------------------------------------------
220     # use LATEX to convert from latex to dvi if PPLATEX is not available    
221     if PPLATEX == '':
222         PPLATEX = LATEX
223     if dtl_tools:
224         # Windows only: DraftDVI
225         addToRC(r'''\converter latex      dvi2       "%s"       "latex"
226 \converter dvi2       dvi        "python -tt $$s/scripts/clean_dvi.py $$i $$o"  ""''' % PPLATEX)
227     else:
228         addToRC(r'\converter latex      dvi        "%s" "latex"' % PPLATEX)
229     # no latex
230     if LATEX != '':
231         # Check if latex is usable
232         writeToFile('chklatex.ltx', '''
233 \\nonstopmode\\makeatletter
234 \\ifx\\undefined\\documentclass\\else
235   \\message{ThisIsLaTeX2e}
236 \\fi
237 \\@@end
238 ''')
239         # run latex on chklatex.ltx and check result
240         if cmdOutput(LATEX + ' chklatex.ltx').find('ThisIsLaTeX2e') != -1:
241             # valid latex2e
242             return LATEX
243         else:
244             print "Latex not usable (not LaTeX2e) "
245         # remove temporary files
246         removeFiles(['chklatex.ltx', 'chklatex.log'])
247     return ''
248
249
250 def checkFormatEntries(dtl_tools):  
251     ''' Check all formats (\Format entries) '''
252     checkViewer('a Tgif viewer and editor', ['tgif'],
253         rc_entry = [r'\Format tgif       obj     Tgif                   "" "%%" "%%"    "vector"'])
254     #
255     checkViewer('a FIG viewer and editor', ['xfig', 'jfig3-itext.jar', 'jfig3.jar'],
256         rc_entry = [r'\Format fig        fig     FIG                    "" "%%" "%%"    "vector"'])
257     #
258     checkViewer('a Grace viewer and editor', ['xmgrace'],
259         rc_entry = [r'\Format agr        agr     Grace                  "" "%%" "%%"    "vector"'])
260     #
261     checkViewer('a FEN viewer and editor', ['xboard -lpf $$i -mode EditPosition'],
262         rc_entry = [r'\Format fen        fen     FEN                    "" "%%" "%%"    ""'])
263     #
264     path, iv = checkViewer('a raster image viewer', ['xv', 'kview', 'gimp-remote', 'gimp'])
265     path, ie = checkViewer('a raster image editor', ['gimp-remote', 'gimp'])
266     addToRC(r'''\Format bmp        bmp     BMP                    "" "%s"       "%s"    ""
267 \Format gif        gif     GIF                    "" "%s"       "%s"    ""
268 \Format jpg        jpg     JPEG                   "" "%s"       "%s"    ""
269 \Format pbm        pbm     PBM                    "" "%s"       "%s"    ""
270 \Format pgm        pgm     PGM                    "" "%s"       "%s"    ""
271 \Format png        png     PNG                    "" "%s"       "%s"    ""
272 \Format ppm        ppm     PPM                    "" "%s"       "%s"    ""
273 \Format tiff       tif     TIFF                   "" "%s"       "%s"    ""
274 \Format xbm        xbm     XBM                    "" "%s"       "%s"    ""
275 \Format xpm        xpm     XPM                    "" "%s"       "%s"    ""''' % \
276         (iv, ie, iv, ie, iv, ie, iv, ie, iv, ie, iv, ie, iv, ie, iv, ie, iv, ie, iv, ie) )
277     #
278     checkViewer('a text editor', ['sensible-editor', 'xemacs', 'gvim', 'kedit', 'kwrite', 'kate', \
279         'nedit', 'gedit', 'notepad'],
280         rc_entry = [r'''\Format asciichess asc    "Plain text (chess output)"  "" ""    "%%"    ""
281 \Format asciiimage asc    "Plain text (image)"         "" ""    "%%"    ""
282 \Format asciixfig  asc    "Plain text (Xfig output)"   "" ""    "%%"    ""
283 \Format dateout    tmp    "date (output)"         "" "" "%%"    ""
284 \Format docbook    sgml    DocBook                B  "" "%%"    "document"
285 \Format docbook-xml xml   "Docbook (XML)"         "" "" "%%"    "document"
286 \Format dot        dot    "Graphviz Dot"          "" "" "%%"    "vector"
287 \Format platex     tex    "LaTeX (pLaTeX)"        "" "" "%%"    "document"
288 \Format literate   nw      NoWeb                  N  "" "%%"    "document"
289 \Format lilypond   ly     "LilyPond music"        "" "" "%%"    "vector"
290 \Format latex      tex    "LaTeX (plain)"         L  "" "%%"    "document"
291 \Format pdflatex   tex    "LaTeX (pdflatex)"      "" "" "%%"    "document"
292 \Format text       txt    "Plain text"            a  "" "%%"    "document"
293 \Format text2      txt    "Plain text (pstotext)" "" "" "%%"    "document"
294 \Format text3      txt    "Plain text (ps2ascii)" "" "" "%%"    "document"
295 \Format text4      txt    "Plain text (catdvi)"   "" "" "%%"    "document"
296 \Format textparagraph txt "Plain Text, Join Lines" "" ""        "%%"    "document"''' ])
297  #
298     checkViewer('a BibTeX editor', ['sensible-editor', 'jabref', 'JabRef', \
299         'pybliographic', 'bibdesk', 'gbib', 'kbib', \
300         'kbibtex', 'sixpack', 'bibedit', 'tkbibtex' \
301         'xemacs', 'gvim', 'kedit', 'kwrite', 'kate', \
302         'nedit', 'gedit', 'notepad'],
303         rc_entry = [r'''\Format bibtex bib    "BibTeX"         "" ""    "%%"    ""''' ])
304     #
305     #checkProg('a Postscript interpreter', ['gs'],
306     #  rc_entry = [ r'\ps_command "%%"' ])
307     checkViewer('a Postscript previewer', ['kghostview', 'evince', 'gv', 'ghostview -swap'],
308         rc_entry = [r'''\Format eps        eps     EPS                    "" "%%"       ""      "vector"
309 \Format ps         ps      Postscript             t  "%%"       ""      "document,vector"'''])
310     #
311     checkViewer('a PDF previewer', ['kpdf', 'evince', 'kghostview', 'xpdf', 'acrobat', 'acroread', \
312                     'gv', 'ghostview'],
313         rc_entry = [r'''\Format pdf        pdf    "PDF (ps2pdf)"          P  "%%"       ""      "document,vector"
314 \Format pdf2       pdf    "PDF (pdflatex)"        F  "%%"       ""      "document,vector"
315 \Format pdf3       pdf    "PDF (dvipdfm)"         m  "%%"       ""      "document,vector"'''])
316     #
317     checkViewer('a DVI previewer', ['xdvi', 'kdvi'],
318         rc_entry = [r'\Format dvi        dvi     DVI                    D  "%%" ""      "document,vector"'])
319     if dtl_tools:
320         # Windows only: DraftDVI
321         addToRC(r'\Format dvi2       dvi     DraftDVI               ""  ""      ""      "vector"')
322     #
323     checkViewer('an HTML previewer', ['firefox', 'mozilla file://$$p$$i', 'netscape'],
324         rc_entry = [r'\Format html       html    HTML                   H  "%%" ""      "document"'])
325     #
326     checkViewer('Noteedit', ['noteedit'],
327         rc_entry = [r'\Format noteedit   not     Noteedit               "" "%%" "%%"    "vector"'])
328     #
329     checkViewer('an OpenDocument viewer', ['swriter', 'oowriter'],
330         rc_entry = [r'\Format odt        odt     OpenDocument           "" "%%" "%%"    "document,vector"'])
331     #
332     # entried that do not need checkProg
333     addToRC(r'''\Format date       ""     "date command"          "" "" ""      ""
334 \Format csv        csv    "Table (CSV)"  "" ""  ""      "document"
335 \Format fax        ""      Fax                    "" "" ""      "document"
336 \Format lyx        lyx     LyX                    "" "" ""      ""
337 \Format lyx13x     lyx13  "LyX 1.3.x"             "" "" ""      "document"
338 \Format lyx14x     lyx14  "LyX 1.4.x"             "" "" ""      "document"
339 \Format lyx15x     lyx15  "LyX 1.5.x"             "" "" ""      "document"
340 \Format lyx16x     lyx16  "LyX 1.6.x"             "" "" ""      "document"
341 \Format clyx       cjklyx "CJK LyX 1.4.x (big5)"  "" "" ""      "document"
342 \Format jlyx       cjklyx "CJK LyX 1.4.x (euc-jp)" "" ""        ""      "document"
343 \Format klyx       cjklyx "CJK LyX 1.4.x (euc-kr)" "" ""        ""      "document"
344 \Format lyxpreview lyxpreview "LyX Preview"       "" "" ""      ""
345 \Format lyxpreview-platex lyxpreview-platex "LyX Preview (pLaTeX)"       "" ""  ""      ""
346 \Format pdftex     pdftex_t PDFTEX                "" "" ""      ""
347 \Format program    ""      Program                "" "" ""      ""
348 \Format pstex      pstex_t PSTEX                  "" "" ""      ""
349 \Format rtf        rtf    "Rich Text Format"      "" "" ""      "document,vector"
350 \Format sxw        sxw    "OpenOffice.Org (sxw)"  ""  ""        ""      "document,vector"
351 \Format wmf        wmf    "Windows Metafile"      "" "" ""      "vector"
352 \Format emf        emf    "Enhanced Metafile"     "" "" ""      "vector"
353 \Format word       doc    "MS Word"               W  "" ""      "document,vector"
354 \Format wordhtml   html   "HTML (MS Word)"        "" ""        ""       "document"
355 ''')
356
357
358 def checkConverterEntries():
359     ''' Check all converters (\converter entries) '''
360     checkProg('the pdflatex program', ['pdflatex $$i'],
361         rc_entry = [ r'\converter pdflatex   pdf2       "%%"    "latex"' ])
362     
363     ''' If we're running LyX in-place then tex2lyx will be found in
364             ../src/tex2lyx. Add this directory to the PATH temporarily and
365             search for tex2lyx.
366             Use PATH to avoid any problems with paths-with-spaces.
367     '''
368     path_orig = os.environ["PATH"]
369     os.environ["PATH"] = os.path.join('..', 'src', 'tex2lyx') + \
370         os.pathsep + path_orig
371
372     checkProg('a LaTeX/Noweb -> LyX converter', ['tex2lyx', 'tex2lyx' + version_suffix],
373         rc_entry = [r'''\converter latex      lyx        "%% -f $$i $$o"        ""
374 \converter literate   lyx        "%% -n -f $$i $$o"     ""'''])
375
376     os.environ["PATH"] = path_orig
377
378     #
379     checkProg('a Noweb -> LaTeX converter', ['noweave -delay -index $$i > $$o'],
380         rc_entry = [r'''\converter literate   latex      "%%"   ""
381 \converter literate   pdflatex      "%%"        ""'''])
382     #
383     checkProg('an HTML -> LaTeX converter', ['html2latex $$i', 'gnuhtml2latex $$i', \
384         'htmltolatex -input $$i -output $$o', 'java -jar htmltolatex.jar -input $$i -output $$o'],
385         rc_entry = [ r'\converter html       latex      "%%"    ""' ])
386     #
387     checkProg('an MS Word -> LaTeX converter', ['wvCleanLatex $$i $$o'],
388         rc_entry = [ r'\converter word       latex      "%%"    ""' ])
389     # On SuSE the scripts have a .sh suffix, and on debian they are in /usr/share/tex4ht/
390     path, htmlconv = checkProg('a LaTeX -> HTML converter', ['htlatex $$i', 'htlatex.sh $$i', \
391         '/usr/share/tex4ht/htlatex $$i', 'tth  -t -e2 -L$$b < $$i > $$o', \
392         'latex2html -no_subdir -split 0 -show_section_numbers $$i', 'hevea -s $$i'],
393         rc_entry = [ r'\converter latex      html       "%%"    "needaux"' ])
394     if htmlconv.find('htlatex') >= 0 or htmlconv == 'latex2html':
395       addToRC(r'''\copier    html       "python -tt $$s/scripts/ext_copy.py -e html,png,css $$i $$o"''')
396     else:
397       addToRC(r'''\copier    html       "python -tt $$s/scripts/ext_copy.py $$i $$o"''')
398
399     # On SuSE the scripts have a .sh suffix, and on debian they are in /usr/share/tex4ht/
400     path, htmlconv = checkProg('a LaTeX -> MS Word converter', ["htlatex $$i 'html,word' 'symbol/!' '-cvalidate'", \
401         "htlatex.sh $$i 'html,word' 'symbol/!' '-cvalidate'", \
402         "/usr/share/tex4ht/htlatex $$i 'html,word' 'symbol/!' '-cvalidate'"],
403         rc_entry = [ r'\converter latex      wordhtml   "%%"    "needaux"' ])
404     if htmlconv.find('htlatex') >= 0:
405       addToRC(r'''\copier    wordhtml       "python -tt $$s/scripts/ext_copy.py -e html,png,css $$i $$o"''')
406     #
407     checkProg('an OpenOffice.org -> LaTeX converter', ['w2l -clean $$i'],
408         rc_entry = [ r'\converter sxw        latex      "%%"    ""' ])
409     #
410     checkProg('an OpenDocument -> LaTeX converter', ['w2l -clean $$i'],
411         rc_entry = [ r'\converter odt        latex      "%%"    ""' ])
412     # According to http://www.tug.org/applications/tex4ht/mn-commands.html
413     # the command mk4ht oolatex $$i has to be used as default,
414     # but as this would require to have Perl installed, in MiKTeX oolatex is
415     # directly available as application.
416     # On SuSE the scripts have a .sh suffix, and on debian they are in /usr/share/tex4ht/
417     # Both SuSE and debian have oolatex
418     checkProg('a LaTeX -> Open Document converter', [
419         'oolatex $$i', 'mk4ht oolatex $$i', 'oolatex.sh $$i', '/usr/share/tex4ht/oolatex $$i',
420         'htlatex $$i \'xhtml,ooffice\' \'ooffice/! -cmozhtf\' \'-coo\' \'-cvalidate\''],
421         rc_entry = [ r'\converter latex      odt        "%%"    "needaux"' ])
422     # On windows it is called latex2rt.exe
423     checkProg('a LaTeX -> RTF converter', ['latex2rtf -p -S -o $$o $$i', 'latex2rt -p -S -o $$o $$i'],
424         rc_entry = [ r'\converter latex      rtf        "%%"    "needaux"' ])
425     #
426     checkProg('a RTF -> HTML converter', ['unrtf --html  $$i > $$o'],
427         rc_entry = [ r'\converter rtf      html        "%%"     ""' ])
428     #
429     checkProg('a PS to PDF converter', ['ps2pdf13 $$i $$o'],
430         rc_entry = [ r'\converter ps         pdf        "%%"    ""' ])
431     #
432     checkProg('a PS to TXT converter', ['pstotext $$i > $$o'],
433         rc_entry = [ r'\converter ps         text2      "%%"    ""' ])
434     #
435     checkProg('a PS to TXT converter', ['ps2ascii $$i $$o'],
436         rc_entry = [ r'\converter ps         text3      "%%"    ""' ])
437     #
438     checkProg('a PS to EPS converter', ['ps2eps $$i'],
439         rc_entry = [ r'\converter ps         eps      "%%"      ""' ])
440     #
441     checkProg('a PDF to PS converter', ['pdf2ps $$i $$o', 'pdftops $$i $$o'],
442         rc_entry = [ r'\converter pdf         ps        "%%"    ""' ])
443     #
444     checkProg('a PDF to EPS converter', ['pdftops -eps -f 1 -l 1 $$i $$o'],
445         rc_entry = [ r'\converter pdf         eps        "%%"   ""' ])
446     #
447     checkProg('a DVI to TXT converter', ['catdvi $$i > $$o'],
448         rc_entry = [ r'\converter dvi        text4      "%%"    ""' ])
449     #
450     checkProg('a DVI to PS converter', ['dvips -o $$o $$i'],
451         rc_entry = [ r'\converter dvi        ps         "%%"    ""' ])
452     #
453     checkProg('a DVI to PDF converter', ['dvipdfmx -o $$o $$i', 'dvipdfm -o $$o $$i'],
454         rc_entry = [ r'\converter dvi        pdf3       "%%"    ""' ])
455     #
456     path, dvipng = checkProg('dvipng', ['dvipng'])
457     if dvipng == "dvipng":
458         addToRC(r'\converter lyxpreview png        "python -tt $$s/scripts/lyxpreview2bitmap.py"        ""')
459     else:
460         addToRC(r'\converter lyxpreview png        ""   ""')
461     #  
462     checkProg('a fax program', ['kdeprintfax $$i', 'ksendfax $$i'],
463         rc_entry = [ r'\converter ps         fax        "%%"    ""'])
464     #
465     checkProg('a FIG -> EPS/PPM converter', ['fig2dev'],
466         rc_entry = [
467             r'''\converter fig        eps        "fig2dev -L eps $$i $$o"       ""
468 \converter fig        ppm        "fig2dev -L ppm $$i $$o"       ""
469 \converter fig        png        "fig2dev -L png $$i $$o"       ""''',
470             ''])
471     #
472     checkProg('a TIFF -> PS converter', ['tiff2ps $$i > $$o'],
473         rc_entry = [ r'\converter tiff       eps        "%%"    ""', ''])
474     #
475     checkProg('a TGIF -> EPS/PPM converter', ['tgif'],
476         rc_entry = [
477             r'''\converter tgif       eps        "tgif -print -color -eps -stdout $$i > $$o"    ""
478 \converter tgif       png        "tgif -print -color -png -o $$d $$i"   ""
479 \converter tgif       pdf        "tgif -print -color -pdf -stdout $$i > $$o"    ""''',
480             ''])
481     #
482     checkProg('a WMF -> EPS converter', ['metafile2eps $$i $$o', 'wmf2eps -o $$o $$i'],
483         rc_entry = [ r'\converter wmf        eps        "%%"    ""'])
484     #
485     checkProg('an EMF -> EPS converter', ['metafile2eps $$i $$o', 'wmf2eps -o $$o $$i'],
486         rc_entry = [ r'\converter emf        eps        "%%"    ""'])
487     #
488     checkProg('an EPS -> PDF converter', ['epstopdf'],
489         rc_entry = [ r'\converter eps        pdf        "epstopdf --outfile=$$o $$i"    ""', ''])
490     #
491     # no agr -> pdf converter, since the pdf library used by gracebat is not
492     # free software and therefore not compiled in in many installations.
493     # Fortunately, this is not a big problem, because we will use epstopdf to
494     # convert from agr to pdf via eps without loss of quality.
495     checkProg('a Grace -> Image converter', ['gracebat'],
496         rc_entry = [
497             r'''\converter agr        eps        "gracebat -hardcopy -printfile $$o -hdevice EPS $$i 2>/dev/null"       ""
498 \converter agr        png        "gracebat -hardcopy -printfile $$o -hdevice PNG $$i 2>/dev/null"       ""
499 \converter agr        jpg        "gracebat -hardcopy -printfile $$o -hdevice JPEG $$i 2>/dev/null"      ""
500 \converter agr        ppm        "gracebat -hardcopy -printfile $$o -hdevice PNM $$i 2>/dev/null"       ""''',
501             ''])
502     #
503     checkProg('a Dot -> PDF converter', ['dot -Tpdf $$i -o $$o'],
504         rc_entry = [ r'\converter dot        pdf        "%%"    ""'])
505     #
506     #
507     path, lilypond = checkProg('a LilyPond -> EPS/PDF/PNG converter', ['lilypond'])
508     if (lilypond != ''):
509         version_string = cmdOutput("lilypond --version")
510         match = re.match('GNU LilyPond (\S+)', version_string)
511         if match:
512             version_number = match.groups()[0]
513             version = version_number.split('.')
514             if int(version[0]) > 2 or (len(version) > 1 and int(version[0]) == 2 and int(version[1]) >= 6):
515                 addToRC(r'''\converter lilypond   eps        "lilypond -b eps --ps $$i" ""
516 \converter lilypond   png        "lilypond -b eps --png $$i"    ""''')
517                 if int(version[0]) > 2 or (len(version) > 1 and int(version[0]) == 2 and int(version[1]) >= 9):
518                     addToRC(r'\converter lilypond   pdf        "lilypond -b eps --pdf $$i"      ""')
519                 print '+  found LilyPond version %s.' % version_number
520             else:
521                 print '+  found LilyPond, but version %s is too old.' % version_number
522         else:
523             print '+  found LilyPond, but could not extract version number.'
524     #
525     checkProg('a Noteedit -> LilyPond converter', ['noteedit --export-lilypond $$i'],
526         rc_entry = [ r'\converter noteedit   lilypond   "%%"    ""', ''])
527     #
528     # FIXME: no rc_entry? comment it out
529     # checkProg('Image converter', ['convert $$i $$o'])
530     #
531     # Entries that do not need checkProg
532     addToRC(r'''\converter lyxpreview ppm        "python -tt $$s/scripts/lyxpreview2bitmap.py"  ""
533 \converter lyxpreview-platex ppm        "python -tt $$s/scripts/lyxpreview-platex2bitmap.py"    ""
534 \converter csv        lyx        "python -tt $$s/scripts/csv2lyx.py $$i $$o"    ""
535 \converter date       dateout    "python -tt $$s/scripts/date.py %d-%m-%Y > $$o"        ""
536 \converter docbook    docbook-xml "cp $$i $$o"  "xml"
537 \converter fen        asciichess "python -tt $$s/scripts/fen2ascii.py $$i $$o"  ""
538 \converter fig        pdftex     "python -tt $$s/scripts/fig2pdftex.py $$i $$o" ""
539 \converter fig        pstex      "python -tt $$s/scripts/fig2pstex.py $$i $$o"  ""
540 \converter lyx        lyx13x     "python -tt $$s/lyx2lyx/lyx2lyx -t 221 $$i > $$o"      ""
541 \converter lyx        lyx14x     "python -tt $$s/lyx2lyx/lyx2lyx -t 245 $$i > $$o"      ""
542 \converter lyx        lyx15x     "python -tt $$s/lyx2lyx/lyx2lyx -t 276 $$i > $$o"      ""
543 \converter lyx        lyx16x     "python -tt $$s/lyx2lyx/lyx2lyx -t 345 $$i > $$o"      ""
544 \converter lyx        clyx       "python -tt $$s/lyx2lyx/lyx2lyx -c big5 -t 245 $$i > $$o"      ""
545 \converter lyx        jlyx       "python -tt $$s/lyx2lyx/lyx2lyx -c euc_jp -t 245 $$i > $$o"    ""
546 \converter lyx        klyx       "python -tt $$s/lyx2lyx/lyx2lyx -c euc_kr -t 245 $$i > $$o"    ""
547 \converter clyx       lyx        "python -tt $$s/lyx2lyx/lyx2lyx -c big5 $$i > $$o"     ""
548 \converter jlyx       lyx        "python -tt $$s/lyx2lyx/lyx2lyx -c euc_jp $$i > $$o"   ""
549 \converter klyx       lyx        "python -tt $$s/lyx2lyx/lyx2lyx -c euc_kr $$i > $$o"   ""
550 ''')
551
552
553 def checkDocBook():
554     ''' Check docbook '''
555     path, DOCBOOK = checkProg('SGML-tools 2.x (DocBook), db2x scripts or xsltproc', ['sgmltools', 'db2dvi', 'xsltproc'],
556         rc_entry = [
557             r'''\converter docbook    dvi        "sgmltools -b dvi $$i" ""
558 \converter docbook    html       "sgmltools -b html $$i"        ""''',
559             r'''\converter docbook    dvi        "db2dvi $$i"   ""
560 \converter docbook    html       "db2html $$i"  ""''',
561             r'''\converter docbook    dvi        ""     ""
562 \converter docbook    html       "" ""''',
563             r'''\converter docbook    dvi        ""     ""
564 \converter docbook    html       ""     ""'''])
565     #
566     if DOCBOOK != '':
567         return ('yes', 'true', '\\def\\hasdocbook{yes}')
568     else:
569         return ('no', 'false', '')
570
571
572 def checkOtherEntries():
573     ''' entries other than Format and Converter '''
574     checkProg('ChkTeX', ['chktex -n1 -n3 -n6 -n9 -n22 -n25 -n30 -n38'],
575         rc_entry = [ r'\chktex_command "%%"' ])
576     checkProg('BibTeX', ['jbibtex', 'bibtex'],
577         rc_entry = [ r'\bibtex_command "%%"' ])
578     checkProg('an index processor', ['texindy', 'makeindex -c -q'],
579         rc_entry = [ r'\index_command "%%"' ])
580     checkProg('a nomenclature processor', ['makeindex'],
581         rc_entry = [ r'\nomencl_command "makeindex -s nomencl.ist"' ])
582     checkProg('a spellchecker', ['ispell'],
583         rc_entry = [ r'\spell_command "%%"' ])
584     ## FIXME: OCTAVE is not used anywhere
585     # path, OCTAVE = checkProg('Octave', ['octave'])
586     ## FIXME: MAPLE is not used anywhere
587     # path, MAPLE = checkProg('Maple', ['maple'])
588     checkProg('a spool command', ['lp', 'lpr'],
589         rc_entry = [
590             r'''\print_spool_printerprefix "-d "
591 \print_spool_command "lp"''',
592             r'''\print_spool_printerprefix "-P",
593 \print_spool_command "lpr"''',
594             ''])
595     # Add the rest of the entries (no checkProg is required)
596     addToRC(r'''\copier    fig        "python -tt $$s/scripts/fig_copy.py $$i $$o"
597 \copier    pstex      "python -tt $$s/scripts/tex_copy.py $$i $$o $$l"
598 \copier    pdftex     "python -tt $$s/scripts/tex_copy.py $$i $$o $$l"
599 \copier    program    "python -tt $$s/scripts/ext_copy.py $$i $$o"
600 ''')
601
602
603 def processLayoutFile(file, bool_docbook):
604     ''' process layout file and get a line of result
605         
606         Declare lines look like this: (article.layout, scrbook.layout, svjog.layout)
607         
608         \DeclareLaTeXClass{article}
609         \DeclareLaTeXClass[scrbook]{book (koma-script)}
610         \DeclareLaTeXClass[svjour,svjog.clo]{article (Springer - svjour/jog)}
611
612         we expect output:
613         
614         "article" "article" "article" "false"
615         "scrbook" "scrbook" "book (koma-script)" "false"
616         "svjog" "svjour" "article (Springer - svjour/jog)" "false"
617     '''
618     classname = file.split(os.sep)[-1].split('.')[0]
619     # return ('LaTeX', '[a,b]', 'a', ',b,c', 'article') for \DeclareLaTeXClass[a,b,c]{article}
620     p = re.compile(r'\Declare(LaTeX|DocBook)Class\s*(\[([^,]*)(,.*)*\])*\s*{(.*)}')
621     for line in open(file).readlines():
622         res = p.search(line)
623         if res != None:
624             (classtype, optAll, opt, opt1, desc) = res.groups()
625             avai = {'LaTeX':'false', 'DocBook':bool_docbook}[classtype]
626             if opt == None:
627                 opt = classname
628             return '"%s" "%s" "%s" "%s"\n' % (classname, opt, desc, avai)
629     print "Layout file " + file + " has no \DeclareXXClass line. "
630     return ""
631
632
633 def checkLatexConfig(check_config, bool_docbook):
634     ''' Explore the LaTeX configuration 
635         Return None (will be passed to sys.exit()) for success.
636     '''
637     print 'checking LaTeX configuration... ',
638     # if --without-latex-config is forced, or if there is no previous 
639     # version of textclass.lst, re-generate a default file.
640     if not os.path.isfile('textclass.lst') or not check_config:
641         # remove the files only if we want to regenerate
642         removeFiles(['textclass.lst', 'packages.lst'])
643         #
644         # Then, generate a default textclass.lst. In case configure.py
645         # fails, we still have something to start lyx.
646         print ' default values'
647         print '+checking list of textclasses... '
648         tx = open('textclass.lst', 'w')
649         tx.write('''
650 # This file declares layouts and their associated definition files
651 # (include dir. relative to the place where this file is).
652 # It contains only default values, since chkconfig.ltx could not be run
653 # for some reason. Run ./configure.py if you need to update it after a
654 # configuration change.
655 ''')
656         # build the list of available layout files and convert it to commands
657         # for chkconfig.ltx
658         foundClasses = []
659         for file in glob.glob( os.path.join('layouts', '*.layout') ) + \
660             glob.glob( os.path.join(srcdir, 'layouts', '*.layout' ) ) :
661             # valid file?
662             if not os.path.isfile(file): 
663                 continue
664             # get stuff between /xxxx.layout .
665             classname = file.split(os.sep)[-1].split('.')[0]
666             #  tr ' -' '__'`
667             cleanclass = classname.replace(' ', '_')
668             cleanclass = cleanclass.replace('-', '_')
669             # make sure the same class is not considered twice
670             if foundClasses.count(cleanclass) == 0: # not found before
671                 foundClasses.append(cleanclass)
672                 retval = processLayoutFile(file, bool_docbook)
673                 if retval != "":
674                     tx.write(retval)
675         tx.close()
676         print '\tdone'
677     if not check_config:
678         return None
679     # the following will generate textclass.lst.tmp, and packages.lst.tmp
680     else:
681         print '\tauto'
682         removeFiles(['wrap_chkconfig.ltx', 'chkconfig.vars', \
683             'chkconfig.classes', 'chklayouts.tex'])
684         rmcopy = False
685         if not os.path.isfile( 'chkconfig.ltx' ):
686             shutil.copyfile( os.path.join(srcdir, 'chkconfig.ltx'), 'chkconfig.ltx' )
687             rmcopy = True
688         writeToFile('wrap_chkconfig.ltx', '%s\n\\input{chkconfig.ltx}\n' % docbook_cmd)
689         # Construct the list of classes to test for.
690         # build the list of available layout files and convert it to commands
691         # for chkconfig.ltx
692         p1 = re.compile(r'\Declare(LaTeX|DocBook)Class')
693         testclasses = list()
694         for file in glob.glob( os.path.join('layouts', '*.layout') ) + \
695             glob.glob( os.path.join(srcdir, 'layouts', '*.layout' ) ) :
696             if not os.path.isfile(file):
697                 continue
698             classname = file.split(os.sep)[-1].split('.')[0]
699             for line in open(file).readlines():
700                 if p1.search(line) == None:
701                     continue
702                 if line[0] != '#':
703                     print "Wrong input layout file with line '" + line
704                     sys.exit(3)
705                 testclasses.append("\\TestDocClass{%s}{%s}" % (classname, line[1:].strip()))
706                 break
707         testclasses.sort()
708         cl = open('chklayouts.tex', 'w')
709         for line in testclasses:
710             cl.write(line + '\n')
711         cl.close()
712         #
713         # we have chklayouts.tex, then process it
714         fout = os.popen(LATEX + ' wrap_chkconfig.ltx')
715         while True:
716             line = fout.readline()
717             if not line:
718                 break;
719             if re.match('^\+', line):
720                 print line,
721         # if the command succeeds, None will be returned
722         ret = fout.close()
723         #
724         # currently, values in chhkconfig are only used to set
725         # \font_encoding
726         values = {}
727         for line in open('chkconfig.vars').readlines():
728             key, val = re.sub('-', '_', line).split('=')
729             val = val.strip()
730             values[key] = val.strip("'")
731         # chk_fontenc may not exist 
732         try:
733             addToRC(r'\font_encoding "%s"' % values["chk_fontenc"])
734         except:
735             pass
736         if rmcopy:   # remove the copied file
737             removeFiles( [ 'chkconfig.ltx' ] )
738         # if configure successed, move textclass.lst.tmp to textclass.lst
739         # and packages.lst.tmp to packages.lst
740         if os.path.isfile('textclass.lst.tmp') and len(open('textclass.lst.tmp').read()) > 0 \
741             and os.path.isfile('packages.lst.tmp') and len(open('packages.lst.tmp').read()) > 0:
742             shutil.move('textclass.lst.tmp', 'textclass.lst')
743             shutil.move('packages.lst.tmp', 'packages.lst')
744         return ret
745
746
747 def checkModulesConfig():
748   removeFiles(['lyxmodules.lst'])
749
750   print '+checking list of modules... '
751   tx = open('lyxmodules.lst', 'w')
752   tx.write('''## This file declares modules and their associated definition files.
753 ## It has been automatically generated by configure
754 ## Use "Options/Reconfigure" if you need to update it after a
755 ## configuration change. 
756 ''')
757   # build the list of available modules
758   foundClasses = []
759   for file in glob.glob( os.path.join('layouts', '*.module') ) + \
760       glob.glob( os.path.join(srcdir, 'layouts', '*.module' ) ) :
761       # valid file?
762       print file
763       if not os.path.isfile(file): 
764           continue
765       retval = processModuleFile(file, bool_docbook)
766       if retval != "":
767           tx.write(retval)
768   tx.close()
769   print '\tdone'
770
771
772 def processModuleFile(file, bool_docbook):
773     ''' process module file and get a line of result
774
775         The top of a module file should look like this:
776           #\DeclareLyXModule[LaTeX Packages]{ModuleName}
777           #BeginDescription
778           #...body of description...
779           #EndDescription
780           #Requires: [list of required modules]
781           #Excludes: [list of excluded modules]
782         The last two lines are optional
783         We expect output:
784           "ModuleName" "filename" "Description" "Packages" "Requires" "Excludes"
785     '''
786     p = re.compile(r'\DeclareLyXModule\s*(?:\[([^]]*?)\])?{(.*)}')
787     r = re.compile(r'#+\s*Requires: (.*)')
788     x = re.compile(r'#+\s*Excludes: (.*)')
789     b = re.compile(r'#+\s*DescriptionBegin\s*$')
790     e = re.compile(r'#+\s*DescriptionEnd\s*$')
791
792     modname = desc = pkgs = req = excl = ""
793     readingDescription = False
794     descLines = []
795     filename = file.split(os.sep)[-1]
796     filename = filename[:-7]
797
798     for line in open(file).readlines():
799       if readingDescription:
800         res = e.search(line)
801         if res != None:
802           readingDescription = False
803           desc = " ".join(descLines)
804           continue
805         descLines.append(line[1:].strip())
806         continue
807       res = b.search(line)
808       if res != None:
809         readingDescription = True
810         continue
811       res = p.search(line)
812       if res != None:
813           (pkgs, modname) = res.groups()
814           if pkgs == None:
815             pkgs = ""
816           else:
817             tmp = [s.strip() for s in pkgs.split(",")]
818             pkgs = ",".join(tmp)
819           continue
820       res = r.search(line)
821       if res != None:
822         req = res.group(1)
823         tmp = [s.strip() for s in req.split("|")]
824         req = "|".join(tmp)
825         continue
826       res = x.search(line)
827       if res != None:
828         excl = res.group(1)
829         tmp = [s.strip() for s in excl.split("|")]
830         excl = "|".join(tmp)
831         continue
832     if modname != "":
833         return '"%s" "%s" "%s" "%s" "%s" "%s"\n' % (modname, filename, desc, pkgs, req, excl)
834     print "Module file without \DeclareLyXModule line. "
835     return ""
836
837
838 def checkTeXAllowSpaces():
839     ''' Let's check whether spaces are allowed in TeX file names '''
840     tex_allows_spaces = 'false'
841     if lyx_check_config:
842         print "Checking whether TeX allows spaces in file names... ",
843         writeToFile('a b.tex', r'\message{working^^J}' )
844         if os.name == 'nt':
845             latex_out = cmdOutput(LATEX + r""" "\nonstopmode\input{\"a b\"}" """)
846         else:
847             latex_out = cmdOutput(LATEX + r""" '\nonstopmode\input{"a b"}' """)
848         if 'working' in latex_out:
849             print 'yes'
850             tex_allows_spaces = 'true'
851         else:
852             print 'no'
853             tex_allows_spaces = 'false'
854         addToRC(r'\tex_allows_spaces ' + tex_allows_spaces)
855         removeFiles( [ 'a b.tex', 'a b.log', 'texput.log' ])
856
857
858 def removeTempFiles():
859     # Final clean-up
860     if not lyx_keep_temps:
861         removeFiles(['chkconfig.vars',  \
862             'wrap_chkconfig.ltx', 'wrap_chkconfig.log', \
863             'chklayouts.tex', 'missfont.log', 
864             'chklatex.ltx', 'chklatex.log'])
865
866
867 if __name__ == '__main__':
868     lyx_check_config = True
869     outfile = 'lyxrc.defaults'
870     rc_entries = ''
871     lyx_keep_temps = False
872     version_suffix = ''
873     logfile = 'configure.log'
874     ## Parse the command line
875     for op in sys.argv[1:]:   # default shell/for list is $*, the options
876         if op in [ '-help', '--help', '-h' ]:
877             print '''Usage: configure [options]
878 Options:
879     --help                   show this help lines
880     --keep-temps             keep temporary files (for debug. purposes)
881     --without-latex-config   do not run LaTeX to determine configuration
882     --with-version-suffix=suffix suffix of binary installed files
883 '''
884             sys.exit(0)
885         elif op == '--without-latex-config':
886             lyx_check_config = False
887         elif op == '--keep-temps':
888             lyx_keep_temps = True
889         elif op[0:22] == '--with-version-suffix=':  # never mind if op is not long enough
890             version_suffix = op[22:]
891         else:
892             print "Unknown option", op
893             sys.exit(1)
894     #
895     # set up log file for stdout and stderr
896     log = open(logfile, 'w')
897     sys.stdout = Tee(sys.stdout, log)
898     sys.stderr = Tee(sys.stderr, log)
899     # check if we run from the right directory
900     srcdir = os.path.dirname(sys.argv[0])
901     if srcdir == '':
902         srcdir = '.'
903     if not os.path.isfile( os.path.join(srcdir, 'chkconfig.ltx') ):
904         print "configure: error: cannot find chkconfig.ltx script"
905         sys.exit(1)
906     setEnviron()
907     createDirectories()
908     windows_style_tex_paths = checkTeXPaths()
909     dtl_tools = checkDTLtools()
910     ## Write the first part of outfile
911     writeToFile(outfile, '''# This file has been automatically generated by LyX' lib/configure.py
912 # script. It contains default settings that have been determined by
913 # examining your system. PLEASE DO NOT MODIFY ANYTHING HERE! If you
914 # want to customize LyX, use LyX' Preferences dialog or modify directly 
915 # the "preferences" file instead. Any setting in that file will
916 # override the values given here.
917 ''')
918     # check latex
919     LATEX = checkLatex(dtl_tools)
920     checkFormatEntries(dtl_tools)
921     checkConverterEntries()
922     (chk_docbook, bool_docbook, docbook_cmd) = checkDocBook()
923     checkTeXAllowSpaces()
924     if windows_style_tex_paths != '':
925         addToRC(r'\tex_expects_windows_paths %s' % windows_style_tex_paths)
926     checkOtherEntries()
927     # --without-latex-config can disable lyx_check_config
928     ret = checkLatexConfig(lyx_check_config and LATEX != '', bool_docbook)
929     checkModulesConfig() #lyx_check_config and LATEX != '')
930     removeTempFiles()
931     # The return error code can be 256. Because most systems expect an error code
932     # in the range 0-127, 256 can be interpretted as 'success'. Because we expect
933     # a None for success, 'ret is not None' is used to exit.
934     sys.exit(ret is not None)