]> git.donarmstrong.com Git - lilypond.git/blob - scripts/ly2dvi.py
23f38433b66b005336773eeb070e0c5039bd9dfa
[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('% Generated automatically 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                 
485         ly2dvi_t = t + \
486                    os.path.join(this.get('root'), 'tex' ) + \
487                    os.pathsep + os.path.join(this.get('root'), 'ps' )
488         # Don't add the magic `//' to TEXINPUTS
489         ly2dvi_t = re.sub ('//*', '/', ly2dvi_t)
490         os.environ['TEXINPUTS'] = ly2dvi_t
491
492         m = ''
493         if os.environ.has_key ('MFINPUTS'):
494                m = os.environ['MFINPUTS'] 
495         ly2dvi_m = m + os.pathsep + \
496                    os.path.join(this.get('root'), 'mf')
497         ly2dvi_m = re.sub ('//*', '/', ly2dvi_m)
498         # Don't add the magic `//' to MFINPUTS
499         os.environ['MFINPUTS'] = ly2dvi_m
500
501         if os.environ.has_key('TMP'):
502             this.__set('tmp',os.environ['TMP'],'environment')
503
504
505     def read_titledefs (this):
506         fd=this.get_texfile_path ('titledefs.tex')
507         mudefs=[]    
508
509         for line in fd.readlines():
510             m=re.match('\\\\newcommand\*{\\\\lilypond([\w]+)}',line)
511             if m:
512                 mudefs.append(m.group(1))
513         fd.close
514         this.__set('titledefs', mudefs, 'init')
515
516     #
517     # __set
518     #
519     def __set(this,var,value,requester):
520         """
521         All of the set methods call this to set a property.  If the value
522         was last set by a requestor of lesser priority the new value is
523         assigned, else the old value has priority and is unchanged.
524         """
525
526         if this.__overrideTable[requester] < this.__data[var][1]:
527             return 0
528         else:
529             this.__data[var] = [value, this.__overrideTable[requester]]
530
531     #
532     # get
533     #
534     def get(this,var):
535         """
536         All of the get methods call this to get a property value.  List
537         variable types are return by value to facilitate an append operation.
538         """
539
540         if var == 'include' or var == 'lilyOutputFiles':
541             return this.__data[var][0][0:]  # return a copy not a ref
542         else:
543             return this.__data[var][0]
544
545     #
546     # get_texfile_path
547     #
548     def get_texfile_path (this, var):
549         """
550         locate and open titledefs.tex file
551         """
552
553         if os.name == 'nt':
554             path = os.path.join(this.get('root'), 'tex', var)
555         else:
556             path =''
557             cmd =('kpsewhich tex %s %s' % (var,errorlog))
558             sys.stderr.write ('executing: %s'% cmd)
559             pipe = os.popen (cmd, 'r')
560             path = pipe.readline ()[:-1] # chop off \n
561             return_status =  pipe.close()
562             sys.stderr.write ('\n')
563             if return_status and not path:
564                 path = os.path.join(this.get('root'), 'tex', var)
565         fd = open(path, 'r')
566         return fd
567
568
569     #
570     # Read rc file
571     #
572     def rcfile(this):
573         """
574         Read initialization file(s)
575         """
576         varTable = [
577             #   name              set method
578             #   ----              ----------
579             ( 'DEBUG',          this.setDebug ),
580             ( 'DEPENDENCIES',   this.setDependencies ),
581             ( 'KEEPLILYPOND',   this.setKeeplilypond ),
582             ( 'KEEPLY2DVI',     this.setKeeply2dvi ),
583             ( 'LANGUAGE',       this.setLanguage ),
584             ( 'LATEXHF',        this.setHeader ),
585             ( 'LATEXPKG',       this.setPackages ),
586             ( 'LILYINCLUDE',    this.setInclude ),
587             ( 'LILYPONDPREFIX', this.setRoot ),
588             ( 'NONUMBER',       this.setNonumber ),
589             ( 'ORIENTATION',    this.setOrientation ),
590             ( 'OUTPUTDIR',      this.setOutput ),
591             ( 'PAPERSIZE',      this.setPaperZize ),
592             ( 'PHEIGHT',        this.setTextHeight ),
593             ( 'POSTSCRIPT',     this.setPostscript ),
594             ( 'PWIDTH',         this.setLineWidth ),
595             ( 'SEPARATE',       this.setSeparate ),
596             ( 'TMP',            this.setTmp ),
597             ]
598
599         if ( os.name == 'posix' ):
600             dotFilename='.lilyrc'
601         else: # Windows apps like edit choke on .lilyrc
602             dotFilename='_lilyrc'
603
604         for d in [os.path.join(this.get('root'),'ly'), \
605                   os.environ['HOME'], os.curdir ]:
606             file=os.path.join(d,dotFilename)
607             try:
608                 fd = open( file, 'r' )
609             except:
610                 continue
611             
612             for line in fd.readlines():
613                 if re.match('#.*',line):
614                     continue
615                 m=re.search('([\w]+)=(.*)',line)
616                 if m:
617                     for var in varTable:
618                         if m.group(1) == var[0]:
619                             var[1](m.group(2),'rcfile')
620                             break
621             fd.close
622
623     #
624     # setPaperZize
625     #
626     def setPaperZize(this,size,requester):
627         """
628         Set paper size properties
629         """
630
631         paperTable = [
632             # regex          width    height      name
633             # -----          -----    ------      ----
634             ( 'a0.*',        2389,    3381,    'a0paper' ),
635             ( 'a1$|a1p.*',   1690,    2389,    'a1paper' ),
636             ( 'a2.*',        1194,    1690,    'a2paper' ),
637             ( 'a3.*',        845,     1194,    'a3paper' ),
638             ( 'a4.*',        597,     845,     'a4paper' ),
639             ( 'a5.*',        423,     597,     'a5paper' ),
640             ( 'a6.*',        298,     423,     'a6paper' ),
641             ( 'a7.*',        211,     298,     'a7paper' ),
642             ( 'a8.*',        305,     211,     'a8paper' ),
643             ( 'a9.*',        105,     305,     'a9paper' ),
644             ( 'a10.*',       74,      105,     'a10paper' ),
645             ( 'b0.*',        2847,    4023,    'b0paper' ),
646             ( 'b1.*',        2012,    2847,    'b1paper' ),
647             ( 'b2.*',        1423,    2012,    'b2paper' ),
648             ( 'b3.*',        1006,    1423,    'b3paper' ),
649             ( 'b4.*',        712,     1006,    'b4paper' ),
650             ( 'b5.*',        503,     712,     'b5paper' ),
651             ( 'archA$',      650,     867,     'archApaper' ),
652             ( 'archB$',      867,     1301,    'archBpaper' ),
653             ( 'archC$',      1301,    1734,    'archCpaper' ),
654             ( 'archD$',      1734,    2602,    'archDpaper' ),
655             ( 'archE$',      2602,    3469,    'archEpaper' ),
656             ( 'flsa$|flse$', 614,     940,     'flsapaper' ),
657             ( 'halfletter$', 397,     614,     'halfletterpaper' ),
658             ( 'ledger$',     1229,    795,     'ledgerpaper' ),
659             ( 'legal$',      614,     1012,    'legalpaper' ),
660             ( 'letter$',     614,     795,     'letterpaper' ),
661             ( 'note$',       542,     723,     'notepaper' )
662             ]
663
664         found=0
665         for paper in paperTable:
666             if re.match(paper[0],size):
667                 found=1
668                 this.__set('papersize',paper[3],requester)
669                 break
670
671         if not found:
672             sys.exit('ExitBadPaper',size)
673
674     #
675     # setTextHeight
676     #
677     def setTextHeight(this,size,requester):
678         """
679         Set textheight property
680         """
681
682         m=re.match('([0-9][.0-9]*)(cm|mm|pt|$)',size)
683         if m:
684             if m.group(2) == 'cm':
685                 this.__set('textheight',\
686                            float(m.group(1)) * 72.27/2.54, requester )
687             elif m.group(2) == 'mm':
688                 this.__set('textheight',\
689                            float(m.group(1)) * 72.27/25.4, requester )
690             elif m.group(2) == 'pt':
691                 this.__set('textheight', float(m.group(1)), requester )
692             elif m.group(2) == '':
693                 this.__set('textheight', float(m.group(1)), requester )
694             else:
695                 sys.exit('ExitBadHeight', m.group(2))
696         else:           
697             sys.exit('ExitBadHeight', size)
698
699     #
700     # setLineWidth
701     #
702     def setLineWidth(this,size,requester):
703         """
704         Set linewidth propery
705         """
706
707         m=re.match('([0-9][.0-9]*)(cm|mm|pt|$)',size)
708         if m:
709             if m.group(2) == 'cm':
710                 this.__set('linewidth', \
711                 float(m.group(1)) * 72.27/2.54, requester )
712             elif m.group(2) == 'mm':
713                 this.__set('linewidth', \
714                 float(m.group(1)) * 72.27/25.4, requester )
715             elif m.group(2) == 'pt':
716                 this.__set('linewidth', float(m.group(1)), requester )
717             elif m.group(2) == '':
718                 this.__set('linewidth', float(m.group(1)), requester )
719             else:
720                 sys.exit('ExitBadWidth', m.group(2))
721         else:           
722             sys.stderr.write ('ly2dvi: warning: ignoring linewidth: ' + size + '\n')
723
724     #
725     # setOrientation
726     #
727     def setOrientation(this,orient,requester):
728         """
729         Set orientation property
730         """
731
732         if orient == 'landscape' or orient == 'portrait':
733             this.__set('orientation', orient, requester )
734         else:
735             sys.exit('ExitBadOrient', orient)
736
737     #
738     # setLanguage
739     #
740     def setLanguage(this,lang,requester):
741         """
742         Set language property
743         """
744
745         this.__set('language', '\\usepackage[%s]{babel}' % (lang), requester )
746
747     #
748     # setInclude
749     #
750     def setInclude(this,inc, requester):
751         """
752         Append an include path
753         """
754
755         tmp = this.get('include')
756         tmp.append(inc)
757         this.__set('include', tmp, requester )
758
759     #
760     # setDebug
761     #
762     def setDebug(this,value,requester):
763         """
764         Set or Clear debug flag
765         """
766
767         if int(value) == 1:
768             this.__set('debug',1,requester)
769         else:
770             this.__set('debug',0,requester)
771
772     #
773     # setKeeplilypond
774     #
775     def setKeeplilypond(this, value, requester):        
776         """
777         Set or Clear keeplilypond flag
778         """
779
780         if int(value) == 1:
781             this.__set('keeplilypond',1,requester)
782         else:
783             this.__set('keeplilypond',0,requester)
784
785     #
786     # setKeeply2dvi
787     #
788     def setKeeply2dvi(this, value, requester):  
789         """
790         Set or Clear keeply2dvi flag
791         """
792
793         if int(value) == 1:
794             this.__set('keeply2dvi',1,requester)
795         else:
796             this.__set('keeply2dvi',0,requester)
797
798     #
799     # setNonumber 
800     #
801     def setNonumber(this, value, requester):    
802         """
803         Set nonumber flag
804         """
805
806         if int(value) == 1:
807             this.__set('pagenumber','\\pagestyle{empty}',requester)
808         else:
809             this.__set('pagenumber','%',requester)
810
811     #
812     # setSeparate
813     #
814     def setSeparate(this, value, requester):    
815         """
816         Set or Clear separate flag
817         """
818
819         if int(value) == 1:
820             this.__set('separate',1,requester)
821         else:
822             this.__set('separate',0,requester)
823
824     #
825     # Set output directory name
826     #
827     def setOutput(this,out,requester):
828         this.__set('output',out,requester)
829
830     #
831     # Set latex header name
832     #
833     def setHeader(this,head, requester):
834         this.__set('header','\\input{' + head + '}'+this.get('header'),requester)
835
836     #
837     # Set latex package name
838     #
839     def setPackages(this,pkgs, requester):
840         this.__set('header','\\usepackage{' + pkgs + '}'+this.get('header'),requester)
841
842     #
843     # Set or Clear Dependencies flag to generate makefile dependencies
844     #
845     def setDependencies(this, value, requester):        
846         """
847         Set or Clear dependencies flag
848         """
849
850         if int(value) == 1:
851             this.__set('dependencies',1,requester)
852         else:
853             this.__set('dependencies',0,requester)
854
855     #
856     # Set tmp directory
857     #
858     def setTmp(this,dir, requester):    
859         this.__set('tmp',dir,requester)
860
861     #
862     # Set lilypond source file name
863     #
864     def setFilename(this,file, requester):      
865         this.__set('filename',file,requester)
866
867     #
868     # Set title commands
869     #
870     def setTitles(this,titles, requester):      
871         this.__set('titles',titles,requester)
872
873     #
874     # Set title commands
875     #
876     def addLilyOutputFiles(this,filelist,requester):
877         """
878         Add a to the lily output list
879         """
880
881         tmp = this.get('lilyOutputFiles')
882         tmp = tmp + filelist
883         this.__set('lilyOutputFiles',tmp,requester)
884
885     #
886     # Set/Clear postscript flag
887     #
888     def setPostscript(this,value,requester):
889         """
890         Set postscript flag
891         """
892
893         if int(value) == 1:
894             this.__set('postscript',1,requester)
895         else:
896             this.__set('postscript',0,requester)
897
898     #
899     # Set root
900     #
901     def setRoot(this,path, requester):  
902         """
903         Set LilyPond root directory
904         """
905
906         os.environ['LILYPONDPREFIX'] = path
907         if os.name == 'nt' or os.name == 'dos':
908             path = unc2dos(path);
909
910         this.__set('root',path,requester)
911         
912
913     #
914     # printProps
915     #
916     def printProps(this):
917         """
918         Print properties
919         """
920         
921         for key in this.__data.keys():
922             print "%s <%s>:<%s>" % (key,this.get(key),
923                                     this.__roverrideTable[this.__data[key][1]])
924
925 \f
926
927 #
928 # Misc functions
929 #
930
931 def getLilyopts():
932     inc = ''    
933     if len(Props.get('include')) > 0: 
934         inc = string.join (map (lambda x: '-I "%s"' % x, Props.get('include')))
935     dep=''
936     if Props.get('dependencies'):
937         dep=' --dependencies'
938     return inc + dep
939
940 def writeLilylog(file,contents):
941     if Props.get('keeplilypond'):
942         base, ext = os.path.splitext(file)
943         tempfile.template=base + "_li"
944         file=tempfile.mktemp('.log')
945         output = Props.get('output')
946         if output != '':
947             file = os.path.join( output, file )
948         try:
949             fd = open( file, 'w' )
950         except:
951             sys.exit('ExitNoWrite', file)
952         fd.write(contents)
953         fd.close()
954
955 def getTeXFile(contents):
956     texfiles=[]
957     for line in string.split(contents,'\n'):
958         m = re.search('paper output to (.+)\.\.\.', line)
959         if m:
960             texfiles.append(m.group(1))
961
962     if texfiles == []:
963         sys.exit('ExitNoTeXName')
964     else:
965         return texfiles
966
967 def getDepFiles (log):
968     files=[]
969     for line in string.split (log,'\n'):
970         m = re.search ("dependencies output to (.+)\.\.\.", line)
971         if m:
972             files.append (m.group (1))
973     return files
974
975 def unc2dos(path):
976     """
977     Convert a path of format //<drive>/this/that/the/other to
978     <drive>:\this\that\the\other
979     """
980     m=re.match('^//([A-Za-z])(/.*)$',path)
981     if m:
982         return m.group(1) + ':' + os.path.normpath(m.group(2))
983     
984     
985
986 def program_id ():
987     return 'ly2dvi (GNU LilyPond) ' + version;
988
989
990 def mailaddress():
991     try:
992         return os.environ['MAILADDRESS']
993     except KeyError:
994         return '(address unknown)'
995
996
997 def identify ():
998     sys.stderr.write (program_id () + '\n')
999
1000 def print_version ():
1001     sys.stdout.write (program_id () + '\n')
1002
1003 def help ():
1004     sys.stdout.write (
1005 """Usage: %s [OPTION]... [FILE]...
1006
1007 Generate dvi file from LilyPond source/output
1008
1009 Options:
1010   -D,--debug           increase verbosity
1011   -F,--headers=        name of additional LaTeX headers file
1012   -H,--Height=         set paper height (points) (see manual page)
1013   -I,--include=DIR     add DIR to LilyPond\'s search path
1014   -K,--keeplilypond    keep LilyPond output files
1015   -L,--landscape       set landscape orientation
1016   -N,--nonumber        switch off page numbering
1017   -O,--orientation=    set orientation (obsolete -- use -L instead)
1018   -P,--postscript      generate PostScript file
1019   -W,--Width=          set paper width (points) (see manual page)
1020   -M,--dependencies    tell LilyPond to make a dependencies file
1021   -h,--help            this help text
1022   -k,--keeply2dvi      keep ly2dvi output files
1023   -l,--language=       give LaTeX language (babel)
1024   -o,--outdir=         set output directory
1025      --output=         set output directory
1026   -p,--papersize=      give LaTeX papersize (eg. a4)
1027   -s,--separate        run all files separately through LaTeX
1028
1029 files may be (a mix of) input to or output from LilyPond(1)
1030 """ % name)
1031
1032 \f
1033
1034 #
1035 # main
1036 #
1037
1038 def main():
1039     """Generate dvi files from LilyPond source/output"""
1040
1041     infile = Input()
1042     outfile = TeXOutput()
1043     texInputFiles=[]
1044     tempfile.tempdir=""
1045
1046     (options, files) = getopt.getopt (sys.argv[1:],
1047                                       'DF:H:I:KLNPW:Mhkl:o:p:s',
1048                                       ['debug', 'headers=', 'Height=',
1049                                        'include=', 'keeplilypond', 'landscape',
1050                                        'nonumber', 'Width=', 'dependencies',
1051                                        'help', 'keeply2dvi', 'language=',
1052                                        'outdir=', 'output=', 'version',
1053                                        'papersize=', 'separate', 'postscript'])
1054
1055     for opt in options:
1056         o = opt[0]
1057         a = opt[1]
1058         if o == '--debug' or o == '-D':
1059             Props.setDebug(1,'commandline')
1060         elif o == '--headers' or o == '-F':
1061             Props.setHeader(a,'commandline')
1062         elif o == '--include' or o == '-I':
1063             Props.setInclude(a,'commandline')
1064         elif o == '--Height' or o == '-H':
1065             Props.setTextHeight(a,'commandline')
1066         elif o == '--keeplilypond' or o == '-K':
1067             Props.setKeeplilypond(1,'commandline')
1068         elif o == '--landscape' or o == '-L':
1069             Props.setOrientation('landscape','commandline')
1070         elif o == '--nonumber' or o == '-N':
1071             Props.setNonumber(1,'commandline')
1072         elif o == '--Width' or o == '-W':
1073             Props.setLineWidth(a,'commandline')
1074         elif o == '--dependencies' or o == '-M':
1075             Props.setDependencies(1,'commandline')
1076         elif o == '--help' or o == '-h':
1077             help()
1078             sys.exit (0)
1079         elif o == '--keeply2dvi' or o == '-k':
1080             Props.setKeeply2dvi(1,'commandline')
1081         elif o == '--language' or o == '-l':
1082             Props.setLanguage(a,'commandline')
1083         elif o == '--outdir' or o == '-o' or o == '--output':
1084             Props.setOutput(a,'commandline')
1085         elif o == '--papersize' or o == '-p':
1086             Props.setPaperZize(a,'commandline')
1087         elif o == '--separate' or o == '-s':
1088             Props.setSeparate(1,'commandline')
1089         elif o == '--postscript' or o == '-P':
1090             Props.setPostscript(1,'commandline')
1091         elif o == '--version':
1092             print_version ()
1093             return 0
1094         else:
1095             print o
1096             raise getopt.error
1097             
1098     identify()
1099     Props.read_titledefs ()
1100     
1101     if len(files):
1102         for file in files:
1103             infile.open(file)
1104             type = infile.type()
1105             infile.close()
1106             if type == 'source':
1107                 if os.environ.has_key('OS') and \
1108                    os.environ['OS'] == 'Windows_95':
1109                     cmd = 'ash -c "lilypond %s %s 2>&1"' %(getLilyopts(), file)
1110                 else:
1111                     cmd = 'lilypond %s %s 2>&1' % (getLilyopts(), file)
1112                 sys.stderr.write ('executing: %s\n'% cmd)
1113                 
1114                 fd = os.popen(cmd , 'r')
1115                 log = ''
1116                 
1117                 s = fd.readline()
1118                 while len(s) > 0:
1119                         sys.stderr.write (s)
1120                         sys.stderr.flush ()
1121                         log = log + s
1122                         s = fd.readline ()
1123                 if 0:
1124                         s = fd.read (1)
1125                         while len(s) > 0:
1126                                 sys.stderr.write (s)
1127                                 sys.stderr.flush ()
1128                                 s = fd.read (1)                 
1129                         log = log + s
1130                 stat = fd.close()
1131                 if stat:
1132                     sys.exit('ExitBadLily', cmd )
1133                 texFiles=getTeXFile(log)
1134                 depFiles=getDepFiles (log)
1135                 writeLilylog(file,log)
1136                 Props.addLilyOutputFiles(texFiles,'program')
1137                 texInputFiles = texInputFiles + texFiles
1138             else:
1139                 texInputFiles.append(file)
1140
1141         firstfile=1
1142         for file in texInputFiles:
1143             infile.open(file)
1144             infile.setVars() # first pass set variables
1145             infile.close()
1146             if Props.get('debug'):
1147                 Props.printProps()
1148             if firstfile:
1149                 outfile.start(file)  # allow for specified name
1150             else:
1151                 outfile.next()
1152             outfile.write("""\
1153 \\input{%s}
1154 """ % (file))
1155             if Props.get('separate'):
1156                 outfile.end()
1157             else:
1158                 firstfile=0
1159         if not Props.get('separate'):
1160             outfile.end()
1161
1162         # --outdir mess
1163         if Props.get ('output'):
1164             outdir=Props.get ('output')
1165             for i in depFiles:
1166                 text=open (i).read ()
1167                 # ugh, should use lilypond -o DIR/foo.tex
1168                 # or --dep-prefix to fix dependencies
1169                 text=re.sub ('\n([^:]*).tex', '\n' + outdir + '/\\1.dvi', text)
1170                 text=re.sub (' ([^:]*).tex', ' ' + outdir + '/\\1.dvi', text)
1171                 open (os.path.join (outdir, i), 'w').write (text)
1172                 os.remove (i)
1173
1174     else:
1175         help()
1176         sys.exit('ExitBadArgs','No files specified')
1177
1178 #
1179 # Exit values
1180 #
1181 ExitTable = {
1182     'ExitInterupt'         : ['Ouch!', 1 ],
1183     'ExitBadArgs'          : ['Wrong number of arguments', 2 ],
1184     'ExitNotFound'         : ['File not found', 3 ],
1185     'ExitBadPaper'         : ['Unknown papersize', 4 ],
1186     'ExitBadHeight'        : ['Invalid Height specification', 5 ],
1187     'ExitBadWidth'         : ['Invalid Width specification', 6 ],
1188     'ExitBadOrient'        : ['Invalid Orientation specification', 7 ],
1189     'ExitNoWrite'          : ['Permission denied', 8 ],
1190     'ExitNoTeXName'        : ['Hmm, I could not find an output file name', 9 ],
1191     'ExitBadLily'          : ['LilyPond failed', 10 ],
1192     'ExitBadLatex'         : ['Latex failed', 11 ],
1193     'ExitBadPostscript'    : ['Postscript failed', 12 ],
1194     'ExitUnknown'          : ['Unknown Exit Code', 20 ],
1195     }
1196
1197 def cleanup():
1198     lilyfiles = []
1199     tmpfiles = []
1200     if not Props.get('keeplilypond'):
1201         lilyfiles = Props.get('lilyOutputFiles')
1202     if not Props.get('keeply2dvi'):
1203         tmpfiles = glob.glob('*_ly[0-9]*.*')
1204     for file in lilyfiles + tmpfiles:
1205         if os.path.isfile(file):
1206             os.remove(file)
1207
1208
1209 Props = Properties()
1210
1211 try:
1212     main()
1213
1214 except KeyboardInterrupt:
1215     print ExitTable['ExitInterupt'][0]
1216     cleanup()
1217     sys.exit(ExitTable['ExitInterupt'][1])
1218
1219 except SystemExit, errno:
1220     if ExitTable.has_key(errno.args[0]):
1221         msg = ExitTable[errno.args[0]]
1222     else:
1223         msg = ExitTable['ExitUnknown']
1224     if len(errno.args) > 1:  
1225         sys.stderr.write( '%s: %s: %s\n' % (name, msg[0], errno.args[1]))
1226     else:
1227         sys.stderr.write( '%s %s\n' % (name, msg[0]))
1228     if Props.get('debug'):
1229         Props.printProps()
1230     cleanup()
1231     sys.exit(msg[1])
1232 else:
1233     cleanup()