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