]> git.donarmstrong.com Git - lilypond.git/blob - scripts/ly2dvi.py
d6858579cc0688128f72597fd241436177716e55
[lilypond.git] / scripts / ly2dvi.py
1 #!@PYTHON@
2
3 # Run lilypond, latex, dvips.
4 #
5 # This is the third incarnation of ly2dvi.
6 #
7 # Earlier incarnations of ly2dvi were written by
8 # Jeffrey B. Reed<daboys@austin.rr.com> (Python version)
9 # Jan Arne Fagertun <Jan.A.Fagertun@@energy.sintef.no> (Bourne shell script)
10 #
11
12 #
13 # Note: gettext work best if we use ' for docstrings and "
14 #       for gettextable strings.
15 #       --> DO NOT USE """ for docstrings.
16
17 '''
18 TODO:
19
20   * figure out which set of command line options should make ly2dvi:
21
22       na: create tex only?  
23       na: create latex only? 
24       na: create tex and latex
25       default: create dvi only
26       na: create tex, latex and dvi
27       -P: create dvi and ps
28       na: * create ps only
29
30      etc.
31
32      for foo.ly, rename ly2dvi.dir to out-ly2dvi, foo.ly2dvi, foo.dir ?
33      
34   * move versatile taglines, 
35   
36      \header {
37         beginfooter=\mutopiaPD
38         endfooter=\tagline  -> 'lily was here <version>'
39      }
40
41      lilytagline (->lily was here), usertagline, copyright etc.
42
43   * head/header tagline/endfooter
44
45   * dvi from lilypond .tex output?  This is hairy, because we create dvi
46     from lilypond .tex *and* header output.
47
48   * multiple \score blocks?
49   
50 '''
51
52
53 import os
54 import stat
55 import string
56 import re
57 import getopt
58 import sys
59 import shutil
60 import __main__
61 import operator
62 import tempfile
63 import traceback
64
65
66 ################################################################
67 # lilylib.py -- options and stuff
68
69 # source file of the GNU LilyPond music typesetter
70
71 # Handle bug in Python 1.6-2.1
72 #
73 # there are recursion limits for some patterns in Python 1.6 til 2.1. 
74 # fix this by importing pre instead. Fix by Mats.
75
76 # todo: should check Python version first.
77 try:
78         import pre
79         re = pre
80         del pre
81 except ImportError:
82         import re
83
84 # Attempt to fix problems with limited stack size set by Python!
85 # Sets unlimited stack size. Note that the resource module only
86 # is available on UNIX.
87 try:
88        import resource
89        resource.setrlimit (resource.RLIMIT_STACK, (-1, -1))
90 except:
91        pass
92
93 try:
94         import gettext
95         gettext.bindtextdomain ('lilypond', localedir)
96         gettext.textdomain ('lilypond')
97         _ = gettext.gettext
98 except:
99         def _ (s):
100                 return s
101
102 program_version = '@TOPLEVEL_VERSION@'
103 if program_version == '@' + 'TOPLEVEL_VERSION' + '@':
104         program_version = '1.5.54'
105
106 def identify ():
107         sys.stdout.write ('%s (GNU LilyPond) %s\n' % (program_name, program_version))
108
109 def warranty ():
110         identify ()
111         sys.stdout.write ('\n')
112         sys.stdout.write (_ ('Copyright (c) %s by' % ' 2001--2002'))
113         sys.stdout.write ('\n')
114         sys.stdout.write ('  Han-Wen Nienhuys')
115         sys.stdout.write ('  Jan Nieuwenhuizen')
116         sys.stdout.write ('\n')
117         sys.stdout.write (_ (r'''
118 Distributed under terms of the GNU General Public License. It comes with
119 NO WARRANTY.'''))
120         sys.stdout.write ('\n')
121
122 def progress (s):
123         errorport.write (s + '\n')
124
125 def warning (s):
126         progress (_ ("warning: ") + s)
127
128 def user_error (s, e=1):
129         errorport.write (program_name + ":" + _ ("error: ") + s + '\n')
130         sys.exit (e)
131         
132 def error (s):
133         '''Report the error S.  Exit by raising an exception. Please
134         do not abuse by trying to catch this error. If you do not want
135         a stack trace, write to the output directly.
136
137         RETURN VALUE
138
139         None
140         
141         '''
142         
143         progress (_ ("error: ") + s)
144         raise _ ("Exiting ... ")
145
146 def getopt_args (opts):
147         '''Construct arguments (LONG, SHORT) for getopt from  list of options.'''
148         short = ''
149         long = []
150         for o in opts:
151                 if o[1]:
152                         short = short + o[1]
153                         if o[0]:
154                                 short = short + ':'
155                 if o[2]:
156                         l = o[2]
157                         if o[0]:
158                                 l = l + '='
159                         long.append (l)
160         return (short, long)
161
162 def option_help_str (o):
163         '''Transform one option description (4-tuple ) into neatly formatted string'''
164         sh = '  '       
165         if o[1]:
166                 sh = '-%s' % o[1]
167
168         sep = ' '
169         if o[1] and o[2]:
170                 sep = ','
171                 
172         long = ''
173         if o[2]:
174                 long= '--%s' % o[2]
175
176         arg = ''
177         if o[0]:
178                 if o[2]:
179                         arg = '='
180                 arg = arg + o[0]
181         return '  ' + sh + sep + long + arg
182
183
184 def options_help_str (opts):
185         '''Convert a list of options into a neatly formatted string'''
186         w = 0
187         strs =[]
188         helps = []
189
190         for o in opts:
191                 s = option_help_str (o)
192                 strs.append ((s, o[3]))
193                 if len (s) > w:
194                         w = len (s)
195
196         str = ''
197         for s in strs:
198                 str = str + '%s%s%s\n' % (s[0], ' ' * (w - len(s[0])  + 3), s[1])
199         return str
200
201 def help ():
202         ls = [(_ ("Usage: %s [OPTION]... FILE") % program_name),
203                 ('\n\n'),
204                 (help_summary),
205                 ('\n\n'),
206                 (_ ("Options:")),
207                 ('\n'),
208                 (options_help_str (option_definitions)),
209                 ('\n\n'),
210                 (_ ("Report bugs to %s") % 'bug-lilypond@gnu.org'),
211                 ('\n')]
212         map (sys.stdout.write, ls)
213         
214 def setup_temp ():
215         """
216         Create a temporary directory, and return its name. 
217         """
218         global temp_dir
219         if not keep_temp_dir_p:
220                 temp_dir = tempfile.mktemp (program_name)
221         try:
222                 os.mkdir (temp_dir, 0777)
223         except OSError:
224                 pass
225
226         return temp_dir
227
228
229 def system (cmd, ignore_error = 0, quiet =0):
230         """Run CMD. If IGNORE_ERROR is set, don't complain when CMD returns non zero.
231
232         RETURN VALUE
233
234         Exit status of CMD
235         """
236         
237         if verbose_p:
238                 progress (_ ("Invoking `%s\'") % cmd)
239
240         st = os.system (cmd)
241         if st:
242                 name = re.match ('[ \t]*([^ \t]*)', cmd).group (1)
243                 msg = name + ': ' + _ ("command exited with value %d") % st
244                 if ignore_error:
245                         if not quiet:
246                                 warning (msg + ' ' + _ ("(ignored)") + ' ')
247                 else:
248                         error (msg)
249
250         return st
251
252
253 def cleanup_temp ():
254         if not keep_temp_dir_p:
255                 if verbose_p:
256                         progress (_ ("Cleaning %s...") % temp_dir)
257                 shutil.rmtree (temp_dir)
258
259
260 def strip_extension (f, ext):
261         (p, e) = os.path.splitext (f)
262         if e == ext:
263                 e = ''
264         return p + e
265
266
267 def cp_to_dir (pattern, dir):
268         "Copy files matching re PATTERN from cwd to DIR"
269         # Duh.  Python style portable: cp *.EXT OUTDIR
270         # system ('cp *.%s %s' % (ext, outdir), 1)
271         files = filter (lambda x, p=pattern: re.match (p, x), os.listdir ('.'))
272         map (lambda x, d=dir: shutil.copy2 (x, os.path.join (d, x)), files)
273
274
275 # Python < 1.5.2 compatibility
276 #
277 # On most platforms, this is equivalent to
278 #`normpath(join(os.getcwd()), PATH)'.  *Added in Python version 1.5.2*
279 if os.path.__dict__.has_key ('abspath'):
280         abspath = os.path.abspath
281 else:
282         def abspath (path):
283                 return os.path.normpath (os.path.join (os.getcwd (), path))
284
285 if os.__dict__.has_key ('makedirs'):
286         makedirs = os.makedirs
287 else:
288         def makedirs (dir, mode=0777):
289                 system ('mkdir -p %s' % dir)
290
291
292 def mkdir_p (dir, mode=0777):
293         if not os.path.isdir (dir):
294                 makedirs (dir, mode)
295
296
297 # if set, LILYPONDPREFIX must take prevalence
298 # if datadir is not set, we're doing a build and LILYPONDPREFIX 
299 datadir = '@local_lilypond_datadir@'
300
301 if os.environ.has_key ('LILYPONDPREFIX') :
302         datadir = os.environ['LILYPONDPREFIX']
303 else:
304         datadir = '@local_lilypond_datadir@'
305
306
307 while datadir[-1] == os.sep:
308         datadir= datadir[:-1]
309
310 sys.path.insert (0, os.path.join (datadir, 'python'))
311
312 ################################################################
313 # END Library
314
315
316 program_name = 'ly2dvi'
317
318 original_dir = os.getcwd ()
319 temp_dir = os.path.join (original_dir,  '%s.dir' % program_name)
320 errorport = sys.stderr
321 keep_temp_dir_p = 0
322 verbose_p = 0
323 preview_p = 0
324 preview_resolution = 90
325 pseudo_filter_p = 0
326
327 help_summary = _ ("Generate .dvi with LaTeX for LilyPond")
328
329 option_definitions = [
330         ('', 'd', 'dependencies', _ ("write Makefile dependencies for every input file")),
331         ('', 'h', 'help', _ ("this help")),
332         (_ ("DIR"), 'I', 'include', _ ("add DIR to LilyPond's search path")),
333         ('', 'k', 'keep', _ ("keep all output, and name the directory %s.dir") % program_name),
334         ('', '', 'no-lily', _ ("don't run LilyPond")),
335         ('', 'm', 'no-paper', _ ("produce MIDI output only")),
336         (_ ("FILE"), 'o', 'output', _ ("write ouput to FILE")),
337         (_ ("FILE"), 'f', 'find-pfa', _ ("find pfa fonts used in FILE")),
338         # why capital P?
339         ('', '', 'preview', _("Make a picture of the first system.")),
340         (_ ('RES'), '', 'preview-resolution', _("Set the resolution of the preview to RES.")),
341         ('', 'P', 'postscript', _ ("generate PostScript output")),
342         ('', 'p', 'pdf', _ ("generate PDF output")),    
343         (_ ("KEY=VAL"), 's', 'set', _ ("change global setting KEY to VAL")),
344         ('', 'V', 'verbose', _ ("verbose")),
345         ('', 'v', 'version', _ ("print version number")),
346         ('', 'w', 'warranty', _ ("show warranty and copyright")),
347         ]
348
349 layout_fields = ['dedication', 'title', 'subtitle', 'subsubtitle',
350           'footer', 'head', 'composer', 'arranger', 'instrument',
351           'opus', 'piece', 'metre', 'meter', 'poet', 'texttranslator']
352
353
354 # init to empty; values here take precedence over values in the file
355
356 ## TODO: change name.
357 extra_init = {
358         'language' : [],
359         'latexheaders' : [],
360         'latexpackages' :  ['geometry'],
361         'latexoptions' : [],
362         'papersize' : [],
363         'pagenumber' : [1],
364         'textheight' : [], 
365         'linewidth' : [],
366         'orientation' : [],
367         'unit' : ['pt'],
368 }
369
370 extra_fields = extra_init.keys ()
371 fields = layout_fields + extra_fields
372
373 include_path = ['.']
374 lily_p = 1
375 paper_p = 1
376
377 output_name = ''
378
379 # Output formats that ly2dvi should create
380 targets = ['DVI', 'LATEX', 'MIDI', 'TEX']
381
382 track_dependencies_p = 0
383 dependency_files = []
384
385
386
387 kpse = os.popen ('kpsexpand \$TEXMF').read()
388 kpse = re.sub('[ \t\n]+$','', kpse)
389 type1_paths = os.popen ('kpsewhich -expand-path=\$T1FONTS').read ()
390
391 environment = {
392         # TODO: * prevent multiple addition.
393         #       * clean TEXINPUTS, MFINPUTS, TFMFONTS,
394         #         as these take prevalence over $TEXMF
395         #         and thus may break tex run?
396         'TEXMF' : "{%s,%s}" % (datadir, kpse) ,
397         'GS_FONTPATH' : type1_paths,
398         'GS_LIB' : datadir + '/ps',
399 }
400
401 # tex needs lots of memory, more than it gets by default on Debian
402 non_path_environment = {
403         'extra_mem_top' : '1000000',
404         'extra_mem_bottom' : '1000000',
405         'pool_size' : '250000',
406 }
407
408 def setup_environment ():
409         # $TEXMF is special, previous value is already taken care of
410         if os.environ.has_key ('TEXMF'):
411                 del os.environ['TEXMF']
412  
413         for key in environment.keys ():
414                 val = environment[key]
415                 if os.environ.has_key (key):
416                         val = os.environ[key] + os.pathsep + val 
417                 os.environ[key] = val
418
419         for key in non_path_environment.keys ():
420                 val = non_path_environment[key]
421                 os.environ[key] = val
422
423 #what a name.
424 def set_setting (dict, key, val):
425         try:
426                 val = string.atoi (val)
427         except ValueError:
428                 #warning (_ ("invalid value: %s") % `val`)
429                 pass
430
431         if type(val) == type ('hoi'):
432                 try:
433                         val = string.atof (val)
434                 except ValueError:
435                         #warning (_ ("invalid value: %s") % `val`)
436                         pass
437
438         try:
439                 dict[key].append (val)
440         except KeyError:
441                 warning (_ ("no such setting: %s") % `key`)
442                 dict[key] = [val]
443
444
445 def print_environment ():
446         for (k,v) in os.environ.items ():
447                 sys.stderr.write ("%s=\"%s\"\n" % (k,v)) 
448
449 def quiet_system (cmd, name, ignore_error = 0):
450         if not verbose_p:
451                 progress ( _("Running %s...") % name)
452                 cmd = cmd + ' 1> /dev/null 2> /dev/null'
453         elif pseudo_filter_p:
454                 cmd = cmd + ' 1> /dev/null'
455
456         return system (cmd, ignore_error, quiet = 1)
457
458
459 def run_lilypond (files, dep_prefix):
460
461         opts = ''
462         opts = opts + ' ' + string.join (map (lambda x : '-I ' + x,
463                                               include_path))
464         if pseudo_filter_p:
465                 opts = opts + ' --output=lelie'
466         if paper_p:
467                 opts = opts + ' ' + string.join (map (lambda x : '-H ' + x,
468                                                       fields))
469         else:
470                 opts = opts + ' --no-paper'
471                 
472         if track_dependencies_p:
473                 opts = opts + " --dependencies"
474                 if dep_prefix:
475                         opts = opts + ' --dep-prefix=%s' % dep_prefix
476
477         fs = string.join (files)
478
479         if not verbose_p:
480                 # cmd = cmd + ' 1> /dev/null 2> /dev/null'
481                 progress ( _("Running %s...") % 'LilyPond')
482         else:
483                 opts = opts + ' --verbose'
484
485                 # for better debugging!
486                 print_environment ()
487
488         cmd = 'lilypond %s %s ' % (opts, fs)
489         if  verbose_p:
490                 progress ("Invoking `%s'"% cmd)
491         status = os.system (cmd)
492
493         signal = 0x0f & status
494         exit_status = status >> 8
495
496         # 2 == user interrupt.
497         if signal and  signal <> 2:
498                 error("\n\nLilyPond crashed (signal %d). Please submit a bugreport to bug-lilypond@gnu.org\n" % signal)
499
500         if status:
501                 error ("\n\nLilyPond failed on the input file. (exit status %d)\n" % exit_status)
502                 
503
504 def analyse_lilypond_output (filename, extra):
505         
506         # urg
507         '''Grep FILENAME for interesting stuff, and
508         put relevant info into EXTRA.'''
509         filename = filename+'.tex'
510         progress (_ ("Analyzing %s...") % filename)
511         s = open (filename).read ()
512
513         # search only the first 10k
514         s = s[:10240]
515         for x in extra_fields:
516                 m = re.search (r'\\def\\lilypondpaper%s{([^}]*)}'%x, s)
517                 if m:
518                         set_setting (extra, x, m.group (1))
519
520 def find_tex_files_for_base (base, extra):
521
522         """
523         Find the \header fields dumped from BASE.
524         """
525         
526         headerfiles = {}
527         for f in layout_fields:
528                 if os.path.exists (base + '.' + f):
529                         headerfiles[f] = base+'.'+f
530
531         if os.path.exists (base  +'.dep'):
532                 dependency_files.append (base + '.dep')
533
534         for f in extra_fields:
535                 if os.path.exists (base + '.' + f):
536                         extra[f].append (open (base + '.' + f).read ())
537         
538         return (base  +'.tex',headerfiles)
539          
540
541 def find_tex_files (files, extra):
542         """
543         Find all .tex files whose prefixes start with some name in FILES. 
544
545         """
546         
547         tfiles = []
548         
549         for f in files:
550                 x = 0
551                 while 1:
552                         fname = os.path.basename (f)
553                         fname = strip_extension (fname, '.ly')
554                         if x:
555                                 fname = fname + '-%d' % x
556
557                         if os.path.exists (fname + '.tex'):
558                                 tfiles.append (find_tex_files_for_base (fname, extra))
559                                 analyse_lilypond_output (fname, extra)
560                         else:
561                                 break
562
563                         x = x + 1
564         if not x:
565                 fstr = string.join (files, ', ')
566                 warning (_ ("no LilyPond output found for %s") % fstr)
567         return tfiles
568
569 def one_latex_definition (defn, first):
570         s = '\n'
571         for (k,v) in defn[1].items ():
572                 val = open (v).read ()
573                 if (string.strip (val)):
574                         s = s + r'''\def\lilypond%s{%s}''' % (k, val)
575                 else:
576                         s = s + r'''\let\lilypond%s\relax''' % k
577                 s = s + '\n'
578
579         if first:
580                 s = s + '\\def\\mustmakelilypondtitle{}\n'
581         else:
582                 s = s + '\\def\\mustmakelilypondpiecetitle{}\n'
583                 
584         s = s + '\\input %s\n' % defn[0] # The final \n seems important here. It ensures that the footers and taglines end up on the right page.
585         return s
586
587
588 ly_paper_to_latexpaper =  {
589         'a4' : 'a4paper',
590         'letter' : 'letterpaper', 
591 }
592
593 #TODO: should set textheight (enlarge) depending on papersize. 
594 def global_latex_preamble (extra):
595         '''construct preamble from EXTRA,'''
596         s = ""
597         s = s + '% generation tag\n'
598
599         options = ''
600
601
602         if extra['papersize']:
603                 try:
604                         options = ly_paper_to_latexpaper[extra['papersize'][0]]
605                 except KeyError:
606                         warning (_ ("invalid value: %s") % `extra['papersize'][0]`)
607                         pass
608
609         if extra['latexoptions']:
610                 options = options + ',' + extra['latexoptions'][-1]
611
612         s = s + '\\documentclass[%s]{article}\n' % options
613
614         if extra['language']:
615                 s = s + r'\usepackage[%s]{babel}' % extra['language'][-1] + '\n'
616
617
618         s = s + '\\usepackage{%s}\n' \
619                 % string.join (extra['latexpackages'], ',')
620
621         if extra['latexheaders']:
622                 s = s + '\\include{%s}\n' \
623                         % string.join (extra['latexheaders'], '}\n\\include{')
624
625         unit = extra['unit'][-1]
626
627         textheight = ''
628         if extra['textheight']:
629                 textheight = ',textheight=%f%s' % (extra['textheight'][0], unit)
630
631         orientation = 'portrait'
632         if extra['orientation']:
633                 orientation = extra['orientation'][0]
634
635         # set sane geometry width (a4-width) for linewidth = -1.
636         maxlw = max (extra['linewidth'] + [-1])
637         if maxlw < 0:
638                 # who the hell is 597 ?
639                 linewidth = '597pt'
640         else:
641                 linewidth = '%d%s' % (maxlw, unit)
642         s = s + '\geometry{width=%s%s,headheight=2mm,footskip=2mm,%s}\n' % (linewidth, textheight, orientation)
643
644         if extra['latexoptions']:
645                 s = s + '\geometry{twosideshift=4mm}\n'
646
647         s = s + r'''
648 \usepackage[latin1]{inputenc}
649 \input{titledefs}
650 '''
651         
652         if extra['pagenumber'] and extra['pagenumber'][-1] and extra['pagenumber'][-1] != 'no':
653                 s = s + '\setcounter{page}{%d}\n' % (extra['pagenumber'][-1])
654                 s = s + '\\pagestyle{plain}\n'
655         else:
656                 s = s + '\\pagestyle{empty}\n'
657
658
659         return s
660
661         
662 def global_latex_definition (tfiles, extra):
663         '''construct preamble from EXTRA, dump Latex stuff for each
664 lily output file in TFILES after that, and return the Latex file constructed.  '''
665
666         
667         s = global_latex_preamble (extra) + '\\begin{document}\n'
668         s = s + '\\thispagestyle{firstpage}\n'
669
670         first = 1
671         for t in tfiles:
672                 s = s + one_latex_definition (t, first)
673                 first = 0
674
675
676         s = s + '\\thispagestyle{lastpage}\n'
677         s = s + '\\end{document}'
678
679         return s
680
681 def run_latex (files, outbase, extra):
682
683         """Construct latex file, for FILES and EXTRA, dump it into
684 OUTBASE.latex. Run LaTeX on it.
685
686 RETURN VALUE
687
688 None
689         """
690
691         latex_fn = outbase + '.latex'
692         
693         wfs = find_tex_files (files, extra)
694         s = global_latex_definition (wfs, extra)
695
696         f = open (latex_fn, 'w')
697         f.write (s)
698         f.close ()
699
700         cmd = 'latex \\\\nonstopmode \\\\input %s' % latex_fn
701         status = quiet_system (cmd, 'LaTeX', ignore_error = 1)
702
703         signal = 0xf & status
704         exit_stat = status >> 8
705
706         if exit_stat:
707                 logstr = open (outbase + '.log').read()
708                 m = re.search ("\n!", logstr)
709                 start = m.start (0)
710                 logstr = logstr[start:start+200]
711                 
712                 sys.stderr.write(_("""LaTeX failed on the output file.
713 The error log is as follows:
714 %s...\n""" % logstr))
715                 raise 'LaTeX error'
716         
717         if preview_p:
718                 # make a preview by rendering only the 1st line.
719                 preview_fn = outbase + '.preview.tex'
720                 f = open(preview_fn, 'w')
721                 f.write (r'''
722 %s
723 \input lilyponddefs
724 \pagestyle{empty}
725 \begin{document}
726 \def\interscoreline{\endinput}
727 \input %s
728 \end{document}
729 ''' % (global_latex_preamble (extra), outbase))
730
731                 f.close()
732                 cmd = 'latex \\\\nonstopmode \\\\input %s' % preview_fn
733                 quiet_system (cmd, "LaTeX for preview")
734         
735
736 def run_dvips (outbase, extra):
737
738
739         """Run dvips using the correct options taken from EXTRA,
740 leaving a PS file in OUTBASE.ps
741
742 RETURN VALUE
743
744 None.
745 """
746         opts = ''
747         if extra['papersize']:
748                 opts = opts + ' -t%s' % extra['papersize'][0]
749
750         if extra['orientation'] and extra['orientation'][0] == 'landscape':
751                 opts = opts + ' -tlandscape'
752
753         if 'PDF' in targets:
754                 opts = opts + ' -Ppdf -G0 -u lilypond.map'
755                 
756         cmd = 'dvips %s -o%s %s' % (opts, outbase + '.ps', outbase + '.dvi')
757         quiet_system (cmd, 'dvips')
758
759         if preview_p:
760                 cmd = 'dvips -E -o%s %s' % ( outbase + '.preview.ps', outbase + '.preview.dvi')         
761                 quiet_system (cmd, 'dvips for preview')
762
763         if 'PDF' in targets:
764                 cmd = 'ps2pdf %s.ps %s.pdf' % (outbase , outbase)
765                 quiet_system (cmd, 'ps2pdf')
766                 
767 def get_bbox (filename):
768         # cut & paste 
769         system ('gs -sDEVICE=bbox -q  -sOutputFile=- -dNOPAUSE %s -c quit > %s.bbox 2>&1 ' % (filename, filename))
770
771         box = open (filename + '.bbox').read()
772         m = re.match ('^%%BoundingBox: ([0-9]+) ([0-9]+) ([0-9]+) ([0-9]+)', box)
773         gr = []
774         if m:
775                 gr = map (string.atoi, m.groups ())
776         
777         return gr
778
779 #
780 # cut & paste from lilypond-book.
781 #
782 def make_preview (name, extra):
783         bbox = get_bbox (name + '.preview.ps')
784         margin = 0
785         fo = open (name + '.trans.eps' , 'w')
786         fo.write ('%d %d translate\n' % (-bbox[0]+margin, -bbox[1]+margin))
787         fo.close ()
788         
789         x = (2* margin + bbox[2] - bbox[0]) * preview_resolution / 72.
790         y = (2* margin + bbox[3] - bbox[1]) * preview_resolution / 72.
791
792         cmd = r'''gs -g%dx%d -sDEVICE=pgm  -dTextAlphaBits=4 -dGraphicsAlphaBits=4  -q -sOutputFile=- -r%d -dNOPAUSE %s %s -c quit | pnmtopng > %s'''
793         
794         cmd = cmd % (x, y, preview_resolution, name + '.trans.eps', name + '.preview.ps',name + '.png')
795         quiet_system (cmd, 'gs')
796
797         try:
798                 status = system (cmd)
799         except:
800                 os.unlink (name + '.png')
801                 error ("Removing output file")
802
803
804
805 def generate_dependency_file (depfile, outname):
806         df = open (depfile, 'w')
807         df.write (outname + ':' )
808         
809         for d in dependency_files:
810                 s = open (d).read ()
811                 s = re.sub ('#[^\n]*\n', '', s)
812                 s = re.sub (r'\\\n', ' ', s)
813                 m = re.search ('.*:(.*)\n', s)
814
815                 # ugh. Different targets?
816                 if m:
817                         df.write ( m.group (1)  + ' ' )
818
819         df.write ('\n')
820         df.close ();
821
822 def find_file_in_path (path, name):
823         for d in string.split (path, os.pathsep):
824                 if name in os.listdir (d):
825                         return os.path.join (d, name)
826
827 # Added as functionality to ly2dvi, because ly2dvi may well need to do this
828 # in future too.
829 PS = '%!PS-Adobe'
830 def find_pfa_fonts (name):
831         s = open (name).read ()
832         if s[:len (PS)] != PS:
833                 # no ps header?
834                 errorport.write (_( "error: ") + _ ("not a PostScript file: `%s\'" % name))
835                 errorport.write ('\n')
836                 sys.exit (1)
837         here = 0
838         m = re.match ('.*?/(feta[-a-z0-9]+) +findfont', s[here:], re.DOTALL)
839         pfa = []
840         while m:
841                 here = m.end (1)
842                 pfa.append (m.group (1))
843                 m = re.match ('.*?/(feta[-a-z0-9]+) +findfont', s[here:], re.DOTALL)
844         return pfa
845
846         
847 (sh, long) = getopt_args (option_definitions)
848 try:
849         (options, files) = getopt.getopt(sys.argv[1:], sh, long)
850 except getopt.error, s:
851         errorport.write ('\n')
852         errorport.write (_ ("error: ") + _ ("getopt says: `%s\'" % s))
853         errorport.write ('\n')
854         errorport.write ('\n')
855         help ()
856         sys.exit (2)
857         
858 for opt in options:     
859         o = opt[0]
860         a = opt[1]
861
862         if 0:
863                 pass
864         elif o == '--help' or o == '-h':
865                 help ()
866                 sys.exit (0)
867         elif o == '--find-pfa' or o == '-f':
868                 fonts = map (lambda x: x + '.pfa', find_pfa_fonts (a))
869                 files = map (lambda x:
870                              find_file_in_path (os.environ['GS_FONTPATH'], x),
871                              fonts)
872                 print string.join (files, ' ')
873                 sys.exit (0)
874         elif o == '--include' or o == '-I':
875                 include_path.append (a)
876         elif o == '--postscript' or o == '-P':
877                 targets.append ('PS')
878         elif o == '--pdf' or o == '-p':
879                 targets.append ('PS')
880                 targets.append ('PDF')
881         elif o == '--keep' or o == '-k':
882                 keep_temp_dir_p = 1
883         elif o == '--no-lily':
884                 lily_p = 0
885         elif o == '--preview':
886                 preview_p = 1
887                 targets.append ('PNG')
888         elif o == '--preview-resolution':
889                 preview_resolution = string.atoi (a)
890         elif o == '--no-paper' or o == '-m':
891                 targets = ['MIDI'] 
892                 paper_p = 0
893         elif o == '--output' or o == '-o':
894                 output_name = a
895         elif o == '--set' or o == '-s':
896                 ss = string.split (a, '=')
897                 set_setting (extra_init, ss[0], ss[1])
898         elif o == '--dependencies' or o == '-d':
899                 track_dependencies_p = 1
900         elif o == '--verbose' or o == '-V':
901                 verbose_p = 1
902         elif o == '--version' or o == '-v':
903                 identify ()
904                 sys.exit (0)
905         elif o == '--warranty' or o == '-w':
906                 status = system ('lilypond -w', ignore_error = 1)
907                 if status:
908                         warranty ()
909
910                 sys.exit (0)
911
912 # Don't convert input files to abspath, rather prepend '.' to include
913 # path.
914 include_path.insert (0, '.')
915
916 # As a neat trick, add directory part of first input file
917 # to include path.  That way you can do without the clumsy -I in:
918
919 #    ly2dvi -I foe/bar/baz foo/bar/baz/baz.ly
920 if files and files[0] != '-' and os.path.dirname (files[0]) != '.':
921         include_path.append (os.path.dirname (files[0]))
922         
923 include_path = map (abspath, include_path)
924
925 if files and (files[0] == '-' or output_name == '-'):
926         if len (files) == 1:
927                 pseudo_filter_p = 1
928                 output_name = 'lelie'
929                 if verbose_p:
930                         progress (_ ("pseudo filter"))
931         else:
932                 help ()
933                 user_error (_ ("pseudo filter only for single input file."), 2)
934                 
935         
936 original_output = output_name
937
938 if files:
939         
940         # Ugh, maybe make a setup () function
941         files = map (lambda x: strip_extension (x, '.ly'), files)
942
943         # hmmm. Wish I'd 've written comments when I wrote this.
944         # now it looks complicated.
945         
946         (outdir, outbase) = ('','')
947         if not output_name:
948                 outbase = os.path.basename (files[0])
949                 outdir = abspath ('.')
950         elif output_name[-1] == os.sep:
951                 outdir = abspath (output_name)
952                 outbase = os.path.basename (files[0])
953         else:
954                 (outdir, outbase) = os.path.split (abspath (output_name))
955
956         for i in ('.dvi', '.latex', '.ly', '.ps', '.tex'):
957                 output_name = strip_extension (output_name, i)
958                 outbase = strip_extension (outbase, i)
959
960         for i in files[:] + [output_name]:
961                 if string.find (i, ' ') >= 0:
962                         user_error (_ ("filename should not contain spaces: `%s'") % i)
963                         
964         if os.path.dirname (output_name) != '.':
965                 dep_prefix = os.path.dirname (output_name)
966         else:
967                 dep_prefix = 0
968
969         reldir = os.path.dirname (output_name)
970         if outdir != '.' and (track_dependencies_p or targets):
971                 mkdir_p (outdir, 0777)
972
973         setup_environment ()
974         tmpdir = setup_temp ()
975
976         # to be sure, add tmpdir *in front* of inclusion path.
977         #os.environ['TEXINPUTS'] =  tmpdir + ':' + os.environ['TEXINPUTS']
978         os.chdir (tmpdir)
979         
980         if lily_p:
981                 try:
982                         run_lilypond (files, dep_prefix)
983                 except:
984                         # TODO: friendly message about LilyPond setup/failing?
985                         #
986                         # TODO: lilypond should fail with different
987                         # error codes for:
988                         #   - guile setup/startup failure
989                         #   - font setup failure
990                         #   - init.ly setup failure
991                         #   - parse error in .ly
992                         #   - unexpected: assert/core dump
993                         targets = []
994                         traceback.print_exc ()
995
996         # Our LilyPond pseudo filter always outputs to 'lelie'
997         # have subsequent stages and use 'lelie' output.
998         if pseudo_filter_p:
999                 files[0] = 'lelie'
1000                 
1001         if 'PNG' in targets and 'PS' not in targets:
1002                 targets.append ('PS')
1003         if 'PS' in targets and 'DVI' not in targets:
1004                 targets.append('DVI')
1005
1006         if 'DVI' in targets:
1007                 try:
1008                         run_latex (files, outbase, extra_init)
1009                         # unless: add --tex, or --latex?
1010                         targets.remove ('TEX')
1011                         targets.remove('LATEX')
1012                 except:
1013                         # TODO: friendly message about TeX/LaTeX setup,
1014                         # trying to run tex/latex by hand
1015                         if 'DVI' in targets:
1016                                 targets.remove ('DVI')
1017                         if 'PS' in targets:
1018                                 targets.remove ('PS')
1019                         traceback.print_exc ()
1020
1021         if 'PS' in targets:
1022                 try:
1023                         run_dvips (outbase, extra_init)
1024                 except: 
1025                         if 'PS' in targets:
1026                                 targets.remove ('PS')
1027                         traceback.print_exc ()
1028
1029         if 'PNG' in  targets:
1030                 make_preview (outbase, extra_init)
1031                 
1032         # add DEP to targets?
1033         if track_dependencies_p:
1034                 depfile = os.path.join (outdir, outbase + '.dep')
1035                 generate_dependency_file (depfile, depfile)
1036                 if os.path.isfile (depfile):
1037                         progress (_ ("dependencies output to `%s'...") %
1038                                   depfile)
1039
1040         if pseudo_filter_p:
1041                 main_target = 0
1042                 for i in 'PDF', 'PS', 'PNG', 'DVI', 'LATEX':
1043                         if i in targets:
1044                                 main_target = i
1045                                 break
1046
1047                 outname = outbase + '.' + string.lower (main_target)
1048                 if os.path.isfile (outname):
1049                         sys.stdout.write (open (outname).read ())
1050                 elif verbose_p:
1051                         warning (_ ("can't find file: `%s'") % outname)
1052                 targets = []
1053                 
1054         # Hmm, if this were a function, we could call it the except: clauses
1055         for i in targets:
1056                 ext = string.lower (i)
1057                 cp_to_dir ('.*\.%s$' % ext, outdir)
1058                 outname = outbase + '.' + string.lower (i)
1059                 abs = os.path.join (outdir, outname)
1060                 if reldir != '.':
1061                         outname = os.path.join (reldir, outname)
1062                 if os.path.isfile (abs):
1063                         progress (_ ("%s output to `%s'...") % (i, outname))
1064                 elif verbose_p:
1065                         warning (_ ("can't find file: `%s'") % outname)
1066                         
1067         os.chdir (original_dir)
1068         cleanup_temp ()
1069         
1070 else:
1071         help ()
1072         user_error (_ ("no files specified on command line."), 2)