]> git.donarmstrong.com Git - lilypond.git/blob - scripts/ly2dvi.py
release: 1.3.107
[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 os.environ['LANG'] = ''         # Can't grep localized msgs
40
41
42
43 class Input:
44     """
45     This class handles all ly2dvi.py input file methods
46     
47     Public methods:
48     
49     __init__()  Constructor
50     open(file)  Open a .ly file or .tex file using lilyinclude path
51     close()     Close current file
52     type()      Determine file type .ly (input) or .tex (output)
53     setVars()   Set title definitions found in .tex (output) file
54     """
55
56     #
57     # Constructors
58     #
59
60     def __init__(this):
61        this.__fd = None 
62
63     #
64     # open
65     #
66     def open(this,file):
67         """
68         open file and set private class variable __fd.  The search
69         sequence is: current directory followed by the directories
70         found in include property list.  Each directory is searched
71         for file, file.ly, file.sly and file.fly.
72         
73         input:  file   filename
74         output: void
75         error:  ExitNotFound Exception
76         """
77
78         for i in [''] + Props.get('include')[0:]:
79             ifile = os.path.join(i,file)
80             for j in ['','.ly','.fly', '.sly']:
81                 jfile = ifile+j
82                 try:
83                     this.__fd = open( jfile, 'r' )
84                     return
85                 except:
86                     pass
87         sys.exit('ExitNotFound', file)
88
89
90     #
91     # close
92     #
93     def close(this):
94         """
95         close file object __fd
96         
97         input:  void
98         output: void
99         error:  None
100         """
101         this.__fd.close()
102
103
104     #
105     # type
106     #
107     def type(this):
108         """
109         Determine input file type.  LilyPond source is 'input' type
110         and LilyPond generated TeX file is 'output' type
111
112         input:  void
113         output: 'input' | 'output'
114         error:  None
115         """
116
117         firstline = this.__fd.readline()
118         this.__fd.seek(0)
119         if  re.match('%created by: GNU LilyPond [0-9]+[.0-9]+',firstline ):
120             return 'output'
121         else:
122             return 'source'
123
124
125     #
126     # setVars
127     #
128     def setVars(this):  
129         """
130         Search for properties in the current input file and set the
131         appropriate values.  The supported properties names are in
132         local variable varTable along with the property list
133         titledefs.
134
135         input:  void
136         output: None
137         error:  None
138         """
139
140         varTable = [
141             #   regexp              set method
142             #   ------              ----------
143             ( 'language',        Props.setLanguage ),
144             ( 'latexheaders',    Props.setHeader ),
145             ( 'orientation',     Props.setOrientation ),
146             ( 'paperpapersize',  Props.setPaperZize ),
147             ( 'papertextheight', Props.setTextHeight ),
148             ( 'paperlinewidth',  Props.setLineWidth ),
149             ( 'filename',        Props.setFilename ),
150             ]
151
152         titles={}
153         for line in this.__fd.readlines():
154             m=re.match('\\\\def\\\\lilypond([\w]+){(.*)}',line)
155             if m:
156                 for var in varTable:
157                     if m.group(1) == var[0]:
158                         var[1](m.group(2),'file')
159                         break
160                 for var in Props.get('titledefs'):
161                     if m.group(1) == var:
162                         titles[var]=m.group(2)
163                         break
164         Props.setTitles(titles,'file')
165         this.__fd.seek(0)
166
167 \f
168
169 class TeXOutput:
170     """
171     This class handles all ly2dvi.py output file methods
172
173     private methods:
174      __lilypondDefs(opt)  Send title info to output file
175
176     Public methods:
177     __init__()  Constructor
178     write(str)  Write a string to output file 
179     start(file) Start the latex file
180     next()      Process next output file
181     end()       Finish latex file and run latex 
182     """
183
184     #
185     # constructor
186     #
187     def __init__(this):
188        this.__fd = None 
189        this.__base = ''
190        this.__outfile = ''
191
192     #
193     # __medelaDefs
194     #
195     def __lilypondDefs(this,opt):
196         """
197         Write titles to output
198
199         input:  opt   Supports header and subheader output
200         output: None
201         error:  None
202         """
203
204         titles = Props.get('titles')
205         for key in titles.keys():
206             this.write('%s\\lilypond%s{%s}%%\n' % (opt,key,titles[key]))
207
208     #
209     # write
210     #
211     def write(this,str):
212         """
213         Write str to current output file
214
215         input:  str  String to write
216         output: None
217         error:  None
218         """
219         
220         this.__fd.write(str)
221
222     #
223     # start
224     #
225     def start(this,file):
226         """
227         Start LaTeX file.  Calculates the horizontal and vertical
228         margin using pagewidth, pageheight, linewidth, and textheight.
229         Creates temporary output filename and opens it for write.
230         Sends the LaTeX header information to output.  Lastly sends
231         the title information to output.
232
233         input:  file  output file name 
234         output: None
235         error:  None
236         """
237
238         now=time.asctime(time.localtime(time.time()))
239         linewidth = Props.get('linewidth')
240         textheight = Props.get('textheight')
241
242         if Props.get('orientation') == 'landscape':
243             pagewidth = Props.get('pageheight')
244             pageheight = Props.get('pagewidth')
245         else:
246             pageheight = Props.get('pageheight')
247             pagewidth = Props.get('pagewidth')
248                                  
249
250         top= r"""
251 %% Creator: %s
252 %% Automatically generated from  %s, %s
253
254 \documentclass[%s]{article}
255
256 %s 
257 \usepackage{geometry}
258 \usepackage[latin1]{inputenc} 
259 %%\usepackage[T1]{fontenc} 
260 %%
261 %s 
262 %% don not waste unused space at bottom of page
263 %% (unless we have footnotes ...)
264 %%\headheight9pt
265 %%\headsep0pt
266 %% Maybe this is too drastic, but let us give it a try.
267 \geometry{width=%spt, textheight=%spt,headheight=2mm,headsep=0pt,footskip=2mm} 
268 \input{titledefs}
269 %s
270 \makeatletter
271 \renewcommand{\@oddhead}{\parbox{\textwidth}%%
272     {\mbox{}\small\theheader\hfill\textbf{\thepage}}}%%
273 %% UGR.
274 %%\renewcommand{\@evenhead}{eve!{\small\lilypondinstrument{,}\quad\textbf{\thepage}}\hfil}%%
275 \renewcommand{\@oddfoot}{\parbox{\textwidth}{\mbox{}\thefooter}}%%
276 \begin{document}
277 """ % ( program_id(), Props.get('filename'), now, Props.get('papersize'),
278         Props.get('language'), Props.get('pagenumber'), linewidth,
279         textheight, Props.get('header') )
280         
281         base, ext = os.path.splitext(file)
282         this.__base = base
283         tempfile.template= base + '_ly'
284         this.__outfile = tempfile.mktemp(ext)
285         base, ext = os.path.splitext(this.__outfile)
286         this.__tmpbase = base
287         try:
288             this.__fd = open(this.__outfile,"w")
289         except:
290             sys.exit('ExitNoWrite', this.__outfile)
291         this.write(top)
292         this.__lilypondDefs('')
293         this.write("""\
294 \\makelilytitle
295 """) 
296
297     #
298     # next
299     #
300     def next(this):
301         """
302         Write LaTeX subheader information to support more than one
303         score in a document.  Lastly send current title information to
304         output.
305
306         input:  None
307         output: None
308         error:  None
309         """
310
311         this.write("""\
312 \\def\\theopus{}%
313 \\def\\thepiece{}%
314 \\def\\lilypondopus{}%
315 \\def\\lilypondpiece{}%
316 """)
317         this.__lilypondDefs("\\def")
318         this.write("""\
319 \\def\\theopus{\\lilypondopus}% ugh
320 \\def\\thepiece{\\lilypondpiece}%
321 \\makelilypiecetitle
322 """)
323
324
325     #
326     # end
327     #
328     def end(this):
329         """
330         Close output file and run latex on it.
331
332         input:  None
333         output: None
334         error:  ExitBadLatex Exception
335         """
336
337         outfile=this.__base + '.dvi'
338         if Props.get('output') != '':
339             outfile = os.path.join(Props.get('output'), outfile )
340             
341         this.write(r"""
342 %% \vfill\hfill{\lilypondtagline}
343 \makeatletter
344 \renewcommand{\@oddfoot}{\parbox{\textwidth}{\mbox{}\lilypondtagline}}%%
345 \makeatother
346 \end{document}
347 """)
348         this.__fd.close()
349         if os.path.isfile(outfile):
350             os.remove(outfile)
351         if ( os.name == 'posix' ):
352             stat = os.system('latex \'\\nonstopmode \\input %s\'' %
353                              (this.__outfile))
354         else: # Windows shells don't eat the single quotes
355             stat = os.system('latex \\nonstopmode \\input %s' %
356                              (this.__outfile))
357         if stat:
358             sys.exit('ExitBadLatex')
359         if not os.path.isfile(outfile):
360                 os.rename(this.__tmpbase + '.dvi', outfile)
361                 
362         sys.stderr.write('\n' + program_id() + ': dvi file name is %s\n\n'
363                          % (outfile))
364
365         if Props.get('postscript'):
366             psoutfile=this.__base + '.ps'
367             if Props.get('output') != '':
368                 psoutfile = os.path.join(Props.get('output'), psoutfile )
369             stat = os.system('dvips -o %s %s' % (psoutfile,outfile))
370             if stat:
371                 sys.exit('ExitBadPostscript')
372             
373
374 \f
375
376
377 # ARG! THIS CODE IS BLOATED:
378 # FIXME: Junk all set/get methods.
379
380 class Properties:
381     """
382     This class handles all ly2dvi.py property manipulation
383
384     Public methods:
385     
386     __init__()  Constructor
387     set<property> methods
388     """
389
390     def __init__(this):
391
392         #
393         # Following is the order of priority for property assignment.  The
394         # list is organized from lowest to highest priority.  Each
395         # assignment is overridden by the next requester in the list.
396         #
397         # Requester     Description
398         # ---------     -----------
399         # init          Initial default values
400         # file          The values found in the LilyPond generated TeX files
401         # environment   Envrionment variables LILYINCLUDE, LILYPONDPREFIX
402         # rcfile        $LILYPONDPREFIX/.lilyrc
403         # rcfile        $HOME/.lilyrc
404         # rcfile        ./.lilyrc
405         # commandline   command line arguments
406         # 
407         this.__overrideTable = {
408             'init'        : 0,
409             'file'        : 1,
410             'environment' : 2,
411             'rcfile'      : 3,
412             'commandline' : 4,
413             'program'     : 5
414             }
415
416         this.__roverrideTable = {} # reverse lookup used for debug
417         for i in this.__overrideTable.items():
418             this.__roverrideTable[i[1]]=i[0]
419         
420         this.__data = {
421             'pagewidth'    :  [597, this.__overrideTable['init']],
422             'pageheight'   :  [845, this.__overrideTable['init']],
423             'papersize'    :  ['a4paper', this.__overrideTable['init']],
424             'textheight'   :  [0, this.__overrideTable['init']],
425             'linewidth'    :  [500, this.__overrideTable['init']],
426             'orientation'  :  ['portrait', this.__overrideTable['init']],
427             'language'     :  ['%', this.__overrideTable['init']],
428             'include'      :  [[], this.__overrideTable['init']],
429             'debug'        :  [0, this.__overrideTable['init']],
430             'keeplilypond' :  [0, this.__overrideTable['init']],
431             'keeply2dvi'   :  [0, this.__overrideTable['init']],
432             'pagenumber'   :  ['%', this.__overrideTable['init']],
433             'separate'     :  [0, this.__overrideTable['init']],
434             'output'       :  ['', this.__overrideTable['init']],
435             'header'       :  ['%', this.__overrideTable['init']],
436             'dependencies' :  [0, this.__overrideTable['init']],
437             'root'         :  ['', this.__overrideTable['init']],
438             'tmp'          :  ['d:\tmp', this.__overrideTable['init']],
439             'filename'     :  ['', this.__overrideTable['init']],
440             'titledefs'    :  [[], this.__overrideTable['init']],
441             'titles'       :  [{}, this.__overrideTable['init']],
442             'lilyOutputFiles' :  [[], this.__overrideTable['init']],
443             'postscript'   :  [0, this.__overrideTable['init']],
444             }
445
446         #
447         # Try to set root and HOME first before calling rcfile
448         #
449         if os.environ.has_key('LILYPONDPREFIX'):
450             this.setRoot(os.environ['LILYPONDPREFIX'], 'environment')
451         else:
452             p=os.path.split(sys.argv[0])
453             p=os.path.split(p[0])
454             # bit silly. for ly2dvi, overrules compiled-in datadir...
455             # how to do this better (without running lily, of course?
456             this.setRoot(os.path.join(p[0],'share','lilypond'), 'init')
457
458         if not os.environ.has_key('HOME'):
459             if os.environ.has_key('HOMEDRIVE') and \
460                  os.environ.has_key('HOMEPATH'):
461                 os.environ['HOME'] = os.environ['HOMEDRIVE'] + \
462                                      os.environ['HOMEPATH']
463             else:
464                 os.environ['HOME'] = os.curdir
465
466         this.rcfile() # Read initialization file(s)
467
468         if os.environ.has_key('LILYINCLUDE'):
469             tmp=this.get('include')
470             for s in string.split(os.environ['LILYINCLUDE'],os.pathsep):
471                 tmp.append(s)
472             this.__set('include', tmp, 'environment')    
473
474
475         t= os.pathsep
476         if os.environ.has_key ('TEXINPUTS'):
477                 t = os.environ['TEXINPUTS'] + os.pathsep
478         os.environ['TEXINPUTS'] = t + \
479         os.path.join(this.get('root'), 'tex' ) + \
480         os.pathsep + os.path.join(this.get('root'), 'ps' )
481
482         t=''
483         if os.environ.has_key ('MFINPUTS'):
484                t = os.environ['MFINPUTS'] 
485         os.environ['MFINPUTS'] = t + os.pathsep + \
486                                  os.path.join(this.get('root'), 'mf')
487
488         if os.environ.has_key('TMP'):
489             this.__set('tmp',os.environ['TMP'],'environment')
490
491
492     def read_titledefs (this):
493         fd=this.get_texfile_path ('titledefs.tex')
494         mudefs=[]    
495
496         for line in fd.readlines():
497             m=re.match('\\\\newcommand\*{\\\\lilypond([\w]+)}',line)
498             if m:
499                 mudefs.append(m.group(1))
500         fd.close
501         this.__set('titledefs', mudefs, 'init')
502
503     #
504     # __set
505     #
506     def __set(this,var,value,requester):
507         """
508         All of the set methods call this to set a property.  If the value
509         was last set by a requestor of lesser priority the new value is
510         assigned, else the old value has priority and is unchanged.
511         """
512
513         if this.__overrideTable[requester] < this.__data[var][1]:
514             return 0
515         else:
516             this.__data[var] = [value, this.__overrideTable[requester]]
517
518     #
519     # get
520     #
521     def get(this,var):
522         """
523         All of the get methods call this to get a property value.  List
524         variable types are return by value to facilitate an append operation.
525         """
526
527         if var == 'include' or var == 'lilyOutputFiles':
528             return this.__data[var][0][0:]  # return a copy not a ref
529         else:
530             return this.__data[var][0]
531
532     #
533     # get_texfile_path
534     #
535     def get_texfile_path (this, var):
536         """
537         locate and open titledefs.tex file
538         """
539
540         if os.name == 'nt':
541             path = os.path.join(this.get('root'), 'tex', var)
542         else:
543             path =''
544             cmd =('kpsewhich tex %s %s' % (var,errorlog))
545             pipe = os.popen (cmd, 'r')
546             path = pipe.readline ()[:-1] # chop off \n
547             return_status =  pipe.close()
548             if return_status and not path:
549                 path = os.path.join(this.get('root'), 'tex', var)
550         fd = open(path, 'r')
551         return fd
552
553
554     #
555     # Read rc file
556     #
557     def rcfile(this):
558         """
559         Read initialization file(s)
560         """
561         varTable = [
562             #   name              set method
563             #   ----              ----------
564             ( 'DEBUG',          this.setDebug ),
565             ( 'DEPENDENCIES',   this.setDependencies ),
566             ( 'KEEPLILYPOND',   this.setKeeplilypond ),
567             ( 'KEEPLY2DVI',     this.setKeeply2dvi ),
568             ( 'LANGUAGE',       this.setLanguage ),
569             ( 'LATEXHF',        this.setHeader ),
570             ( 'LILYINCLUDE',    this.setInclude ),
571             ( 'LILYPONDPREFIX', this.setRoot ),
572             ( 'NONUMBER',       this.setNonumber ),
573             ( 'ORIENTATION',    this.setOrientation ),
574             ( 'OUTPUTDIR',      this.setOutput ),
575             ( 'PAPERSIZE',      this.setPaperZize ),
576             ( 'PHEIGHT',        this.setTextHeight ),
577             ( 'POSTSCRIPT',     this.setPostscript ),
578             ( 'PWIDTH',         this.setLineWidth ),
579             ( 'SEPARATE',       this.setSeparate ),
580             ( 'TMP',            this.setTmp ),
581             ]
582
583         if ( os.name == 'posix' ):
584             dotFilename='.lilyrc'
585         else: # Windows apps like edit choke on .lilyrc
586             dotFilename='_lilyrc'
587
588         for d in [os.path.join(this.get('root'),'ly'), \
589                   os.environ['HOME'], os.curdir ]:
590             file=os.path.join(d,dotFilename)
591             try:
592                 fd = open( file, 'r' )
593             except:
594                 continue
595             
596             for line in fd.readlines():
597                 if re.match('#.*',line):
598                     continue
599                 m=re.search('([\w]+)=(.*)',line)
600                 if m:
601                     for var in varTable:
602                         if m.group(1) == var[0]:
603                             var[1](m.group(2),'rcfile')
604                             break
605             fd.close
606
607     #
608     # setPaperZize
609     #
610     def setPaperZize(this,size,requester):
611         """
612         Set paper size properties
613         """
614
615         paperTable = [
616             # regex          width    height      name
617             # -----          -----    ------      ----
618             ( 'a0.*',        2389,    3381,    'a0paper' ),
619             ( 'a1$|a1p.*',   1690,    2389,    'a1paper' ),
620             ( 'a2.*',        1194,    1690,    'a2paper' ),
621             ( 'a3.*',        845,     1194,    'a3paper' ),
622             ( 'a4.*',        597,     845,     'a4paper' ),
623             ( 'a5.*',        423,     597,     'a5paper' ),
624             ( 'a6.*',        298,     423,     'a6paper' ),
625             ( 'a7.*',        211,     298,     'a7paper' ),
626             ( 'a8.*',        305,     211,     'a8paper' ),
627             ( 'a9.*',        105,     305,     'a9paper' ),
628             ( 'a10.*',       74,      105,     'a10paper' ),
629             ( 'b0.*',        2847,    4023,    'b0paper' ),
630             ( 'b1.*',        2012,    2847,    'b1paper' ),
631             ( 'b2.*',        1423,    2012,    'b2paper' ),
632             ( 'b3.*',        1006,    1423,    'b3paper' ),
633             ( 'b4.*',        712,     1006,    'b4paper' ),
634             ( 'b5.*',        503,     712,     'b5paper' ),
635             ( 'archA$',      650,     867,     'archApaper' ),
636             ( 'archB$',      867,     1301,    'archBpaper' ),
637             ( 'archC$',      1301,    1734,    'archCpaper' ),
638             ( 'archD$',      1734,    2602,    'archDpaper' ),
639             ( 'archE$',      2602,    3469,    'archEpaper' ),
640             ( 'flsa$|flse$', 614,     940,     'flsapaper' ),
641             ( 'halfletter$', 397,     614,     'halfletterpaper' ),
642             ( 'ledger$',     1229,    795,     'ledgerpaper' ),
643             ( 'legal$',      614,     1012,    'legalpaper' ),
644             ( 'letter$',     614,     795,     'letterpaper' ),
645             ( 'note$',       542,     723,     'notepaper' )
646             ]
647
648         found=0
649         for paper in paperTable:
650             if re.match(paper[0],size):
651                 found=1
652                 this.__set('pagewidth',paper[1],requester)
653                 this.__set('pageheight',paper[2],requester)
654                 this.__set('papersize',paper[3],requester)
655                 break
656
657         if not found:
658             sys.exit('ExitBadPaper',size)
659
660     #
661     # setTextHeight
662     #
663     def setTextHeight(this,size,requester):
664         """
665         Set textheight property
666         """
667
668         m=re.match('([0-9][.0-9]*)(cm|mm|pt|$)',size)
669         if m:
670             if m.group(2) == 'cm':
671                 this.__set('textheight',\
672                            float(m.group(1)) * 72.27/2.54, requester )
673             elif m.group(2) == 'mm':
674                 this.__set('textheight',\
675                            float(m.group(1)) * 72.27/25.4, requester )
676             elif m.group(2) == 'pt':
677                 this.__set('textheight', float(m.group(1)), requester )
678             elif m.group(2) == '':
679                 this.__set('textheight', float(m.group(1)), requester )
680             else:
681                 sys.exit('ExitBadHeight', m.group(2))
682         else:           
683             sys.exit('ExitBadHeight', size)
684
685     #
686     # setLineWidth
687     #
688     def setLineWidth(this,size,requester):
689         """
690         Set linewidth propery
691         """
692
693         m=re.match('([0-9][.0-9]*)(cm|mm|pt|$)',size)
694         if m:
695             if m.group(2) == 'cm':
696                 this.__set('linewidth', \
697                 float(m.group(1)) * 72.27/2.54, requester )
698             elif m.group(2) == 'mm':
699                 this.__set('linewidth', \
700                 float(m.group(1)) * 72.27/25.4, requester )
701             elif m.group(2) == 'pt':
702                 this.__set('linewidth', float(m.group(1)), requester )
703             elif m.group(2) == '':
704                 this.__set('linewidth', float(m.group(1)), requester )
705             else:
706                 sys.exit('ExitBadWidth', m.group(2))
707         else:           
708             sys.stderr.write ('ly2dvi: warning: ignoring linewidth: ' + size + '\n')
709
710     #
711     # setOrientation
712     #
713     def setOrientation(this,orient,requester):
714         """
715         Set orientation property
716         """
717
718         if orient == 'landscape' or orient == 'portrait':
719             this.__set('orientation', orient, requester )
720         else:
721             sys.exit('ExitBadOrient', orient)
722
723     #
724     # setLanguage
725     #
726     def setLanguage(this,lang,requester):
727         """
728         Set language property
729         """
730
731         this.__set('language', '\\usepackage[%s]{babel}' % (lang), requester )
732
733     #
734     # setInclude
735     #
736     def setInclude(this,inc, requester):
737         """
738         Append an include path
739         """
740
741         tmp = this.get('include')
742         tmp.append(inc)
743         this.__set('include', tmp, requester )
744
745     #
746     # setDebug
747     #
748     def setDebug(this,value,requester):
749         """
750         Set or Clear debug flag
751         """
752
753         if int(value) == 1:
754             this.__set('debug',1,requester)
755         else:
756             this.__set('debug',0,requester)
757
758     #
759     # setKeeplilypond
760     #
761     def setKeeplilypond(this, value, requester):        
762         """
763         Set or Clear keeplilypond flag
764         """
765
766         if int(value) == 1:
767             this.__set('keeplilypond',1,requester)
768         else:
769             this.__set('keeplilypond',0,requester)
770
771     #
772     # setKeeply2dvi
773     #
774     def setKeeply2dvi(this, value, requester):  
775         """
776         Set or Clear keeply2dvi flag
777         """
778
779         if int(value) == 1:
780             this.__set('keeply2dvi',1,requester)
781         else:
782             this.__set('keeply2dvi',0,requester)
783
784     #
785     # setNonumber 
786     #
787     def setNonumber(this, value, requester):    
788         """
789         Set nonumber flag
790         """
791
792         if int(value) == 1:
793             this.__set('pagenumber','\\pagestyle{empty}',requester)
794         else:
795             this.__set('pagenumber','%',requester)
796
797     #
798     # setSeparate
799     #
800     def setSeparate(this, value, requester):    
801         """
802         Set or Clear separate flag
803         """
804
805         if int(value) == 1:
806             this.__set('separate',1,requester)
807         else:
808             this.__set('separate',0,requester)
809
810     #
811     # Set output directory name
812     #
813     def setOutput(this,out,requester):
814         this.__set('output',out,requester)
815
816     #
817     # Set latex header name
818     #
819     def setHeader(this,head, requester):
820         this.__set('header','\\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()