]> git.donarmstrong.com Git - lilypond.git/blob - scripts/ly2dvi.py
release: 1.3.25
[lilypond.git] / scripts / ly2dvi.py
1 #!@PYTHON@
2
3
4 # TODO: Rewrite this.  The control structure is too hairy.
5 #
6
7 # TODO:
8 # Should use files in /tmp/ only.  This potentially messes with
9 # usergenerated files in the CWD
10
11
12 """
13 =======================================================================
14 LilyPond to dvi converter
15
16 Features include Title information, paper size specification, and image
17 orientation.  
18
19 Usage: ly2dvi.py [OPTION]... [FILE]...
20 Input: LilyPond source or LilyPond generated TeX files
21 Output: DVI file
22 =======================================================================
23 """
24
25 name = 'ly2dvi'
26 version = '@TOPLEVEL_VERSION@'
27 errorlog = ''
28
29 import sys
30 import os
31 import getopt
32 import re
33 import string
34 import time
35 import glob
36 import tempfile
37
38 os.environ['LANG'] = ''         # Can't grep localized msgs
39
40
41
42 class Input:
43     """
44     This class handles all ly2dvi.py input file methods
45     
46     Public methods:
47     
48     __init__()  Constructor
49     open(file)  Open a .ly file or .tex file using lilyinclude path
50     close()     Close current file
51     type()      Determine file type .ly (input) or .tex (output)
52     setVars()   Set title definitions found in .tex (output) file
53     """
54
55     #
56     # Constructors
57     #
58
59     def __init__(this):
60        this.__fd = None 
61
62     #
63     # open
64     #
65     def open(this,file):
66         """
67         open file and set private class variable __fd.  The search
68         sequence is: current directory followed by the directories
69         found in include property list.  Each directory is searched
70         for file, file.ly, file.sly and file.fly.
71         
72         input:  file   filename
73         output: void
74         error:  ExitNotFound Exception
75         """
76
77         for i in [''] + Props.get('include')[0:]:
78             ifile = os.path.join(i,file)
79             for j in ['','.ly','.fly', '.sly']:
80                 jfile = ifile+j
81                 try:
82                     this.__fd = open( jfile, 'r' )
83                     return
84                 except:
85                     pass
86         sys.exit('ExitNotFound', file)
87
88
89     #
90     # close
91     #
92     def close(this):
93         """
94         close file object __fd
95         
96         input:  void
97         output: void
98         error:  None
99         """
100         this.__fd.close()
101
102
103     #
104     # type
105     #
106     def type(this):
107         """
108         Determine input file type.  LilyPond source is 'input' type
109         and LilyPond generated TeX file is 'output' type
110
111         input:  void
112         output: 'input' | 'output'
113         error:  None
114         """
115
116         firstline = this.__fd.readline()
117         this.__fd.seek(0)
118         if  re.match('%created by: GNU LilyPond [0-9]+[.0-9]+',firstline ):
119             return 'output'
120         else:
121             return 'source'
122
123
124     #
125     # setVars
126     #
127     def setVars(this):  
128         """
129         Search for properties in the current input file and set the
130         appropriate values.  The supported properties names are in
131         local variable varTable along with the property list
132         titledefs.
133
134         input:  void
135         output: None
136         error:  None
137         """
138
139         varTable = [
140             #   regexp              set method
141             #   ------              ----------
142             ( 'language',        Props.setLanguage ),
143             ( 'latexheaders',    Props.setHeader ),
144             ( 'orientation',     Props.setOrientation ),
145             ( 'paperpapersize',  Props.setPaperZize ),
146             ( 'papertextheight', Props.setTextHeight ),
147             ( 'paperlinewidth',  Props.setLineWidth ),
148             ( 'filename',        Props.setFilename ),
149             ]
150
151         titles={}
152         for line in this.__fd.readlines():
153             m=re.match('\\\\def\\\\mudela([\w]+){(.*)}',line)
154             if m:
155                 for var in varTable:
156                     if m.group(1) == var[0]:
157                         var[1](m.group(2),'file')
158                         break
159                 for var in Props.get('titledefs'):
160                     if m.group(1) == var:
161                         titles[var]=m.group(2)
162                         break
163         Props.setTitles(titles,'file')
164         this.__fd.seek(0)
165
166 \f
167
168 class TeXOutput:
169     """
170     This class handles all ly2dvi.py output file methods
171
172     private methods:
173      __mudelaDefs(opt)  Send title info to output file
174
175     Public methods:
176     __init__()  Constructor
177     write(str)  Write a string to output file 
178     start(file) Start the latex file
179     next()      Process next output file
180     end()       Finish latex file and run latex 
181     """
182
183     #
184     # constructor
185     #
186     def __init__(this):
187        this.__fd = None 
188        this.__base = ''
189        this.__outfile = ''
190
191     #
192     # __medelaDefs
193     #
194     def __mudelaDefs(this,opt):
195         """
196         Write titles to output
197
198         input:  opt   Supports header and subheader output
199         output: None
200         error:  None
201         """
202
203         titles = Props.get('titles')
204         for key in titles.keys():
205             this.write('%s\\mudela%s{%s}%%\n' % (opt,key,titles[key]))
206
207     #
208     # write
209     #
210     def write(this,str):
211         """
212         Write str to current output file
213
214         input:  str  String to write
215         output: None
216         error:  None
217         """
218         
219         this.__fd.write(str)
220
221     #
222     # start
223     #
224     def start(this,file):
225         """
226         Start LaTeX file.  Calculates the horizontal and vertical
227         margin using pagewidth, pageheight, linewidth, and textheight.
228         Creates temporary output filename and opens it for write.
229         Sends the LaTeX header information to output.  Lastly sends
230         the title information to output.
231
232         input:  file  output file name 
233         output: None
234         error:  None
235         """
236
237         now=time.asctime(time.localtime(time.time()))
238         linewidth = Props.get('linewidth')
239         textheight = Props.get('textheight')
240
241         if Props.get('orientation') == 'landscape':
242             pagewidth = Props.get('pageheight')
243             pageheight = Props.get('pagewidth')
244         else:
245             pageheight = Props.get('pageheight')
246             pagewidth = Props.get('pagewidth')
247                                  
248         horizontalMarginArg =  ( (pagewidth - linewidth)/2 )   
249         verticalMarginArg =  ( (pageheight - textheight)/2  )
250
251         top= r"""
252 %% Creator: %s
253 %% Automatically generated from  %s, %s
254
255 \documentclass[%s]{article}
256
257 %s 
258 \usepackage{geometry}
259 \usepackage[latin1]{inputenc} 
260 %%\usepackage[T1]{fontenc} 
261 %s 
262 %%\addtolength{\oddsidemargin}{-1cm} 
263 %%\addtolength{\topmargin}{-1cm} 
264 %%\setlength{\textwidth}{%s} 
265 %%\setlength{\textheight}{%s} 
266 \geometry{width=%spt, left=%spt, height=%spt, top=%spt, nohead} 
267 \input lilyponddefs 
268 \input titledefs 
269 %s
270 \makeatletter
271 \renewcommand{\@oddhead}{\hfil{\small\theheader\quad\textbf{\thepage}}}%%
272 %% UGR.
273 %%\renewcommand{\@evenhead}{eve!{\small\mudelainstrument{,}\quad\textbf{\thepage}}\hfil}%%
274 \renewcommand{\@oddfoot}{{\thefooter}\hfil}%%
275 \begin{document}
276 """ % ( program_id(), Props.get('filename'), now, Props.get('papersize'),
277         Props.get('language'), Props.get('pagenumber'), linewidth, textheight,
278         linewidth, horizontalMarginArg, textheight, verticalMarginArg,
279         Props.get('header') )
280         
281         base, ext = os.path.splitext(file)
282         this.__base = base
283         tempfile.template= base + '_ly'
284         this.__outfile = tempfile.mktemp(ext)
285         base, ext = os.path.splitext(this.__outfile)
286         this.__tmpbase = base
287         try:
288             this.__fd = open(this.__outfile,"w")
289         except:
290             sys.exit('ExitNoWrite', this.__outfile)
291         this.write(top)
292         this.__mudelaDefs('')
293         this.write("""\
294 \\makelilytitle
295 """) 
296
297     #
298     # next
299     #
300     def next(this):
301         """
302         Write LaTeX subheader information to support more than one
303         score in a document.  Lastly send current title information to
304         output.
305
306         input:  None
307         output: None
308         error:  None
309         """
310
311         this.write("""\
312 \\def\\theopus{}%
313 \\def\\thepiece{}%
314 \\def\\mudelaopus{}%
315 \\def\\mudelapiece{}%
316 """)
317         this.__mudelaDefs("\\def")
318         this.write("""\
319 \\def\\theopus{\\mudelaopus}% ugh
320 \\def\\thepiece{\\mudelapiece}%
321 \\makelilypiecetitle
322 """)
323
324
325     #
326     # end
327     #
328     def end(this):
329         """
330         Close output file and run latex on it.
331
332         input:  None
333         output: None
334         error:  ExitBadLatex Exception
335         """
336
337         outfile=this.__base + '.dvi'
338         if Props.get('output') != '':
339             outfile = os.path.join(Props.get('output'), outfile )
340             
341         this.write(r"""
342 %% \vfill\hfill{\mudelatagline}
343 \makeatletter
344 \renewcommand{\@oddfoot}{\thefooter\hfill{\mudelatagline}}%
345 \makeatother
346 \end{document}
347 """)
348         this.__fd.close()
349         if os.path.isfile(outfile):
350             os.remove(outfile)
351         if ( os.name == 'posix' ):
352             stat = os.system('latex \'\\nonstopmode \\input %s\'' %
353                              (this.__outfile))
354         else: # Windows shells don't eat the single quotes
355             stat = os.system('latex \\nonstopmode \\input %s' %
356                              (this.__outfile))
357         if stat:
358             sys.exit('ExitBadLatex')
359         if not os.path.isfile(outfile):
360                 os.rename(this.__tmpbase + '.dvi', outfile)
361                 
362         sys.stderr.write('\n' + program_id() + ': dvi file name is %s\n\n'
363                          % (outfile))
364
365         if Props.get('postscript'):
366             psoutfile=this.__base + '.ps'
367             if Props.get('output') != '':
368                 psoutfile = os.path.join(Props.get('output'), psoutfile )
369             stat = os.system('dvips -o %s %s' % (psoutfile,outfile))
370             if stat:
371                 sys.exit('ExitBadPostscript')
372             
373
374 \f
375
376
377 # ARG! THIS CODE IS BLOATED:
378 # FIXME: Junk all set/get methods.
379
380 class Properties:
381     """
382     This class handles all ly2dvi.py property manipulation
383
384     Public methods:
385     
386     __init__()  Constructor
387     set<property> methods
388     """
389
390     def __init__(this):
391
392         #
393         # Following is the order of priority for property assignment.  The
394         # list is organized from lowest to highest priority.  Each
395         # assignment is overridden by the next requester in the list.
396         #
397         # Requester     Description
398         # ---------     -----------
399         # init          Initial default values
400         # file          The values found in the lilypond generated TeX files
401         # environment   Envrionment variables LILYINCLUDE, LILYPONDPREFIX
402         # rcfile        $LILYPONDPREFIX/.lilyrc
403         # rcfile        $HOME/.lilyrc
404         # rcfile        ./.lilyrc
405         # commandline   command line arguments
406         # 
407         this.__overrideTable = {
408             'init'        : 0,
409             'file'        : 1,
410             'environment' : 2,
411             'rcfile'      : 3,
412             'commandline' : 4,
413             'program'     : 5
414             }
415
416         this.__roverrideTable = {} # reverse lookup used for debug
417         for i in this.__overrideTable.items():
418             this.__roverrideTable[i[1]]=i[0]
419         
420         this.__data = {
421             'pagewidth'    :  [597, this.__overrideTable['init']],
422             'pageheight'   :  [845, this.__overrideTable['init']],
423             'papersize'    :  ['a4paper', this.__overrideTable['init']],
424             'textheight'   :  [0, this.__overrideTable['init']],
425             'linewidth'    :  [500, this.__overrideTable['init']],
426             'orientation'  :  ['portrait', this.__overrideTable['init']],
427             'language'     :  ['%', this.__overrideTable['init']],
428             'include'      :  [[], this.__overrideTable['init']],
429             'debug'        :  [0, this.__overrideTable['init']],
430             'keeplilypond' :  [0, this.__overrideTable['init']],
431             'keeply2dvi'   :  [0, this.__overrideTable['init']],
432             'pagenumber'   :  ['%', this.__overrideTable['init']],
433             'separate'     :  [0, this.__overrideTable['init']],
434             'output'       :  ['', this.__overrideTable['init']],
435             'header'       :  ['%', this.__overrideTable['init']],
436             'dependencies' :  [0, this.__overrideTable['init']],
437             'root'         :  ['', this.__overrideTable['init']],
438             'tmp'          :  ['d:\tmp', this.__overrideTable['init']],
439             'filename'     :  ['', this.__overrideTable['init']],
440             'titledefs'    :  [[], this.__overrideTable['init']],
441             'titles'       :  [{}, this.__overrideTable['init']],
442             'lilyOutputFiles' :  [[], this.__overrideTable['init']],
443             'postscript'   :  [0, this.__overrideTable['init']],
444             }
445
446         #
447         # Try to set root and HOME first before calling rcfile
448         #
449         if os.environ.has_key('LILYPONDPREFIX'):
450             this.setRoot(os.environ['LILYPONDPREFIX'], 'environment')
451         else:
452             p=os.path.split(sys.argv[0])
453             p=os.path.split(p[0])
454             # bit silly. for ly2dvi, overrules compiled-in datadir...
455             # how to do this better (without running lily, of course?
456             this.setRoot(os.path.join(p[0],'share','lilypond'), 'init')
457
458         if not os.environ.has_key('HOME'):
459             if os.environ.has_key('HOMEDRIVE') and \
460                  os.environ.has_key('HOMEPATH'):
461                 os.environ['HOME'] = os.environ['HOMEDRIVE'] + \
462                                      os.environ['HOMEPATH']
463             else:
464                 os.environ['HOME'] = os.curdir
465
466         this.rcfile() # Read initialization file(s)
467
468         if os.environ.has_key('LILYINCLUDE'):
469             tmp=this.get('include')
470             for s in string.split(os.environ['LILYINCLUDE'],os.pathsep):
471                 tmp.append(s)
472             this.__set('include', tmp, 'environment')    
473
474
475         t= os.pathsep
476         if os.environ.has_key ('TEXINPUTS'):
477                 t = os.environ['TEXINPUTS'] + os.pathsep
478         os.environ['TEXINPUTS'] = t + \
479         os.path.join(this.get('root'), 'tex' ) + \
480         os.pathsep + os.path.join(this.get('root'), 'ps' )
481
482         t=''
483         if os.environ.has_key ('MFINPUTS'):
484                t = os.environ['MFINPUTS'] 
485         os.environ['MFINPUTS'] = t + os.pathsep + \
486                                  os.path.join(this.get('root'), 'mf')
487
488         if os.environ.has_key('TMP'):
489             this.__set('tmp',os.environ['TMP'],'environment')
490
491
492     def read_titledefs (this):
493         fd=this.get_texfile_path ('titledefs.tex')
494         mudefs=[]    
495
496         for line in fd.readlines():
497             m=re.match('\\\\newcommand\*{\\\\mudela([\w]+)}',line)
498             if m:
499                 mudefs.append(m.group(1))
500         fd.close
501         this.__set('titledefs', mudefs, 'init')
502
503     #
504     # __set
505     #
506     def __set(this,var,value,requester):
507         """
508         All of the set methods call this to set a property.  If the value
509         was last set by a requestor of lesser priority the new value is
510         assigned, else the old value has priority and is unchanged.
511         """
512
513         if this.__overrideTable[requester] < this.__data[var][1]:
514             return 0
515         else:
516             this.__data[var] = [value, this.__overrideTable[requester]]
517
518     #
519     # get
520     #
521     def get(this,var):
522         """
523         All of the get methods call this to get a property value.  List
524         variable types are return by value to facilitate an append operation.
525         """
526
527         if var == 'include' or var == 'lilyOutputFiles':
528             return this.__data[var][0][0:]  # return a copy not a ref
529         else:
530             return this.__data[var][0]
531
532     #
533     # get_texfile_path
534     #
535     def get_texfile_path (this, var):
536         """
537         locate and open titledefs.tex file
538         """
539
540         if os.name == 'nt':
541             path = os.path.join(this.get('root'), 'tex', var)
542         else:
543             path =''
544             cmd =('kpsewhich tex %s %s' % (var,errorlog))
545             pipe = os.popen (cmd, 'r')
546             path = pipe.readline ()[:-1] # chop off \n
547             return_status =  pipe.close()
548             if return_status and not path:
549                 path = os.path.join(this.get('root'), 'tex', var)
550         fd = open(path, 'r')
551         return fd
552
553
554     #
555     # Read rc file
556     #
557     def rcfile(this):
558         """
559         Read initialization file(s)
560         """
561         varTable = [
562             #   name              set method
563             #   ----              ----------
564             ( 'DEBUG',          this.setDebug ),
565             ( 'DEPENDENCIES',   this.setDependencies ),
566             ( 'KEEPLILYPOND',   this.setKeeplilypond ),
567             ( 'KEEPLY2DVI',     this.setKeeply2dvi ),
568             ( 'LANGUAGE',       this.setLanguage ),
569             ( 'LATEXHF',        this.setHeader ),
570             ( 'LILYINCLUDE',    this.setInclude ),
571             ( 'LILYPONDPREFIX', this.setRoot ),
572             ( 'NONUMBER',       this.setNonumber ),
573             ( 'ORIENTATION',    this.setOrientation ),
574             ( 'OUTPUTDIR',      this.setOutput ),
575             ( 'PAPERSIZE',      this.setPaperZize ),
576             ( 'PHEIGHT',        this.setTextHeight ),
577             ( 'POSTSCRIPT',     this.setPostscript ),
578             ( 'PWIDTH',         this.setLineWidth ),
579             ( 'SEPARATE',       this.setSeparate ),
580             ( 'TMP',            this.setTmp ),
581             ]
582
583         if ( os.name == 'posix' ):
584             dotFilename='.lilyrc'
585         else: # Windows apps like edit choke on .lilyrc
586             dotFilename='_lilyrc'
587
588         for d in [os.path.join(this.get('root'),'ly'), \
589                   os.environ['HOME'], os.curdir ]:
590             file=os.path.join(d,dotFilename)
591             try:
592                 fd = open( file, 'r' )
593             except:
594                 continue
595             
596             for line in fd.readlines():
597                 if re.match('#.*',line):
598                     continue
599                 m=re.search('([\w]+)=(.*)',line)
600                 if m:
601                     for var in varTable:
602                         if m.group(1) == var[0]:
603                             var[1](m.group(2),'rcfile')
604                             break
605             fd.close
606
607     #
608     # setPaperZize
609     #
610     def setPaperZize(this,size,requester):
611         """
612         Set paper size properties
613         """
614
615         paperTable = [
616             # regex          width    height      name
617             # -----          -----    ------      ----
618             ( 'a0.*',        2389,    3381,    'a0paper' ),
619             ( 'a1$|a1p.*',   1690,    2389,    'a1paper' ),
620             ( 'a2.*',        1194,    1690,    'a2paper' ),
621             ( 'a3.*',        845,     1194,    'a3paper' ),
622             ( 'a4.*',        597,     845,     'a4paper' ),
623             ( 'a5.*',        423,     597,     'a5paper' ),
624             ( 'a6.*',        298,     423,     'a6paper' ),
625             ( 'a7.*',        211,     298,     'a7paper' ),
626             ( 'a8.*',        305,     211,     'a8paper' ),
627             ( 'a9.*',        105,     305,     'a9paper' ),
628             ( 'a10.*',       74,      105,     'a10paper' ),
629             ( 'b0.*',        2847,    4023,    'b0paper' ),
630             ( 'b1.*',        2012,    2847,    'b1paper' ),
631             ( 'b2.*',        1423,    2012,    'b2paper' ),
632             ( 'b3.*',        1006,    1423,    'b3paper' ),
633             ( 'b4.*',        712,     1006,    'b4paper' ),
634             ( 'b5.*',        503,     712,     'b5paper' ),
635             ( 'archA$',      650,     867,     'archApaper' ),
636             ( 'archB$',      867,     1301,    'archBpaper' ),
637             ( 'archC$',      1301,    1734,    'archCpaper' ),
638             ( 'archD$',      1734,    2602,    'archDpaper' ),
639             ( 'archE$',      2602,    3469,    'archEpaper' ),
640             ( 'flsa$|flse$', 614,     940,     'flsapaper' ),
641             ( 'halfletter$', 397,     614,     'halfletterpaper' ),
642             ( 'ledger$',     1229,    795,     'ledgerpaper' ),
643             ( 'legal$',      614,     1012,    'legalpaper' ),
644             ( 'letter$',     614,     795,     'letterpaper' ),
645             ( 'note$',       542,     723,     'notepaper' )
646             ]
647
648         found=0
649         for paper in paperTable:
650             if re.match(paper[0],size):
651                 found=1
652                 this.__set('pagewidth',paper[1],requester)
653                 this.__set('pageheight',paper[2],requester)
654                 this.__set('papersize',paper[3],requester)
655                 break
656
657         if not found:
658             sys.exit('ExitBadPaper',size)
659
660     #
661     # setTextHeight
662     #
663     def setTextHeight(this,size,requester):
664         """
665         Set textheight property
666         """
667
668         m=re.match('([0-9][.0-9]*)(cm|mm|pt|$)',size)
669         if m:
670             if m.group(2) == 'cm':
671                 this.__set('textheight',\
672                            float(m.group(1)) * 72.27/2.54, requester )
673             elif m.group(2) == 'mm':
674                 this.__set('textheight',\
675                            float(m.group(1)) * 72.27/25.4, requester )
676             elif m.group(2) == 'pt':
677                 this.__set('textheight', float(m.group(1)), requester )
678             elif m.group(2) == '':
679                 this.__set('textheight', float(m.group(1)), requester )
680             else:
681                 sys.exit('ExitBadHeight', m.group(2))
682         else:           
683             sys.exit('ExitBadHeight', size)
684
685     #
686     # setLineWidth
687     #
688     def setLineWidth(this,size,requester):
689         """
690         Set linewidth propery
691         """
692
693         m=re.match('([0-9][.0-9]*)(cm|mm|pt|$)',size)
694         if m:
695             if m.group(2) == 'cm':
696                 this.__set('linewidth', \
697                 float(m.group(1)) * 72.27/2.54, requester )
698             elif m.group(2) == 'mm':
699                 this.__set('linewidth', \
700                 float(m.group(1)) * 72.27/25.4, requester )
701             elif m.group(2) == 'pt':
702                 this.__set('linewidth', float(m.group(1)), requester )
703             elif m.group(2) == '':
704                 this.__set('linewidth', float(m.group(1)), requester )
705             else:
706                 sys.exit('ExitBadWidth', m.group(2))
707         else:           
708             sys.stderr.write ('ly2dvi: warning: ignoring linewidth: ' + size + '\n')
709
710     #
711     # setOrientation
712     #
713     def setOrientation(this,orient,requester):
714         """
715         Set orientation property
716         """
717
718         if orient == 'landscape' or orient == 'portrait':
719             this.__set('orientation', orient, requester )
720         else:
721             sys.exit('ExitBadOrient', orient)
722
723     #
724     # setLanguage
725     #
726     def setLanguage(this,lang,requester):
727         """
728         Set language property
729         """
730
731         this.__set('language', '\\usepackage[%s]{babel}' % (lang), requester )
732
733     #
734     # setInclude
735     #
736     def setInclude(this,inc, requester):
737         """
738         Append an include path
739         """
740
741         tmp = this.get('include')
742         tmp.append(inc)
743         this.__set('include', tmp, requester )
744
745     #
746     # setDebug
747     #
748     def setDebug(this,value,requester):
749         """
750         Set or Clear debug flag
751         """
752
753         if int(value) == 1:
754             this.__set('debug',1,requester)
755         else:
756             this.__set('debug',0,requester)
757
758     #
759     # setKeeplilypond
760     #
761     def setKeeplilypond(this, value, requester):        
762         """
763         Set or Clear keeplilypond flag
764         """
765
766         if int(value) == 1:
767             this.__set('keeplilypond',1,requester)
768         else:
769             this.__set('keeplilypond',0,requester)
770
771     #
772     # setKeeply2dvi
773     #
774     def setKeeply2dvi(this, value, requester):  
775         """
776         Set or Clear keeply2dvi flag
777         """
778
779         if int(value) == 1:
780             this.__set('keeply2dvi',1,requester)
781         else:
782             this.__set('keeply2dvi',0,requester)
783
784     #
785     # setNonumber 
786     #
787     def setNonumber(this, value, requester):    
788         """
789         Set nonumber flag
790         """
791
792         if int(value) == 1:
793             this.__set('pagenumber','\\pagestyle{empty}',requester)
794         else:
795             this.__set('pagenumber','%',requester)
796
797     #
798     # setSeparate
799     #
800     def setSeparate(this, value, requester):    
801         """
802         Set or Clear separate flag
803         """
804
805         if int(value) == 1:
806             this.__set('separate',1,requester)
807         else:
808             this.__set('separate',0,requester)
809
810     #
811     # Set output directory name
812     #
813     def setOutput(this,out,requester):
814         this.__set('output',out,requester)
815
816     #
817     # Set latex header name
818     #
819     def setHeader(this,head, requester):
820         this.__set('header',head,requester)
821
822     #
823     # Set or Clear Dependencies flag to generate makefile dependencies
824     #
825     def setDependencies(this, value, requester):        
826         """
827         Set or Clear dependencies flag
828         """
829
830         if int(value) == 1:
831             this.__set('dependencies',1,requester)
832         else:
833             this.__set('dependencies',0,requester)
834
835     #
836     # Set tmp directory
837     #
838     def setTmp(this,dir, requester):    
839         this.__set('tmp',dir,requester)
840
841     #
842     # Set mudela source file name
843     #
844     def setFilename(this,file, requester):      
845         this.__set('filename',file,requester)
846
847     #
848     # Set title commands
849     #
850     def setTitles(this,titles, requester):      
851         this.__set('titles',titles,requester)
852
853     #
854     # Set title commands
855     #
856     def addLilyOutputFiles(this,filelist,requester):
857         """
858         Add a to the lily output list
859         """
860
861         tmp = this.get('lilyOutputFiles')
862         tmp = tmp + filelist
863         this.__set('lilyOutputFiles',tmp,requester)
864
865     #
866     # Set/Clear postscript flag
867     #
868     def setPostscript(this,value,requester):
869         """
870         Set postscript flag
871         """
872
873         if int(value) == 1:
874             this.__set('postscript',1,requester)
875         else:
876             this.__set('postscript',0,requester)
877
878     #
879     # Set root
880     #
881     def setRoot(this,path, requester):  
882         """
883         Set lilypond root directory
884         """
885
886         os.environ['LILYPONDPREFIX'] = path
887         if os.name == 'nt' or os.name == 'dos':
888             path = unc2dos(path);
889
890         this.__set('root',path,requester)
891         
892
893     #
894     # printProps
895     #
896     def printProps(this):
897         """
898         Print properties
899         """
900         
901         for key in this.__data.keys():
902             print "%s <%s>:<%s>" % (key,this.get(key),
903                                     this.__roverrideTable[this.__data[key][1]])
904
905 \f
906
907 #
908 # Misc functions
909 #
910
911 def getLilyopts():
912     inc = ''    
913     if len(Props.get('include')) > 0: 
914         inc = string.join (map (lambda x: '-I "%s"' % x, Props.get('include')))
915     else:
916
917         if Props.get('dependencies'):
918             dep=' -M'
919         else:
920             dep=''
921         return inc + dep
922     return inc
923
924 def writeLilylog(file,contents):
925     if Props.get('keeplilypond'):
926         base, ext = os.path.splitext(file)
927         tempfile.template=base + "_li"
928         file=tempfile.mktemp('.log')
929         output = Props.get('output')
930         if output != '':
931             file = os.path.join( output, file )
932         try:
933             fd = open( file, 'w' )
934         except:
935             sys.exit('ExitNoWrite', file)
936         fd.write(contents)
937         fd.close()
938
939 def getTeXFile(contents):
940     texfiles=[]
941     for line in string.split(contents,'\n'):
942         m = re.search('paper output to (.+)\.\.\.', line)
943         if m:
944             texfiles.append(m.group(1))
945
946     if texfiles == []:
947         sys.exit('ExitNoTeXName')
948     else:
949         return texfiles
950
951 def unc2dos(path):
952     """
953     Convert a path of format //<drive>/this/that/the/other to
954     <drive>:\this\that\the\other
955     """
956     m=re.match('^//([A-Za-z])(/.*)$',path)
957     if m:
958         return m.group(1) + ':' + os.path.normpath(m.group(2))
959     
960     
961
962 def program_id ():
963     return 'ly2dvi (GNU lilypond) ' + version;
964
965
966 def mailaddress():
967     try:
968         return os.environ['MAILADDRESS']
969     except KeyError:
970         return '(address unknown)'
971
972
973 def identify ():
974     sys.stderr.write (program_id () + '\n')
975
976 def print_version ():
977     sys.stdout.write (program_id () + '\n')
978
979 def help ():
980     sys.stdout.write (
981 """Usage: %s [OPTION]... [FILE]...
982
983 Generate dvi file from mudela or lilypond output
984
985 Options:
986   -D,--debug           increase verbosity
987   -F,--headers=        name of additional LaTeX headers file
988   -H,--Height=         set paper height (points) (see manual page)
989   -I,--include=DIR     add DIR to LilyPond\'s search path
990   -K,--keeplilypond    keep lilypond output files
991   -L,--landscape       set landscape orientation
992   -N,--nonumber        switch off page numbering
993   -O,--orientation=    set orientation (obsolete - use -L instead)
994   -P,--postscript      generate postscript file
995   -W,--Width=          set paper width (points) (see manual page)
996   -M,--dependencies    tell lilypond make a dependencies file
997   -h,--help            this help text
998   -k,--keeply2dvi      keep ly2dvi output files
999   -l,--language=       give LaTeX language (babel)
1000   -o,--output=         set output directory
1001   -p,--papersize=      give LaTeX papersize (eg. a4)
1002   -s,--separate        run all files separately through LaTeX
1003
1004 files may be (a mix of) input to or output from lilypond(1)
1005 """ % name)
1006
1007 \f
1008
1009 #
1010 # main
1011 #
1012
1013 def main():
1014     """Generate dvi files from lilypond source/output"""
1015
1016     infile = Input()
1017     outfile = TeXOutput()
1018     texInputFiles=[]
1019     tempfile.tempdir=""
1020
1021     (options, files) = getopt.getopt (sys.argv[1:],
1022                                       'DF:H:I:KLNPW:Mhkl:o:p:s',
1023                                       ['debug', 'headers=', 'Height=',
1024                                        'include=', 'keeplilypond', 'landscape',
1025                                        'nonumber', 'Width=', 'dependencies',
1026                                        'help', 'keeply2dvi', 'language=',
1027                                        'output=', 'version', 'papersize=', 'separate',
1028                                        'postscript'])
1029     for opt in options:
1030         o = opt[0]
1031         a = opt[1]
1032         if o == '--debug' or o == '-D':
1033             Props.setDebug(1,'commandline')
1034         elif o == '--headers' or o == '-F':
1035             Props.setHeader(a,'commandline')
1036         elif o == '--include' or o == '-I':
1037             Props.setInclude(a,'commandline')
1038         elif o == '--Height' or o == '-H':
1039             Props.setTextHeight(a,'commandline')
1040         elif o == '--keeplilypond' or o == '-K':
1041             Props.setKeeplilypond(1,'commandline')
1042         elif o == '--landscape' or o == '-L':
1043             Props.setOrientation('landscape','commandline')
1044         elif o == '--nonumber' or o == '-N':
1045             Props.setNonumber(1,'commandline')
1046         elif o == '--Width' or o == '-W':
1047             Props.setLineWidth(a,'commandline')
1048         elif o == '--dependencies' or o == '-M':
1049             Props.setDependencies(1,'commandline')
1050         elif o == '--help' or o == '-h':
1051             help()
1052             sys.exit (0)
1053         elif o == '--keeply2dvi' or o == '-k':
1054             Props.setKeeply2dvi(1,'commandline')
1055         elif o == '--language' or o == '-l':
1056             Props.setLanguage(a,'commandline')
1057         elif o == '--output' or o == '-o':
1058             Props.setOutput(a,'commandline')
1059         elif o == '--papersize' or o == '-p':
1060             Props.setPaperZize(a,'commandline')
1061         elif o == '--separate' or o == '-s':
1062             Props.setSeparate(1,'commandline')
1063         elif o == '--postscript' or o == '-P':
1064             Props.setPostscript(1,'commandline')
1065         elif o == '--version':
1066             print_version ()
1067             return 0
1068             
1069     identify()
1070     Props.read_titledefs ()
1071     
1072     if len(files):
1073         for file in files:
1074             infile.open(file)
1075             type = infile.type()
1076             infile.close()
1077             if type == 'source':
1078                 if os.environ.has_key('OS') and \
1079                    os.environ['OS'] == 'Windows_95':
1080                     cmd = 'ash -c "lilypond %s %s 2>&1"' %(getLilyopts(), file)
1081                 else:
1082                     cmd = 'lilypond %s %s 2>&1' % (getLilyopts(), file)
1083                 sys.stderr.write ('executing: %s\n'% cmd)
1084                 
1085                 fd = os.popen(cmd , 'r')
1086                 log = ''
1087                 
1088                 s = fd.readline()
1089                 while len(s) > 0:
1090                         sys.stderr.write (s)
1091                         sys.stderr.flush ()
1092                         log = log + s
1093                         s = fd.readline ()
1094                 if 0:
1095                         s = fd.read (1)
1096                         while len(s) > 0:
1097                                 sys.stderr.write (s)
1098                                 sys.stderr.flush ()
1099                                 s = fd.read (1)                 
1100                         log = log + s
1101                 stat = fd.close()
1102                 if stat:
1103                     sys.exit('ExitBadLily', cmd )
1104                 texFiles=getTeXFile(log)
1105                 writeLilylog(file,log)
1106                 Props.addLilyOutputFiles(texFiles,'program')
1107                 texInputFiles = texInputFiles + texFiles
1108             else:
1109                 texInputFiles.append(file)
1110
1111         firstfile=1
1112         for file in texInputFiles:
1113             infile.open(file)
1114             infile.setVars() # first pass set variables
1115             infile.close()
1116             if Props.get('debug'):
1117                 Props.printProps()
1118             if firstfile:
1119                 outfile.start(file)  # allow for specified name
1120             else:
1121                 outfile.next()
1122             outfile.write("""\
1123 \\input{%s}
1124 """ % (file))
1125             if Props.get('separate'):
1126                 outfile.end()
1127             else:
1128                 firstfile=0
1129         if not Props.get('separate'):
1130             outfile.end()
1131     else:
1132         help()
1133         sys.exit('ExitBadArgs','No files specified')
1134
1135 #
1136 # Exit values
1137 #
1138 ExitTable = {
1139     'ExitInterupt'         : ['Ouch!', 1 ],
1140     'ExitBadArgs'          : ['Wrong number of arguments', 2 ],
1141     'ExitNotFound'         : ['File not found', 3 ],
1142     'ExitBadPaper'         : ['Unknown papersize', 4 ],
1143     'ExitBadHeight'        : ['Invalid Height specification', 5 ],
1144     'ExitBadWidth'         : ['Invalid Width specification', 6 ],
1145     'ExitBadOrient'        : ['Invalid Orientation specification', 7 ],
1146     'ExitNoWrite'          : ['Permission denied', 8 ],
1147     'ExitNoTeXName'        : ['hmm, I could not find an output file name', 9 ],
1148     'ExitBadLily'          : ['Lilypond failed', 10 ],
1149     'ExitBadLatex'         : ['Latex failed', 11 ],
1150     'ExitBadPostscript'    : ['Postscript failed', 12 ],
1151     'ExitUnknown'          : ['Unknown Exit Code', 20 ],
1152     }
1153
1154 def cleanup():
1155     lilyfiles = []
1156     tmpfiles = []
1157     if not Props.get('keeplilypond'):
1158         lilyfiles = Props.get('lilyOutputFiles')
1159     if not Props.get('keeply2dvi'):
1160         tmpfiles = glob.glob('*_ly[0-9]*.*')
1161     for file in lilyfiles + tmpfiles:
1162         if os.path.isfile(file):
1163             os.remove(file)
1164
1165
1166 Props = Properties()
1167
1168 try:
1169     main()
1170
1171 except KeyboardInterrupt:
1172     print ExitTable['ExitInterupt'][0]
1173     cleanup()
1174     sys.exit(ExitTable['ExitInterupt'][1])
1175
1176 except SystemExit, errno:
1177     if ExitTable.has_key(errno.args[0]):
1178         msg = ExitTable[errno.args[0]]
1179     else:
1180         msg = ExitTable['ExitUnknown']
1181     if len(errno.args) > 1:  
1182         sys.stderr.write( '%s: %s: %s\n' % (name, msg[0], errno.args[1]))
1183     else:
1184         sys.stderr.write( '%s %s\n' % (name, msg[0]))
1185     if Props.get('debug'):
1186         Props.printProps()
1187     cleanup()
1188     sys.exit(msg[1])
1189 else:
1190     cleanup()