]> git.donarmstrong.com Git - lilypond.git/blob - scripts/ly2dvi.py
release: 1.3.123
[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 # usergenerated 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             ( 'paperorientation', Props.setOrientation ),
149             ( 'paperpapersize',   Props.setPaperZize ),
150             ( 'papertextheight',  Props.setTextHeight ),
151             ( 'paperlinewidth',   Props.setLineWidth ),
152             ( 'filename',         Props.setFilename ),
153             ]
154
155         titles={}
156         for line in this.__fd.readlines():
157             m=re.match('\\\\def\\\\lilypond([\w]+){(.*)}',line)
158             if m:
159                 for var in varTable:
160                     if m.group(1) == var[0]:
161                         var[1](m.group(2),'file')
162                         break
163                 for var in Props.get('titledefs'):
164                     if m.group(1) == var:
165                         titles[var]=m.group(2)
166                         break
167         Props.setTitles(titles,'file')
168         this.__fd.seek(0)
169
170 \f
171
172 class TeXOutput:
173     """
174     This class handles all ly2dvi.py output file methods
175
176     private methods:
177      __lilypondDefs(opt)  Send title info to output file
178
179     Public methods:
180     __init__()  Constructor
181     write(str)  Write a string to output file 
182     start(file) Start the latex file
183     next()      Process next output file
184     end()       Finish latex file and run latex 
185     """
186
187     #
188     # constructor
189     #
190     def __init__(this):
191        this.__fd = None 
192        this.__base = ''
193        this.__outfile = ''
194
195     #
196     # __medelaDefs
197     #
198     def __lilypondDefs(this,opt):
199         """
200         Write titles to output
201
202         input:  opt   Supports header and subheader output
203         output: None
204         error:  None
205         """
206
207         titles = Props.get('titles')
208         for key in titles.keys():
209             this.write('%s\\lilypond%s{%s}%%\n' % (opt,key,titles[key]))
210
211     #
212     # write
213     #
214     def write(this,str):
215         """
216         Write str to current output file
217
218         input:  str  String to write
219         output: None
220         error:  None
221         """
222         
223         this.__fd.write(str)
224
225     #
226     # start
227     #
228     def start(this,file):
229         """
230         Start LaTeX file. Sets the linewidth (and possibly the
231         textheight) and leaves the page layout to the geometry
232         package. Creates temporary output filename and opens it
233         for write. Sends the LaTeX header information to output.
234         Lastly sends the title information to output.
235
236         input:  file  output file name 
237         output: None
238         error:  None
239         """
240
241         now=time.asctime(time.localtime(time.time()))
242
243         # Only set the textheight if it was explicitly set by the user,
244         # otherwise use the default. Helps to handle landscape correctly!
245         if Props.get('textheight') > 0:
246             textheightsetting = ',textheight=' + `Props.get('textheight')` + 'pt'
247         else:
248             textheightsetting = ''
249
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 %%
262 %s 
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 \begin{document}
278 """ % ( program_id(), Props.get('filename'), now, Props.get('papersize'),
279         Props.get('language'), Props.get('pagenumber'), Props.get('linewidth'),
280         textheightsetting, Props.get('orientation'), Props.get('header') )
281         
282         base, ext = os.path.splitext(file)
283         this.__base = base
284         tempfile.template= base + '_ly'
285         this.__outfile = tempfile.mktemp(ext)
286         base, ext = os.path.splitext(this.__outfile)
287         this.__tmpbase = base
288         try:
289             this.__fd = open(this.__outfile,"w")
290         except:
291             sys.exit('ExitNoWrite', this.__outfile)
292         this.write(top)
293         this.__lilypondDefs('')
294         this.write("""\
295 \\makelilytitle
296 """) 
297
298     #
299     # next
300     #
301     def next(this):
302         """
303         Write LaTeX subheader information to support more than one
304         score in a document.  Lastly send current title information to
305         output.
306
307         input:  None
308         output: None
309         error:  None
310         """
311
312         this.write("""\
313 \\def\\theopus{}%
314 \\def\\thepiece{}%
315 \\def\\lilypondopus{}%
316 \\def\\lilypondpiece{}%
317 """)
318         this.__lilypondDefs("\\def")
319         this.write("""\
320 \\def\\theopus{\\lilypondopus}% ugh
321 \\def\\thepiece{\\lilypondpiece}%
322 \\makelilypiecetitle
323 """)
324
325
326     #
327     # end
328     #
329     def end(this):
330         """
331         Close output file and run latex on it.
332
333         input:  None
334         output: None
335         error:  ExitBadLatex Exception
336         """
337
338         outfile=this.__base + '.dvi'
339         if Props.get('output') != '':
340             outfile = os.path.join(Props.get('output'), outfile )
341             
342         this.write(r"""
343 %% \vfill\hfill{\lilypondtagline}
344 \makeatletter
345 \renewcommand{\@oddfoot}{\parbox{\textwidth}{\mbox{}\lilypondtagline}}%%
346 \makeatother
347 \end{document}
348 """)
349         this.__fd.close()
350         if os.path.isfile(outfile):
351             os.remove(outfile)
352         if ( os.name == 'posix' ):
353             stat = os.system('latex \'\\nonstopmode \\input %s\'' %
354                              (this.__outfile))
355         else: # Windows shells don't eat the single quotes
356             stat = os.system('latex \\nonstopmode \\input %s' %
357                              (this.__outfile))
358         if stat:
359             sys.exit('ExitBadLatex')
360         if not os.path.isfile(outfile):
361                 os.rename(this.__tmpbase + '.dvi', outfile)
362                 
363         sys.stderr.write('\n' + program_id() + ': dvi file name is %s\n\n'
364                          % (outfile))
365
366         if Props.get('postscript'):
367             dvipsopts=''
368             if Props.get('orientation') == 'landscape':
369                 dvipsopts=dvipsopts + ' -t landscape'
370             psoutfile=this.__base + '.ps'
371             if Props.get('output') != '':
372                 psoutfile = os.path.join(Props.get('output'), psoutfile )
373             stat = os.system('dvips %s -o %s %s' % (dvipsopts,psoutfile,outfile))
374             if stat:
375                 sys.exit('ExitBadPostscript')
376             
377
378 \f
379
380
381 # ARG! THIS CODE IS BLOATED:
382 # FIXME: Junk all set/get methods.
383
384 class Properties:
385     """
386     This class handles all ly2dvi.py property manipulation
387
388     Public methods:
389     
390     __init__()  Constructor
391     set<property> methods
392     """
393
394     def __init__(this):
395
396         #
397         # Following is the order of priority for property assignment.  The
398         # list is organized from lowest to highest priority.  Each
399         # assignment is overridden by the next requester in the list.
400         #
401         # Requester     Description
402         # ---------     -----------
403         # init          Initial default values
404         # file          The values found in the LilyPond generated TeX files
405         # environment   Envrionment variables LILYINCLUDE, LILYPONDPREFIX
406         # rcfile        $LILYPONDPREFIX/.lilyrc
407         # rcfile        $HOME/.lilyrc
408         # rcfile        ./.lilyrc
409         # commandline   command line arguments
410         # 
411         this.__overrideTable = {
412             'init'        : 0,
413             'file'        : 1,
414             'environment' : 2,
415             'rcfile'      : 3,
416             'commandline' : 4,
417             'program'     : 5
418             }
419
420         this.__roverrideTable = {} # reverse lookup used for debug
421         for i in this.__overrideTable.items():
422             this.__roverrideTable[i[1]]=i[0]
423         
424         this.__data = {
425             'papersize'    :  ['a4paper', this.__overrideTable['init']],
426             'textheight'   :  [0, this.__overrideTable['init']],
427             'linewidth'    :  [500, this.__overrideTable['init']],
428             'orientation'  :  ['portrait', this.__overrideTable['init']],
429             'language'     :  ['%', this.__overrideTable['init']],
430             'include'      :  [[], this.__overrideTable['init']],
431             'debug'        :  [0, this.__overrideTable['init']],
432             'keeplilypond' :  [0, this.__overrideTable['init']],
433             'keeply2dvi'   :  [0, this.__overrideTable['init']],
434             'pagenumber'   :  ['%', this.__overrideTable['init']],
435             'separate'     :  [0, this.__overrideTable['init']],
436             'output'       :  ['', this.__overrideTable['init']],
437             'header'       :  ['%', this.__overrideTable['init']],
438             'dependencies' :  [0, this.__overrideTable['init']],
439             'root'         :  ['', this.__overrideTable['init']],
440             'tmp'          :  ['d:\tmp', this.__overrideTable['init']],
441             'filename'     :  ['', this.__overrideTable['init']],
442             'titledefs'    :  [[], this.__overrideTable['init']],
443             'titles'       :  [{}, this.__overrideTable['init']],
444             'lilyOutputFiles' :  [[], this.__overrideTable['init']],
445             'postscript'   :  [0, this.__overrideTable['init']],
446             }
447
448         #
449         # Try to set root and HOME first before calling rcfile
450         #
451         if os.environ.has_key('LILYPONDPREFIX'):
452             this.setRoot(os.environ['LILYPONDPREFIX'], 'environment')
453         else:
454             p=os.path.split(sys.argv[0])
455             p=os.path.split(p[0])
456             # bit silly. for ly2dvi, overrules compiled-in datadir...
457             # how to do this better (without running lily, of course?
458             this.setRoot(os.path.join(p[0],'share','lilypond'), 'init')
459
460         if not os.environ.has_key('HOME'):
461             if os.environ.has_key('HOMEDRIVE') and \
462                  os.environ.has_key('HOMEPATH'):
463                 os.environ['HOME'] = os.environ['HOMEDRIVE'] + \
464                                      os.environ['HOMEPATH']
465             else:
466                 os.environ['HOME'] = os.curdir
467
468         this.rcfile() # Read initialization file(s)
469
470         if os.environ.has_key('LILYINCLUDE'):
471             tmp=this.get('include')
472             for s in string.split(os.environ['LILYINCLUDE'],os.pathsep):
473                 tmp.append(s)
474             this.__set('include', tmp, 'environment')    
475
476
477         t= os.pathsep
478         if os.environ.has_key ('TEXINPUTS'):
479                 t = os.environ['TEXINPUTS'] + os.pathsep
480         os.environ['TEXINPUTS'] = t + \
481         os.path.join(this.get('root'), 'tex' ) + \
482         os.pathsep + os.path.join(this.get('root'), 'ps' )
483
484         t=''
485         if os.environ.has_key ('MFINPUTS'):
486                t = os.environ['MFINPUTS'] 
487         os.environ['MFINPUTS'] = t + os.pathsep + \
488                                  os.path.join(this.get('root'), 'mf')
489
490         if os.environ.has_key('TMP'):
491             this.__set('tmp',os.environ['TMP'],'environment')
492
493
494     def read_titledefs (this):
495         fd=this.get_texfile_path ('titledefs.tex')
496         mudefs=[]    
497
498         for line in fd.readlines():
499             m=re.match('\\\\newcommand\*{\\\\lilypond([\w]+)}',line)
500             if m:
501                 mudefs.append(m.group(1))
502         fd.close
503         this.__set('titledefs', mudefs, 'init')
504
505     #
506     # __set
507     #
508     def __set(this,var,value,requester):
509         """
510         All of the set methods call this to set a property.  If the value
511         was last set by a requestor of lesser priority the new value is
512         assigned, else the old value has priority and is unchanged.
513         """
514
515         if this.__overrideTable[requester] < this.__data[var][1]:
516             return 0
517         else:
518             this.__data[var] = [value, this.__overrideTable[requester]]
519
520     #
521     # get
522     #
523     def get(this,var):
524         """
525         All of the get methods call this to get a property value.  List
526         variable types are return by value to facilitate an append operation.
527         """
528
529         if var == 'include' or var == 'lilyOutputFiles':
530             return this.__data[var][0][0:]  # return a copy not a ref
531         else:
532             return this.__data[var][0]
533
534     #
535     # get_texfile_path
536     #
537     def get_texfile_path (this, var):
538         """
539         locate and open titledefs.tex file
540         """
541
542         if os.name == 'nt':
543             path = os.path.join(this.get('root'), 'tex', var)
544         else:
545             path =''
546             cmd =('kpsewhich tex %s %s' % (var,errorlog))
547             pipe = os.popen (cmd, 'r')
548             path = pipe.readline ()[:-1] # chop off \n
549             return_status =  pipe.close()
550             if return_status and not path:
551                 path = os.path.join(this.get('root'), 'tex', var)
552         fd = open(path, 'r')
553         return fd
554
555
556     #
557     # Read rc file
558     #
559     def rcfile(this):
560         """
561         Read initialization file(s)
562         """
563         varTable = [
564             #   name              set method
565             #   ----              ----------
566             ( 'DEBUG',          this.setDebug ),
567             ( 'DEPENDENCIES',   this.setDependencies ),
568             ( 'KEEPLILYPOND',   this.setKeeplilypond ),
569             ( 'KEEPLY2DVI',     this.setKeeply2dvi ),
570             ( 'LANGUAGE',       this.setLanguage ),
571             ( 'LATEXHF',        this.setHeader ),
572             ( 'LILYINCLUDE',    this.setInclude ),
573             ( 'LILYPONDPREFIX', this.setRoot ),
574             ( 'NONUMBER',       this.setNonumber ),
575             ( 'ORIENTATION',    this.setOrientation ),
576             ( 'OUTPUTDIR',      this.setOutput ),
577             ( 'PAPERSIZE',      this.setPaperZize ),
578             ( 'PHEIGHT',        this.setTextHeight ),
579             ( 'POSTSCRIPT',     this.setPostscript ),
580             ( 'PWIDTH',         this.setLineWidth ),
581             ( 'SEPARATE',       this.setSeparate ),
582             ( 'TMP',            this.setTmp ),
583             ]
584
585         if ( os.name == 'posix' ):
586             dotFilename='.lilyrc'
587         else: # Windows apps like edit choke on .lilyrc
588             dotFilename='_lilyrc'
589
590         for d in [os.path.join(this.get('root'),'ly'), \
591                   os.environ['HOME'], os.curdir ]:
592             file=os.path.join(d,dotFilename)
593             try:
594                 fd = open( file, 'r' )
595             except:
596                 continue
597             
598             for line in fd.readlines():
599                 if re.match('#.*',line):
600                     continue
601                 m=re.search('([\w]+)=(.*)',line)
602                 if m:
603                     for var in varTable:
604                         if m.group(1) == var[0]:
605                             var[1](m.group(2),'rcfile')
606                             break
607             fd.close
608
609     #
610     # setPaperZize
611     #
612     def setPaperZize(this,size,requester):
613         """
614         Set paper size properties
615         """
616
617         paperTable = [
618             # regex          width    height      name
619             # -----          -----    ------      ----
620             ( 'a0.*',        2389,    3381,    'a0paper' ),
621             ( 'a1$|a1p.*',   1690,    2389,    'a1paper' ),
622             ( 'a2.*',        1194,    1690,    'a2paper' ),
623             ( 'a3.*',        845,     1194,    'a3paper' ),
624             ( 'a4.*',        597,     845,     'a4paper' ),
625             ( 'a5.*',        423,     597,     'a5paper' ),
626             ( 'a6.*',        298,     423,     'a6paper' ),
627             ( 'a7.*',        211,     298,     'a7paper' ),
628             ( 'a8.*',        305,     211,     'a8paper' ),
629             ( 'a9.*',        105,     305,     'a9paper' ),
630             ( 'a10.*',       74,      105,     'a10paper' ),
631             ( 'b0.*',        2847,    4023,    'b0paper' ),
632             ( 'b1.*',        2012,    2847,    'b1paper' ),
633             ( 'b2.*',        1423,    2012,    'b2paper' ),
634             ( 'b3.*',        1006,    1423,    'b3paper' ),
635             ( 'b4.*',        712,     1006,    'b4paper' ),
636             ( 'b5.*',        503,     712,     'b5paper' ),
637             ( 'archA$',      650,     867,     'archApaper' ),
638             ( 'archB$',      867,     1301,    'archBpaper' ),
639             ( 'archC$',      1301,    1734,    'archCpaper' ),
640             ( 'archD$',      1734,    2602,    'archDpaper' ),
641             ( 'archE$',      2602,    3469,    'archEpaper' ),
642             ( 'flsa$|flse$', 614,     940,     'flsapaper' ),
643             ( 'halfletter$', 397,     614,     'halfletterpaper' ),
644             ( 'ledger$',     1229,    795,     'ledgerpaper' ),
645             ( 'legal$',      614,     1012,    'legalpaper' ),
646             ( 'letter$',     614,     795,     'letterpaper' ),
647             ( 'note$',       542,     723,     'notepaper' )
648             ]
649
650         found=0
651         for paper in paperTable:
652             if re.match(paper[0],size):
653                 found=1
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','\\input{' + 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 lilypond 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 LilyPond source/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 to 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
1030     for opt in options:
1031         o = opt[0]
1032         a = opt[1]
1033         if o == '--debug' or o == '-D':
1034             Props.setDebug(1,'commandline')
1035         elif o == '--headers' or o == '-F':
1036             Props.setHeader(a,'commandline')
1037         elif o == '--include' or o == '-I':
1038             Props.setInclude(a,'commandline')
1039         elif o == '--Height' or o == '-H':
1040             Props.setTextHeight(a,'commandline')
1041         elif o == '--keeplilypond' or o == '-K':
1042             Props.setKeeplilypond(1,'commandline')
1043         elif o == '--landscape' or o == '-L':
1044             Props.setOrientation('landscape','commandline')
1045         elif o == '--nonumber' or o == '-N':
1046             Props.setNonumber(1,'commandline')
1047         elif o == '--Width' or o == '-W':
1048             Props.setLineWidth(a,'commandline')
1049         elif o == '--dependencies' or o == '-M':
1050             Props.setDependencies(1,'commandline')
1051         elif o == '--help' or o == '-h':
1052             help()
1053             sys.exit (0)
1054         elif o == '--keeply2dvi' or o == '-k':
1055             Props.setKeeply2dvi(1,'commandline')
1056         elif o == '--language' or o == '-l':
1057             Props.setLanguage(a,'commandline')
1058         elif o == '--output' or o == '-o':
1059             Props.setOutput(a,'commandline')
1060         elif o == '--papersize' or o == '-p':
1061             Props.setPaperZize(a,'commandline')
1062         elif o == '--separate' or o == '-s':
1063             Props.setSeparate(1,'commandline')
1064         elif o == '--postscript' or o == '-P':
1065             Props.setPostscript(1,'commandline')
1066         elif o == '--version':
1067             print_version ()
1068             return 0
1069         else:
1070             print o
1071             raise getopt.error
1072             
1073     identify()
1074     Props.read_titledefs ()
1075     
1076     if len(files):
1077         for file in files:
1078             infile.open(file)
1079             type = infile.type()
1080             infile.close()
1081             if type == 'source':
1082                 if os.environ.has_key('OS') and \
1083                    os.environ['OS'] == 'Windows_95':
1084                     cmd = 'ash -c "lilypond %s %s 2>&1"' %(getLilyopts(), file)
1085                 else:
1086                     cmd = 'lilypond %s %s 2>&1' % (getLilyopts(), file)
1087                 sys.stderr.write ('executing: %s\n'% cmd)
1088                 
1089                 fd = os.popen(cmd , 'r')
1090                 log = ''
1091                 
1092                 s = fd.readline()
1093                 while len(s) > 0:
1094                         sys.stderr.write (s)
1095                         sys.stderr.flush ()
1096                         log = log + s
1097                         s = fd.readline ()
1098                 if 0:
1099                         s = fd.read (1)
1100                         while len(s) > 0:
1101                                 sys.stderr.write (s)
1102                                 sys.stderr.flush ()
1103                                 s = fd.read (1)                 
1104                         log = log + s
1105                 stat = fd.close()
1106                 if stat:
1107                     sys.exit('ExitBadLily', cmd )
1108                 texFiles=getTeXFile(log)
1109                 writeLilylog(file,log)
1110                 Props.addLilyOutputFiles(texFiles,'program')
1111                 texInputFiles = texInputFiles + texFiles
1112             else:
1113                 texInputFiles.append(file)
1114
1115         firstfile=1
1116         for file in texInputFiles:
1117             infile.open(file)
1118             infile.setVars() # first pass set variables
1119             infile.close()
1120             if Props.get('debug'):
1121                 Props.printProps()
1122             if firstfile:
1123                 outfile.start(file)  # allow for specified name
1124             else:
1125                 outfile.next()
1126             outfile.write("""\
1127 \\input{%s}
1128 """ % (file))
1129             if Props.get('separate'):
1130                 outfile.end()
1131             else:
1132                 firstfile=0
1133         if not Props.get('separate'):
1134             outfile.end()
1135     else:
1136         help()
1137         sys.exit('ExitBadArgs','No files specified')
1138
1139 #
1140 # Exit values
1141 #
1142 ExitTable = {
1143     'ExitInterupt'         : ['Ouch!', 1 ],
1144     'ExitBadArgs'          : ['Wrong number of arguments', 2 ],
1145     'ExitNotFound'         : ['File not found', 3 ],
1146     'ExitBadPaper'         : ['Unknown papersize', 4 ],
1147     'ExitBadHeight'        : ['Invalid Height specification', 5 ],
1148     'ExitBadWidth'         : ['Invalid Width specification', 6 ],
1149     'ExitBadOrient'        : ['Invalid Orientation specification', 7 ],
1150     'ExitNoWrite'          : ['Permission denied', 8 ],
1151     'ExitNoTeXName'        : ['Hmm, I could not find an output file name', 9 ],
1152     'ExitBadLily'          : ['LilyPond failed', 10 ],
1153     'ExitBadLatex'         : ['Latex failed', 11 ],
1154     'ExitBadPostscript'    : ['Postscript failed', 12 ],
1155     'ExitUnknown'          : ['Unknown Exit Code', 20 ],
1156     }
1157
1158 def cleanup():
1159     lilyfiles = []
1160     tmpfiles = []
1161     if not Props.get('keeplilypond'):
1162         lilyfiles = Props.get('lilyOutputFiles')
1163     if not Props.get('keeply2dvi'):
1164         tmpfiles = glob.glob('*_ly[0-9]*.*')
1165     for file in lilyfiles + tmpfiles:
1166         if os.path.isfile(file):
1167             os.remove(file)
1168
1169
1170 Props = Properties()
1171
1172 try:
1173     main()
1174
1175 except KeyboardInterrupt:
1176     print ExitTable['ExitInterupt'][0]
1177     cleanup()
1178     sys.exit(ExitTable['ExitInterupt'][1])
1179
1180 except SystemExit, errno:
1181     if ExitTable.has_key(errno.args[0]):
1182         msg = ExitTable[errno.args[0]]
1183     else:
1184         msg = ExitTable['ExitUnknown']
1185     if len(errno.args) > 1:  
1186         sys.stderr.write( '%s: %s: %s\n' % (name, msg[0], errno.args[1]))
1187     else:
1188         sys.stderr.write( '%s %s\n' % (name, msg[0]))
1189     if Props.get('debug'):
1190         Props.printProps()
1191     cleanup()
1192     sys.exit(msg[1])
1193 else:
1194     cleanup()