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